From 9bab4ef746a380ca13d7ba0e611a6a30116c21aa Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 6 Apr 2020 18:36:22 +0200 Subject: [PATCH 01/36] Move ownerships of home, dev tools and discover (#62612) * move ownership * fix es-ui ownership --- .github/CODEOWNERS | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index da85fb986ae01..3ae01b079d37c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -15,18 +15,20 @@ /src/legacy/core_plugins/metrics/ @elastic/kibana-app /src/legacy/core_plugins/vis_type_vislib/ @elastic/kibana-app /src/legacy/core_plugins/vis_type_xy/ @elastic/kibana-app -# Exclude tutorials folder for now because they are not owned by Kibana app and most will move out soon -/src/plugins/home/public @elastic/kibana-app -/src/plugins/home/server/*.ts @elastic/kibana-app -/src/plugins/home/server/services/ @elastic/kibana-app -# Exclude tutorial resources folder for now because they are not owned by Kibana app and most will move out soon -/src/legacy/core_plugins/kibana/public/home/*.ts @elastic/kibana-app -/src/legacy/core_plugins/kibana/public/home/*.scss @elastic/kibana-app -/src/legacy/core_plugins/kibana/public/home/np_ready/ @elastic/kibana-app /src/plugins/kibana_legacy/ @elastic/kibana-app /src/plugins/timelion/ @elastic/kibana-app -/src/plugins/dev_tools/ @elastic/kibana-app /src/plugins/dashboard/ @elastic/kibana-app +/src/plugins/discover/ @elastic/kibana-app + +# Core UI +# Exclude tutorials folder for now because they are not owned by Kibana app and most will move out soon +/src/plugins/home/public @elastic/kibana-core-ui +/src/plugins/home/server/*.ts @elastic/kibana-core-ui +/src/plugins/home/server/services/ @elastic/kibana-core-ui +# Exclude tutorial resources folder for now because they are not owned by Kibana app and most will move out soon +/src/legacy/core_plugins/kibana/public/home/*.ts @elastic/kibana-core-ui +/src/legacy/core_plugins/kibana/public/home/*.scss @elastic/kibana-core-ui +/src/legacy/core_plugins/kibana/public/home/np_ready/ @elastic/kibana-core-ui # App Architecture /examples/url_generators_examples/ @elastic/kibana-app-arch @@ -175,6 +177,7 @@ **/*.scss @elastic/kibana-design # Elasticsearch UI +/src/plugins/dev_tools/ @elastic/es-ui /src/plugins/console/ @elastic/es-ui /src/plugins/es_ui_shared/ @elastic/es-ui /x-pack/legacy/plugins/cross_cluster_replication/ @elastic/es-ui From 5c8dda265636529c2ba54ed915dac88ff21fbd05 Mon Sep 17 00:00:00 2001 From: marshallmain <55718608+marshallmain@users.noreply.github.com> Date: Mon, 6 Apr 2020 12:41:49 -0400 Subject: [PATCH 02/36] [Endpoint] Add pipeline to generator that redirect alerts to alert index (#62512) * add ingest pipeline to generator script * make alert index name configurable * move pipeline name to constant * update setupOnly flag help text Co-authored-by: Elastic Machine --- .../endpoint/scripts/alert_mapping.json | 2375 +++++++++++++++++ .../{mapping.json => event_mapping.json} | 3 +- .../endpoint/scripts/resolver_generator.ts | 81 +- 3 files changed, 2445 insertions(+), 14 deletions(-) create mode 100644 x-pack/plugins/endpoint/scripts/alert_mapping.json rename x-pack/plugins/endpoint/scripts/{mapping.json => event_mapping.json} (99%) diff --git a/x-pack/plugins/endpoint/scripts/alert_mapping.json b/x-pack/plugins/endpoint/scripts/alert_mapping.json new file mode 100644 index 0000000000000..a21e48b4bc95f --- /dev/null +++ b/x-pack/plugins/endpoint/scripts/alert_mapping.json @@ -0,0 +1,2375 @@ +{ + "mappings": { + "_meta": { + "version": "1.5.0-dev" + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "mutable_state": { + "properties": { + "triage_status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "dll": { + "properties": { + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "compile_time": { + "type": "date" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "malware_classification": { + "properties": { + "features": { + "properties": { + "data": { + "properties": { + "buffer": { + "ignore_above": 1024, + "type": "keyword" + }, + "decompressed_size": { + "type": "integer" + }, + "encoding": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "identifier": { + "ignore_above": 1024, + "type": "keyword" + }, + "score": { + "type": "double" + }, + "threshold": { + "type": "double" + }, + "upx_packed": { + "type": "boolean" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "mapped_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "mapped_size": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "endpoint": { + "properties": { + "artifact": { + "properties": { + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "policy": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "sequence": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "file": { + "properties": { + "accessed": { + "type": "date" + }, + "attributes": { + "ignore_above": 1024, + "type": "keyword" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "created": { + "type": "date" + }, + "ctime": { + "type": "date" + }, + "device": { + "ignore_above": 1024, + "type": "keyword" + }, + "directory": { + "ignore_above": 1024, + "type": "keyword" + }, + "drive_letter": { + "ignore_above": 1, + "type": "keyword" + }, + "entry_modified": { + "type": "double" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "macro": { + "properties": { + "code_page": { + "type": "long" + }, + "collection": { + "properties": { + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + }, + "type": "object" + }, + "errors": { + "properties": { + "count": { + "type": "long" + }, + "error_type": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "file_extension": { + "type": "long" + }, + "project_file": { + "properties": { + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + }, + "type": "object" + }, + "stream": { + "properties": { + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "raw_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "raw_code_size": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + } + } + }, + "malware_classification": { + "properties": { + "features": { + "properties": { + "data": { + "properties": { + "buffer": { + "ignore_above": 1024, + "type": "keyword" + }, + "decompressed_size": { + "type": "integer" + }, + "encoding": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "identifier": { + "ignore_above": 1024, + "type": "keyword" + }, + "score": { + "type": "double" + }, + "threshold": { + "type": "double" + }, + "upx_packed": { + "type": "boolean" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mtime": { + "type": "date" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "size": { + "type": "long" + }, + "target_path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "temp_file_path": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "process": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "cpu_percent": { + "type": "double" + }, + "cwd": { + "ignore_above": 1024, + "type": "keyword" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "env_variables": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "handles": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "malware_classification": { + "properties": { + "features": { + "properties": { + "data": { + "properties": { + "buffer": { + "ignore_above": 1024, + "type": "keyword" + }, + "decompressed_size": { + "type": "integer" + }, + "encoding": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "identifier": { + "ignore_above": 1024, + "type": "keyword" + }, + "score": { + "type": "double" + }, + "threshold": { + "type": "double" + }, + "upx_packed": { + "type": "boolean" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "memory_percent": { + "type": "double" + }, + "memory_region": { + "properties": { + "allocation_base": { + "ignore_above": 1024, + "type": "keyword" + }, + "allocation_protection": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "histogram": { + "properties": { + "histogram_array": { + "ignore_above": 1024, + "type": "keyword" + }, + "histogram_flavor": { + "ignore_above": 1024, + "type": "keyword" + }, + "histogram_resolution": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "length": { + "ignore_above": 1024, + "type": "keyword" + }, + "memory": { + "ignore_above": 1024, + "type": "keyword" + }, + "memory_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "module_path": { + "ignore_above": 1024, + "type": "keyword" + }, + "permission": { + "ignore_above": 1024, + "type": "keyword" + }, + "protection": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_base": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_size": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_tag": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "unbacked_on_disk": { + "type": "boolean" + } + }, + "type": "nested" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "num_threads": { + "type": "long" + }, + "parent": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "entrypoint": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "service": { + "ignore_above": 1024, + "type": "keyword" + }, + "start": { + "type": "date" + }, + "start_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "start_address_module": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pe": { + "properties": { + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "phys_memory_bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "services": { + "ignore_above": 1024, + "type": "keyword" + }, + "session_id": { + "type": "long" + }, + "short_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "sid": { + "ignore_above": 1024, + "type": "keyword" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "call_stack": { + "properties": { + "instruction_pointer": { + "ignore_above": 1024, + "type": "keyword" + }, + "memory_section": { + "properties": { + "memory_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "memory_size": { + "ignore_above": 1024, + "type": "keyword" + }, + "protection": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "module_path": { + "ignore_above": 1024, + "type": "keyword" + }, + "rva": { + "ignore_above": 1024, + "type": "keyword" + }, + "symbol_info": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "entrypoint": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "service": { + "ignore_above": 1024, + "type": "keyword" + }, + "start": { + "type": "date" + }, + "start_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "start_address_module": { + "ignore_above": 1024, + "type": "keyword" + }, + "token": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "impersonation_level": { + "ignore_above": 1024, + "type": "keyword" + }, + "integrity_level": { + "type": "long" + }, + "integrity_level_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "is_appcontainer": { + "type": "boolean" + }, + "privileges": { + "properties": { + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "sid": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "uptime": { + "type": "long" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "token": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "impersonation_level": { + "ignore_above": 1024, + "type": "keyword" + }, + "integrity_level": { + "type": "long" + }, + "integrity_level_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "is_appcontainer": { + "type": "boolean" + }, + "privileges": { + "properties": { + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "sid": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "tty_device": { + "properties": { + "major_number": { + "type": "integer" + }, + "minor_number": { + "type": "integer" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "uptime": { + "type": "long" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + }, + "virt_memory_bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "rule": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "ruleset": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "target": { + "properties": { + "dll": { + "properties": { + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "compile_time": { + "type": "date" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "malware_classification": { + "properties": { + "features": { + "properties": { + "data": { + "properties": { + "buffer": { + "ignore_above": 1024, + "type": "keyword" + }, + "decompressed_size": { + "type": "integer" + }, + "encoding": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "identifier": { + "ignore_above": 1024, + "type": "keyword" + }, + "score": { + "type": "double" + }, + "threshold": { + "type": "double" + }, + "upx_packed": { + "type": "boolean" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "mapped_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "mapped_size": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "process": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "cpu_percent": { + "type": "double" + }, + "cwd": { + "ignore_above": 1024, + "type": "keyword" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "env_variables": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "handles": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "malware_classification": { + "properties": { + "features": { + "properties": { + "data": { + "properties": { + "buffer": { + "ignore_above": 1024, + "type": "keyword" + }, + "decompressed_size": { + "type": "integer" + }, + "encoding": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "identifier": { + "ignore_above": 1024, + "type": "keyword" + }, + "score": { + "type": "double" + }, + "threshold": { + "type": "double" + }, + "upx_packed": { + "type": "boolean" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "memory_percent": { + "type": "double" + }, + "memory_region": { + "properties": { + "allocation_base": { + "ignore_above": 1024, + "type": "keyword" + }, + "allocation_protection": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "histogram": { + "properties": { + "histogram_array": { + "ignore_above": 1024, + "type": "keyword" + }, + "histogram_flavor": { + "ignore_above": 1024, + "type": "keyword" + }, + "histogram_resolution": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "length": { + "ignore_above": 1024, + "type": "keyword" + }, + "memory": { + "ignore_above": 1024, + "type": "keyword" + }, + "memory_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "module_path": { + "ignore_above": 1024, + "type": "keyword" + }, + "permission": { + "ignore_above": 1024, + "type": "keyword" + }, + "protection": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_base": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_size": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_tag": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "unbacked_on_disk": { + "type": "boolean" + } + }, + "type": "nested" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "num_threads": { + "type": "long" + }, + "parent": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "entrypoint": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "service": { + "ignore_above": 1024, + "type": "keyword" + }, + "start": { + "type": "date" + }, + "start_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "start_address_module": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pe": { + "properties": { + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "phys_memory_bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "services": { + "ignore_above": 1024, + "type": "keyword" + }, + "session_id": { + "type": "long" + }, + "short_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "sid": { + "ignore_above": 1024, + "type": "keyword" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "call_stack": { + "properties": { + "instruction_pointer": { + "ignore_above": 1024, + "type": "keyword" + }, + "memory_section": { + "properties": { + "memory_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "memory_size": { + "ignore_above": 1024, + "type": "keyword" + }, + "protection": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "module_path": { + "ignore_above": 1024, + "type": "keyword" + }, + "rva": { + "ignore_above": 1024, + "type": "keyword" + }, + "symbol_info": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "entrypoint": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "service": { + "ignore_above": 1024, + "type": "keyword" + }, + "start": { + "type": "date" + }, + "start_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "start_address_module": { + "ignore_above": 1024, + "type": "keyword" + }, + "token": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "impersonation_level": { + "ignore_above": 1024, + "type": "keyword" + }, + "integrity_level": { + "type": "long" + }, + "integrity_level_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "is_appcontainer": { + "type": "boolean" + }, + "privileges": { + "properties": { + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "sid": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "uptime": { + "type": "long" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "token": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "impersonation_level": { + "ignore_above": 1024, + "type": "keyword" + }, + "integrity_level": { + "type": "long" + }, + "integrity_level_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "is_appcontainer": { + "type": "boolean" + }, + "privileges": { + "properties": { + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "type": "nested" + }, + "sid": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "tty_device": { + "properties": { + "major_number": { + "type": "integer" + }, + "minor_number": { + "type": "integer" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "uptime": { + "type": "long" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + }, + "virt_memory_bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "threat": { + "properties": { + "framework": { + "ignore_above": 1024, + "type": "keyword" + }, + "tactic": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "technique": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "mapping": { + "total_fields": { + "limit": 10000 + } + }, + "refresh_interval": "5s" + } + } +} \ No newline at end of file diff --git a/x-pack/plugins/endpoint/scripts/mapping.json b/x-pack/plugins/endpoint/scripts/event_mapping.json similarity index 99% rename from x-pack/plugins/endpoint/scripts/mapping.json rename to x-pack/plugins/endpoint/scripts/event_mapping.json index 5878e01b52a47..59d1ed17852b1 100644 --- a/x-pack/plugins/endpoint/scripts/mapping.json +++ b/x-pack/plugins/endpoint/scripts/event_mapping.json @@ -2361,7 +2361,8 @@ "limit": 10000 } }, - "refresh_interval": "5s" + "refresh_interval": "5s", + "default_pipeline": "endpoint-event-pipeline" } } } \ No newline at end of file diff --git a/x-pack/plugins/endpoint/scripts/resolver_generator.ts b/x-pack/plugins/endpoint/scripts/resolver_generator.ts index aebf92eff6cb8..333846bde6ce4 100644 --- a/x-pack/plugins/endpoint/scripts/resolver_generator.ts +++ b/x-pack/plugins/endpoint/scripts/resolver_generator.ts @@ -8,7 +8,8 @@ import seedrandom from 'seedrandom'; import { Client, ClientOptions } from '@elastic/elasticsearch'; import { ResponseError } from '@elastic/elasticsearch/lib/errors'; import { EndpointDocGenerator, Event } from '../common/generate_data'; -import { default as mapping } from './mapping.json'; +import { default as eventMapping } from './event_mapping.json'; +import { default as alertMapping } from './alert_mapping.json'; main(); @@ -25,6 +26,12 @@ async function main() { default: 'http://localhost:9200', type: 'string', }, + alertIndex: { + alias: 'ai', + describe: 'index to store alerts in', + default: '.alerts-endpoint-000001', + type: 'string', + }, eventIndex: { alias: 'ei', describe: 'index to store events in', @@ -95,7 +102,16 @@ async function main() { type: 'boolean', default: false, }, + setupOnly: { + alias: 'so', + describe: + 'Run only the index and pipeline creation then exit. This is intended to be used to set up the Endpoint App for use with the real Elastic Endpoint.', + type: 'boolean', + default: false, + }, }).argv; + const pipelineName = 'endpoint-event-pipeline'; + eventMapping.settings.index.default_pipeline = pipelineName; const clientOptions: ClientOptions = { node: argv.node, }; @@ -107,7 +123,7 @@ async function main() { if (argv.delete) { try { await client.indices.delete({ - index: [argv.eventIndex, argv.metadataIndex], + index: [argv.eventIndex, argv.metadataIndex, argv.alertIndex], }); } catch (err) { if (err instanceof ResponseError && err.statusCode !== 404) { @@ -117,21 +133,42 @@ async function main() { } } } + + const pipeline = { + description: 'redirects alerts to their own index', + processors: [ + { + set: { + field: '_index', + value: argv.alertIndex, + if: "ctx.event.kind == 'alert'", + }, + }, + { + set: { + field: 'mutable_state.triage_status', + value: 'open', + }, + }, + ], + }; try { - await client.indices.create({ - index: argv.eventIndex, - body: mapping, + await client.ingest.putPipeline({ + id: pipelineName, + body: pipeline, }); } catch (err) { - if ( - err instanceof ResponseError && - err.body.error.type !== 'resource_already_exists_exception' - ) { - // eslint-disable-next-line no-console - console.log(err.body); - process.exit(1); - } + // eslint-disable-next-line no-console + console.log(err); + process.exit(1); } + + await createIndex(client, argv.alertIndex, alertMapping); + await createIndex(client, argv.eventIndex, eventMapping); + if (argv.setupOnly) { + process.exit(0); + } + let seed = argv.seed; if (!seed) { seed = Math.random().toString(); @@ -183,3 +220,21 @@ async function main() { } } } + +async function createIndex(client: Client, index: string, mapping: any) { + try { + await client.indices.create({ + index, + body: mapping, + }); + } catch (err) { + if ( + err instanceof ResponseError && + err.body.error.type !== 'resource_already_exists_exception' + ) { + // eslint-disable-next-line no-console + console.log(err.body); + process.exit(1); + } + } +} From 2cd86a4c838c81c17a7c66e5b4379b9ffa72f7fe Mon Sep 17 00:00:00 2001 From: Sonja Krause-Harder Date: Mon, 6 Apr 2020 18:48:18 +0200 Subject: [PATCH 03/36] [EPM] Refactor expandFields() (#62180) * Do not modify input array in expandFields() * Add unit tests for processFields() Co-authored-by: Elastic Machine --- .../server/services/epm/fields/field.test.ts | 100 ++++++++++++++++++ .../server/services/epm/fields/field.ts | 30 +++--- 2 files changed, 114 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/ingest_manager/server/services/epm/fields/field.test.ts b/x-pack/plugins/ingest_manager/server/services/epm/fields/field.test.ts index 929f2518ee748..e3aef6077dbc3 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/fields/field.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/fields/field.test.ts @@ -80,3 +80,103 @@ describe('getField searches recursively for nested field in fields given an arra expect(getField(searchFields, ['2', '2-2', '2-2-1'])?.name).toBe('2-2-1'); }); }); + +describe('processFields', () => { + const flattenedFields = [ + { + name: 'a.a', + type: 'text', + }, + { + name: 'a.b', + type: 'text', + }, + ]; + const expandedFields = [ + { + name: 'a', + type: 'group', + fields: [ + { + name: 'a', + type: 'text', + }, + { + name: 'b', + type: 'text', + }, + ], + }, + ]; + test('correctly expands flattened fields', () => { + expect(JSON.stringify(processFields(flattenedFields))).toEqual(JSON.stringify(expandedFields)); + }); + test('leaves expanded fields unchanged', () => { + expect(JSON.stringify(processFields(expandedFields))).toEqual(JSON.stringify(expandedFields)); + }); + + const mixedFieldsA = [ + { + name: 'a.a', + type: 'group', + fields: [ + { + name: 'a', + type: 'text', + }, + { + name: 'b', + type: 'text', + }, + ], + }, + ]; + + const mixedFieldsB = [ + { + name: 'a', + type: 'group', + fields: [ + { + name: 'a.a', + type: 'text', + }, + { + name: 'a.b', + type: 'text', + }, + ], + }, + ]; + + const mixedFieldsExpanded = [ + { + name: 'a', + type: 'group', + fields: [ + { + name: 'a', + type: 'group', + fields: [ + { + name: 'a', + type: 'text', + }, + { + name: 'b', + type: 'text', + }, + ], + }, + ], + }, + ]; + test('correctly expands a mix of expanded and flattened fields', () => { + expect(JSON.stringify(processFields(mixedFieldsA))).toEqual( + JSON.stringify(mixedFieldsExpanded) + ); + expect(JSON.stringify(processFields(mixedFieldsB))).toEqual( + JSON.stringify(mixedFieldsExpanded) + ); + }); +}); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/fields/field.ts b/x-pack/plugins/ingest_manager/server/services/epm/fields/field.ts index 4a1a84baf6599..810896bb50389 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/fields/field.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/fields/field.ts @@ -52,13 +52,12 @@ export type Fields = Field[]; * expandFields takes the given fields read from yaml and expands them. * There are dotted fields in the field.yml like `foo.bar`. These should * be stored as an field within a 'group' field. - * - * Note: This function modifies the passed fields array. */ -export function expandFields(fields: Fields) { +export function expandFields(fields: Fields): Fields { + const newFields: Fields = []; + fields.forEach((field, key) => { const fieldName = field.name; - // If the field name contains a dot, it means we need to // - take the first part of the name // - create a field of type 'group' with this first part @@ -71,30 +70,29 @@ export function expandFields(fields: Fields) { const groupFieldName = nameParts[0]; // Put back together the parts again for the new field name - const restFieldName = nameParts.slice(1).join('.'); + const nestedFieldName = nameParts.slice(1).join('.'); // keep all properties of the original field, but give it the shortened name - field.name = restFieldName; + const nestedField = { ...field, name: nestedFieldName }; // create a new field of type group with the original field in the fields array const groupField: Field = { name: groupFieldName, type: 'group', - fields: [field], + fields: expandFields([nestedField]), }; - // check child fields further down the tree - if (groupField.fields) { - expandFields(groupField.fields); - } // Replace the original field in the array with the new one - fields[key] = groupField; + newFields.push(groupField); } else { // even if this field doesn't have dots to expand, its child fields further down the tree might - if (field.fields) { - expandFields(field.fields); + const newField = { ...field }; + if (newField.fields) { + newField.fields = expandFields(newField.fields); } + newFields.push(newField); } }); + return newFields; } /** * dedupFields takes the given fields and merges sibling fields with the @@ -180,8 +178,8 @@ export const getField = (fields: Fields, pathNames: string[]): Field | undefined }; export function processFields(fields: Fields): Fields { - expandFields(fields); - const dedupedFields = dedupFields(fields); + const expandedFields = expandFields(fields); + const dedupedFields = dedupFields(expandedFields); return validateFields(dedupedFields, dedupedFields); } From 4bdbe7356d10d87fb7cf38b8fe529c63cb2a316f Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Mon, 6 Apr 2020 09:49:23 -0700 Subject: [PATCH 04/36] Remove ES-UI as code owner of Transform app. (#62556) --- .github/CODEOWNERS | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3ae01b079d37c..feaf47e45fd69 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -87,9 +87,8 @@ /x-pack/test/functional/apps/machine_learning/ @elastic/ml-ui /x-pack/test/functional/services/machine_learning/ @elastic/ml-ui /x-pack/test/functional/services/ml.ts @elastic/ml-ui -# ML team owns the transform plugin, ES team added here for visibility -# because the plugin lives in Kibana's Elasticsearch management section. -/x-pack/plugins/transform/ @elastic/ml-ui @elastic/es-ui +# ML team owns and maintains the transform plugin despite it living in the Elasticsearch management section. +/x-pack/plugins/transform/ @elastic/ml-ui /x-pack/test/functional/apps/transform/ @elastic/ml-ui /x-pack/test/functional/services/transform_ui/ @elastic/ml-ui /x-pack/test/functional/services/transform.ts @elastic/ml-ui From e7a4ca261b17418e32567b0f69fd842ffc989318 Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Mon, 6 Apr 2020 18:02:58 +0100 Subject: [PATCH 05/36] [Event Log] adds query support to the Event Log (#62015) * added Start api on Event Log plugin * added empty skeleton for Event Log FTs * added functional test to public find events api * added test for pagination * fixed unit tests * added support for date ranges * removed unused code * replaces valdiation typing * Revert "replaces valdiation typing" This reverts commit 711c098e9b2a8329c58c9674fc99de23842884d1. * replaces match with term * added sorting * fixed saved objects nested query * updated plugin FTs path * Update x-pack/plugins/encrypted_saved_objects/README.md Co-Authored-By: Aleh Zasypkin * Update x-pack/plugins/encrypted_saved_objects/README.md Co-Authored-By: Aleh Zasypkin * remofed validation from tests * fixed typos Co-authored-by: Elastic Machine Co-authored-by: Aleh Zasypkin --- package.json | 3 +- test/scripts/jenkins_xpack_build_kibana.sh | 1 + .../plugins/encrypted_saved_objects/README.md | 4 +- x-pack/plugins/event_log/common/index.ts | 7 + .../server/es/cluster_client_adapter.mock.ts | 1 + .../server/es/cluster_client_adapter.test.ts | 229 ++++++++++++++ .../server/es/cluster_client_adapter.ts | 91 ++++++ .../event_log/server/event_log_client.mock.ts | 18 ++ .../event_log/server/event_log_client.test.ts | 292 ++++++++++++++++++ .../event_log/server/event_log_client.ts | 86 ++++++ .../server/event_log_start_service.mock.ts | 18 ++ .../server/event_log_start_service.test.ts | 59 ++++ .../server/event_log_start_service.ts | 48 +++ x-pack/plugins/event_log/server/index.ts | 2 +- x-pack/plugins/event_log/server/mocks.ts | 5 +- x-pack/plugins/event_log/server/plugin.ts | 42 ++- .../server/routes/_mock_handler_arguments.ts | 70 +++++ .../event_log/server/routes/find.test.ts | 98 ++++++ .../plugins/event_log/server/routes/find.ts | 50 +++ .../plugins/event_log/server/routes/index.ts | 7 + x-pack/plugins/event_log/server/types.ts | 23 ++ x-pack/plugins/task_manager/server/README.md | 4 +- x-pack/scripts/functional_tests.js | 2 +- .../{config.js => config.ts} | 11 +- .../plugins/event_log/kibana.json | 9 + .../plugins/event_log/package.json | 15 + .../plugins/event_log/server/index.ts | 11 + .../plugins/event_log/server/plugin.ts | 98 ++++++ .../test_suites/event_log/index.ts | 15 + .../event_log/public_api_integration.ts | 236 ++++++++++++++ .../event_log/service_api_integration.ts | 11 + 31 files changed, 1552 insertions(+), 14 deletions(-) create mode 100644 x-pack/plugins/event_log/common/index.ts create mode 100644 x-pack/plugins/event_log/server/event_log_client.mock.ts create mode 100644 x-pack/plugins/event_log/server/event_log_client.test.ts create mode 100644 x-pack/plugins/event_log/server/event_log_client.ts create mode 100644 x-pack/plugins/event_log/server/event_log_start_service.mock.ts create mode 100644 x-pack/plugins/event_log/server/event_log_start_service.test.ts create mode 100644 x-pack/plugins/event_log/server/event_log_start_service.ts create mode 100644 x-pack/plugins/event_log/server/routes/_mock_handler_arguments.ts create mode 100644 x-pack/plugins/event_log/server/routes/find.test.ts create mode 100644 x-pack/plugins/event_log/server/routes/find.ts create mode 100644 x-pack/plugins/event_log/server/routes/index.ts rename x-pack/test/plugin_api_integration/{config.js => config.ts} (77%) create mode 100644 x-pack/test/plugin_api_integration/plugins/event_log/kibana.json create mode 100644 x-pack/test/plugin_api_integration/plugins/event_log/package.json create mode 100644 x-pack/test/plugin_api_integration/plugins/event_log/server/index.ts create mode 100644 x-pack/test/plugin_api_integration/plugins/event_log/server/plugin.ts create mode 100644 x-pack/test/plugin_api_integration/test_suites/event_log/index.ts create mode 100644 x-pack/test/plugin_api_integration/test_suites/event_log/public_api_integration.ts create mode 100644 x-pack/test/plugin_api_integration/test_suites/event_log/service_api_integration.ts diff --git a/package.json b/package.json index 46e0b9adfea25..e807cd4d95198 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,8 @@ "examples/*", "test/plugin_functional/plugins/*", "test/interpreter_functional/plugins/*", - "x-pack/test/functional_with_es_ssl/fixtures/plugins/*" + "x-pack/test/functional_with_es_ssl/fixtures/plugins/*", + "x-pack/test/plugin_api_integration/plugins/*" ], "nohoist": [ "**/@types/*", diff --git a/test/scripts/jenkins_xpack_build_kibana.sh b/test/scripts/jenkins_xpack_build_kibana.sh index 777d98080e407..962d2794f712f 100755 --- a/test/scripts/jenkins_xpack_build_kibana.sh +++ b/test/scripts/jenkins_xpack_build_kibana.sh @@ -7,6 +7,7 @@ echo " -> building kibana platform plugins" node scripts/build_kibana_platform_plugins \ --scan-dir "$XPACK_DIR/test/plugin_functional/plugins" \ --scan-dir "$XPACK_DIR/test/functional_with_es_ssl/fixtures/plugins" \ + --scan-dir "$XPACK_DIR/test/plugin_api_integration/plugins" \ --verbose; # doesn't persist, also set in kibanaPipeline.groovy diff --git a/x-pack/plugins/encrypted_saved_objects/README.md b/x-pack/plugins/encrypted_saved_objects/README.md index a352989870079..6085b52d392a4 100644 --- a/x-pack/plugins/encrypted_saved_objects/README.md +++ b/x-pack/plugins/encrypted_saved_objects/README.md @@ -100,10 +100,10 @@ $ node scripts/jest.js In one shell, from `kibana-root-folder/x-pack`: ```bash -$ node scripts/functional_tests_server.js --config test/plugin_api_integration/config.js +$ node scripts/functional_tests_server.js --config test/encrypted_saved_objects_api_integration/config.ts ``` In another shell, from `kibana-root-folder/x-pack`: ```bash -$ node ../scripts/functional_test_runner.js --config test/plugin_api_integration/config.js --grep="{TEST_NAME}" +$ node ../scripts/functional_test_runner.js --config test/encrypted_saved_objects_api_integration/config.ts --grep="{TEST_NAME}" ``` diff --git a/x-pack/plugins/event_log/common/index.ts b/x-pack/plugins/event_log/common/index.ts new file mode 100644 index 0000000000000..3ee274916c127 --- /dev/null +++ b/x-pack/plugins/event_log/common/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const BASE_EVENT_LOG_API_PATH = '/api/event_log'; diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.mock.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.mock.ts index 87e8fb0f521a9..bd57958b0cb88 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.mock.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.mock.ts @@ -15,6 +15,7 @@ const createClusterClientMock = () => { createIndexTemplate: jest.fn(), doesAliasExist: jest.fn(), createIndex: jest.fn(), + queryEventsBySavedObject: jest.fn(), }; return mock; }; diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts index b61196439ee4f..ae26d7a7ece07 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts @@ -7,6 +7,8 @@ import { ClusterClient, Logger } from '../../../../../src/core/server'; import { elasticsearchServiceMock, loggingServiceMock } from '../../../../../src/core/server/mocks'; import { ClusterClientAdapter, IClusterClientAdapter } from './cluster_client_adapter'; +import moment from 'moment'; +import { findOptionsSchema } from '../event_log_client'; type EsClusterClient = Pick, 'callAsInternalUser' | 'asScoped'>; @@ -195,3 +197,230 @@ describe('createIndex', () => { await clusterClientAdapter.createIndex('foo'); }); }); + +describe('queryEventsBySavedObject', () => { + const DEFAULT_OPTIONS = findOptionsSchema.validate({}); + + test('should call cluster with proper arguments', async () => { + clusterClient.callAsInternalUser.mockResolvedValue({ + hits: { + hits: [], + total: { value: 0 }, + }, + }); + await clusterClientAdapter.queryEventsBySavedObject( + 'index-name', + 'saved-object-type', + 'saved-object-id', + DEFAULT_OPTIONS + ); + + const [method, query] = clusterClient.callAsInternalUser.mock.calls[0]; + expect(method).toEqual('search'); + expect(query).toMatchObject({ + index: 'index-name', + body: { + from: 0, + size: 10, + sort: { 'event.start': { order: 'asc' } }, + query: { + bool: { + must: [ + { + nested: { + path: 'kibana.saved_objects', + query: { + bool: { + must: [ + { + term: { + 'kibana.saved_objects.type': { + value: 'saved-object-type', + }, + }, + }, + { + term: { + 'kibana.saved_objects.id': { + value: 'saved-object-id', + }, + }, + }, + ], + }, + }, + }, + }, + ], + }, + }, + }, + }); + }); + + test('should call cluster with sort', async () => { + clusterClient.callAsInternalUser.mockResolvedValue({ + hits: { + hits: [], + total: { value: 0 }, + }, + }); + await clusterClientAdapter.queryEventsBySavedObject( + 'index-name', + 'saved-object-type', + 'saved-object-id', + { ...DEFAULT_OPTIONS, sort_field: 'event.end', sort_order: 'desc' } + ); + + const [method, query] = clusterClient.callAsInternalUser.mock.calls[0]; + expect(method).toEqual('search'); + expect(query).toMatchObject({ + index: 'index-name', + body: { + sort: { 'event.end': { order: 'desc' } }, + }, + }); + }); + + test('supports open ended date', async () => { + clusterClient.callAsInternalUser.mockResolvedValue({ + hits: { + hits: [], + total: { value: 0 }, + }, + }); + + const start = moment() + .subtract(1, 'days') + .toISOString(); + + await clusterClientAdapter.queryEventsBySavedObject( + 'index-name', + 'saved-object-type', + 'saved-object-id', + { ...DEFAULT_OPTIONS, start } + ); + + const [method, query] = clusterClient.callAsInternalUser.mock.calls[0]; + expect(method).toEqual('search'); + expect(query).toMatchObject({ + index: 'index-name', + body: { + query: { + bool: { + must: [ + { + nested: { + path: 'kibana.saved_objects', + query: { + bool: { + must: [ + { + term: { + 'kibana.saved_objects.type': { + value: 'saved-object-type', + }, + }, + }, + { + term: { + 'kibana.saved_objects.id': { + value: 'saved-object-id', + }, + }, + }, + ], + }, + }, + }, + }, + { + range: { + 'event.start': { + gte: start, + }, + }, + }, + ], + }, + }, + }, + }); + }); + + test('supports optional date range', async () => { + clusterClient.callAsInternalUser.mockResolvedValue({ + hits: { + hits: [], + total: { value: 0 }, + }, + }); + + const start = moment() + .subtract(1, 'days') + .toISOString(); + const end = moment() + .add(1, 'days') + .toISOString(); + + await clusterClientAdapter.queryEventsBySavedObject( + 'index-name', + 'saved-object-type', + 'saved-object-id', + { ...DEFAULT_OPTIONS, start, end } + ); + + const [method, query] = clusterClient.callAsInternalUser.mock.calls[0]; + expect(method).toEqual('search'); + expect(query).toMatchObject({ + index: 'index-name', + body: { + query: { + bool: { + must: [ + { + nested: { + path: 'kibana.saved_objects', + query: { + bool: { + must: [ + { + term: { + 'kibana.saved_objects.type': { + value: 'saved-object-type', + }, + }, + }, + { + term: { + 'kibana.saved_objects.id': { + value: 'saved-object-id', + }, + }, + }, + ], + }, + }, + }, + }, + { + range: { + 'event.start': { + gte: start, + }, + }, + }, + { + range: { + 'event.end': { + lte: end, + }, + }, + }, + ], + }, + }, + }, + }); + }); +}); diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts index d585fd4f539b5..36bc94edfca4e 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts @@ -4,7 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import { reject, isUndefined } from 'lodash'; import { Logger, ClusterClient } from '../../../../../src/core/server'; +import { IEvent } from '../types'; +import { FindOptionsType } from '../event_log_client'; export type EsClusterClient = Pick; export type IClusterClientAdapter = PublicMethodsOf; @@ -14,6 +17,13 @@ export interface ConstructorOpts { clusterClient: EsClusterClient; } +export interface QueryEventsBySavedObjectResult { + page: number; + per_page: number; + total: number; + data: IEvent[]; +} + export class ClusterClientAdapter { private readonly logger: Logger; private readonly clusterClient: EsClusterClient; @@ -107,6 +117,87 @@ export class ClusterClientAdapter { } } + public async queryEventsBySavedObject( + index: string, + type: string, + id: string, + { page, per_page: perPage, start, end, sort_field, sort_order }: FindOptionsType + ): Promise { + try { + const { + hits: { + hits, + total: { value: total }, + }, + } = await this.callEs('search', { + index, + body: { + size: perPage, + from: (page - 1) * perPage, + sort: { [sort_field]: { order: sort_order } }, + query: { + bool: { + must: reject( + [ + { + nested: { + path: 'kibana.saved_objects', + query: { + bool: { + must: [ + { + term: { + 'kibana.saved_objects.type': { + value: type, + }, + }, + }, + { + term: { + 'kibana.saved_objects.id': { + value: id, + }, + }, + }, + ], + }, + }, + }, + }, + start && { + range: { + 'event.start': { + gte: start, + }, + }, + }, + end && { + range: { + 'event.end': { + lte: end, + }, + }, + }, + ], + isUndefined + ), + }, + }, + }, + }); + return { + page, + per_page: perPage, + total, + data: hits.map((hit: any) => hit._source) as IEvent[], + }; + } catch (err) { + throw new Error( + `querying for Event Log by for type "${type}" and id "${id}" failed with: ${err.message}` + ); + } + } + private async callEs(operation: string, body?: any): Promise { try { this.debug(`callEs(${operation}) calls:`, body); diff --git a/x-pack/plugins/event_log/server/event_log_client.mock.ts b/x-pack/plugins/event_log/server/event_log_client.mock.ts new file mode 100644 index 0000000000000..31cab802555d0 --- /dev/null +++ b/x-pack/plugins/event_log/server/event_log_client.mock.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IEventLogClient } from './types'; + +const createEventLogClientMock = () => { + const mock: jest.Mocked = { + findEventsBySavedObject: jest.fn(), + }; + return mock; +}; + +export const eventLogClientMock = { + create: createEventLogClientMock, +}; diff --git a/x-pack/plugins/event_log/server/event_log_client.test.ts b/x-pack/plugins/event_log/server/event_log_client.test.ts new file mode 100644 index 0000000000000..6d4c9b67abc1b --- /dev/null +++ b/x-pack/plugins/event_log/server/event_log_client.test.ts @@ -0,0 +1,292 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EventLogClient } from './event_log_client'; +import { contextMock } from './es/context.mock'; +import { savedObjectsClientMock } from 'src/core/server/mocks'; +import { merge } from 'lodash'; +import moment from 'moment'; + +describe('EventLogStart', () => { + describe('findEventsBySavedObject', () => { + test('verifies that the user can access the specified saved object', async () => { + const esContext = contextMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); + const eventLogClient = new EventLogClient({ + esContext, + savedObjectsClient, + }); + + savedObjectsClient.get.mockResolvedValueOnce({ + id: 'saved-object-id', + type: 'saved-object-type', + attributes: {}, + references: [], + }); + + await eventLogClient.findEventsBySavedObject('saved-object-type', 'saved-object-id'); + + expect(savedObjectsClient.get).toHaveBeenCalledWith('saved-object-type', 'saved-object-id'); + }); + + test('throws when the user doesnt have permission to access the specified saved object', async () => { + const esContext = contextMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); + const eventLogClient = new EventLogClient({ + esContext, + savedObjectsClient, + }); + + savedObjectsClient.get.mockRejectedValue(new Error('Fail')); + + expect( + eventLogClient.findEventsBySavedObject('saved-object-type', 'saved-object-id') + ).rejects.toMatchInlineSnapshot(`[Error: Fail]`); + }); + + test('fetches all event that reference the saved object', async () => { + const esContext = contextMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); + const eventLogClient = new EventLogClient({ + esContext, + savedObjectsClient, + }); + + savedObjectsClient.get.mockResolvedValueOnce({ + id: 'saved-object-id', + type: 'saved-object-type', + attributes: {}, + references: [], + }); + + const expectedEvents = [ + fakeEvent({ + kibana: { + saved_objects: [ + { + id: 'saved-object-id', + type: 'saved-object-type', + }, + { + type: 'action', + id: '1', + }, + ], + }, + }), + fakeEvent({ + kibana: { + saved_objects: [ + { + id: 'saved-object-id', + type: 'saved-object-type', + }, + { + type: 'action', + id: '2', + }, + ], + }, + }), + ]; + + const result = { + page: 0, + per_page: 10, + total: expectedEvents.length, + data: expectedEvents, + }; + esContext.esAdapter.queryEventsBySavedObject.mockResolvedValue(result); + + expect( + await eventLogClient.findEventsBySavedObject('saved-object-type', 'saved-object-id') + ).toEqual(result); + + expect(esContext.esAdapter.queryEventsBySavedObject).toHaveBeenCalledWith( + esContext.esNames.alias, + 'saved-object-type', + 'saved-object-id', + { + page: 1, + per_page: 10, + sort_field: 'event.start', + sort_order: 'asc', + } + ); + }); + + test('fetches all events in time frame that reference the saved object', async () => { + const esContext = contextMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); + const eventLogClient = new EventLogClient({ + esContext, + savedObjectsClient, + }); + + savedObjectsClient.get.mockResolvedValueOnce({ + id: 'saved-object-id', + type: 'saved-object-type', + attributes: {}, + references: [], + }); + + const expectedEvents = [ + fakeEvent({ + kibana: { + saved_objects: [ + { + id: 'saved-object-id', + type: 'saved-object-type', + }, + { + type: 'action', + id: '1', + }, + ], + }, + }), + fakeEvent({ + kibana: { + saved_objects: [ + { + id: 'saved-object-id', + type: 'saved-object-type', + }, + { + type: 'action', + id: '2', + }, + ], + }, + }), + ]; + + const result = { + page: 0, + per_page: 10, + total: expectedEvents.length, + data: expectedEvents, + }; + esContext.esAdapter.queryEventsBySavedObject.mockResolvedValue(result); + + const start = moment() + .subtract(1, 'days') + .toISOString(); + const end = moment() + .add(1, 'days') + .toISOString(); + + expect( + await eventLogClient.findEventsBySavedObject('saved-object-type', 'saved-object-id', { + start, + end, + }) + ).toEqual(result); + + expect(esContext.esAdapter.queryEventsBySavedObject).toHaveBeenCalledWith( + esContext.esNames.alias, + 'saved-object-type', + 'saved-object-id', + { + page: 1, + per_page: 10, + sort_field: 'event.start', + sort_order: 'asc', + start, + end, + } + ); + }); + + test('validates that the start date is valid', async () => { + const esContext = contextMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); + const eventLogClient = new EventLogClient({ + esContext, + savedObjectsClient, + }); + + savedObjectsClient.get.mockResolvedValueOnce({ + id: 'saved-object-id', + type: 'saved-object-type', + attributes: {}, + references: [], + }); + + esContext.esAdapter.queryEventsBySavedObject.mockResolvedValue({ + page: 0, + per_page: 0, + total: 0, + data: [], + }); + + expect( + eventLogClient.findEventsBySavedObject('saved-object-type', 'saved-object-id', { + start: 'not a date string', + }) + ).rejects.toMatchInlineSnapshot(`[Error: [start]: Invalid Date]`); + }); + + test('validates that the end date is valid', async () => { + const esContext = contextMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); + const eventLogClient = new EventLogClient({ + esContext, + savedObjectsClient, + }); + + savedObjectsClient.get.mockResolvedValueOnce({ + id: 'saved-object-id', + type: 'saved-object-type', + attributes: {}, + references: [], + }); + + esContext.esAdapter.queryEventsBySavedObject.mockResolvedValue({ + page: 0, + per_page: 0, + total: 0, + data: [], + }); + + expect( + eventLogClient.findEventsBySavedObject('saved-object-type', 'saved-object-id', { + end: 'not a date string', + }) + ).rejects.toMatchInlineSnapshot(`[Error: [end]: Invalid Date]`); + }); + }); +}); + +function fakeEvent(overrides = {}) { + return merge( + { + event: { + provider: 'actions', + action: 'execute', + start: '2020-03-30T14:55:47.054Z', + end: '2020-03-30T14:55:47.055Z', + duration: 1000000, + }, + kibana: { + namespace: 'default', + saved_objects: [ + { + type: 'action', + id: '968f1b82-0414-4a10-becc-56b6473e4a29', + }, + ], + server_uuid: '5b2de169-2785-441b-ae8c-186a1936b17d', + }, + message: 'action executed: .server-log:968f1b82-0414-4a10-becc-56b6473e4a29: logger', + '@timestamp': '2020-03-30T14:55:47.055Z', + ecs: { + version: '1.3.1', + }, + }, + overrides + ); +} diff --git a/x-pack/plugins/event_log/server/event_log_client.ts b/x-pack/plugins/event_log/server/event_log_client.ts new file mode 100644 index 0000000000000..765f0895f8e0d --- /dev/null +++ b/x-pack/plugins/event_log/server/event_log_client.ts @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Observable } from 'rxjs'; +import { ClusterClient, SavedObjectsClientContract } from 'src/core/server'; + +import { schema, TypeOf } from '@kbn/config-schema'; +import { EsContext } from './es'; +import { IEventLogClient } from './types'; +import { QueryEventsBySavedObjectResult } from './es/cluster_client_adapter'; +export type PluginClusterClient = Pick; +export type AdminClusterClient$ = Observable; + +interface EventLogServiceCtorParams { + esContext: EsContext; + savedObjectsClient: SavedObjectsClientContract; +} + +const optionalDateFieldSchema = schema.maybe( + schema.string({ + validate(value) { + if (isNaN(Date.parse(value))) { + return 'Invalid Date'; + } + }, + }) +); + +export const findOptionsSchema = schema.object({ + per_page: schema.number({ defaultValue: 10, min: 0 }), + page: schema.number({ defaultValue: 1, min: 1 }), + start: optionalDateFieldSchema, + end: optionalDateFieldSchema, + sort_field: schema.oneOf( + [ + schema.literal('event.start'), + schema.literal('event.end'), + schema.literal('event.provider'), + schema.literal('event.duration'), + schema.literal('event.action'), + schema.literal('message'), + ], + { + defaultValue: 'event.start', + } + ), + sort_order: schema.oneOf([schema.literal('asc'), schema.literal('desc')], { + defaultValue: 'asc', + }), +}); +// page & perPage are required, other fields are optional +// using schema.maybe allows us to set undefined, but not to make the field optional +export type FindOptionsType = Pick< + TypeOf, + 'page' | 'per_page' | 'sort_field' | 'sort_order' +> & + Partial>; + +// note that clusterClient may be null, indicating we can't write to ES +export class EventLogClient implements IEventLogClient { + private esContext: EsContext; + private savedObjectsClient: SavedObjectsClientContract; + + constructor({ esContext, savedObjectsClient }: EventLogServiceCtorParams) { + this.esContext = esContext; + this.savedObjectsClient = savedObjectsClient; + } + + async findEventsBySavedObject( + type: string, + id: string, + options?: Partial + ): Promise { + // verify the user has the required permissions to view this saved object + await this.savedObjectsClient.get(type, id); + return await this.esContext.esAdapter.queryEventsBySavedObject( + this.esContext.esNames.alias, + type, + id, + findOptionsSchema.validate(options ?? {}) + ); + } +} diff --git a/x-pack/plugins/event_log/server/event_log_start_service.mock.ts b/x-pack/plugins/event_log/server/event_log_start_service.mock.ts new file mode 100644 index 0000000000000..e99ec777b473b --- /dev/null +++ b/x-pack/plugins/event_log/server/event_log_start_service.mock.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IEventLogClientService } from './types'; + +const createEventLogServiceMock = () => { + const mock: jest.Mocked = { + getClient: jest.fn(), + }; + return mock; +}; + +export const eventLogStartServiceMock = { + create: createEventLogServiceMock, +}; diff --git a/x-pack/plugins/event_log/server/event_log_start_service.test.ts b/x-pack/plugins/event_log/server/event_log_start_service.test.ts new file mode 100644 index 0000000000000..a8d75bc6c2e5a --- /dev/null +++ b/x-pack/plugins/event_log/server/event_log_start_service.test.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EventLogClientService } from './event_log_start_service'; +import { contextMock } from './es/context.mock'; +import { KibanaRequest } from 'kibana/server'; +import { savedObjectsServiceMock } from 'src/core/server/saved_objects/saved_objects_service.mock'; +import { savedObjectsClientMock } from 'src/core/server/mocks'; + +jest.mock('./event_log_client'); + +describe('EventLogClientService', () => { + const esContext = contextMock.create(); + + describe('getClient', () => { + test('creates a client with a scoped SavedObjects client', () => { + const savedObjectsService = savedObjectsServiceMock.createStartContract(); + const request = fakeRequest(); + + const eventLogStartService = new EventLogClientService({ + esContext, + savedObjectsService, + }); + + eventLogStartService.getClient(request); + + expect(savedObjectsService.getScopedClient).toHaveBeenCalledWith(request); + + const [{ value: savedObjectsClient }] = savedObjectsService.getScopedClient.mock.results; + + expect(jest.requireMock('./event_log_client').EventLogClient).toHaveBeenCalledWith({ + esContext, + savedObjectsClient, + }); + }); + }); +}); + +function fakeRequest(): KibanaRequest { + const savedObjectsClient = savedObjectsClientMock.create(); + return { + headers: {}, + getBasePath: () => '', + path: '/', + route: { settings: {} }, + url: { + href: '/', + }, + raw: { + req: { + url: '/', + }, + }, + getSavedObjectsClient: () => savedObjectsClient, + } as any; +} diff --git a/x-pack/plugins/event_log/server/event_log_start_service.ts b/x-pack/plugins/event_log/server/event_log_start_service.ts new file mode 100644 index 0000000000000..5938f7a2e614e --- /dev/null +++ b/x-pack/plugins/event_log/server/event_log_start_service.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import _ from 'lodash'; +import { Observable } from 'rxjs'; +import { + ClusterClient, + KibanaRequest, + SavedObjectsServiceStart, + SavedObjectsClientContract, +} from 'src/core/server'; + +import { EsContext } from './es'; +import { IEventLogClientService } from './types'; +import { EventLogClient } from './event_log_client'; +export type PluginClusterClient = Pick; +export type AdminClusterClient$ = Observable; + +interface EventLogServiceCtorParams { + esContext: EsContext; + savedObjectsService: SavedObjectsServiceStart; +} + +// note that clusterClient may be null, indicating we can't write to ES +export class EventLogClientService implements IEventLogClientService { + private esContext: EsContext; + private savedObjectsService: SavedObjectsServiceStart; + + constructor({ esContext, savedObjectsService }: EventLogServiceCtorParams) { + this.esContext = esContext; + this.savedObjectsService = savedObjectsService; + } + + getClient( + request: KibanaRequest, + savedObjectsClient: SavedObjectsClientContract = this.savedObjectsService.getScopedClient( + request + ) + ) { + return new EventLogClient({ + esContext: this.esContext, + savedObjectsClient, + }); + } +} diff --git a/x-pack/plugins/event_log/server/index.ts b/x-pack/plugins/event_log/server/index.ts index 81a56faa49964..b7fa25cb6eb9c 100644 --- a/x-pack/plugins/event_log/server/index.ts +++ b/x-pack/plugins/event_log/server/index.ts @@ -8,6 +8,6 @@ import { PluginInitializerContext } from 'src/core/server'; import { ConfigSchema } from './types'; import { Plugin } from './plugin'; -export { IEventLogService, IEventLogger, IEvent } from './types'; +export { IEventLogService, IEventLogger, IEventLogClientService, IEvent } from './types'; export const config = { schema: ConfigSchema }; export const plugin = (context: PluginInitializerContext) => new Plugin(context); diff --git a/x-pack/plugins/event_log/server/mocks.ts b/x-pack/plugins/event_log/server/mocks.ts index aad6cf3e24561..2f632a52d2f36 100644 --- a/x-pack/plugins/event_log/server/mocks.ts +++ b/x-pack/plugins/event_log/server/mocks.ts @@ -5,8 +5,9 @@ */ import { eventLogServiceMock } from './event_log_service.mock'; +import { eventLogStartServiceMock } from './event_log_start_service.mock'; -export { eventLogServiceMock }; +export { eventLogServiceMock, eventLogStartServiceMock }; export { eventLoggerMock } from './event_logger.mock'; const createSetupMock = () => { @@ -14,7 +15,7 @@ const createSetupMock = () => { }; const createStartMock = () => { - return undefined; + return eventLogStartServiceMock.create(); }; export const eventLogMock = { diff --git a/x-pack/plugins/event_log/server/plugin.ts b/x-pack/plugins/event_log/server/plugin.ts index fdb08b2d090a6..2cc41354b4fbc 100644 --- a/x-pack/plugins/event_log/server/plugin.ts +++ b/x-pack/plugins/event_log/server/plugin.ts @@ -14,11 +14,21 @@ import { PluginInitializerContext, ClusterClient, SharedGlobalConfig, + IContextProvider, + RequestHandler, } from 'src/core/server'; -import { IEventLogConfig, IEventLogService, IEventLogger, IEventLogConfig$ } from './types'; +import { + IEventLogConfig, + IEventLogService, + IEventLogger, + IEventLogConfig$, + IEventLogClientService, +} from './types'; +import { findRoute } from './routes'; import { EventLogService } from './event_log_service'; import { createEsContext, EsContext } from './es'; +import { EventLogClientService } from './event_log_start_service'; export type PluginClusterClient = Pick; @@ -29,13 +39,14 @@ const ACTIONS = { stopping: 'stopping', }; -export class Plugin implements CorePlugin { +export class Plugin implements CorePlugin { private readonly config$: IEventLogConfig$; private systemLogger: Logger; private eventLogService?: IEventLogService; private esContext?: EsContext; private eventLogger?: IEventLogger; private globalConfig$: Observable; + private eventLogClientService?: EventLogClientService; constructor(private readonly context: PluginInitializerContext) { this.systemLogger = this.context.logger.get(); @@ -71,10 +82,17 @@ export class Plugin implements CorePlugin { event: { provider: PROVIDER }, }); + core.http.registerRouteHandlerContext('eventLog', this.createRouteHandlerContext()); + + // Routes + const router = core.http.createRouter(); + // Register routes + findRoute(router); + return this.eventLogService; } - async start(core: CoreStart) { + async start(core: CoreStart): Promise { this.systemLogger.debug('starting plugin'); if (!this.esContext) throw new Error('esContext not initialized'); @@ -91,8 +109,26 @@ export class Plugin implements CorePlugin { event: { action: ACTIONS.starting }, message: 'eventLog starting', }); + + this.eventLogClientService = new EventLogClientService({ + esContext: this.esContext, + savedObjectsService: core.savedObjects, + }); + return this.eventLogClientService; } + private createRouteHandlerContext = (): IContextProvider< + RequestHandler, + 'eventLog' + > => { + return async (context, request) => { + return { + getEventLogClient: () => + this.eventLogClientService!.getClient(request, context.core.savedObjects.client), + }; + }; + }; + stop() { this.systemLogger.debug('stopping plugin'); diff --git a/x-pack/plugins/event_log/server/routes/_mock_handler_arguments.ts b/x-pack/plugins/event_log/server/routes/_mock_handler_arguments.ts new file mode 100644 index 0000000000000..6640683bf6005 --- /dev/null +++ b/x-pack/plugins/event_log/server/routes/_mock_handler_arguments.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RequestHandlerContext, KibanaRequest, KibanaResponseFactory } from 'kibana/server'; +import { identity, merge } from 'lodash'; +import { httpServerMock } from '../../../../../src/core/server/mocks'; +import { IEventLogClient } from '../types'; + +export function mockHandlerArguments( + eventLogClient: IEventLogClient, + req: any, + res?: Array> +): [RequestHandlerContext, KibanaRequest, KibanaResponseFactory] { + return [ + ({ + eventLog: { + getEventLogClient() { + return eventLogClient; + }, + }, + } as unknown) as RequestHandlerContext, + req as KibanaRequest, + mockResponseFactory(res), + ]; +} + +export const mockResponseFactory = (resToMock: Array> = []) => { + const factory: jest.Mocked = httpServerMock.createResponseFactory(); + resToMock.forEach((key: string) => { + if (key in factory) { + Object.defineProperty(factory, key, { + value: jest.fn(identity), + }); + } + }); + return (factory as unknown) as KibanaResponseFactory; +}; + +export function fakeEvent(overrides = {}) { + return merge( + { + event: { + provider: 'actions', + action: 'execute', + start: '2020-03-30T14:55:47.054Z', + end: '2020-03-30T14:55:47.055Z', + duration: 1000000, + }, + kibana: { + namespace: 'default', + saved_objects: [ + { + type: 'action', + id: '968f1b82-0414-4a10-becc-56b6473e4a29', + }, + ], + server_uuid: '5b2de169-2785-441b-ae8c-186a1936b17d', + }, + message: 'action executed: .server-log:968f1b82-0414-4a10-becc-56b6473e4a29: logger', + '@timestamp': '2020-03-30T14:55:47.055Z', + ecs: { + version: '1.3.1', + }, + }, + overrides + ); +} diff --git a/x-pack/plugins/event_log/server/routes/find.test.ts b/x-pack/plugins/event_log/server/routes/find.test.ts new file mode 100644 index 0000000000000..844a84dc117a9 --- /dev/null +++ b/x-pack/plugins/event_log/server/routes/find.test.ts @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { findRoute } from './find'; +import { mockRouter, RouterMock } from '../../../../../src/core/server/http/router/router.mock'; +import { mockHandlerArguments, fakeEvent } from './_mock_handler_arguments'; +import { eventLogClientMock } from '../event_log_client.mock'; + +const eventLogClient = eventLogClientMock.create(); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('find', () => { + it('finds events with proper parameters', async () => { + const router: RouterMock = mockRouter.create(); + + findRoute(router); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/event_log/{type}/{id}/_find"`); + + const events = [fakeEvent(), fakeEvent()]; + const result = { + page: 0, + per_page: 10, + total: events.length, + data: events, + }; + eventLogClient.findEventsBySavedObject.mockResolvedValueOnce(result); + + const [context, req, res] = mockHandlerArguments( + eventLogClient, + { + params: { id: '1', type: 'action' }, + }, + ['ok'] + ); + + await handler(context, req, res); + + expect(eventLogClient.findEventsBySavedObject).toHaveBeenCalledTimes(1); + + const [type, id] = eventLogClient.findEventsBySavedObject.mock.calls[0]; + expect(type).toEqual(`action`); + expect(id).toEqual(`1`); + + expect(res.ok).toHaveBeenCalledWith({ + body: result, + }); + }); + + it('supports optional pagination parameters', async () => { + const router: RouterMock = mockRouter.create(); + + findRoute(router); + + const [, handler] = router.get.mock.calls[0]; + eventLogClient.findEventsBySavedObject.mockResolvedValueOnce({ + page: 0, + per_page: 10, + total: 0, + data: [], + }); + + const [context, req, res] = mockHandlerArguments( + eventLogClient, + { + params: { id: '1', type: 'action' }, + query: { page: 3, per_page: 10 }, + }, + ['ok'] + ); + + await handler(context, req, res); + + expect(eventLogClient.findEventsBySavedObject).toHaveBeenCalledTimes(1); + + const [type, id, options] = eventLogClient.findEventsBySavedObject.mock.calls[0]; + expect(type).toEqual(`action`); + expect(id).toEqual(`1`); + expect(options).toMatchObject({}); + + expect(res.ok).toHaveBeenCalledWith({ + body: { + page: 0, + per_page: 10, + total: 0, + data: [], + }, + }); + }); +}); diff --git a/x-pack/plugins/event_log/server/routes/find.ts b/x-pack/plugins/event_log/server/routes/find.ts new file mode 100644 index 0000000000000..cb170e50fb447 --- /dev/null +++ b/x-pack/plugins/event_log/server/routes/find.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; +import { + IRouter, + RequestHandlerContext, + KibanaRequest, + IKibanaResponse, + KibanaResponseFactory, +} from 'kibana/server'; +import { BASE_EVENT_LOG_API_PATH } from '../../common'; +import { findOptionsSchema, FindOptionsType } from '../event_log_client'; + +const paramSchema = schema.object({ + type: schema.string(), + id: schema.string(), +}); + +export const findRoute = (router: IRouter) => { + router.get( + { + path: `${BASE_EVENT_LOG_API_PATH}/{type}/{id}/_find`, + validate: { + params: paramSchema, + query: findOptionsSchema, + }, + }, + router.handleLegacyErrors(async function( + context: RequestHandlerContext, + req: KibanaRequest, FindOptionsType, any, any>, + res: KibanaResponseFactory + ): Promise> { + if (!context.eventLog) { + return res.badRequest({ body: 'RouteHandlerContext is not registered for eventLog' }); + } + const eventLogClient = context.eventLog.getEventLogClient(); + const { + params: { id, type }, + query, + } = req; + return res.ok({ + body: await eventLogClient.findEventsBySavedObject(type, id, query), + }); + }) + ); +}; diff --git a/x-pack/plugins/event_log/server/routes/index.ts b/x-pack/plugins/event_log/server/routes/index.ts new file mode 100644 index 0000000000000..85d9b3e0db8cd --- /dev/null +++ b/x-pack/plugins/event_log/server/routes/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { findRoute } from './find'; diff --git a/x-pack/plugins/event_log/server/types.ts b/x-pack/plugins/event_log/server/types.ts index f606bb2be6c6c..baf53ef447914 100644 --- a/x-pack/plugins/event_log/server/types.ts +++ b/x-pack/plugins/event_log/server/types.ts @@ -8,7 +8,10 @@ import { Observable } from 'rxjs'; import { schema, TypeOf } from '@kbn/config-schema'; export { IEvent, IValidatedEvent, EventSchema, ECS_VERSION } from '../generated/schemas'; +import { KibanaRequest } from 'kibana/server'; import { IEvent } from '../generated/schemas'; +import { FindOptionsType } from './event_log_client'; +import { QueryEventsBySavedObjectResult } from './es/cluster_client_adapter'; export const ConfigSchema = schema.object({ enabled: schema.boolean({ defaultValue: true }), @@ -19,6 +22,14 @@ export const ConfigSchema = schema.object({ export type IEventLogConfig = TypeOf; export type IEventLogConfig$ = Observable>; +declare module 'src/core/server' { + interface RequestHandlerContext { + eventLog?: { + getEventLogClient: () => IEventLogClient; + }; + } +} + // the object exposed by plugin.setup() export interface IEventLogService { isEnabled(): boolean; @@ -31,6 +42,18 @@ export interface IEventLogService { getLogger(properties: IEvent): IEventLogger; } +export interface IEventLogClientService { + getClient(request: KibanaRequest): IEventLogClient; +} + +export interface IEventLogClient { + findEventsBySavedObject( + type: string, + id: string, + options?: Partial + ): Promise; +} + export interface IEventLogger { logEvent(properties: IEvent): void; startTiming(event: IEvent): void; diff --git a/x-pack/plugins/task_manager/server/README.md b/x-pack/plugins/task_manager/server/README.md index a4154f3ecf212..c3d45be5d8f22 100644 --- a/x-pack/plugins/task_manager/server/README.md +++ b/x-pack/plugins/task_manager/server/README.md @@ -456,6 +456,6 @@ The task manager's public API is create / delete / list. Updates aren't directly ``` - Integration tests: ``` - node scripts/functional_tests_server.js --config x-pack/test/plugin_api_integration/config.js - node scripts/functional_test_runner --config x-pack/test/plugin_api_integration/config.js + node scripts/functional_tests_server.js --config x-pack/test/plugin_api_integration/config.ts + node scripts/functional_test_runner --config x-pack/test/plugin_api_integration/config.ts ``` diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js index b0ca33b00fde8..7943da07716a1 100644 --- a/x-pack/scripts/functional_tests.js +++ b/x-pack/scripts/functional_tests.js @@ -17,7 +17,7 @@ const onlyNotInCoverageTests = [ require.resolve('../test/alerting_api_integration/spaces_only/config.ts'), require.resolve('../test/alerting_api_integration/security_and_spaces/config.ts'), require.resolve('../test/detection_engine_api_integration/security_and_spaces/config.ts'), - require.resolve('../test/plugin_api_integration/config.js'), + require.resolve('../test/plugin_api_integration/config.ts'), require.resolve('../test/plugin_functional/config.ts'), require.resolve('../test/kerberos_api_integration/config.ts'), require.resolve('../test/kerberos_api_integration/anonymous_access.config.ts'), diff --git a/x-pack/test/plugin_api_integration/config.js b/x-pack/test/plugin_api_integration/config.ts similarity index 77% rename from x-pack/test/plugin_api_integration/config.js rename to x-pack/test/plugin_api_integration/config.ts index 83e8b1f84a9e0..c581e0c246e13 100644 --- a/x-pack/test/plugin_api_integration/config.js +++ b/x-pack/test/plugin_api_integration/config.ts @@ -6,9 +6,10 @@ import path from 'path'; import fs from 'fs'; +import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; import { services } from './services'; -export default async function({ readConfigFile }) { +export default async function({ readConfigFile }: FtrConfigProviderContext) { const integrationConfig = await readConfigFile(require.resolve('../api_integration/config')); // Find all folders in ./plugins since we treat all them as plugin folder @@ -18,7 +19,10 @@ export default async function({ readConfigFile }) { ); return { - testFiles: [require.resolve('./test_suites/task_manager')], + testFiles: [ + require.resolve('./test_suites/task_manager'), + require.resolve('./test_suites/event_log'), + ], services, servers: integrationConfig.get('servers'), esTestCluster: integrationConfig.get('esTestCluster'), @@ -34,6 +38,9 @@ export default async function({ readConfigFile }) { ...integrationConfig.get('kbnTestServer'), serverArgs: [ ...integrationConfig.get('kbnTestServer.serverArgs'), + '--xpack.eventLog.enabled=true', + '--xpack.eventLog.logEntries=true', + '--xpack.eventLog.indexEntries=true', ...plugins.map( pluginDir => `--plugin-path=${path.resolve(__dirname, 'plugins', pluginDir)}` ), diff --git a/x-pack/test/plugin_api_integration/plugins/event_log/kibana.json b/x-pack/test/plugin_api_integration/plugins/event_log/kibana.json new file mode 100644 index 0000000000000..4b467ce975012 --- /dev/null +++ b/x-pack/test/plugin_api_integration/plugins/event_log/kibana.json @@ -0,0 +1,9 @@ +{ + "id": "event_log_fixture", + "version": "1.0.0", + "kibanaVersion": "kibana", + "configPath": ["xpack"], + "requiredPlugins": ["eventLog"], + "server": true, + "ui": false +} diff --git a/x-pack/test/plugin_api_integration/plugins/event_log/package.json b/x-pack/test/plugin_api_integration/plugins/event_log/package.json new file mode 100644 index 0000000000000..222dfb2338e20 --- /dev/null +++ b/x-pack/test/plugin_api_integration/plugins/event_log/package.json @@ -0,0 +1,15 @@ +{ + "name": "event_log_fixture", + "version": "0.0.0", + "kibana": { + "version": "kibana" + }, + "main": "target/test/plugin_api_integration/plugins/event_log", + "scripts": { + "kbn": "node ../../../../../scripts/kbn.js", + "build": "rm -rf './target' && tsc" + }, + "devDependencies": { + "typescript": "3.7.2" + } +} diff --git a/x-pack/test/plugin_api_integration/plugins/event_log/server/index.ts b/x-pack/test/plugin_api_integration/plugins/event_log/server/index.ts new file mode 100644 index 0000000000000..3d794a7c80fa3 --- /dev/null +++ b/x-pack/test/plugin_api_integration/plugins/event_log/server/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializerContext } from 'kibana/server'; +import { EventLogFixturePlugin } from './plugin'; + +export const plugin = (initContext: PluginInitializerContext) => + new EventLogFixturePlugin(initContext); diff --git a/x-pack/test/plugin_api_integration/plugins/event_log/server/plugin.ts b/x-pack/test/plugin_api_integration/plugins/event_log/server/plugin.ts new file mode 100644 index 0000000000000..eccbd4fb7f90b --- /dev/null +++ b/x-pack/test/plugin_api_integration/plugins/event_log/server/plugin.ts @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + Plugin, + CoreSetup, + RequestHandlerContext, + KibanaRequest, + KibanaResponseFactory, + IKibanaResponse, + IRouter, + Logger, + PluginInitializerContext, + RouteValidationResultFactory, +} from 'kibana/server'; +import { + IEventLogService, + IEventLogClientService, + IEventLogger, +} from '../../../../../plugins/event_log/server'; +import { IValidatedEvent } from '../../../../../plugins/event_log/server/types'; + +// this plugin's dependendencies +export interface EventLogFixtureSetupDeps { + eventLog: IEventLogService; +} +export interface EventLogFixtureStartDeps { + eventLog: IEventLogClientService; +} + +export class EventLogFixturePlugin + implements Plugin { + private readonly logger: Logger; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get('plugins', 'eventLogFixture'); + } + + public setup(core: CoreSetup, { eventLog }: EventLogFixtureSetupDeps) { + const router = core.http.createRouter(); + + eventLog.registerProviderActions('event_log_fixture', ['test']); + const eventLogger = eventLog.getLogger({ + event: { provider: 'event_log_fixture' }, + }); + + core.savedObjects.registerType({ + name: 'event_log_test', + hidden: false, + namespaceAgnostic: true, + mappings: { + properties: {}, + }, + }); + + logEventRoute(router, eventLogger, this.logger); + } + + public start() {} + public stop() {} +} + +const logEventRoute = (router: IRouter, eventLogger: IEventLogger, logger: Logger) => { + router.post( + { + path: `/api/log_event_fixture/{id}/_log`, + validate: { + // removed validation as schema is currently broken in tests + // blocked by: https://github.com/elastic/kibana/issues/61652 + params: (value: any, { ok }: RouteValidationResultFactory) => ok(value), + body: (value: any, { ok }: RouteValidationResultFactory) => ok(value), + }, + }, + async function( + context: RequestHandlerContext, + req: KibanaRequest, + res: KibanaResponseFactory + ): Promise> { + const { id } = req.params as { id: string }; + const event: IValidatedEvent = req.body; + logger.info(`test fixture: log event: ${id} ${JSON.stringify(event)}`); + try { + await context.core.savedObjects.client.get('event_log_test', id); + logger.info(`found existing saved object`); + } catch (ex) { + logger.info(`log event error: ${ex}`); + await context.core.savedObjects.client.create('event_log_test', {}, { id }); + logger.info(`created saved object`); + } + eventLogger.logEvent(event); + logger.info(`logged`); + return res.ok({}); + } + ); +}; diff --git a/x-pack/test/plugin_api_integration/test_suites/event_log/index.ts b/x-pack/test/plugin_api_integration/test_suites/event_log/index.ts new file mode 100644 index 0000000000000..a68378decb1fd --- /dev/null +++ b/x-pack/test/plugin_api_integration/test_suites/event_log/index.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function({ loadTestFile }: FtrProviderContext) { + describe('event_log', function taskManagerSuite() { + this.tags('ciGroup2'); + loadTestFile(require.resolve('./public_api_integration')); + loadTestFile(require.resolve('./service_api_integration')); + }); +} diff --git a/x-pack/test/plugin_api_integration/test_suites/event_log/public_api_integration.ts b/x-pack/test/plugin_api_integration/test_suites/event_log/public_api_integration.ts new file mode 100644 index 0000000000000..c440971225d78 --- /dev/null +++ b/x-pack/test/plugin_api_integration/test_suites/event_log/public_api_integration.ts @@ -0,0 +1,236 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { merge, omit, times, chunk, isEmpty } from 'lodash'; +import uuid from 'uuid'; +import expect from '@kbn/expect/expect.js'; +import moment, { Moment } from 'moment'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { IEvent } from '../../../../plugins/event_log/server'; +import { IValidatedEvent } from '../../../../plugins/event_log/server/types'; + +const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + +export default function({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const log = getService('log'); + const retry = getService('retry'); + + describe('Event Log public API', () => { + it('should allow querying for events by Saved Object', async () => { + const id = uuid.v4(); + + const expectedEvents = [fakeEvent(id), fakeEvent(id)]; + + await logTestEvent(id, expectedEvents[0]); + await logTestEvent(id, expectedEvents[1]); + + await retry.try(async () => { + const { + body: { data, total }, + } = await findEvents(id, {}); + + expect(data.length).to.be(2); + expect(total).to.be(2); + + assertEventsFromApiMatchCreatedEvents(data, expectedEvents); + }); + }); + + it('should support pagination for events', async () => { + const id = uuid.v4(); + + const timestamp = moment(); + const [firstExpectedEvent, ...expectedEvents] = times(6, () => + fakeEvent(id, fakeEventTiming(timestamp.add(1, 's'))) + ); + // run one first to create the SO and avoid clashes + await logTestEvent(id, firstExpectedEvent); + await Promise.all(expectedEvents.map(event => logTestEvent(id, event))); + + await retry.try(async () => { + const { + body: { data: foundEvents }, + } = await findEvents(id, {}); + + expect(foundEvents.length).to.be(6); + }); + + const [expectedFirstPage, expectedSecondPage] = chunk( + [firstExpectedEvent, ...expectedEvents], + 3 + ); + + const { + body: { data: firstPage }, + } = await findEvents(id, { per_page: 3 }); + + expect(firstPage.length).to.be(3); + assertEventsFromApiMatchCreatedEvents(firstPage, expectedFirstPage); + + const { + body: { data: secondPage }, + } = await findEvents(id, { per_page: 3, page: 2 }); + + expect(secondPage.length).to.be(3); + assertEventsFromApiMatchCreatedEvents(secondPage, expectedSecondPage); + }); + + it('should support sorting by event end', async () => { + const id = uuid.v4(); + + const timestamp = moment(); + const [firstExpectedEvent, ...expectedEvents] = times(6, () => + fakeEvent(id, fakeEventTiming(timestamp.add(1, 's'))) + ); + // run one first to create the SO and avoid clashes + await logTestEvent(id, firstExpectedEvent); + await Promise.all(expectedEvents.map(event => logTestEvent(id, event))); + + await retry.try(async () => { + const { + body: { data: foundEvents }, + } = await findEvents(id, { sort_field: 'event.end', sort_order: 'desc' }); + + expect(foundEvents.length).to.be(6); + assertEventsFromApiMatchCreatedEvents( + foundEvents, + [firstExpectedEvent, ...expectedEvents].reverse() + ); + }); + }); + + it('should support date ranges for events', async () => { + const id = uuid.v4(); + + const timestamp = moment(); + + const firstEvent = fakeEvent(id, fakeEventTiming(timestamp)); + await logTestEvent(id, firstEvent); + await delay(100); + + const start = timestamp.add(1, 's').toISOString(); + + const expectedEvents = times(6, () => fakeEvent(id, fakeEventTiming(timestamp.add(1, 's')))); + await Promise.all(expectedEvents.map(event => logTestEvent(id, event))); + + const end = timestamp.add(1, 's').toISOString(); + + await delay(100); + const lastEvent = fakeEvent(id, fakeEventTiming(timestamp.add(1, 's'))); + await logTestEvent(id, lastEvent); + + await retry.try(async () => { + const { + body: { data: foundEvents, total }, + } = await findEvents(id, {}); + + expect(foundEvents.length).to.be(8); + expect(total).to.be(8); + }); + + const { + body: { data: eventsWithinRange }, + } = await findEvents(id, { start, end }); + + expect(eventsWithinRange.length).to.be(expectedEvents.length); + assertEventsFromApiMatchCreatedEvents(eventsWithinRange, expectedEvents); + + const { + body: { data: eventsFrom }, + } = await findEvents(id, { start }); + + expect(eventsFrom.length).to.be(expectedEvents.length + 1); + assertEventsFromApiMatchCreatedEvents(eventsFrom, [...expectedEvents, lastEvent]); + + const { + body: { data: eventsUntil }, + } = await findEvents(id, { end }); + + expect(eventsUntil.length).to.be(expectedEvents.length + 1); + assertEventsFromApiMatchCreatedEvents(eventsUntil, [firstEvent, ...expectedEvents]); + }); + }); + + async function findEvents(id: string, query: Record = {}) { + const uri = `/api/event_log/event_log_test/${id}/_find${ + isEmpty(query) + ? '' + : `?${Object.entries(query) + .map(([key, val]) => `${key}=${val}`) + .join('&')}` + }`; + log.debug(`calling ${uri}`); + return await supertest + .get(uri) + .set('kbn-xsrf', 'foo') + .expect(200); + } + + function assertEventsFromApiMatchCreatedEvents( + foundEvents: IValidatedEvent[], + expectedEvents: IEvent[] + ) { + try { + foundEvents.forEach((foundEvent: IValidatedEvent, index: number) => { + expect(foundEvent!.event).to.eql(expectedEvents[index]!.event); + expect(omit(foundEvent!.kibana ?? {}, 'server_uuid')).to.eql(expectedEvents[index]!.kibana); + expect(foundEvent!.message).to.eql(expectedEvents[index]!.message); + }); + } catch (ex) { + log.debug(`failed to match ${JSON.stringify({ foundEvents, expectedEvents })}`); + throw ex; + } + } + + async function logTestEvent(id: string, event: IEvent) { + log.debug(`Logging Event for Saved Object ${id}`); + return await supertest + .post(`/api/log_event_fixture/${id}/_log`) + .set('kbn-xsrf', 'foo') + .send(event) + .expect(200); + } + + function fakeEventTiming(start: Moment): Partial { + return { + event: { + start: start.toISOString(), + end: start + .clone() + .add(500, 'milliseconds') + .toISOString(), + }, + }; + } + + function fakeEvent(id: string, overrides: Partial = {}): IEvent { + const start = moment().toISOString(); + const end = moment().toISOString(); + return merge( + { + event: { + provider: 'event_log_fixture', + action: 'test', + start, + end, + duration: 1000000, + }, + kibana: { + namespace: 'default', + saved_objects: [ + { + type: 'event_log_test', + id, + }, + ], + }, + message: `test ${moment().toISOString()}`, + }, + overrides + ); + } +} diff --git a/x-pack/test/plugin_api_integration/test_suites/event_log/service_api_integration.ts b/x-pack/test/plugin_api_integration/test_suites/event_log/service_api_integration.ts new file mode 100644 index 0000000000000..b055b22879bf9 --- /dev/null +++ b/x-pack/test/plugin_api_integration/test_suites/event_log/service_api_integration.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export default function() { + describe('Event Log service API', () => { + it('should allow logging an event', async () => {}); + }); +} From d67f2220b34a887a0c7564d64bd1472de87dcc4e Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Mon, 6 Apr 2020 20:22:46 +0300 Subject: [PATCH 06/36] [SIEM][CASE] Configuration page tests (#61093) * Test ClosureOptionsRadio component * Test ClosureOptions component * Test ConnectorsDropdown component * Test Connectors * Test FieldMappingRow * Test FieldMapping * Create utils functions and refactor to be able to test * Test Mapping * Improve tests * Test ConfigureCases * Refactor tests * Fix flacky tests * Remove snapshots * Refactor tests * Test button * Test reducer * Move test * Better structure Co-authored-by: Elastic Machine --- .../configure_cases/__mock__/index.tsx | 122 +++ .../configure_cases/button.test.tsx | 114 +++ .../components/configure_cases/button.tsx | 10 +- .../configure_cases/closure_options.test.tsx | 67 ++ .../configure_cases/closure_options.tsx | 10 +- .../closure_options_radio.test.tsx | 79 ++ .../configure_cases/closure_options_radio.tsx | 3 +- .../configure_cases/connectors.test.tsx | 90 +++ .../components/configure_cases/connectors.tsx | 16 +- .../connectors_dropdown.test.tsx | 86 ++ .../configure_cases/connectors_dropdown.tsx | 7 +- .../configure_cases/field_mapping.test.tsx | 84 ++ .../configure_cases/field_mapping.tsx | 31 +- .../field_mapping_row.test.tsx | 106 +++ .../configure_cases/field_mapping_row.tsx | 4 +- .../components/configure_cases/index.test.tsx | 748 ++++++++++++++++++ .../case/components/configure_cases/index.tsx | 16 +- .../configure_cases/mapping.test.tsx | 65 ++ .../components/configure_cases/mapping.tsx | 13 +- .../configure_cases/reducer.test.ts | 68 ++ .../components/configure_cases/utils.test.tsx | 63 ++ .../case/components/configure_cases/utils.ts | 44 ++ 22 files changed, 1807 insertions(+), 39 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/__mock__/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/reducer.test.ts create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/utils.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/utils.ts diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/__mock__/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/__mock__/index.tsx new file mode 100644 index 0000000000000..a3df3664398ad --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/__mock__/index.tsx @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + Connector, + CasesConfigurationMapping, +} from '../../../../../containers/case/configure/types'; +import { State } from '../reducer'; +import { ReturnConnectors } from '../../../../../containers/case/configure/use_connectors'; +import { ReturnUseCaseConfigure } from '../../../../../containers/case/configure/use_configure'; +import { createUseKibanaMock } from '../../../../../mock/kibana_react'; + +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { actionTypeRegistryMock } from '../../../../../../../../../plugins/triggers_actions_ui/public/application/action_type_registry.mock'; + +export const connectors: Connector[] = [ + { + id: '123', + actionTypeId: '.servicenow', + name: 'My Connector', + config: { + apiUrl: 'https://instance1.service-now.com', + casesConfiguration: { + mapping: [ + { + source: 'title', + target: 'short_description', + actionType: 'overwrite', + }, + { + source: 'description', + target: 'description', + actionType: 'append', + }, + { + source: 'comments', + target: 'comments', + actionType: 'append', + }, + ], + }, + }, + }, + { + id: '456', + actionTypeId: '.servicenow', + name: 'My Connector 2', + config: { + apiUrl: 'https://instance2.service-now.com', + casesConfiguration: { + mapping: [ + { + source: 'title', + target: 'short_description', + actionType: 'overwrite', + }, + { + source: 'description', + target: 'description', + actionType: 'overwrite', + }, + { + source: 'comments', + target: 'comments', + actionType: 'append', + }, + ], + }, + }, + }, +]; + +export const mapping: CasesConfigurationMapping[] = [ + { + source: 'title', + target: 'short_description', + actionType: 'overwrite', + }, + { + source: 'description', + target: 'description', + actionType: 'append', + }, + { + source: 'comments', + target: 'comments', + actionType: 'append', + }, +]; + +export const searchURL = + '?timerange=(global:(linkTo:!(),timerange:(from:1585487656371,fromStr:now-24h,kind:relative,to:1585574056371,toStr:now)),timeline:(linkTo:!(),timerange:(from:1585227005527,kind:absolute,to:1585313405527)))'; + +export const initialState: State = { + connectorId: 'none', + closureType: 'close-by-user', + mapping: null, + currentConfiguration: { connectorId: 'none', closureType: 'close-by-user' }, +}; + +export const useCaseConfigureResponse: ReturnUseCaseConfigure = { + loading: false, + persistLoading: false, + refetchCaseConfigure: jest.fn(), + persistCaseConfigure: jest.fn(), +}; + +export const useConnectorsResponse: ReturnConnectors = { + loading: false, + connectors, + refetchConnectors: jest.fn(), +}; + +export const kibanaMockImplementationArgs = { + services: { + ...createUseKibanaMock()().services, + triggers_actions_ui: { actionTypeRegistry: actionTypeRegistryMock.create() }, + }, +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.test.tsx new file mode 100644 index 0000000000000..cf52fef94ed17 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.test.tsx @@ -0,0 +1,114 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { ReactWrapper, mount } from 'enzyme'; +import { EuiText } from '@elastic/eui'; + +import { ConfigureCaseButton, ConfigureCaseButtonProps } from './button'; +import { TestProviders } from '../../../../mock'; +import { searchURL } from './__mock__'; + +describe('Configuration button', () => { + let wrapper: ReactWrapper; + const props: ConfigureCaseButtonProps = { + isDisabled: false, + label: 'My label', + msgTooltip: <>, + showToolTip: false, + titleTooltip: '', + urlSearch: searchURL, + }; + + beforeAll(() => { + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it renders without the tooltip', () => { + expect( + wrapper + .find('[data-test-subj="configure-case-button"]') + .first() + .exists() + ).toBe(true); + + expect( + wrapper + .find('[data-test-subj="configure-case-tooltip"]') + .first() + .exists() + ).toBe(false); + }); + + test('it pass the correct props to the button', () => { + expect( + wrapper + .find('[data-test-subj="configure-case-button"]') + .first() + .props() + ).toMatchObject({ + href: `#/link-to/case/configure${searchURL}`, + iconType: 'controlsHorizontal', + isDisabled: false, + 'aria-label': 'My label', + children: 'My label', + }); + }); + + test('it renders the tooltip', () => { + const msgTooltip = {'My message tooltip'}; + + const newWrapper = mount( + , + { + wrappingComponent: TestProviders, + } + ); + + expect( + newWrapper + .find('[data-test-subj="configure-case-tooltip"]') + .first() + .exists() + ).toBe(true); + + expect( + wrapper + .find('[data-test-subj="configure-case-button"]') + .first() + .exists() + ).toBe(true); + }); + + test('it shows the tooltip when hovering the button', () => { + const msgTooltip = 'My message tooltip'; + const titleTooltip = 'My title'; + + const newWrapper = mount( + {msgTooltip}} + />, + { + wrappingComponent: TestProviders, + } + ); + + newWrapper + .find('[data-test-subj="configure-case-button"]') + .first() + .simulate('mouseOver'); + + expect(newWrapper.find('.euiToolTipPopover').text()).toBe(`${titleTooltip}${msgTooltip}`); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.tsx index b0bea83148bda..844ffea28415f 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.tsx @@ -8,7 +8,7 @@ import { EuiButton, EuiToolTip } from '@elastic/eui'; import React, { memo, useMemo } from 'react'; import { getConfigureCasesUrl } from '../../../../components/link_to'; -interface ConfigureCaseButtonProps { +export interface ConfigureCaseButtonProps { label: string; isDisabled: boolean; msgTooltip: JSX.Element; @@ -32,6 +32,7 @@ const ConfigureCaseButtonComponent: React.FC = ({ iconType="controlsHorizontal" isDisabled={isDisabled} aria-label={label} + data-test-subj="configure-case-button" > {label} @@ -39,7 +40,12 @@ const ConfigureCaseButtonComponent: React.FC = ({ [label, isDisabled, urlSearch] ); return showToolTip ? ( - {msgTooltip}

}> + {msgTooltip}

} + data-test-subj="configure-case-tooltip" + > {configureCaseButton}
) : ( diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.test.tsx new file mode 100644 index 0000000000000..209dce9aedffc --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.test.tsx @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount, ReactWrapper } from 'enzyme'; + +import { ClosureOptions, ClosureOptionsProps } from './closure_options'; +import { TestProviders } from '../../../../mock'; +import { ClosureOptionsRadio } from './closure_options_radio'; + +describe('ClosureOptions', () => { + let wrapper: ReactWrapper; + const onChangeClosureType = jest.fn(); + const props: ClosureOptionsProps = { + disabled: false, + closureTypeSelected: 'close-by-user', + onChangeClosureType, + }; + + beforeAll(() => { + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it shows the closure options form group', () => { + expect( + wrapper + .find('[data-test-subj="case-closure-options-form-group"]') + .first() + .exists() + ).toBe(true); + }); + + test('it shows the closure options form row', () => { + expect( + wrapper + .find('[data-test-subj="case-closure-options-form-row"]') + .first() + .exists() + ).toBe(true); + }); + + test('it shows closure options', () => { + expect( + wrapper + .find('[data-test-subj="case-closure-options-radio"]') + .first() + .exists() + ).toBe(true); + }); + + test('it pass the correct props to child', () => { + const closureOptionsRadioComponent = wrapper.find(ClosureOptionsRadio); + expect(closureOptionsRadioComponent.props().disabled).toEqual(false); + expect(closureOptionsRadioComponent.props().closureTypeSelected).toEqual('close-by-user'); + expect(closureOptionsRadioComponent.props().onChangeClosureType).toEqual(onChangeClosureType); + }); + + test('the closure type is changed successfully', () => { + wrapper.find('input[id="close-by-pushing"]').simulate('change'); + + expect(onChangeClosureType).toHaveBeenCalled(); + expect(onChangeClosureType).toHaveBeenCalledWith('close-by-pushing'); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.tsx index 9879b9149059a..6fa97818dd0ce 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.tsx @@ -11,7 +11,7 @@ import { ClosureType } from '../../../../containers/case/configure/types'; import { ClosureOptionsRadio } from './closure_options_radio'; import * as i18n from './translations'; -interface ClosureOptionsProps { +export interface ClosureOptionsProps { closureTypeSelected: ClosureType; disabled: boolean; onChangeClosureType: (newClosureType: ClosureType) => void; @@ -27,12 +27,18 @@ const ClosureOptionsComponent: React.FC = ({ fullWidth title={

{i18n.CASE_CLOSURE_OPTIONS_TITLE}

} description={i18n.CASE_CLOSURE_OPTIONS_DESC} + data-test-subj="case-closure-options-form-group" > - + diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.test.tsx new file mode 100644 index 0000000000000..f2ef2c2d55c28 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.test.tsx @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { ReactWrapper, mount } from 'enzyme'; + +import { ClosureOptionsRadio, ClosureOptionsRadioComponentProps } from './closure_options_radio'; +import { TestProviders } from '../../../../mock'; + +describe('ClosureOptionsRadio', () => { + let wrapper: ReactWrapper; + const onChangeClosureType = jest.fn(); + const props: ClosureOptionsRadioComponentProps = { + disabled: false, + closureTypeSelected: 'close-by-user', + onChangeClosureType, + }; + + beforeAll(() => { + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it renders', () => { + expect( + wrapper + .find('[data-test-subj="closure-options-radio-group"]') + .first() + .exists() + ).toBe(true); + }); + + test('it shows the correct number of radio buttons', () => { + expect(wrapper.find('input[name="closure_options"]')).toHaveLength(2); + }); + + test('it renders close by user radio button', () => { + expect(wrapper.find('input[id="close-by-user"]').exists()).toBeTruthy(); + }); + + test('it renders close by pushing radio button', () => { + expect(wrapper.find('input[id="close-by-pushing"]').exists()).toBeTruthy(); + }); + + test('it disables the close by user radio button', () => { + const newWrapper = mount(, { + wrappingComponent: TestProviders, + }); + + expect(newWrapper.find('input[id="close-by-user"]').prop('disabled')).toEqual(true); + }); + + test('it disables correctly the close by pushing radio button', () => { + const newWrapper = mount(, { + wrappingComponent: TestProviders, + }); + + expect(newWrapper.find('input[id="close-by-pushing"]').prop('disabled')).toEqual(true); + }); + + test('it selects the correct radio button', () => { + const newWrapper = mount( + , + { + wrappingComponent: TestProviders, + } + ); + expect(newWrapper.find('input[id="close-by-pushing"]').prop('checked')).toEqual(true); + }); + + test('it calls the onChangeClosureType function', () => { + wrapper.find('input[id="close-by-pushing"]').simulate('change'); + wrapper.update(); + expect(onChangeClosureType).toHaveBeenCalled(); + expect(onChangeClosureType).toHaveBeenCalledWith('close-by-pushing'); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.tsx index f32f867b2471d..d2cdb7ecda7ba 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.tsx @@ -26,7 +26,7 @@ const radios: ClosureRadios[] = [ }, ]; -interface ClosureOptionsRadioComponentProps { +export interface ClosureOptionsRadioComponentProps { closureTypeSelected: ClosureType; disabled: boolean; onChangeClosureType: (newClosureType: ClosureType) => void; @@ -51,6 +51,7 @@ const ClosureOptionsRadioComponent: React.FC idSelected={closureTypeSelected} onChange={onChangeLocal} name="closure_options" + data-test-subj="closure-options-radio-group" /> ); }; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.test.tsx new file mode 100644 index 0000000000000..5fb52c374b482 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.test.tsx @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount, ReactWrapper } from 'enzyme'; + +import { Connectors, Props } from './connectors'; +import { TestProviders } from '../../../../mock'; +import { ConnectorsDropdown } from './connectors_dropdown'; +import { connectors } from './__mock__'; + +describe('Connectors', () => { + let wrapper: ReactWrapper; + const onChangeConnector = jest.fn(); + const handleShowAddFlyout = jest.fn(); + const props: Props = { + disabled: false, + connectors, + selectedConnector: 'none', + isLoading: false, + onChangeConnector, + handleShowAddFlyout, + }; + + beforeAll(() => { + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it shows the connectors from group', () => { + expect( + wrapper + .find('[data-test-subj="case-connectors-form-group"]') + .first() + .exists() + ).toBe(true); + }); + + test('it shows the connectors form row', () => { + expect( + wrapper + .find('[data-test-subj="case-connectors-form-row"]') + .first() + .exists() + ).toBe(true); + }); + + test('it shows the connectors dropdown', () => { + expect( + wrapper + .find('[data-test-subj="case-connectors-dropdown"]') + .first() + .exists() + ).toBe(true); + }); + + test('it pass the correct props to child', () => { + const connectorsDropdownProps = wrapper.find(ConnectorsDropdown).props(); + expect(connectorsDropdownProps).toMatchObject({ + disabled: false, + isLoading: false, + connectors, + selectedConnector: 'none', + onChange: props.onChangeConnector, + }); + }); + + test('the connector is changed successfully', () => { + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + wrapper.find('button[data-test-subj="dropdown-connector-456"]').simulate('click'); + + expect(onChangeConnector).toHaveBeenCalled(); + expect(onChangeConnector).toHaveBeenCalledWith('456'); + }); + + test('the connector is changed successfully to none', () => { + onChangeConnector.mockClear(); + const newWrapper = mount(, { + wrappingComponent: TestProviders, + }); + + newWrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + newWrapper.find('button[data-test-subj="dropdown-connector-no-connector"]').simulate('click'); + + expect(onChangeConnector).toHaveBeenCalled(); + expect(onChangeConnector).toHaveBeenCalledWith('none'); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.tsx index 8fb1cfb1aa6cc..de6d5f76cfad0 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.tsx @@ -28,7 +28,7 @@ const EuiFormRowExtended = styled(EuiFormRow)` } `; -interface Props { +export interface Props { connectors: Connector[]; disabled: boolean; isLoading: boolean; @@ -48,7 +48,11 @@ const ConnectorsComponent: React.FC = ({ {i18n.INCIDENT_MANAGEMENT_SYSTEM_LABEL} - + {i18n.ADD_NEW_CONNECTOR} @@ -61,14 +65,20 @@ const ConnectorsComponent: React.FC = ({ fullWidth title={

{i18n.INCIDENT_MANAGEMENT_SYSTEM_TITLE}

} description={i18n.INCIDENT_MANAGEMENT_SYSTEM_DESC} + data-test-subj="case-connectors-form-group" > - + diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.test.tsx new file mode 100644 index 0000000000000..044108962efc7 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.test.tsx @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount, ReactWrapper } from 'enzyme'; +import { EuiSuperSelect } from '@elastic/eui'; + +import { ConnectorsDropdown, Props } from './connectors_dropdown'; +import { TestProviders } from '../../../../mock'; +import { connectors } from './__mock__'; + +describe('ConnectorsDropdown', () => { + let wrapper: ReactWrapper; + const props: Props = { + disabled: false, + connectors, + isLoading: false, + onChange: jest.fn(), + selectedConnector: 'none', + }; + + beforeAll(() => { + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it renders', () => { + expect( + wrapper + .find('[data-test-subj="dropdown-connectors"]') + .first() + .exists() + ).toBe(true); + }); + + test('it formats the connectors correctly', () => { + const selectProps = wrapper.find(EuiSuperSelect).props(); + + expect(selectProps.options).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + value: 'none', + 'data-test-subj': 'dropdown-connector-no-connector', + }), + expect.objectContaining({ value: '123', 'data-test-subj': 'dropdown-connector-123' }), + expect.objectContaining({ value: '456', 'data-test-subj': 'dropdown-connector-456' }), + ]) + ); + }); + + test('it disables the dropdown', () => { + const newWrapper = mount(, { + wrappingComponent: TestProviders, + }); + + expect( + newWrapper + .find('[data-test-subj="dropdown-connectors"]') + .first() + .prop('disabled') + ).toEqual(true); + }); + + test('it loading correctly', () => { + const newWrapper = mount(, { + wrappingComponent: TestProviders, + }); + + expect( + newWrapper + .find('[data-test-subj="dropdown-connectors"]') + .first() + .prop('isLoading') + ).toEqual(true); + }); + + test('it selects the correct connector', () => { + const newWrapper = mount(, { + wrappingComponent: TestProviders, + }); + + expect(newWrapper.find('button span').text()).toEqual('My Connector'); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.tsx index a0a0ad6cd3e7f..15066e73eee82 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.tsx @@ -12,7 +12,7 @@ import { Connector } from '../../../../containers/case/configure/types'; import { connectors as connectorsDefinition } from '../../../../lib/connectors/config'; import * as i18n from './translations'; -interface Props { +export interface Props { connectors: Connector[]; disabled: boolean; isLoading: boolean; @@ -34,7 +34,7 @@ const noConnectorOption = { {i18n.NO_CONNECTOR} ), - 'data-test-subj': 'no-connector', + 'data-test-subj': 'dropdown-connector-no-connector', }; const ConnectorsDropdownComponent: React.FC = ({ @@ -60,7 +60,7 @@ const ConnectorsDropdownComponent: React.FC = ({ {connector.name} ), - 'data-test-subj': connector.id, + 'data-test-subj': `dropdown-connector-${connector.id}`, }, ], [noConnectorOption] @@ -76,6 +76,7 @@ const ConnectorsDropdownComponent: React.FC = ({ valueOfSelected={selectedConnector} fullWidth onChange={onChange} + data-test-subj="dropdown-connectors" /> ); }; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.test.tsx new file mode 100644 index 0000000000000..9ab752bb589c0 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.test.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount, ReactWrapper } from 'enzyme'; + +import { FieldMapping, FieldMappingProps } from './field_mapping'; +import { mapping } from './__mock__'; +import { FieldMappingRow } from './field_mapping_row'; +import { defaultMapping } from '../../../../lib/connectors/config'; +import { TestProviders } from '../../../../mock'; + +describe('FieldMappingRow', () => { + let wrapper: ReactWrapper; + const onChangeMapping = jest.fn(); + const props: FieldMappingProps = { + disabled: false, + mapping, + onChangeMapping, + }; + + beforeAll(() => { + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it renders', () => { + expect( + wrapper + .find('[data-test-subj="case-configure-field-mapping-cols"]') + .first() + .exists() + ).toBe(true); + + expect( + wrapper + .find('[data-test-subj="case-configure-field-mapping-row-wrapper"]') + .first() + .exists() + ).toBe(true); + + expect(wrapper.find(FieldMappingRow).length).toEqual(3); + }); + + test('it shows the correct number of FieldMappingRow with default mapping', () => { + const newWrapper = mount(, { + wrappingComponent: TestProviders, + }); + + expect(newWrapper.find(FieldMappingRow).length).toEqual(3); + }); + + test('it pass the corrects props to mapping row', () => { + const rows = wrapper.find(FieldMappingRow); + rows.forEach((row, index) => { + expect(row.prop('siemField')).toEqual(mapping[index].source); + expect(row.prop('selectedActionType')).toEqual(mapping[index].actionType); + expect(row.prop('selectedThirdParty')).toEqual(mapping[index].target); + }); + }); + + test('it pass the default mapping when mapping is null', () => { + const newWrapper = mount(, { + wrappingComponent: TestProviders, + }); + + const rows = newWrapper.find(FieldMappingRow); + rows.forEach((row, index) => { + expect(row.prop('siemField')).toEqual(defaultMapping[index].source); + expect(row.prop('selectedActionType')).toEqual(defaultMapping[index].actionType); + expect(row.prop('selectedThirdParty')).toEqual(defaultMapping[index].target); + }); + }); + + test('it should show zero rows on empty array', () => { + const newWrapper = mount(, { + wrappingComponent: TestProviders, + }); + + expect(newWrapper.find(FieldMappingRow).length).toEqual(0); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx index 0c0dc14f1c218..2934b1056e29c 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx @@ -18,6 +18,7 @@ import { FieldMappingRow } from './field_mapping_row'; import * as i18n from './translations'; import { defaultMapping } from '../../../../lib/connectors/config'; +import { setActionTypeToMapping, setThirdPartyToMapping } from './utils'; const FieldRowWrapper = styled.div` margin-top: 8px; @@ -28,22 +29,26 @@ const supportedThirdPartyFields: Array> = { value: 'not_mapped', inputDisplay: {i18n.FIELD_MAPPING_FIELD_NOT_MAPPED}, + 'data-test-subj': 'third-party-field-not-mapped', }, { value: 'short_description', inputDisplay: {i18n.FIELD_MAPPING_FIELD_SHORT_DESC}, + 'data-test-subj': 'third-party-field-short-description', }, { value: 'comments', inputDisplay: {i18n.FIELD_MAPPING_FIELD_COMMENTS}, + 'data-test-subj': 'third-party-field-comments', }, { value: 'description', inputDisplay: {i18n.FIELD_MAPPING_FIELD_DESC}, + 'data-test-subj': 'third-party-field-description', }, ]; -interface FieldMappingProps { +export interface FieldMappingProps { disabled: boolean; mapping: CasesConfigurationMapping[] | null; onChangeMapping: (newMapping: CasesConfigurationMapping[]) => void; @@ -57,14 +62,7 @@ const FieldMappingComponent: React.FC = ({ const onChangeActionType = useCallback( (caseField: CaseField, newActionType: ActionType) => { const myMapping = mapping ?? defaultMapping; - const findItemIndex = myMapping.findIndex(item => item.source === caseField); - if (findItemIndex >= 0) { - onChangeMapping([ - ...myMapping.slice(0, findItemIndex), - { ...myMapping[findItemIndex], actionType: newActionType }, - ...myMapping.slice(findItemIndex + 1), - ]); - } + onChangeMapping(setActionTypeToMapping(caseField, newActionType, myMapping)); }, [mapping] ); @@ -72,22 +70,13 @@ const FieldMappingComponent: React.FC = ({ const onChangeThirdParty = useCallback( (caseField: CaseField, newThirdPartyField: ThirdPartyField) => { const myMapping = mapping ?? defaultMapping; - onChangeMapping( - myMapping.map(item => { - if (item.source !== caseField && item.target === newThirdPartyField) { - return { ...item, target: 'not_mapped' }; - } else if (item.source === caseField) { - return { ...item, target: newThirdPartyField }; - } - return item; - }) - ); + onChangeMapping(setThirdPartyToMapping(caseField, newThirdPartyField, myMapping)); }, [mapping] ); return ( <> - + {i18n.FIELD_MAPPING_FIRST_COL} @@ -100,7 +89,7 @@ const FieldMappingComponent: React.FC = ({ - + {(mapping ?? defaultMapping).map(item => ( > = [ + { + value: 'short_description', + inputDisplay: {'Short Description'}, + 'data-test-subj': 'third-party-short-desc', + }, + { + value: 'description', + inputDisplay: {'Description'}, + 'data-test-subj': 'third-party-desc', + }, +]; + +describe('FieldMappingRow', () => { + let wrapper: ReactWrapper; + const onChangeActionType = jest.fn(); + const onChangeThirdParty = jest.fn(); + + const props: RowProps = { + disabled: false, + siemField: 'title', + thirdPartyOptions, + onChangeActionType, + onChangeThirdParty, + selectedActionType: 'nothing', + selectedThirdParty: 'short_description', + }; + + beforeAll(() => { + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it renders', () => { + expect( + wrapper + .find('[data-test-subj="case-configure-third-party-select"]') + .first() + .exists() + ).toBe(true); + + expect( + wrapper + .find('[data-test-subj="case-configure-action-type-select"]') + .first() + .exists() + ).toBe(true); + }); + + test('it passes thirdPartyOptions correctly', () => { + const selectProps = wrapper + .find(EuiSuperSelect) + .first() + .props(); + + expect(selectProps.options).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + value: 'short_description', + 'data-test-subj': 'third-party-short-desc', + }), + expect.objectContaining({ + value: 'description', + 'data-test-subj': 'third-party-desc', + }), + ]) + ); + }); + + test('it passes the correct actionTypeOptions', () => { + const selectProps = wrapper + .find(EuiSuperSelect) + .at(1) + .props(); + + expect(selectProps.options).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + value: 'nothing', + 'data-test-subj': 'edit-update-option-nothing', + }), + expect.objectContaining({ + value: 'overwrite', + 'data-test-subj': 'edit-update-option-overwrite', + }), + expect.objectContaining({ + value: 'append', + 'data-test-subj': 'edit-update-option-append', + }), + ]) + ); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.tsx index 62e43c86af8d9..732a11a58d35a 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.tsx @@ -21,7 +21,7 @@ import { ThirdPartyField, } from '../../../../containers/case/configure/types'; -interface RowProps { +export interface RowProps { disabled: boolean; siemField: CaseField; thirdPartyOptions: Array>; @@ -77,6 +77,7 @@ const FieldMappingRowComponent: React.FC = ({ options={thirdPartyOptions} valueOfSelected={selectedThirdParty} onChange={onChangeThirdParty.bind(null, siemField)} + data-test-subj={'case-configure-third-party-select'} /> @@ -85,6 +86,7 @@ const FieldMappingRowComponent: React.FC = ({ options={actionTypeOptions} valueOfSelected={selectedActionType} onChange={onChangeActionType.bind(null, siemField)} + data-test-subj={'case-configure-action-type-select'} />
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.test.tsx new file mode 100644 index 0000000000000..5ea3f500c0349 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.test.tsx @@ -0,0 +1,748 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useEffect } from 'react'; +import { ReactWrapper, mount } from 'enzyme'; + +import { useKibana } from '../../../../lib/kibana'; +import { useConnectors } from '../../../../containers/case/configure/use_connectors'; +import { useCaseConfigure } from '../../../../containers/case/configure/use_configure'; +import { useGetUrlSearch } from '../../../../components/navigation/use_get_url_search'; + +import { + connectors, + searchURL, + useCaseConfigureResponse, + useConnectorsResponse, + kibanaMockImplementationArgs, +} from './__mock__'; + +jest.mock('../../../../lib/kibana'); +jest.mock('../../../../containers/case/configure/use_connectors'); +jest.mock('../../../../containers/case/configure/use_configure'); +jest.mock('../../../../components/navigation/use_get_url_search'); + +const useKibanaMock = useKibana as jest.Mock; +const useConnectorsMock = useConnectors as jest.Mock; +const useCaseConfigureMock = useCaseConfigure as jest.Mock; +const useGetUrlSearchMock = useGetUrlSearch as jest.Mock; + +import { ConfigureCases } from './'; +import { TestProviders } from '../../../../mock'; +import { Connectors } from './connectors'; +import { ClosureOptions } from './closure_options'; +import { Mapping } from './mapping'; +import { + ActionsConnectorsContextProvider, + ConnectorAddFlyout, + ConnectorEditFlyout, +} from '../../../../../../../../plugins/triggers_actions_ui/public'; +import { EuiBottomBar } from '@elastic/eui'; + +describe('rendering', () => { + let wrapper: ReactWrapper; + beforeEach(() => { + jest.resetAllMocks(); + useCaseConfigureMock.mockImplementation(() => useCaseConfigureResponse); + useConnectorsMock.mockImplementation(() => ({ ...useConnectorsResponse, connectors: [] })); + useKibanaMock.mockImplementation(() => kibanaMockImplementationArgs); + useGetUrlSearchMock.mockImplementation(() => searchURL); + + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it renders the Connectors', () => { + expect(wrapper.find('[data-test-subj="case-connectors-form-group"]').exists()).toBeTruthy(); + }); + + test('it renders the ClosureType', () => { + expect( + wrapper.find('[data-test-subj="case-closure-options-form-group"]').exists() + ).toBeTruthy(); + }); + + test('it renders the Mapping', () => { + expect(wrapper.find('[data-test-subj="case-mapping-form-group"]').exists()).toBeTruthy(); + }); + + test('it renders the ActionsConnectorsContextProvider', () => { + // Components from triggers_actions_ui do not have a data-test-subj + expect(wrapper.find(ActionsConnectorsContextProvider).exists()).toBeTruthy(); + }); + + test('it renders the ConnectorAddFlyout', () => { + // Components from triggers_actions_ui do not have a data-test-subj + expect(wrapper.find(ConnectorAddFlyout).exists()).toBeTruthy(); + }); + + test('it does NOT render the ConnectorEditFlyout', () => { + // Components from triggers_actions_ui do not have a data-test-subj + expect(wrapper.find(ConnectorEditFlyout).exists()).toBeFalsy(); + }); + + test('it does NOT render the EuiCallOut', () => { + expect(wrapper.find('[data-test-subj="configure-cases-warning-callout"]').exists()).toBeFalsy(); + }); + + test('it does NOT render the EuiBottomBar', () => { + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeFalsy(); + }); +}); + +describe('ConfigureCases - Unhappy path', () => { + beforeEach(() => { + jest.resetAllMocks(); + useCaseConfigureMock.mockImplementation(() => useCaseConfigureResponse); + useConnectorsMock.mockImplementation(() => ({ ...useConnectorsResponse, connectors: [] })); + useKibanaMock.mockImplementation(() => kibanaMockImplementationArgs); + useGetUrlSearchMock.mockImplementation(() => searchURL); + }); + + test('it shows the warning callout when configuration is invalid', () => { + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('not-id'), []); + return useCaseConfigureResponse; + } + ); + + const wrapper = mount(, { wrappingComponent: TestProviders }); + + expect( + wrapper.find('[data-test-subj="configure-cases-warning-callout"]').exists() + ).toBeTruthy(); + }); +}); + +describe('ConfigureCases - Happy path', () => { + let wrapper: ReactWrapper; + + beforeEach(() => { + jest.resetAllMocks(); + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('123'), []); + useEffect(() => setClosureType('close-by-user'), []); + useEffect( + () => + setCurrentConfiguration({ + connectorId: '123', + closureType: 'close-by-user', + }), + [] + ); + return useCaseConfigureResponse; + } + ); + useConnectorsMock.mockImplementation(() => useConnectorsResponse); + useKibanaMock.mockImplementation(() => kibanaMockImplementationArgs); + useGetUrlSearchMock.mockImplementation(() => searchURL); + + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it renders the ConnectorEditFlyout', () => { + expect(wrapper.find(ConnectorEditFlyout).exists()).toBeTruthy(); + }); + + test('it renders with correct props', () => { + // Connector + expect(wrapper.find(Connectors).prop('connectors')).toEqual(connectors); + expect(wrapper.find(Connectors).prop('disabled')).toBe(false); + expect(wrapper.find(Connectors).prop('isLoading')).toBe(false); + expect(wrapper.find(Connectors).prop('selectedConnector')).toBe('123'); + + // ClosureOptions + expect(wrapper.find(ClosureOptions).prop('disabled')).toBe(false); + expect(wrapper.find(ClosureOptions).prop('closureTypeSelected')).toBe('close-by-user'); + + // Mapping + expect(wrapper.find(Mapping).prop('disabled')).toBe(true); + expect(wrapper.find(Mapping).prop('updateConnectorDisabled')).toBe(false); + expect(wrapper.find(Mapping).prop('mapping')).toEqual( + connectors[0].config.casesConfiguration.mapping + ); + + // Flyouts + expect(wrapper.find(ConnectorAddFlyout).prop('addFlyoutVisible')).toBe(false); + expect(wrapper.find(ConnectorAddFlyout).prop('actionTypes')).toEqual([ + { + id: '.servicenow', + name: 'ServiceNow', + enabled: true, + enabledInConfig: true, + enabledInLicense: true, + minimumLicenseRequired: 'platinum', + }, + ]); + + expect(wrapper.find(ConnectorEditFlyout).prop('editFlyoutVisible')).toBe(false); + expect(wrapper.find(ConnectorEditFlyout).prop('initialConnector')).toEqual(connectors[0]); + }); + + test('it disables correctly when the user cannot crud', () => { + const newWrapper = mount(, { + wrappingComponent: TestProviders, + }); + + expect(newWrapper.find(Connectors).prop('disabled')).toBe(true); + expect(newWrapper.find(ClosureOptions).prop('disabled')).toBe(true); + expect(newWrapper.find(Mapping).prop('disabled')).toBe(true); + expect(newWrapper.find(Mapping).prop('updateConnectorDisabled')).toBe(true); + }); + + test('it disables correctly Connector when loading connectors', () => { + useConnectorsMock.mockImplementation(() => ({ + ...useConnectorsResponse, + loading: true, + })); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(Connectors).prop('disabled')).toBe(true); + }); + + test('it disables correctly Connector when saving configuration', () => { + useCaseConfigureMock.mockImplementation(() => ({ + ...useCaseConfigureResponse, + persistLoading: true, + })); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(Connectors).prop('disabled')).toBe(true); + }); + + test('it pass the correct value to isLoading attribute on Connector', () => { + useConnectorsMock.mockImplementation(() => ({ + ...useConnectorsResponse, + loading: true, + })); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(Connectors).prop('isLoading')).toBe(true); + }); + + test('it set correctly the selected connector', () => { + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('456'), []); + return useCaseConfigureResponse; + } + ); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(Connectors).prop('selectedConnector')).toBe('456'); + }); + + test('it show the add flyout when pressing the add connector button', () => { + wrapper.find('button[data-test-subj="case-configure-add-connector-button"]').simulate('click'); + wrapper.update(); + + expect(wrapper.find(ConnectorAddFlyout).prop('addFlyoutVisible')).toBe(true); + expect(wrapper.find(EuiBottomBar).exists()).toBeFalsy(); + }); + + test('it disables correctly ClosureOptions when loading connectors', () => { + useConnectorsMock.mockImplementation(() => ({ + ...useConnectorsResponse, + loading: true, + })); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(ClosureOptions).prop('disabled')).toBe(true); + }); + + test('it disables correctly ClosureOptions when saving configuration', () => { + useCaseConfigureMock.mockImplementation(() => ({ + ...useCaseConfigureResponse, + persistLoading: true, + })); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(ClosureOptions).prop('disabled')).toBe(true); + }); + + test('it disables correctly ClosureOptions when the connector is set to none', () => { + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('none'), []); + return useCaseConfigureResponse; + } + ); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(ClosureOptions).prop('disabled')).toBe(true); + }); + + test('it disables the mapping permanently', () => { + expect(wrapper.find(Mapping).prop('disabled')).toBe(true); + }); + + test('it disables the update connector button when loading the connectors', () => { + useConnectorsMock.mockImplementation(() => ({ + ...useConnectorsResponse, + loading: true, + })); + + expect(wrapper.find(Mapping).prop('disabled')).toBe(true); + }); + + test('it disables the update connector button when loading the configuration', () => { + useCaseConfigureMock.mockImplementation(() => ({ + ...useCaseConfigureResponse, + loading: true, + })); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(Mapping).prop('disabled')).toBe(true); + }); + + test('it disables the update connector button when saving the configuration', () => { + useCaseConfigureMock.mockImplementation(() => ({ + ...useCaseConfigureResponse, + persistLoading: true, + })); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(Mapping).prop('disabled')).toBe(true); + }); + + test('it disables the update connector button when the connectorId is invalid', () => { + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('not-id'), []); + return useCaseConfigureResponse; + } + ); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(Mapping).prop('disabled')).toBe(true); + }); + + test('it disables the update connector button when the connectorId is set to none', () => { + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('none'), []); + return useCaseConfigureResponse; + } + ); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(Mapping).prop('disabled')).toBe(true); + }); + + test('it show the edit flyout when pressing the update connector button', () => { + wrapper.find('button[data-test-subj="case-mapping-update-connector-button"]').simulate('click'); + wrapper.update(); + + expect(wrapper.find(ConnectorEditFlyout).prop('editFlyoutVisible')).toBe(true); + expect(wrapper.find(EuiBottomBar).exists()).toBeFalsy(); + }); + + test('it sets the mapping of a connector correctly', () => { + expect(wrapper.find(Mapping).prop('mapping')).toEqual( + connectors[0].config.casesConfiguration.mapping + ); + }); + + // TODO: When mapping is enabled the test.todo should be implemented. + test.todo('the mapping is changed successfully when changing the third party'); + test.todo('the mapping is changed successfully when changing the action type'); + + test('it does not shows the action bar when there is no change', () => { + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeFalsy(); + }); + + test('it shows the action bar when the connector is changed', () => { + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + wrapper.update(); + wrapper.find('button[data-test-subj="dropdown-connector-456"]').simulate('click'); + wrapper.update(); + + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeTruthy(); + expect( + wrapper + .find('[data-test-subj="case-configure-action-bottom-bar-total-changes"]') + .first() + .text() + ).toBe('1 unsaved changes'); + }); + + test('it shows the action bar when the closure type is changed', () => { + wrapper.find('input[id="close-by-pushing"]').simulate('change'); + wrapper.update(); + + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeTruthy(); + expect( + wrapper + .find('[data-test-subj="case-configure-action-bottom-bar-total-changes"]') + .first() + .text() + ).toBe('1 unsaved changes'); + }); + + test('it tracks the changes successfully', () => { + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + wrapper.update(); + wrapper.find('button[data-test-subj="dropdown-connector-456"]').simulate('click'); + wrapper.update(); + wrapper.find('input[id="close-by-pushing"]').simulate('change'); + wrapper.update(); + + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeTruthy(); + expect( + wrapper + .find('[data-test-subj="case-configure-action-bottom-bar-total-changes"]') + .first() + .text() + ).toBe('2 unsaved changes'); + }); + + test('it tracks and reverts the changes successfully ', () => { + // change settings + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + wrapper.update(); + wrapper.find('button[data-test-subj="dropdown-connector-456"]').simulate('click'); + wrapper.update(); + wrapper.find('input[id="close-by-pushing"]').simulate('change'); + wrapper.update(); + + // revert back to initial settings + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + wrapper.update(); + wrapper.find('button[data-test-subj="dropdown-connector-123"]').simulate('click'); + wrapper.update(); + wrapper.find('input[id="close-by-user"]').simulate('change'); + wrapper.update(); + + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeFalsy(); + }); + + test('it close and restores the action bar when the add connector button is pressed', () => { + // Change closure type + wrapper.find('input[id="close-by-pushing"]').simulate('change'); + wrapper.update(); + + // Press add connector button + wrapper.find('button[data-test-subj="case-configure-add-connector-button"]').simulate('click'); + wrapper.update(); + + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeFalsy(); + + expect(wrapper.find(ConnectorAddFlyout).prop('addFlyoutVisible')).toBe(true); + + // Close the add flyout + wrapper.find('button[data-test-subj="euiFlyoutCloseButton"]').simulate('click'); + wrapper.update(); + + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeTruthy(); + + expect(wrapper.find(ConnectorAddFlyout).prop('addFlyoutVisible')).toBe(false); + + expect( + wrapper + .find('[data-test-subj="case-configure-action-bottom-bar-total-changes"]') + .first() + .text() + ).toBe('1 unsaved changes'); + }); + + test('it close and restores the action bar when the update connector button is pressed', () => { + // Change closure type + wrapper.find('input[id="close-by-pushing"]').simulate('change'); + wrapper.update(); + + // Press update connector button + wrapper.find('button[data-test-subj="case-mapping-update-connector-button"]').simulate('click'); + wrapper.update(); + + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeFalsy(); + + expect(wrapper.find(ConnectorEditFlyout).prop('editFlyoutVisible')).toBe(true); + + // Close the edit flyout + wrapper.find('button[data-test-subj="euiFlyoutCloseButton"]').simulate('click'); + wrapper.update(); + + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeTruthy(); + + expect(wrapper.find(ConnectorEditFlyout).prop('editFlyoutVisible')).toBe(false); + + expect( + wrapper + .find('[data-test-subj="case-configure-action-bottom-bar-total-changes"]') + .first() + .text() + ).toBe('1 unsaved changes'); + }); + + test('it disables the buttons of action bar when loading connectors', () => { + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('456'), []); + useEffect(() => setClosureType('close-by-user'), []); + useEffect( + () => + setCurrentConfiguration({ + connectorId: '123', + closureType: 'close-by-user', + }), + [] + ); + return useCaseConfigureResponse; + } + ); + + useConnectorsMock.mockImplementation(() => ({ + ...useConnectorsResponse, + loading: true, + })); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect( + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-cancel-button"]') + .first() + .prop('isDisabled') + ).toBe(true); + + expect( + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-save-button"]') + .first() + .prop('isDisabled') + ).toBe(true); + }); + + test('it disables the buttons of action bar when loading configuration', () => { + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('456'), []); + useEffect(() => setClosureType('close-by-user'), []); + useEffect( + () => + setCurrentConfiguration({ + connectorId: '123', + closureType: 'close-by-user', + }), + [] + ); + return { ...useCaseConfigureResponse, loading: true }; + } + ); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect( + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-cancel-button"]') + .first() + .prop('isDisabled') + ).toBe(true); + + expect( + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-save-button"]') + .first() + .prop('isDisabled') + ).toBe(true); + }); + + test('it disables the buttons of action bar when saving configuration', () => { + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('456'), []); + useEffect(() => setClosureType('close-by-user'), []); + useEffect( + () => + setCurrentConfiguration({ + connectorId: '123', + closureType: 'close-by-user', + }), + [] + ); + return { ...useCaseConfigureResponse, persistLoading: true }; + } + ); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect( + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-cancel-button"]') + .first() + .prop('isDisabled') + ).toBe(true); + + expect( + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-save-button"]') + .first() + .prop('isDisabled') + ).toBe(true); + }); + + test('it shows the loading spinner when saving configuration', () => { + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('456'), []); + useEffect(() => setClosureType('close-by-user'), []); + useEffect( + () => + setCurrentConfiguration({ + connectorId: '123', + closureType: 'close-by-user', + }), + [] + ); + return { ...useCaseConfigureResponse, persistLoading: true }; + } + ); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect( + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-cancel-button"]') + .first() + .prop('isLoading') + ).toBe(true); + + expect( + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-save-button"]') + .first() + .prop('isLoading') + ).toBe(true); + }); + + test('it closes the action bar when pressing save', () => { + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('456'), []); + useEffect(() => setClosureType('close-by-user'), []); + useEffect( + () => + setCurrentConfiguration({ + connectorId: '123', + closureType: 'close-by-user', + }), + [] + ); + return useCaseConfigureResponse; + } + ); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-save-button"]') + .first() + .simulate('click'); + + newWrapper.update(); + + expect( + newWrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeFalsy(); + }); + + test('it submits the configuration correctly', () => { + const persistCaseConfigure = jest.fn(); + + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('456'), []); + useEffect(() => setClosureType('close-by-user'), []); + useEffect( + () => + setCurrentConfiguration({ + connectorId: '123', + closureType: 'close-by-pushing', + }), + [] + ); + return { ...useCaseConfigureResponse, persistCaseConfigure }; + } + ); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-save-button"]') + .first() + .simulate('click'); + + newWrapper.update(); + + expect(persistCaseConfigure).toHaveBeenCalled(); + expect(persistCaseConfigure).toHaveBeenCalledWith({ + connectorId: '456', + connectorName: 'My Connector 2', + closureType: 'close-by-user', + }); + }); + + test('it has the correct url on cancel button', () => { + const persistCaseConfigure = jest.fn(); + + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('456'), []); + useEffect(() => setClosureType('close-by-user'), []); + useEffect( + () => + setCurrentConfiguration({ + connectorId: '123', + closureType: 'close-by-user', + }), + [] + ); + return { ...useCaseConfigureResponse, persistCaseConfigure }; + } + ); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect( + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-cancel-button"]') + .first() + .prop('href') + ).toBe(`#/link-to/case${searchURL}`); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx index b8cf5a3880801..241dcef14a145 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx @@ -140,6 +140,7 @@ const ConfigureCasesComponent: React.FC = ({ userC setClosureType, setCurrentConfiguration, }); + const { loading: isLoadingConnectors, connectors, refetchConnectors } = useConnectors(); // ActionsConnectorsContextProvider reloadConnectors prop expects a Promise. @@ -251,7 +252,12 @@ const ConfigureCasesComponent: React.FC = ({ userC {!connectorIsValid && ( - + {i18n.WARNING_NO_CONNECTOR_MESSAGE} @@ -283,11 +289,13 @@ const ConfigureCasesComponent: React.FC = ({ userC /> {actionBarVisible && ( - + - {i18n.UNSAVED_CHANGES(totalConfigurationChanges)} + + {i18n.UNSAVED_CHANGES(totalConfigurationChanges)} + @@ -300,6 +308,7 @@ const ConfigureCasesComponent: React.FC = ({ userC isLoading={persistLoading} aria-label={i18n.CANCEL} href={getCaseUrl(search)} + data-test-subj="case-configure-action-bottom-bar-cancel-button" > {i18n.CANCEL} @@ -313,6 +322,7 @@ const ConfigureCasesComponent: React.FC = ({ userC isDisabled={isLoadingAny} isLoading={persistLoading} onClick={handleSubmit} + data-test-subj="case-configure-action-bottom-bar-save-button" > {i18n.SAVE_CHANGES} diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.test.tsx new file mode 100644 index 0000000000000..fefcb2ca8cf6a --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.test.tsx @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount, ReactWrapper } from 'enzyme'; + +import { TestProviders } from '../../../../mock'; +import { Mapping, MappingProps } from './mapping'; +import { mapping } from './__mock__'; + +describe('Mapping', () => { + let wrapper: ReactWrapper; + const onChangeMapping = jest.fn(); + const setEditFlyoutVisibility = jest.fn(); + const props: MappingProps = { + disabled: false, + mapping, + updateConnectorDisabled: false, + onChangeMapping, + setEditFlyoutVisibility, + }; + + beforeAll(() => { + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it shows mapping form group', () => { + expect( + wrapper + .find('[data-test-subj="case-mapping-form-group"]') + .first() + .exists() + ).toBe(true); + }); + + test('it shows mapping form row', () => { + expect( + wrapper + .find('[data-test-subj="case-mapping-form-row"]') + .first() + .exists() + ).toBe(true); + }); + + test('it shows the update button', () => { + expect( + wrapper + .find('[data-test-subj="case-mapping-update-connector-button"]') + .first() + .exists() + ).toBe(true); + }); + + test('it shows the field mapping', () => { + expect( + wrapper + .find('[data-test-subj="case-mapping-field"]') + .first() + .exists() + ).toBe(true); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.tsx index 8cba73d1249df..7340a49f6d0bb 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.tsx @@ -20,7 +20,7 @@ import * as i18n from './translations'; import { FieldMapping } from './field_mapping'; import { CasesConfigurationMapping } from '../../../../containers/case/configure/types'; -interface MappingProps { +export interface MappingProps { disabled: boolean; updateConnectorDisabled: boolean; mapping: CasesConfigurationMapping[] | null; @@ -45,20 +45,27 @@ const MappingComponent: React.FC = ({ fullWidth title={

{i18n.FIELD_MAPPING_TITLE}

} description={i18n.FIELD_MAPPING_DESC} + data-test-subj="case-mapping-form-group" > - + {i18n.UPDATE_CONNECTOR} - + ); }; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/reducer.test.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/reducer.test.ts new file mode 100644 index 0000000000000..df958b75dc6b8 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/reducer.test.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { configureCasesReducer, Action, State } from './reducer'; +import { initialState, mapping } from './__mock__'; + +describe('Reducer', () => { + let reducer: (state: State, action: Action) => State; + + beforeAll(() => { + reducer = configureCasesReducer(); + }); + + test('it should set the correct configuration', () => { + const action: Action = { + type: 'setCurrentConfiguration', + currentConfiguration: { connectorId: '123', closureType: 'close-by-user' }, + }; + const state = reducer(initialState, action); + + expect(state).toEqual({ + ...state, + currentConfiguration: action.currentConfiguration, + }); + }); + + test('it should set the correct connector id', () => { + const action: Action = { + type: 'setConnectorId', + connectorId: '456', + }; + const state = reducer(initialState, action); + + expect(state).toEqual({ + ...state, + connectorId: action.connectorId, + }); + }); + + test('it should set the closure type', () => { + const action: Action = { + type: 'setClosureType', + closureType: 'close-by-pushing', + }; + const state = reducer(initialState, action); + + expect(state).toEqual({ + ...state, + closureType: action.closureType, + }); + }); + + test('it should set the mapping', () => { + const action: Action = { + type: 'setMapping', + mapping, + }; + const state = reducer(initialState, action); + + expect(state).toEqual({ + ...state, + mapping: action.mapping, + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/utils.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/utils.test.tsx new file mode 100644 index 0000000000000..1c6fc9b2d405f --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/utils.test.tsx @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { mapping } from './__mock__'; +import { setActionTypeToMapping, setThirdPartyToMapping } from './utils'; +import { CasesConfigurationMapping } from '../../../../containers/case/configure/types'; + +describe('FieldMappingRow', () => { + test('it should change the action type', () => { + const newMapping = setActionTypeToMapping('title', 'nothing', mapping); + expect(newMapping[0].actionType).toBe('nothing'); + }); + + test('it should not change other fields', () => { + const [newTitle, description, comments] = setActionTypeToMapping('title', 'nothing', mapping); + expect(newTitle).not.toEqual(mapping[0]); + expect(description).toEqual(mapping[1]); + expect(comments).toEqual(mapping[2]); + }); + + test('it should return a new array when changing action type', () => { + const newMapping = setActionTypeToMapping('title', 'nothing', mapping); + expect(newMapping).not.toBe(mapping); + }); + + test('it should change the third party', () => { + const newMapping = setThirdPartyToMapping('title', 'description', mapping); + expect(newMapping[0].target).toBe('description'); + }); + + test('it should not change other fields when there is not a conflict', () => { + const tempMapping: CasesConfigurationMapping[] = [ + { + source: 'title', + target: 'short_description', + actionType: 'overwrite', + }, + { + source: 'comments', + target: 'comments', + actionType: 'append', + }, + ]; + + const [newTitle, comments] = setThirdPartyToMapping('title', 'description', tempMapping); + + expect(newTitle).not.toEqual(mapping[0]); + expect(comments).toEqual(tempMapping[1]); + }); + + test('it should return a new array when changing third party', () => { + const newMapping = setThirdPartyToMapping('title', 'description', mapping); + expect(newMapping).not.toBe(mapping); + }); + + test('it should change the target of the conflicting third party field to not_mapped', () => { + const newMapping = setThirdPartyToMapping('title', 'description', mapping); + expect(newMapping[1].target).toBe('not_mapped'); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/utils.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/utils.ts new file mode 100644 index 0000000000000..2ac6cc1a38587 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/utils.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + CaseField, + ActionType, + CasesConfigurationMapping, + ThirdPartyField, +} from '../../../../containers/case/configure/types'; + +export const setActionTypeToMapping = ( + caseField: CaseField, + newActionType: ActionType, + mapping: CasesConfigurationMapping[] +): CasesConfigurationMapping[] => { + const findItemIndex = mapping.findIndex(item => item.source === caseField); + + if (findItemIndex >= 0) { + return [ + ...mapping.slice(0, findItemIndex), + { ...mapping[findItemIndex], actionType: newActionType }, + ...mapping.slice(findItemIndex + 1), + ]; + } + + return [...mapping]; +}; + +export const setThirdPartyToMapping = ( + caseField: CaseField, + newThirdPartyField: ThirdPartyField, + mapping: CasesConfigurationMapping[] +): CasesConfigurationMapping[] => + mapping.map(item => { + if (item.source !== caseField && item.target === newThirdPartyField) { + return { ...item, target: 'not_mapped' }; + } else if (item.source === caseField) { + return { ...item, target: newThirdPartyField }; + } + return item; + }); From 0ebfe76b3fa0cb104c6accf8469fe390ba239b40 Mon Sep 17 00:00:00 2001 From: patrykkopycinski Date: Mon, 6 Apr 2020 19:26:40 +0200 Subject: [PATCH 07/36] [SIEM][Detection Engine] Fix signals count in Rule notifications (#62311) --- .../notifications/get_signals_count.ts | 53 +-- .../rules_notification_alert_type.ts | 22 +- .../schedule_notification_actions.ts | 4 +- .../detection_engine/notifications/utils.ts | 13 +- .../signals/search_after_bulk_create.test.ts | 64 ++- .../signals/search_after_bulk_create.ts | 13 +- .../signals/signal_rule_alert_type.test.ts | 399 ++++++++++++++++++ .../signals/signal_rule_alert_type.ts | 31 +- .../signals/single_bulk_create.test.ts | 40 +- .../signals/single_bulk_create.ts | 8 +- 10 files changed, 567 insertions(+), 80 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/get_signals_count.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/get_signals_count.ts index 33cee6d074b70..7ff6a4e5164bd 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/get_signals_count.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/get_signals_count.ts @@ -4,63 +4,40 @@ * you may not use this file except in compliance with the Elastic License. */ -import moment from 'moment'; -import { getNotificationResultsLink } from './utils'; -import { NotificationExecutorOptions } from './types'; -import { parseScheduleDates } from '../signals/utils'; +import { AlertServices } from '../../../../../../../plugins/alerting/server'; import { buildSignalsSearchQuery } from './build_signals_query'; -interface SignalsCountResults { - signalsCount: string; - resultsLink: string; -} - interface GetSignalsCount { - from: Date | string; - to: Date | string; - ruleAlertId: string; + from?: string; + to?: string; ruleId: string; index: string; - kibanaSiemAppUrl: string | undefined; - callCluster: NotificationExecutorOptions['services']['callCluster']; + callCluster: AlertServices['callCluster']; +} + +interface CountResult { + count: number; } export const getSignalsCount = async ({ from, to, - ruleAlertId, ruleId, index, callCluster, - kibanaSiemAppUrl = '', -}: GetSignalsCount): Promise => { - const fromMoment = moment.isDate(from) ? moment(from) : parseScheduleDates(from); - const toMoment = moment.isDate(to) ? moment(to) : parseScheduleDates(to); - - if (!fromMoment || !toMoment) { - throw new Error(`There was an issue with parsing ${from} or ${to} into Moment object`); +}: GetSignalsCount): Promise => { + if (from == null || to == null) { + throw Error('"from" or "to" was not provided to signals count query'); } - const fromInMs = fromMoment.format('x'); - const toInMs = toMoment.format('x'); - const query = buildSignalsSearchQuery({ index, ruleId, - to: toInMs, - from: fromInMs, + to, + from, }); - const result = await callCluster('count', query); - const resultsLink = getNotificationResultsLink({ - kibanaSiemAppUrl: `${kibanaSiemAppUrl}`, - id: ruleAlertId, - from: fromInMs, - to: toInMs, - }); + const result: CountResult = await callCluster('count', query); - return { - signalsCount: result.count, - resultsLink, - }; + return result.count; }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts index e74da583e9193..546488caa5ee7 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts @@ -13,6 +13,8 @@ import { getSignalsCount } from './get_signals_count'; import { RuleAlertAttributes } from '../signals/types'; import { siemRuleActionGroups } from '../signals/siem_rule_action_groups'; import { scheduleNotificationActions } from './schedule_notification_actions'; +import { getNotificationResultsLink } from './utils'; +import { parseScheduleDates } from '../signals/utils'; export const rulesNotificationAlertType = ({ logger, @@ -42,16 +44,26 @@ export const rulesNotificationAlertType = ({ const { params: ruleAlertParams, name: ruleName } = ruleAlertSavedObject.attributes; const ruleParams = { ...ruleAlertParams, name: ruleName, id: ruleAlertSavedObject.id }; - const { signalsCount, resultsLink } = await getSignalsCount({ - from: previousStartedAt ?? `now-${ruleParams.interval}`, - to: startedAt, + const fromInMs = parseScheduleDates( + previousStartedAt ? previousStartedAt.toISOString() : `now-${ruleParams.interval}` + )?.format('x'); + const toInMs = parseScheduleDates(startedAt.toISOString())?.format('x'); + + const signalsCount = await getSignalsCount({ + from: fromInMs, + to: toInMs, index: ruleParams.outputIndex, ruleId: ruleParams.ruleId!, - kibanaSiemAppUrl: ruleAlertParams.meta?.kibanaSiemAppUrl as string, - ruleAlertId: ruleAlertSavedObject.id, callCluster: services.callCluster, }); + const resultsLink = getNotificationResultsLink({ + from: fromInMs, + to: toInMs, + id: ruleAlertSavedObject.id, + kibanaSiemAppUrl: ruleAlertParams.meta?.kibanaSiemAppUrl as string, + }); + logger.info( `Found ${signalsCount} signals using signal rule name: "${ruleParams.name}", id: "${params.ruleAlertId}", rule_id: "${ruleParams.ruleId}" in "${ruleParams.outputIndex}" index` ); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/schedule_notification_actions.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/schedule_notification_actions.ts index b858b25377ffe..749b892ef506f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/schedule_notification_actions.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/schedule_notification_actions.ts @@ -15,7 +15,7 @@ type NotificationRuleTypeParams = RuleTypeParams & { interface ScheduleNotificationActions { alertInstance: AlertInstance; - signalsCount: string; + signalsCount: number; resultsLink: string; ruleParams: NotificationRuleTypeParams; } @@ -23,7 +23,7 @@ interface ScheduleNotificationActions { export const scheduleNotificationActions = ({ alertInstance, signalsCount, - resultsLink, + resultsLink = '', ruleParams, }: ScheduleNotificationActions): AlertInstance => alertInstance diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/utils.ts index b8a3c4199c4f0..5dc7e7fc30b7f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/utils.ts @@ -5,14 +5,17 @@ */ export const getNotificationResultsLink = ({ - kibanaSiemAppUrl, + kibanaSiemAppUrl = '/app/siem', id, from, to, }: { kibanaSiemAppUrl: string; id: string; - from: string; - to: string; -}) => - `${kibanaSiemAppUrl}#/detections/rules/id/${id}?timerange=(global:(linkTo:!(timeline),timerange:(from:${from},kind:absolute,to:${to})),timeline:(linkTo:!(global),timerange:(from:${from},kind:absolute,to:${to})))`; + from?: string; + to?: string; +}) => { + if (from == null || to == null) return ''; + + return `${kibanaSiemAppUrl}#/detections/rules/id/${id}?timerange=(global:(linkTo:!(timeline),timerange:(from:${from},kind:absolute,to:${to})),timeline:(linkTo:!(global),timerange:(from:${from},kind:absolute,to:${to})))`; +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts index 06652028b3741..414270ffcdd5c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts @@ -34,7 +34,7 @@ describe('searchAfterAndBulkCreate', () => { test('if successful with empty search results', async () => { const sampleParams = sampleRuleAlertParams(); - const { success } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ someResult: sampleEmptyDocSearchResults(), ruleParams: sampleParams, services: mockService, @@ -57,6 +57,7 @@ describe('searchAfterAndBulkCreate', () => { }); expect(mockService.callCluster).toHaveBeenCalledTimes(0); expect(success).toEqual(true); + expect(createdSignalsCount).toEqual(0); }); test('if successful iteration of while loop with maxDocs', async () => { @@ -70,6 +71,11 @@ describe('searchAfterAndBulkCreate', () => { { fakeItemValue: 'fakeItemKey', }, + { + create: { + status: 201, + }, + }, ], }) .mockReturnValueOnce(repeatedSearchResultsWithSortId(3, 1, someGuids.slice(0, 3))) @@ -80,6 +86,11 @@ describe('searchAfterAndBulkCreate', () => { { fakeItemValue: 'fakeItemKey', }, + { + create: { + status: 201, + }, + }, ], }) .mockReturnValueOnce(repeatedSearchResultsWithSortId(3, 1, someGuids.slice(3, 6))) @@ -90,9 +101,14 @@ describe('searchAfterAndBulkCreate', () => { { fakeItemValue: 'fakeItemKey', }, + { + create: { + status: 201, + }, + }, ], }); - const { success } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ someResult: repeatedSearchResultsWithSortId(3, 1, someGuids.slice(6, 9)), ruleParams: sampleParams, services: mockService, @@ -115,13 +131,14 @@ describe('searchAfterAndBulkCreate', () => { }); expect(mockService.callCluster).toHaveBeenCalledTimes(5); expect(success).toEqual(true); + expect(createdSignalsCount).toEqual(3); }); test('if unsuccessful first bulk create', async () => { const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); const sampleParams = sampleRuleAlertParams(10); mockService.callCluster.mockReturnValue(sampleBulkCreateDuplicateResult); - const { success } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), ruleParams: sampleParams, services: mockService, @@ -144,6 +161,7 @@ describe('searchAfterAndBulkCreate', () => { }); expect(mockLogger.error).toHaveBeenCalled(); expect(success).toEqual(false); + expect(createdSignalsCount).toEqual(1); }); test('if unsuccessful iteration of searchAfterAndBulkCreate due to empty sort ids', async () => { @@ -155,9 +173,14 @@ describe('searchAfterAndBulkCreate', () => { { fakeItemValue: 'fakeItemKey', }, + { + create: { + status: 201, + }, + }, ], }); - const { success } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ someResult: sampleDocSearchResultsNoSortId(), ruleParams: sampleParams, services: mockService, @@ -180,6 +203,7 @@ describe('searchAfterAndBulkCreate', () => { }); expect(mockLogger.error).toHaveBeenCalled(); expect(success).toEqual(false); + expect(createdSignalsCount).toEqual(1); }); test('if unsuccessful iteration of searchAfterAndBulkCreate due to empty sort ids and 0 total hits', async () => { @@ -191,9 +215,14 @@ describe('searchAfterAndBulkCreate', () => { { fakeItemValue: 'fakeItemKey', }, + { + create: { + status: 201, + }, + }, ], }); - const { success } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ someResult: sampleDocSearchResultsNoSortIdNoHits(), ruleParams: sampleParams, services: mockService, @@ -215,6 +244,7 @@ describe('searchAfterAndBulkCreate', () => { throttle: 'no_actions', }); expect(success).toEqual(true); + expect(createdSignalsCount).toEqual(1); }); test('if successful iteration of while loop with maxDocs and search after returns results with no sort ids', async () => { @@ -228,10 +258,15 @@ describe('searchAfterAndBulkCreate', () => { { fakeItemValue: 'fakeItemKey', }, + { + create: { + status: 201, + }, + }, ], }) .mockReturnValueOnce(sampleDocSearchResultsNoSortId()); - const { success } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), ruleParams: sampleParams, services: mockService, @@ -253,6 +288,7 @@ describe('searchAfterAndBulkCreate', () => { throttle: 'no_actions', }); expect(success).toEqual(true); + expect(createdSignalsCount).toEqual(1); }); test('if successful iteration of while loop with maxDocs and search after returns empty results with no sort ids', async () => { @@ -266,10 +302,15 @@ describe('searchAfterAndBulkCreate', () => { { fakeItemValue: 'fakeItemKey', }, + { + create: { + status: 201, + }, + }, ], }) .mockReturnValueOnce(sampleEmptyDocSearchResults()); - const { success } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), ruleParams: sampleParams, services: mockService, @@ -291,6 +332,7 @@ describe('searchAfterAndBulkCreate', () => { throttle: 'no_actions', }); expect(success).toEqual(true); + expect(createdSignalsCount).toEqual(1); }); test('if returns false when singleSearchAfter throws an exception', async () => { @@ -304,12 +346,17 @@ describe('searchAfterAndBulkCreate', () => { { fakeItemValue: 'fakeItemKey', }, + { + create: { + status: 201, + }, + }, ], }) .mockImplementation(() => { throw Error('Fake Error'); }); - const { success } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), ruleParams: sampleParams, services: mockService, @@ -331,5 +378,6 @@ describe('searchAfterAndBulkCreate', () => { throttle: 'no_actions', }); expect(success).toEqual(false); + expect(createdSignalsCount).toEqual(1); }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts index a5d5dd0a7b710..ff81730bc4a72 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts @@ -39,6 +39,7 @@ export interface SearchAfterAndBulkCreateReturnType { searchAfterTimes: string[]; bulkCreateTimes: string[]; lastLookBackDate: Date | null | undefined; + createdSignalsCount: number; } // search_after through documents and re-index using bulk endpoint. @@ -68,6 +69,7 @@ export const searchAfterAndBulkCreate = async ({ searchAfterTimes: [], bulkCreateTimes: [], lastLookBackDate: null, + createdSignalsCount: 0, }; if (someResult.hits.hits.length === 0) { toReturn.success = true; @@ -75,7 +77,7 @@ export const searchAfterAndBulkCreate = async ({ } logger.debug('[+] starting bulk insertion'); - const { bulkCreateDuration } = await singleBulkCreate({ + const { bulkCreateDuration, createdItemsCount } = await singleBulkCreate({ someResult, ruleParams, services, @@ -97,6 +99,9 @@ export const searchAfterAndBulkCreate = async ({ someResult.hits.hits.length > 0 ? new Date(someResult.hits.hits[someResult.hits.hits.length - 1]?._source['@timestamp']) : null; + if (createdItemsCount) { + toReturn.createdSignalsCount = createdItemsCount; + } if (bulkCreateDuration) { toReturn.bulkCreateTimes.push(bulkCreateDuration); } @@ -156,7 +161,10 @@ export const searchAfterAndBulkCreate = async ({ } sortId = sortIds[0]; logger.debug('next bulk index'); - const { bulkCreateDuration: bulkDuration } = await singleBulkCreate({ + const { + bulkCreateDuration: bulkDuration, + createdItemsCount: createdCount, + } = await singleBulkCreate({ someResult: searchResult, ruleParams, services, @@ -175,6 +183,7 @@ export const searchAfterAndBulkCreate = async ({ throttle, }); logger.debug('finished next bulk index'); + toReturn.createdSignalsCount += createdCount; if (bulkDuration) { toReturn.bulkCreateTimes.push(bulkDuration); } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts new file mode 100644 index 0000000000000..11d31f1805440 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts @@ -0,0 +1,399 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import moment from 'moment'; +import { savedObjectsClientMock } from 'src/core/server/mocks'; +import { loggerMock } from 'src/core/server/logging/logger.mock'; +import { getResult, getMlResult } from '../routes/__mocks__/request_responses'; +import { signalRulesAlertType } from './signal_rule_alert_type'; +import { AlertInstance } from '../../../../../../../plugins/alerting/server'; +import { ruleStatusServiceFactory } from './rule_status_service'; +import { getGapBetweenRuns } from './utils'; +import { RuleExecutorOptions } from './types'; +import { searchAfterAndBulkCreate } from './search_after_bulk_create'; +import { scheduleNotificationActions } from '../notifications/schedule_notification_actions'; +import { RuleAlertType } from '../rules/types'; +import { findMlSignals } from './find_ml_signals'; +import { bulkCreateMlSignals } from './bulk_create_ml_signals'; + +jest.mock('./rule_status_saved_objects_client'); +jest.mock('./rule_status_service'); +jest.mock('./search_after_bulk_create'); +jest.mock('./get_filter'); +jest.mock('./utils'); +jest.mock('../notifications/schedule_notification_actions'); +jest.mock('./find_ml_signals'); +jest.mock('./bulk_create_ml_signals'); + +const getPayload = ( + ruleAlert: RuleAlertType, + alertInstanceFactoryMock: () => AlertInstance, + savedObjectsClient: ReturnType, + callClusterMock: jest.Mock +) => ({ + alertId: ruleAlert.id, + services: { + savedObjectsClient, + alertInstanceFactory: alertInstanceFactoryMock, + callCluster: callClusterMock, + }, + params: { + ...ruleAlert.params, + actions: [], + enabled: ruleAlert.enabled, + interval: ruleAlert.schedule.interval, + name: ruleAlert.name, + tags: ruleAlert.tags, + throttle: ruleAlert.throttle!, + scrollSize: 10, + scrollLock: '0', + }, + state: {}, + spaceId: '', + name: 'name', + tags: [], + startedAt: new Date('2019-12-13T16:50:33.400Z'), + previousStartedAt: new Date('2019-12-13T16:40:33.400Z'), + createdBy: 'elastic', + updatedBy: 'elastic', +}); + +describe('rules_notification_alert_type', () => { + const version = '8.0.0'; + const jobsSummaryMock = jest.fn(); + const mlMock = { + mlClient: { + callAsInternalUser: jest.fn(), + close: jest.fn(), + asScoped: jest.fn(), + }, + jobServiceProvider: jest.fn().mockReturnValue({ + jobsSummary: jobsSummaryMock, + }), + anomalyDetectorsProvider: jest.fn(), + mlSystemProvider: jest.fn(), + modulesProvider: jest.fn(), + resultsServiceProvider: jest.fn(), + }; + let payload: RuleExecutorOptions; + let alert: ReturnType; + let alertInstanceMock: Record; + let alertInstanceFactoryMock: () => AlertInstance; + let savedObjectsClient: ReturnType; + let logger: ReturnType; + let callClusterMock: jest.Mock; + let ruleStatusService: Record; + + beforeEach(() => { + alertInstanceMock = { + scheduleActions: jest.fn(), + replaceState: jest.fn(), + }; + alertInstanceMock.replaceState.mockReturnValue(alertInstanceMock); + alertInstanceFactoryMock = jest.fn().mockReturnValue(alertInstanceMock); + callClusterMock = jest.fn(); + savedObjectsClient = savedObjectsClientMock.create(); + logger = loggerMock.create(); + ruleStatusService = { + success: jest.fn(), + find: jest.fn(), + goingToRun: jest.fn(), + error: jest.fn(), + }; + (ruleStatusServiceFactory as jest.Mock).mockReturnValue(ruleStatusService); + (getGapBetweenRuns as jest.Mock).mockReturnValue(moment.duration(0)); + (searchAfterAndBulkCreate as jest.Mock).mockResolvedValue({ + success: true, + searchAfterTimes: [], + createdSignalsCount: 10, + }); + callClusterMock.mockResolvedValue({ + hits: { + total: { value: 10 }, + }, + }); + const ruleAlert = getResult(); + savedObjectsClient.get.mockResolvedValue({ + id: 'id', + type: 'type', + references: [], + attributes: ruleAlert, + }); + + payload = getPayload(ruleAlert, alertInstanceFactoryMock, savedObjectsClient, callClusterMock); + + alert = signalRulesAlertType({ + logger, + version, + ml: mlMock, + }); + }); + + describe('executor', () => { + it('should warn about the gap between runs', async () => { + (getGapBetweenRuns as jest.Mock).mockReturnValue(moment.duration(1000)); + await alert.executor(payload); + expect(logger.warn).toHaveBeenCalled(); + expect(logger.warn.mock.calls[0][0]).toContain( + 'a few seconds (1000ms) has passed since last rule execution, and signals may have been missed.' + ); + expect(ruleStatusService.error).toHaveBeenCalled(); + expect(ruleStatusService.error.mock.calls[0][0]).toContain( + 'a few seconds (1000ms) has passed since last rule execution, and signals may have been missed.' + ); + expect(ruleStatusService.error.mock.calls[0][1]).toEqual({ + gap: 'a few seconds', + }); + }); + + it('should call scheduleActions if signalsCount was greater than 0 and rule has actions defined', async () => { + const ruleAlert = getResult(); + ruleAlert.actions = [ + { + actionTypeId: '.slack', + params: { + message: + 'Rule generated {{state.signals_count}} signals\n\n{{context.rule.name}}\n{{{context.results_link}}}', + }, + group: 'default', + id: '99403909-ca9b-49ba-9d7a-7e5320e68d05', + }, + ]; + + savedObjectsClient.get.mockResolvedValue({ + id: 'id', + type: 'type', + references: [], + attributes: ruleAlert, + }); + + await alert.executor(payload); + + expect(scheduleNotificationActions).toHaveBeenCalledWith( + expect.objectContaining({ + signalsCount: 10, + }) + ); + }); + + describe('ML rule', () => { + it('should throw an error if ML plugin was not available', async () => { + const ruleAlert = getMlResult(); + payload = getPayload( + ruleAlert, + alertInstanceFactoryMock, + savedObjectsClient, + callClusterMock + ); + alert = signalRulesAlertType({ + logger, + version, + ml: undefined, + }); + await alert.executor(payload); + expect(logger.error).toHaveBeenCalled(); + expect(logger.error.mock.calls[0][0]).toContain( + 'ML plugin unavailable during rule execution' + ); + }); + + it('should throw an error if machineLearningJobId or anomalyThreshold was not null', async () => { + const ruleAlert = getMlResult(); + ruleAlert.params.anomalyThreshold = undefined; + payload = getPayload( + ruleAlert, + alertInstanceFactoryMock, + savedObjectsClient, + callClusterMock + ); + await alert.executor(payload); + expect(logger.error).toHaveBeenCalled(); + expect(logger.error.mock.calls[0][0]).toContain( + 'Machine learning rule is missing job id and/or anomaly threshold' + ); + }); + + it('should throw an error if Machine learning job summary was null', async () => { + const ruleAlert = getMlResult(); + payload = getPayload( + ruleAlert, + alertInstanceFactoryMock, + savedObjectsClient, + callClusterMock + ); + jobsSummaryMock.mockResolvedValue([]); + await alert.executor(payload); + expect(logger.warn).toHaveBeenCalled(); + expect(logger.warn.mock.calls[0][0]).toContain('Machine learning job is not started'); + expect(ruleStatusService.error).toHaveBeenCalled(); + expect(ruleStatusService.error.mock.calls[0][0]).toContain( + 'Machine learning job is not started' + ); + }); + + it('should log an error if Machine learning job was not started', async () => { + const ruleAlert = getMlResult(); + payload = getPayload( + ruleAlert, + alertInstanceFactoryMock, + savedObjectsClient, + callClusterMock + ); + jobsSummaryMock.mockResolvedValue([ + { + id: 'some_job_id', + jobState: 'starting', + datafeedState: 'started', + }, + ]); + (findMlSignals as jest.Mock).mockResolvedValue({ + hits: { + hits: [], + }, + }); + await alert.executor(payload); + expect(logger.warn).toHaveBeenCalled(); + expect(logger.warn.mock.calls[0][0]).toContain('Machine learning job is not started'); + expect(ruleStatusService.error).toHaveBeenCalled(); + expect(ruleStatusService.error.mock.calls[0][0]).toContain( + 'Machine learning job is not started' + ); + }); + + it('should not call ruleStatusService.success if no anomalies were found', async () => { + const ruleAlert = getMlResult(); + payload = getPayload( + ruleAlert, + alertInstanceFactoryMock, + savedObjectsClient, + callClusterMock + ); + jobsSummaryMock.mockResolvedValue([]); + (findMlSignals as jest.Mock).mockResolvedValue({ + hits: { + hits: [], + }, + }); + (bulkCreateMlSignals as jest.Mock).mockResolvedValue({ + success: true, + bulkCreateDuration: 0, + createdItemsCount: 0, + }); + await alert.executor(payload); + expect(ruleStatusService.success).not.toHaveBeenCalled(); + }); + + it('should call ruleStatusService.success if signals were created', async () => { + const ruleAlert = getMlResult(); + payload = getPayload( + ruleAlert, + alertInstanceFactoryMock, + savedObjectsClient, + callClusterMock + ); + jobsSummaryMock.mockResolvedValue([ + { + id: 'some_job_id', + jobState: 'started', + datafeedState: 'started', + }, + ]); + (findMlSignals as jest.Mock).mockResolvedValue({ + hits: { + hits: [{}], + }, + }); + (bulkCreateMlSignals as jest.Mock).mockResolvedValue({ + success: true, + bulkCreateDuration: 1, + createdItemsCount: 1, + }); + await alert.executor(payload); + expect(ruleStatusService.success).toHaveBeenCalled(); + }); + + it('should call scheduleActions if signalsCount was greater than 0 and rule has actions defined', async () => { + const ruleAlert = getMlResult(); + ruleAlert.actions = [ + { + actionTypeId: '.slack', + params: { + message: + 'Rule generated {{state.signals_count}} signals\n\n{{context.rule.name}}\n{{{context.results_link}}}', + }, + group: 'default', + id: '99403909-ca9b-49ba-9d7a-7e5320e68d05', + }, + ]; + payload = getPayload( + ruleAlert, + alertInstanceFactoryMock, + savedObjectsClient, + callClusterMock + ); + savedObjectsClient.get.mockResolvedValue({ + id: 'id', + type: 'type', + references: [], + attributes: ruleAlert, + }); + jobsSummaryMock.mockResolvedValue([]); + (findMlSignals as jest.Mock).mockResolvedValue({ + hits: { + hits: [{}], + }, + }); + (bulkCreateMlSignals as jest.Mock).mockResolvedValue({ + success: true, + bulkCreateDuration: 1, + createdItemsCount: 1, + }); + + await alert.executor(payload); + + expect(scheduleNotificationActions).toHaveBeenCalledWith( + expect.objectContaining({ + signalsCount: 1, + }) + ); + }); + }); + }); + + describe('should catch error', () => { + it('when bulk indexing failed', async () => { + (searchAfterAndBulkCreate as jest.Mock).mockResolvedValue({ + success: false, + searchAfterTimes: [], + bulkCreateTimes: [], + lastLookBackDate: null, + createdSignalsCount: 0, + }); + await alert.executor(payload); + expect(logger.error).toHaveBeenCalled(); + expect(logger.error.mock.calls[0][0]).toContain( + 'Bulk Indexing of signals failed. Check logs for further details.' + ); + expect(ruleStatusService.error).toHaveBeenCalled(); + }); + + it('when error was thrown', async () => { + (searchAfterAndBulkCreate as jest.Mock).mockResolvedValue({}); + await alert.executor(payload); + expect(logger.error).toHaveBeenCalled(); + expect(logger.error.mock.calls[0][0]).toContain('An error occurred during rule execution'); + expect(ruleStatusService.error).toHaveBeenCalled(); + }); + + it('and call ruleStatusService with the default message', async () => { + (searchAfterAndBulkCreate as jest.Mock).mockRejectedValue({}); + await alert.executor(payload); + expect(logger.error).toHaveBeenCalled(); + expect(logger.error.mock.calls[0][0]).toContain('An error occurred during rule execution'); + expect(ruleStatusService.error).toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts index 246701e94c99a..417fcbbe42a56 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -19,16 +19,16 @@ import { } from './search_after_bulk_create'; import { getFilter } from './get_filter'; import { SignalRuleAlertTypeDefinition, RuleAlertAttributes } from './types'; -import { getGapBetweenRuns, makeFloatString } from './utils'; +import { getGapBetweenRuns, makeFloatString, parseScheduleDates } from './utils'; import { signalParamsSchema } from './signal_params_schema'; import { siemRuleActionGroups } from './siem_rule_action_groups'; import { findMlSignals } from './find_ml_signals'; import { bulkCreateMlSignals } from './bulk_create_ml_signals'; -import { getSignalsCount } from '../notifications/get_signals_count'; import { scheduleNotificationActions } from '../notifications/schedule_notification_actions'; import { ruleStatusServiceFactory } from './rule_status_service'; import { buildRuleMessageFactory } from './rule_messages'; import { ruleStatusSavedObjectsClientFactory } from './rule_status_saved_objects_client'; +import { getNotificationResultsLink } from '../notifications/utils'; export const signalRulesAlertType = ({ logger, @@ -71,6 +71,7 @@ export const signalRulesAlertType = ({ bulkCreateTimes: [], searchAfterTimes: [], lastLookBackDate: null, + createdSignalsCount: 0, }; const ruleStatusClient = ruleStatusSavedObjectsClientFactory(services.savedObjectsClient); const ruleStatusService = await ruleStatusServiceFactory({ @@ -161,7 +162,7 @@ export const signalRulesAlertType = ({ logger.info(buildRuleMessage(`Found ${anomalyCount} signals from ML anomalies.`)); } - const { success, bulkCreateDuration } = await bulkCreateMlSignals({ + const { success, bulkCreateDuration, createdItemsCount } = await bulkCreateMlSignals({ actions, throttle, someResult: anomalyResults, @@ -180,6 +181,7 @@ export const signalRulesAlertType = ({ tags, }); result.success = success; + result.createdSignalsCount = createdItemsCount; if (bulkCreateDuration) { result.bulkCreateTimes.push(bulkCreateDuration); } @@ -249,23 +251,26 @@ export const signalRulesAlertType = ({ name, id: savedObject.id, }; - const { signalsCount, resultsLink } = await getSignalsCount({ - from: `now-${interval}`, - to: 'now', - index: ruleParams.outputIndex, - ruleId: ruleParams.ruleId!, + + const fromInMs = parseScheduleDates(`now-${interval}`)?.format('x'); + const toInMs = parseScheduleDates('now')?.format('x'); + + const resultsLink = getNotificationResultsLink({ + from: fromInMs, + to: toInMs, + id: savedObject.id, kibanaSiemAppUrl: meta?.kibanaSiemAppUrl as string, - ruleAlertId: savedObject.id, - callCluster: services.callCluster, }); - logger.info(buildRuleMessage(`Found ${signalsCount} signals for notification.`)); + logger.info( + buildRuleMessage(`Found ${result.createdSignalsCount} signals for notification.`) + ); - if (signalsCount) { + if (result.createdSignalsCount) { const alertInstance = services.alertInstanceFactory(alertId); scheduleNotificationActions({ alertInstance, - signalsCount, + signalsCount: result.createdSignalsCount, resultsLink, ruleParams: notificationRuleParams, }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts index 45b5610e2d3c3..56f061cdfa3ca 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts @@ -144,7 +144,7 @@ describe('singleBulkCreate', () => { }, ], }); - const { success } = await singleBulkCreate({ + const { success, createdItemsCount } = await singleBulkCreate({ someResult: sampleDocSearchResultsNoSortId(), ruleParams: sampleParams, services: mockService, @@ -163,6 +163,7 @@ describe('singleBulkCreate', () => { throttle: 'no_actions', }); expect(success).toEqual(true); + expect(createdItemsCount).toEqual(0); }); test('create successful bulk create with docs with no versioning', async () => { @@ -176,7 +177,7 @@ describe('singleBulkCreate', () => { }, ], }); - const { success } = await singleBulkCreate({ + const { success, createdItemsCount } = await singleBulkCreate({ someResult: sampleDocSearchResultsNoSortIdNoVersion(), ruleParams: sampleParams, services: mockService, @@ -195,12 +196,13 @@ describe('singleBulkCreate', () => { throttle: 'no_actions', }); expect(success).toEqual(true); + expect(createdItemsCount).toEqual(0); }); test('create unsuccessful bulk create due to empty search results', async () => { const sampleParams = sampleRuleAlertParams(); mockService.callCluster.mockReturnValue(false); - const { success } = await singleBulkCreate({ + const { success, createdItemsCount } = await singleBulkCreate({ someResult: sampleEmptyDocSearchResults(), ruleParams: sampleParams, services: mockService, @@ -219,13 +221,14 @@ describe('singleBulkCreate', () => { throttle: 'no_actions', }); expect(success).toEqual(true); + expect(createdItemsCount).toEqual(0); }); test('create successful bulk create when bulk create has duplicate errors', async () => { const sampleParams = sampleRuleAlertParams(); const sampleSearchResult = sampleDocSearchResultsNoSortId; mockService.callCluster.mockReturnValue(sampleBulkCreateDuplicateResult); - const { success } = await singleBulkCreate({ + const { success, createdItemsCount } = await singleBulkCreate({ someResult: sampleSearchResult(), ruleParams: sampleParams, services: mockService, @@ -246,13 +249,14 @@ describe('singleBulkCreate', () => { expect(mockLogger.error).not.toHaveBeenCalled(); expect(success).toEqual(true); + expect(createdItemsCount).toEqual(1); }); test('create successful bulk create when bulk create has multiple error statuses', async () => { const sampleParams = sampleRuleAlertParams(); const sampleSearchResult = sampleDocSearchResultsNoSortId; mockService.callCluster.mockReturnValue(sampleBulkCreateErrorResult); - const { success } = await singleBulkCreate({ + const { success, createdItemsCount } = await singleBulkCreate({ someResult: sampleSearchResult(), ruleParams: sampleParams, services: mockService, @@ -273,6 +277,7 @@ describe('singleBulkCreate', () => { expect(mockLogger.error).toHaveBeenCalled(); expect(success).toEqual(true); + expect(createdItemsCount).toEqual(1); }); test('filter duplicate rules will return an empty array given an empty array', () => { @@ -341,4 +346,29 @@ describe('singleBulkCreate', () => { }, ]); }); + + test('create successful and returns proper createdItemsCount', async () => { + const sampleParams = sampleRuleAlertParams(); + mockService.callCluster.mockReturnValue(sampleBulkCreateDuplicateResult); + const { success, createdItemsCount } = await singleBulkCreate({ + someResult: sampleDocSearchResultsNoSortId(), + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + id: sampleRuleGuid, + signalsIndex: DEFAULT_SIGNALS_INDEX, + actions: [], + name: 'rule-name', + createdAt: '2020-01-28T15:58:34.810Z', + updatedAt: '2020-01-28T15:59:14.004Z', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], + throttle: 'no_actions', + }); + expect(success).toEqual(true); + expect(createdItemsCount).toEqual(1); + }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts index ffec40b839bf6..6dd8823b57e4d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts @@ -58,6 +58,7 @@ export const filterDuplicateRules = ( export interface SingleBulkCreateResponse { success: boolean; bulkCreateDuration?: string; + createdItemsCount: number; } // Bulk Index documents. @@ -81,7 +82,7 @@ export const singleBulkCreate = async ({ }: SingleBulkCreateParams): Promise => { someResult.hits.hits = filterDuplicateRules(id, someResult); if (someResult.hits.hits.length === 0) { - return { success: true }; + return { success: true, createdItemsCount: 0 }; } // index documents after creating an ID based on the // source documents' originating index, and the original @@ -145,5 +146,8 @@ export const singleBulkCreate = async ({ ); } } - return { success: true, bulkCreateDuration: makeFloatString(end - start) }; + + const createdItemsCount = countBy(response.items, 'create.status')['201'] ?? 0; + + return { success: true, bulkCreateDuration: makeFloatString(end - start), createdItemsCount }; }; From dfa083dc6041edbe584ca58618ecb9fe2f81d81e Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Mon, 6 Apr 2020 13:45:46 -0400 Subject: [PATCH 08/36] Prep for embed saved object refactor + helper (#62486) --- .../list_container/embeddable_list_item.tsx | 64 --------------- .../public/list_container/list_container.tsx | 19 ++--- .../list_container_component.tsx | 26 ++++-- .../list_container/list_container_factory.ts | 6 +- .../multi_task_todo_component.tsx | 17 ++-- .../multi_task_todo_embeddable.tsx | 27 +++---- examples/embeddable_examples/public/plugin.ts | 7 +- .../searchable_list_container.tsx | 16 ++-- .../searchable_list_container_component.tsx | 79 +++++++++++++------ .../searchable_list_container_factory.ts | 6 +- .../public/todo/todo_component.tsx | 10 ++- examples/embeddable_explorer/public/app.tsx | 13 +-- .../public/embeddable_panel_example.tsx | 49 ++---------- .../public/list_container_example.tsx | 10 ++- .../embeddable_child_panel.test.tsx | 2 +- .../lib/embeddables/with_subscription.tsx | 12 +-- .../lib/panel/embeddable_panel.test.tsx | 4 +- .../inspect_panel_action.test.tsx | 2 +- src/plugins/embeddable/public/mocks.ts | 10 ++- .../public/{plugin.ts => plugin.tsx} | 54 ++++++++++--- .../public/tests/apply_filter_action.test.ts | 2 +- .../embeddable/public/tests/test_plugin.ts | 11 ++- test/examples/embeddables/list_container.ts | 9 +-- .../public/np_ready/public/app/app.tsx | 34 ++------ .../app/dashboard_container_example.tsx | 33 ++------ .../public/np_ready/public/plugin.tsx | 15 +--- 26 files changed, 234 insertions(+), 303 deletions(-) delete mode 100644 examples/embeddable_examples/public/list_container/embeddable_list_item.tsx rename src/plugins/embeddable/public/{plugin.ts => plugin.tsx} (76%) diff --git a/examples/embeddable_examples/public/list_container/embeddable_list_item.tsx b/examples/embeddable_examples/public/list_container/embeddable_list_item.tsx deleted file mode 100644 index 2c80cef8a6364..0000000000000 --- a/examples/embeddable_examples/public/list_container/embeddable_list_item.tsx +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import { EuiPanel, EuiLoadingSpinner, EuiFlexItem } from '@elastic/eui'; -import { IEmbeddable } from '../../../../src/plugins/embeddable/public'; - -interface Props { - embeddable: IEmbeddable; -} - -export class EmbeddableListItem extends React.Component { - private embeddableRoot: React.RefObject; - private rendered = false; - - constructor(props: Props) { - super(props); - this.embeddableRoot = React.createRef(); - } - - public componentDidMount() { - if (this.embeddableRoot.current && this.props.embeddable) { - this.props.embeddable.render(this.embeddableRoot.current); - this.rendered = true; - } - } - - public componentDidUpdate() { - if (this.embeddableRoot.current && this.props.embeddable && !this.rendered) { - this.props.embeddable.render(this.embeddableRoot.current); - this.rendered = true; - } - } - - public render() { - return ( - - - {this.props.embeddable ? ( -
- ) : ( - - )} - - - ); - } -} diff --git a/examples/embeddable_examples/public/list_container/list_container.tsx b/examples/embeddable_examples/public/list_container/list_container.tsx index bbbd0d6e32304..9e7bec7a1c951 100644 --- a/examples/embeddable_examples/public/list_container/list_container.tsx +++ b/examples/embeddable_examples/public/list_container/list_container.tsx @@ -31,16 +31,14 @@ export class ListContainer extends Container<{}, ContainerInput> { public readonly type = LIST_CONTAINER; private node?: HTMLElement; - constructor( - input: ContainerInput, - getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory'] - ) { - super(input, { embeddableLoaded: {} }, getEmbeddableFactory); + constructor(input: ContainerInput, private embeddableServices: EmbeddableStart) { + super(input, { embeddableLoaded: {} }, embeddableServices.getEmbeddableFactory); } - // This container has no input itself. - getInheritedInput(id: string) { - return {}; + getInheritedInput() { + return { + viewMode: this.input.viewMode, + }; } public render(node: HTMLElement) { @@ -48,7 +46,10 @@ export class ListContainer extends Container<{}, ContainerInput> { if (this.node) { ReactDOM.unmountComponentAtNode(this.node); } - ReactDOM.render(, node); + ReactDOM.render( + , + node + ); } public destroy() { diff --git a/examples/embeddable_examples/public/list_container/list_container_component.tsx b/examples/embeddable_examples/public/list_container/list_container_component.tsx index f6e04933ee897..da27889a27603 100644 --- a/examples/embeddable_examples/public/list_container/list_container_component.tsx +++ b/examples/embeddable_examples/public/list_container/list_container_component.tsx @@ -24,30 +24,35 @@ import { withEmbeddableSubscription, ContainerInput, ContainerOutput, + EmbeddableStart, } from '../../../../src/plugins/embeddable/public'; -import { EmbeddableListItem } from './embeddable_list_item'; interface Props { embeddable: IContainer; input: ContainerInput; output: ContainerOutput; + embeddableServices: EmbeddableStart; } -function renderList(embeddable: IContainer, panels: ContainerInput['panels']) { +function renderList( + embeddable: IContainer, + panels: ContainerInput['panels'], + embeddableServices: EmbeddableStart +) { let number = 0; const list = Object.values(panels).map(panel => { const child = embeddable.getChild(panel.explicitInput.id); number++; return ( - +

{number}

- +
@@ -56,12 +61,12 @@ function renderList(embeddable: IContainer, panels: ContainerInput['panels']) { return list; } -export function ListContainerComponentInner(props: Props) { +export function ListContainerComponentInner({ embeddable, input, embeddableServices }: Props) { return (
-

{props.embeddable.getTitle()}

+

{embeddable.getTitle()}

- {renderList(props.embeddable, props.input.panels)} + {renderList(embeddable, input.panels, embeddableServices)}
); } @@ -71,4 +76,9 @@ export function ListContainerComponentInner(props: Props) { // anything on input or output state changes. If you don't want that to happen (for example // if you expect something on input or output state to change frequently that your react // component does not care about, then you should probably hook this up manually). -export const ListContainerComponent = withEmbeddableSubscription(ListContainerComponentInner); +export const ListContainerComponent = withEmbeddableSubscription< + ContainerInput, + ContainerOutput, + IContainer, + { embeddableServices: EmbeddableStart } +>(ListContainerComponentInner); diff --git a/examples/embeddable_examples/public/list_container/list_container_factory.ts b/examples/embeddable_examples/public/list_container/list_container_factory.ts index 1fde254110c62..02a024b95349f 100644 --- a/examples/embeddable_examples/public/list_container/list_container_factory.ts +++ b/examples/embeddable_examples/public/list_container/list_container_factory.ts @@ -26,7 +26,7 @@ import { import { LIST_CONTAINER, ListContainer } from './list_container'; interface StartServices { - getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory']; + embeddableServices: EmbeddableStart; } export class ListContainerFactory implements EmbeddableFactoryDefinition { @@ -40,8 +40,8 @@ export class ListContainerFactory implements EmbeddableFactoryDefinition { } public create = async (initialInput: ContainerInput) => { - const { getEmbeddableFactory } = await this.getStartServices(); - return new ListContainer(initialInput, getEmbeddableFactory); + const { embeddableServices } = await this.getStartServices(); + return new ListContainer(initialInput, embeddableServices); }; public getDisplayName() { diff --git a/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_component.tsx b/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_component.tsx index e33dfab0eaf4a..b2882c97ef501 100644 --- a/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_component.tsx +++ b/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_component.tsx @@ -54,7 +54,7 @@ function wrapSearchTerms(task: string, search?: string) { ); } -function renderTasks(tasks: MultiTaskTodoOutput['tasks'], search?: string) { +function renderTasks(tasks: MultiTaskTodoInput['tasks'], search?: string) { return tasks.map(task => ( + {icon ? : } - +

{wrapSearchTerms(title, search)}

@@ -89,6 +88,8 @@ export function MultiTaskTodoEmbeddableComponentInner({ ); } -export const MultiTaskTodoEmbeddableComponent = withEmbeddableSubscription( - MultiTaskTodoEmbeddableComponentInner -); +export const MultiTaskTodoEmbeddableComponent = withEmbeddableSubscription< + MultiTaskTodoInput, + MultiTaskTodoOutput, + MultiTaskTodoEmbeddable +>(MultiTaskTodoEmbeddableComponentInner); diff --git a/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_embeddable.tsx b/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_embeddable.tsx index a2197c9c06fe9..a9e58c5538107 100644 --- a/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_embeddable.tsx +++ b/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_embeddable.tsx @@ -36,30 +36,27 @@ export interface MultiTaskTodoInput extends EmbeddableInput { title: string; } -// This embeddable has output! It's the tasks list that is filtered. -// Output state is something only the embeddable itself can update. It -// can be something completely internal, or it can be state that is +// This embeddable has output! Output state is something only the embeddable itself +// can update. It can be something completely internal, or it can be state that is // derived from input state and updates when input does. export interface MultiTaskTodoOutput extends EmbeddableOutput { - tasks: string[]; + hasMatch: boolean; } -function getFilteredTasks(tasks: string[], search?: string) { - const filteredTasks: string[] = []; - if (search === undefined) return tasks; +function getHasMatch(tasks: string[], title?: string, search?: string) { + if (search === undefined || search === '') return false; - tasks.forEach(task => { - if (task.match(search)) { - filteredTasks.push(task); - } - }); + if (title && title.match(search)) return true; + + const match = tasks.find(task => task.match(search)); + if (match) return true; - return filteredTasks; + return false; } function getOutput(input: MultiTaskTodoInput) { - const tasks = getFilteredTasks(input.tasks, input.search); - return { tasks, hasMatch: tasks.length > 0 || (input.search && input.title.match(input.search)) }; + const hasMatch = getHasMatch(input.tasks, input.title, input.search); + return { hasMatch }; } export class MultiTaskTodoEmbeddable extends Embeddable { diff --git a/examples/embeddable_examples/public/plugin.ts b/examples/embeddable_examples/public/plugin.ts index 5c202d96ceb1a..31a3037332dda 100644 --- a/examples/embeddable_examples/public/plugin.ts +++ b/examples/embeddable_examples/public/plugin.ts @@ -53,20 +53,17 @@ export class EmbeddableExamplesPlugin new MultiTaskTodoEmbeddableFactory() ); - // These are registered in the start method because `getEmbeddableFactory ` - // is only available in start. We could reconsider this I think and make it - // available in both. deps.embeddable.registerEmbeddableFactory( SEARCHABLE_LIST_CONTAINER, new SearchableListContainerFactory(async () => ({ - getEmbeddableFactory: (await core.getStartServices())[1].embeddable.getEmbeddableFactory, + embeddableServices: (await core.getStartServices())[1].embeddable, })) ); deps.embeddable.registerEmbeddableFactory( LIST_CONTAINER, new ListContainerFactory(async () => ({ - getEmbeddableFactory: (await core.getStartServices())[1].embeddable.getEmbeddableFactory, + embeddableServices: (await core.getStartServices())[1].embeddable, })) ); diff --git a/examples/embeddable_examples/public/searchable_list_container/searchable_list_container.tsx b/examples/embeddable_examples/public/searchable_list_container/searchable_list_container.tsx index 06462937c768d..f6efb0b722c4c 100644 --- a/examples/embeddable_examples/public/searchable_list_container/searchable_list_container.tsx +++ b/examples/embeddable_examples/public/searchable_list_container/searchable_list_container.tsx @@ -40,11 +40,8 @@ export class SearchableListContainer extends Container, node); + ReactDOM.render( + , + node + ); } public destroy() { diff --git a/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_component.tsx b/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_component.tsx index b79f86e2a0192..49dbce74788bf 100644 --- a/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_component.tsx +++ b/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_component.tsx @@ -34,14 +34,15 @@ import { withEmbeddableSubscription, ContainerOutput, EmbeddableOutput, + EmbeddableStart, } from '../../../../src/plugins/embeddable/public'; -import { EmbeddableListItem } from '../list_container/embeddable_list_item'; import { SearchableListContainer, SearchableContainerInput } from './searchable_list_container'; interface Props { embeddable: SearchableListContainer; input: SearchableContainerInput; output: ContainerOutput; + embeddableServices: EmbeddableStart; } interface State { @@ -111,13 +112,27 @@ export class SearchableListContainerComponentInner extends Component { + const { input, embeddable } = this.props; + const checked: { [key: string]: boolean } = {}; + Object.values(input.panels).map(panel => { + const child = embeddable.getChild(panel.explicitInput.id); + const output = child.getOutput(); + if (hasHasMatchOutput(output) && output.hasMatch) { + checked[panel.explicitInput.id] = true; + } + }); + this.setState({ checked }); + }; + private toggleCheck = (isChecked: boolean, id: string) => { this.setState(prevState => ({ checked: { ...prevState.checked, [id]: isChecked } })); }; public renderControls() { + const { input } = this.props; return ( - + this.deleteChecked()}> @@ -125,6 +140,17 @@ export class SearchableListContainerComponentInner extends Component + + + this.checkMatching()} + > + Check matching + + + -

{embeddable.getTitle()}

- - {this.renderControls()} - - {this.renderList()} -
+ + +

{embeddable.getTitle()}

+ + {this.renderControls()} + + {this.renderList()} +
+
); } private renderList() { + const { embeddableServices, input, embeddable } = this.props; let id = 0; - const list = Object.values(this.props.input.panels).map(panel => { - const embeddable = this.props.embeddable.getChild(panel.explicitInput.id); - if (this.props.input.search && !this.state.hasMatch[panel.explicitInput.id]) return; + const list = Object.values(input.panels).map(panel => { + const childEmbeddable = embeddable.getChild(panel.explicitInput.id); id++; - return embeddable ? ( - - + return childEmbeddable ? ( + + this.toggleCheck(e.target.checked, embeddable.id)} + data-test-subj={`todoCheckBox-${childEmbeddable.id}`} + disabled={!childEmbeddable} + id={childEmbeddable ? childEmbeddable.id : ''} + checked={this.state.checked[childEmbeddable.id]} + onChange={e => this.toggleCheck(e.target.checked, childEmbeddable.id)} /> - + @@ -183,6 +211,9 @@ export class SearchableListContainerComponentInner extends Component(SearchableListContainerComponentInner); diff --git a/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_factory.ts b/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_factory.ts index 382bb65e769ef..34ea43c29462a 100644 --- a/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_factory.ts +++ b/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_factory.ts @@ -29,7 +29,7 @@ import { } from './searchable_list_container'; interface StartServices { - getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory']; + embeddableServices: EmbeddableStart; } export class SearchableListContainerFactory implements EmbeddableFactoryDefinition { @@ -43,8 +43,8 @@ export class SearchableListContainerFactory implements EmbeddableFactoryDefiniti } public create = async (initialInput: SearchableContainerInput) => { - const { getEmbeddableFactory } = await this.getStartServices(); - return new SearchableListContainer(initialInput, getEmbeddableFactory); + const { embeddableServices } = await this.getStartServices(); + return new SearchableListContainer(initialInput, embeddableServices); }; public getDisplayName() { diff --git a/examples/embeddable_examples/public/todo/todo_component.tsx b/examples/embeddable_examples/public/todo/todo_component.tsx index fbebfc98627b5..a4593bea3cc5e 100644 --- a/examples/embeddable_examples/public/todo/todo_component.tsx +++ b/examples/embeddable_examples/public/todo/todo_component.tsx @@ -51,12 +51,12 @@ function wrapSearchTerms(task: string, search?: string) { export function TodoEmbeddableComponentInner({ input: { icon, title, task, search } }: Props) { return ( - + {icon ? : } - +

{wrapSearchTerms(title || '', search)}

@@ -71,4 +71,8 @@ export function TodoEmbeddableComponentInner({ input: { icon, title, task, searc ); } -export const TodoEmbeddableComponent = withEmbeddableSubscription(TodoEmbeddableComponentInner); +export const TodoEmbeddableComponent = withEmbeddableSubscription< + TodoInput, + EmbeddableOutput, + TodoEmbeddable +>(TodoEmbeddableComponentInner); diff --git a/examples/embeddable_explorer/public/app.tsx b/examples/embeddable_explorer/public/app.tsx index 9c8568454855d..e18012b4b3d80 100644 --- a/examples/embeddable_explorer/public/app.tsx +++ b/examples/embeddable_explorer/public/app.tsx @@ -117,18 +117,7 @@ const EmbeddableExplorerApp = ({ { title: 'Dynamically adding children to a container', id: 'embeddablePanelExamplae', - component: ( - - ), + component: , }, ]; diff --git a/examples/embeddable_explorer/public/embeddable_panel_example.tsx b/examples/embeddable_explorer/public/embeddable_panel_example.tsx index b26111bed7ff2..54cd7c5b5b2c0 100644 --- a/examples/embeddable_explorer/public/embeddable_panel_example.tsx +++ b/examples/embeddable_explorer/public/embeddable_panel_example.tsx @@ -29,43 +29,19 @@ import { EuiText, } from '@elastic/eui'; import { EuiSpacer } from '@elastic/eui'; -import { OverlayStart, CoreStart, SavedObjectsStart, IUiSettingsClient } from 'kibana/public'; -import { - EmbeddablePanel, - EmbeddableStart, - IEmbeddable, -} from '../../../src/plugins/embeddable/public'; +import { EmbeddableStart, IEmbeddable } from '../../../src/plugins/embeddable/public'; import { HELLO_WORLD_EMBEDDABLE, TODO_EMBEDDABLE, MULTI_TASK_TODO_EMBEDDABLE, SEARCHABLE_LIST_CONTAINER, } from '../../embeddable_examples/public'; -import { UiActionsStart } from '../../../src/plugins/ui_actions/public'; -import { Start as InspectorStartContract } from '../../../src/plugins/inspector/public'; -import { getSavedObjectFinder } from '../../../src/plugins/saved_objects/public'; interface Props { - getAllEmbeddableFactories: EmbeddableStart['getEmbeddableFactories']; - getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory']; - uiActionsApi: UiActionsStart; - overlays: OverlayStart; - notifications: CoreStart['notifications']; - inspector: InspectorStartContract; - savedObject: SavedObjectsStart; - uiSettingsClient: IUiSettingsClient; + embeddableServices: EmbeddableStart; } -export function EmbeddablePanelExample({ - inspector, - notifications, - overlays, - getAllEmbeddableFactories, - getEmbeddableFactory, - uiActionsApi, - savedObject, - uiSettingsClient, -}: Props) { +export function EmbeddablePanelExample({ embeddableServices }: Props) { const searchableInput = { id: '1', title: 'My searchable todo list', @@ -105,7 +81,7 @@ export function EmbeddablePanelExample({ useEffect(() => { ref.current = true; if (!embeddable) { - const factory = getEmbeddableFactory(SEARCHABLE_LIST_CONTAINER); + const factory = embeddableServices.getEmbeddableFactory(SEARCHABLE_LIST_CONTAINER); const promise = factory?.create(searchableInput); if (promise) { promise.then(e => { @@ -134,22 +110,13 @@ export function EmbeddablePanelExample({ You can render your embeddable inside the EmbeddablePanel component. This adds some extra rendering and offers a context menu with pluggable actions. Using EmbeddablePanel - to render your embeddable means you get access to the "e;Add panel flyout"e;. - Now you can see how to add embeddables to your container, and how - "e;getExplicitInput"e; is used to grab input not provided by the container. + to render your embeddable means you get access to the "Add panel flyout". Now + you can see how to add embeddables to your container, and how + "getExplicitInput" is used to grab input not provided by the container. {embeddable ? ( - + ) : ( Loading... )} diff --git a/examples/embeddable_explorer/public/list_container_example.tsx b/examples/embeddable_explorer/public/list_container_example.tsx index 969fdb0ca46db..98ad50418d3fe 100644 --- a/examples/embeddable_explorer/public/list_container_example.tsx +++ b/examples/embeddable_explorer/public/list_container_example.tsx @@ -29,7 +29,11 @@ import { EuiText, } from '@elastic/eui'; import { EuiSpacer } from '@elastic/eui'; -import { EmbeddableFactoryRenderer, EmbeddableStart } from '../../../src/plugins/embeddable/public'; +import { + EmbeddableFactoryRenderer, + EmbeddableStart, + ViewMode, +} from '../../../src/plugins/embeddable/public'; import { HELLO_WORLD_EMBEDDABLE, TODO_EMBEDDABLE, @@ -46,6 +50,7 @@ export function ListContainerExample({ getEmbeddableFactory }: Props) { const listInput = { id: 'hello', title: 'My todo list', + viewMode: ViewMode.VIEW, panels: { '1': { type: HELLO_WORLD_EMBEDDABLE, @@ -76,6 +81,7 @@ export function ListContainerExample({ getEmbeddableFactory }: Props) { const searchableInput = { id: '1', title: 'My searchable todo list', + viewMode: ViewMode.VIEW, panels: { '1': { type: HELLO_WORLD_EMBEDDABLE, @@ -150,7 +156,7 @@ export function ListContainerExample({ getEmbeddableFactory }: Props) {

- Check out the "e;Dynamically adding children"e; section, to see how to add + Check out the "Dynamically adding children" section, to see how to add children to this container, and see it rendered inside an `EmbeddablePanel` component.

diff --git a/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx b/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx index 9e47da5cea032..2a0ffd723850b 100644 --- a/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx +++ b/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx @@ -29,7 +29,7 @@ import { ContactCardEmbeddable, } from '../test_samples/embeddables/contact_card/contact_card_embeddable'; // eslint-disable-next-line -import { inspectorPluginMock } from 'src/plugins/inspector/public/mocks'; +import { inspectorPluginMock } from '../../../../inspector/public/mocks'; import { mount } from 'enzyme'; import { embeddablePluginMock } from '../../mocks'; diff --git a/src/plugins/embeddable/public/lib/embeddables/with_subscription.tsx b/src/plugins/embeddable/public/lib/embeddables/with_subscription.tsx index 47b8001961cf5..9bc5889715c76 100644 --- a/src/plugins/embeddable/public/lib/embeddables/with_subscription.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/with_subscription.tsx @@ -23,18 +23,19 @@ import { IEmbeddable, EmbeddableInput, EmbeddableOutput } from './i_embeddable'; export const withEmbeddableSubscription = < I extends EmbeddableInput, O extends EmbeddableOutput, - E extends IEmbeddable = IEmbeddable + E extends IEmbeddable = IEmbeddable, + ExtraProps = {} >( - WrappedComponent: React.ComponentType<{ input: I; output: O; embeddable: E }> -): React.ComponentType<{ embeddable: E }> => + WrappedComponent: React.ComponentType<{ input: I; output: O; embeddable: E } & ExtraProps> +): React.ComponentType<{ embeddable: E } & ExtraProps> => class WithEmbeddableSubscription extends React.Component< - { embeddable: E }, + { embeddable: E } & ExtraProps, { input: I; output: O } > { private subscription?: Rx.Subscription; private mounted: boolean = false; - constructor(props: { embeddable: E }) { + constructor(props: { embeddable: E } & ExtraProps) { super(props); this.state = { input: this.props.embeddable.getInput(), @@ -71,6 +72,7 @@ export const withEmbeddableSubscription = < input={this.state.input} output={this.state.output} embeddable={this.props.embeddable} + {...this.props} /> ); } diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx index 649677dc67c7d..1e7cbb2f3dafc 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx @@ -25,7 +25,7 @@ import { nextTick } from 'test_utils/enzyme_helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; import { I18nProvider } from '@kbn/i18n/react'; import { CONTEXT_MENU_TRIGGER } from '../triggers'; -import { Action, UiActionsStart, ActionType } from 'src/plugins/ui_actions/public'; +import { Action, UiActionsStart, ActionType } from '../../../../ui_actions/public'; import { Trigger, ViewMode } from '../types'; import { isErrorEmbeddable } from '../embeddables'; import { EmbeddablePanel } from './embeddable_panel'; @@ -41,7 +41,7 @@ import { ContactCardEmbeddableOutput, } from '../test_samples/embeddables/contact_card/contact_card_embeddable'; // eslint-disable-next-line -import { inspectorPluginMock } from 'src/plugins/inspector/public/mocks'; +import { inspectorPluginMock } from '../../../../inspector/public/mocks'; import { EuiBadge } from '@elastic/eui'; import { embeddablePluginMock } from '../../mocks'; diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.test.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.test.tsx index ee31127cb5a40..491eaad9faefa 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.test.tsx @@ -27,7 +27,7 @@ import { ContactCardEmbeddable, } from '../../../test_samples'; // eslint-disable-next-line -import { inspectorPluginMock } from 'src/plugins/inspector/public/mocks'; +import { inspectorPluginMock } from '../../../../../../../plugins/inspector/public/mocks'; import { EmbeddableOutput, isErrorEmbeddable, ErrorEmbeddable } from '../../../embeddables'; import { of } from '../../../../tests/helpers'; import { esFilters } from '../../../../../../../plugins/data/public'; diff --git a/src/plugins/embeddable/public/mocks.ts b/src/plugins/embeddable/public/mocks.ts index 2ee05d8316ace..65b15f3a7614f 100644 --- a/src/plugins/embeddable/public/mocks.ts +++ b/src/plugins/embeddable/public/mocks.ts @@ -16,11 +16,12 @@ * specific language governing permissions and limitations * under the License. */ - import { EmbeddableStart, EmbeddableSetup } from '.'; import { EmbeddablePublicPlugin } from './plugin'; import { coreMock } from '../../../core/public/mocks'; +// eslint-disable-next-line +import { inspectorPluginMock } from '../../inspector/public/mocks'; // eslint-disable-next-line import { uiActionsPluginMock } from '../../ui_actions/public/mocks'; @@ -39,6 +40,7 @@ const createStartContract = (): Start => { const startContract: Start = { getEmbeddableFactories: jest.fn(), getEmbeddableFactory: jest.fn(), + EmbeddablePanel: jest.fn(), }; return startContract; }; @@ -48,7 +50,11 @@ const createInstance = () => { const setup = plugin.setup(coreMock.createSetup(), { uiActions: uiActionsPluginMock.createSetupContract(), }); - const doStart = () => plugin.start(coreMock.createStart()); + const doStart = () => + plugin.start(coreMock.createStart(), { + uiActions: uiActionsPluginMock.createStartContract(), + inspector: inspectorPluginMock.createStartContract(), + }); return { plugin, setup, diff --git a/src/plugins/embeddable/public/plugin.ts b/src/plugins/embeddable/public/plugin.tsx similarity index 76% rename from src/plugins/embeddable/public/plugin.ts rename to src/plugins/embeddable/public/plugin.tsx index a483f90f76dde..01fbf52c80182 100644 --- a/src/plugins/embeddable/public/plugin.ts +++ b/src/plugins/embeddable/public/plugin.tsx @@ -16,7 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -import { UiActionsSetup } from 'src/plugins/ui_actions/public'; +import React from 'react'; +import { getSavedObjectFinder } from '../../saved_objects/public'; +import { UiActionsSetup, UiActionsStart } from '../../ui_actions/public'; +import { Start as InspectorStart } from '../../inspector/public'; import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/public'; import { EmbeddableFactoryRegistry, EmbeddableFactoryProvider } from './types'; import { bootstrap } from './bootstrap'; @@ -26,6 +29,7 @@ import { EmbeddableOutput, defaultEmbeddableFactoryProvider, IEmbeddable, + EmbeddablePanel, } from './lib'; import { EmbeddableFactoryDefinition } from './lib/embeddables/embeddable_factory_definition'; @@ -33,6 +37,11 @@ export interface EmbeddableSetupDependencies { uiActions: UiActionsSetup; } +export interface EmbeddableStartDependencies { + uiActions: UiActionsStart; + inspector: InspectorStart; +} + export interface EmbeddableSetup { registerEmbeddableFactory: ( id: string, @@ -50,6 +59,7 @@ export interface EmbeddableStart { embeddableFactoryId: string ) => EmbeddableFactory | undefined; getEmbeddableFactories: () => IterableIterator; + EmbeddablePanel: React.FC<{ embeddable: IEmbeddable; hideHeader?: boolean }>; } export class EmbeddablePublicPlugin implements Plugin { @@ -78,7 +88,10 @@ export class EmbeddablePublicPlugin implements Plugin { this.embeddableFactories.set( def.type, @@ -89,15 +102,36 @@ export class EmbeddablePublicPlugin implements Plugin { - this.ensureFactoriesExist(); - return this.embeddableFactories.values(); - }, + getEmbeddableFactories: this.getEmbeddableFactories, + EmbeddablePanel: ({ + embeddable, + hideHeader, + }: { + embeddable: IEmbeddable; + hideHeader?: boolean; + }) => ( + + ), }; } public stop() {} + private getEmbeddableFactories = () => { + this.ensureFactoriesExist(); + return this.embeddableFactories.values(); + }; + private registerEmbeddableFactory = ( embeddableFactoryId: string, factory: EmbeddableFactoryDefinition @@ -130,11 +164,11 @@ export class EmbeddablePublicPlugin implements Plugin { this.embeddableFactoryDefinitions.forEach(def => this.ensureFactoryExists(def.type)); - } + }; - private ensureFactoryExists(type: string) { + private ensureFactoryExists = (type: string) => { if (!this.embeddableFactories.get(type)) { const def = this.embeddableFactoryDefinitions.get(type); if (!def) return; @@ -145,5 +179,5 @@ export class EmbeddablePublicPlugin implements Plugin { diff --git a/src/plugins/embeddable/public/tests/test_plugin.ts b/src/plugins/embeddable/public/tests/test_plugin.ts index e199ef193aa1c..e13a906e30338 100644 --- a/src/plugins/embeddable/public/tests/test_plugin.ts +++ b/src/plugins/embeddable/public/tests/test_plugin.ts @@ -18,9 +18,11 @@ */ import { CoreSetup, CoreStart } from 'src/core/public'; +import { UiActionsStart } from '../../../ui_actions/public'; // eslint-disable-next-line -import { uiActionsPluginMock } from 'src/plugins/ui_actions/public/mocks'; -import { UiActionsStart } from 'src/plugins/ui_actions/public'; +import { uiActionsPluginMock } from '../../../ui_actions/public/mocks'; +// eslint-disable-next-line +import { inspectorPluginMock } from '../../../inspector/public/mocks'; import { coreMock } from '../../../../core/public/mocks'; import { EmbeddablePublicPlugin, EmbeddableSetup, EmbeddableStart } from '../plugin'; @@ -48,7 +50,10 @@ export const testPlugin = ( coreStart, setup, doStart: (anotherCoreStart: CoreStart = coreStart) => { - const start = plugin.start(anotherCoreStart); + const start = plugin.start(anotherCoreStart, { + uiActions: uiActionsPluginMock.createStartContract(), + inspector: inspectorPluginMock.createStartContract(), + }); return start; }, uiActions: uiActions.doStart(coreStart), diff --git a/test/examples/embeddables/list_container.ts b/test/examples/embeddables/list_container.ts index b1b91ad2c37f1..9e93d479471e8 100644 --- a/test/examples/embeddables/list_container.ts +++ b/test/examples/embeddables/list_container.ts @@ -57,13 +57,12 @@ export default function({ getService }: PluginFunctionalProviderContext) { expect(text).to.eql(['HELLO WORLD!']); }); - it('searchable container filters multi-task children', async () => { + it('searchable container finds matches in multi-task children', async () => { await testSubjects.setValue('filterTodos', 'earth'); + await testSubjects.click('checkMatchingTodos'); + await testSubjects.click('deleteCheckedTodos'); - await retry.try(async () => { - const tasks = await testSubjects.getVisibleTextAll('multiTaskTodoTask'); - expect(tasks).to.eql(['Watch planet earth']); - }); + await testSubjects.missingOrFail('multiTaskTodoTask'); }); }); } diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/app.tsx b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/app.tsx index 54d13efe4d790..2ecde823dc4df 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/app.tsx +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/app.tsx @@ -18,21 +18,11 @@ */ import { EuiTab } from '@elastic/eui'; import React, { Component } from 'react'; -import { CoreStart } from 'src/core/public'; import { EmbeddableStart } from 'src/plugins/embeddable/public'; -import { UiActionsService } from '../../../../../../../../src/plugins/ui_actions/public'; import { DashboardContainerExample } from './dashboard_container_example'; -import { Start as InspectorStartContract } from '../../../../../../../../src/plugins/inspector/public'; export interface AppProps { - getActions: UiActionsService['getTriggerCompatibleActions']; - getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory']; - getAllEmbeddableFactories: EmbeddableStart['getEmbeddableFactories']; - overlays: CoreStart['overlays']; - notifications: CoreStart['notifications']; - inspector: InspectorStartContract; - SavedObjectFinder: React.ComponentType; - I18nContext: CoreStart['i18n']['Context']; + embeddableServices: EmbeddableStart; } export class App extends Component { @@ -72,29 +62,17 @@ export class App extends Component { public render() { return ( - -
-
{this.renderTabs()}
- {this.getContentsForTab()} -
-
+
+
{this.renderTabs()}
+ {this.getContentsForTab()} +
); } private getContentsForTab() { switch (this.state.selectedTabId) { case 'dashboardContainer': { - return ( - - ); + return ; } } } diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_container_example.tsx b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_container_example.tsx index fd07416cadbc5..16c2840d6a32e 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_container_example.tsx +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_container_example.tsx @@ -19,32 +19,17 @@ import React from 'react'; import { EuiButton, EuiLoadingChart } from '@elastic/eui'; import { ContainerOutput } from 'src/plugins/embeddable/public'; -import { - ErrorEmbeddable, - ViewMode, - isErrorEmbeddable, - EmbeddablePanel, - EmbeddableStart, -} from '../embeddable_api'; +import { ErrorEmbeddable, ViewMode, isErrorEmbeddable, EmbeddableStart } from '../embeddable_api'; import { DASHBOARD_CONTAINER_TYPE, DashboardContainer, DashboardContainerInput, } from '../../../../../../../../src/plugins/dashboard/public'; -import { CoreStart } from '../../../../../../../../src/core/public'; import { dashboardInput } from './dashboard_input'; -import { Start as InspectorStartContract } from '../../../../../../../../src/plugins/inspector/public'; -import { UiActionsService } from '../../../../../../../../src/plugins/ui_actions/public'; interface Props { - getActions: UiActionsService['getTriggerCompatibleActions']; - getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory']; - getAllEmbeddableFactories: EmbeddableStart['getEmbeddableFactories']; - overlays: CoreStart['overlays']; - notifications: CoreStart['notifications']; - inspector: InspectorStartContract; - SavedObjectFinder: React.ComponentType; + embeddableServices: EmbeddableStart; } interface State { @@ -67,7 +52,7 @@ export class DashboardContainerExample extends React.Component { public async componentDidMount() { this.mounted = true; - const dashboardFactory = this.props.getEmbeddableFactory< + const dashboardFactory = this.props.embeddableServices.getEmbeddableFactory< DashboardContainerInput, ContainerOutput, DashboardContainer @@ -99,6 +84,7 @@ export class DashboardContainerExample extends React.Component { }; public render() { + const { embeddableServices } = this.props; return (

Dashboard Container

@@ -108,16 +94,7 @@ export class DashboardContainerExample extends React.Component { {!this.state.loaded || !this.container ? ( ) : ( - + )}
); diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx index 18ceec652392d..e5f5faa6ac361 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx @@ -33,7 +33,6 @@ const REACT_ROOT_ID = 'embeddableExplorerRoot'; import { SayHelloAction, createSendMessageAction } from './embeddable_api'; import { App } from './app'; -import { getSavedObjectFinder } from '../../../../../../../src/plugins/saved_objects/public'; import { EmbeddableStart, EmbeddableSetup, @@ -78,19 +77,7 @@ export class EmbeddableExplorerPublicPlugin plugins.__LEGACY.onRenderComplete(() => { const root = document.getElementById(REACT_ROOT_ID); - ReactDOM.render( - , - root - ); + ReactDOM.render(, root); }); } From 29abe5b81bddd17dcdd671cf3456f99bfe7b08a0 Mon Sep 17 00:00:00 2001 From: nnamdifrankie <56440728+nnamdifrankie@users.noreply.github.com> Date: Mon, 6 Apr 2020 13:54:21 -0400 Subject: [PATCH 09/36] [Ingest] EMT-146: agent status impl preparation (#62557) [Ingest] EMT-146: very light refactor a precursor for endpoint status change --- x-pack/plugins/endpoint/server/plugin.ts | 2 +- .../endpoint/server/routes/alerts/details/handlers.ts | 2 +- .../server/routes/{metadata.ts => metadata/index.ts} | 9 +++------ .../server/routes/{ => metadata}/metadata.test.ts | 10 +++++----- .../metadata/query_builders.test.ts} | 5 +---- .../metadata/query_builders.ts} | 4 ++-- 6 files changed, 13 insertions(+), 19 deletions(-) rename x-pack/plugins/endpoint/server/routes/{metadata.ts => metadata/index.ts} (93%) rename x-pack/plugins/endpoint/server/routes/{ => metadata}/metadata.test.ts (96%) rename x-pack/plugins/endpoint/server/{services/endpoint/metadata_query_builders.test.ts => routes/metadata/query_builders.test.ts} (97%) rename x-pack/plugins/endpoint/server/{services/endpoint/metadata_query_builders.ts => routes/metadata/query_builders.ts} (100%) diff --git a/x-pack/plugins/endpoint/server/plugin.ts b/x-pack/plugins/endpoint/server/plugin.ts index 6d2e9e510551a..d3a399124124f 100644 --- a/x-pack/plugins/endpoint/server/plugin.ts +++ b/x-pack/plugins/endpoint/server/plugin.ts @@ -9,9 +9,9 @@ import { PluginSetupContract as FeaturesPluginSetupContract } from '../../featur import { createConfig$, EndpointConfigType } from './config'; import { EndpointAppContext } from './types'; -import { registerEndpointRoutes } from './routes/metadata'; import { registerAlertRoutes } from './routes/alerts'; import { registerResolverRoutes } from './routes/resolver'; +import { registerEndpointRoutes } from './routes/metadata'; export type EndpointPluginStart = void; export type EndpointPluginSetup = void; diff --git a/x-pack/plugins/endpoint/server/routes/alerts/details/handlers.ts b/x-pack/plugins/endpoint/server/routes/alerts/details/handlers.ts index b95c1aaf87c14..725e362f91ec7 100644 --- a/x-pack/plugins/endpoint/server/routes/alerts/details/handlers.ts +++ b/x-pack/plugins/endpoint/server/routes/alerts/details/handlers.ts @@ -9,7 +9,7 @@ import { AlertEvent, EndpointAppConstants } from '../../../../common/types'; import { EndpointAppContext } from '../../../types'; import { AlertDetailsRequestParams } from '../types'; import { AlertDetailsPagination } from './lib'; -import { getHostData } from '../../../routes/metadata'; +import { getHostData } from '../../metadata'; export const alertDetailsHandlerWrapper = function( endpointAppContext: EndpointAppContext diff --git a/x-pack/plugins/endpoint/server/routes/metadata.ts b/x-pack/plugins/endpoint/server/routes/metadata/index.ts similarity index 93% rename from x-pack/plugins/endpoint/server/routes/metadata.ts rename to x-pack/plugins/endpoint/server/routes/metadata/index.ts index 787ffe58a5372..ef01db9af98c4 100644 --- a/x-pack/plugins/endpoint/server/routes/metadata.ts +++ b/x-pack/plugins/endpoint/server/routes/metadata/index.ts @@ -8,12 +8,9 @@ import { IRouter, RequestHandlerContext } from 'kibana/server'; import { SearchResponse } from 'elasticsearch'; import { schema } from '@kbn/config-schema'; -import { - kibanaRequestToMetadataListESQuery, - getESQueryHostMetadataByID, -} from '../services/endpoint/metadata_query_builders'; -import { HostMetadata, HostResultList } from '../../common/types'; -import { EndpointAppContext } from '../types'; +import { HostMetadata, HostResultList } from '../../../common/types'; +import { EndpointAppContext } from '../../types'; +import { getESQueryHostMetadataByID, kibanaRequestToMetadataListESQuery } from './query_builders'; interface HitSource { _source: HostMetadata; diff --git a/x-pack/plugins/endpoint/server/routes/metadata.test.ts b/x-pack/plugins/endpoint/server/routes/metadata/metadata.test.ts similarity index 96% rename from x-pack/plugins/endpoint/server/routes/metadata.test.ts rename to x-pack/plugins/endpoint/server/routes/metadata/metadata.test.ts index 65e07edbcde24..e0fd11e737e7d 100644 --- a/x-pack/plugins/endpoint/server/routes/metadata.test.ts +++ b/x-pack/plugins/endpoint/server/routes/metadata/metadata.test.ts @@ -17,12 +17,12 @@ import { httpServerMock, httpServiceMock, loggingServiceMock, -} from '../../../../../src/core/server/mocks'; -import { HostMetadata, HostResultList } from '../../common/types'; +} from '../../../../../../src/core/server/mocks'; +import { HostMetadata, HostResultList } from '../../../common/types'; import { SearchResponse } from 'elasticsearch'; -import { registerEndpointRoutes } from './metadata'; -import { EndpointConfigSchema } from '../config'; -import * as data from '../test_data/all_metadata_data.json'; +import { EndpointConfigSchema } from '../../config'; +import * as data from '../../test_data/all_metadata_data.json'; +import { registerEndpointRoutes } from './index'; describe('test endpoint route', () => { let routerMock: jest.Mocked; diff --git a/x-pack/plugins/endpoint/server/services/endpoint/metadata_query_builders.test.ts b/x-pack/plugins/endpoint/server/routes/metadata/query_builders.test.ts similarity index 97% rename from x-pack/plugins/endpoint/server/services/endpoint/metadata_query_builders.test.ts rename to x-pack/plugins/endpoint/server/routes/metadata/query_builders.test.ts index 0966b52c79f7d..2514d5aa85811 100644 --- a/x-pack/plugins/endpoint/server/services/endpoint/metadata_query_builders.test.ts +++ b/x-pack/plugins/endpoint/server/routes/metadata/query_builders.test.ts @@ -5,10 +5,7 @@ */ import { httpServerMock, loggingServiceMock } from '../../../../../../src/core/server/mocks'; import { EndpointConfigSchema } from '../../config'; -import { - kibanaRequestToMetadataListESQuery, - getESQueryHostMetadataByID, -} from './metadata_query_builders'; +import { kibanaRequestToMetadataListESQuery, getESQueryHostMetadataByID } from './query_builders'; import { EndpointAppConstants } from '../../../common/types'; describe('query builder', () => { diff --git a/x-pack/plugins/endpoint/server/services/endpoint/metadata_query_builders.ts b/x-pack/plugins/endpoint/server/routes/metadata/query_builders.ts similarity index 100% rename from x-pack/plugins/endpoint/server/services/endpoint/metadata_query_builders.ts rename to x-pack/plugins/endpoint/server/routes/metadata/query_builders.ts index 57b0a4ef10519..bd07604fe9ad2 100644 --- a/x-pack/plugins/endpoint/server/services/endpoint/metadata_query_builders.ts +++ b/x-pack/plugins/endpoint/server/routes/metadata/query_builders.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ import { KibanaRequest } from 'kibana/server'; -import { EndpointAppConstants } from '../../../common/types'; -import { EndpointAppContext } from '../../types'; import { esKuery } from '../../../../../../src/plugins/data/server'; +import { EndpointAppContext } from '../../types'; +import { EndpointAppConstants } from '../../../common/types'; export const kibanaRequestToMetadataListESQuery = async ( request: KibanaRequest, From 42d7bb0c8154e7e7c01805254b9d726bcdbc5102 Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Mon, 6 Apr 2020 11:11:56 -0700 Subject: [PATCH 10/36] [DOCS] Fixes nesting in APM and spaces API (#62659) --- .../resolve_copy_saved_objects_conflicts.asciidoc | 2 +- docs/apm/api.asciidoc | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/api/spaces-management/resolve_copy_saved_objects_conflicts.asciidoc b/docs/api/spaces-management/resolve_copy_saved_objects_conflicts.asciidoc index 7f35dc3834f00..565d12513815b 100644 --- a/docs/api/spaces-management/resolve_copy_saved_objects_conflicts.asciidoc +++ b/docs/api/spaces-management/resolve_copy_saved_objects_conflicts.asciidoc @@ -103,7 +103,7 @@ Execute the <>, w .Properties of `error` [%collapsible%open] ======= - `type`::::: + `type`:::: (string) The type of error. For example, `unsupported_type`, `missing_references`, or `unknown`. ======= ====== diff --git a/docs/apm/api.asciidoc b/docs/apm/api.asciidoc index 76d898ba0cb11..a8f4f4bf0baaa 100644 --- a/docs/apm/api.asciidoc +++ b/docs/apm/api.asciidoc @@ -44,7 +44,7 @@ The following Agent configuration APIs are available: `service`:: (required, object) Service identifying the configuration to create or update. - ++ .Properties of `service` [%collapsible%open] ====== @@ -100,7 +100,7 @@ PUT /api/apm/settings/agent-configuration ===== Request body `service`:: (required, object) Service identifying the configuration to delete - ++ .Properties of `service` [%collapsible%open] ====== @@ -217,7 +217,7 @@ GET /api/apm/settings/agent-configuration `service`:: (required, object) Service identifying the configuration. - ++ .Properties of `service` [%collapsible%open] ====== From 0da20fea6a1ec391113d30797be749a653b0f42f Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Mon, 6 Apr 2020 15:21:39 -0400 Subject: [PATCH 11/36] [Fleet] Move actions to their own saved objects (#62137) --- .../ingest_manager/common/constants/agent.ts | 2 +- .../common/types/models/agent.ts | 9 +- .../ingest_manager/server/constants/index.ts | 3 +- .../routes/agent/actions_handlers.test.ts | 2 +- .../server/routes/agent/actions_handlers.ts | 10 +- .../server/routes/agent/handlers.ts | 3 +- .../server/routes/agent/index.ts | 2 +- .../ingest_manager/server/saved_objects.ts | 20 +- .../server/services/agents/acks.test.ts | 96 +- .../server/services/agents/acks.ts | 107 +- .../server/services/agents/actions.test.ts | 68 +- .../server/services/agents/actions.ts | 60 +- .../server/services/agents/checkin.test.ts | 91 +- .../server/services/agents/checkin.ts | 26 +- .../server/services/agents/enroll.ts | 1 - .../server/services/agents/saved_objects.ts | 18 +- .../ingest_manager/server/types/index.tsx | 1 + .../server/types/models/agent.ts | 2 +- .../api_integration/apis/fleet/agents/acks.ts | 2 +- .../apis/fleet/agents/actions.ts | 21 +- .../es_archives/fleet/agents/data.json | 102 +- .../es_archives/fleet/agents/mappings.json | 1771 +++++++++++++++-- 22 files changed, 1962 insertions(+), 455 deletions(-) diff --git a/x-pack/plugins/ingest_manager/common/constants/agent.ts b/x-pack/plugins/ingest_manager/common/constants/agent.ts index fe6f7f57e2899..0b462fb4c0319 100644 --- a/x-pack/plugins/ingest_manager/common/constants/agent.ts +++ b/x-pack/plugins/ingest_manager/common/constants/agent.ts @@ -5,8 +5,8 @@ */ export const AGENT_SAVED_OBJECT_TYPE = 'agents'; - export const AGENT_EVENT_SAVED_OBJECT_TYPE = 'agent_events'; +export const AGENT_ACTION_SAVED_OBJECT_TYPE = 'agent_actions'; export const AGENT_TYPE_PERMANENT = 'PERMANENT'; export const AGENT_TYPE_EPHEMERAL = 'EPHEMERAL'; diff --git a/x-pack/plugins/ingest_manager/common/types/models/agent.ts b/x-pack/plugins/ingest_manager/common/types/models/agent.ts index aa5729a101e11..4d03a30f9a590 100644 --- a/x-pack/plugins/ingest_manager/common/types/models/agent.ts +++ b/x-pack/plugins/ingest_manager/common/types/models/agent.ts @@ -16,15 +16,21 @@ export type AgentStatus = 'offline' | 'error' | 'online' | 'inactive' | 'warning export interface NewAgentAction { type: 'CONFIG_CHANGE' | 'DATA_DUMP' | 'RESUME' | 'PAUSE'; - data?: string; + data?: any; sent_at?: string; } export type AgentAction = NewAgentAction & { id: string; + agent_id: string; created_at: string; } & SavedObjectAttributes; +export interface AgentActionSOAttributes extends NewAgentAction, SavedObjectAttributes { + created_at: string; + agent_id: string; +} + export interface AgentEvent { type: 'STATE' | 'ERROR' | 'ACTION_RESULT' | 'ACTION'; subtype: // State @@ -62,7 +68,6 @@ interface AgentBase { config_revision?: number; config_newest_revision?: number; last_checkin?: string; - actions: AgentAction[]; } export interface Agent extends AgentBase { diff --git a/x-pack/plugins/ingest_manager/server/constants/index.ts b/x-pack/plugins/ingest_manager/server/constants/index.ts index f6ee475614c5e..6ac92ca5d2a91 100644 --- a/x-pack/plugins/ingest_manager/server/constants/index.ts +++ b/x-pack/plugins/ingest_manager/server/constants/index.ts @@ -20,8 +20,9 @@ export { INSTALL_SCRIPT_API_ROUTES, SETUP_API_ROUTE, // Saved object types - AGENT_EVENT_SAVED_OBJECT_TYPE, AGENT_SAVED_OBJECT_TYPE, + AGENT_EVENT_SAVED_OBJECT_TYPE, + AGENT_ACTION_SAVED_OBJECT_TYPE, AGENT_CONFIG_SAVED_OBJECT_TYPE, DATASOURCE_SAVED_OBJECT_TYPE, OUTPUT_SAVED_OBJECT_TYPE, diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.test.ts b/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.test.ts index a20ba4a880537..76247c338a24f 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.test.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.test.ts @@ -78,7 +78,7 @@ describe('test actions handlers', () => { getAgent: jest.fn().mockReturnValueOnce({ id: 'agent', }), - updateAgentActions: jest.fn().mockReturnValueOnce(agentAction), + createAgentAction: jest.fn().mockReturnValueOnce(agentAction), } as jest.Mocked; const postNewAgentActionHandler = postNewAgentActionHandlerBuilder(actionsService); diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.ts b/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.ts index 2b9c230803593..8eb427e5739b0 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.ts @@ -28,11 +28,11 @@ export const postNewAgentActionHandlerBuilder = function( const newAgentAction = request.body.action as NewAgentAction; - const savedAgentAction = await actionsService.updateAgentActions( - soClient, - agent, - newAgentAction - ); + const savedAgentAction = await actionsService.createAgentAction(soClient, { + created_at: new Date().toISOString(), + ...newAgentAction, + agent_id: agent.id, + }); const body: PostNewAgentActionResponse = { success: true, diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts index adff1fda11200..89c827abe30ec 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts @@ -187,8 +187,9 @@ export const postAgentCheckinHandler: RequestHandler< action: 'checkin', success: true, actions: actions.map(a => ({ + agent_id: agent.id, type: a.type, - data: a.data ? JSON.parse(a.data) : a.data, + data: a.data, id: a.id, created_at: a.created_at, })), diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/index.ts b/x-pack/plugins/ingest_manager/server/routes/agent/index.ts index d461027017842..ac27e47db155e 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/index.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/index.ts @@ -122,7 +122,7 @@ export const registerRoutes = (router: IRouter) => { }, postNewAgentActionHandlerBuilder({ getAgent: AgentService.getAgent, - updateAgentActions: AgentService.updateAgentActions, + createAgentAction: AgentService.createAgentAction, }) ); diff --git a/x-pack/plugins/ingest_manager/server/saved_objects.ts b/x-pack/plugins/ingest_manager/server/saved_objects.ts index 9f3035e1aac17..13f84e4efa790 100644 --- a/x-pack/plugins/ingest_manager/server/saved_objects.ts +++ b/x-pack/plugins/ingest_manager/server/saved_objects.ts @@ -10,6 +10,7 @@ import { PACKAGES_SAVED_OBJECT_TYPE, AGENT_SAVED_OBJECT_TYPE, AGENT_EVENT_SAVED_OBJECT_TYPE, + AGENT_ACTION_SAVED_OBJECT_TYPE, ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, } from './constants'; @@ -38,17 +39,16 @@ export const savedObjectMappings = { default_api_key: { type: 'keyword' }, updated_at: { type: 'date' }, current_error_events: { type: 'text' }, + }, + }, + [AGENT_ACTION_SAVED_OBJECT_TYPE]: { + properties: { + agent_id: { type: 'keyword' }, + type: { type: 'keyword' }, // FIXME_INGEST https://github.com/elastic/kibana/issues/56554 - actions: { - type: 'nested', - properties: { - id: { type: 'keyword' }, - type: { type: 'keyword' }, - data: { type: 'text' }, - sent_at: { type: 'date' }, - created_at: { type: 'date' }, - }, - }, + data: { type: 'flattened' }, + sent_at: { type: 'date' }, + created_at: { type: 'date' }, }, }, [AGENT_EVENT_SAVED_OBJECT_TYPE]: { diff --git a/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts b/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts index 3c07463e3af5d..b4c1f09015a69 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts @@ -3,29 +3,46 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import Boom from 'boom'; +import { SavedObjectsBulkResponse } from 'kibana/server'; import { savedObjectsClientMock } from '../../../../../../src/core/server/saved_objects/service/saved_objects_client.mock'; -import { Agent, AgentAction, AgentEvent } from '../../../common/types/models'; +import { + Agent, + AgentAction, + AgentActionSOAttributes, + AgentEvent, +} from '../../../common/types/models'; import { AGENT_TYPE_PERMANENT } from '../../../common/constants'; import { acknowledgeAgentActions } from './acks'; -import { isBoom } from 'boom'; describe('test agent acks services', () => { it('should succeed on valid and matched actions', async () => { const mockSavedObjectsClient = savedObjectsClientMock.create(); + + mockSavedObjectsClient.bulkGet.mockReturnValue( + Promise.resolve({ + saved_objects: [ + { + id: 'action1', + references: [], + type: 'agent_actions', + attributes: { + type: 'CONFIG_CHANGE', + agent_id: 'id', + sent_at: '2020-03-14T19:45:02.620Z', + timestamp: '2019-01-04T14:32:03.36764-05:00', + created_at: '2020-03-14T19:45:02.620Z', + }, + }, + ], + } as SavedObjectsBulkResponse) + ); + const agentActions = await acknowledgeAgentActions( mockSavedObjectsClient, ({ id: 'id', type: AGENT_TYPE_PERMANENT, - actions: [ - { - type: 'CONFIG_CHANGE', - id: 'action1', - sent_at: '2020-03-14T19:45:02.620Z', - timestamp: '2019-01-04T14:32:03.36764-05:00', - created_at: '2020-03-14T19:45:02.620Z', - }, - ], } as unknown) as Agent, [ { @@ -41,6 +58,7 @@ describe('test agent acks services', () => { ({ type: 'CONFIG_CHANGE', id: 'action1', + agent_id: 'id', sent_at: '2020-03-14T19:45:02.620Z', timestamp: '2019-01-04T14:32:03.36764-05:00', created_at: '2020-03-14T19:45:02.620Z', @@ -50,21 +68,26 @@ describe('test agent acks services', () => { it('should fail for actions that cannot be found on agent actions list', async () => { const mockSavedObjectsClient = savedObjectsClientMock.create(); + mockSavedObjectsClient.bulkGet.mockReturnValue( + Promise.resolve({ + saved_objects: [ + { + id: 'action1', + error: { + message: 'Not found', + statusCode: 404, + }, + }, + ], + } as SavedObjectsBulkResponse) + ); + try { await acknowledgeAgentActions( mockSavedObjectsClient, ({ id: 'id', type: AGENT_TYPE_PERMANENT, - actions: [ - { - type: 'CONFIG_CHANGE', - id: 'action1', - sent_at: '2020-03-14T19:45:02.620Z', - timestamp: '2019-01-04T14:32:03.36764-05:00', - created_at: '2020-03-14T19:45:02.620Z', - }, - ], } as unknown) as Agent, [ ({ @@ -78,27 +101,38 @@ describe('test agent acks services', () => { ); expect(true).toBeFalsy(); } catch (e) { - expect(isBoom(e)).toBeTruthy(); + expect(Boom.isBoom(e)).toBeTruthy(); } }); it('should fail for events that have types not in the allowed acknowledgement type list', async () => { const mockSavedObjectsClient = savedObjectsClientMock.create(); + + mockSavedObjectsClient.bulkGet.mockReturnValue( + Promise.resolve({ + saved_objects: [ + { + id: 'action1', + references: [], + type: 'agent_actions', + attributes: { + type: 'CONFIG_CHANGE', + agent_id: 'id', + sent_at: '2020-03-14T19:45:02.620Z', + timestamp: '2019-01-04T14:32:03.36764-05:00', + created_at: '2020-03-14T19:45:02.620Z', + }, + }, + ], + } as SavedObjectsBulkResponse) + ); + try { await acknowledgeAgentActions( mockSavedObjectsClient, ({ id: 'id', type: AGENT_TYPE_PERMANENT, - actions: [ - { - type: 'CONFIG_CHANGE', - id: 'action1', - sent_at: '2020-03-14T19:45:02.620Z', - timestamp: '2019-01-04T14:32:03.36764-05:00', - created_at: '2020-03-14T19:45:02.620Z', - }, - ], } as unknown) as Agent, [ ({ @@ -112,7 +146,7 @@ describe('test agent acks services', () => { ); expect(true).toBeFalsy(); } catch (e) { - expect(isBoom(e)).toBeTruthy(); + expect(Boom.isBoom(e)).toBeTruthy(); } }); }); diff --git a/x-pack/plugins/ingest_manager/server/services/agents/acks.ts b/x-pack/plugins/ingest_manager/server/services/agents/acks.ts index cf9a47979ae8b..24c3b322aad7f 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/acks.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/acks.ts @@ -17,8 +17,14 @@ import { AgentEvent, AgentEventSOAttributes, AgentSOAttributes, + AgentActionSOAttributes, } from '../../types'; -import { AGENT_EVENT_SAVED_OBJECT_TYPE, AGENT_SAVED_OBJECT_TYPE } from '../../constants'; +import { + AGENT_EVENT_SAVED_OBJECT_TYPE, + AGENT_SAVED_OBJECT_TYPE, + AGENT_ACTION_SAVED_OBJECT_TYPE, +} from '../../constants'; +import { getAgentActionByIds } from './actions'; const ALLOWED_ACKNOWLEDGEMENT_TYPE: string[] = ['ACTION_RESULT']; @@ -27,50 +33,81 @@ export async function acknowledgeAgentActions( agent: Agent, agentEvents: AgentEvent[] ): Promise { - const now = new Date().toISOString(); - - const agentActionMap: Map = new Map( - agent.actions.map(agentAction => [agentAction.id, agentAction]) - ); - - const matchedUpdatedActions: AgentAction[] = []; - - agentEvents.forEach(agentEvent => { + for (const agentEvent of agentEvents) { if (!isAllowedType(agentEvent.type)) { throw Boom.badRequest(`${agentEvent.type} not allowed for acknowledgment only ACTION_RESULT`); } - if (agentActionMap.has(agentEvent.action_id!)) { - const action = agentActionMap.get(agentEvent.action_id!) as AgentAction; - if (!action.sent_at) { - action.sent_at = now; - } - matchedUpdatedActions.push(action); - } else { - throw Boom.badRequest('all actions should belong to current agent'); + } + + const actionIds = agentEvents + .map(event => event.action_id) + .filter(actionId => actionId !== undefined) as string[]; + + let actions; + try { + actions = await getAgentActionByIds(soClient, actionIds); + } catch (error) { + if (Boom.isBoom(error) && error.output.statusCode === 404) { + throw Boom.badRequest(`One or more actions cannot be found`); + } + throw error; + } + + for (const action of actions) { + if (action.agent_id !== agent.id) { + throw Boom.badRequest(`${action.id} not found`); } - }); + } + + if (actions.length === 0) { + return []; + } + const configRevision = getLatestConfigRevison(agent, actions); - if (matchedUpdatedActions.length > 0) { - const configRevision = matchedUpdatedActions.reduce((acc, action) => { - if (action.type !== 'CONFIG_CHANGE') { - return acc; - } - const data = action.data ? JSON.parse(action.data as string) : {}; + await soClient.bulkUpdate([ + buildUpdateAgentConfigRevision(agent.id, configRevision), + ...buildUpdateAgentActionSentAt(actionIds), + ]); - if (data?.config?.id !== agent.config_id) { - return acc; - } + return actions; +} - return data?.config?.revision > acc ? data?.config?.revision : acc; - }, agent.config_revision || 0); +function getLatestConfigRevison(agent: Agent, actions: AgentAction[]) { + return actions.reduce((acc, action) => { + if (action.type !== 'CONFIG_CHANGE') { + return acc; + } + const data = action.data || {}; - await soClient.update(AGENT_SAVED_OBJECT_TYPE, agent.id, { - actions: matchedUpdatedActions, + if (data?.config?.id !== agent.config_id) { + return acc; + } + + return data?.config?.revision > acc ? data?.config?.revision : acc; + }, agent.config_revision || 0); +} + +function buildUpdateAgentConfigRevision(agentId: string, configRevision: number) { + return { + type: AGENT_SAVED_OBJECT_TYPE, + id: agentId, + attributes: { config_revision: configRevision, - }); - } + }, + }; +} - return matchedUpdatedActions; +function buildUpdateAgentActionSentAt( + actionsIds: string[], + sentAt: string = new Date().toISOString() +) { + return actionsIds.map(actionId => ({ + type: AGENT_ACTION_SAVED_OBJECT_TYPE, + id: actionId, + attributes: { + sent_at: sentAt, + }, + })); } function isAllowedType(eventType: string): boolean { diff --git a/x-pack/plugins/ingest_manager/server/services/agents/actions.test.ts b/x-pack/plugins/ingest_manager/server/services/agents/actions.test.ts index b500aeb825fec..f2e671c6dbaa8 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/actions.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/actions.test.ts @@ -4,64 +4,34 @@ * you may not use this file except in compliance with the Elastic License. */ -import { createAgentAction, updateAgentActions } from './actions'; -import { Agent, AgentAction, NewAgentAction } from '../../../common/types/models'; +import { createAgentAction } from './actions'; +import { SavedObject } from 'kibana/server'; +import { AgentAction, AgentActionSOAttributes } from '../../../common/types/models'; import { savedObjectsClientMock } from '../../../../../../src/core/server/saved_objects/service/saved_objects_client.mock'; -import { AGENT_TYPE_PERMANENT } from '../../../common/constants'; - -interface UpdatedActions { - actions: AgentAction[]; -} describe('test agent actions services', () => { - it('should update agent current actions with new action', async () => { + it('should create a new action', async () => { const mockSavedObjectsClient = savedObjectsClientMock.create(); - const newAgentAction: NewAgentAction = { + const newAgentAction: AgentActionSOAttributes = { + agent_id: 'agentid', type: 'CONFIG_CHANGE', data: 'data', sent_at: '2020-03-14T19:45:02.620Z', + created_at: '2020-03-14T19:45:02.620Z', }; - - await updateAgentActions( - mockSavedObjectsClient, - ({ - id: 'id', - type: AGENT_TYPE_PERMANENT, - actions: [ - { - type: 'CONFIG_CHANGE', - id: 'action1', - sent_at: '2020-03-14T19:45:02.620Z', - timestamp: '2019-01-04T14:32:03.36764-05:00', - created_at: '2020-03-14T19:45:02.620Z', - }, - ], - } as unknown) as Agent, - newAgentAction + mockSavedObjectsClient.create.mockReturnValue( + Promise.resolve({ + attributes: {}, + } as SavedObject) ); - - const updatedAgentActions = (mockSavedObjectsClient.update.mock - .calls[0][2] as unknown) as UpdatedActions; - - expect(updatedAgentActions.actions.length).toEqual(2); - const actualAgentAction = updatedAgentActions.actions.find(action => action?.data === 'data'); - expect(actualAgentAction?.type).toEqual(newAgentAction.type); - expect(actualAgentAction?.data).toEqual(newAgentAction.data); - expect(actualAgentAction?.sent_at).toEqual(newAgentAction.sent_at); - }); - - it('should create agent action from new agent action model', async () => { - const newAgentAction: NewAgentAction = { - type: 'CONFIG_CHANGE', - data: 'data', - sent_at: '2020-03-14T19:45:02.620Z', - }; - const now = new Date(); - const agentAction = createAgentAction(now, newAgentAction); - - expect(agentAction.type).toEqual(newAgentAction.type); - expect(agentAction.data).toEqual(newAgentAction.data); - expect(agentAction.sent_at).toEqual(newAgentAction.sent_at); + await createAgentAction(mockSavedObjectsClient, newAgentAction); + + const createdAction = (mockSavedObjectsClient.create.mock + .calls[0][1] as unknown) as AgentAction; + expect(createdAction).toBeDefined(); + expect(createdAction?.type).toEqual(newAgentAction.type); + expect(createdAction?.data).toEqual(newAgentAction.data); + expect(createdAction?.sent_at).toEqual(newAgentAction.sent_at); }); }); diff --git a/x-pack/plugins/ingest_manager/server/services/agents/actions.ts b/x-pack/plugins/ingest_manager/server/services/agents/actions.ts index 2f8ed9f504453..a8ef0820f8d9f 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/actions.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/actions.ts @@ -5,46 +5,52 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; -import uuid from 'uuid'; -import { - Agent, - AgentAction, - AgentSOAttributes, - NewAgentAction, -} from '../../../common/types/models'; -import { AGENT_SAVED_OBJECT_TYPE } from '../../../common/constants'; - -export async function updateAgentActions( +import { Agent, AgentAction, AgentActionSOAttributes } from '../../../common/types/models'; +import { AGENT_ACTION_SAVED_OBJECT_TYPE } from '../../../common/constants'; +import { savedObjectToAgentAction } from './saved_objects'; + +export async function createAgentAction( soClient: SavedObjectsClientContract, - agent: Agent, - newAgentAction: NewAgentAction + newAgentAction: AgentActionSOAttributes ): Promise { - const agentAction = createAgentAction(new Date(), newAgentAction); + const so = await soClient.create(AGENT_ACTION_SAVED_OBJECT_TYPE, { + ...newAgentAction, + }); - agent.actions.push(agentAction); + return savedObjectToAgentAction(so); +} - await soClient.update(AGENT_SAVED_OBJECT_TYPE, agent.id, { - actions: agent.actions, +export async function getAgentActionsForCheckin( + soClient: SavedObjectsClientContract, + agentId: string +): Promise { + const res = await soClient.find({ + type: AGENT_ACTION_SAVED_OBJECT_TYPE, + filter: `not ${AGENT_ACTION_SAVED_OBJECT_TYPE}.attributes.sent_at: * and ${AGENT_ACTION_SAVED_OBJECT_TYPE}.attributes.agent_id:${agentId}`, }); - return agentAction; + return res.saved_objects.map(savedObjectToAgentAction); } -export function createAgentAction(createdAt: Date, newAgentAction: NewAgentAction): AgentAction { - const agentAction = { - id: uuid.v4(), - created_at: createdAt.toISOString(), - }; - - return Object.assign(agentAction, newAgentAction); +export async function getAgentActionByIds( + soClient: SavedObjectsClientContract, + actionIds: string[] +) { + const res = await soClient.bulkGet( + actionIds.map(actionId => ({ + id: actionId, + type: AGENT_ACTION_SAVED_OBJECT_TYPE, + })) + ); + + return res.saved_objects.map(savedObjectToAgentAction); } export interface ActionsService { getAgent: (soClient: SavedObjectsClientContract, agentId: string) => Promise; - updateAgentActions: ( + createAgentAction: ( soClient: SavedObjectsClientContract, - agent: Agent, - newAgentAction: NewAgentAction + newAgentAction: AgentActionSOAttributes ) => Promise; } diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin.test.ts b/x-pack/plugins/ingest_manager/server/services/agents/checkin.test.ts index d3e10fcb6b63f..d98052ea87e86 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/checkin.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/checkin.test.ts @@ -14,13 +14,13 @@ function getAgent(data: Partial) { describe('Agent checkin service', () => { describe('shouldCreateConfigAction', () => { it('should return false if the agent do not have an assigned config', () => { - const res = shouldCreateConfigAction(getAgent({})); + const res = shouldCreateConfigAction(getAgent({}), []); expect(res).toBeFalsy(); }); it('should return true if this is agent first checkin', () => { - const res = shouldCreateConfigAction(getAgent({ config_id: 'config1' })); + const res = shouldCreateConfigAction(getAgent({ config_id: 'config1' }), []); expect(res).toBeTruthy(); }); @@ -32,7 +32,8 @@ describe('Agent checkin service', () => { last_checkin: '2018-01-02T00:00:00', config_revision: 1, config_newest_revision: 1, - }) + }), + [] ); expect(res).toBeFalsy(); @@ -45,20 +46,21 @@ describe('Agent checkin service', () => { last_checkin: '2018-01-02T00:00:00', config_revision: 1, config_newest_revision: 2, - actions: [ - { - id: 'action1', - type: 'CONFIG_CHANGE', - created_at: new Date().toISOString(), - data: JSON.stringify({ - config: { - id: 'config1', - revision: 2, - }, - }), - }, - ], - }) + }), + [ + { + id: 'action1', + agent_id: 'agent1', + type: 'CONFIG_CHANGE', + created_at: new Date().toISOString(), + data: JSON.stringify({ + config: { + id: 'config1', + revision: 2, + }, + }), + }, + ] ); expect(res).toBeFalsy(); @@ -71,31 +73,33 @@ describe('Agent checkin service', () => { last_checkin: '2018-01-02T00:00:00', config_revision: 1, config_newest_revision: 2, - actions: [ - { - id: 'action1', - type: 'CONFIG_CHANGE', - created_at: new Date().toISOString(), - data: JSON.stringify({ - config: { - id: 'config2', - revision: 2, - }, - }), - }, - { - id: 'action1', - type: 'CONFIG_CHANGE', - created_at: new Date().toISOString(), - data: JSON.stringify({ - config: { - id: 'config1', - revision: 1, - }, - }), - }, - ], - }) + }), + [ + { + id: 'action1', + agent_id: 'agent1', + type: 'CONFIG_CHANGE', + created_at: new Date().toISOString(), + data: JSON.stringify({ + config: { + id: 'config2', + revision: 2, + }, + }), + }, + { + id: 'action1', + agent_id: 'agent1', + type: 'CONFIG_CHANGE', + created_at: new Date().toISOString(), + data: JSON.stringify({ + config: { + id: 'config1', + revision: 1, + }, + }), + }, + ] ); expect(res).toBeTruthy(); @@ -108,7 +112,8 @@ describe('Agent checkin service', () => { last_checkin: '2018-01-02T00:00:00', config_revision: 1, config_newest_revision: 2, - }) + }), + [] ); expect(res).toBeTruthy(); diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin.ts b/x-pack/plugins/ingest_manager/server/services/agents/checkin.ts index d80fff5d8eceb..9a2b3f22b9431 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/checkin.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/checkin.ts @@ -5,7 +5,6 @@ */ import { SavedObjectsClientContract, SavedObjectsBulkCreateObject } from 'src/core/server'; -import uuid from 'uuid'; import { Agent, AgentEvent, @@ -17,6 +16,7 @@ import { import { agentConfigService } from '../agent_config'; import * as APIKeysService from '../api_keys'; import { AGENT_SAVED_OBJECT_TYPE, AGENT_EVENT_SAVED_OBJECT_TYPE } from '../../constants'; +import { getAgentActionsForCheckin, createAgentAction } from './actions'; export async function agentCheckin( soClient: SavedObjectsClientContract, @@ -34,10 +34,10 @@ export async function agentCheckin( last_checkin: new Date().toISOString(), }; - const actions = filterActionsForCheckin(agent); + const actions = await getAgentActionsForCheckin(soClient, agent.id); // Generate new agent config if config is updated - if (agent.config_id && shouldCreateConfigAction(agent)) { + if (agent.config_id && shouldCreateConfigAction(agent, actions)) { const config = await agentConfigService.getFullConfig(soClient, agent.config_id); if (config) { // Assign output API keys @@ -52,18 +52,14 @@ export async function agentCheckin( // Mutate the config to set the api token for this agent config.outputs.default.api_key = agent.default_api_key || updateData.default_api_key; - const configChangeAction: AgentAction = { - id: uuid.v4(), + const configChangeAction = await createAgentAction(soClient, { + agent_id: agent.id, type: 'CONFIG_CHANGE', + data: { config } as any, created_at: new Date().toISOString(), - data: JSON.stringify({ - config, - }), sent_at: undefined, - }; + }); actions.push(configChangeAction); - // persist new action - updateData.actions = actions; } } if (localMetadata) { @@ -149,7 +145,7 @@ function isActionEvent(event: AgentEvent) { ); } -export function shouldCreateConfigAction(agent: Agent): boolean { +export function shouldCreateConfigAction(agent: Agent, actions: AgentAction[]): boolean { if (!agent.config_id) { return false; } @@ -167,7 +163,7 @@ export function shouldCreateConfigAction(agent: Agent): boolean { return false; } - const isActionAlreadyGenerated = !!agent.actions.find(action => { + const isActionAlreadyGenerated = !!actions.find(action => { if (!action.data || action.type !== 'CONFIG_CHANGE') { return false; } @@ -181,7 +177,3 @@ export function shouldCreateConfigAction(agent: Agent): boolean { return !isActionAlreadyGenerated; } - -function filterActionsForCheckin(agent: Agent): AgentAction[] { - return agent.actions.filter((a: AgentAction) => !a.sent_at); -} diff --git a/x-pack/plugins/ingest_manager/server/services/agents/enroll.ts b/x-pack/plugins/ingest_manager/server/services/agents/enroll.ts index 52547e9bcb0fb..a34d2e03e9b3d 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/enroll.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/enroll.ts @@ -35,7 +35,6 @@ export async function enroll( user_provided_metadata: JSON.stringify(metadata?.userProvided ?? {}), local_metadata: JSON.stringify(metadata?.local ?? {}), current_error_events: undefined, - actions: [], access_api_key_id: undefined, last_checkin: undefined, default_api_key: undefined, diff --git a/x-pack/plugins/ingest_manager/server/services/agents/saved_objects.ts b/x-pack/plugins/ingest_manager/server/services/agents/saved_objects.ts index dbe268818713d..aa88520740687 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/saved_objects.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/saved_objects.ts @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import Boom from 'boom'; import { SavedObject } from 'src/core/server'; -import { Agent, AgentSOAttributes } from '../../types'; +import { Agent, AgentSOAttributes, AgentAction, AgentActionSOAttributes } from '../../types'; export function savedObjectToAgent(so: SavedObject): Agent { if (so.error) { @@ -24,3 +25,18 @@ export function savedObjectToAgent(so: SavedObject): Agent { status: undefined, }; } + +export function savedObjectToAgentAction(so: SavedObject): AgentAction { + if (so.error) { + if (so.error.statusCode === 404) { + throw Boom.notFound(so.error.message); + } + + throw new Error(so.error.message); + } + + return { + id: so.id, + ...so.attributes, + }; +} diff --git a/x-pack/plugins/ingest_manager/server/types/index.tsx b/x-pack/plugins/ingest_manager/server/types/index.tsx index 59c7f152e5cbc..1cd5622c0c7b0 100644 --- a/x-pack/plugins/ingest_manager/server/types/index.tsx +++ b/x-pack/plugins/ingest_manager/server/types/index.tsx @@ -14,6 +14,7 @@ export { AgentEvent, AgentEventSOAttributes, AgentAction, + AgentActionSOAttributes, Datasource, NewDatasource, FullAgentConfigDatasource, diff --git a/x-pack/plugins/ingest_manager/server/types/models/agent.ts b/x-pack/plugins/ingest_manager/server/types/models/agent.ts index f70b3cf0ed092..f18846348432b 100644 --- a/x-pack/plugins/ingest_manager/server/types/models/agent.ts +++ b/x-pack/plugins/ingest_manager/server/types/models/agent.ts @@ -60,6 +60,6 @@ export const NewAgentActionSchema = schema.object({ schema.literal('RESUME'), schema.literal('PAUSE'), ]), - data: schema.maybe(schema.string()), + data: schema.maybe(schema.any()), sent_at: schema.maybe(schema.string()), }); diff --git a/x-pack/test/api_integration/apis/fleet/agents/acks.ts b/x-pack/test/api_integration/apis/fleet/agents/acks.ts index a2eba2c23c39d..f08ce33d8b60f 100644 --- a/x-pack/test/api_integration/apis/fleet/agents/acks.ts +++ b/x-pack/test/api_integration/apis/fleet/agents/acks.ts @@ -178,7 +178,7 @@ export default function(providerContext: FtrProviderContext) { ], }) .expect(400); - expect(apiResponse.message).to.eql('all actions should belong to current agent'); + expect(apiResponse.message).to.eql('One or more actions cannot be found'); }); it('should return a 400 when request event list contains action types that are not allowed for acknowledgement', async () => { diff --git a/x-pack/test/api_integration/apis/fleet/agents/actions.ts b/x-pack/test/api_integration/apis/fleet/agents/actions.ts index f27b932cff5cb..cf0641acf9e1c 100644 --- a/x-pack/test/api_integration/apis/fleet/agents/actions.ts +++ b/x-pack/test/api_integration/apis/fleet/agents/actions.ts @@ -28,28 +28,15 @@ export default function(providerContext: FtrProviderContext) { .send({ action: { type: 'CONFIG_CHANGE', - data: 'action_data', + data: { data: 'action_data' }, sent_at: '2020-03-18T19:45:02.620Z', }, }) .expect(200); expect(apiResponse.success).to.be(true); - expect(apiResponse.item.data).to.be('action_data'); + expect(apiResponse.item.data).to.eql({ data: 'action_data' }); expect(apiResponse.item.sent_at).to.be('2020-03-18T19:45:02.620Z'); - - const { body: agentResponse } = await supertest - .get(`/api/ingest_manager/fleet/agents/agent1`) - .set('kbn-xsrf', 'xx') - .expect(200); - - const updatedAction = agentResponse.item.actions.find( - (itemAction: Record) => itemAction?.data === 'action_data' - ); - - expect(updatedAction.type).to.be('CONFIG_CHANGE'); - expect(updatedAction.data).to.be('action_data'); - expect(updatedAction.sent_at).to.be('2020-03-18T19:45:02.620Z'); }); it('should return a 400 when request does not have type information', async () => { @@ -58,7 +45,7 @@ export default function(providerContext: FtrProviderContext) { .set('kbn-xsrf', 'xx') .send({ action: { - data: 'action_data', + data: { data: 'action_data' }, sent_at: '2020-03-18T19:45:02.620Z', }, }) @@ -75,7 +62,7 @@ export default function(providerContext: FtrProviderContext) { .send({ action: { type: 'CONFIG_CHANGE', - data: 'action_data', + data: { data: 'action_data' }, sent_at: '2020-03-18T19:45:02.620Z', }, }) diff --git a/x-pack/test/functional/es_archives/fleet/agents/data.json b/x-pack/test/functional/es_archives/fleet/agents/data.json index 9b29767d5162d..1ffb119ca1023 100644 --- a/x-pack/test/functional/es_archives/fleet/agents/data.json +++ b/x-pack/test/functional/es_archives/fleet/agents/data.json @@ -12,30 +12,7 @@ "config_id": "1", "type": "PERMANENT", "local_metadata": "{}", - "user_provided_metadata": "{}", - "actions": [{ - "id": "37ed51ff-e80f-4f2a-a62d-f4fa975e7d85", - "created_at": "2019-09-04T15:04:07+0000", - "type": "RESUME" - }, - { - "id": "b400439c-bbbf-43d5-83cb-cf8b7e32506f", - "type": "PAUSE", - "created_at": "2019-09-04T15:01:07+0000", - "sent_at": "2019-09-04T15:03:07+0000" - }, - { - "created_at" : "2020-03-15T03:47:15.129Z", - "id" : "48cebde1-c906-4893-b89f-595d943b72a1", - "type" : "CONFIG_CHANGE", - "sent_at": "2020-03-04T15:03:07+0000" - }, - { - "created_at" : "2020-03-16T03:47:15.129Z", - "id" : "48cebde1-c906-4893-b89f-595d943b72a2", - "type" : "CONFIG_CHANGE", - "sent_at": "2020-03-04T15:03:07+0000" - }] + "user_provided_metadata": "{}" } } } @@ -54,8 +31,7 @@ "shared_id": "agent2_filebeat", "type": "PERMANENT", "local_metadata": "{}", - "user_provided_metadata": "{}", - "actions": [] + "user_provided_metadata": "{}" } } } @@ -74,8 +50,7 @@ "shared_id": "agent3_metricbeat", "type": "PERMANENT", "local_metadata": "{}", - "user_provided_metadata": "{}", - "actions": [] + "user_provided_metadata": "{}" } } } @@ -94,8 +69,7 @@ "shared_id": "agent4_metricbeat", "type": "PERMANENT", "local_metadata": "{}", - "user_provided_metadata": "{}", - "actions": [] + "user_provided_metadata": "{}" } } } @@ -157,3 +131,71 @@ } } } + +{ + "type": "doc", + "value": { + "id": "agent_actions:37ed51ff-e80f-4f2a-a62d-f4fa975e7d85", + "index": ".kibana", + "source": { + "type": "agent_actions", + "agent_actions": { + "agent_id": "agent1", + "created_at": "2019-09-04T15:04:07+0000", + "type": "RESUME", + "sent_at": "2019-09-04T15:03:07+0000" + } + } + } +} + +{ + "type": "doc", + "value": { + "id": "agent_actions:b400439c-bbbf-43d5-83cb-cf8b7e32506f", + "index": ".kibana", + "source": { + "type": "agent_actions", + "agent_actions": { + "agent_id": "agent1", + "type": "PAUSE", + "created_at": "2019-09-04T15:01:07+0000", + "sent_at": "2019-09-04T15:03:07+0000" + } + } + } +} + +{ + "type": "doc", + "value": { + "id": "agent_actions:48cebde1-c906-4893-b89f-595d943b72a1", + "index": ".kibana", + "source": { + "type": "agent_actions", + "agent_actions": { + "agent_id": "agent1", + "type": "CONFIG_CHANGE", + "created_at": "2020-03-15T03:47:15.129Z", + "sent_at": "2020-03-04T15:03:07+0000" + } + } + } +} + +{ + "type": "doc", + "value": { + "id": "agent_actions:48cebde1-c906-4893-b89f-595d943b72a2", + "index": ".kibana", + "source": { + "type": "agent_actions", + "agent_actions": { + "agent_id": "agent1", + "type": "CONFIG_CHANGE", + "created_at": "2020-03-15T03:47:15.129Z", + "sent_at": "2020-03-04T15:03:07+0000" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/fleet/agents/mappings.json b/x-pack/test/functional/es_archives/fleet/agents/mappings.json index 0f632b7333ee7..31ae161049303 100644 --- a/x-pack/test/functional/es_archives/fleet/agents/mappings.json +++ b/x-pack/test/functional/es_archives/fleet/agents/mappings.json @@ -9,58 +9,168 @@ "dynamic": "strict", "_meta": { "migrationMappingPropertyHashes": { + "outputs": "aee9782e0d500b867859650a36280165", "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", - "server": "ec97f1c5da1a19609a60874e5af1100c", "visualization": "52d7a13ad68a150c4525b292d23e12cc", "references": "7997cf5a56cc02bdc9c93361bde732b0", "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", - "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", - "policies": "1a096b98c98c2efebfdba77cefcfe54a", "type": "2f4316de49999235636386fe51dc06c1", - "lens": "21c3ea0763beb1ecb0162529706b88c5", - "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", "infrastructure-ui-source": "ddc0ecb18383f6b26101a2fadb2dab0c", + "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", + "application_usage_totals": "c897e4310c5f24b07caaff3db53ae2c1", + "action": "6e96ac5e648f57523879661ea72525b7", + "agent_configs": "38abaf89513877745c359e7700c0c66a", + "dashboard": "d00f614b29a80360e1190193fd333bab", + "metrics-explorer-view": "53c5365793677328df0ccb6138bf3cdd", + "siem-detection-engine-rule-actions": "90eee2e4635260f4be0a1da8f5bc0aa0", + "agent_events": "3231653fafe4ef3196fe3b32ab774bf2", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "application_usage_transactional": "965839e75f809fefe04f92dc4d99722a", + "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", + "inventory-view": "9ecce5b58867403613d82fe496470b34", + "enrollment_api_keys": "28b91e20b105b6f928e2012600085d8f", + "upgrade-assistant-reindex-operation": "a53a20fe086b72c9a86da3cc12dad8a6", + "cases-comments": "c2061fb929f585df57425102fa928b4b", + "canvas-element": "7390014e1091044523666d97247392fc", + "datasources": "d4bc0c252b2b5683ff21ea32d00acffc", + "telemetry": "36a616f7026dfa617d6655df850fe16d", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", + "server": "ec97f1c5da1a19609a60874e5af1100c", + "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", + "lens": "21c3ea0763beb1ecb0162529706b88c5", "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", "search": "181661168bbadd1eff5902361e2a0d5c", "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "cases-configure": "42711cbb311976c0687853f4c1354572", "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "alert": "7b44fba6773e37c806ce290ea9b7024e", + "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0", "map": "23d7aa4a720d4938ccde3983f87bd58d", - "dashboard": "d00f614b29a80360e1190193fd333bab", - "apm-services-telemetry": "07ee1939fa4302c62ddc052ec03fed90", - "metrics-explorer-view": "53c5365793677328df0ccb6138bf3cdd", - "epm": "abf5b64aa599932bd181efc86dce14a7", - "siem-ui-timeline": "6485ab095be8d15246667b98a1a34295", - "agent_events": "8060c5567d33f6697164e1fd5c81b8ed", - "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", - "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "uptime-dynamic-settings": "b6289473c8985c79b6c47eebc19a0ca5", + "epm-package": "75d12cd13c867fd713d7dfb27366bc20", + "apm-telemetry": "3525d7c22c42bc80f5e6e9cb3f2b26a2", + "cases": "08b8b110dbca273d37e8aef131ecab61", + "siem-ui-timeline": "ac8020190f5950dd3250b6499144e7fb", "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", "url": "c7f66a0df8b1b52f17c28c4adb111105", - "apm-indices": "c69b68f3fe2bf27b4788d4191c1d6011", - "agents": "1c8e942384219bd899f381fd40e407d7", + "agents": "c3eeb7b9d97176f15f6d126370ab23c7", "migrationVersion": "4a1746014a75ade3a714e1db5763276f", - "inventory-view": "84b320fd67209906333ffce261128462", - "enrollment_api_keys": "90e66b79e8e948e9c15434fdb3ae576e", - "upgrade-assistant-reindex-operation": "a53a20fe086b72c9a86da3cc12dad8a6", "index-pattern": "66eccb05066c5a89924f48a9e9736499", - "canvas-element": "7390014e1091044523666d97247392fc", - "datasources": "2fed9e9883b9622cd59a73ee5550ef4f", - "maps-telemetry": "a4229f8b16a6820c6d724b7e0c1f729d", + "maps-telemetry": "268da3a48066123fc5baf35abaa55014", "namespace": "2f4316de49999235636386fe51dc06c1", - "telemetry": "358ffaa88ba34a97d55af0933a117de4", + "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", + "agent_actions": "ed270b46812f0fa1439366c428a2cf17", "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", - "config": "87aca8fdb053154f11383fce3dbf3edf", - "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", - "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327" + "config": "ae24d22d5986d04124cc6568f771066f", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215" } }, "properties": { + "action": { + "properties": { + "actionTypeId": { + "type": "keyword" + }, + "config": { + "type": "object", + "enabled": false + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + }, + "secrets": { + "type": "binary" + } + } + }, + "action_task_params": { + "properties": { + "actionId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "params": { + "type": "object", + "enabled": false + } + } + }, + "agent_actions": { + "properties": { + "agent_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "data": { + "type": "flattened" + }, + "sent_at": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "agent_configs": { + "properties": { + "datasources": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "is_default": { + "type": "boolean" + }, + "name": { + "type": "text" + }, + "namespace": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "status": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "updated_on": { + "type": "keyword" + } + } + }, "agent_events": { "properties": { + "action_id": { + "type": "keyword" + }, "agent_id": { "type": "keyword" }, + "config_id": { + "type": "keyword" + }, "data": { "type": "text" }, @@ -70,6 +180,9 @@ "payload": { "type": "text" }, + "stream_id": { + "type": "keyword" + }, "subtype": { "type": "keyword" }, @@ -86,29 +199,24 @@ "access_api_key_id": { "type": "keyword" }, - "actions": { - "type": "nested", - "properties": { - "created_at": { - "type": "date" - }, - "data": { - "type": "text" - }, - "id": { - "type": "keyword" - }, - "sent_at": { - "type": "date" - }, - "type": { - "type": "keyword" - } - } - }, "active": { "type": "boolean" }, + "config_id": { + "type": "keyword" + }, + "config_newest_revision": { + "type": "integer" + }, + "config_revision": { + "type": "integer" + }, + "current_error_events": { + "type": "text" + }, + "default_api_key": { + "type": "keyword" + }, "enrolled_at": { "type": "date" }, @@ -121,9 +229,6 @@ "local_metadata": { "type": "text" }, - "config_id": { - "type": "keyword" - }, "shared_id": { "type": "keyword" }, @@ -136,21 +241,95 @@ "user_provided_metadata": { "type": "text" }, - "current_error_events": { - "type": "text" - }, "version": { "type": "keyword" } } }, - "apm-indices": { + "alert": { "properties": { - "apm_oss": { + "actions": { + "type": "nested", "properties": { - "apmAgentConfigurationIndex": { + "actionRef": { + "type": "keyword" + }, + "actionTypeId": { + "type": "keyword" + }, + "group": { "type": "keyword" }, + "params": { + "type": "object", + "enabled": false + } + } + }, + "alertTypeId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "apiKeyOwner": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + }, + "createdBy": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "muteAll": { + "type": "boolean" + }, + "mutedInstanceIds": { + "type": "keyword" + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + }, + "params": { + "type": "object", + "enabled": false + }, + "schedule": { + "properties": { + "interval": { + "type": "keyword" + } + } + }, + "scheduledTaskId": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "throttle": { + "type": "keyword" + }, + "updatedBy": { + "type": "keyword" + } + } + }, + "apm-indices": { + "properties": { + "apm_oss": { + "properties": { "errorIndices": { "type": "keyword" }, @@ -173,33 +352,779 @@ } } }, - "apm-services-telemetry": { + "apm-telemetry": { "properties": { - "has_any_services": { - "type": "boolean" - }, - "services_per_agent": { + "agents": { "properties": { "dotnet": { - "type": "long", - "null_value": 0 + "properties": { + "agent": { + "properties": { + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "language": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "runtime": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + } + } + } + } }, "go": { - "type": "long", - "null_value": 0 + "properties": { + "agent": { + "properties": { + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "language": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "runtime": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + } + } + } + } }, "java": { - "type": "long", - "null_value": 0 + "properties": { + "agent": { + "properties": { + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "language": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "runtime": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + } + } + } + } }, "js-base": { - "type": "long", - "null_value": 0 - }, - "nodejs": { - "type": "long", - "null_value": 0 - }, + "properties": { + "agent": { + "properties": { + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "language": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "runtime": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + } + } + } + } + }, + "nodejs": { + "properties": { + "agent": { + "properties": { + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "language": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "runtime": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + } + } + } + } + }, + "python": { + "properties": { + "agent": { + "properties": { + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "language": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "runtime": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + } + } + } + } + }, + "ruby": { + "properties": { + "agent": { + "properties": { + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "language": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "runtime": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + } + } + } + } + }, + "rum-js": { + "properties": { + "agent": { + "properties": { + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "language": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "runtime": { + "properties": { + "composite": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + } + } + } + } + } + } + }, + "cardinality": { + "properties": { + "transaction": { + "properties": { + "name": { + "properties": { + "all_agents": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "rum": { + "properties": { + "1d": { + "type": "long" + } + } + } + } + } + } + }, + "user_agent": { + "properties": { + "original": { + "properties": { + "all_agents": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "rum": { + "properties": { + "1d": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "counts": { + "properties": { + "agent_configuration": { + "properties": { + "all": { + "type": "long" + } + } + }, + "error": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "max_error_groups_per_service": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "max_transaction_groups_per_service": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "metric": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "onboarding": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "services": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "sourcemap": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "span": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "traces": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "transaction": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + } + } + }, + "has_any_services": { + "type": "boolean" + }, + "indices": { + "properties": { + "all": { + "properties": { + "total": { + "properties": { + "docs": { + "properties": { + "count": { + "type": "long" + } + } + }, + "store": { + "properties": { + "size_in_bytes": { + "type": "long" + } + } + } + } + } + } + }, + "shards": { + "properties": { + "total": { + "type": "long" + } + } + } + } + }, + "integrations": { + "properties": { + "ml": { + "properties": { + "all_jobs_count": { + "type": "long" + } + } + } + } + }, + "retainment": { + "properties": { + "error": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "metric": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "onboarding": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "span": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "transaction": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "services_per_agent": { + "properties": { + "dotnet": { + "type": "long", + "null_value": 0 + }, + "go": { + "type": "long", + "null_value": 0 + }, + "java": { + "type": "long", + "null_value": 0 + }, + "js-base": { + "type": "long", + "null_value": 0 + }, + "nodejs": { + "type": "long", + "null_value": 0 + }, "python": { "type": "long", "null_value": 0 @@ -213,6 +1138,155 @@ "null_value": 0 } } + }, + "tasks": { + "properties": { + "agent_configuration": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "agents": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "cardinality": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "groupings": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "indices_stats": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "integrations": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "processor_events": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "services": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "versions": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + } + } + }, + "version": { + "properties": { + "apm_server": { + "properties": { + "major": { + "type": "long" + }, + "minor": { + "type": "long" + }, + "patch": { + "type": "long" + } + } + } + } + } + } + }, + "application_usage_totals": { + "properties": { + "appId": { + "type": "keyword" + }, + "minutesOnScreen": { + "type": "float" + }, + "numberOfClicks": { + "type": "long" + } + } + }, + "application_usage_transactional": { + "properties": { + "appId": { + "type": "keyword" + }, + "minutesOnScreen": { + "type": "float" + }, + "numberOfClicks": { + "type": "long" + }, + "timestamp": { + "type": "date" } } }, @@ -244,22 +1318,253 @@ } } }, - "canvas-workpad": { - "dynamic": "false", + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + } + } + }, + "cases": { + "properties": { + "closed_at": { + "type": "date" + }, + "closed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "description": { + "type": "text" + }, + "external_service": { + "properties": { + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "external_id": { + "type": "keyword" + }, + "external_title": { + "type": "text" + }, + "external_url": { + "type": "text" + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "status": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-comments": { + "properties": { + "comment": { + "type": "text" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-configure": { + "properties": { + "closure_type": { + "type": "keyword" + }, + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-user-actions": { "properties": { - "@created": { - "type": "date" + "action": { + "type": "keyword" }, - "@timestamp": { + "action_at": { "type": "date" }, - "name": { - "type": "text", - "fields": { - "keyword": { + "action_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { "type": "keyword" } } + }, + "action_field": { + "type": "keyword" + }, + "new_value": { + "type": "text" + }, + "old_value": { + "type": "text" } } }, @@ -327,81 +1632,76 @@ }, "datasources": { "properties": { - "id": { - "type": "keyword" - }, - "name": { + "config_id": { "type": "keyword" }, - "package": { - "properties": { - "assets": { - "properties": { - "id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "description": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "title": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } + "description": { + "type": "text" }, - "read_alias": { - "type": "keyword" + "enabled": { + "type": "boolean" }, - "streams": { + "inputs": { + "type": "nested", "properties": { "config": { "type": "flattened" }, - "id": { + "enabled": { + "type": "boolean" + }, + "processors": { "type": "keyword" }, - "input": { + "streams": { + "type": "nested", "properties": { "config": { "type": "flattened" }, - "fields": { - "type": "flattened" - }, - "id": { - "type": "keyword" - }, - "ilm_policy": { + "dataset": { "type": "keyword" }, - "index_template": { - "type": "keyword" + "enabled": { + "type": "boolean" }, - "ingest_pipelines": { + "id": { "type": "keyword" }, - "type": { + "processors": { "type": "keyword" } } }, - "output_id": { + "type": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "output_id": { + "type": "keyword" + }, + "package": { + "properties": { + "name": { + "type": "keyword" + }, + "title": { "type": "keyword" }, - "processors": { + "version": { "type": "keyword" } } + }, + "revision": { + "type": "integer" } } }, @@ -416,49 +1716,18 @@ "api_key_id": { "type": "keyword" }, + "config_id": { + "type": "keyword" + }, "created_at": { "type": "date" }, - "enrollment_rules": { - "type": "nested", - "properties": { - "created_at": { - "type": "date" - }, - "id": { - "type": "keyword" - }, - "ip_ranges": { - "type": "keyword" - }, - "types": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - }, - "window_duration": { - "type": "nested", - "properties": { - "from": { - "type": "date" - }, - "to": { - "type": "date" - } - } - } - } - }, "expire_at": { "type": "date" }, "name": { "type": "keyword" }, - "config_id": { - "type": "keyword" - }, "type": { "type": "keyword" }, @@ -467,7 +1736,7 @@ } } }, - "epm": { + "epm-package": { "properties": { "installed": { "type": "nested", @@ -479,6 +1748,12 @@ "type": "keyword" } } + }, + "name": { + "type": "keyword" + }, + "version": { + "type": "keyword" } } }, @@ -631,6 +1906,26 @@ } } }, + "customMetrics": { + "type": "nested", + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, "customOptions": { "type": "nested", "properties": { @@ -665,6 +1960,18 @@ }, "metric": { "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, "type": { "type": "keyword" } @@ -792,9 +2099,19 @@ } } }, + "indexPatternsWithGeoFieldCount": { + "type": "long" + }, "mapsTotalCount": { "type": "long" }, + "settings": { + "properties": { + "showMapVisualizationTypes": { + "type": "boolean" + } + } + }, "timeCaptured": { "type": "date" } @@ -894,30 +2211,33 @@ "namespace": { "type": "keyword" }, - "policies": { + "outputs": { "properties": { - "datasources": { + "api_key": { "type": "keyword" }, - "description": { - "type": "text" - }, - "id": { + "ca_sha256": { "type": "keyword" }, - "label": { - "type": "keyword" + "config": { + "type": "flattened" }, - "name": { - "type": "text" + "fleet_enroll_password": { + "type": "binary" }, - "status": { + "fleet_enroll_username": { + "type": "binary" + }, + "hosts": { "type": "keyword" }, - "updated_by": { + "is_default": { + "type": "boolean" + }, + "name": { "type": "keyword" }, - "updated_on": { + "type": { "type": "keyword" } } @@ -1011,6 +2331,73 @@ } } }, + "siem-detection-engine-rule-actions": { + "properties": { + "actions": { + "properties": { + "action_type_id": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "params": { + "type": "object", + "dynamic": "true" + } + } + }, + "alertThrottle": { + "type": "keyword" + }, + "ruleAlertId": { + "type": "keyword" + }, + "ruleThrottle": { + "type": "keyword" + } + } + }, + "siem-detection-engine-rule-status": { + "properties": { + "alertId": { + "type": "keyword" + }, + "bulkCreateTimeDurations": { + "type": "float" + }, + "gap": { + "type": "text" + }, + "lastFailureAt": { + "type": "date" + }, + "lastFailureMessage": { + "type": "text" + }, + "lastLookBackDate": { + "type": "date" + }, + "lastSuccessAt": { + "type": "date" + }, + "lastSuccessMessage": { + "type": "text" + }, + "searchAfterTimeDurations": { + "type": "float" + }, + "status": { + "type": "keyword" + }, + "statusDate": { + "type": "date" + } + } + }, "siem-ui-timeline": { "properties": { "columns": { @@ -1145,6 +2532,9 @@ "description": { "type": "text" }, + "eventType": { + "type": "keyword" + }, "favorite": { "properties": { "favoriteDate": { @@ -1349,6 +2739,9 @@ }, "telemetry": { "properties": { + "allowChangingOptInStatus": { + "type": "boolean" + }, "enabled": { "type": "boolean" }, @@ -1356,12 +2749,16 @@ "type": "date" }, "lastVersionChecked": { - "type": "keyword", - "ignore_above": 256 + "type": "keyword" + }, + "reportFailureCount": { + "type": "integer" + }, + "reportFailureVersion": { + "type": "keyword" }, "sendUsageFrom": { - "type": "keyword", - "ignore_above": 256 + "type": "keyword" }, "userHasSeenNotice": { "type": "boolean" @@ -1409,6 +2806,13 @@ } } }, + "tsvb-validation-telemetry": { + "properties": { + "failedRequests": { + "type": "long" + } + } + }, "type": { "type": "keyword" }, @@ -1485,6 +2889,13 @@ } } }, + "uptime-dynamic-settings": { + "properties": { + "heartbeatIndices": { + "type": "keyword" + } + } + }, "url": { "properties": { "accessCount": { From ab0cc8894a924dda18fc8664cf903fdf7a2d9920 Mon Sep 17 00:00:00 2001 From: Chris Roberson Date: Mon, 6 Apr 2020 15:31:01 -0400 Subject: [PATCH 12/36] [Monitoring] Cluster state watch to Kibana alerting (#61685) * WIP * Add new alert with tests * Fix type issues, and disable new alerting for tests * Fix up the view all alerts view * Turn off for merging * Fix jest test Co-authored-by: Elastic Machine --- .../plugins/monitoring/common/constants.ts | 8 +- .../public/components/alerts/alerts.js | 44 +- .../public/components/alerts/status.test.tsx | 8 +- .../public/components/alerts/status.tsx | 2 +- .../cluster/overview/alerts_panel.js | 81 ++- .../monitoring/public/views/alerts/index.js | 30 +- x-pack/plugins/monitoring/common/constants.ts | 8 +- .../server/alerts/cluster_state.test.ts | 186 ++++++ .../monitoring/server/alerts/cluster_state.ts | 134 ++++ .../plugins/monitoring/server/alerts/enums.ts | 16 + .../server/alerts/license_expiration.test.ts | 572 +++++------------- .../server/alerts/license_expiration.ts | 127 ++-- .../monitoring/server/alerts/types.d.ts | 62 +- .../lib/alerts/cluster_state.lib.test.ts | 70 +++ .../server/lib/alerts/cluster_state.lib.ts | 88 +++ .../lib/alerts/fetch_cluster_state.test.ts | 39 ++ .../server/lib/alerts/fetch_cluster_state.ts | 53 ++ .../server/lib/alerts/fetch_clusters.test.ts | 46 +- .../server/lib/alerts/fetch_clusters.ts | 41 +- .../server/lib/alerts/fetch_licenses.test.ts | 67 +- .../server/lib/alerts/fetch_licenses.ts | 16 +- .../server/lib/alerts/fetch_status.test.ts | 122 ++++ .../server/lib/alerts/fetch_status.ts | 100 ++- .../lib/alerts/get_prepared_alert.test.ts | 163 +++++ .../server/lib/alerts/get_prepared_alert.ts | 87 +++ .../lib/alerts/license_expiration.lib.test.ts | 23 +- .../lib/alerts/license_expiration.lib.ts | 56 +- .../lib/cluster/get_clusters_from_request.js | 12 +- x-pack/plugins/monitoring/server/plugin.ts | 12 + .../server/routes/api/v1/alerts/alerts.js | 53 +- 30 files changed, 1570 insertions(+), 756 deletions(-) create mode 100644 x-pack/plugins/monitoring/server/alerts/cluster_state.test.ts create mode 100644 x-pack/plugins/monitoring/server/alerts/cluster_state.ts create mode 100644 x-pack/plugins/monitoring/server/alerts/enums.ts create mode 100644 x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.test.ts create mode 100644 x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.ts create mode 100644 x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.test.ts create mode 100644 x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.ts create mode 100644 x-pack/plugins/monitoring/server/lib/alerts/fetch_status.test.ts create mode 100644 x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.test.ts create mode 100644 x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.ts diff --git a/x-pack/legacy/plugins/monitoring/common/constants.ts b/x-pack/legacy/plugins/monitoring/common/constants.ts index 9a4030f3eb214..3a4c7b71dcd03 100644 --- a/x-pack/legacy/plugins/monitoring/common/constants.ts +++ b/x-pack/legacy/plugins/monitoring/common/constants.ts @@ -239,11 +239,15 @@ export const ALERT_TYPE_PREFIX = 'monitoring_'; * This is the alert type id for the license expiration alert */ export const ALERT_TYPE_LICENSE_EXPIRATION = `${ALERT_TYPE_PREFIX}alert_type_license_expiration`; +/** + * This is the alert type id for the cluster state alert + */ +export const ALERT_TYPE_CLUSTER_STATE = `${ALERT_TYPE_PREFIX}alert_type_cluster_state`; /** * A listing of all alert types */ -export const ALERT_TYPES = [ALERT_TYPE_LICENSE_EXPIRATION]; +export const ALERT_TYPES = [ALERT_TYPE_LICENSE_EXPIRATION, ALERT_TYPE_CLUSTER_STATE]; /** * Matches the id for the built-in in email action type @@ -254,7 +258,7 @@ export const ALERT_ACTION_TYPE_EMAIL = '.email'; /** * The number of alerts that have been migrated */ -export const NUMBER_OF_MIGRATED_ALERTS = 1; +export const NUMBER_OF_MIGRATED_ALERTS = 2; /** * The advanced settings config name for the email address diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/alerts.js b/x-pack/legacy/plugins/monitoring/public/components/alerts/alerts.js index 11fcef73a4b97..95c1af5549198 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/alerts/alerts.js +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/alerts.js @@ -6,10 +6,15 @@ import React from 'react'; import chrome from '../../np_imports/ui/chrome'; -import { capitalize } from 'lodash'; +import { capitalize, get } from 'lodash'; import { formatDateTimeLocal } from '../../../common/formatting'; import { formatTimestampToDuration } from '../../../common'; -import { CALCULATE_DURATION_SINCE, EUI_SORT_DESCENDING } from '../../../common/constants'; +import { + CALCULATE_DURATION_SINCE, + EUI_SORT_DESCENDING, + ALERT_TYPE_LICENSE_EXPIRATION, + ALERT_TYPE_CLUSTER_STATE, +} from '../../../common/constants'; import { mapSeverity } from './map_severity'; import { FormattedAlert } from 'plugins/monitoring/components/alerts/formatted_alert'; import { EuiMonitoringTable } from 'plugins/monitoring/components/table'; @@ -21,6 +26,8 @@ const linkToCategories = { 'elasticsearch/indices': 'Elasticsearch Indices', 'kibana/instances': 'Kibana Instances', 'logstash/instances': 'Logstash Nodes', + [ALERT_TYPE_LICENSE_EXPIRATION]: 'License expiration', + [ALERT_TYPE_CLUSTER_STATE]: 'Cluster state', }; const getColumns = (kbnUrl, scope, timezone) => [ { @@ -94,19 +101,22 @@ const getColumns = (kbnUrl, scope, timezone) => [ }), field: 'message', sortable: true, - render: (message, alert) => ( - { - scope.$evalAsync(() => { - kbnUrl.changePath(target); - }); - }} - /> - ), + render: (_message, alert) => { + const message = get(alert, 'message.text', get(alert, 'message', '')); + return ( + { + scope.$evalAsync(() => { + kbnUrl.changePath(target); + }); + }} + /> + ); + }, }, { name: i18n.translate('xpack.monitoring.alerts.categoryColumnTitle', { @@ -148,8 +158,8 @@ const getColumns = (kbnUrl, scope, timezone) => [ export const Alerts = ({ alerts, angular, sorting, pagination, onTableChange }) => { const alertsFlattened = alerts.map(alert => ({ ...alert, - status: alert.metadata.severity, - category: alert.metadata.link, + status: get(alert, 'metadata.severity', get(alert, 'severity', 0)), + category: get(alert, 'metadata.link', get(alert, 'type', null)), })); const injector = chrome.dangerouslyGetActiveInjector(); diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/status.test.tsx b/x-pack/legacy/plugins/monitoring/public/components/alerts/status.test.tsx index 258a5b68db372..d3cf4b463a2cc 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/alerts/status.test.tsx +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/status.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { kfetch } from 'ui/kfetch'; import { AlertsStatus, AlertsStatusProps } from './status'; -import { ALERT_TYPE_PREFIX } from '../../../common/constants'; +import { ALERT_TYPES } from '../../../common/constants'; import { getSetupModeState } from '../../lib/setup_mode'; import { mockUseEffects } from '../../jest.helpers'; @@ -63,11 +63,7 @@ describe('Status', () => { it('should render a success message if all alerts have been migrated and in setup mode', async () => { (kfetch as jest.Mock).mockReturnValue({ - data: [ - { - alertTypeId: ALERT_TYPE_PREFIX, - }, - ], + data: ALERT_TYPES.map(type => ({ alertTypeId: type })), }); (getSetupModeState as jest.Mock).mockReturnValue({ diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/status.tsx b/x-pack/legacy/plugins/monitoring/public/components/alerts/status.tsx index 072a98b123452..5f5329bf7fff8 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/alerts/status.tsx +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/status.tsx @@ -142,7 +142,7 @@ export const AlertsStatus: React.FC = (props: AlertsStatusPro ); } - const allMigrated = kibanaAlerts.length === NUMBER_OF_MIGRATED_ALERTS; + const allMigrated = kibanaAlerts.length >= NUMBER_OF_MIGRATED_ALERTS; if (allMigrated) { if (setupModeEnabled) { return ( diff --git a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/alerts_panel.js b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/alerts_panel.js index 8455fb8cf3088..d87ff98e79be0 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/alerts_panel.js +++ b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/alerts_panel.js @@ -6,14 +6,12 @@ import React, { Fragment } from 'react'; import moment from 'moment-timezone'; -import chrome from '../../../np_imports/ui/chrome'; import { FormattedAlert } from 'plugins/monitoring/components/alerts/formatted_alert'; import { mapSeverity } from 'plugins/monitoring/components/alerts/map_severity'; import { formatTimestampToDuration } from '../../../../common/format_timestamp_to_duration'; import { CALCULATE_DURATION_SINCE, KIBANA_ALERTING_ENABLED, - ALERT_TYPE_LICENSE_EXPIRATION, CALCULATE_DURATION_UNTIL, } from '../../../../common/constants'; import { formatDateTimeLocal } from '../../../../common/formatting'; @@ -31,6 +29,37 @@ import { EuiLink, } from '@elastic/eui'; +function replaceTokens(alert) { + if (!alert.message.tokens) { + return alert.message.text; + } + + let text = alert.message.text; + + for (const token of alert.message.tokens) { + if (token.type === 'time') { + text = text.replace( + token.startToken, + token.isRelative + ? formatTimestampToDuration(alert.expirationTime, CALCULATE_DURATION_UNTIL) + : moment.tz(alert.expirationTime, moment.tz.guess()).format('LLL z') + ); + } else if (token.type === 'link') { + const linkPart = new RegExp(`${token.startToken}(.+?)${token.endToken}`).exec(text); + // TODO: we assume this is at the end, which works for now but will not always work + const nonLinkText = text.replace(linkPart[0], ''); + text = ( + + {nonLinkText} + {linkPart[1]} + + ); + } + } + + return text; +} + export function AlertsPanel({ alerts, changeUrl }) { const goToAlerts = () => changeUrl('/alerts'); @@ -58,9 +87,6 @@ export function AlertsPanel({ alerts, changeUrl }) { severityIcon.iconType = 'check'; } - const injector = chrome.dangerouslyGetActiveInjector(); - const timezone = injector.get('config').get('dateFormat:tz'); - return ( @@ -96,14 +122,7 @@ export function AlertsPanel({ alerts, changeUrl }) { const alertsList = KIBANA_ALERTING_ENABLED ? alerts.map((alert, idx) => { const callOutProps = mapSeverity(alert.severity); - let message = alert.message - // scan message prefix and replace relative times - // \w: Matches any alphanumeric character from the basic Latin alphabet, including the underscore. Equivalent to [A-Za-z0-9_]. - .replace( - '#relative', - formatTimestampToDuration(alert.expirationTime, CALCULATE_DURATION_UNTIL) - ) - .replace('#absolute', moment.tz(alert.expirationTime, moment.tz.guess()).format('LLL z')); + const message = replaceTokens(alert); if (!alert.isFiring) { callOutProps.title = i18n.translate( @@ -118,22 +137,30 @@ export function AlertsPanel({ alerts, changeUrl }) { ); callOutProps.color = 'success'; callOutProps.iconType = 'check'; - } else { - if (alert.type === ALERT_TYPE_LICENSE_EXPIRATION) { - message = ( - - {message} -   - Please update your license - - ); - } } return ( - -

{message}

-
+ + +

{message}

+ +

+ +

+
+
+ +
); }) : alerts.map((item, index) => ( diff --git a/x-pack/legacy/plugins/monitoring/public/views/alerts/index.js b/x-pack/legacy/plugins/monitoring/public/views/alerts/index.js index 7c065a78a8af9..62cc985887e9f 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/alerts/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/alerts/index.js @@ -18,25 +18,37 @@ import { Alerts } from '../../components/alerts'; import { MonitoringViewBaseEuiTableController } from '../base_eui_table_controller'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiPage, EuiPageBody, EuiPageContent, EuiSpacer, EuiLink } from '@elastic/eui'; -import { CODE_PATH_ALERTS } from '../../../common/constants'; +import { CODE_PATH_ALERTS, KIBANA_ALERTING_ENABLED } from '../../../common/constants'; function getPageData($injector) { const globalState = $injector.get('globalState'); const $http = $injector.get('$http'); const Private = $injector.get('Private'); - const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/legacy_alerts`; + const url = KIBANA_ALERTING_ENABLED + ? `../api/monitoring/v1/alert_status` + : `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/legacy_alerts`; const timeBounds = timefilter.getBounds(); + const data = { + timeRange: { + min: timeBounds.min.toISOString(), + max: timeBounds.max.toISOString(), + }, + }; + + if (!KIBANA_ALERTING_ENABLED) { + data.ccs = globalState.ccs; + } return $http - .post(url, { - ccs: globalState.ccs, - timeRange: { - min: timeBounds.min.toISOString(), - max: timeBounds.max.toISOString(), - }, + .post(url, data) + .then(response => { + const result = get(response, 'data', []); + if (KIBANA_ALERTING_ENABLED) { + return result.alerts; + } + return result; }) - .then(response => get(response, 'data', [])) .catch(err => { const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); return ajaxErrorHandlers(err); diff --git a/x-pack/plugins/monitoring/common/constants.ts b/x-pack/plugins/monitoring/common/constants.ts index 9a4030f3eb214..3a4c7b71dcd03 100644 --- a/x-pack/plugins/monitoring/common/constants.ts +++ b/x-pack/plugins/monitoring/common/constants.ts @@ -239,11 +239,15 @@ export const ALERT_TYPE_PREFIX = 'monitoring_'; * This is the alert type id for the license expiration alert */ export const ALERT_TYPE_LICENSE_EXPIRATION = `${ALERT_TYPE_PREFIX}alert_type_license_expiration`; +/** + * This is the alert type id for the cluster state alert + */ +export const ALERT_TYPE_CLUSTER_STATE = `${ALERT_TYPE_PREFIX}alert_type_cluster_state`; /** * A listing of all alert types */ -export const ALERT_TYPES = [ALERT_TYPE_LICENSE_EXPIRATION]; +export const ALERT_TYPES = [ALERT_TYPE_LICENSE_EXPIRATION, ALERT_TYPE_CLUSTER_STATE]; /** * Matches the id for the built-in in email action type @@ -254,7 +258,7 @@ export const ALERT_ACTION_TYPE_EMAIL = '.email'; /** * The number of alerts that have been migrated */ -export const NUMBER_OF_MIGRATED_ALERTS = 1; +export const NUMBER_OF_MIGRATED_ALERTS = 2; /** * The advanced settings config name for the email address diff --git a/x-pack/plugins/monitoring/server/alerts/cluster_state.test.ts b/x-pack/plugins/monitoring/server/alerts/cluster_state.test.ts new file mode 100644 index 0000000000000..6a9ca88437347 --- /dev/null +++ b/x-pack/plugins/monitoring/server/alerts/cluster_state.test.ts @@ -0,0 +1,186 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { Logger } from 'src/core/server'; +import { savedObjectsClientMock } from 'src/core/server/mocks'; +import { getClusterState } from './cluster_state'; +import { AlertServices } from '../../../alerting/server'; +import { ALERT_TYPE_CLUSTER_STATE } from '../../common/constants'; +import { AlertCommonParams, AlertCommonState, AlertClusterStatePerClusterState } from './types'; +import { getPreparedAlert } from '../lib/alerts/get_prepared_alert'; +import { executeActions } from '../lib/alerts/cluster_state.lib'; +import { AlertClusterStateState } from './enums'; + +jest.mock('../lib/alerts/cluster_state.lib', () => ({ + executeActions: jest.fn(), + getUiMessage: jest.fn(), +})); + +jest.mock('../lib/alerts/get_prepared_alert', () => ({ + getPreparedAlert: jest.fn(() => { + return { + emailAddress: 'foo@foo.com', + }; + }), +})); + +interface MockServices { + callCluster: jest.Mock; + alertInstanceFactory: jest.Mock; + savedObjectsClient: jest.Mock; +} + +describe('getClusterState', () => { + const services: MockServices | AlertServices = { + callCluster: jest.fn(), + alertInstanceFactory: jest.fn(), + savedObjectsClient: savedObjectsClientMock.create(), + }; + + const params: AlertCommonParams = { + dateFormat: 'YYYY', + timezone: 'UTC', + }; + + const emailAddress = 'foo@foo.com'; + const clusterUuid = 'kdksdfj434'; + const clusterName = 'monitoring_test'; + const cluster = { clusterUuid, clusterName }; + + async function setupAlert( + previousState: AlertClusterStateState, + newState: AlertClusterStateState + ): Promise { + const logger: Logger = { + warn: jest.fn(), + log: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), + error: jest.fn(), + fatal: jest.fn(), + info: jest.fn(), + get: jest.fn(), + }; + const getLogger = (): Logger => logger; + const ccrEnabled = false; + (getPreparedAlert as jest.Mock).mockImplementation(() => ({ + emailAddress, + data: [ + { + state: newState, + clusterUuid, + }, + ], + clusters: [cluster], + })); + + const alert = getClusterState(null as any, null as any, getLogger, ccrEnabled); + const state: AlertCommonState = { + [clusterUuid]: { + state: previousState, + ui: { + isFiring: false, + severity: 0, + message: null, + resolvedMS: 0, + lastCheckedMS: 0, + triggeredMS: 0, + }, + } as AlertClusterStatePerClusterState, + }; + + return (await alert.executor({ services, params, state } as any)) as AlertCommonState; + } + + afterEach(() => { + (executeActions as jest.Mock).mockClear(); + }); + + it('should configure the alert properly', () => { + const alert = getClusterState(null as any, null as any, jest.fn(), false); + expect(alert.id).toBe(ALERT_TYPE_CLUSTER_STATE); + expect(alert.actionGroups).toEqual([{ id: 'default', name: 'Default' }]); + }); + + it('should alert if green -> yellow', async () => { + const result = await setupAlert(AlertClusterStateState.Green, AlertClusterStateState.Yellow); + expect(executeActions).toHaveBeenCalledWith( + undefined, + cluster, + AlertClusterStateState.Yellow, + emailAddress + ); + const clusterResult = result[clusterUuid] as AlertClusterStatePerClusterState; + expect(clusterResult.state).toBe(AlertClusterStateState.Yellow); + expect(clusterResult.ui.isFiring).toBe(true); + expect(clusterResult.ui.resolvedMS).toBe(0); + }); + + it('should alert if yellow -> green', async () => { + const result = await setupAlert(AlertClusterStateState.Yellow, AlertClusterStateState.Green); + expect(executeActions).toHaveBeenCalledWith( + undefined, + cluster, + AlertClusterStateState.Green, + emailAddress, + true + ); + const clusterResult = result[clusterUuid] as AlertClusterStatePerClusterState; + expect(clusterResult.state).toBe(AlertClusterStateState.Green); + expect(clusterResult.ui.resolvedMS).toBeGreaterThan(0); + }); + + it('should alert if green -> red', async () => { + const result = await setupAlert(AlertClusterStateState.Green, AlertClusterStateState.Red); + expect(executeActions).toHaveBeenCalledWith( + undefined, + cluster, + AlertClusterStateState.Red, + emailAddress + ); + const clusterResult = result[clusterUuid] as AlertClusterStatePerClusterState; + expect(clusterResult.state).toBe(AlertClusterStateState.Red); + expect(clusterResult.ui.isFiring).toBe(true); + expect(clusterResult.ui.resolvedMS).toBe(0); + }); + + it('should alert if red -> green', async () => { + const result = await setupAlert(AlertClusterStateState.Red, AlertClusterStateState.Green); + expect(executeActions).toHaveBeenCalledWith( + undefined, + cluster, + AlertClusterStateState.Green, + emailAddress, + true + ); + const clusterResult = result[clusterUuid] as AlertClusterStatePerClusterState; + expect(clusterResult.state).toBe(AlertClusterStateState.Green); + expect(clusterResult.ui.resolvedMS).toBeGreaterThan(0); + }); + + it('should not alert if red -> yellow', async () => { + const result = await setupAlert(AlertClusterStateState.Red, AlertClusterStateState.Yellow); + expect(executeActions).not.toHaveBeenCalled(); + const clusterResult = result[clusterUuid] as AlertClusterStatePerClusterState; + expect(clusterResult.state).toBe(AlertClusterStateState.Red); + expect(clusterResult.ui.resolvedMS).toBe(0); + }); + + it('should not alert if yellow -> red', async () => { + const result = await setupAlert(AlertClusterStateState.Yellow, AlertClusterStateState.Red); + expect(executeActions).not.toHaveBeenCalled(); + const clusterResult = result[clusterUuid] as AlertClusterStatePerClusterState; + expect(clusterResult.state).toBe(AlertClusterStateState.Yellow); + expect(clusterResult.ui.resolvedMS).toBe(0); + }); + + it('should not alert if green -> green', async () => { + const result = await setupAlert(AlertClusterStateState.Green, AlertClusterStateState.Green); + expect(executeActions).not.toHaveBeenCalled(); + const clusterResult = result[clusterUuid] as AlertClusterStatePerClusterState; + expect(clusterResult.state).toBe(AlertClusterStateState.Green); + expect(clusterResult.ui.resolvedMS).toBe(0); + }); +}); diff --git a/x-pack/plugins/monitoring/server/alerts/cluster_state.ts b/x-pack/plugins/monitoring/server/alerts/cluster_state.ts new file mode 100644 index 0000000000000..9a5805b8af7ce --- /dev/null +++ b/x-pack/plugins/monitoring/server/alerts/cluster_state.ts @@ -0,0 +1,134 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import moment from 'moment-timezone'; +import { i18n } from '@kbn/i18n'; +import { Logger, ICustomClusterClient, UiSettingsServiceStart } from 'src/core/server'; +import { ALERT_TYPE_CLUSTER_STATE } from '../../common/constants'; +import { AlertType } from '../../../alerting/server'; +import { executeActions, getUiMessage } from '../lib/alerts/cluster_state.lib'; +import { + AlertCommonExecutorOptions, + AlertCommonState, + AlertClusterStatePerClusterState, + AlertCommonCluster, +} from './types'; +import { AlertClusterStateState } from './enums'; +import { getPreparedAlert } from '../lib/alerts/get_prepared_alert'; +import { fetchClusterState } from '../lib/alerts/fetch_cluster_state'; + +export const getClusterState = ( + getUiSettingsService: () => Promise, + monitoringCluster: ICustomClusterClient, + getLogger: (...scopes: string[]) => Logger, + ccsEnabled: boolean +): AlertType => { + const logger = getLogger(ALERT_TYPE_CLUSTER_STATE); + return { + id: ALERT_TYPE_CLUSTER_STATE, + name: 'Monitoring Alert - Cluster Status', + actionGroups: [ + { + id: 'default', + name: i18n.translate('xpack.monitoring.alerts.clusterState.actionGroups.default', { + defaultMessage: 'Default', + }), + }, + ], + defaultActionGroupId: 'default', + async executor({ + services, + params, + state, + }: AlertCommonExecutorOptions): Promise { + logger.debug( + `Firing alert with params: ${JSON.stringify(params)} and state: ${JSON.stringify(state)}` + ); + + const preparedAlert = await getPreparedAlert( + ALERT_TYPE_CLUSTER_STATE, + getUiSettingsService, + monitoringCluster, + logger, + ccsEnabled, + services, + fetchClusterState + ); + + if (!preparedAlert) { + return state; + } + + const { emailAddress, data: states, clusters } = preparedAlert; + + const result: AlertCommonState = { ...state }; + const defaultAlertState: AlertClusterStatePerClusterState = { + state: AlertClusterStateState.Green, + ui: { + isFiring: false, + message: null, + severity: 0, + resolvedMS: 0, + triggeredMS: 0, + lastCheckedMS: 0, + }, + }; + + for (const clusterState of states) { + const alertState: AlertClusterStatePerClusterState = + (state[clusterState.clusterUuid] as AlertClusterStatePerClusterState) || + defaultAlertState; + const cluster = clusters.find( + (c: AlertCommonCluster) => c.clusterUuid === clusterState.clusterUuid + ); + if (!cluster) { + logger.warn(`Unable to find cluster for clusterUuid='${clusterState.clusterUuid}'`); + continue; + } + const isNonGreen = clusterState.state !== AlertClusterStateState.Green; + const severity = clusterState.state === AlertClusterStateState.Red ? 2100 : 1100; + + const ui = alertState.ui; + let triggered = ui.triggeredMS; + let resolved = ui.resolvedMS; + let message = ui.message || {}; + let lastState = alertState.state; + const instance = services.alertInstanceFactory(ALERT_TYPE_CLUSTER_STATE); + + if (isNonGreen) { + if (lastState === AlertClusterStateState.Green) { + logger.debug(`Cluster state changed from green to ${clusterState.state}`); + executeActions(instance, cluster, clusterState.state, emailAddress); + lastState = clusterState.state; + triggered = moment().valueOf(); + } + message = getUiMessage(clusterState.state); + resolved = 0; + } else if (!isNonGreen && lastState !== AlertClusterStateState.Green) { + logger.debug(`Cluster state changed from ${lastState} to green`); + executeActions(instance, cluster, clusterState.state, emailAddress, true); + lastState = clusterState.state; + message = getUiMessage(clusterState.state, true); + resolved = moment().valueOf(); + } + + result[clusterState.clusterUuid] = { + state: lastState, + ui: { + message, + isFiring: isNonGreen, + severity, + resolvedMS: resolved, + triggeredMS: triggered, + lastCheckedMS: moment().valueOf(), + }, + } as AlertClusterStatePerClusterState; + } + + return result; + }, + }; +}; diff --git a/x-pack/plugins/monitoring/server/alerts/enums.ts b/x-pack/plugins/monitoring/server/alerts/enums.ts new file mode 100644 index 0000000000000..ccff588743af1 --- /dev/null +++ b/x-pack/plugins/monitoring/server/alerts/enums.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export enum AlertClusterStateState { + Green = 'green', + Red = 'red', + Yellow = 'yellow', +} + +export enum AlertCommonPerClusterMessageTokenType { + Time = 'time', + Link = 'link', +} diff --git a/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts b/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts index 0773af6e7f070..92047e300bc1f 100644 --- a/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts @@ -6,42 +6,31 @@ import moment from 'moment-timezone'; import { getLicenseExpiration } from './license_expiration'; -import { - ALERT_TYPE_LICENSE_EXPIRATION, - MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS, -} from '../../common/constants'; +import { ALERT_TYPE_LICENSE_EXPIRATION } from '../../common/constants'; import { Logger } from 'src/core/server'; -import { AlertServices, AlertInstance } from '../../../alerting/server'; +import { AlertServices } from '../../../alerting/server'; import { savedObjectsClientMock } from 'src/core/server/mocks'; import { - AlertState, - AlertClusterState, - AlertParams, - LicenseExpirationAlertExecutorOptions, + AlertCommonParams, + AlertCommonState, + AlertLicensePerClusterState, + AlertLicense, } from './types'; -import { SavedObject, SavedObjectAttributes } from 'src/core/server'; -import { SavedObjectsClientContract } from 'src/core/server'; - -function fillLicense(license: any, clusterUuid?: string) { - return { - hits: { - hits: [ - { - _source: { - license, - cluster_uuid: clusterUuid, - }, - }, - ], - }, - }; -} - -const clusterUuid = 'a4545jhjb'; -const params: AlertParams = { - dateFormat: 'YYYY', - timezone: 'UTC', -}; +import { executeActions } from '../lib/alerts/license_expiration.lib'; +import { PreparedAlert, getPreparedAlert } from '../lib/alerts/get_prepared_alert'; + +jest.mock('../lib/alerts/license_expiration.lib', () => ({ + executeActions: jest.fn(), + getUiMessage: jest.fn(), +})); + +jest.mock('../lib/alerts/get_prepared_alert', () => ({ + getPreparedAlert: jest.fn(() => { + return { + emailAddress: 'foo@foo.com', + }; + }), +})); interface MockServices { callCluster: jest.Mock; @@ -49,428 +38,169 @@ interface MockServices { savedObjectsClient: jest.Mock; } -const alertExecutorOptions: LicenseExpirationAlertExecutorOptions = { - alertId: '', - startedAt: new Date(), - services: { - callCluster: (path: string, opts: any) => new Promise(resolve => resolve()), - alertInstanceFactory: (id: string) => new AlertInstance(), - savedObjectsClient: {} as jest.Mocked, - }, - params: {}, - state: {}, - spaceId: '', - name: '', - tags: [], - previousStartedAt: null, - createdBy: null, - updatedBy: null, -}; - describe('getLicenseExpiration', () => { - const emailAddress = 'foo@foo.com'; - const getUiSettingsService: any = () => ({ - asScopedToClient: (): any => ({ - get: () => new Promise(resolve => resolve(emailAddress)), - }), - }); - const monitoringCluster: any = null; - const logger: Logger = { - warn: jest.fn(), - log: jest.fn(), - debug: jest.fn(), - trace: jest.fn(), - error: jest.fn(), - fatal: jest.fn(), - info: jest.fn(), - get: jest.fn(), + const services: MockServices | AlertServices = { + callCluster: jest.fn(), + alertInstanceFactory: jest.fn(), + savedObjectsClient: savedObjectsClientMock.create(), }; - const getLogger = (): Logger => logger; - const ccrEnabled = false; - afterEach(() => { - (logger.warn as jest.Mock).mockClear(); - }); - - it('should have the right id and actionGroups', () => { - const alert = getLicenseExpiration( - getUiSettingsService, - monitoringCluster, - getLogger, - ccrEnabled - ); - expect(alert.id).toBe(ALERT_TYPE_LICENSE_EXPIRATION); - expect(alert.actionGroups).toEqual([{ id: 'default', name: 'Default' }]); - }); + const params: AlertCommonParams = { + dateFormat: 'YYYY', + timezone: 'UTC', + }; - it('should return the state if no license is provided', async () => { - const alert = getLicenseExpiration( - getUiSettingsService, - monitoringCluster, - getLogger, - ccrEnabled - ); + const emailAddress = 'foo@foo.com'; + const clusterUuid = 'kdksdfj434'; + const clusterName = 'monitoring_test'; + const dateFormat = 'YYYY-MM-DD'; + const cluster = { clusterUuid, clusterName }; + const defaultUiState = { + isFiring: false, + severity: 0, + message: null, + resolvedMS: 0, + lastCheckedMS: 0, + triggeredMS: 0, + }; - const services: MockServices | AlertServices = { - callCluster: jest.fn(), - alertInstanceFactory: jest.fn(), - savedObjectsClient: savedObjectsClientMock.create(), + async function setupAlert( + license: AlertLicense | null, + expiredCheckDateMS: number, + preparedAlertResponse: PreparedAlert | null | undefined = undefined + ): Promise { + const logger: Logger = { + warn: jest.fn(), + log: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), + error: jest.fn(), + fatal: jest.fn(), + info: jest.fn(), + get: jest.fn(), }; - const state = { foo: 1 }; - - const result = await alert.executor({ - ...alertExecutorOptions, - services, - params, - state, - }); - - expect(result).toEqual(state); - }); + const getLogger = (): Logger => logger; + const ccrEnabled = false; + (getPreparedAlert as jest.Mock).mockImplementation(() => { + if (preparedAlertResponse !== undefined) { + return preparedAlertResponse; + } - it('should log a warning if no email is provided', async () => { - const customGetUiSettingsService: any = () => ({ - asScopedToClient: () => ({ - get: () => null, - }), + return { + emailAddress, + data: [license], + clusters: [cluster], + dateFormat, + }; }); - const alert = getLicenseExpiration( - customGetUiSettingsService, - monitoringCluster, - getLogger, - ccrEnabled - ); - const services = { - callCluster: jest.fn( - (method: string, { filterPath }): Promise => { - return new Promise(resolve => { - if (filterPath.includes('hits.hits._source.license.*')) { - resolve( - fillLicense({ - status: 'good', - type: 'basic', - expiry_date_in_millis: moment() - .add(7, 'days') - .valueOf(), - }) - ); - } - resolve({}); - }); - } - ), - alertInstanceFactory: jest.fn(), - savedObjectsClient: savedObjectsClientMock.create(), + const alert = getLicenseExpiration(null as any, null as any, getLogger, ccrEnabled); + const state: AlertCommonState = { + [clusterUuid]: { + expiredCheckDateMS, + ui: { ...defaultUiState }, + } as AlertLicensePerClusterState, }; - const state = {}; + return (await alert.executor({ services, params, state } as any)) as AlertCommonState; + } - await alert.executor({ - ...alertExecutorOptions, - services, - params, - state, - }); - - expect((logger.warn as jest.Mock).mock.calls.length).toBe(1); - expect(logger.warn).toHaveBeenCalledWith( - `Unable to send email for ${ALERT_TYPE_LICENSE_EXPIRATION} because there is no email configured.` - ); + afterEach(() => { + (executeActions as jest.Mock).mockClear(); + (getPreparedAlert as jest.Mock).mockClear(); }); - it('should fire actions if going to expire', async () => { - const scheduleActions = jest.fn(); - const alertInstanceFactory = jest.fn( - (id: string): AlertInstance => { - const instance = new AlertInstance(); - instance.scheduleActions = scheduleActions; - return instance; - } - ); + it('should have the right id and actionGroups', () => { + const alert = getLicenseExpiration(null as any, null as any, jest.fn(), false); + expect(alert.id).toBe(ALERT_TYPE_LICENSE_EXPIRATION); + expect(alert.actionGroups).toEqual([{ id: 'default', name: 'Default' }]); + }); - const alert = getLicenseExpiration( - getUiSettingsService, - monitoringCluster, - getLogger, - ccrEnabled - ); + it('should return the state if no license is provided', async () => { + const result = await setupAlert(null, 0, null); + expect(result[clusterUuid].ui).toEqual(defaultUiState); + }); - const savedObjectsClient = savedObjectsClientMock.create(); - savedObjectsClient.get.mockReturnValue( - new Promise(resolve => { - const savedObject: SavedObject = { - id: '', - type: '', - references: [], - attributes: { - [MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS]: emailAddress, - }, - }; - resolve(savedObject); - }) - ); - const services = { - callCluster: jest.fn( - (method: string, { filterPath }): Promise => { - return new Promise(resolve => { - if (filterPath.includes('hits.hits._source.license.*')) { - resolve( - fillLicense( - { - status: 'active', - type: 'gold', - expiry_date_in_millis: moment() - .add(7, 'days') - .valueOf(), - }, - clusterUuid - ) - ); - } - resolve({}); - }); - } - ), - alertInstanceFactory, - savedObjectsClient, + it('should fire actions if going to expire', async () => { + const expiryDateMS = moment() + .add(7, 'days') + .valueOf(); + const license = { + status: 'active', + type: 'gold', + expiryDateMS, + clusterUuid, }; - - const state = {}; - - const result: AlertState = (await alert.executor({ - ...alertExecutorOptions, - services, - params, - state, - })) as AlertState; - - const newState: AlertClusterState = result[clusterUuid] as AlertClusterState; - + const result = await setupAlert(license, 0); + const newState = result[clusterUuid] as AlertLicensePerClusterState; expect(newState.expiredCheckDateMS > 0).toBe(true); - expect(scheduleActions.mock.calls.length).toBe(1); - expect(scheduleActions.mock.calls[0][1].subject).toBe( - 'NEW X-Pack Monitoring: License Expiration' + expect(executeActions).toHaveBeenCalledWith( + undefined, + cluster, + moment.utc(expiryDateMS), + dateFormat, + emailAddress ); - expect(scheduleActions.mock.calls[0][1].to).toBe(emailAddress); }); it('should fire actions if the user fixed their license', async () => { - const scheduleActions = jest.fn(); - const alertInstanceFactory = jest.fn( - (id: string): AlertInstance => { - const instance = new AlertInstance(); - instance.scheduleActions = scheduleActions; - return instance; - } - ); - const alert = getLicenseExpiration( - getUiSettingsService, - monitoringCluster, - getLogger, - ccrEnabled - ); - - const savedObjectsClient = savedObjectsClientMock.create(); - savedObjectsClient.get.mockReturnValue( - new Promise(resolve => { - const savedObject: SavedObject = { - id: '', - type: '', - references: [], - attributes: { - [MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS]: emailAddress, - }, - }; - resolve(savedObject); - }) - ); - const services = { - callCluster: jest.fn( - (method: string, { filterPath }): Promise => { - return new Promise(resolve => { - if (filterPath.includes('hits.hits._source.license.*')) { - resolve( - fillLicense( - { - status: 'active', - type: 'gold', - expiry_date_in_millis: moment() - .add(120, 'days') - .valueOf(), - }, - clusterUuid - ) - ); - } - resolve({}); - }); - } - ), - alertInstanceFactory, - savedObjectsClient, - }; - - const state: AlertState = { - [clusterUuid]: { - expiredCheckDateMS: moment() - .subtract(1, 'day') - .valueOf(), - ui: { isFiring: true, severity: 0, message: null, resolvedMS: 0, expirationTime: 0 }, - }, + const expiryDateMS = moment() + .add(365, 'days') + .valueOf(); + const license = { + status: 'active', + type: 'gold', + expiryDateMS, + clusterUuid, }; - - const result: AlertState = (await alert.executor({ - ...alertExecutorOptions, - services, - params, - state, - })) as AlertState; - - const newState: AlertClusterState = result[clusterUuid] as AlertClusterState; + const result = await setupAlert(license, 100); + const newState = result[clusterUuid] as AlertLicensePerClusterState; expect(newState.expiredCheckDateMS).toBe(0); - expect(scheduleActions.mock.calls.length).toBe(1); - expect(scheduleActions.mock.calls[0][1].subject).toBe( - 'RESOLVED X-Pack Monitoring: License Expiration' + expect(executeActions).toHaveBeenCalledWith( + undefined, + cluster, + moment.utc(expiryDateMS), + dateFormat, + emailAddress, + true ); - expect(scheduleActions.mock.calls[0][1].to).toBe(emailAddress); }); it('should not fire actions for trial license that expire in more than 14 days', async () => { - const scheduleActions = jest.fn(); - const alertInstanceFactory = jest.fn( - (id: string): AlertInstance => { - const instance = new AlertInstance(); - instance.scheduleActions = scheduleActions; - return instance; - } - ); - const alert = getLicenseExpiration( - getUiSettingsService, - monitoringCluster, - getLogger, - ccrEnabled - ); - - const savedObjectsClient = savedObjectsClientMock.create(); - savedObjectsClient.get.mockReturnValue( - new Promise(resolve => { - const savedObject: SavedObject = { - id: '', - type: '', - references: [], - attributes: { - [MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS]: emailAddress, - }, - }; - resolve(savedObject); - }) - ); - const services = { - callCluster: jest.fn( - (method: string, { filterPath }): Promise => { - return new Promise(resolve => { - if (filterPath.includes('hits.hits._source.license.*')) { - resolve( - fillLicense( - { - status: 'active', - type: 'trial', - expiry_date_in_millis: moment() - .add(15, 'days') - .valueOf(), - }, - clusterUuid - ) - ); - } - resolve({}); - }); - } - ), - alertInstanceFactory, - savedObjectsClient, + const expiryDateMS = moment() + .add(20, 'days') + .valueOf(); + const license = { + status: 'active', + type: 'trial', + expiryDateMS, + clusterUuid, }; - - const state = {}; - const result: AlertState = (await alert.executor({ - ...alertExecutorOptions, - services, - params, - state, - })) as AlertState; - - const newState: AlertClusterState = result[clusterUuid] as AlertClusterState; - expect(newState.expiredCheckDateMS).toBe(undefined); - expect(scheduleActions).not.toHaveBeenCalled(); + const result = await setupAlert(license, 0); + const newState = result[clusterUuid] as AlertLicensePerClusterState; + expect(newState.expiredCheckDateMS).toBe(0); + expect(executeActions).not.toHaveBeenCalled(); }); it('should fire actions for trial license that in 14 days or less', async () => { - const scheduleActions = jest.fn(); - const alertInstanceFactory = jest.fn( - (id: string): AlertInstance => { - const instance = new AlertInstance(); - instance.scheduleActions = scheduleActions; - return instance; - } - ); - const alert = getLicenseExpiration( - getUiSettingsService, - monitoringCluster, - getLogger, - ccrEnabled - ); - - const savedObjectsClient = savedObjectsClientMock.create(); - savedObjectsClient.get.mockReturnValue( - new Promise(resolve => { - const savedObject: SavedObject = { - id: '', - type: '', - references: [], - attributes: { - [MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS]: emailAddress, - }, - }; - resolve(savedObject); - }) - ); - const services = { - callCluster: jest.fn( - (method: string, { filterPath }): Promise => { - return new Promise(resolve => { - if (filterPath.includes('hits.hits._source.license.*')) { - resolve( - fillLicense( - { - status: 'active', - type: 'trial', - expiry_date_in_millis: moment() - .add(13, 'days') - .valueOf(), - }, - clusterUuid - ) - ); - } - resolve({}); - }); - } - ), - alertInstanceFactory, - savedObjectsClient, + const expiryDateMS = moment() + .add(7, 'days') + .valueOf(); + const license = { + status: 'active', + type: 'trial', + expiryDateMS, + clusterUuid, }; - - const state = {}; - const result: AlertState = (await alert.executor({ - ...alertExecutorOptions, - services, - params, - state, - })) as AlertState; - - const newState: AlertClusterState = result[clusterUuid] as AlertClusterState; + const result = await setupAlert(license, 0); + const newState = result[clusterUuid] as AlertLicensePerClusterState; expect(newState.expiredCheckDateMS > 0).toBe(true); - expect(scheduleActions.mock.calls.length).toBe(1); + expect(executeActions).toHaveBeenCalledWith( + undefined, + cluster, + moment.utc(expiryDateMS), + dateFormat, + emailAddress + ); }); }); diff --git a/x-pack/plugins/monitoring/server/alerts/license_expiration.ts b/x-pack/plugins/monitoring/server/alerts/license_expiration.ts index 93397ff3641ae..2e5356150086b 100644 --- a/x-pack/plugins/monitoring/server/alerts/license_expiration.ts +++ b/x-pack/plugins/monitoring/server/alerts/license_expiration.ts @@ -5,24 +5,20 @@ */ import moment from 'moment-timezone'; -import { get } from 'lodash'; import { Logger, ICustomClusterClient, UiSettingsServiceStart } from 'src/core/server'; import { i18n } from '@kbn/i18n'; -import { ALERT_TYPE_LICENSE_EXPIRATION, INDEX_PATTERN_ELASTICSEARCH } from '../../common/constants'; +import { ALERT_TYPE_LICENSE_EXPIRATION } from '../../common/constants'; import { AlertType } from '../../../../plugins/alerting/server'; import { fetchLicenses } from '../lib/alerts/fetch_licenses'; -import { fetchDefaultEmailAddress } from '../lib/alerts/fetch_default_email_address'; -import { fetchClusters } from '../lib/alerts/fetch_clusters'; -import { fetchAvailableCcs } from '../lib/alerts/fetch_available_ccs'; import { - AlertLicense, - AlertState, - AlertClusterState, - AlertClusterUiState, - LicenseExpirationAlertExecutorOptions, + AlertCommonState, + AlertLicensePerClusterState, + AlertCommonExecutorOptions, + AlertCommonCluster, + AlertLicensePerClusterUiState, } from './types'; -import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern'; import { executeActions, getUiMessage } from '../lib/alerts/license_expiration.lib'; +import { getPreparedAlert } from '../lib/alerts/get_prepared_alert'; const EXPIRES_DAYS = [60, 30, 14, 7]; @@ -32,14 +28,6 @@ export const getLicenseExpiration = ( getLogger: (...scopes: string[]) => Logger, ccsEnabled: boolean ): AlertType => { - async function getCallCluster(services: any): Promise { - if (!monitoringCluster) { - return services.callCluster; - } - - return monitoringCluster.callAsInternalUser; - } - const logger = getLogger(ALERT_TYPE_LICENSE_EXPIRATION); return { id: ALERT_TYPE_LICENSE_EXPIRATION, @@ -53,54 +41,50 @@ export const getLicenseExpiration = ( }, ], defaultActionGroupId: 'default', - async executor({ - services, - params, - state, - }: LicenseExpirationAlertExecutorOptions): Promise { + async executor({ services, params, state }: AlertCommonExecutorOptions): Promise { logger.debug( `Firing alert with params: ${JSON.stringify(params)} and state: ${JSON.stringify(state)}` ); - const callCluster = await getCallCluster(services); - - // Support CCS use cases by querying to find available remote clusters - // and then adding those to the index pattern we are searching against - let esIndexPattern = INDEX_PATTERN_ELASTICSEARCH; - if (ccsEnabled) { - const availableCcs = await fetchAvailableCcs(callCluster); - if (availableCcs.length > 0) { - esIndexPattern = getCcsIndexPattern(esIndexPattern, availableCcs); - } - } - - const clusters = await fetchClusters(callCluster, esIndexPattern); + const preparedAlert = await getPreparedAlert( + ALERT_TYPE_LICENSE_EXPIRATION, + getUiSettingsService, + monitoringCluster, + logger, + ccsEnabled, + services, + fetchLicenses + ); - // Fetch licensing information from cluster_stats documents - const licenses: AlertLicense[] = await fetchLicenses(callCluster, clusters, esIndexPattern); - if (licenses.length === 0) { - logger.warn(`No license found for ${ALERT_TYPE_LICENSE_EXPIRATION}.`); + if (!preparedAlert) { return state; } - const uiSettings = (await getUiSettingsService()).asScopedToClient( - services.savedObjectsClient - ); - const dateFormat: string = await uiSettings.get('dateFormat'); - const timezone: string = await uiSettings.get('dateFormat:tz'); - const emailAddress = await fetchDefaultEmailAddress(uiSettings); - if (!emailAddress) { - // TODO: we can do more here - logger.warn( - `Unable to send email for ${ALERT_TYPE_LICENSE_EXPIRATION} because there is no email configured.` - ); - return; - } + const { emailAddress, data: licenses, clusters, dateFormat } = preparedAlert; - const result: AlertState = { ...state }; + const result: AlertCommonState = { ...state }; + const defaultAlertState: AlertLicensePerClusterState = { + expiredCheckDateMS: 0, + ui: { + isFiring: false, + message: null, + severity: 0, + resolvedMS: 0, + lastCheckedMS: 0, + triggeredMS: 0, + }, + }; for (const license of licenses) { - const licenseState: AlertClusterState = state[license.clusterUuid] || {}; + const alertState: AlertLicensePerClusterState = + (state[license.clusterUuid] as AlertLicensePerClusterState) || defaultAlertState; + const cluster = clusters.find( + (c: AlertCommonCluster) => c.clusterUuid === license.clusterUuid + ); + if (!cluster) { + logger.warn(`Unable to find cluster for clusterUuid='${license.clusterUuid}'`); + continue; + } const $expiry = moment.utc(license.expiryDateMS); let isExpired = false; let severity = 0; @@ -123,31 +107,26 @@ export const getLicenseExpiration = ( } } - const ui: AlertClusterUiState = get(licenseState, 'ui', { - isFiring: false, - message: null, - severity: 0, - resolvedMS: 0, - expirationTime: 0, - }); + const ui = alertState.ui; + let triggered = ui.triggeredMS; let resolved = ui.resolvedMS; let message = ui.message; - let expiredCheckDate = licenseState.expiredCheckDateMS; + let expiredCheckDate = alertState.expiredCheckDateMS; const instance = services.alertInstanceFactory(ALERT_TYPE_LICENSE_EXPIRATION); if (isExpired) { - if (!licenseState.expiredCheckDateMS) { + if (!alertState.expiredCheckDateMS) { logger.debug(`License will expire soon, sending email`); - executeActions(instance, license, $expiry, dateFormat, emailAddress); - expiredCheckDate = moment().valueOf(); + executeActions(instance, cluster, $expiry, dateFormat, emailAddress); + expiredCheckDate = triggered = moment().valueOf(); } - message = getUiMessage(license, timezone); + message = getUiMessage(); resolved = 0; - } else if (!isExpired && licenseState.expiredCheckDateMS) { + } else if (!isExpired && alertState.expiredCheckDateMS) { logger.debug(`License expiration has been resolved, sending email`); - executeActions(instance, license, $expiry, dateFormat, emailAddress, true); + executeActions(instance, cluster, $expiry, dateFormat, emailAddress, true); expiredCheckDate = 0; - message = getUiMessage(license, timezone, true); + message = getUiMessage(true); resolved = moment().valueOf(); } @@ -159,8 +138,10 @@ export const getLicenseExpiration = ( isFiring: expiredCheckDate > 0, severity, resolvedMS: resolved, - }, - }; + triggeredMS: triggered, + lastCheckedMS: moment().valueOf(), + } as AlertLicensePerClusterUiState, + } as AlertLicensePerClusterState; } return result; diff --git a/x-pack/plugins/monitoring/server/alerts/types.d.ts b/x-pack/plugins/monitoring/server/alerts/types.d.ts index ff47d6f2ad4dc..b689d008b51a7 100644 --- a/x-pack/plugins/monitoring/server/alerts/types.d.ts +++ b/x-pack/plugins/monitoring/server/alerts/types.d.ts @@ -5,41 +5,79 @@ */ import { Moment } from 'moment'; import { AlertExecutorOptions } from '../../../alerting/server'; +import { AlertClusterStateState, AlertCommonPerClusterMessageTokenType } from './enums'; export interface AlertLicense { status: string; type: string; expiryDateMS: number; clusterUuid: string; - clusterName: string; } -export interface AlertState { - [clusterUuid: string]: AlertClusterState; +export interface AlertClusterState { + state: AlertClusterStateState; + clusterUuid: string; +} + +export interface AlertCommonState { + [clusterUuid: string]: AlertCommonPerClusterState; } -export interface AlertClusterState { - expiredCheckDateMS: number | Moment; - ui: AlertClusterUiState; +export interface AlertCommonPerClusterState { + ui: AlertCommonPerClusterUiState; } -export interface AlertClusterUiState { +export interface AlertClusterStatePerClusterState extends AlertCommonPerClusterState { + state: AlertClusterStateState; +} + +export interface AlertLicensePerClusterState extends AlertCommonPerClusterState { + expiredCheckDateMS: number; +} + +export interface AlertCommonPerClusterUiState { isFiring: boolean; severity: number; - message: string | null; + message: AlertCommonPerClusterMessage | null; resolvedMS: number; + lastCheckedMS: number; + triggeredMS: number; +} + +export interface AlertCommonPerClusterMessage { + text: string; // Do this. #link this is a link #link + tokens?: AlertCommonPerClusterMessageToken[]; +} + +export interface AlertCommonPerClusterMessageToken { + startToken: string; + endToken?: string; + type: AlertCommonPerClusterMessageTokenType; +} + +export interface AlertCommonPerClusterMessageLinkToken extends AlertCommonPerClusterMessageToken { + url?: string; +} + +export interface AlertCommonPerClusterMessageTimeToken extends AlertCommonPerClusterMessageToken { + isRelative: boolean; + isAbsolute: boolean; +} + +export interface AlertLicensePerClusterUiState extends AlertCommonPerClusterUiState { expirationTime: number; } -export interface AlertCluster { +export interface AlertCommonCluster { clusterUuid: string; + clusterName: string; } -export interface LicenseExpirationAlertExecutorOptions extends AlertExecutorOptions { - state: AlertState; +export interface AlertCommonExecutorOptions extends AlertExecutorOptions { + state: AlertCommonState; } -export interface AlertParams { +export interface AlertCommonParams { dateFormat: string; timezone: string; } diff --git a/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.test.ts new file mode 100644 index 0000000000000..81e375734cc50 --- /dev/null +++ b/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.test.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { executeActions, getUiMessage } from './cluster_state.lib'; +import { AlertClusterStateState } from '../../alerts/enums'; +import { AlertCommonPerClusterMessageLinkToken } from '../../alerts/types'; + +describe('clusterState lib', () => { + describe('executeActions', () => { + const clusterName = 'clusterA'; + const instance: any = { scheduleActions: jest.fn() }; + const license: any = { clusterName }; + const status = AlertClusterStateState.Green; + const emailAddress = 'test@test.com'; + + beforeEach(() => { + instance.scheduleActions.mockClear(); + }); + + it('should schedule actions when firing', () => { + executeActions(instance, license, status, emailAddress, false); + expect(instance.scheduleActions).toHaveBeenCalledWith('default', { + subject: 'NEW X-Pack Monitoring: Cluster Status', + message: `Allocate missing replica shards for cluster '${clusterName}'`, + to: emailAddress, + }); + }); + + it('should have a different message for red state', () => { + executeActions(instance, license, AlertClusterStateState.Red, emailAddress, false); + expect(instance.scheduleActions).toHaveBeenCalledWith('default', { + subject: 'NEW X-Pack Monitoring: Cluster Status', + message: `Allocate missing primary and replica shards for cluster '${clusterName}'`, + to: emailAddress, + }); + }); + + it('should schedule actions when resolved', () => { + executeActions(instance, license, status, emailAddress, true); + expect(instance.scheduleActions).toHaveBeenCalledWith('default', { + subject: 'RESOLVED X-Pack Monitoring: Cluster Status', + message: `This cluster alert has been resolved: Allocate missing replica shards for cluster '${clusterName}'`, + to: emailAddress, + }); + }); + }); + + describe('getUiMessage', () => { + it('should return a message when firing', () => { + const message = getUiMessage(AlertClusterStateState.Red, false); + expect(message.text).toBe( + `Elasticsearch cluster status is red. #start_linkAllocate missing primary and replica shards#end_link` + ); + expect(message.tokens && message.tokens.length).toBe(1); + expect(message.tokens && message.tokens[0].startToken).toBe('#start_link'); + expect(message.tokens && message.tokens[0].endToken).toBe('#end_link'); + expect( + message.tokens && (message.tokens[0] as AlertCommonPerClusterMessageLinkToken).url + ).toBe('elasticsearch/indices'); + }); + + it('should return a message when resolved', () => { + const message = getUiMessage(AlertClusterStateState.Green, true); + expect(message.text).toBe(`Elasticsearch cluster status is green.`); + expect(message.tokens).not.toBeDefined(); + }); + }); +}); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.ts b/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.ts new file mode 100644 index 0000000000000..ae66d603507ca --- /dev/null +++ b/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { i18n } from '@kbn/i18n'; +import { AlertInstance } from '../../../../alerting/server'; +import { + AlertCommonCluster, + AlertCommonPerClusterMessage, + AlertCommonPerClusterMessageLinkToken, +} from '../../alerts/types'; +import { AlertClusterStateState, AlertCommonPerClusterMessageTokenType } from '../../alerts/enums'; + +const RESOLVED_SUBJECT = i18n.translate('xpack.monitoring.alerts.clusterStatus.resolvedSubject', { + defaultMessage: 'RESOLVED X-Pack Monitoring: Cluster Status', +}); + +const NEW_SUBJECT = i18n.translate('xpack.monitoring.alerts.clusterStatus.newSubject', { + defaultMessage: 'NEW X-Pack Monitoring: Cluster Status', +}); + +const RED_STATUS_MESSAGE = i18n.translate('xpack.monitoring.alerts.clusterStatus.redMessage', { + defaultMessage: 'Allocate missing primary and replica shards', +}); + +const YELLOW_STATUS_MESSAGE = i18n.translate( + 'xpack.monitoring.alerts.clusterStatus.yellowMessage', + { + defaultMessage: 'Allocate missing replica shards', + } +); + +export function executeActions( + instance: AlertInstance, + cluster: AlertCommonCluster, + status: AlertClusterStateState, + emailAddress: string, + resolved: boolean = false +) { + const message = + status === AlertClusterStateState.Red ? RED_STATUS_MESSAGE : YELLOW_STATUS_MESSAGE; + if (resolved) { + instance.scheduleActions('default', { + subject: RESOLVED_SUBJECT, + message: `This cluster alert has been resolved: ${message} for cluster '${cluster.clusterName}'`, + to: emailAddress, + }); + } else { + instance.scheduleActions('default', { + subject: NEW_SUBJECT, + message: `${message} for cluster '${cluster.clusterName}'`, + to: emailAddress, + }); + } +} + +export function getUiMessage( + status: AlertClusterStateState, + resolved: boolean = false +): AlertCommonPerClusterMessage { + if (resolved) { + return { + text: i18n.translate('xpack.monitoring.alerts.clusterStatus.ui.resolvedMessage', { + defaultMessage: `Elasticsearch cluster status is green.`, + }), + }; + } + const message = + status === AlertClusterStateState.Red ? RED_STATUS_MESSAGE : YELLOW_STATUS_MESSAGE; + return { + text: i18n.translate('xpack.monitoring.alerts.clusterStatus.ui.firingMessage', { + defaultMessage: `Elasticsearch cluster status is {status}. #start_link{message}#end_link`, + values: { + status, + message, + }, + }), + tokens: [ + { + startToken: '#start_link', + endToken: '#end_link', + type: AlertCommonPerClusterMessageTokenType.Link, + url: 'elasticsearch/indices', + } as AlertCommonPerClusterMessageLinkToken, + ], + }; +} diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.test.ts new file mode 100644 index 0000000000000..642ae3c39a027 --- /dev/null +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.test.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { fetchClusterState } from './fetch_cluster_state'; + +describe('fetchClusterState', () => { + it('should return the cluster state', async () => { + const status = 'green'; + const clusterUuid = 'sdfdsaj34434'; + const callCluster = jest.fn(() => ({ + hits: { + hits: [ + { + _source: { + cluster_state: { + status, + }, + cluster_uuid: clusterUuid, + }, + }, + ], + }, + })); + + const clusters = [{ clusterUuid, clusterName: 'foo' }]; + const index = '.monitoring-es-*'; + + const state = await fetchClusterState(callCluster, clusters, index); + expect(state).toEqual([ + { + state: status, + clusterUuid, + }, + ]); + }); +}); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.ts new file mode 100644 index 0000000000000..66ea30d5f2e96 --- /dev/null +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { get } from 'lodash'; +import { AlertCommonCluster, AlertClusterState } from '../../alerts/types'; + +export async function fetchClusterState( + callCluster: any, + clusters: AlertCommonCluster[], + index: string +): Promise { + const params = { + index, + filterPath: ['hits.hits._source.cluster_state.status', 'hits.hits._source.cluster_uuid'], + body: { + size: 1, + sort: [{ timestamp: { order: 'desc' } }], + query: { + bool: { + filter: [ + { + terms: { + cluster_uuid: clusters.map(cluster => cluster.clusterUuid), + }, + }, + { + term: { + type: 'cluster_stats', + }, + }, + { + range: { + timestamp: { + gte: 'now-2m', + }, + }, + }, + ], + }, + }, + }, + }; + + const response = await callCluster('search', params); + return get(response, 'hits.hits', []).map((hit: any) => { + return { + state: get(hit, '_source.cluster_state.status'), + clusterUuid: get(hit, '_source.cluster_uuid'), + }; + }); +} diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.test.ts index 78eb9773df15f..7a9b61f37707b 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.test.ts @@ -6,21 +6,51 @@ import { fetchClusters } from './fetch_clusters'; describe('fetchClusters', () => { + const clusterUuid = '1sdfds734'; + const clusterName = 'monitoring'; + it('return a list of clusters', async () => { const callCluster = jest.fn().mockImplementation(() => ({ - aggregations: { - clusters: { - buckets: [ - { - key: 'clusterA', + hits: { + hits: [ + { + _source: { + cluster_uuid: clusterUuid, + cluster_name: clusterName, + }, + }, + ], + }, + })); + const index = '.monitoring-es-*'; + const result = await fetchClusters(callCluster, index); + expect(result).toEqual([{ clusterUuid, clusterName }]); + }); + + it('return the metadata name if available', async () => { + const metadataName = 'custom-monitoring'; + const callCluster = jest.fn().mockImplementation(() => ({ + hits: { + hits: [ + { + _source: { + cluster_uuid: clusterUuid, + cluster_name: clusterName, + cluster_settings: { + cluster: { + metadata: { + display_name: metadataName, + }, + }, + }, }, - ], - }, + }, + ], }, })); const index = '.monitoring-es-*'; const result = await fetchClusters(callCluster, index); - expect(result).toEqual([{ clusterUuid: 'clusterA' }]); + expect(result).toEqual([{ clusterUuid, clusterName: metadataName }]); }); it('should limit the time period in the query', async () => { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts index 8ef7339618a2c..d1513ac16fb15 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts @@ -4,18 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ import { get } from 'lodash'; -import { AlertCluster } from '../../alerts/types'; +import { AlertCommonCluster } from '../../alerts/types'; -interface AggregationResult { - key: string; -} - -export async function fetchClusters(callCluster: any, index: string): Promise { +export async function fetchClusters( + callCluster: any, + index: string +): Promise { const params = { index, - filterPath: 'aggregations.clusters.buckets', + filterPath: [ + 'hits.hits._source.cluster_settings.cluster.metadata.display_name', + 'hits.hits._source.cluster_uuid', + 'hits.hits._source.cluster_name', + ], body: { - size: 0, + size: 1000, query: { bool: { filter: [ @@ -34,19 +37,21 @@ export async function fetchClusters(callCluster: any, index: string): Promise ({ - clusterUuid: bucket.key, - })); + return get(response, 'hits.hits', []).map((hit: any) => { + const clusterName: string = + get(hit, '_source.cluster_settings.cluster.metadata.display_name') || + get(hit, '_source.cluster_name') || + get(hit, '_source.cluster_uuid'); + return { + clusterUuid: get(hit, '_source.cluster_uuid'), + clusterName, + }; + }); } diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.test.ts index dd6c074e68b1f..9dcb4ffb82a5f 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.test.ts @@ -6,28 +6,28 @@ import { fetchLicenses } from './fetch_licenses'; describe('fetchLicenses', () => { + const clusterName = 'MyCluster'; + const clusterUuid = 'clusterA'; + const license = { + status: 'active', + expiry_date_in_millis: 1579532493876, + type: 'basic', + }; + it('return a list of licenses', async () => { - const clusterName = 'MyCluster'; - const clusterUuid = 'clusterA'; - const license = { - status: 'active', - expiry_date_in_millis: 1579532493876, - type: 'basic', - }; const callCluster = jest.fn().mockImplementation(() => ({ hits: { hits: [ { _source: { license, - cluster_name: clusterName, cluster_uuid: clusterUuid, }, }, ], }, })); - const clusters = [{ clusterUuid }]; + const clusters = [{ clusterUuid, clusterName }]; const index = '.monitoring-es-*'; const result = await fetchLicenses(callCluster, clusters, index); expect(result).toEqual([ @@ -36,15 +36,13 @@ describe('fetchLicenses', () => { type: license.type, expiryDateMS: license.expiry_date_in_millis, clusterUuid, - clusterName, }, ]); }); it('should only search for the clusters provided', async () => { - const clusterUuid = 'clusterA'; const callCluster = jest.fn(); - const clusters = [{ clusterUuid }]; + const clusters = [{ clusterUuid, clusterName }]; const index = '.monitoring-es-*'; await fetchLicenses(callCluster, clusters, index); const params = callCluster.mock.calls[0][1]; @@ -52,54 +50,11 @@ describe('fetchLicenses', () => { }); it('should limit the time period in the query', async () => { - const clusterUuid = 'clusterA'; const callCluster = jest.fn(); - const clusters = [{ clusterUuid }]; + const clusters = [{ clusterUuid, clusterName }]; const index = '.monitoring-es-*'; await fetchLicenses(callCluster, clusters, index); const params = callCluster.mock.calls[0][1]; expect(params.body.query.bool.filter[2].range.timestamp.gte).toBe('now-2m'); }); - - it('should give priority to the metadata name', async () => { - const clusterName = 'MyCluster'; - const clusterUuid = 'clusterA'; - const license = { - status: 'active', - expiry_date_in_millis: 1579532493876, - type: 'basic', - }; - const callCluster = jest.fn().mockImplementation(() => ({ - hits: { - hits: [ - { - _source: { - license, - cluster_name: 'fakeName', - cluster_uuid: clusterUuid, - cluster_settings: { - cluster: { - metadata: { - display_name: clusterName, - }, - }, - }, - }, - }, - ], - }, - })); - const clusters = [{ clusterUuid }]; - const index = '.monitoring-es-*'; - const result = await fetchLicenses(callCluster, clusters, index); - expect(result).toEqual([ - { - status: license.status, - type: license.type, - expiryDateMS: license.expiry_date_in_millis, - clusterUuid, - clusterName, - }, - ]); - }); }); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts index 31a68e8aa9c3e..5b05c907e796e 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts @@ -4,21 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ import { get } from 'lodash'; -import { AlertLicense, AlertCluster } from '../../alerts/types'; +import { AlertLicense, AlertCommonCluster } from '../../alerts/types'; export async function fetchLicenses( callCluster: any, - clusters: AlertCluster[], + clusters: AlertCommonCluster[], index: string ): Promise { const params = { index, - filterPath: [ - 'hits.hits._source.license.*', - 'hits.hits._source.cluster_settings.cluster.metadata.display_name', - 'hits.hits._source.cluster_uuid', - 'hits.hits._source.cluster_name', - ], + filterPath: ['hits.hits._source.license.*', 'hits.hits._source.cluster_uuid'], body: { size: 1, sort: [{ timestamp: { order: 'desc' } }], @@ -50,17 +45,12 @@ export async function fetchLicenses( const response = await callCluster('search', params); return get(response, 'hits.hits', []).map((hit: any) => { - const clusterName: string = - get(hit, '_source.cluster_settings.cluster.metadata.display_name') || - get(hit, '_source.cluster_name') || - get(hit, '_source.cluster_uuid'); const rawLicense: any = get(hit, '_source.license', {}); const license: AlertLicense = { status: rawLicense.status, type: rawLicense.type, expiryDateMS: rawLicense.expiry_date_in_millis, clusterUuid: get(hit, '_source.cluster_uuid'), - clusterName, }; return license; }); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.test.ts new file mode 100644 index 0000000000000..a3bcb61afacd6 --- /dev/null +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.test.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { fetchStatus } from './fetch_status'; +import { AlertCommonPerClusterState } from '../../alerts/types'; + +describe('fetchStatus', () => { + const alertType = 'monitoringTest'; + const log = { warn: jest.fn() }; + const start = 0; + const end = 0; + const id = 1; + const defaultUiState = { + isFiring: false, + severity: 0, + message: null, + resolvedMS: 0, + lastCheckedMS: 0, + triggeredMS: 0, + }; + const alertsClient = { + find: jest.fn(() => ({ + total: 1, + data: [ + { + id, + }, + ], + })), + getAlertState: jest.fn(() => ({ + alertTypeState: { + state: { + ui: defaultUiState, + } as AlertCommonPerClusterState, + }, + })), + }; + + afterEach(() => { + (alertsClient.find as jest.Mock).mockClear(); + (alertsClient.getAlertState as jest.Mock).mockClear(); + }); + + it('should fetch from the alerts client', async () => { + const status = await fetchStatus(alertsClient as any, [alertType], start, end, log as any); + expect(status).toEqual([]); + }); + + it('should return alerts that are firing', async () => { + alertsClient.getAlertState = jest.fn(() => ({ + alertTypeState: { + state: { + ui: { + ...defaultUiState, + isFiring: true, + }, + } as AlertCommonPerClusterState, + }, + })); + + const status = await fetchStatus(alertsClient as any, [alertType], start, end, log as any); + expect(status.length).toBe(1); + expect(status[0].type).toBe(alertType); + expect(status[0].isFiring).toBe(true); + }); + + it('should return alerts that have been resolved in the time period', async () => { + alertsClient.getAlertState = jest.fn(() => ({ + alertTypeState: { + state: { + ui: { + ...defaultUiState, + resolvedMS: 1500, + }, + } as AlertCommonPerClusterState, + }, + })); + + const customStart = 1000; + const customEnd = 2000; + + const status = await fetchStatus( + alertsClient as any, + [alertType], + customStart, + customEnd, + log as any + ); + expect(status.length).toBe(1); + expect(status[0].type).toBe(alertType); + expect(status[0].isFiring).toBe(false); + }); + + it('should pass in the right filter to the alerts client', async () => { + await fetchStatus(alertsClient as any, [alertType], start, end, log as any); + expect((alertsClient.find as jest.Mock).mock.calls[0][0].options.filter).toBe( + `alert.attributes.alertTypeId:${alertType}` + ); + }); + + it('should return nothing if no alert state is found', async () => { + alertsClient.getAlertState = jest.fn(() => ({ + alertTypeState: null, + })) as any; + + const status = await fetchStatus(alertsClient as any, [alertType], start, end, log as any); + expect(status).toEqual([]); + }); + + it('should return nothing if no alerts are found', async () => { + alertsClient.find = jest.fn(() => ({ + total: 0, + data: [], + })) as any; + + const status = await fetchStatus(alertsClient as any, [alertType], start, end, log as any); + expect(status).toEqual([]); + }); +}); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts index 9f7c1d5a994d2..bf6ee965d3b2f 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts @@ -4,81 +4,53 @@ * you may not use this file except in compliance with the Elastic License. */ import moment from 'moment'; -import { get } from 'lodash'; -import { AlertClusterState } from '../../alerts/types'; -import { ALERT_TYPES, LOGGING_TAG } from '../../../common/constants'; +import { Logger } from '../../../../../../src/core/server'; +import { AlertCommonPerClusterState } from '../../alerts/types'; +import { AlertsClient } from '../../../../alerting/server'; export async function fetchStatus( - callCluster: any, + alertsClient: AlertsClient, + alertTypes: string[], start: number, end: number, - clusterUuid: string, - server: any + log: Logger ): Promise { - // TODO: this shouldn't query task manager directly but rather - // use an api exposed by the alerting/actions plugin - // See https://github.com/elastic/kibana/issues/48442 const statuses = await Promise.all( - ALERT_TYPES.map( + alertTypes.map( type => new Promise(async (resolve, reject) => { - try { - const params = { - index: '.kibana_task_manager', - filterPath: ['hits.hits._source.task.state'], - body: { - size: 1, - sort: [{ updated_at: { order: 'desc' } }], - query: { - bool: { - filter: [ - { - term: { - 'task.taskType': `alerting:${type}`, - }, - }, - ], - }, - }, - }, - }; - - const response = await callCluster('search', params); - const state = get(response, 'hits.hits[0]._source.task.state', '{}'); - const clusterState: AlertClusterState = get( - JSON.parse(state), - `alertTypeState.${clusterUuid}`, - { - expiredCheckDateMS: 0, - ui: { - isFiring: false, - message: null, - severity: 0, - resolvedMS: 0, - expirationTime: 0, - }, - } - ); - const isInBetween = moment(clusterState.ui.resolvedMS).isBetween(start, end); - if (clusterState.ui.isFiring || isInBetween) { - return resolve({ - type, - ...clusterState.ui, - }); - } + // We need to get the id from the alertTypeId + const alerts = await alertsClient.find({ + options: { + filter: `alert.attributes.alertTypeId:${type}`, + }, + }); + if (alerts.total === 0) { return resolve(false); - } catch (err) { - const reason = get(err, 'body.error.type'); - if (reason === 'index_not_found_exception') { - server.log( - ['error', LOGGING_TAG], - `Unable to fetch alerts. Alerts depends on task manager, which has not been started yet.` - ); - } else { - server.log(['error', LOGGING_TAG], err.message); - } + } + + if (alerts.total !== 1) { + log.warn(`Found more than one alert for type ${type} which is unexpected.`); + } + + const id = alerts.data[0].id; + + // Now that we have the id, we can get the state + const states = await alertsClient.getAlertState({ id }); + if (!states || !states.alertTypeState) { + log.warn(`No alert states found for type ${type} which is unexpected.`); return resolve(false); } + + const state = Object.values(states.alertTypeState)[0] as AlertCommonPerClusterState; + const isInBetween = moment(state.ui.resolvedMS).isBetween(start, end); + if (state.ui.isFiring || isInBetween) { + return resolve({ + type, + ...state.ui, + }); + } + return resolve(false); }) ) ); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.test.ts new file mode 100644 index 0000000000000..1840a2026a753 --- /dev/null +++ b/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.test.ts @@ -0,0 +1,163 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getPreparedAlert } from './get_prepared_alert'; +import { fetchClusters } from './fetch_clusters'; +import { fetchDefaultEmailAddress } from './fetch_default_email_address'; + +jest.mock('./fetch_clusters', () => ({ + fetchClusters: jest.fn(), +})); + +jest.mock('./fetch_default_email_address', () => ({ + fetchDefaultEmailAddress: jest.fn(), +})); + +describe('getPreparedAlert', () => { + const uiSettings = { get: jest.fn() }; + const alertType = 'test'; + const getUiSettingsService = async () => ({ + asScopedToClient: () => uiSettings, + }); + const monitoringCluster = null; + const logger = { warn: jest.fn() }; + const ccsEnabled = false; + const services = { + callCluster: jest.fn(), + savedObjectsClient: null, + }; + const emailAddress = 'foo@foo.com'; + const data = [{ foo: 1 }]; + const dataFetcher = () => data; + const clusterName = 'MonitoringCluster'; + const clusterUuid = 'sdf34sdf'; + const clusters = [{ clusterName, clusterUuid }]; + + afterEach(() => { + (uiSettings.get as jest.Mock).mockClear(); + (services.callCluster as jest.Mock).mockClear(); + (fetchClusters as jest.Mock).mockClear(); + (fetchDefaultEmailAddress as jest.Mock).mockClear(); + }); + + beforeEach(() => { + (fetchClusters as jest.Mock).mockImplementation(() => clusters); + (fetchDefaultEmailAddress as jest.Mock).mockImplementation(() => emailAddress); + }); + + it('should return fields as expected', async () => { + (uiSettings.get as jest.Mock).mockImplementation(() => { + return emailAddress; + }); + + const alert = await getPreparedAlert( + alertType, + getUiSettingsService as any, + monitoringCluster as any, + logger as any, + ccsEnabled, + services as any, + dataFetcher as any + ); + + expect(alert && alert.emailAddress).toBe(emailAddress); + expect(alert && alert.data).toBe(data); + }); + + it('should add ccs if specified', async () => { + const ccsClusterName = 'remoteCluster'; + (services.callCluster as jest.Mock).mockImplementation(() => { + return { + [ccsClusterName]: { + connected: true, + }, + }; + }); + + await getPreparedAlert( + alertType, + getUiSettingsService as any, + monitoringCluster as any, + logger as any, + true, + services as any, + dataFetcher as any + ); + + expect((fetchClusters as jest.Mock).mock.calls[0][1].includes(ccsClusterName)).toBe(true); + }); + + it('should ignore ccs if no remote clusters are available', async () => { + const ccsClusterName = 'remoteCluster'; + (services.callCluster as jest.Mock).mockImplementation(() => { + return { + [ccsClusterName]: { + connected: false, + }, + }; + }); + + await getPreparedAlert( + alertType, + getUiSettingsService as any, + monitoringCluster as any, + logger as any, + true, + services as any, + dataFetcher as any + ); + + expect((fetchClusters as jest.Mock).mock.calls[0][1].includes(ccsClusterName)).toBe(false); + }); + + it('should pass in the clusters into the data fetcher', async () => { + const customDataFetcher = jest.fn(() => data); + + await getPreparedAlert( + alertType, + getUiSettingsService as any, + monitoringCluster as any, + logger as any, + true, + services as any, + customDataFetcher as any + ); + + expect((customDataFetcher as jest.Mock).mock.calls[0][1]).toBe(clusters); + }); + + it('should return nothing if the data fetcher returns nothing', async () => { + const customDataFetcher = jest.fn(() => []); + + const result = await getPreparedAlert( + alertType, + getUiSettingsService as any, + monitoringCluster as any, + logger as any, + true, + services as any, + customDataFetcher as any + ); + + expect(result).toBe(null); + }); + + it('should return nothing if there is no email address', async () => { + (fetchDefaultEmailAddress as jest.Mock).mockImplementation(() => null); + + const result = await getPreparedAlert( + alertType, + getUiSettingsService as any, + monitoringCluster as any, + logger as any, + true, + services as any, + dataFetcher as any + ); + + expect(result).toBe(null); + }); +}); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.ts b/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.ts new file mode 100644 index 0000000000000..83a9e26e4c589 --- /dev/null +++ b/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.ts @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Logger, ICustomClusterClient, UiSettingsServiceStart } from 'kibana/server'; +import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; +import { AlertServices } from '../../../../alerting/server'; +import { AlertCommonCluster } from '../../alerts/types'; +import { INDEX_PATTERN_ELASTICSEARCH } from '../../../common/constants'; +import { fetchAvailableCcs } from './fetch_available_ccs'; +import { getCcsIndexPattern } from './get_ccs_index_pattern'; +import { fetchClusters } from './fetch_clusters'; +import { fetchDefaultEmailAddress } from './fetch_default_email_address'; + +export interface PreparedAlert { + emailAddress: string; + clusters: AlertCommonCluster[]; + data: any[]; + timezone: string; + dateFormat: string; +} + +async function getCallCluster( + monitoringCluster: ICustomClusterClient, + services: Pick +): Promise { + if (!monitoringCluster) { + return services.callCluster; + } + + return monitoringCluster.callAsInternalUser; +} + +export async function getPreparedAlert( + alertType: string, + getUiSettingsService: () => Promise, + monitoringCluster: ICustomClusterClient, + logger: Logger, + ccsEnabled: boolean, + services: Pick, + dataFetcher: ( + callCluster: CallCluster, + clusters: AlertCommonCluster[], + esIndexPattern: string + ) => Promise +): Promise { + const callCluster = await getCallCluster(monitoringCluster, services); + + // Support CCS use cases by querying to find available remote clusters + // and then adding those to the index pattern we are searching against + let esIndexPattern = INDEX_PATTERN_ELASTICSEARCH; + if (ccsEnabled) { + const availableCcs = await fetchAvailableCcs(callCluster); + if (availableCcs.length > 0) { + esIndexPattern = getCcsIndexPattern(esIndexPattern, availableCcs); + } + } + + const clusters = await fetchClusters(callCluster, esIndexPattern); + + // Fetch the specific data + const data = await dataFetcher(callCluster, clusters, esIndexPattern); + if (data.length === 0) { + logger.warn(`No data found for ${alertType}.`); + return null; + } + + const uiSettings = (await getUiSettingsService()).asScopedToClient(services.savedObjectsClient); + const dateFormat: string = await uiSettings.get('dateFormat'); + const timezone: string = await uiSettings.get('dateFormat:tz'); + const emailAddress = await fetchDefaultEmailAddress(uiSettings); + if (!emailAddress) { + // TODO: we can do more here + logger.warn(`Unable to send email for ${alertType} because there is no email configured.`); + return null; + } + + return { + emailAddress, + data, + clusters, + dateFormat, + timezone, + }; +} diff --git a/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.test.ts index 1a2eb1e44be84..6c0301b6cc347 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.test.ts @@ -39,17 +39,26 @@ describe('licenseExpiration lib', () => { }); describe('getUiMessage', () => { - const timezone = 'Europe/London'; - const license: any = { expiryDateMS: moment.tz('2020-01-20 08:00:00', timezone).utc() }; - it('should return a message when firing', () => { - const message = getUiMessage(license, timezone, false); - expect(message).toBe(`This cluster's license is going to expire in #relative at #absolute.`); + const message = getUiMessage(false); + expect(message.text).toBe( + `This cluster's license is going to expire in #relative at #absolute. #start_linkPlease update your license#end_link` + ); + // LOL How do I avoid this in TS???? + if (!message.tokens) { + return expect(false).toBe(true); + } + expect(message.tokens.length).toBe(3); + expect(message.tokens[0].startToken).toBe('#relative'); + expect(message.tokens[1].startToken).toBe('#absolute'); + expect(message.tokens[2].startToken).toBe('#start_link'); + expect(message.tokens[2].endToken).toBe('#end_link'); }); it('should return a message when resolved', () => { - const message = getUiMessage(license, timezone, true); - expect(message).toBe(`This cluster's license is active.`); + const message = getUiMessage(true); + expect(message.text).toBe(`This cluster's license is active.`); + expect(message.tokens).not.toBeDefined(); }); }); }); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts b/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts index 41b68d69bbd25..a590021a2f29b 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts @@ -6,7 +6,13 @@ import { Moment } from 'moment-timezone'; import { i18n } from '@kbn/i18n'; import { AlertInstance } from '../../../../alerting/server'; -import { AlertLicense } from '../../alerts/types'; +import { + AlertCommonPerClusterMessageLinkToken, + AlertCommonPerClusterMessageTimeToken, + AlertCommonCluster, + AlertCommonPerClusterMessage, +} from '../../alerts/types'; +import { AlertCommonPerClusterMessageTokenType } from '../../alerts/enums'; const RESOLVED_SUBJECT = i18n.translate( 'xpack.monitoring.alerts.licenseExpiration.resolvedSubject', @@ -21,7 +27,7 @@ const NEW_SUBJECT = i18n.translate('xpack.monitoring.alerts.licenseExpiration.ne export function executeActions( instance: AlertInstance, - license: AlertLicense, + cluster: AlertCommonCluster, $expiry: Moment, dateFormat: string, emailAddress: string, @@ -31,14 +37,14 @@ export function executeActions( instance.scheduleActions('default', { subject: RESOLVED_SUBJECT, message: `This cluster alert has been resolved: Cluster '${ - license.clusterName + cluster.clusterName }' license was going to expire on ${$expiry.format(dateFormat)}.`, to: emailAddress, }); } else { instance.scheduleActions('default', { subject: NEW_SUBJECT, - message: `Cluster '${license.clusterName}' license is going to expire on ${$expiry.format( + message: `Cluster '${cluster.clusterName}' license is going to expire on ${$expiry.format( dateFormat )}. Please update your license.`, to: emailAddress, @@ -46,13 +52,43 @@ export function executeActions( } } -export function getUiMessage(license: AlertLicense, timezone: string, resolved: boolean = false) { +export function getUiMessage(resolved: boolean = false): AlertCommonPerClusterMessage { if (resolved) { - return i18n.translate('xpack.monitoring.alerts.licenseExpiration.ui.resolvedMessage', { - defaultMessage: `This cluster's license is active.`, - }); + return { + text: i18n.translate('xpack.monitoring.alerts.licenseExpiration.ui.resolvedMessage', { + defaultMessage: `This cluster's license is active.`, + }), + }; } - return i18n.translate('xpack.monitoring.alerts.licenseExpiration.ui.firingMessage', { - defaultMessage: `This cluster's license is going to expire in #relative at #absolute.`, + const linkText = i18n.translate('xpack.monitoring.alerts.licenseExpiration.linkText', { + defaultMessage: 'Please update your license', }); + return { + text: i18n.translate('xpack.monitoring.alerts.licenseExpiration.ui.firingMessage', { + defaultMessage: `This cluster's license is going to expire in #relative at #absolute. #start_link{linkText}#end_link`, + values: { + linkText, + }, + }), + tokens: [ + { + startToken: '#relative', + type: AlertCommonPerClusterMessageTokenType.Time, + isRelative: true, + isAbsolute: false, + } as AlertCommonPerClusterMessageTimeToken, + { + startToken: '#absolute', + type: AlertCommonPerClusterMessageTokenType.Time, + isAbsolute: true, + isRelative: false, + } as AlertCommonPerClusterMessageTimeToken, + { + startToken: '#start_link', + endToken: '#end_link', + type: AlertCommonPerClusterMessageTokenType.Link, + url: 'license', + } as AlertCommonPerClusterMessageLinkToken, + ], + }; } diff --git a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js index c5091c36c3bbe..1bddede52207b 100644 --- a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js +++ b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js @@ -29,6 +29,7 @@ import { CODE_PATH_BEATS, CODE_PATH_APM, KIBANA_ALERTING_ENABLED, + ALERT_TYPES, } from '../../../common/constants'; import { getApmsForClusters } from '../apm/get_apms_for_clusters'; import { i18n } from '@kbn/i18n'; @@ -102,15 +103,8 @@ export async function getClustersFromRequest( if (isInCodePath(codePaths, [CODE_PATH_ALERTS])) { if (KIBANA_ALERTING_ENABLED) { - const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); - const callCluster = (...args) => callWithRequest(req, ...args); - cluster.alerts = await fetchStatus( - callCluster, - start, - end, - cluster.cluster_uuid, - req.server - ); + const alertsClient = req.getAlertsClient ? req.getAlertsClient() : null; + cluster.alerts = await fetchStatus(alertsClient, ALERT_TYPES, start, end, req.logger); } else { cluster.alerts = await alertsClusterSearch( req, diff --git a/x-pack/plugins/monitoring/server/plugin.ts b/x-pack/plugins/monitoring/server/plugin.ts index 24d8bcaa4397c..784226dca66fe 100644 --- a/x-pack/plugins/monitoring/server/plugin.ts +++ b/x-pack/plugins/monitoring/server/plugin.ts @@ -47,6 +47,7 @@ import { PluginSetupContract as AlertingPluginSetupContract, } from '../../alerting/server'; import { getLicenseExpiration } from './alerts/license_expiration'; +import { getClusterState } from './alerts/cluster_state'; import { InfraPluginSetup } from '../../infra/server'; export interface LegacyAPI { @@ -154,6 +155,17 @@ export class Plugin { config.ui.ccs.enabled ) ); + plugins.alerting.registerType( + getClusterState( + async () => { + const coreStart = (await core.getStartServices())[0]; + return coreStart.uiSettings; + }, + cluster, + this.getLogger, + config.ui.ccs.enabled + ) + ); } // Initialize telemetry diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/alerts/alerts.js b/x-pack/plugins/monitoring/server/routes/api/v1/alerts/alerts.js index 56922bd8e87e2..d5a43d32f600a 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/alerts/alerts.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/alerts/alerts.js @@ -8,8 +8,12 @@ import { schema } from '@kbn/config-schema'; import { isFunction } from 'lodash'; import { ALERT_TYPE_LICENSE_EXPIRATION, + ALERT_TYPE_CLUSTER_STATE, MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS, + ALERT_TYPES, } from '../../../../../common/constants'; +import { handleError } from '../../../../lib/errors'; +import { fetchStatus } from '../../../../lib/alerts/fetch_status'; async function createAlerts(req, alertsClient, { selectedEmailActionId }) { const createdAlerts = []; @@ -17,7 +21,21 @@ async function createAlerts(req, alertsClient, { selectedEmailActionId }) { // Create alerts const ALERT_TYPES = { [ALERT_TYPE_LICENSE_EXPIRATION]: { - schedule: { interval: '10s' }, + schedule: { interval: '1m' }, + actions: [ + { + group: 'default', + id: selectedEmailActionId, + params: { + subject: '{{context.subject}}', + message: `{{context.message}}`, + to: ['{{context.to}}'], + }, + }, + ], + }, + [ALERT_TYPE_CLUSTER_STATE]: { + schedule: { interval: '1m' }, actions: [ { group: 'default', @@ -86,4 +104,37 @@ export function createKibanaAlertsRoute(server) { return { alerts, emailResponse }; }, }); + + server.route({ + method: 'POST', + path: '/api/monitoring/v1/alert_status', + config: { + validate: { + payload: schema.object({ + timeRange: schema.object({ + min: schema.string(), + max: schema.string(), + }), + }), + }, + }, + async handler(req, headers) { + const alertsClient = isFunction(req.getAlertsClient) ? req.getAlertsClient() : null; + if (!alertsClient) { + return headers.response().code(404); + } + + const start = req.payload.timeRange.min; + const end = req.payload.timeRange.max; + let alerts; + + try { + alerts = await fetchStatus(alertsClient, ALERT_TYPES, start, end, req.logger); + } catch (err) { + throw handleError(err, req); + } + + return { alerts }; + }, + }); } From 813d6cb796b42738fc24db43b0df2f4d337a06ed Mon Sep 17 00:00:00 2001 From: MadameSheema Date: Mon, 6 Apr 2020 21:42:43 +0200 Subject: [PATCH 13/36] [SIEM] View signal in default timeline (#62616) * adds test data * adds 'View a signal in timeline' test * implements test * fixes implementation * changes view signal for investigate signal --- .../integration/detections_timeline.spec.ts | 43 + .../plugins/siem/cypress/objects/timeline.ts | 10 + .../siem/cypress/screens/detections.ts | 6 + .../plugins/siem/cypress/screens/timeline.ts | 2 + .../plugins/siem/cypress/tasks/detections.ts | 14 + .../es_archives/timeline_signals/data.json.gz | Bin 0 -> 225608 bytes .../timeline_signals/mappings.json | 9063 +++++++++++++++++ 7 files changed, 9138 insertions(+) create mode 100644 x-pack/legacy/plugins/siem/cypress/integration/detections_timeline.spec.ts create mode 100644 x-pack/legacy/plugins/siem/cypress/objects/timeline.ts create mode 100644 x-pack/test/siem_cypress/es_archives/timeline_signals/data.json.gz create mode 100644 x-pack/test/siem_cypress/es_archives/timeline_signals/mappings.json diff --git a/x-pack/legacy/plugins/siem/cypress/integration/detections_timeline.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/detections_timeline.spec.ts new file mode 100644 index 0000000000000..2cac6e0f603b9 --- /dev/null +++ b/x-pack/legacy/plugins/siem/cypress/integration/detections_timeline.spec.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SIGNAL_ID } from '../screens/detections'; +import { PROVIDER_BADGE } from '../screens/timeline'; + +import { + expandFirstSignal, + investigateFirstSignalInTimeline, + waitForSignalsPanelToBeLoaded, +} from '../tasks/detections'; +import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; +import { loginAndWaitForPage } from '../tasks/login'; + +import { DETECTIONS } from '../urls/navigation'; + +describe('Detections timeline', () => { + beforeEach(() => { + esArchiverLoad('timeline_signals'); + loginAndWaitForPage(DETECTIONS); + }); + + afterEach(() => { + esArchiverUnload('timeline_signals'); + }); + + it('Investigate signal in default timeline', () => { + waitForSignalsPanelToBeLoaded(); + expandFirstSignal(); + cy.get(SIGNAL_ID) + .first() + .invoke('text') + .then(eventId => { + investigateFirstSignalInTimeline(); + cy.get(PROVIDER_BADGE) + .invoke('text') + .should('eql', `_id: "${eventId}"`); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/cypress/objects/timeline.ts b/x-pack/legacy/plugins/siem/cypress/objects/timeline.ts new file mode 100644 index 0000000000000..bca99bfa9266a --- /dev/null +++ b/x-pack/legacy/plugins/siem/cypress/objects/timeline.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +interface Timeline { + title: string; + query: string; +} diff --git a/x-pack/legacy/plugins/siem/cypress/screens/detections.ts b/x-pack/legacy/plugins/siem/cypress/screens/detections.ts index cb776be8d7b6b..d9ffa5b5a4ab2 100644 --- a/x-pack/legacy/plugins/siem/cypress/screens/detections.ts +++ b/x-pack/legacy/plugins/siem/cypress/screens/detections.ts @@ -6,6 +6,8 @@ export const CLOSED_SIGNALS_BTN = '[data-test-subj="closedSignals"]'; +export const EXPAND_SIGNAL_BTN = '[data-test-subj="expand-event"]'; + export const LOADING_SIGNALS_PANEL = '[data-test-subj="loading-signals-panel"]'; export const MANAGE_SIGNAL_DETECTION_RULES_BTN = '[data-test-subj="manage-signal-detection-rules"]'; @@ -20,8 +22,12 @@ export const OPENED_SIGNALS_BTN = '[data-test-subj="openSignals"]'; export const SELECTED_SIGNALS = '[data-test-subj="selectedSignals"]'; +export const SEND_SIGNAL_TO_TIMELINE_BTN = '[data-test-subj="send-signal-to-timeline-button"]'; + export const SHOWING_SIGNALS = '[data-test-subj="showingSignals"]'; export const SIGNALS = '[data-test-subj="event"]'; +export const SIGNAL_ID = '[data-test-subj="draggable-content-_id"]'; + export const SIGNAL_CHECKBOX = '[data-test-subj="select-event-container"] .euiCheckbox__input'; diff --git a/x-pack/legacy/plugins/siem/cypress/screens/timeline.ts b/x-pack/legacy/plugins/siem/cypress/screens/timeline.ts index fbce585a70f86..53d8273d9ce6b 100644 --- a/x-pack/legacy/plugins/siem/cypress/screens/timeline.ts +++ b/x-pack/legacy/plugins/siem/cypress/screens/timeline.ts @@ -14,6 +14,8 @@ export const ID_FIELD = '[data-test-subj="timeline"] [data-test-subj="field-name export const ID_TOGGLE_FIELD = '[data-test-subj="toggle-field-_id"]'; +export const PROVIDER_BADGE = '[data-test-subj="providerBadge"]'; + export const SEARCH_OR_FILTER_CONTAINER = '[data-test-subj="timeline-search-or-filter-search-container"]'; diff --git a/x-pack/legacy/plugins/siem/cypress/tasks/detections.ts b/x-pack/legacy/plugins/siem/cypress/tasks/detections.ts index abea4a887b8ba..c30a178eab489 100644 --- a/x-pack/legacy/plugins/siem/cypress/tasks/detections.ts +++ b/x-pack/legacy/plugins/siem/cypress/tasks/detections.ts @@ -6,11 +6,13 @@ import { CLOSED_SIGNALS_BTN, + EXPAND_SIGNAL_BTN, LOADING_SIGNALS_PANEL, MANAGE_SIGNAL_DETECTION_RULES_BTN, OPEN_CLOSE_SIGNAL_BTN, OPEN_CLOSE_SIGNALS_BTN, OPENED_SIGNALS_BTN, + SEND_SIGNAL_TO_TIMELINE_BTN, SIGNALS, SIGNAL_CHECKBOX, } from '../screens/detections'; @@ -26,6 +28,12 @@ export const closeSignals = () => { cy.get(OPEN_CLOSE_SIGNALS_BTN).click({ force: true }); }; +export const expandFirstSignal = () => { + cy.get(EXPAND_SIGNAL_BTN) + .first() + .click({ force: true }); +}; + export const goToClosedSignals = () => { cy.get(CLOSED_SIGNALS_BTN).click({ force: true }); }; @@ -58,6 +66,12 @@ export const selectNumberOfSignals = (numberOfSignals: number) => { } }; +export const investigateFirstSignalInTimeline = () => { + cy.get(SEND_SIGNAL_TO_TIMELINE_BTN) + .first() + .click({ force: true }); +}; + export const waitForSignals = () => { cy.get(REFRESH_BUTTON) .invoke('text') diff --git a/x-pack/test/siem_cypress/es_archives/timeline_signals/data.json.gz b/x-pack/test/siem_cypress/es_archives/timeline_signals/data.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..485d9868efd21af89ca7145386c7b95142f17205 GIT binary patch literal 225608 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�v;L$hnqumq#%Q&~bCD~dtqmHn?q_jJa+^^T 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%0AdcUA0@M3 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

b7 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

6HtE@>Q|5mvQH37=0N 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 RO?c=MA;jMto;NH2{{i5^BW3^q literal 0 HcmV?d00001 diff --git a/x-pack/test/siem_cypress/es_archives/timeline_signals/mappings.json b/x-pack/test/siem_cypress/es_archives/timeline_signals/mappings.json new file mode 100644 index 0000000000000..a1a9e7bfeae7f --- /dev/null +++ b/x-pack/test/siem_cypress/es_archives/timeline_signals/mappings.json @@ -0,0 +1,9063 @@ +{ + "type": "index", + "value": { + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "action": "c0c235fba02ebd2a2412bcda79009b58", + "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "alert": "e588043a01d3d43477e7cad7efa0f5d8", + "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", + "apm-services-telemetry": "07ee1939fa4302c62ddc052ec03fed90", + "canvas-element": "7390014e1091044523666d97247392fc", + "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "config": "87aca8fdb053154f11383fce3dbf3edf", + "dashboard": "d00f614b29a80360e1190193fd333bab", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", + "index-pattern": "66eccb05066c5a89924f48a9e9736499", + "infrastructure-ui-source": "ddc0ecb18383f6b26101a2fadb2dab0c", + "inventory-view": "84b320fd67209906333ffce261128462", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "lens": "21c3ea0763beb1ecb0162529706b88c5", + "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", + "map": "23d7aa4a720d4938ccde3983f87bd58d", + "maps-telemetry": "268da3a48066123fc5baf35abaa55014", + "metrics-explorer-view": "53c5365793677328df0ccb6138bf3cdd", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", + "namespace": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "181661168bbadd1eff5902361e2a0d5c", + "server": "ec97f1c5da1a19609a60874e5af1100c", + "siem-detection-engine-rule-status": "0367e4d775814b56a4bee29384f9aafe", + "siem-ui-timeline": "ac8020190f5950dd3250b6499144e7fb", + "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", + "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", + "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", + "telemetry": "358ffaa88ba34a97d55af0933a117de4", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "upgrade-assistant-reindex-operation": "a53a20fe086b72c9a86da3cc12dad8a6", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "url": "c7f66a0df8b1b52f17c28c4adb111105", + "visualization": "52d7a13ad68a150c4525b292d23e12cc" + } + }, + "dynamic": "strict", + "properties": { + "action": { + "properties": { + "actionTypeId": { + "type": "keyword" + }, + "config": { + "enabled": false, + "type": "object" + }, + "name": { + "type": "text" + }, + "secrets": { + "type": "binary" + } + } + }, + "action_task_params": { + "properties": { + "actionId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alert": { + "properties": { + "actions": { + "properties": { + "actionRef": { + "type": "keyword" + }, + "actionTypeId": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + }, + "type": "nested" + }, + "alertTypeId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "apiKeyOwner": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + }, + "createdBy": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "muteAll": { + "type": "boolean" + }, + "mutedInstanceIds": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "params": { + "enabled": false, + "type": "object" + }, + "schedule": { + "properties": { + "interval": { + "type": "keyword" + } + } + }, + "scheduledTaskId": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "throttle": { + "type": "keyword" + }, + "updatedBy": { + "type": "keyword" + } + } + }, + "apm-indices": { + "properties": { + "apm_oss": { + "properties": { + "errorIndices": { + "type": "keyword" + }, + "metricsIndices": { + "type": "keyword" + }, + "onboardingIndices": { + "type": "keyword" + }, + "sourcemapIndices": { + "type": "keyword" + }, + "spanIndices": { + "type": "keyword" + }, + "transactionIndices": { + "type": "keyword" + } + } + } + } + }, + "apm-services-telemetry": { + "properties": { + "has_any_services": { + "type": "boolean" + }, + "services_per_agent": { + "properties": { + "dotnet": { + "null_value": 0, + "type": "long" + }, + "go": { + "null_value": 0, + "type": "long" + }, + "java": { + "null_value": 0, + "type": "long" + }, + "js-base": { + "null_value": 0, + "type": "long" + }, + "nodejs": { + "null_value": 0, + "type": "long" + }, + "python": { + "null_value": 0, + "type": "long" + }, + "ruby": { + "null_value": 0, + "type": "long" + }, + "rum-js": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "canvas-element": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "content": { + "type": "text" + }, + "help": { + "type": "text" + }, + "image": { + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "config": { + "dynamic": "true", + "properties": { + "buildNum": { + "type": "keyword" + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "file-upload-telemetry": { + "properties": { + "filesUploadedTotalCount": { + "type": "long" + } + } + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" + } + } + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "typeMeta": { + "type": "keyword" + } + } + }, + "infrastructure-ui-source": { + "properties": { + "description": { + "type": "text" + }, + "fields": { + "properties": { + "container": { + "type": "keyword" + }, + "host": { + "type": "keyword" + }, + "pod": { + "type": "keyword" + }, + "tiebreaker": { + "type": "keyword" + }, + "timestamp": { + "type": "keyword" + } + } + }, + "logAlias": { + "type": "keyword" + }, + "logColumns": { + "properties": { + "fieldColumn": { + "properties": { + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + } + } + }, + "messageColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "timestampColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + } + }, + "type": "nested" + }, + "metricAlias": { + "type": "keyword" + }, + "name": { + "type": "text" + } + } + }, + "inventory-view": { + "properties": { + "autoBounds": { + "type": "boolean" + }, + "autoReload": { + "type": "boolean" + }, + "boundsOverride": { + "properties": { + "max": { + "type": "integer" + }, + "min": { + "type": "integer" + } + } + }, + "customOptions": { + "properties": { + "field": { + "type": "keyword" + }, + "text": { + "type": "keyword" + } + }, + "type": "nested" + }, + "filterQuery": { + "properties": { + "expression": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + } + } + }, + "groupBy": { + "properties": { + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "metric": { + "properties": { + "type": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "nodeType": { + "type": "keyword" + }, + "time": { + "type": "integer" + }, + "view": { + "type": "keyword" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "lens": { + "properties": { + "expression": { + "index": false, + "type": "keyword" + }, + "state": { + "type": "flattened" + }, + "title": { + "type": "text" + }, + "visualizationType": { + "type": "keyword" + } + } + }, + "lens-ui-telemetry": { + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "map": { + "properties": { + "bounds": { + "type": "geo_shape" + }, + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "maps-telemetry": { + "properties": { + "attributesPerMap": { + "properties": { + "dataSourcesCount": { + "properties": { + "avg": { + "type": "long" + }, + "max": { + "type": "long" + }, + "min": { + "type": "long" + } + } + }, + "emsVectorLayersCount": { + "dynamic": "true", + "type": "object" + }, + "layerTypesCount": { + "dynamic": "true", + "type": "object" + }, + "layersCount": { + "properties": { + "avg": { + "type": "long" + }, + "max": { + "type": "long" + }, + "min": { + "type": "long" + } + } + } + } + }, + "indexPatternsWithGeoFieldCount": { + "type": "long" + }, + "mapsTotalCount": { + "type": "long" + }, + "settings": { + "properties": { + "showMapVisualizationTypes": { + "type": "boolean" + } + } + }, + "timeCaptured": { + "type": "date" + } + } + }, + "metrics-explorer-view": { + "properties": { + "chartOptions": { + "properties": { + "stack": { + "type": "boolean" + }, + "type": { + "type": "keyword" + }, + "yAxisMode": { + "type": "keyword" + } + } + }, + "currentTimerange": { + "properties": { + "from": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "to": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "options": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "filterQuery": { + "type": "keyword" + }, + "groupBy": { + "type": "keyword" + }, + "limit": { + "type": "integer" + }, + "metrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "color": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + } + } + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "dashboard": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "index-pattern": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "search": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "space": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "visualization": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "ml-telemetry": { + "properties": { + "file_data_visualizer": { + "properties": { + "index_creation_count": { + "type": "long" + } + } + } + } + }, + "namespace": { + "type": "keyword" + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } + } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "sample-data-telemetry": { + "properties": { + "installCount": { + "type": "long" + }, + "unInstallCount": { + "type": "long" + } + } + }, + "search": { + "properties": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "server": { + "properties": { + "uuid": { + "type": "keyword" + } + } + }, + "siem-detection-engine-rule-status": { + "properties": { + "alertId": { + "type": "keyword" + }, + "lastFailureAt": { + "type": "date" + }, + "lastFailureMessage": { + "type": "text" + }, + "lastSuccessAt": { + "type": "date" + }, + "lastSuccessMessage": { + "type": "text" + }, + "status": { + "type": "keyword" + }, + "statusDate": { + "type": "date" + } + } + }, + "siem-ui-timeline": { + "properties": { + "columns": { + "properties": { + "aggregatable": { + "type": "boolean" + }, + "category": { + "type": "keyword" + }, + "columnHeaderType": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "example": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "indexes": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "placeholder": { + "type": "text" + }, + "searchable": { + "type": "boolean" + }, + "type": { + "type": "keyword" + } + } + }, + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "dataProviders": { + "properties": { + "and": { + "properties": { + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + } + } + }, + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + } + } + }, + "dateRange": { + "properties": { + "end": { + "type": "date" + }, + "start": { + "type": "date" + } + } + }, + "description": { + "type": "text" + }, + "eventType": { + "type": "keyword" + }, + "favorite": { + "properties": { + "favoriteDate": { + "type": "date" + }, + "fullName": { + "type": "text" + }, + "keySearch": { + "type": "text" + }, + "userName": { + "type": "text" + } + } + }, + "filters": { + "properties": { + "exists": { + "type": "text" + }, + "match_all": { + "type": "text" + }, + "meta": { + "properties": { + "alias": { + "type": "text" + }, + "controlledBy": { + "type": "text" + }, + "disabled": { + "type": "boolean" + }, + "field": { + "type": "text" + }, + "formattedValue": { + "type": "text" + }, + "index": { + "type": "keyword" + }, + "key": { + "type": "keyword" + }, + "negate": { + "type": "boolean" + }, + "params": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "value": { + "type": "text" + } + } + }, + "missing": { + "type": "text" + }, + "query": { + "type": "text" + }, + "range": { + "type": "text" + }, + "script": { + "type": "text" + } + } + }, + "kqlMode": { + "type": "keyword" + }, + "kqlQuery": { + "properties": { + "filterQuery": { + "properties": { + "kuery": { + "properties": { + "expression": { + "type": "text" + }, + "kind": { + "type": "keyword" + } + } + }, + "serializedQuery": { + "type": "text" + } + } + } + } + }, + "savedQueryId": { + "type": "keyword" + }, + "sort": { + "properties": { + "columnId": { + "type": "keyword" + }, + "sortDirection": { + "type": "keyword" + } + } + }, + "title": { + "type": "text" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-note": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "note": { + "type": "text" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-pinned-event": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "imageUrl": { + "index": false, + "type": "text" + }, + "initials": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "telemetry": { + "properties": { + "enabled": { + "type": "boolean" + }, + "lastReported": { + "type": "date" + }, + "lastVersionChecked": { + "ignore_above": 256, + "type": "keyword" + }, + "sendUsageFrom": { + "ignore_above": 256, + "type": "keyword" + }, + "userHasSeenNotice": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "tsvb-validation-telemetry": { + "properties": { + "failedRequests": { + "type": "long" + } + } + }, + "type": { + "type": "keyword" + }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "updated_at": { + "type": "date" + }, + "upgrade-assistant-reindex-operation": { + "dynamic": "true", + "properties": { + "indexName": { + "type": "keyword" + }, + "status": { + "type": "integer" + } + } + }, + "upgrade-assistant-telemetry": { + "properties": { + "features": { + "properties": { + "deprecation_logging": { + "properties": { + "enabled": { + "null_value": true, + "type": "boolean" + } + } + } + } + }, + "ui_open": { + "properties": { + "cluster": { + "null_value": 0, + "type": "long" + }, + "indices": { + "null_value": 0, + "type": "long" + }, + "overview": { + "null_value": 0, + "type": "long" + } + } + }, + "ui_reindex": { + "properties": { + "close": { + "null_value": 0, + "type": "long" + }, + "open": { + "null_value": 0, + "type": "long" + }, + "start": { + "null_value": 0, + "type": "long" + }, + "stop": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchRefName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + ".siem-signals-default": { + "is_write_index": true + } + }, + "index": ".siem-signals-default-000001", + "mappings": { + "dynamic": "false", + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "client": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "cloud": { + "properties": { + "account": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "availability_zone": { + "ignore_above": 1024, + "type": "keyword" + }, + "instance": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "machine": { + "properties": { + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "region": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "container": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "tag": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "type": "object" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "runtime": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "destination": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "dns": { + "properties": { + "answers": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "data": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "ttl": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "header_flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "op_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "question": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "resolved_ip": { + "type": "ip" + }, + "response_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "message": { + "norms": false, + "type": "text" + }, + "stack_trace": { + "doc_values": false, + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "doc_values": false, + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_norm": { + "type": "float" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "start": { + "type": "date" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "file": { + "properties": { + "accessed": { + "type": "date" + }, + "attributes": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "ctime": { + "type": "date" + }, + "device": { + "ignore_above": 1024, + "type": "keyword" + }, + "directory": { + "ignore_above": 1024, + "type": "keyword" + }, + "drive_letter": { + "ignore_above": 1, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mtime": { + "type": "date" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "target_path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "http": { + "properties": { + "request": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "method": { + "ignore_above": 1024, + "type": "keyword" + }, + "referrer": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "response": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "status_code": { + "type": "long" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "type": "object" + }, + "log": { + "properties": { + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "logger": { + "ignore_above": 1024, + "type": "keyword" + }, + "origin": { + "properties": { + "file": { + "properties": { + "line": { + "type": "integer" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "function": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "original": { + "doc_values": false, + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "syslog": { + "properties": { + "facility": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "priority": { + "type": "long" + }, + "severity": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "message": { + "norms": false, + "type": "text" + }, + "network": { + "properties": { + "application": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "community_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "direction": { + "ignore_above": 1024, + "type": "keyword" + }, + "forwarded_ip": { + "type": "ip" + }, + "iana_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "transport": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "observer": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "vendor": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "organization": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "package": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "build_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "checksum": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "install_scope": { + "ignore_above": 1024, + "type": "keyword" + }, + "installed": { + "type": "date" + }, + "license": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "process": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "parent": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "registry": { + "properties": { + "data": { + "properties": { + "bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "strings": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hive": { + "ignore_above": 1024, + "type": "keyword" + }, + "key": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "value": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "related": { + "properties": { + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "rule": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "ruleset": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "server": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "service": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "signal": { + "properties": { + "ancestors": { + "properties": { + "depth": { + "type": "long" + }, + "id": { + "type": "keyword" + }, + "rule": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "original_event": { + "properties": { + "action": { + "type": "keyword" + }, + "category": { + "type": "keyword" + }, + "code": { + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + }, + "hash": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + }, + "module": { + "type": "keyword" + }, + "original": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "outcome": { + "type": "keyword" + }, + "provider": { + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_norm": { + "type": "float" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "start": { + "type": "date" + }, + "timezone": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "original_time": { + "type": "date" + }, + "parent": { + "properties": { + "depth": { + "type": "long" + }, + "id": { + "type": "keyword" + }, + "index": { + "type": "keyword" + }, + "rule": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "rule": { + "properties": { + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "enabled": { + "type": "keyword" + }, + "false_positives": { + "type": "keyword" + }, + "filters": { + "type": "object" + }, + "from": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "immutable": { + "type": "keyword" + }, + "index": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "language": { + "type": "keyword" + }, + "max_signals": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "output_index": { + "type": "keyword" + }, + "query": { + "type": "keyword" + }, + "references": { + "type": "keyword" + }, + "risk_score": { + "type": "keyword" + }, + "rule_id": { + "type": "keyword" + }, + "saved_id": { + "type": "keyword" + }, + "severity": { + "type": "keyword" + }, + "size": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "threat": { + "properties": { + "framework": { + "type": "keyword" + }, + "tactic": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "reference": { + "type": "keyword" + } + } + }, + "technique": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "reference": { + "type": "keyword" + } + } + } + } + }, + "timeline_id": { + "type": "keyword" + }, + "timeline_title": { + "type": "keyword" + }, + "to": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "status": { + "type": "keyword" + } + } + }, + "source": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + }, + "threat": { + "properties": { + "framework": { + "ignore_above": 1024, + "type": "keyword" + }, + "tactic": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "technique": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "tls": { + "properties": { + "cipher": { + "ignore_above": 1024, + "type": "keyword" + }, + "client": { + "properties": { + "certificate": { + "ignore_above": 1024, + "type": "keyword" + }, + "certificate_chain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "ja3": { + "ignore_above": 1024, + "type": "keyword" + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "server_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "ignore_above": 1024, + "type": "keyword" + }, + "supported_ciphers": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "established": { + "type": "boolean" + }, + "next_protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "resumed": { + "type": "boolean" + }, + "server": { + "properties": { + "certificate": { + "ignore_above": 1024, + "type": "keyword" + }, + "certificate_chain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "ja3s": { + "ignore_above": 1024, + "type": "keyword" + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "subject": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + }, + "version_protocol": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "trace": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "transaction": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "url": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "fragment": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "password": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "scheme": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "username": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user_agent": { + "properties": { + "device": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vulnerability": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "classification": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "enumeration": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "report_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "scanner": { + "properties": { + "vendor": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "score": { + "properties": { + "base": { + "type": "float" + }, + "environmental": { + "type": "float" + }, + "temporal": { + "type": "float" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "severity": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "lifecycle": { + "name": ".siem-signals-default", + "rollover_alias": ".siem-signals-default" + }, + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + "auditbeat-7.6.2": { + "is_write_index": true + } + }, + "index": "auditbeat-7.6.2-2020.03.20-000001", + "mappings": { + "_meta": { + "beat": "auditbeat", + "version": "7.6.2" + }, + "date_detection": false, + "dynamic_templates": [ + { + "labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "labels.*" + } + }, + { + "container.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "container.labels.*" + } + }, + { + "dns.answers": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "dns.answers.*" + } + }, + { + "log.syslog": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "log.syslog.*" + } + }, + { + "fields": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "fields.*" + } + }, + { + "docker.container.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "docker.container.labels.*" + } + }, + { + "kubernetes.labels.*": { + "mapping": { + "type": "keyword" + }, + "path_match": "kubernetes.labels.*" + } + }, + { + "kubernetes.annotations.*": { + "mapping": { + "type": "keyword" + }, + "path_match": "kubernetes.annotations.*" + } + }, + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "auditd": { + "properties": { + "data": { + "properties": { + "a0": { + "ignore_above": 1024, + "type": "keyword" + }, + "a1": { + "ignore_above": 1024, + "type": "keyword" + }, + "a2": { + "ignore_above": 1024, + "type": "keyword" + }, + "a3": { + "ignore_above": 1024, + "type": "keyword" + }, + "a[0-3]": { + "ignore_above": 1024, + "type": "keyword" + }, + "acct": { + "ignore_above": 1024, + "type": "keyword" + }, + "acl": { + "ignore_above": 1024, + "type": "keyword" + }, + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "added": { + "ignore_above": 1024, + "type": "keyword" + }, + "addr": { + "ignore_above": 1024, + "type": "keyword" + }, + "apparmor": { + "ignore_above": 1024, + "type": "keyword" + }, + "arch": { + "ignore_above": 1024, + "type": "keyword" + }, + "argc": { + "ignore_above": 1024, + "type": "keyword" + }, + "audit_backlog_limit": { + "ignore_above": 1024, + "type": "keyword" + }, + "audit_backlog_wait_time": { + "ignore_above": 1024, + "type": "keyword" + }, + "audit_enabled": { + "ignore_above": 1024, + "type": "keyword" + }, + "audit_failure": { + "ignore_above": 1024, + "type": "keyword" + }, + "banners": { + "ignore_above": 1024, + "type": "keyword" + }, + "bool": { + "ignore_above": 1024, + "type": "keyword" + }, + "bus": { + "ignore_above": 1024, + "type": "keyword" + }, + "cap_fe": { + "ignore_above": 1024, + "type": "keyword" + }, + "cap_fi": { + "ignore_above": 1024, + "type": "keyword" + }, + "cap_fp": { + "ignore_above": 1024, + "type": "keyword" + }, + "cap_fver": { + "ignore_above": 1024, + "type": "keyword" + }, + "cap_pe": { + "ignore_above": 1024, + "type": "keyword" + }, + "cap_pi": { + "ignore_above": 1024, + "type": "keyword" + }, + "cap_pp": { + "ignore_above": 1024, + "type": "keyword" + }, + "capability": { + "ignore_above": 1024, + "type": "keyword" + }, + "cgroup": { + "ignore_above": 1024, + "type": "keyword" + }, + "changed": { + "ignore_above": 1024, + "type": "keyword" + }, + "cipher": { + "ignore_above": 1024, + "type": "keyword" + }, + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "cmd": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "compat": { + "ignore_above": 1024, + "type": "keyword" + }, + "daddr": { + "ignore_above": 1024, + "type": "keyword" + }, + "data": { + "ignore_above": 1024, + "type": "keyword" + }, + "default-context": { + "ignore_above": 1024, + "type": "keyword" + }, + "device": { + "ignore_above": 1024, + "type": "keyword" + }, + "dir": { + "ignore_above": 1024, + "type": "keyword" + }, + "direction": { + "ignore_above": 1024, + "type": "keyword" + }, + "dmac": { + "ignore_above": 1024, + "type": "keyword" + }, + "dport": { + "ignore_above": 1024, + "type": "keyword" + }, + "enforcing": { + "ignore_above": 1024, + "type": "keyword" + }, + "entries": { + "ignore_above": 1024, + "type": "keyword" + }, + "exit": { + "ignore_above": 1024, + "type": "keyword" + }, + "fam": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "fd": { + "ignore_above": 1024, + "type": "keyword" + }, + "fe": { + "ignore_above": 1024, + "type": "keyword" + }, + "feature": { + "ignore_above": 1024, + "type": "keyword" + }, + "fi": { + "ignore_above": 1024, + "type": "keyword" + }, + "file": { + "ignore_above": 1024, + "type": "keyword" + }, + "flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "format": { + "ignore_above": 1024, + "type": "keyword" + }, + "fp": { + "ignore_above": 1024, + "type": "keyword" + }, + "fver": { + "ignore_above": 1024, + "type": "keyword" + }, + "grantors": { + "ignore_above": 1024, + "type": "keyword" + }, + "grp": { + "ignore_above": 1024, + "type": "keyword" + }, + "hook": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "icmp_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "igid": { + "ignore_above": 1024, + "type": "keyword" + }, + "img-ctx": { + "ignore_above": 1024, + "type": "keyword" + }, + "inif": { + "ignore_above": 1024, + "type": "keyword" + }, + "ino": { + "ignore_above": 1024, + "type": "keyword" + }, + "inode_gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "inode_uid": { + "ignore_above": 1024, + "type": "keyword" + }, + "invalid_context": { + "ignore_above": 1024, + "type": "keyword" + }, + "ioctlcmd": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "ignore_above": 1024, + "type": "keyword" + }, + "ipid": { + "ignore_above": 1024, + "type": "keyword" + }, + "ipx-net": { + "ignore_above": 1024, + "type": "keyword" + }, + "items": { + "ignore_above": 1024, + "type": "keyword" + }, + "iuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "ksize": { + "ignore_above": 1024, + "type": "keyword" + }, + "laddr": { + "ignore_above": 1024, + "type": "keyword" + }, + "len": { + "ignore_above": 1024, + "type": "keyword" + }, + "list": { + "ignore_above": 1024, + "type": "keyword" + }, + "lport": { + "ignore_above": 1024, + "type": "keyword" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "macproto": { + "ignore_above": 1024, + "type": "keyword" + }, + "maj": { + "ignore_above": 1024, + "type": "keyword" + }, + "major": { + "ignore_above": 1024, + "type": "keyword" + }, + "minor": { + "ignore_above": 1024, + "type": "keyword" + }, + "model": { + "ignore_above": 1024, + "type": "keyword" + }, + "msg": { + "ignore_above": 1024, + "type": "keyword" + }, + "nargs": { + "ignore_above": 1024, + "type": "keyword" + }, + "net": { + "ignore_above": 1024, + "type": "keyword" + }, + "new": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-chardev": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-disk": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-enabled": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-fs": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-level": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-log_passwd": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-mem": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-net": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-range": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-rng": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-role": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-seuser": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-vcpu": { + "ignore_above": 1024, + "type": "keyword" + }, + "new_gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "new_lock": { + "ignore_above": 1024, + "type": "keyword" + }, + "new_pe": { + "ignore_above": 1024, + "type": "keyword" + }, + "new_pi": { + "ignore_above": 1024, + "type": "keyword" + }, + "new_pp": { + "ignore_above": 1024, + "type": "keyword" + }, + "nlnk-fam": { + "ignore_above": 1024, + "type": "keyword" + }, + "nlnk-grp": { + "ignore_above": 1024, + "type": "keyword" + }, + "nlnk-pid": { + "ignore_above": 1024, + "type": "keyword" + }, + "oauid": { + "ignore_above": 1024, + "type": "keyword" + }, + "obj": { + "ignore_above": 1024, + "type": "keyword" + }, + "obj_gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "obj_uid": { + "ignore_above": 1024, + "type": "keyword" + }, + "ocomm": { + "ignore_above": 1024, + "type": "keyword" + }, + "oflag": { + "ignore_above": 1024, + "type": "keyword" + }, + "old": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-auid": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-chardev": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-disk": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-enabled": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-fs": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-level": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-log_passwd": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-mem": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-net": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-range": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-rng": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-role": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-ses": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-seuser": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-vcpu": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_enforcing": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_lock": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_pe": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_pi": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_pp": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_prom": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_val": { + "ignore_above": 1024, + "type": "keyword" + }, + "op": { + "ignore_above": 1024, + "type": "keyword" + }, + "opid": { + "ignore_above": 1024, + "type": "keyword" + }, + "oses": { + "ignore_above": 1024, + "type": "keyword" + }, + "outif": { + "ignore_above": 1024, + "type": "keyword" + }, + "parent": { + "ignore_above": 1024, + "type": "keyword" + }, + "per": { + "ignore_above": 1024, + "type": "keyword" + }, + "perm": { + "ignore_above": 1024, + "type": "keyword" + }, + "perm_mask": { + "ignore_above": 1024, + "type": "keyword" + }, + "permissive": { + "ignore_above": 1024, + "type": "keyword" + }, + "pfs": { + "ignore_above": 1024, + "type": "keyword" + }, + "printer": { + "ignore_above": 1024, + "type": "keyword" + }, + "prom": { + "ignore_above": 1024, + "type": "keyword" + }, + "proto": { + "ignore_above": 1024, + "type": "keyword" + }, + "qbytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "range": { + "ignore_above": 1024, + "type": "keyword" + }, + "reason": { + "ignore_above": 1024, + "type": "keyword" + }, + "removed": { + "ignore_above": 1024, + "type": "keyword" + }, + "res": { + "ignore_above": 1024, + "type": "keyword" + }, + "resrc": { + "ignore_above": 1024, + "type": "keyword" + }, + "rport": { + "ignore_above": 1024, + "type": "keyword" + }, + "sauid": { + "ignore_above": 1024, + "type": "keyword" + }, + "scontext": { + "ignore_above": 1024, + "type": "keyword" + }, + "selected-context": { + "ignore_above": 1024, + "type": "keyword" + }, + "seperm": { + "ignore_above": 1024, + "type": "keyword" + }, + "seperms": { + "ignore_above": 1024, + "type": "keyword" + }, + "seqno": { + "ignore_above": 1024, + "type": "keyword" + }, + "seresult": { + "ignore_above": 1024, + "type": "keyword" + }, + "ses": { + "ignore_above": 1024, + "type": "keyword" + }, + "seuser": { + "ignore_above": 1024, + "type": "keyword" + }, + "sig": { + "ignore_above": 1024, + "type": "keyword" + }, + "sigev_signo": { + "ignore_above": 1024, + "type": "keyword" + }, + "smac": { + "ignore_above": 1024, + "type": "keyword" + }, + "socket": { + "properties": { + "addr": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "ignore_above": 1024, + "type": "keyword" + }, + "saddr": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "spid": { + "ignore_above": 1024, + "type": "keyword" + }, + "sport": { + "ignore_above": 1024, + "type": "keyword" + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "subj": { + "ignore_above": 1024, + "type": "keyword" + }, + "success": { + "ignore_above": 1024, + "type": "keyword" + }, + "syscall": { + "ignore_above": 1024, + "type": "keyword" + }, + "table": { + "ignore_above": 1024, + "type": "keyword" + }, + "tclass": { + "ignore_above": 1024, + "type": "keyword" + }, + "tcontext": { + "ignore_above": 1024, + "type": "keyword" + }, + "terminal": { + "ignore_above": 1024, + "type": "keyword" + }, + "tty": { + "ignore_above": 1024, + "type": "keyword" + }, + "unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "uri": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "val": { + "ignore_above": 1024, + "type": "keyword" + }, + "ver": { + "ignore_above": 1024, + "type": "keyword" + }, + "virt": { + "ignore_above": 1024, + "type": "keyword" + }, + "vm": { + "ignore_above": 1024, + "type": "keyword" + }, + "vm-ctx": { + "ignore_above": 1024, + "type": "keyword" + }, + "vm-pid": { + "ignore_above": 1024, + "type": "keyword" + }, + "watch": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "message_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "paths": { + "properties": { + "dev": { + "ignore_above": 1024, + "type": "keyword" + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "item": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "nametype": { + "ignore_above": 1024, + "type": "keyword" + }, + "obj_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "obj_level": { + "ignore_above": 1024, + "type": "keyword" + }, + "obj_role": { + "ignore_above": 1024, + "type": "keyword" + }, + "obj_user": { + "ignore_above": 1024, + "type": "keyword" + }, + "objtype": { + "ignore_above": 1024, + "type": "keyword" + }, + "ogid": { + "ignore_above": 1024, + "type": "keyword" + }, + "ouid": { + "ignore_above": 1024, + "type": "keyword" + }, + "rdev": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "result": { + "ignore_above": 1024, + "type": "keyword" + }, + "sequence": { + "type": "long" + }, + "session": { + "ignore_above": 1024, + "type": "keyword" + }, + "summary": { + "properties": { + "actor": { + "properties": { + "primary": { + "ignore_above": 1024, + "type": "keyword" + }, + "secondary": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "how": { + "ignore_above": 1024, + "type": "keyword" + }, + "object": { + "properties": { + "primary": { + "ignore_above": 1024, + "type": "keyword" + }, + "secondary": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "client": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "cloud": { + "properties": { + "account": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "availability_zone": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "instance": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "machine": { + "properties": { + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "project": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "region": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "container": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "tag": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "type": "object" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "runtime": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "destination": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "dns": { + "properties": { + "answers": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "data": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "ttl": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "header_flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "op_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "question": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "resolved_ip": { + "type": "ip" + }, + "response_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "docker": { + "properties": { + "container": { + "properties": { + "labels": { + "type": "object" + } + } + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "message": { + "norms": false, + "type": "text" + }, + "stack_trace": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "origin": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "ignore_above": 1024, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_norm": { + "type": "float" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "start": { + "type": "date" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "fields": { + "type": "object" + }, + "file": { + "properties": { + "accessed": { + "type": "date" + }, + "attributes": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "ctime": { + "type": "date" + }, + "device": { + "ignore_above": 1024, + "type": "keyword" + }, + "directory": { + "ignore_above": 1024, + "type": "keyword" + }, + "drive_letter": { + "ignore_above": 1, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mtime": { + "type": "date" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "origin": { + "fields": { + "raw": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "selinux": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "role": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "setgid": { + "type": "boolean" + }, + "setuid": { + "type": "boolean" + }, + "size": { + "type": "long" + }, + "target_path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "geoip": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "properties": { + "blake2b_256": { + "ignore_above": 1024, + "type": "keyword" + }, + "blake2b_384": { + "ignore_above": 1024, + "type": "keyword" + }, + "blake2b_512": { + "ignore_above": 1024, + "type": "keyword" + }, + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha224": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha384": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_224": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_384": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_512": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512_224": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512_256": { + "ignore_above": 1024, + "type": "keyword" + }, + "xxh64": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "containerized": { + "type": "boolean" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "build": { + "ignore_above": 1024, + "type": "keyword" + }, + "codename": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "http": { + "properties": { + "request": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "method": { + "ignore_above": 1024, + "type": "keyword" + }, + "referrer": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "response": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "status_code": { + "type": "long" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "jolokia": { + "properties": { + "agent": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "secured": { + "type": "boolean" + }, + "server": { + "properties": { + "product": { + "ignore_above": 1024, + "type": "keyword" + }, + "vendor": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "url": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "kubernetes": { + "properties": { + "annotations": { + "properties": { + "*": { + "type": "object" + } + } + }, + "container": { + "properties": { + "image": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "deployment": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "properties": { + "*": { + "type": "object" + } + } + }, + "namespace": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pod": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "replicaset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "statefulset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "labels": { + "type": "object" + }, + "log": { + "properties": { + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "logger": { + "ignore_above": 1024, + "type": "keyword" + }, + "origin": { + "properties": { + "file": { + "properties": { + "line": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "function": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "original": { + "ignore_above": 1024, + "type": "keyword" + }, + "syslog": { + "properties": { + "facility": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "priority": { + "type": "long" + }, + "severity": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "message": { + "norms": false, + "type": "text" + }, + "network": { + "properties": { + "application": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "community_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "direction": { + "ignore_above": 1024, + "type": "keyword" + }, + "forwarded_ip": { + "type": "ip" + }, + "iana_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "transport": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "observer": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "vendor": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "organization": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "package": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "build_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "checksum": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "install_scope": { + "ignore_above": 1024, + "type": "keyword" + }, + "installed": { + "type": "date" + }, + "license": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "process": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "hash": { + "properties": { + "blake2b_256": { + "ignore_above": 1024, + "type": "keyword" + }, + "blake2b_384": { + "ignore_above": 1024, + "type": "keyword" + }, + "blake2b_512": { + "ignore_above": 1024, + "type": "keyword" + }, + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha224": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha384": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_224": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_384": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_512": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512_224": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512_256": { + "ignore_above": 1024, + "type": "keyword" + }, + "xxh64": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "parent": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "registry": { + "properties": { + "data": { + "properties": { + "bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "strings": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hive": { + "ignore_above": 1024, + "type": "keyword" + }, + "key": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "value": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "related": { + "properties": { + "ip": { + "type": "ip" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "rule": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "ruleset": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "server": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "service": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "socket": { + "properties": { + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "system": { + "properties": { + "audit": { + "properties": { + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "boottime": { + "type": "date" + }, + "containerized": { + "type": "boolean" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "codename": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "timezone": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "offset": { + "properties": { + "sec": { + "type": "long" + } + } + } + } + }, + "uptime": { + "type": "long" + } + } + }, + "package": { + "properties": { + "arch": { + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "installtime": { + "type": "date" + }, + "license": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "release": { + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "summary": { + "ignore_above": 1024, + "type": "keyword" + }, + "url": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user": { + "properties": { + "dir": { + "ignore_above": 1024, + "type": "keyword" + }, + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "type": "object" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "password": { + "properties": { + "last_changed": { + "type": "date" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "shell": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + }, + "user_information": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + }, + "threat": { + "properties": { + "framework": { + "ignore_above": 1024, + "type": "keyword" + }, + "tactic": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "technique": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "timeseries": { + "properties": { + "instance": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "tls": { + "properties": { + "cipher": { + "ignore_above": 1024, + "type": "keyword" + }, + "client": { + "properties": { + "certificate": { + "ignore_above": 1024, + "type": "keyword" + }, + "certificate_chain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "ja3": { + "ignore_above": 1024, + "type": "keyword" + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "server_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "ignore_above": 1024, + "type": "keyword" + }, + "supported_ciphers": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "established": { + "type": "boolean" + }, + "next_protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "resumed": { + "type": "boolean" + }, + "server": { + "properties": { + "certificate": { + "ignore_above": 1024, + "type": "keyword" + }, + "certificate_chain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "ja3s": { + "ignore_above": 1024, + "type": "keyword" + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "subject": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + }, + "version_protocol": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "tracing": { + "properties": { + "trace": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "transaction": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "url": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "fragment": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "password": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "scheme": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "username": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user": { + "properties": { + "audit": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "effective": { + "properties": { + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "filesystem": { + "properties": { + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "name_map": { + "type": "object" + }, + "saved": { + "properties": { + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "selinux": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "role": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "terminal": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user_agent": { + "properties": { + "device": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vulnerability": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "classification": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "enumeration": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "report_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "scanner": { + "properties": { + "vendor": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "score": { + "properties": { + "base": { + "type": "float" + }, + "environmental": { + "type": "float" + }, + "temporal": { + "type": "float" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "severity": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "lifecycle": { + "name": "auditbeat", + "rollover_alias": "auditbeat-7.6.2" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "number_of_replicas": "1", + "number_of_shards": "1", + "query": { + "default_field": [ + "message", + "tags", + "agent.ephemeral_id", + "agent.id", + "agent.name", + "agent.type", + "agent.version", + "as.organization.name", + "client.address", + "client.as.organization.name", + "client.domain", + "client.geo.city_name", + "client.geo.continent_name", + "client.geo.country_iso_code", + "client.geo.country_name", + "client.geo.name", + "client.geo.region_iso_code", + "client.geo.region_name", + "client.mac", + "client.registered_domain", + "client.top_level_domain", + "client.user.domain", + "client.user.email", + "client.user.full_name", + "client.user.group.domain", + "client.user.group.id", + "client.user.group.name", + "client.user.hash", + "client.user.id", + "client.user.name", + "cloud.account.id", + "cloud.availability_zone", + "cloud.instance.id", + "cloud.instance.name", + "cloud.machine.type", + "cloud.provider", + "cloud.region", + "container.id", + "container.image.name", + "container.image.tag", + "container.name", + "container.runtime", + "destination.address", + "destination.as.organization.name", + "destination.domain", + "destination.geo.city_name", + "destination.geo.continent_name", + "destination.geo.country_iso_code", + "destination.geo.country_name", + "destination.geo.name", + "destination.geo.region_iso_code", + "destination.geo.region_name", + "destination.mac", + "destination.registered_domain", + "destination.top_level_domain", + "destination.user.domain", + "destination.user.email", + "destination.user.full_name", + "destination.user.group.domain", + "destination.user.group.id", + "destination.user.group.name", + "destination.user.hash", + "destination.user.id", + "destination.user.name", + "dns.answers.class", + "dns.answers.data", + "dns.answers.name", + "dns.answers.type", + "dns.header_flags", + "dns.id", + "dns.op_code", + "dns.question.class", + "dns.question.name", + "dns.question.registered_domain", + "dns.question.subdomain", + "dns.question.top_level_domain", + "dns.question.type", + "dns.response_code", + "dns.type", + "ecs.version", + "error.code", + "error.id", + "error.message", + "error.stack_trace", + "error.type", + "event.action", + "event.category", + "event.code", + "event.dataset", + "event.hash", + "event.id", + "event.kind", + "event.module", + "event.original", + "event.outcome", + "event.provider", + "event.timezone", + "event.type", + "file.device", + "file.directory", + "file.extension", + "file.gid", + "file.group", + "file.hash.md5", + "file.hash.sha1", + "file.hash.sha256", + "file.hash.sha512", + "file.inode", + "file.mode", + "file.name", + "file.owner", + "file.path", + "file.target_path", + "file.type", + "file.uid", + "geo.city_name", + "geo.continent_name", + "geo.country_iso_code", + "geo.country_name", + "geo.name", + "geo.region_iso_code", + "geo.region_name", + "group.domain", + "group.id", + "group.name", + "hash.md5", + "hash.sha1", + "hash.sha256", + "hash.sha512", + "host.architecture", + "host.geo.city_name", + "host.geo.continent_name", + "host.geo.country_iso_code", + "host.geo.country_name", + "host.geo.name", + "host.geo.region_iso_code", + "host.geo.region_name", + "host.hostname", + "host.id", + "host.mac", + "host.name", + "host.os.family", + "host.os.full", + "host.os.kernel", + "host.os.name", + "host.os.platform", + "host.os.version", + "host.type", + "host.user.domain", + "host.user.email", + "host.user.full_name", + "host.user.group.domain", + "host.user.group.id", + "host.user.group.name", + "host.user.hash", + "host.user.id", + "host.user.name", + "http.request.body.content", + "http.request.method", + "http.request.referrer", + "http.response.body.content", + "http.version", + "log.level", + "log.logger", + "log.origin.file.name", + "log.origin.function", + "log.original", + "log.syslog.facility.name", + "log.syslog.severity.name", + "network.application", + "network.community_id", + "network.direction", + "network.iana_number", + "network.name", + "network.protocol", + "network.transport", + "network.type", + "observer.geo.city_name", + "observer.geo.continent_name", + "observer.geo.country_iso_code", + "observer.geo.country_name", + "observer.geo.name", + "observer.geo.region_iso_code", + "observer.geo.region_name", + "observer.hostname", + "observer.mac", + "observer.name", + "observer.os.family", + "observer.os.full", + "observer.os.kernel", + "observer.os.name", + "observer.os.platform", + "observer.os.version", + "observer.product", + "observer.serial_number", + "observer.type", + "observer.vendor", + "observer.version", + "organization.id", + "organization.name", + "os.family", + "os.full", + "os.kernel", + "os.name", + "os.platform", + "os.version", + "package.architecture", + "package.checksum", + "package.description", + "package.install_scope", + "package.license", + "package.name", + "package.path", + "package.version", + "process.args", + "text", + "process.executable", + "process.hash.md5", + "process.hash.sha1", + "process.hash.sha256", + "process.hash.sha512", + "process.name", + "text", + "text", + "text", + "text", + "text", + "process.thread.name", + "process.title", + "process.working_directory", + "server.address", + "server.as.organization.name", + "server.domain", + "server.geo.city_name", + "server.geo.continent_name", + "server.geo.country_iso_code", + "server.geo.country_name", + "server.geo.name", + "server.geo.region_iso_code", + "server.geo.region_name", + "server.mac", + "server.registered_domain", + "server.top_level_domain", + "server.user.domain", + "server.user.email", + "server.user.full_name", + "server.user.group.domain", + "server.user.group.id", + "server.user.group.name", + "server.user.hash", + "server.user.id", + "server.user.name", + "service.ephemeral_id", + "service.id", + "service.name", + "service.node.name", + "service.state", + "service.type", + "service.version", + "source.address", + "source.as.organization.name", + "source.domain", + "source.geo.city_name", + "source.geo.continent_name", + "source.geo.country_iso_code", + "source.geo.country_name", + "source.geo.name", + "source.geo.region_iso_code", + "source.geo.region_name", + "source.mac", + "source.registered_domain", + "source.top_level_domain", + "source.user.domain", + "source.user.email", + "source.user.full_name", + "source.user.group.domain", + "source.user.group.id", + "source.user.group.name", + "source.user.hash", + "source.user.id", + "source.user.name", + "threat.framework", + "threat.tactic.id", + "threat.tactic.name", + "threat.tactic.reference", + "threat.technique.id", + "threat.technique.name", + "threat.technique.reference", + "tracing.trace.id", + "tracing.transaction.id", + "url.domain", + "url.extension", + "url.fragment", + "url.full", + "url.original", + "url.password", + "url.path", + "url.query", + "url.registered_domain", + "url.scheme", + "url.top_level_domain", + "url.username", + "user.domain", + "user.email", + "user.full_name", + "user.group.domain", + "user.group.id", + "user.group.name", + "user.hash", + "user.id", + "user.name", + "user_agent.device.name", + "user_agent.name", + "text", + "user_agent.original", + "user_agent.os.family", + "user_agent.os.full", + "user_agent.os.kernel", + "user_agent.os.name", + "user_agent.os.platform", + "user_agent.os.version", + "user_agent.version", + "text", + "agent.hostname", + "timeseries.instance", + "cloud.project.id", + "cloud.image.id", + "host.os.build", + "host.os.codename", + "kubernetes.pod.name", + "kubernetes.pod.uid", + "kubernetes.namespace", + "kubernetes.node.name", + "kubernetes.replicaset.name", + "kubernetes.deployment.name", + "kubernetes.statefulset.name", + "kubernetes.container.name", + "kubernetes.container.image", + "jolokia.agent.version", + "jolokia.agent.id", + "jolokia.server.product", + "jolokia.server.version", + "jolokia.server.vendor", + "jolokia.url", + "raw", + "file.origin", + "file.selinux.user", + "file.selinux.role", + "file.selinux.domain", + "file.selinux.level", + "user.audit.id", + "user.audit.name", + "user.effective.id", + "user.effective.name", + "user.effective.group.id", + "user.effective.group.name", + "user.filesystem.id", + "user.filesystem.name", + "user.filesystem.group.id", + "user.filesystem.group.name", + "user.saved.id", + "user.saved.name", + "user.saved.group.id", + "user.saved.group.name", + "user.selinux.user", + "user.selinux.role", + "user.selinux.domain", + "user.selinux.level", + "user.selinux.category", + "source.path", + "destination.path", + "auditd.message_type", + "auditd.session", + "auditd.result", + "auditd.summary.actor.primary", + "auditd.summary.actor.secondary", + "auditd.summary.object.type", + "auditd.summary.object.primary", + "auditd.summary.object.secondary", + "auditd.summary.how", + "auditd.paths.inode", + "auditd.paths.dev", + "auditd.paths.obj_user", + "auditd.paths.obj_role", + "auditd.paths.obj_domain", + "auditd.paths.obj_level", + "auditd.paths.objtype", + "auditd.paths.ouid", + "auditd.paths.rdev", + "auditd.paths.nametype", + "auditd.paths.ogid", + "auditd.paths.item", + "auditd.paths.mode", + "auditd.paths.name", + "auditd.data.action", + "auditd.data.minor", + "auditd.data.acct", + "auditd.data.addr", + "auditd.data.cipher", + "auditd.data.id", + "auditd.data.entries", + "auditd.data.kind", + "auditd.data.ksize", + "auditd.data.spid", + "auditd.data.arch", + "auditd.data.argc", + "auditd.data.major", + "auditd.data.unit", + "auditd.data.table", + "auditd.data.terminal", + "auditd.data.grantors", + "auditd.data.direction", + "auditd.data.op", + "auditd.data.tty", + "auditd.data.syscall", + "auditd.data.data", + "auditd.data.family", + "auditd.data.mac", + "auditd.data.pfs", + "auditd.data.items", + "auditd.data.a0", + "auditd.data.a1", + "auditd.data.a2", + "auditd.data.a3", + "auditd.data.hostname", + "auditd.data.lport", + "auditd.data.rport", + "auditd.data.exit", + "auditd.data.fp", + "auditd.data.laddr", + "auditd.data.sport", + "auditd.data.capability", + "auditd.data.nargs", + "auditd.data.new-enabled", + "auditd.data.audit_backlog_limit", + "auditd.data.dir", + "auditd.data.cap_pe", + "auditd.data.model", + "auditd.data.new_pp", + "auditd.data.old-enabled", + "auditd.data.oauid", + "auditd.data.old", + "auditd.data.banners", + "auditd.data.feature", + "auditd.data.vm-ctx", + "auditd.data.opid", + "auditd.data.seperms", + "auditd.data.seresult", + "auditd.data.new-rng", + "auditd.data.old-net", + "auditd.data.sigev_signo", + "auditd.data.ino", + "auditd.data.old_enforcing", + "auditd.data.old-vcpu", + "auditd.data.range", + "auditd.data.res", + "auditd.data.added", + "auditd.data.fam", + "auditd.data.nlnk-pid", + "auditd.data.subj", + "auditd.data.a[0-3]", + "auditd.data.cgroup", + "auditd.data.kernel", + "auditd.data.ocomm", + "auditd.data.new-net", + "auditd.data.permissive", + "auditd.data.class", + "auditd.data.compat", + "auditd.data.fi", + "auditd.data.changed", + "auditd.data.msg", + "auditd.data.dport", + "auditd.data.new-seuser", + "auditd.data.invalid_context", + "auditd.data.dmac", + "auditd.data.ipx-net", + "auditd.data.iuid", + "auditd.data.macproto", + "auditd.data.obj", + "auditd.data.ipid", + "auditd.data.new-fs", + "auditd.data.vm-pid", + "auditd.data.cap_pi", + "auditd.data.old-auid", + "auditd.data.oses", + "auditd.data.fd", + "auditd.data.igid", + "auditd.data.new-disk", + "auditd.data.parent", + "auditd.data.len", + "auditd.data.oflag", + "auditd.data.uuid", + "auditd.data.code", + "auditd.data.nlnk-grp", + "auditd.data.cap_fp", + "auditd.data.new-mem", + "auditd.data.seperm", + "auditd.data.enforcing", + "auditd.data.new-chardev", + "auditd.data.old-rng", + "auditd.data.outif", + "auditd.data.cmd", + "auditd.data.hook", + "auditd.data.new-level", + "auditd.data.sauid", + "auditd.data.sig", + "auditd.data.audit_backlog_wait_time", + "auditd.data.printer", + "auditd.data.old-mem", + "auditd.data.perm", + "auditd.data.old_pi", + "auditd.data.state", + "auditd.data.format", + "auditd.data.new_gid", + "auditd.data.tcontext", + "auditd.data.maj", + "auditd.data.watch", + "auditd.data.device", + "auditd.data.grp", + "auditd.data.bool", + "auditd.data.icmp_type", + "auditd.data.new_lock", + "auditd.data.old_prom", + "auditd.data.acl", + "auditd.data.ip", + "auditd.data.new_pi", + "auditd.data.default-context", + "auditd.data.inode_gid", + "auditd.data.new-log_passwd", + "auditd.data.new_pe", + "auditd.data.selected-context", + "auditd.data.cap_fver", + "auditd.data.file", + "auditd.data.net", + "auditd.data.virt", + "auditd.data.cap_pp", + "auditd.data.old-range", + "auditd.data.resrc", + "auditd.data.new-range", + "auditd.data.obj_gid", + "auditd.data.proto", + "auditd.data.old-disk", + "auditd.data.audit_failure", + "auditd.data.inif", + "auditd.data.vm", + "auditd.data.flags", + "auditd.data.nlnk-fam", + "auditd.data.old-fs", + "auditd.data.old-ses", + "auditd.data.seqno", + "auditd.data.fver", + "auditd.data.qbytes", + "auditd.data.seuser", + "auditd.data.cap_fe", + "auditd.data.new-vcpu", + "auditd.data.old-level", + "auditd.data.old_pp", + "auditd.data.daddr", + "auditd.data.old-role", + "auditd.data.ioctlcmd", + "auditd.data.smac", + "auditd.data.apparmor", + "auditd.data.fe", + "auditd.data.perm_mask", + "auditd.data.ses", + "auditd.data.cap_fi", + "auditd.data.obj_uid", + "auditd.data.reason", + "auditd.data.list", + "auditd.data.old_lock", + "auditd.data.bus", + "auditd.data.old_pe", + "auditd.data.new-role", + "auditd.data.prom", + "auditd.data.uri", + "auditd.data.audit_enabled", + "auditd.data.old-log_passwd", + "auditd.data.old-seuser", + "auditd.data.per", + "auditd.data.scontext", + "auditd.data.tclass", + "auditd.data.ver", + "auditd.data.new", + "auditd.data.val", + "auditd.data.img-ctx", + "auditd.data.old-chardev", + "auditd.data.old_val", + "auditd.data.success", + "auditd.data.inode_uid", + "auditd.data.removed", + "auditd.data.socket.port", + "auditd.data.socket.saddr", + "auditd.data.socket.addr", + "auditd.data.socket.family", + "auditd.data.socket.path", + "geoip.continent_name", + "geoip.city_name", + "geoip.region_name", + "geoip.country_iso_code", + "hash.blake2b_256", + "hash.blake2b_384", + "hash.blake2b_512", + "hash.md5", + "hash.sha1", + "hash.sha224", + "hash.sha256", + "hash.sha384", + "hash.sha3_224", + "hash.sha3_256", + "hash.sha3_384", + "hash.sha3_512", + "hash.sha512", + "hash.sha512_224", + "hash.sha512_256", + "hash.xxh64", + "event.origin", + "user.entity_id", + "user.terminal", + "process.entity_id", + "process.hash.blake2b_256", + "process.hash.blake2b_384", + "process.hash.blake2b_512", + "process.hash.sha224", + "process.hash.sha384", + "process.hash.sha3_224", + "process.hash.sha3_256", + "process.hash.sha3_384", + "process.hash.sha3_512", + "process.hash.sha512_224", + "process.hash.sha512_256", + "process.hash.xxh64", + "socket.entity_id", + "system.audit.host.timezone.name", + "system.audit.host.hostname", + "system.audit.host.id", + "system.audit.host.architecture", + "system.audit.host.mac", + "system.audit.host.os.codename", + "system.audit.host.os.platform", + "system.audit.host.os.name", + "system.audit.host.os.family", + "system.audit.host.os.version", + "system.audit.host.os.kernel", + "system.audit.package.entity_id", + "system.audit.package.name", + "system.audit.package.version", + "system.audit.package.release", + "system.audit.package.arch", + "system.audit.package.license", + "system.audit.package.summary", + "system.audit.package.url", + "system.audit.user.name", + "system.audit.user.uid", + "system.audit.user.gid", + "system.audit.user.dir", + "system.audit.user.shell", + "system.audit.user.user_information", + "system.audit.user.password.type", + "fields.*" + ] + }, + "refresh_interval": "5s" + } + } + } +} \ No newline at end of file From 0bdcda8f20c1ac4e69a6829860d607d90d0a3d3b Mon Sep 17 00:00:00 2001 From: Garrett Spong Date: Mon, 6 Apr 2020 13:44:46 -0600 Subject: [PATCH 14/36] [SIEM] Fixes UX issues around prebuilt ML Rules (#62396) ## Summary This PR fixes a number of UX issues around the new prebuilt `machine_learning` rules when the user does not have the necessary permissions to manage the backing ML Job. Along with https://github.com/elastic/kibana/pull/62383, this ensures there is adequate information for the user determine if a rule is not working because the backing job is not running (and helping to prevent this from occurring). This also includes some requested copy changes, including: * Renames `Anomaly Detection` dropdown to `ML job settings`

* Updates copy in `ML job settings` dropdown

* Only shows `ML job settings` UI when on `/detections/` routes

### All Rules Changes * Disables the `activate switch` if user does not have permission to enable/disable jobs

* Adds warning toast when attempting to activate via bulk actions (if user does not have permission to enable/disable jobs)

### Rule Details Changes * `Machine Learning job` link now links to ML App with table filtered to the relevant job * Disables the `activate switch` if user does not have permission to enable/disable jobs

### Create/Edit Rule Changes * If the job selected _is not running_, a warning will be displayed to remind the user to enable the job before running the rule. cc @benskelker @MikePaquette -- this okay copy here?

Resolves https://github.com/elastic/siem-team/issues/575 Resolves https://github.com/elastic/siem-team/issues/519 ### Checklist Delete any items that are not applicable to this PR. - [X] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md) - [ ] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials - Scheduled time with @benskelker to update docs - [X] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios --- .../components/header_global/index.test.tsx | 14 +++ .../public/components/header_global/index.tsx | 116 +++++++++--------- .../popover_description.test.tsx.snap | 2 +- .../components/ml_popover/ml_popover.tsx | 6 +- .../ml_popover/popover_description.tsx | 2 +- .../components/ml_popover/translations.ts | 13 +- .../siem/public/components/toasters/utils.ts | 24 ++++ .../rules/all/batch_actions.tsx | 22 +++- .../detection_engine/rules/all/columns.tsx | 32 +++-- .../detection_engine/rules/all/index.tsx | 29 ++++- .../description_step/ml_job_description.tsx | 4 +- .../rules/components/ml_job_select/index.tsx | 112 +++++++++++------ .../step_define_rule/translations.tsx | 8 ++ .../detection_engine/rules/details/index.tsx | 34 +++-- .../pages/detection_engine/translations.ts | 14 +++ .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - 17 files changed, 305 insertions(+), 131 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx index 098de39bbfef5..56fa0d56f3c3a 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx @@ -10,6 +10,16 @@ import React from 'react'; import '../../mock/match_media'; import { HeaderGlobal } from './index'; +jest.mock('react-router-dom', () => ({ + useLocation: () => ({ + pathname: '/app/siem#/hosts/allHosts', + hash: '', + search: '', + state: '', + }), + withRouter: () => jest.fn(), +})); + jest.mock('ui/new_platform'); // Test will fail because we will to need to mock some core services to make the test work @@ -19,6 +29,10 @@ jest.mock('../search_bar', () => ({ })); describe('HeaderGlobal', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + test('it renders', () => { const wrapper = shallow(); diff --git a/x-pack/legacy/plugins/siem/public/components/header_global/index.tsx b/x-pack/legacy/plugins/siem/public/components/header_global/index.tsx index a12fab8a4f5d9..adc2be4f9c365 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_global/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_global/index.tsx @@ -9,6 +9,7 @@ import { pickBy } from 'lodash/fp'; import React from 'react'; import styled, { css } from 'styled-components'; +import { useLocation } from 'react-router-dom'; import { gutterTimeline } from '../../lib/helpers'; import { navTabs } from '../../pages/home/home_navigations'; import { SiemPageName } from '../../pages/home/types'; @@ -36,63 +37,68 @@ FlexItem.displayName = 'FlexItem'; interface HeaderGlobalProps { hideDetectionEngine?: boolean; } -export const HeaderGlobal = React.memo(({ hideDetectionEngine = false }) => ( - - - - {({ indicesExist }) => ( - <> - - - - - - - +export const HeaderGlobal = React.memo(({ hideDetectionEngine = false }) => { + const currentLocation = useLocation(); - - {indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - key !== SiemPageName.detections, navTabs) - : navTabs - } - /> - ) : ( - key === SiemPageName.overview, navTabs)} - /> - )} - - - - - - - {indicesExistOrDataTemporarilyUnavailable(indicesExist) && ( + return ( + + + + {({ indicesExist }) => ( + <> + + - + + + + + + + {indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( + key !== SiemPageName.detections, navTabs) + : navTabs + } + /> + ) : ( + key === SiemPageName.overview, navTabs)} + /> + )} - )} + + - - - {i18n.BUTTON_ADD_DATA} - - - - - - )} - - - -)); + + + {indicesExistOrDataTemporarilyUnavailable(indicesExist) && + currentLocation.pathname.includes(`/${SiemPageName.detections}/`) && ( + + + + )} + + + + {i18n.BUTTON_ADD_DATA} + + + + + + )} + +
+ + ); +}); HeaderGlobal.displayName = 'HeaderGlobal'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/__snapshots__/popover_description.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/ml_popover/__snapshots__/popover_description.test.tsx.snap index 09e95c5ff59ea..46e61f9e939ee 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/__snapshots__/popover_description.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/__snapshots__/popover_description.test.tsx.snap @@ -5,7 +5,7 @@ exports[`JobsTableFilters renders correctly against snapshot 1`] = ` size="s" > { iconSide="right" onClick={() => setIsPopoverOpen(!isPopoverOpen)} > - {i18n.ANOMALY_DETECTION} + {i18n.ML_JOB_SETTINGS} } isOpen={isPopoverOpen} @@ -142,14 +142,14 @@ export const MlPopover = React.memo(() => { dispatch({ type: 'refresh' }); }} > - {i18n.ANOMALY_DETECTION} + {i18n.ML_JOB_SETTINGS} } isOpen={isPopoverOpen} closePopover={() => setIsPopoverOpen(!isPopoverOpen)} > - {i18n.ANOMALY_DETECTION_TITLE} + {i18n.ML_JOB_SETTINGS} diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.tsx index 20e8dd2492fef..a491d4b6b769c 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.tsx @@ -14,7 +14,7 @@ export const PopoverDescriptionComponent = () => ( diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/translations.ts b/x-pack/legacy/plugins/siem/public/components/ml_popover/translations.ts index 442068dd0e193..613691e55dcfd 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/translations.ts +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/translations.ts @@ -6,17 +6,10 @@ import { i18n } from '@kbn/i18n'; -export const ANOMALY_DETECTION = i18n.translate( - 'xpack.siem.components.mlPopup.anomalyDetectionButtonLabel', +export const ML_JOB_SETTINGS = i18n.translate( + 'xpack.siem.components.mlPopup.mlJobSettingsButtonLabel', { - defaultMessage: 'Anomaly detection', - } -); - -export const ANOMALY_DETECTION_TITLE = i18n.translate( - 'xpack.siem.components.mlPopup.anomalyDetectionTitle', - { - defaultMessage: 'Anomaly detection settings', + defaultMessage: 'ML job settings', } ); diff --git a/x-pack/legacy/plugins/siem/public/components/toasters/utils.ts b/x-pack/legacy/plugins/siem/public/components/toasters/utils.ts index e624144c9826f..e7cc389d4c06b 100644 --- a/x-pack/legacy/plugins/siem/public/components/toasters/utils.ts +++ b/x-pack/legacy/plugins/siem/public/components/toasters/utils.ts @@ -37,6 +37,30 @@ export const displayErrorToast = ( }); }; +/** + * Displays a warning toast for the provided title and message + * + * @param title warning message to display in toaster and modal + * @param dispatchToaster provided by useStateToaster() + * @param id unique ID if necessary + */ +export const displayWarningToast = ( + title: string, + dispatchToaster: React.Dispatch, + id: string = uuid.v4() +): void => { + const toast: AppToast = { + id, + title, + color: 'warning', + iconType: 'help', + }; + dispatchToaster({ + type: 'addToaster', + toast, + }); +}; + /** * Displays a success toast for the provided title and message * diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx index 60ad68b8c9141..454ef18e0ae14 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx @@ -14,13 +14,15 @@ import { enableRulesAction, exportRulesAction, } from './actions'; -import { ActionToaster } from '../../../../components/toasters'; +import { ActionToaster, displayWarningToast } from '../../../../components/toasters'; import { Rule } from '../../../../containers/detection_engine/rules'; +import * as detectionI18n from '../../translations'; interface GetBatchItems { closePopover: () => void; dispatch: Dispatch; dispatchToaster: Dispatch; + hasMlPermissions: boolean; loadingRuleIds: string[]; reFetchRules: (refreshPrePackagedRule?: boolean) => void; rules: Rule[]; @@ -31,6 +33,7 @@ export const getBatchItems = ({ closePopover, dispatch, dispatchToaster, + hasMlPermissions, loadingRuleIds, reFetchRules, rules, @@ -57,7 +60,22 @@ export const getBatchItems = ({ const deactivatedIds = selectedRuleIds.filter( id => !rules.find(r => r.id === id)?.enabled ?? false ); - await enableRulesAction(deactivatedIds, true, dispatch, dispatchToaster); + + const deactivatedIdsNoML = deactivatedIds.filter( + id => rules.find(r => r.id === id)?.type !== 'machine_learning' ?? false + ); + + const mlRuleCount = deactivatedIds.length - deactivatedIdsNoML.length; + if (!hasMlPermissions && mlRuleCount > 0) { + displayWarningToast(detectionI18n.ML_RULES_UNAVAILABLE(mlRuleCount), dispatchToaster); + } + + await enableRulesAction( + hasMlPermissions ? deactivatedIds : deactivatedIdsNoML, + true, + dispatch, + dispatchToaster + ); }} > {i18n.BATCH_ACTION_ACTIVATE_SELECTED} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx index 9a84d33ab5fdf..80e644f800334 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx @@ -13,6 +13,7 @@ import { EuiTableActionsColumnType, EuiText, EuiHealth, + EuiToolTip, } from '@elastic/eui'; import { FormattedRelative } from '@kbn/i18n/react'; import * as H from 'history'; @@ -36,6 +37,8 @@ import { } from './actions'; import { Action } from './reducer'; import { LocalizedDateTooltip } from '../../../../components/localized_date_tooltip'; +import * as detectionI18n from '../../translations'; +import { isMlRule } from '../../../../../common/detection_engine/ml_helpers'; export const getActions = ( dispatch: React.Dispatch, @@ -88,6 +91,7 @@ interface GetColumns { dispatch: React.Dispatch; dispatchToaster: Dispatch; history: H.History; + hasMlPermissions: boolean; hasNoPermissions: boolean; loadingRuleIds: string[]; reFetchRules: (refreshPrePackagedRule?: boolean) => void; @@ -98,6 +102,7 @@ export const getColumns = ({ dispatch, dispatchToaster, history, + hasMlPermissions, hasNoPermissions, loadingRuleIds, reFetchRules, @@ -182,14 +187,25 @@ export const getColumns = ({ field: 'enabled', name: i18n.COLUMN_ACTIVATE, render: (value: Rule['enabled'], item: Rule) => ( - + + + ), sortable: true, width: '95px', diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx index ccdfd1ed1be38..e96ed856208bd 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx @@ -40,6 +40,8 @@ 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 '../../../../components/ml_popover/hooks/use_ml_capabilities'; +import { hasMlAdminPermissions } from '../../../../components/ml/permissions/has_ml_admin_permissions'; const SORT_FIELD = 'enabled'; const initialState: State = { @@ -111,6 +113,11 @@ export const AllRules = React.memo( const { loading: isLoadingRulesStatuses, rulesStatuses } = useRulesStatuses(rules); const history = useHistory(); const [, dispatchToaster] = useStateToaster(); + const mlCapabilities = useMlCapabilities(); + + // TODO: Refactor license check + hasMlAdminPermissions to common check + const hasMlPermissions = + mlCapabilities.isPlatinumOrTrialLicense && hasMlAdminPermissions(mlCapabilities); const setRules = useCallback((newRules: Rule[], newPagination: Partial) => { dispatch({ @@ -145,6 +152,7 @@ export const AllRules = React.memo( closePopover, dispatch, dispatchToaster, + hasMlPermissions, loadingRuleIds, selectedRuleIds, reFetchRules: reFetchRulesData, @@ -152,7 +160,15 @@ export const AllRules = React.memo( })} /> ), - [dispatch, dispatchToaster, loadingRuleIds, reFetchRulesData, rules, selectedRuleIds] + [ + dispatch, + dispatchToaster, + hasMlPermissions, + loadingRuleIds, + reFetchRulesData, + rules, + selectedRuleIds, + ] ); const paginationMemo = useMemo( @@ -184,6 +200,7 @@ export const AllRules = React.memo( dispatch, dispatchToaster, history, + hasMlPermissions, hasNoPermissions, loadingRuleIds: loadingRulesAction != null && @@ -192,7 +209,15 @@ export const AllRules = React.memo( : [], reFetchRules: reFetchRulesData, }); - }, [dispatch, dispatchToaster, history, loadingRuleIds, loadingRulesAction, reFetchRulesData]); + }, [ + dispatch, + dispatchToaster, + hasMlPermissions, + history, + loadingRuleIds, + loadingRulesAction, + reFetchRulesData, + ]); const monitoringColumns = useMemo(() => getMonitoringColumns(), []); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/ml_job_description.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/ml_job_description.tsx index 8276aa3578563..5a9593f1a6de2 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/ml_job_description.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/ml_job_description.tsx @@ -66,7 +66,9 @@ const Wrapper = styled.div` `; export const MlJobDescription: React.FC<{ job: SiemJob }> = ({ job }) => { - const jobUrl = useKibana().services.application.getUrlForApp('ml#/jobs'); + const jobUrl = useKibana().services.application.getUrlForApp( + `ml#/jobs?mlManagement=(jobId:${encodeURI(job.id)})` + ); return ( diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/ml_job_select/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/ml_job_select/index.tsx index 3d253b71b53d6..794edf0ab5de7 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/ml_job_select/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/ml_job_select/index.tsx @@ -4,37 +4,64 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFlexGroup, EuiFlexItem, EuiFormRow, + EuiIcon, EuiLink, EuiSuperSelect, EuiText, } from '@elastic/eui'; +import styled from 'styled-components'; import { FieldHook, getFieldValidityAndErrorMessage } from '../../../../../shared_imports'; import { useSiemJobs } from '../../../../../components/ml_popover/hooks/use_siem_jobs'; import { useKibana } from '../../../../../lib/kibana'; -import { ML_JOB_SELECT_PLACEHOLDER_TEXT } from '../step_define_rule/translations'; +import { + ML_JOB_SELECT_PLACEHOLDER_TEXT, + ENABLE_ML_JOB_WARNING, +} from '../step_define_rule/translations'; +import { isJobStarted } from '../../../../../../common/detection_engine/ml_helpers'; + +const HelpTextWarningContainer = styled.div` + margin-top: 10px; +`; + +const MlJobSelectEuiFlexGroup = styled(EuiFlexGroup)` + margin-bottom: 5px; +`; -const HelpText: React.FC<{ href: string }> = ({ href }) => ( - - - - ), - }} - /> +const HelpText: React.FC<{ href: string; showEnableWarning: boolean }> = ({ + href, + showEnableWarning = false, +}) => ( + <> + + + + ), + }} + /> + {showEnableWarning && ( + + + + {ENABLE_ML_JOB_WARNING} + + + )} + ); const JobDisplay: React.FC<{ title: string; description: string }> = ({ title, description }) => ( @@ -77,26 +104,37 @@ export const MlJobSelect: React.FC = ({ describedByIds = [], f const options = [placeholderOption, ...jobOptions]; + const isJobRunning = useMemo(() => { + // If the selected job is not found in the list, it means the placeholder is selected + // and so we don't want to show the warning, thus isJobRunning will be true when 'job == null' + const job = siemJobs.find(j => j.id === jobId); + return job == null || isJobStarted(job.jobState, job.datafeedState); + }, [siemJobs, jobId]); + return ( - } - isInvalid={isInvalid} - error={errorMessage} - data-test-subj="mlJobSelect" - describedByIds={describedByIds} - > - - - - - - + + + } + isInvalid={isInvalid} + error={errorMessage} + data-test-subj="mlJobSelect" + describedByIds={describedByIds} + > + + + + + + + + ); }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/translations.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/translations.tsx index 1d8821aceb249..bbdb2130ce298 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/translations.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/translations.tsx @@ -62,3 +62,11 @@ export const ML_JOB_SELECT_PLACEHOLDER_TEXT = i18n.translate( defaultMessage: 'Select a job', } ); + +export const ENABLE_ML_JOB_WARNING = i18n.translate( + 'xpack.siem.detectionEngine.createRule.stepDefineRule.mlEnableJobWarningTitle', + { + defaultMessage: + 'This ML job is not currently running. Please set this job to run via "ML job settings" before activating this rule.', + } +); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx index cb4d88a8bb539..2b648a3b3f825 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx @@ -14,6 +14,7 @@ import { EuiSpacer, EuiTab, EuiTabs, + EuiToolTip, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { FC, memo, useCallback, useMemo, useState } from 'react'; @@ -66,6 +67,8 @@ import { RuleActionsOverflow } from '../components/rule_actions_overflow'; import { RuleStatusFailedCallOut } from './status_failed_callout'; import { FailureHistory } from './failure_history'; import { RuleStatus } from '../components/rule_status'; +import { useMlCapabilities } from '../../../../components/ml_popover/hooks/use_ml_capabilities'; +import { hasMlAdminPermissions } from '../../../../components/ml/permissions/has_ml_admin_permissions'; enum RuleDetailTabs { signals = 'signals', @@ -114,6 +117,11 @@ const RuleDetailsPageComponent: FC = ({ scheduleRuleData: null, }; const [lastSignals] = useSignalInfo({ ruleId }); + const mlCapabilities = useMlCapabilities(); + + // TODO: Refactor license check + hasMlAdminPermissions to common check + const hasMlPermissions = + mlCapabilities.isPlatinumOrTrialLicense && hasMlAdminPermissions(mlCapabilities); const title = isLoading === true || rule === null ? : rule.name; const subTitle = useMemo( @@ -259,13 +267,25 @@ const RuleDetailsPageComponent: FC = ({ > - + + + diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts index 39277b3d3c77e..008d660be9d88 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts @@ -86,3 +86,17 @@ export const USER_UNAUTHENTICATED_MSG_BODY = i18n.translate( 'You do not have the required permissions for viewing the detection engine. For more help, contact your administrator.', } ); + +export const ML_RULES_DISABLED_MESSAGE = i18n.translate( + 'xpack.siem.detectionEngine.mlRulesDisabledMessageTitle', + { + defaultMessage: 'ML rules require Platinum License and ML Admin Permissions', + } +); + +export const ML_RULES_UNAVAILABLE = (totalRules: number) => + i18n.translate('xpack.siem.detectionEngine.mlUnavailableTitle', { + values: { totalRules }, + defaultMessage: + '{totalRules} {totalRules, plural, =1 {rule requires} other {rules require}} Machine Learning to enable.', + }); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 023a97274b957..79c1bbc49810b 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -13548,9 +13548,7 @@ "xpack.siem.components.mlPopover.jobsTable.filters.searchFilterPlaceholder": "例: rare_process_linux", "xpack.siem.components.mlPopover.jobsTable.filters.showAllJobsLabel": "Elastic ジョブ", "xpack.siem.components.mlPopover.jobsTable.filters.showSiemJobsLabel": "カスタムジョブ", - "xpack.siem.components.mlPopup.anomalyDetectionButtonLabel": "異常検知", "xpack.siem.components.mlPopup.anomalyDetectionDescription": "下のいずれかの機械学習ジョブを実行して、SIEM アプリケーション全体の異常イベントを表示することができます。始めに使えるように、いくつかの一般的な検出ジョブが提供されています。独自のカスタムジョブを追加する場合は、{machineLearning} アプリケーションでジョブを作成して「SIEM」でタグ付けすると、ここに追加されます。", - "xpack.siem.components.mlPopup.anomalyDetectionTitle": "異常検知設定", "xpack.siem.components.mlPopup.cloudLink": "クラウド展開", "xpack.siem.components.mlPopup.errors.createJobFailureTitle": "ジョブ作成エラー", "xpack.siem.components.mlPopup.errors.startJobFailureTitle": "ジョブ開始エラー", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index b359014e95e70..77bf8f1467783 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -13552,9 +13552,7 @@ "xpack.siem.components.mlPopover.jobsTable.filters.searchFilterPlaceholder": "例如 rare_process_linux", "xpack.siem.components.mlPopover.jobsTable.filters.showAllJobsLabel": "Elastic 作业", "xpack.siem.components.mlPopover.jobsTable.filters.showSiemJobsLabel": "定制作业", - "xpack.siem.components.mlPopup.anomalyDetectionButtonLabel": "异常检测", "xpack.siem.components.mlPopup.anomalyDetectionDescription": "运行以下任何 Machine Learning 作业以查看该 SIEM 应用程序的所有异常事件。我们提供若干可让您入门的常规检测作业。如果您希望添加自己的定制作业,只需从用于纳入定制作业的 {machineLearning} 应用程序中创建定制作业并使用“SIEM”标记它们。", - "xpack.siem.components.mlPopup.anomalyDetectionTitle": "异常检测设置", "xpack.siem.components.mlPopup.cloudLink": "云部署", "xpack.siem.components.mlPopup.errors.createJobFailureTitle": "创建作业失败", "xpack.siem.components.mlPopup.errors.startJobFailureTitle": "启动作业失败", From ee02df445ba64dc99a274d7a9bad9b773552e1e3 Mon Sep 17 00:00:00 2001 From: Sandra Gonzales Date: Mon, 6 Apr 2020 15:45:11 -0400 Subject: [PATCH 15/36] [EPM] fix /packages response to return older packages (#62623) * compare package list by name * use the internal property, adding as saved object attribute * remove HiddenPackages type --- .../plugins/ingest_manager/server/saved_objects.ts | 1 + .../server/services/epm/packages/get.ts | 12 ++++++------ .../server/services/epm/packages/install.ts | 8 +++++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/ingest_manager/server/saved_objects.ts b/x-pack/plugins/ingest_manager/server/saved_objects.ts index 13f84e4efa790..6800cb4056700 100644 --- a/x-pack/plugins/ingest_manager/server/saved_objects.ts +++ b/x-pack/plugins/ingest_manager/server/saved_objects.ts @@ -148,6 +148,7 @@ export const savedObjectMappings = { properties: { name: { type: 'keyword' }, version: { type: 'keyword' }, + internal: { type: 'boolean' }, installed: { type: 'nested', properties: { diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts index d655b81f8cdef..e963ea138dfd5 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts @@ -31,17 +31,17 @@ export async function getPackages( Object.assign({}, item, { title: item.title || nameAsTitle(item.name) }) ); }); - const searchObjects = registryItems.map(({ name, version }) => ({ + // get the installed packages + const results = await savedObjectsClient.find({ type: PACKAGES_SAVED_OBJECT_TYPE, - id: `${name}-${version}`, - })); - const results = await savedObjectsClient.bulkGet(searchObjects); - const savedObjects = results.saved_objects.filter(o => !o.error); // ignore errors for now + }); + // filter out any internal packages + const savedObjectsVisible = results.saved_objects.filter(o => !o.attributes.internal); const packageList = registryItems .map(item => createInstallableFrom( item, - savedObjects.find(({ id }) => id === `${item.name}-${item.version}`) + savedObjectsVisible.find(({ attributes }) => attributes.name === item.name) ) ) .sort(sortByName); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts index 3cce238f582f4..82523e37509d1 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts @@ -87,7 +87,7 @@ export async function installPackage(options: { }): Promise { const { savedObjectsClient, pkgkey, callCluster } = options; const registryPackageInfo = await Registry.fetchInfo(pkgkey); - const { name: pkgName, version: pkgVersion } = registryPackageInfo; + const { name: pkgName, version: pkgVersion, internal = false } = registryPackageInfo; const installKibanaAssetsPromise = installKibanaAssets({ savedObjectsClient, @@ -116,6 +116,7 @@ export async function installPackage(options: { pkgkey, pkgName, pkgVersion, + internal, toSave, }); return toSave; @@ -145,9 +146,10 @@ export async function saveInstallationReferences(options: { pkgkey: string; pkgName: string; pkgVersion: string; + internal: boolean; toSave: AssetReference[]; }) { - const { savedObjectsClient, pkgkey, pkgName, pkgVersion, toSave } = options; + const { savedObjectsClient, pkgkey, pkgName, pkgVersion, internal, toSave } = options; const installation = await getInstallation({ savedObjectsClient, pkgkey }); const savedRefs = installation?.installed || []; const mergeRefsReducer = (current: AssetReference[], pending: AssetReference) => { @@ -159,7 +161,7 @@ export async function saveInstallationReferences(options: { const toInstall = toSave.reduce(mergeRefsReducer, savedRefs); await savedObjectsClient.create( PACKAGES_SAVED_OBJECT_TYPE, - { installed: toInstall, name: pkgName, version: pkgVersion }, + { installed: toInstall, name: pkgName, version: pkgVersion, internal }, { id: pkgkey, overwrite: true } ); From a7b3e5539efa7cc26458770f585b52e219cf4052 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Mon, 6 Apr 2020 21:47:15 +0200 Subject: [PATCH 16/36] [ML] Fix boolean cell values in analytics table result views and transforms wizard. (#62618) Fixes the rendering of boolean values in table results views and transforms wizards: - Fixed: Boolean cells in the transform wizard ended up being empty. - Fixed: Boolean cells in regression/classification result table would render as Yes/No instead of true/false. --- .../classification_exploration/results_table.tsx | 1 + .../components/regression_exploration/results_table.tsx | 1 + .../public/app/components/pivot_preview/pivot_preview.tsx | 4 ++++ .../source_index_preview/source_index_preview.tsx | 7 +++++++ 4 files changed, 13 insertions(+) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/results_table.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/results_table.tsx index fbdb47c87c7ef..9758dd969b443 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/results_table.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/results_table.tsx @@ -211,6 +211,7 @@ export const ResultsTable: FC = React.memo( switch (type) { case ES_FIELD_TYPES.BOOLEAN: column.dataType = ES_FIELD_TYPES.BOOLEAN; + column.render = d => (d ? 'true' : 'false'); break; case ES_FIELD_TYPES.DATE: column.align = 'right'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/results_table.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/results_table.tsx index 8d53a9278a1af..a35be5400f46b 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/results_table.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/results_table.tsx @@ -213,6 +213,7 @@ export const ResultsTable: FC = React.memo( switch (type) { case ES_FIELD_TYPES.BOOLEAN: column.dataType = ES_FIELD_TYPES.BOOLEAN; + column.render = d => (d ? 'true' : 'false'); break; case ES_FIELD_TYPES.DATE: column.align = 'right'; diff --git a/x-pack/plugins/transform/public/app/components/pivot_preview/pivot_preview.tsx b/x-pack/plugins/transform/public/app/components/pivot_preview/pivot_preview.tsx index c0c85f74418fc..c50df0366d698 100644 --- a/x-pack/plugins/transform/public/app/components/pivot_preview/pivot_preview.tsx +++ b/x-pack/plugins/transform/public/app/components/pivot_preview/pivot_preview.tsx @@ -245,6 +245,10 @@ export const PivotPreview: FC = React.memo( return formatHumanReadableDateTimeSeconds(moment(cellValue).unix() * 1000); } + if (previewMappings.properties[columnId].type === ES_FIELD_TYPES.BOOLEAN) { + return cellValue ? 'true' : 'false'; + } + return cellValue; }; }, [pageData, pagination.pageIndex, pagination.pageSize, previewMappings.properties]); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.tsx index c56263b721032..bcdeb7ddb0d36 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.tsx @@ -105,6 +105,9 @@ export const SourceIndexPreview: React.FC = React.memo(({ indexPattern, q let schema; switch (field?.type) { + case KBN_FIELD_TYPES.BOOLEAN: + schema = 'boolean'; + break; case KBN_FIELD_TYPES.DATE: schema = 'datetime'; break; @@ -190,6 +193,10 @@ export const SourceIndexPreview: React.FC = React.memo(({ indexPattern, q return formatHumanReadableDateTimeSeconds(moment(cellValue).unix() * 1000); } + if (field?.type === KBN_FIELD_TYPES.BOOLEAN) { + return cellValue ? 'true' : 'false'; + } + return cellValue; }; }, [data, indexPattern.fields, pagination.pageIndex, pagination.pageSize]); From f80925af9768cd5e9b4ccd3d4d4d5ebd739fe49c Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Mon, 6 Apr 2020 14:01:38 -0600 Subject: [PATCH 17/36] [Maps] Move layers to np maps (#61877) * Move layers to new location * Update layer path refs * Update np kibana services to cover all required services * Init np kibana services in legacy plugin. Port init functions to np * Path updates, supporting file moves, general clean up * More moves of related files and clean-up of legacy refs * Path updates. Typescript warning fixes * Update test paths * Clean up unused kibana services usage in legacy * Remove unused http ref * Test fixes and clean up * Remove unused snapshots * Add np service init to embeddables too * Move validate color picker to NP --- .../maps/public/actions/map_actions.d.ts | 57 +------------ .../maps/public/angular/get_initial_layers.js | 9 +- .../public/angular/get_initial_layers.test.js | 8 +- .../public/angular/services/saved_gis_map.js | 3 +- .../connected_components/gis_map/index.d.ts | 3 +- .../connected_components/gis_map/view.js | 3 +- .../layer_addpanel/import_editor/view.js | 3 +- .../source_select/source_select.js | 3 +- .../join_editor/resources/join_expression.js | 6 +- .../resources/metrics_expression.js | 3 +- .../resources/metrics_expression.test.js | 2 +- .../layer_settings/layer_settings.js | 3 +- .../layer_panel/view.d.ts | 8 +- .../feature_geometry_filter_form.js | 3 +- .../map/mb/draw_control/draw_control.js | 3 +- .../connected_components/map/mb/utils.js | 63 ++------------ .../connected_components/map/mb/view.js | 9 +- .../maps/public/embeddable/map_embeddable.tsx | 3 +- .../embeddable/map_embeddable_factory.ts | 7 ++ x-pack/legacy/plugins/maps/public/index.scss | 2 +- x-pack/legacy/plugins/maps/public/index.ts | 6 +- .../plugins/maps/public/kibana_services.js | 62 -------------- .../plugins/maps/public/layers/_index.scss | 1 - .../maps/public/layers/styles/_index.scss | 4 - x-pack/legacy/plugins/maps/public/plugin.ts | 36 ++++---- .../maps/public/selectors/map_selectors.js | 21 +++-- .../public/selectors/map_selectors.test.js | 10 +-- .../maps/public/actions/map_actions.d.ts | 64 +++++++++++++++ .../add_tooltip_field_popover.test.js.snap | 0 .../tooltip_selector.test.js.snap | 0 .../validated_range.test.js.snap | 0 .../components/add_tooltip_field_popover.js | 2 +- .../add_tooltip_field_popover.test.js | 0 .../maps/public/components/metric_editor.js | 0 .../maps/public/components/metric_select.js | 0 .../maps/public/components/metrics_editor.js | 0 .../components/no_index_pattern_callout.js | 8 +- .../public/components/single_field_select.js | 2 +- .../public/components/tooltip_selector.js | 0 .../components/tooltip_selector.test.js | 0 .../maps/public/components/validated_range.js | 0 .../public/components/validated_range.test.js | 0 .../layer_panel/view.d.ts | 14 ++++ .../map/mb/image_utils.js | 0 .../connected_components/map/mb/utils.js | 62 ++++++++++++++ .../maps/public/elasticsearch_geo_utils.js | 0 .../public/elasticsearch_geo_utils.test.js | 2 +- .../plugins/maps/public/index_pattern_util.js | 2 +- .../maps/public/index_pattern_util.test.js | 0 x-pack/plugins/maps/public/kibana_services.js | 82 +++++++++++++++++++ x-pack/plugins/maps/public/layers/_index.scss | 1 + .../public/layers/blended_vector_layer.ts | 0 .../public/layers/fields/ems_file_field.ts | 0 .../public/layers/fields/es_agg_field.test.ts | 0 .../maps/public/layers/fields/es_agg_field.ts | 0 .../maps/public/layers/fields/es_doc_field.ts | 4 +- .../maps/public/layers/fields/field.ts | 0 .../layers/fields/kibana_region_field.ts | 0 .../fields/top_term_percentage_field.ts | 0 .../maps/public/layers/heatmap_layer.js | 0 .../maps/public/layers/joins/inner_join.js | 0 .../public/layers/joins/inner_join.test.js | 0 .../plugins/maps/public/layers/joins/join.ts | 0 .../plugins/maps/public/layers/layer.d.ts | 0 .../plugins/maps/public/layers/layer.js | 2 +- .../public/layers/layer_wizard_registry.ts | 0 .../maps/public/layers/load_layer_wizards.js | 0 .../create_client_file_source_editor.js | 0 .../client_file_source/geojson_file_source.js | 0 .../sources/client_file_source/index.js | 0 .../ems_file_source/create_source_editor.js | 0 .../ems_file_source/ems_file_source.d.ts | 0 .../ems_file_source/ems_file_source.js | 0 .../ems_file_source/ems_file_source.test.js | 0 .../layers/sources/ems_file_source/index.js | 0 .../ems_file_source/update_source_editor.js | 0 .../sources/ems_tms_source/ems_tms_source.js | 0 .../ems_tms_source/ems_tms_source.test.js | 0 .../layers/sources/ems_tms_source/index.js | 0 .../ems_tms_source/tile_service_select.js | 0 .../ems_tms_source/update_source_editor.js | 0 .../layers/sources/ems_unavailable_message.js | 0 .../public/layers/sources/es_agg_source.d.ts | 0 .../public/layers/sources/es_agg_source.js | 0 .../layers/sources/es_agg_source.test.ts | 0 .../es_geo_grid_source/convert_to_geojson.js | 0 .../convert_to_geojson.test.ts | 0 .../create_source_editor.js | 0 .../es_geo_grid_source.d.ts | 0 .../es_geo_grid_source/es_geo_grid_source.js | 0 .../es_geo_grid_source.test.ts | 0 .../es_geo_grid_source/geo_tile_utils.js | 0 .../es_geo_grid_source/geo_tile_utils.test.js | 0 .../sources/es_geo_grid_source/index.js | 0 .../es_geo_grid_source/render_as_select.tsx | 0 .../es_geo_grid_source/resolution_editor.js | 0 .../update_source_editor.js | 2 +- .../es_pew_pew_source/convert_to_lines.js | 0 .../convert_to_lines.test.ts | 0 .../es_pew_pew_source/create_source_editor.js | 0 .../es_pew_pew_source/es_pew_pew_source.js | 2 +- .../es_pew_pew_source/update_source_editor.js | 2 +- .../__snapshots__/scaling_form.test.tsx.snap | 0 .../update_source_editor.test.js.snap | 0 .../sources/es_search_source/constants.js | 0 .../es_search_source/create_source_editor.js | 2 +- .../es_search_source/es_search_source.d.ts | 0 .../es_search_source/es_search_source.js | 0 .../es_search_source/es_search_source.test.ts | 0 .../layers/sources/es_search_source/index.js | 0 .../es_search_source/load_index_settings.js | 0 .../es_search_source/scaling_form.test.tsx | 0 .../sources/es_search_source/scaling_form.tsx | 4 +- .../es_search_source/update_source_editor.js | 2 +- .../update_source_editor.test.js | 0 .../maps/public/layers/sources/es_source.d.ts | 2 +- .../maps/public/layers/sources/es_source.js | 2 +- .../public/layers/sources/es_term_source.d.ts | 0 .../public/layers/sources/es_term_source.js | 0 .../layers/sources/es_term_source.test.js | 0 .../create_source_editor.js | 0 .../sources/kibana_regionmap_source/index.js | 0 .../kibana_regionmap_source.d.ts | 0 .../kibana_regionmap_source.js | 0 .../create_source_editor.js | 0 .../sources/kibana_tilemap_source/index.js | 0 .../kibana_tilemap_source.js | 0 .../maps/public/layers/sources/source.d.ts | 0 .../maps/public/layers/sources/source.js | 2 +- .../public/layers/sources/source_registry.ts | 0 .../public/layers/sources/tms_source.d.ts | 0 .../maps/public/layers/sources/tms_source.js | 0 .../layers/sources/vector_feature_types.js | 0 .../public/layers/sources/vector_source.d.ts | 0 .../public/layers/sources/vector_source.js | 0 .../public/layers/sources/wms_source/index.js | 0 .../layers/sources/wms_source/wms_client.js | 0 .../sources/wms_source/wms_client.test.js | 0 .../wms_source/wms_create_source_editor.js | 0 .../layers/sources/wms_source/wms_source.js | 0 .../public/layers/sources/xyz_tms_source.d.ts | 0 .../public/layers/sources/xyz_tms_source.js | 0 .../layers/sources/xyz_tms_source.test.ts | 0 .../maps/public/layers/styles/_index.scss | 4 + .../public/layers/styles/abstract_style.js | 0 .../maps/public/layers/styles/color_utils.js | 2 +- .../public/layers/styles/color_utils.test.js | 0 .../styles/components/_color_gradient.scss | 0 .../styles/components/color_gradient.js | 0 .../components/ranged_style_legend_row.js | 0 .../heatmap_style_editor.test.js.snap | 0 .../heatmap/components/heatmap_constants.js | 0 .../components/heatmap_style_editor.js | 0 .../components/heatmap_style_editor.test.js | 0 .../components/legend/heatmap_legend.js | 0 .../layers/styles/heatmap/heatmap_style.js | 0 .../vector/components/_style_prop_editor.scss | 0 .../vector/components/color/_color_stops.scss | 0 .../components/color/color_map_select.js | 0 .../vector/components/color/color_stops.js | 0 .../color/color_stops_categorical.js | 0 .../components/color/color_stops_ordinal.js | 0 .../components/color/color_stops_utils.js | 0 .../components/color/dynamic_color_form.js | 0 .../color/mb_validated_color_picker.tsx | 0 .../components/color/static_color_form.js | 0 .../color/vector_style_color_editor.js | 0 .../categorical_field_meta_popover.tsx | 0 .../field_meta/field_meta_popover.tsx | 0 .../field_meta/ordinal_field_meta_popover.tsx | 0 .../styles/vector/components/field_select.js | 2 +- .../components/get_vector_style_label.js | 0 .../components/label/dynamic_label_form.js | 0 .../components/label/static_label_form.js | 0 .../vector_style_label_border_size_editor.js | 0 .../label/vector_style_label_editor.js | 0 .../__snapshots__/vector_icon.test.js.snap | 0 .../vector/components/legend/category.js | 0 .../vector/components/legend/circle_icon.js | 0 .../extract_color_from_style_property.js | 0 .../vector/components/legend/line_icon.js | 0 .../vector/components/legend/polygon_icon.js | 0 .../vector/components/legend/symbol_icon.js | 0 .../vector/components/legend/vector_icon.js | 0 .../components/legend/vector_icon.test.js | 0 .../components/legend/vector_style_legend.js | 0 .../orientation/dynamic_orientation_form.js | 0 .../orientation/orientation_editor.js | 0 .../orientation/static_orientation_form.js | 0 .../components/size/dynamic_size_form.js | 0 .../components/size/size_range_selector.js | 2 +- .../components/size/static_size_form.js | 0 .../size/vector_style_size_editor.js | 0 .../styles/vector/components/stop_input.js | 0 .../vector/components/style_map_select.js | 0 .../vector/components/style_option_shapes.js | 0 .../vector/components/style_prop_editor.js | 0 .../__snapshots__/icon_select.test.js.snap | 0 .../components/symbol/_icon_select.scss | 0 .../components/symbol/dynamic_icon_form.js | 0 .../components/symbol/icon_map_select.js | 0 .../vector/components/symbol/icon_select.js | 0 .../components/symbol/icon_select.test.js | 0 .../vector/components/symbol/icon_stops.js | 0 .../components/symbol/icon_stops.test.js | 0 .../components/symbol/static_icon_form.js | 0 .../symbol/vector_style_icon_editor.js | 0 .../vector_style_symbolize_as_editor.js | 0 .../vector/components/vector_style_editor.js | 0 .../dynamic_color_property.test.js.snap | 0 .../components/categorical_legend.js | 0 .../properties/components/ordinal_legend.js | 0 .../properties/dynamic_color_property.js | 0 .../properties/dynamic_color_property.test.js | 0 .../properties/dynamic_icon_property.js | 0 .../dynamic_orientation_property.js | 0 .../properties/dynamic_size_property.js | 0 .../properties/dynamic_style_property.d.ts | 0 .../properties/dynamic_style_property.js | 0 .../properties/dynamic_text_property.js | 0 .../properties/label_border_size_property.js | 0 .../properties/static_color_property.js | 0 .../vector/properties/static_icon_property.js | 0 .../properties/static_orientation_property.js | 0 .../vector/properties/static_size_property.js | 0 .../properties/static_style_property.js | 0 .../vector/properties/static_text_property.js | 0 .../vector/properties/style_property.ts | 0 .../properties/symbolize_as_property.js | 0 .../public/layers/styles/vector/style_meta.ts | 0 .../public/layers/styles/vector/style_util.js | 0 .../layers/styles/vector/style_util.test.js | 0 .../layers/styles/vector/symbol_utils.js | 0 .../layers/styles/vector/symbol_utils.test.js | 0 .../layers/styles/vector/vector_style.d.ts | 0 .../layers/styles/vector/vector_style.js | 0 .../layers/styles/vector/vector_style.test.js | 0 .../styles/vector/vector_style_defaults.ts | 0 .../maps/public/layers/tile_layer.d.ts | 0 .../plugins/maps/public/layers/tile_layer.js | 0 .../maps/public/layers/tile_layer.test.ts | 0 .../tooltips/es_agg_tooltip_property.ts | 0 .../layers/tooltips/es_tooltip_property.ts | 4 +- .../layers/tooltips/join_tooltip_property.ts | 2 +- .../layers/tooltips/tooltip_property.ts | 4 +- .../layers/util/assign_feature_ids.test.ts | 0 .../public/layers/util/assign_feature_ids.ts | 0 .../public/layers/util/can_skip_fetch.test.js | 0 .../maps/public/layers/util/can_skip_fetch.ts | 0 .../maps/public/layers/util/data_request.ts | 0 .../public/layers/util/es_agg_utils.test.ts | 0 .../maps/public/layers/util/es_agg_utils.ts | 2 +- .../public/layers/util/is_metric_countable.ts | 0 .../layers/util/is_refresh_only_query.ts | 0 .../layers/util/mb_filter_expressions.ts | 0 .../maps/public/layers/vector_layer.d.ts | 0 .../maps/public/layers/vector_layer.js | 0 .../maps/public/layers/vector_tile_layer.js | 0 .../{legacy => }/plugins/maps/public/meta.js | 25 +++--- .../plugins/maps/public/meta.test.js | 45 ++++------ x-pack/plugins/maps/public/plugin.ts | 37 +++++++++ 261 files changed, 417 insertions(+), 318 deletions(-) delete mode 100644 x-pack/legacy/plugins/maps/public/layers/_index.scss delete mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/_index.scss create mode 100644 x-pack/plugins/maps/public/actions/map_actions.d.ts rename x-pack/{legacy => }/plugins/maps/public/components/__snapshots__/add_tooltip_field_popover.test.js.snap (100%) rename x-pack/{legacy => }/plugins/maps/public/components/__snapshots__/tooltip_selector.test.js.snap (100%) rename x-pack/{legacy => }/plugins/maps/public/components/__snapshots__/validated_range.test.js.snap (100%) rename x-pack/{legacy => }/plugins/maps/public/components/add_tooltip_field_popover.js (98%) rename x-pack/{legacy => }/plugins/maps/public/components/add_tooltip_field_popover.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/components/metric_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/components/metric_select.js (100%) rename x-pack/{legacy => }/plugins/maps/public/components/metrics_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/components/no_index_pattern_callout.js (85%) rename x-pack/{legacy => }/plugins/maps/public/components/single_field_select.js (95%) rename x-pack/{legacy => }/plugins/maps/public/components/tooltip_selector.js (100%) rename x-pack/{legacy => }/plugins/maps/public/components/tooltip_selector.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/components/validated_range.js (100%) rename x-pack/{legacy => }/plugins/maps/public/components/validated_range.test.js (100%) create mode 100644 x-pack/plugins/maps/public/connected_components/layer_panel/view.d.ts rename x-pack/{legacy => }/plugins/maps/public/connected_components/map/mb/image_utils.js (100%) create mode 100644 x-pack/plugins/maps/public/connected_components/map/mb/utils.js rename x-pack/{legacy => }/plugins/maps/public/elasticsearch_geo_utils.js (100%) rename x-pack/{legacy => }/plugins/maps/public/elasticsearch_geo_utils.test.js (99%) rename x-pack/{legacy => }/plugins/maps/public/index_pattern_util.js (95%) rename x-pack/{legacy => }/plugins/maps/public/index_pattern_util.test.js (100%) create mode 100644 x-pack/plugins/maps/public/layers/_index.scss rename x-pack/{legacy => }/plugins/maps/public/layers/blended_vector_layer.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/fields/ems_file_field.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/fields/es_agg_field.test.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/fields/es_agg_field.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/fields/es_doc_field.ts (96%) rename x-pack/{legacy => }/plugins/maps/public/layers/fields/field.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/fields/kibana_region_field.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/fields/top_term_percentage_field.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/heatmap_layer.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/joins/inner_join.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/joins/inner_join.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/joins/join.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/layer.d.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/layer.js (99%) rename x-pack/{legacy => }/plugins/maps/public/layers/layer_wizard_registry.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/load_layer_wizards.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/client_file_source/create_client_file_source_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/client_file_source/index.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/ems_file_source/create_source_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.d.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/ems_file_source/index.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/ems_file_source/update_source_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/ems_tms_source/index.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/ems_tms_source/tile_service_select.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/ems_tms_source/update_source_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/ems_unavailable_message.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_agg_source.d.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_agg_source.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_agg_source.test.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.test.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.d.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.test.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_geo_grid_source/index.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_geo_grid_source/render_as_select.tsx (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_geo_grid_source/resolution_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js (97%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.test.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js (98%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_pew_pew_source/update_source_editor.js (96%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_search_source/__snapshots__/scaling_form.test.tsx.snap (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_search_source/__snapshots__/update_source_editor.test.js.snap (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_search_source/constants.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js (98%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_search_source/es_search_source.d.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_search_source/es_search_source.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_search_source/es_search_source.test.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_search_source/index.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_search_source/load_index_settings.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_search_source/scaling_form.test.tsx (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_search_source/scaling_form.tsx (97%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js (98%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_search_source/update_source_editor.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_source.d.ts (92%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_source.js (99%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_term_source.d.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_term_source.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/es_term_source.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/kibana_regionmap_source/create_source_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/kibana_regionmap_source/index.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.d.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/kibana_tilemap_source/create_source_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/kibana_tilemap_source/index.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/kibana_tilemap_source/kibana_tilemap_source.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/source.d.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/source.js (96%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/source_registry.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/tms_source.d.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/tms_source.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/vector_feature_types.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/vector_source.d.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/vector_source.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/wms_source/index.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/wms_source/wms_client.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/wms_source/wms_client.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/wms_source/wms_create_source_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/wms_source/wms_source.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/xyz_tms_source.d.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/xyz_tms_source.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/sources/xyz_tms_source.test.ts (100%) create mode 100644 x-pack/plugins/maps/public/layers/styles/_index.scss rename x-pack/{legacy => }/plugins/maps/public/layers/styles/abstract_style.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/color_utils.js (98%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/color_utils.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/components/_color_gradient.scss (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/components/color_gradient.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/components/ranged_style_legend_row.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/heatmap/components/__snapshots__/heatmap_style_editor.test.js.snap (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/heatmap/components/heatmap_constants.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/heatmap/components/heatmap_style_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/heatmap/components/heatmap_style_editor.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/heatmap/components/legend/heatmap_legend.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/heatmap/heatmap_style.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/_style_prop_editor.scss (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/color/_color_stops.scss (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/color/color_stops.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/color/color_stops_categorical.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/color/color_stops_ordinal.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/color/color_stops_utils.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/color/mb_validated_color_picker.tsx (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/color/static_color_form.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/color/vector_style_color_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/field_meta/categorical_field_meta_popover.tsx (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/field_meta/field_meta_popover.tsx (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/field_meta/ordinal_field_meta_popover.tsx (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/field_select.js (97%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/get_vector_style_label.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_form.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/label/static_label_form.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_border_size_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/legend/__snapshots__/vector_icon.test.js.snap (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/legend/category.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/legend/circle_icon.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/legend/extract_color_from_style_property.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/legend/line_icon.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/legend/polygon_icon.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/legend/symbol_icon.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_form.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/orientation/orientation_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_form.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_form.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js (92%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/size/static_size_form.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/size/vector_style_size_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/stop_input.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/style_map_select.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/style_option_shapes.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/style_prop_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/symbol/__snapshots__/icon_select.test.js.snap (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/symbol/_icon_select.scss (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/symbol/dynamic_icon_form.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/symbol/icon_map_select.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/symbol/static_icon_form.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_icon_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_symbolize_as_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/components/categorical_legend.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/components/ordinal_legend.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/dynamic_icon_property.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/dynamic_orientation_property.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.d.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/dynamic_text_property.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/label_border_size_property.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/static_color_property.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/static_icon_property.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/static_orientation_property.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/static_size_property.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/static_style_property.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/static_text_property.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/style_property.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/properties/symbolize_as_property.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/style_meta.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/style_util.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/style_util.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/symbol_utils.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/symbol_utils.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/vector_style.d.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/vector_style.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/vector_style.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/styles/vector/vector_style_defaults.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/tile_layer.d.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/tile_layer.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/tile_layer.test.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/tooltips/es_agg_tooltip_property.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/tooltips/es_tooltip_property.ts (95%) rename x-pack/{legacy => }/plugins/maps/public/layers/tooltips/join_tooltip_property.ts (96%) rename x-pack/{legacy => }/plugins/maps/public/layers/tooltips/tooltip_property.ts (92%) rename x-pack/{legacy => }/plugins/maps/public/layers/util/assign_feature_ids.test.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/util/assign_feature_ids.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/util/can_skip_fetch.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/util/can_skip_fetch.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/util/data_request.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/util/es_agg_utils.test.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/util/es_agg_utils.ts (95%) rename x-pack/{legacy => }/plugins/maps/public/layers/util/is_metric_countable.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/util/is_refresh_only_query.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/util/mb_filter_expressions.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/vector_layer.d.ts (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/vector_layer.js (100%) rename x-pack/{legacy => }/plugins/maps/public/layers/vector_tile_layer.js (100%) rename x-pack/{legacy => }/plugins/maps/public/meta.js (76%) rename x-pack/{legacy => }/plugins/maps/public/meta.test.js (57%) diff --git a/x-pack/legacy/plugins/maps/public/actions/map_actions.d.ts b/x-pack/legacy/plugins/maps/public/actions/map_actions.d.ts index b4a8ff90c3512..34f8c30b51874 100644 --- a/x-pack/legacy/plugins/maps/public/actions/map_actions.d.ts +++ b/x-pack/legacy/plugins/maps/public/actions/map_actions.d.ts @@ -5,59 +5,4 @@ */ /* eslint-disable @typescript-eslint/consistent-type-definitions */ -import { Filter, Query, TimeRange } from 'src/plugins/data/public'; -import { AnyAction } from 'redux'; -import { LAYER_TYPE } from '../../common/constants'; -import { DataMeta, MapFilters } from '../../common/descriptor_types'; -import { - MapCenterAndZoom, - MapRefreshConfig, -} from '../../../../../plugins/maps/common/descriptor_types'; - -export type SyncContext = { - startLoading(dataId: string, requestToken: symbol, meta: DataMeta): void; - stopLoading(dataId: string, requestToken: symbol, data: unknown, meta: DataMeta): void; - onLoadError(dataId: string, requestToken: symbol, errorMessage: string): void; - updateSourceData(newData: unknown): void; - isRequestStillActive(dataId: string, requestToken: symbol): boolean; - registerCancelCallback(requestToken: symbol, callback: () => void): void; - dataFilters: MapFilters; -}; - -export function updateSourceProp( - layerId: string, - propName: string, - value: unknown, - newLayerType?: LAYER_TYPE -): void; - -export function setGotoWithCenter(config: MapCenterAndZoom): AnyAction; - -export function replaceLayerList(layerList: unknown[]): AnyAction; - -export type QueryGroup = { - filters: Filter[]; - query?: Query; - timeFilters?: TimeRange; - refresh?: boolean; -}; - -export function setQuery(query: QueryGroup): AnyAction; - -export function setRefreshConfig(config: MapRefreshConfig): AnyAction; - -export function disableScrollZoom(): AnyAction; - -export function disableInteractive(): AnyAction; - -export function disableTooltipControl(): AnyAction; - -export function hideToolbarOverlay(): AnyAction; - -export function hideLayerControl(): AnyAction; - -export function hideViewControl(): AnyAction; - -export function setHiddenLayers(hiddenLayerIds: string[]): AnyAction; - -export function addLayerWithoutDataSync(layerDescriptor: unknown): AnyAction; +export * from '../../../../../plugins/maps/public/actions/map_actions'; diff --git a/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.js b/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.js index 8fc32aef54770..5e497ff0736b2 100644 --- a/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.js +++ b/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.js @@ -4,10 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ import _ from 'lodash'; -import { KibanaTilemapSource } from '../layers/sources/kibana_tilemap_source'; -import { EMSTMSSource } from '../layers/sources/ems_tms_source'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { KibanaTilemapSource } from '../../../../../plugins/maps/public/layers/sources/kibana_tilemap_source'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { EMSTMSSource } from '../../../../../plugins/maps/public/layers/sources/ems_tms_source'; import { getInjectedVarFunc } from '../kibana_services'; -import { getKibanaTileMap } from '../meta'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getKibanaTileMap } from '../../../../../plugins/maps/public/meta'; export function getInitialLayers(layerListJSON, initialLayers = []) { if (layerListJSON) { diff --git a/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.test.js b/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.test.js index f41ed26b2a05d..5334beaaf714a 100644 --- a/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.test.js +++ b/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('../meta', () => { +jest.mock('../../../../../plugins/maps/public/meta', () => { return {}; }); jest.mock('../kibana_services'); @@ -32,7 +32,7 @@ describe('Saved object has layer list', () => { describe('kibana.yml configured with map.tilemap.url', () => { beforeAll(() => { - require('../meta').getKibanaTileMap = () => { + require('../../../../../plugins/maps/public/meta').getKibanaTileMap = () => { return { url: 'myTileUrl', }; @@ -62,7 +62,7 @@ describe('kibana.yml configured with map.tilemap.url', () => { describe('EMS is enabled', () => { beforeAll(() => { - require('../meta').getKibanaTileMap = () => { + require('../../../../../plugins/maps/public/meta').getKibanaTileMap = () => { return null; }; require('../kibana_services').getInjectedVarFunc = () => key => { @@ -106,7 +106,7 @@ describe('EMS is enabled', () => { describe('EMS is not enabled', () => { beforeAll(() => { - require('../meta').getKibanaTileMap = () => { + require('../../../../../plugins/maps/public/meta').getKibanaTileMap = () => { return null; }; diff --git a/x-pack/legacy/plugins/maps/public/angular/services/saved_gis_map.js b/x-pack/legacy/plugins/maps/public/angular/services/saved_gis_map.js index f846d3d4a617f..990a0613da681 100644 --- a/x-pack/legacy/plugins/maps/public/angular/services/saved_gis_map.js +++ b/x-pack/legacy/plugins/maps/public/angular/services/saved_gis_map.js @@ -17,7 +17,8 @@ import { getFilters, } from '../../selectors/map_selectors'; import { getIsLayerTOCOpen, getOpenTOCDetails } from '../../selectors/ui_selectors'; -import { convertMapExtentToPolygon } from '../../elasticsearch_geo_utils'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { convertMapExtentToPolygon } from '../../../../../../plugins/maps/public/elasticsearch_geo_utils'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { copyPersistentState } from '../../../../../../plugins/maps/public/reducers/util'; import { extractReferences, injectReferences } from '../../../common/migrations/references'; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/gis_map/index.d.ts b/x-pack/legacy/plugins/maps/public/connected_components/gis_map/index.d.ts index 00a9400109dc1..8689d88297171 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/gis_map/index.d.ts +++ b/x-pack/legacy/plugins/maps/public/connected_components/gis_map/index.d.ts @@ -6,7 +6,8 @@ import React from 'react'; import { Filter } from 'src/plugins/data/public'; -import { RenderToolTipContent } from '../../layers/tooltips/tooltip_property'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { RenderToolTipContent } from '../../../../../../plugins/maps/public/layers/tooltips/tooltip_property'; export const GisMap: React.ComponentType<{ addFilters: ((filters: Filter[]) => void) | null; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/gis_map/view.js b/x-pack/legacy/plugins/maps/public/connected_components/gis_map/view.js index 97139103ab7c1..358313b8f5b6d 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/gis_map/view.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/gis_map/view.js @@ -13,7 +13,8 @@ import { LayerPanel } from '../layer_panel/index'; import { AddLayerPanel } from '../layer_addpanel/index'; import { EuiFlexGroup, EuiFlexItem, EuiCallOut } from '@elastic/eui'; import { ExitFullScreenButton } from 'ui/exit_full_screen'; -import { getIndexPatternsFromIds } from '../../index_pattern_util'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getIndexPatternsFromIds } from '../../../../../../plugins/maps/public/index_pattern_util'; import { ES_GEO_FIELD_TYPE } from '../../../common/constants'; import { indexPatterns as indexPatternsUtils } from '../../../../../../../src/plugins/data/public'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/import_editor/view.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/import_editor/view.js index 762409b256286..cb20d80733c33 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/import_editor/view.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/import_editor/view.js @@ -7,7 +7,8 @@ import React, { Fragment } from 'react'; import { EuiSpacer, EuiPanel, EuiButtonEmpty } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { uploadLayerWizardConfig } from '../../../layers/sources/client_file_source'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { uploadLayerWizardConfig } from '../../../../../../../plugins/maps/public/layers/sources/client_file_source'; export const ImportEditor = ({ clearSource, isIndexingTriggered, ...props }) => { const editorProperties = getEditorProperties({ isIndexingTriggered, ...props }); diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/source_select/source_select.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/source_select/source_select.js index b34a432bec88c..67cc17ebaa224 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/source_select/source_select.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/source_select/source_select.js @@ -5,7 +5,8 @@ */ import React, { Fragment } from 'react'; -import { getLayerWizards } from '../../../layers/layer_wizard_registry'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getLayerWizards } from '../../../../../../../plugins/maps/public/layers/layer_wizard_registry'; import { EuiTitle, EuiSpacer, EuiCard, EuiIcon } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import _ from 'lodash'; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join_expression.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join_expression.js index f7edcf6e85e25..6c080ace4442a 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join_expression.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join_expression.js @@ -16,9 +16,11 @@ import { EuiFormHelpText, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { SingleFieldSelect } from '../../../../components/single_field_select'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { SingleFieldSelect } from '../../../../../../../../plugins/maps/public/components/single_field_select'; import { FormattedMessage } from '@kbn/i18n/react'; -import { getTermsFields } from '../../../../index_pattern_util'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getTermsFields } from '../../../../../../../../plugins/maps/public/index_pattern_util'; import { getIndexPatternService, getIndexPatternSelectComponent, diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.js index 0944d0e602c2f..c6a79a398f9af 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.js @@ -14,7 +14,8 @@ import { EuiFormErrorText, EuiFormHelpText, } from '@elastic/eui'; -import { MetricsEditor } from '../../../../components/metrics_editor'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { MetricsEditor } from '../../../../../../../../plugins/maps/public/components/metrics_editor'; import { FormattedMessage } from '@kbn/i18n/react'; import { AGG_TYPE } from '../../../../../common/constants'; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.test.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.test.js index e4e3776c8e92c..d8bf862249448 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.test.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('../../../../components/metrics_editor', () => ({ +jest.mock('../../../../../../../../plugins/maps/public/components/metric_editor', () => ({ MetricsEditor: () => { return
mockMetricsEditor
; }, diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/layer_settings/layer_settings.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/layer_settings/layer_settings.js index eb23607aa2150..bd27450943638 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/layer_settings/layer_settings.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/layer_settings/layer_settings.js @@ -8,7 +8,8 @@ import React, { Fragment } from 'react'; import { EuiTitle, EuiPanel, EuiFormRow, EuiFieldText, EuiSpacer } from '@elastic/eui'; -import { ValidatedRange } from '../../../components/validated_range'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { ValidatedRange } from '../../../../../../../plugins/maps/public/components/validated_range'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { ValidatedDualRange } from '../../../../../../../../src/plugins/kibana_react/public'; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/view.d.ts b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/view.d.ts index 6d1d076c723ad..cf4fdc7be70c6 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/view.d.ts +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/view.d.ts @@ -5,10 +5,4 @@ */ /* eslint-disable @typescript-eslint/consistent-type-definitions */ -import { LAYER_TYPE } from '../../../common/constants'; - -export type OnSourceChangeArgs = { - propName: string; - value: unknown; - newLayerType?: LAYER_TYPE; -}; +export * from '../../../../../../plugins/maps/public/connected_components/layer_panel/view'; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js b/x-pack/legacy/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js index 416af95581058..7063c50edad6a 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js @@ -8,7 +8,8 @@ import React, { Component, Fragment } from 'react'; import { EuiIcon } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { createSpatialFilterWithGeometry } from '../../../elasticsearch_geo_utils'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { createSpatialFilterWithGeometry } from '../../../../../../../plugins/maps/public/elasticsearch_geo_utils'; import { GEO_JSON_TYPE } from '../../../../common/constants'; import { GeometryFilterForm } from '../../../components/geometry_filter_form'; import { UrlOverflowService } from 'ui/error_url_overflow'; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js index 99abe5d108b5a..df2988d399c5b 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js @@ -16,7 +16,8 @@ import { createSpatialFilterWithGeometry, getBoundingBoxGeometry, roundCoordinates, -} from '../../../../elasticsearch_geo_utils'; + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../../../../plugins/maps/public/elasticsearch_geo_utils'; import { DrawTooltip } from './draw_tooltip'; const mbDrawModes = MapboxDraw.modes; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/utils.js b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/utils.js index a2850d2bb6c23..a1d1341b7c4f7 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/utils.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/utils.js @@ -5,7 +5,13 @@ */ import _ from 'lodash'; -import { RGBAImage } from './image_utils'; +import { + loadSpriteSheetImageData, + addSpriteSheetToMapFromImageData, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../../../plugins/maps/public/connected_components/map/mb/utils'; + +export { loadSpriteSheetImageData, addSpriteSheetToMapFromImageData }; export function removeOrphanedSourcesAndLayers(mbMap, layerList) { const mbStyle = mbMap.getStyle(); @@ -95,62 +101,7 @@ export function syncLayerOrderForSingleLayer(mbMap, layerList) { }); } -function getImageData(img) { - const canvas = window.document.createElement('canvas'); - const context = canvas.getContext('2d'); - if (!context) { - throw new Error('failed to create canvas 2d context'); - } - canvas.width = img.width; - canvas.height = img.height; - context.drawImage(img, 0, 0, img.width, img.height); - return context.getImageData(0, 0, img.width, img.height); -} - -export async function loadSpriteSheetImageData(imgUrl) { - return new Promise((resolve, reject) => { - const image = new Image(); - if (isCrossOriginUrl(imgUrl)) { - image.crossOrigin = 'Anonymous'; - } - image.onload = el => { - const imgData = getImageData(el.currentTarget); - resolve(imgData); - }; - image.onerror = e => { - reject(e); - }; - image.src = imgUrl; - }); -} - -export function addSpriteSheetToMapFromImageData(json, imgData, mbMap) { - for (const imageId in json) { - if (!(json.hasOwnProperty(imageId) && !mbMap.hasImage(imageId))) { - continue; - } - const { width, height, x, y, sdf, pixelRatio } = json[imageId]; - if (typeof width !== 'number' || typeof height !== 'number') { - continue; - } - - const data = new RGBAImage({ width, height }); - RGBAImage.copy(imgData, data, { x, y }, { x: 0, y: 0 }, { width, height }); - mbMap.addImage(imageId, data, { pixelRatio, sdf }); - } -} - export async function addSpritesheetToMap(json, imgUrl, mbMap) { const imgData = await loadSpriteSheetImageData(imgUrl); addSpriteSheetToMapFromImageData(json, imgData, mbMap); } - -function isCrossOriginUrl(url) { - const a = window.document.createElement('a'); - a.href = url; - return ( - a.protocol !== window.document.location.protocol || - a.host !== window.document.location.host || - a.port !== window.document.location.port - ); -} diff --git a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js index 2995ea039e7a8..fedc1902d80a2 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js @@ -12,7 +12,8 @@ import { removeOrphanedSourcesAndLayers, addSpritesheetToMap, } from './utils'; -import { getGlyphUrl, isRetina } from '../../../meta'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getGlyphUrl, isRetina } from '../../../../../../../plugins/maps/public/meta'; import { DECIMAL_DEGREES_PRECISION, ZOOM_PRECISION } from '../../../../common/constants'; import mapboxgl from 'mapbox-gl/dist/mapbox-gl-csp'; import mbWorkerUrl from '!!file-loader!mapbox-gl/dist/mapbox-gl-csp-worker'; @@ -23,7 +24,11 @@ import sprites1 from '@elastic/maki/dist/sprite@1.png'; import sprites2 from '@elastic/maki/dist/sprite@2.png'; import { DrawControl } from './draw_control'; import { TooltipControl } from './tooltip_control'; -import { clampToLatBounds, clampToLonBounds } from '../../../elasticsearch_geo_utils'; +import { + clampToLatBounds, + clampToLonBounds, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../../../plugins/maps/public/elasticsearch_geo_utils'; mapboxgl.workerUrl = mbWorkerUrl; mapboxgl.setRTLTextPlugin(mbRtlPlugin); diff --git a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.tsx index 9544e8714f265..bdd2d863e6920 100644 --- a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.tsx +++ b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.tsx @@ -57,7 +57,8 @@ import { } from '../../../../../plugins/maps/public/reducers/non_serializable_instances'; import { getMapCenter, getMapZoom, getHiddenLayerIds } from '../selectors/map_selectors'; import { MAP_SAVED_OBJECT_TYPE } from '../../common/constants'; -import { RenderToolTipContent } from '../layers/tooltips/tooltip_property'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { RenderToolTipContent } from '../../../../../plugins/maps/public/layers/tooltips/tooltip_property'; interface MapEmbeddableConfig { editUrl?: string; diff --git a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.ts b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.ts index 5a036ed47fb62..5deb3057a449e 100644 --- a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.ts +++ b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.ts @@ -27,6 +27,11 @@ import { getInitialLayers } from '../angular/get_initial_layers'; import { mergeInputWithSavedMap } from './merge_input_with_saved_map'; import '../angular/services/gis_map_saved_object_loader'; import { bindSetupCoreAndPlugins, bindStartCoreAndPlugins } from '../plugin'; +// @ts-ignore +import { + bindSetupCoreAndPlugins as bindNpSetupCoreAndPlugins, + bindStartCoreAndPlugins as bindNpStartCoreAndPlugins, +} from '../../../../../plugins/maps/public/plugin'; // eslint-disable-line @kbn/eslint/no-restricted-paths export class MapEmbeddableFactory implements EmbeddableFactoryDefinition { type = MAP_SAVED_OBJECT_TYPE; @@ -40,7 +45,9 @@ export class MapEmbeddableFactory implements EmbeddableFactoryDefinition { constructor() { // Init required services. Necessary while in legacy bindSetupCoreAndPlugins(npSetup.core, npSetup.plugins); + bindNpSetupCoreAndPlugins(npSetup.core, npSetup.plugins); bindStartCoreAndPlugins(npStart.core, npStart.plugins); + bindNpStartCoreAndPlugins(npStart.core, npStart.plugins); } async isEditable() { diff --git a/x-pack/legacy/plugins/maps/public/index.scss b/x-pack/legacy/plugins/maps/public/index.scss index 328b2e576e0e6..b2ac514299d80 100644 --- a/x-pack/legacy/plugins/maps/public/index.scss +++ b/x-pack/legacy/plugins/maps/public/index.scss @@ -14,4 +14,4 @@ @import './mapbox_hacks'; @import './connected_components/index'; @import './components/index'; -@import './layers/index'; +@import '../../../../plugins/maps/public/layers/index'; diff --git a/x-pack/legacy/plugins/maps/public/index.ts b/x-pack/legacy/plugins/maps/public/index.ts index 2d13f005f1a70..b69485e251be4 100644 --- a/x-pack/legacy/plugins/maps/public/index.ts +++ b/x-pack/legacy/plugins/maps/public/index.ts @@ -26,5 +26,9 @@ export const plugin = (initializerContext: PluginInitializerContext) => { return new MapsPlugin(); }; -export { RenderTooltipContentParams, ITooltipProperty } from './layers/tooltips/tooltip_property'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +export { + RenderTooltipContentParams, + ITooltipProperty, +} from '../../../../plugins/maps/public/layers/tooltips/tooltip_property'; export { MapEmbeddable, MapEmbeddableInput } from './embeddable'; diff --git a/x-pack/legacy/plugins/maps/public/kibana_services.js b/x-pack/legacy/plugins/maps/public/kibana_services.js index 3b0f501dc0f60..a6491fe1aa6d4 100644 --- a/x-pack/legacy/plugins/maps/public/kibana_services.js +++ b/x-pack/legacy/plugins/maps/public/kibana_services.js @@ -4,88 +4,26 @@ * you may not use this file except in compliance with the Elastic License. */ -import { esFilters, search } from '../../../../../src/plugins/data/public'; -const { getRequestInspectorStats, getResponseInspectorStats } = search; - -export const SPATIAL_FILTER_TYPE = esFilters.FILTERS.SPATIAL_FILTER; -export { SearchSource } from '../../../../../src/plugins/data/public'; - let indexPatternService; export const setIndexPatternService = dataIndexPatterns => (indexPatternService = dataIndexPatterns); export const getIndexPatternService = () => indexPatternService; -let autocompleteService; -export const setAutocompleteService = dataAutoComplete => (autocompleteService = dataAutoComplete); -export const getAutocompleteService = () => autocompleteService; - -let licenseId; -export const setLicenseId = latestLicenseId => (licenseId = latestLicenseId); -export const getLicenseId = () => { - return licenseId; -}; - let inspector; export const setInspector = newInspector => (inspector = newInspector); export const getInspector = () => { return inspector; }; -let fileUploadPlugin; -export const setFileUpload = fileUpload => (fileUploadPlugin = fileUpload); -export const getFileUploadComponent = () => { - return fileUploadPlugin.JsonUploadAndParse; -}; - let getInjectedVar; export const setInjectedVarFunc = getInjectedVarFunc => (getInjectedVar = getInjectedVarFunc); export const getInjectedVarFunc = () => getInjectedVar; -let uiSettings; -export const setUiSettings = coreUiSettings => (uiSettings = coreUiSettings); -export const getUiSettings = () => uiSettings; - let indexPatternSelectComponent; export const setIndexPatternSelect = indexPatternSelect => (indexPatternSelectComponent = indexPatternSelect); export const getIndexPatternSelectComponent = () => indexPatternSelectComponent; -let coreHttp; -export const setHttp = http => (coreHttp = http); -export const getHttp = () => coreHttp; - let dataTimeFilter; export const setTimeFilter = timeFilter => (dataTimeFilter = timeFilter); export const getTimeFilter = () => dataTimeFilter; - -let toast; -export const setToasts = notificationToast => (toast = notificationToast); -export const getToasts = () => toast; - -export async function fetchSearchSourceAndRecordWithInspector({ - searchSource, - requestId, - requestName, - requestDesc, - inspectorAdapters, - abortSignal, -}) { - const inspectorRequest = inspectorAdapters.requests.start(requestName, { - id: requestId, - description: requestDesc, - }); - let resp; - try { - inspectorRequest.stats(getRequestInspectorStats(searchSource)); - searchSource.getSearchRequestBody().then(body => { - inspectorRequest.json(body); - }); - resp = await searchSource.fetch({ abortSignal }); - inspectorRequest.stats(getResponseInspectorStats(searchSource, resp)).ok({ json: resp }); - } catch (error) { - inspectorRequest.error({ error }); - throw error; - } - - return resp; -} diff --git a/x-pack/legacy/plugins/maps/public/layers/_index.scss b/x-pack/legacy/plugins/maps/public/layers/_index.scss deleted file mode 100644 index a2ce58e0381af..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './styles/index'; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/_index.scss b/x-pack/legacy/plugins/maps/public/layers/styles/_index.scss deleted file mode 100644 index b5d9113619c76..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/_index.scss +++ /dev/null @@ -1,4 +0,0 @@ -@import './components/color_gradient'; -@import './vector/components/style_prop_editor'; -@import './vector/components/color/color_stops'; -@import './vector/components/symbol/icon_select'; diff --git a/x-pack/legacy/plugins/maps/public/plugin.ts b/x-pack/legacy/plugins/maps/public/plugin.ts index c08ed6fc6da61..0fa7e1106a6df 100644 --- a/x-pack/legacy/plugins/maps/public/plugin.ts +++ b/x-pack/legacy/plugins/maps/public/plugin.ts @@ -4,9 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import './layers/layer_wizard_registry'; -import './layers/sources/source_registry'; -import './layers/load_layer_wizards'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import '../../../../plugins/maps/public/layers/layer_wizard_registry'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import '../../../../plugins/maps/public/layers/sources/source_registry'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import '../../../../plugins/maps/public/layers/load_layer_wizards'; import { Plugin, CoreStart, CoreSetup } from 'src/core/public'; // @ts-ignore @@ -17,20 +20,17 @@ import { Start as InspectorStartContract } from 'src/plugins/inspector/public'; import { MapListing } from './components/map_listing'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { - setLicenseId, setInspector, - setFileUpload, setIndexPatternSelect, - setHttp, setTimeFilter, - setUiSettings, setInjectedVarFunc, - setToasts, setIndexPatternService, - setAutocompleteService, } from './kibana_services'; // @ts-ignore -import { setInjectedVarFunc as npSetInjectedVarFunc } from '../../../../plugins/maps/public/kibana_services'; // eslint-disable-line @kbn/eslint/no-restricted-paths +import { + bindSetupCoreAndPlugins as bindNpSetupCoreAndPlugins, + bindStartCoreAndPlugins as bindNpStartCoreAndPlugins, +} from '../../../../plugins/maps/public/plugin'; // eslint-disable-line @kbn/eslint/no-restricted-paths import { HomePublicPluginSetup } from '../../../../../src/plugins/home/public'; import { LicensingPluginSetup } from '../../../../plugins/licensing/public'; import { featureCatalogueEntry } from './feature_catalogue_entry'; @@ -63,27 +63,17 @@ interface MapsPluginStartDependencies { } export const bindSetupCoreAndPlugins = (core: CoreSetup, plugins: any) => { - const { licensing } = plugins; - const { injectedMetadata, http } = core; - if (licensing) { - licensing.license$.subscribe(({ uid }: { uid: string }) => setLicenseId(uid)); - } + const { injectedMetadata } = core; setInjectedVarFunc(injectedMetadata.getInjectedVar); - setHttp(http); - setUiSettings(core.uiSettings); setInjectedVarFunc(core.injectedMetadata.getInjectedVar); - npSetInjectedVarFunc(core.injectedMetadata.getInjectedVar); - setToasts(core.notifications.toasts); }; export const bindStartCoreAndPlugins = (core: CoreStart, plugins: any) => { - const { file_upload, data, inspector } = plugins; + const { data, inspector } = plugins; setInspector(inspector); - setFileUpload(file_upload); setIndexPatternSelect(data.ui.IndexPatternSelect); setTimeFilter(data.query.timefilter.timefilter); setIndexPatternService(data.indexPatterns); - setAutocompleteService(data.autocomplete); }; /** @internal */ @@ -96,11 +86,13 @@ export class MapsPlugin implements Plugin { }); bindSetupCoreAndPlugins(core, np); + bindNpSetupCoreAndPlugins(core, np); np.home.featureCatalogue.register(featureCatalogueEntry); } public start(core: CoreStart, plugins: MapsPluginStartDependencies) { bindStartCoreAndPlugins(core, plugins); + bindNpStartCoreAndPlugins(core, plugins); } } diff --git a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js index 397478cfd1d1b..59346e4c6fb98 100644 --- a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js +++ b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js @@ -6,11 +6,16 @@ import { createSelector } from 'reselect'; import _ from 'lodash'; -import { TileLayer } from '../layers/tile_layer'; -import { VectorTileLayer } from '../layers/vector_tile_layer'; -import { VectorLayer } from '../layers/vector_layer'; -import { HeatmapLayer } from '../layers/heatmap_layer'; -import { BlendedVectorLayer } from '../layers/blended_vector_layer'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { TileLayer } from '../../../../../plugins/maps/public/layers/tile_layer'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { VectorTileLayer } from '../../../../../plugins/maps/public/layers/vector_tile_layer'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { VectorLayer } from '../../../../../plugins/maps/public/layers/vector_layer'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { HeatmapLayer } from '../../../../../plugins/maps/public/layers/heatmap_layer'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { BlendedVectorLayer } from '../../../../../plugins/maps/public/layers/blended_vector_layer'; import { getTimeFilter } from '../kibana_services'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { getInspectorAdapters } from '../../../../../plugins/maps/public/reducers/non_serializable_instances'; @@ -19,8 +24,10 @@ import { TRACKED_LAYER_DESCRIPTOR, // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../../../../plugins/maps/public/reducers/util'; -import { InnerJoin } from '../layers/joins/inner_join'; -import { getSourceByType } from '../layers/sources/source_registry'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { InnerJoin } from '../../../../../plugins/maps/public/layers/joins/inner_join'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getSourceByType } from '../../../../../plugins/maps/public/layers/sources/source_registry'; function createLayerInstance(layerDescriptor, inspectorAdapters) { const source = createSourceInstance(layerDescriptor.sourceDescriptor, inspectorAdapters); diff --git a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.test.js b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.test.js index 1a5ab633a569f..77bd29259647c 100644 --- a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.test.js +++ b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.test.js @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('../layers/vector_layer', () => {}); -jest.mock('../layers/blended_vector_layer', () => {}); -jest.mock('../layers/heatmap_layer', () => {}); -jest.mock('../layers/vector_tile_layer', () => {}); -jest.mock('../layers/joins/inner_join', () => {}); +jest.mock('../../../../../plugins/maps/public/layers/vector_layer', () => {}); +jest.mock('../../../../../plugins/maps/public/layers/blended_vector_layer', () => {}); +jest.mock('../../../../../plugins/maps/public/layers/heatmap_layer', () => {}); +jest.mock('../../../../../plugins/maps/public/layers/vector_tile_layer', () => {}); +jest.mock('../../../../../plugins/maps/public/layers/joins/inner_join', () => {}); jest.mock('../../../../../plugins/maps/public/reducers/non_serializable_instances', () => ({ getInspectorAdapters: () => { return {}; diff --git a/x-pack/plugins/maps/public/actions/map_actions.d.ts b/x-pack/plugins/maps/public/actions/map_actions.d.ts new file mode 100644 index 0000000000000..debead3ad5c45 --- /dev/null +++ b/x-pack/plugins/maps/public/actions/map_actions.d.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +/* eslint-disable @typescript-eslint/consistent-type-definitions */ + +import { Filter, Query, TimeRange } from 'src/plugins/data/public'; +import { AnyAction } from 'redux'; +import { LAYER_TYPE } from '../../common/constants'; +import { + DataMeta, + MapFilters, + MapCenterAndZoom, + MapRefreshConfig, +} from '../../common/descriptor_types'; + +export type SyncContext = { + startLoading(dataId: string, requestToken: symbol, meta: DataMeta): void; + stopLoading(dataId: string, requestToken: symbol, data: unknown, meta: DataMeta): void; + onLoadError(dataId: string, requestToken: symbol, errorMessage: string): void; + updateSourceData(newData: unknown): void; + isRequestStillActive(dataId: string, requestToken: symbol): boolean; + registerCancelCallback(requestToken: symbol, callback: () => void): void; + dataFilters: MapFilters; +}; + +export function updateSourceProp( + layerId: string, + propName: string, + value: unknown, + newLayerType?: LAYER_TYPE +): void; + +export function setGotoWithCenter(config: MapCenterAndZoom): AnyAction; + +export function replaceLayerList(layerList: unknown[]): AnyAction; + +export type QueryGroup = { + filters: Filter[]; + query?: Query; + timeFilters?: TimeRange; + refresh?: boolean; +}; + +export function setQuery(query: QueryGroup): AnyAction; + +export function setRefreshConfig(config: MapRefreshConfig): AnyAction; + +export function disableScrollZoom(): AnyAction; + +export function disableInteractive(): AnyAction; + +export function disableTooltipControl(): AnyAction; + +export function hideToolbarOverlay(): AnyAction; + +export function hideLayerControl(): AnyAction; + +export function hideViewControl(): AnyAction; + +export function setHiddenLayers(hiddenLayerIds: string[]): AnyAction; + +export function addLayerWithoutDataSync(layerDescriptor: unknown): AnyAction; diff --git a/x-pack/legacy/plugins/maps/public/components/__snapshots__/add_tooltip_field_popover.test.js.snap b/x-pack/plugins/maps/public/components/__snapshots__/add_tooltip_field_popover.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/maps/public/components/__snapshots__/add_tooltip_field_popover.test.js.snap rename to x-pack/plugins/maps/public/components/__snapshots__/add_tooltip_field_popover.test.js.snap diff --git a/x-pack/legacy/plugins/maps/public/components/__snapshots__/tooltip_selector.test.js.snap b/x-pack/plugins/maps/public/components/__snapshots__/tooltip_selector.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/maps/public/components/__snapshots__/tooltip_selector.test.js.snap rename to x-pack/plugins/maps/public/components/__snapshots__/tooltip_selector.test.js.snap diff --git a/x-pack/legacy/plugins/maps/public/components/__snapshots__/validated_range.test.js.snap b/x-pack/plugins/maps/public/components/__snapshots__/validated_range.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/maps/public/components/__snapshots__/validated_range.test.js.snap rename to x-pack/plugins/maps/public/components/__snapshots__/validated_range.test.js.snap diff --git a/x-pack/legacy/plugins/maps/public/components/add_tooltip_field_popover.js b/x-pack/plugins/maps/public/components/add_tooltip_field_popover.js similarity index 98% rename from x-pack/legacy/plugins/maps/public/components/add_tooltip_field_popover.js rename to x-pack/plugins/maps/public/components/add_tooltip_field_popover.js index 07bc54663c1d8..984ace4fd8708 100644 --- a/x-pack/legacy/plugins/maps/public/components/add_tooltip_field_popover.js +++ b/x-pack/plugins/maps/public/components/add_tooltip_field_popover.js @@ -17,7 +17,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { FieldIcon } from '../../../../../../src/plugins/kibana_react/public'; +import { FieldIcon } from '../../../../../src/plugins/kibana_react/public'; const sortByLabel = (a, b) => { return a.label.localeCompare(b.label); diff --git a/x-pack/legacy/plugins/maps/public/components/add_tooltip_field_popover.test.js b/x-pack/plugins/maps/public/components/add_tooltip_field_popover.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/components/add_tooltip_field_popover.test.js rename to x-pack/plugins/maps/public/components/add_tooltip_field_popover.test.js diff --git a/x-pack/legacy/plugins/maps/public/components/metric_editor.js b/x-pack/plugins/maps/public/components/metric_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/components/metric_editor.js rename to x-pack/plugins/maps/public/components/metric_editor.js diff --git a/x-pack/legacy/plugins/maps/public/components/metric_select.js b/x-pack/plugins/maps/public/components/metric_select.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/components/metric_select.js rename to x-pack/plugins/maps/public/components/metric_select.js diff --git a/x-pack/legacy/plugins/maps/public/components/metrics_editor.js b/x-pack/plugins/maps/public/components/metrics_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/components/metrics_editor.js rename to x-pack/plugins/maps/public/components/metrics_editor.js diff --git a/x-pack/legacy/plugins/maps/public/components/no_index_pattern_callout.js b/x-pack/plugins/maps/public/components/no_index_pattern_callout.js similarity index 85% rename from x-pack/legacy/plugins/maps/public/components/no_index_pattern_callout.js rename to x-pack/plugins/maps/public/components/no_index_pattern_callout.js index 3266f13155ca7..1319607546808 100644 --- a/x-pack/legacy/plugins/maps/public/components/no_index_pattern_callout.js +++ b/x-pack/plugins/maps/public/components/no_index_pattern_callout.js @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import chrome from 'ui/chrome'; - +import { getHttp } from '../kibana_services'; import React from 'react'; import { EuiCallOut, EuiLink } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; export function NoIndexPatternCallout() { + const http = getHttp(); return ( - + - + { + const image = new Image(); + if (isCrossOriginUrl(imgUrl)) { + image.crossOrigin = 'Anonymous'; + } + image.onload = el => { + const imgData = getImageData(el.currentTarget); + resolve(imgData); + }; + image.onerror = e => { + reject(e); + }; + image.src = imgUrl; + }); +} + +export function addSpriteSheetToMapFromImageData(json, imgData, mbMap) { + for (const imageId in json) { + if (!(json.hasOwnProperty(imageId) && !mbMap.hasImage(imageId))) { + continue; + } + const { width, height, x, y, sdf, pixelRatio } = json[imageId]; + if (typeof width !== 'number' || typeof height !== 'number') { + continue; + } + + const data = new RGBAImage({ width, height }); + RGBAImage.copy(imgData, data, { x, y }, { x: 0, y: 0 }, { width, height }); + mbMap.addImage(imageId, data, { pixelRatio, sdf }); + } +} diff --git a/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.js b/x-pack/plugins/maps/public/elasticsearch_geo_utils.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.js rename to x-pack/plugins/maps/public/elasticsearch_geo_utils.js diff --git a/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.test.js b/x-pack/plugins/maps/public/elasticsearch_geo_utils.test.js similarity index 99% rename from x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.test.js rename to x-pack/plugins/maps/public/elasticsearch_geo_utils.test.js index fb4b0a6e29e6c..5db7556be4639 100644 --- a/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.test.js +++ b/x-pack/plugins/maps/public/elasticsearch_geo_utils.test.js @@ -20,7 +20,7 @@ import { convertMapExtentToPolygon, roundCoordinates, } from './elasticsearch_geo_utils'; -import { indexPatterns } from '../../../../../src/plugins/data/public'; +import { indexPatterns } from '../../../../src/plugins/data/public'; const geoFieldName = 'location'; const mapExtent = { diff --git a/x-pack/legacy/plugins/maps/public/index_pattern_util.js b/x-pack/plugins/maps/public/index_pattern_util.js similarity index 95% rename from x-pack/legacy/plugins/maps/public/index_pattern_util.js rename to x-pack/plugins/maps/public/index_pattern_util.js index 30a0a6826db83..6cb02c7605e28 100644 --- a/x-pack/legacy/plugins/maps/public/index_pattern_util.js +++ b/x-pack/plugins/maps/public/index_pattern_util.js @@ -5,7 +5,7 @@ */ import { getIndexPatternService } from './kibana_services'; -import { indexPatterns } from '../../../../../src/plugins/data/public'; +import { indexPatterns } from '../../../../src/plugins/data/public'; import { ES_GEO_FIELD_TYPE } from '../common/constants'; export async function getIndexPatternsFromIds(indexPatternIds = []) { diff --git a/x-pack/legacy/plugins/maps/public/index_pattern_util.test.js b/x-pack/plugins/maps/public/index_pattern_util.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/index_pattern_util.test.js rename to x-pack/plugins/maps/public/index_pattern_util.test.js diff --git a/x-pack/plugins/maps/public/kibana_services.js b/x-pack/plugins/maps/public/kibana_services.js index 1073e44fa711e..d2ddecfdf915b 100644 --- a/x-pack/plugins/maps/public/kibana_services.js +++ b/x-pack/plugins/maps/public/kibana_services.js @@ -3,7 +3,89 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { esFilters, search } from '../../../../src/plugins/data/public'; + +export { SearchSource } from '../../../../src/plugins/data/public'; + +export const SPATIAL_FILTER_TYPE = esFilters.FILTERS.SPATIAL_FILTER; +const { getRequestInspectorStats, getResponseInspectorStats } = search; + +let indexPatternService; +export const setIndexPatternService = dataIndexPatterns => + (indexPatternService = dataIndexPatterns); +export const getIndexPatternService = () => indexPatternService; + +let autocompleteService; +export const setAutocompleteService = dataAutoComplete => (autocompleteService = dataAutoComplete); +export const getAutocompleteService = () => autocompleteService; + +let licenseId; +export const setLicenseId = latestLicenseId => (licenseId = latestLicenseId); +export const getLicenseId = () => { + return licenseId; +}; + +let inspector; +export const setInspector = newInspector => (inspector = newInspector); +export const getInspector = () => { + return inspector; +}; + +let fileUploadPlugin; +export const setFileUpload = fileUpload => (fileUploadPlugin = fileUpload); +export const getFileUploadComponent = () => { + return fileUploadPlugin.JsonUploadAndParse; +}; let getInjectedVar; export const setInjectedVarFunc = getInjectedVarFunc => (getInjectedVar = getInjectedVarFunc); export const getInjectedVarFunc = () => getInjectedVar; + +let uiSettings; +export const setUiSettings = coreUiSettings => (uiSettings = coreUiSettings); +export const getUiSettings = () => uiSettings; + +let indexPatternSelectComponent; +export const setIndexPatternSelect = indexPatternSelect => + (indexPatternSelectComponent = indexPatternSelect); +export const getIndexPatternSelectComponent = () => indexPatternSelectComponent; + +let coreHttp; +export const setHttp = http => (coreHttp = http); +export const getHttp = () => coreHttp; + +let dataTimeFilter; +export const setTimeFilter = timeFilter => (dataTimeFilter = timeFilter); +export const getTimeFilter = () => dataTimeFilter; + +let toast; +export const setToasts = notificationToast => (toast = notificationToast); +export const getToasts = () => toast; + +export async function fetchSearchSourceAndRecordWithInspector({ + searchSource, + requestId, + requestName, + requestDesc, + inspectorAdapters, + abortSignal, +}) { + const inspectorRequest = inspectorAdapters.requests.start(requestName, { + id: requestId, + description: requestDesc, + }); + let resp; + try { + inspectorRequest.stats(getRequestInspectorStats(searchSource)); + searchSource.getSearchRequestBody().then(body => { + inspectorRequest.json(body); + }); + resp = await searchSource.fetch({ abortSignal }); + inspectorRequest.stats(getResponseInspectorStats(searchSource, resp)).ok({ json: resp }); + } catch (error) { + inspectorRequest.error({ error }); + throw error; + } + + return resp; +} diff --git a/x-pack/plugins/maps/public/layers/_index.scss b/x-pack/plugins/maps/public/layers/_index.scss new file mode 100644 index 0000000000000..29a5761255278 --- /dev/null +++ b/x-pack/plugins/maps/public/layers/_index.scss @@ -0,0 +1 @@ +@import 'styles/index'; diff --git a/x-pack/legacy/plugins/maps/public/layers/blended_vector_layer.ts b/x-pack/plugins/maps/public/layers/blended_vector_layer.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/blended_vector_layer.ts rename to x-pack/plugins/maps/public/layers/blended_vector_layer.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/ems_file_field.ts b/x-pack/plugins/maps/public/layers/fields/ems_file_field.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/fields/ems_file_field.ts rename to x-pack/plugins/maps/public/layers/fields/ems_file_field.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.test.ts b/x-pack/plugins/maps/public/layers/fields/es_agg_field.test.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.test.ts rename to x-pack/plugins/maps/public/layers/fields/es_agg_field.test.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.ts b/x-pack/plugins/maps/public/layers/fields/es_agg_field.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.ts rename to x-pack/plugins/maps/public/layers/fields/es_agg_field.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/es_doc_field.ts b/x-pack/plugins/maps/public/layers/fields/es_doc_field.ts similarity index 96% rename from x-pack/legacy/plugins/maps/public/layers/fields/es_doc_field.ts rename to x-pack/plugins/maps/public/layers/fields/es_doc_field.ts index 4401452841a46..b7647d881fcf6 100644 --- a/x-pack/legacy/plugins/maps/public/layers/fields/es_doc_field.ts +++ b/x-pack/plugins/maps/public/layers/fields/es_doc_field.ts @@ -8,8 +8,8 @@ import { FIELD_ORIGIN } from '../../../common/constants'; import { ESTooltipProperty } from '../tooltips/es_tooltip_property'; import { ITooltipProperty, TooltipProperty } from '../tooltips/tooltip_property'; import { COLOR_PALETTE_MAX_SIZE } from '../../../common/constants'; -import { indexPatterns } from '../../../../../../../src/plugins/data/public'; -import { IFieldType } from '../../../../../../../src/plugins/data/public'; +import { indexPatterns } from '../../../../../../src/plugins/data/public'; +import { IFieldType } from '../../../../../../src/plugins/data/public'; import { IField, AbstractField } from './field'; import { IESSource } from '../sources/es_source'; import { IVectorSource } from '../sources/vector_source'; diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/field.ts b/x-pack/plugins/maps/public/layers/fields/field.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/fields/field.ts rename to x-pack/plugins/maps/public/layers/fields/field.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/kibana_region_field.ts b/x-pack/plugins/maps/public/layers/fields/kibana_region_field.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/fields/kibana_region_field.ts rename to x-pack/plugins/maps/public/layers/fields/kibana_region_field.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/top_term_percentage_field.ts b/x-pack/plugins/maps/public/layers/fields/top_term_percentage_field.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/fields/top_term_percentage_field.ts rename to x-pack/plugins/maps/public/layers/fields/top_term_percentage_field.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/heatmap_layer.js b/x-pack/plugins/maps/public/layers/heatmap_layer.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/heatmap_layer.js rename to x-pack/plugins/maps/public/layers/heatmap_layer.js diff --git a/x-pack/legacy/plugins/maps/public/layers/joins/inner_join.js b/x-pack/plugins/maps/public/layers/joins/inner_join.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/joins/inner_join.js rename to x-pack/plugins/maps/public/layers/joins/inner_join.js diff --git a/x-pack/legacy/plugins/maps/public/layers/joins/inner_join.test.js b/x-pack/plugins/maps/public/layers/joins/inner_join.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/joins/inner_join.test.js rename to x-pack/plugins/maps/public/layers/joins/inner_join.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/joins/join.ts b/x-pack/plugins/maps/public/layers/joins/join.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/joins/join.ts rename to x-pack/plugins/maps/public/layers/joins/join.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/layer.d.ts b/x-pack/plugins/maps/public/layers/layer.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/layer.d.ts rename to x-pack/plugins/maps/public/layers/layer.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/layer.js b/x-pack/plugins/maps/public/layers/layer.js similarity index 99% rename from x-pack/legacy/plugins/maps/public/layers/layer.js rename to x-pack/plugins/maps/public/layers/layer.js index e9616be89b601..26bce872b3c2c 100644 --- a/x-pack/legacy/plugins/maps/public/layers/layer.js +++ b/x-pack/plugins/maps/public/layers/layer.js @@ -15,7 +15,7 @@ import { } from '../../common/constants'; import uuid from 'uuid/v4'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { copyPersistentState } from '../../../../../plugins/maps/public/reducers/util.js'; +import { copyPersistentState } from '../reducers/util.js'; import { i18n } from '@kbn/i18n'; export class AbstractLayer { diff --git a/x-pack/legacy/plugins/maps/public/layers/layer_wizard_registry.ts b/x-pack/plugins/maps/public/layers/layer_wizard_registry.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/layer_wizard_registry.ts rename to x-pack/plugins/maps/public/layers/layer_wizard_registry.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/load_layer_wizards.js b/x-pack/plugins/maps/public/layers/load_layer_wizards.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/load_layer_wizards.js rename to x-pack/plugins/maps/public/layers/load_layer_wizards.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/create_client_file_source_editor.js b/x-pack/plugins/maps/public/layers/sources/client_file_source/create_client_file_source_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/create_client_file_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/client_file_source/create_client_file_source_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js b/x-pack/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js rename to x-pack/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/index.js b/x-pack/plugins/maps/public/layers/sources/client_file_source/index.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/index.js rename to x-pack/plugins/maps/public/layers/sources/client_file_source/index.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/create_source_editor.js b/x-pack/plugins/maps/public/layers/sources/ems_file_source/create_source_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/create_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/ems_file_source/create_source_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.d.ts b/x-pack/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.d.ts rename to x-pack/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.js b/x-pack/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.js rename to x-pack/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.test.js b/x-pack/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.test.js rename to x-pack/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/index.js b/x-pack/plugins/maps/public/layers/sources/ems_file_source/index.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/index.js rename to x-pack/plugins/maps/public/layers/sources/ems_file_source/index.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/update_source_editor.js b/x-pack/plugins/maps/public/layers/sources/ems_file_source/update_source_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/update_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/ems_file_source/update_source_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js b/x-pack/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js rename to x-pack/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.test.js b/x-pack/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.test.js rename to x-pack/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/index.js b/x-pack/plugins/maps/public/layers/sources/ems_tms_source/index.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/index.js rename to x-pack/plugins/maps/public/layers/sources/ems_tms_source/index.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/tile_service_select.js b/x-pack/plugins/maps/public/layers/sources/ems_tms_source/tile_service_select.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/tile_service_select.js rename to x-pack/plugins/maps/public/layers/sources/ems_tms_source/tile_service_select.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/update_source_editor.js b/x-pack/plugins/maps/public/layers/sources/ems_tms_source/update_source_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/update_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/ems_tms_source/update_source_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_unavailable_message.js b/x-pack/plugins/maps/public/layers/sources/ems_unavailable_message.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_unavailable_message.js rename to x-pack/plugins/maps/public/layers/sources/ems_unavailable_message.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.d.ts b/x-pack/plugins/maps/public/layers/sources/es_agg_source.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.d.ts rename to x-pack/plugins/maps/public/layers/sources/es_agg_source.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.js b/x-pack/plugins/maps/public/layers/sources/es_agg_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.js rename to x-pack/plugins/maps/public/layers/sources/es_agg_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.test.ts b/x-pack/plugins/maps/public/layers/sources/es_agg_source.test.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.test.ts rename to x-pack/plugins/maps/public/layers/sources/es_agg_source.test.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.js b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.js rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.test.ts b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.test.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.test.ts rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.test.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.d.ts b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.d.ts rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.test.ts b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.test.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.test.ts rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.test.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.js b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.js rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.test.js b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.test.js rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/index.js b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/index.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/index.js rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/index.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/render_as_select.tsx b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/render_as_select.tsx similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/render_as_select.tsx rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/render_as_select.tsx diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/resolution_editor.js b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/resolution_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/resolution_editor.js rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/resolution_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js similarity index 97% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js index 269c2a8b8633a..cd494db3897fb 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js +++ b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js @@ -14,7 +14,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; import { isMetricCountable } from '../../util/is_metric_countable'; -import { indexPatterns } from '../../../../../../../../src/plugins/data/public'; +import { indexPatterns } from '../../../../../../../src/plugins/data/public'; export class UpdateSourceEditor extends Component { state = { diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.js b/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.js rename to x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.test.ts b/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.test.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.test.ts rename to x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.test.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js b/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js b/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js similarity index 98% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js rename to x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js index da2b663746b9d..ea3a2d2fe634d 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js +++ b/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js @@ -25,7 +25,7 @@ import { convertToLines } from './convert_to_lines'; import { AbstractESAggSource } from '../es_agg_source'; import { DynamicStyleProperty } from '../../styles/vector/properties/dynamic_style_property'; import { COLOR_GRADIENTS } from '../../styles/color_utils'; -import { indexPatterns } from '../../../../../../../../src/plugins/data/public'; +import { indexPatterns } from '../../../../../../../src/plugins/data/public'; import { registerSource } from '../source_registry'; const MAX_GEOTILE_LEVEL = 29; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/update_source_editor.js b/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/update_source_editor.js similarity index 96% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/update_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/update_source_editor.js index ce1f53c33ba53..dea59a1c82f8a 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/update_source_editor.js +++ b/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/update_source_editor.js @@ -11,7 +11,7 @@ import { getIndexPatternService } from '../../../kibana_services'; import { i18n } from '@kbn/i18n'; import { EuiPanel, EuiTitle, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { indexPatterns } from '../../../../../../../../src/plugins/data/public'; +import { indexPatterns } from '../../../../../../../src/plugins/data/public'; export class UpdateSourceEditor extends Component { state = { diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/__snapshots__/scaling_form.test.tsx.snap b/x-pack/plugins/maps/public/layers/sources/es_search_source/__snapshots__/scaling_form.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/__snapshots__/scaling_form.test.tsx.snap rename to x-pack/plugins/maps/public/layers/sources/es_search_source/__snapshots__/scaling_form.test.tsx.snap diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/__snapshots__/update_source_editor.test.js.snap b/x-pack/plugins/maps/public/layers/sources/es_search_source/__snapshots__/update_source_editor.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/__snapshots__/update_source_editor.test.js.snap rename to x-pack/plugins/maps/public/layers/sources/es_search_source/__snapshots__/update_source_editor.test.js.snap diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/constants.js b/x-pack/plugins/maps/public/layers/sources/es_search_source/constants.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/constants.js rename to x-pack/plugins/maps/public/layers/sources/es_search_source/constants.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js b/x-pack/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js similarity index 98% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js index 73bea574ace28..aeb3835354f07 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js +++ b/x-pack/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js @@ -15,7 +15,7 @@ import { NoIndexPatternCallout } from '../../../components/no_index_pattern_call import { i18n } from '@kbn/i18n'; import { ES_GEO_FIELD_TYPE, SCALING_TYPES } from '../../../../common/constants'; import { DEFAULT_FILTER_BY_MAP_BOUNDS } from './constants'; -import { indexPatterns } from '../../../../../../../../src/plugins/data/public'; +import { indexPatterns } from '../../../../../../../src/plugins/data/public'; import { ScalingForm } from './scaling_form'; import { getTermsFields } from '../../../index_pattern_util'; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.d.ts b/x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.d.ts rename to x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js b/x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js rename to x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.test.ts b/x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.test.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.test.ts rename to x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.test.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/index.js b/x-pack/plugins/maps/public/layers/sources/es_search_source/index.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/index.js rename to x-pack/plugins/maps/public/layers/sources/es_search_source/index.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/load_index_settings.js b/x-pack/plugins/maps/public/layers/sources/es_search_source/load_index_settings.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/load_index_settings.js rename to x-pack/plugins/maps/public/layers/sources/es_search_source/load_index_settings.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/scaling_form.test.tsx b/x-pack/plugins/maps/public/layers/sources/es_search_source/scaling_form.test.tsx similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/scaling_form.test.tsx rename to x-pack/plugins/maps/public/layers/sources/es_search_source/scaling_form.test.tsx diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/scaling_form.tsx b/x-pack/plugins/maps/public/layers/sources/es_search_source/scaling_form.tsx similarity index 97% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/scaling_form.tsx rename to x-pack/plugins/maps/public/layers/sources/es_search_source/scaling_form.tsx index c5950f1132974..d86fc6d4026e6 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/scaling_form.tsx +++ b/x-pack/plugins/maps/public/layers/sources/es_search_source/scaling_form.tsx @@ -22,8 +22,6 @@ import { SingleFieldSelect } from '../../../components/single_field_select'; // @ts-ignore import { indexPatternService } from '../../../kibana_services'; // @ts-ignore -import { getTermsFields, getSourceFields } from '../../../index_pattern_util'; -// @ts-ignore import { ValidatedRange } from '../../../components/validated_range'; import { DEFAULT_MAX_INNER_RESULT_WINDOW, @@ -33,7 +31,7 @@ import { } from '../../../../common/constants'; // @ts-ignore import { loadIndexSettings } from './load_index_settings'; -import { IFieldType } from '../../../../../../../../src/plugins/data/public'; +import { IFieldType } from '../../../../../../../src/plugins/data/public'; import { OnSourceChangeArgs } from '../../../connected_components/layer_panel/view'; interface Props { diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js b/x-pack/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js similarity index 98% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js index 9c92ec5801e49..cb6255afd0a42 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js +++ b/x-pack/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js @@ -16,7 +16,7 @@ import { getTermsFields, getSourceFields } from '../../../index_pattern_util'; import { SORT_ORDER } from '../../../../common/constants'; import { ESDocField } from '../../fields/es_doc_field'; import { FormattedMessage } from '@kbn/i18n/react'; -import { indexPatterns } from '../../../../../../../../src/plugins/data/public'; +import { indexPatterns } from '../../../../../../../src/plugins/data/public'; import { ScalingForm } from './scaling_form'; export class UpdateSourceEditor extends Component { diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/update_source_editor.test.js b/x-pack/plugins/maps/public/layers/sources/es_search_source/update_source_editor.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/update_source_editor.test.js rename to x-pack/plugins/maps/public/layers/sources/es_search_source/update_source_editor.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.d.ts b/x-pack/plugins/maps/public/layers/sources/es_source.d.ts similarity index 92% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_source.d.ts rename to x-pack/plugins/maps/public/layers/sources/es_source.d.ts index ffd1d343b59e0..65851d0e7bd38 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.d.ts +++ b/x-pack/plugins/maps/public/layers/sources/es_source.d.ts @@ -6,7 +6,7 @@ import { AbstractVectorSource } from './vector_source'; import { IVectorSource } from './vector_source'; -import { IndexPattern, SearchSource } from '../../../../../../../src/plugins/data/public'; +import { IndexPattern, SearchSource } from '../../../../../../src/plugins/data/public'; import { VectorSourceRequestMeta } from '../../../common/descriptor_types'; export interface IESSource extends IVectorSource { diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js b/x-pack/plugins/maps/public/layers/sources/es_source.js similarity index 99% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_source.js rename to x-pack/plugins/maps/public/layers/sources/es_source.js index 441d52d23398a..d90a802a38344 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js +++ b/x-pack/plugins/maps/public/layers/sources/es_source.js @@ -17,7 +17,7 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import uuid from 'uuid/v4'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { copyPersistentState } from '../../../../../../plugins/maps/public/reducers/util'; +import { copyPersistentState } from '../../reducers/util'; import { ES_GEO_FIELD_TYPE } from '../../../common/constants'; import { DataRequestAbortError } from '../util/data_request'; import { expandToTileBoundaries } from './es_geo_grid_source/geo_tile_utils'; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.d.ts b/x-pack/plugins/maps/public/layers/sources/es_term_source.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.d.ts rename to x-pack/plugins/maps/public/layers/sources/es_term_source.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js b/x-pack/plugins/maps/public/layers/sources/es_term_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js rename to x-pack/plugins/maps/public/layers/sources/es_term_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js b/x-pack/plugins/maps/public/layers/sources/es_term_source.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js rename to x-pack/plugins/maps/public/layers/sources/es_term_source.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/create_source_editor.js b/x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/create_source_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/create_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/create_source_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/index.js b/x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/index.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/index.js rename to x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/index.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.d.ts b/x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.d.ts rename to x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js b/x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js rename to x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_tilemap_source/create_source_editor.js b/x-pack/plugins/maps/public/layers/sources/kibana_tilemap_source/create_source_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/kibana_tilemap_source/create_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/kibana_tilemap_source/create_source_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_tilemap_source/index.js b/x-pack/plugins/maps/public/layers/sources/kibana_tilemap_source/index.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/kibana_tilemap_source/index.js rename to x-pack/plugins/maps/public/layers/sources/kibana_tilemap_source/index.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_tilemap_source/kibana_tilemap_source.js b/x-pack/plugins/maps/public/layers/sources/kibana_tilemap_source/kibana_tilemap_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/kibana_tilemap_source/kibana_tilemap_source.js rename to x-pack/plugins/maps/public/layers/sources/kibana_tilemap_source/kibana_tilemap_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/source.d.ts b/x-pack/plugins/maps/public/layers/sources/source.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/source.d.ts rename to x-pack/plugins/maps/public/layers/sources/source.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/source.js b/x-pack/plugins/maps/public/layers/sources/source.js similarity index 96% rename from x-pack/legacy/plugins/maps/public/layers/sources/source.js rename to x-pack/plugins/maps/public/layers/sources/source.js index b6b6c10831bb5..368de421e23ce 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/source.js +++ b/x-pack/plugins/maps/public/layers/sources/source.js @@ -5,7 +5,7 @@ */ // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { copyPersistentState } from '../../../../../../plugins/maps/public/reducers/util'; +import { copyPersistentState } from '../../reducers/util'; export class AbstractSource { static isIndexingSource = false; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/source_registry.ts b/x-pack/plugins/maps/public/layers/sources/source_registry.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/source_registry.ts rename to x-pack/plugins/maps/public/layers/sources/source_registry.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/tms_source.d.ts b/x-pack/plugins/maps/public/layers/sources/tms_source.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/tms_source.d.ts rename to x-pack/plugins/maps/public/layers/sources/tms_source.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/tms_source.js b/x-pack/plugins/maps/public/layers/sources/tms_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/tms_source.js rename to x-pack/plugins/maps/public/layers/sources/tms_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/vector_feature_types.js b/x-pack/plugins/maps/public/layers/sources/vector_feature_types.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/vector_feature_types.js rename to x-pack/plugins/maps/public/layers/sources/vector_feature_types.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.d.ts b/x-pack/plugins/maps/public/layers/sources/vector_source.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/vector_source.d.ts rename to x-pack/plugins/maps/public/layers/sources/vector_source.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.js b/x-pack/plugins/maps/public/layers/sources/vector_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/vector_source.js rename to x-pack/plugins/maps/public/layers/sources/vector_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/wms_source/index.js b/x-pack/plugins/maps/public/layers/sources/wms_source/index.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/wms_source/index.js rename to x-pack/plugins/maps/public/layers/sources/wms_source/index.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/wms_source/wms_client.js b/x-pack/plugins/maps/public/layers/sources/wms_source/wms_client.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/wms_source/wms_client.js rename to x-pack/plugins/maps/public/layers/sources/wms_source/wms_client.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/wms_source/wms_client.test.js b/x-pack/plugins/maps/public/layers/sources/wms_source/wms_client.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/wms_source/wms_client.test.js rename to x-pack/plugins/maps/public/layers/sources/wms_source/wms_client.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/wms_source/wms_create_source_editor.js b/x-pack/plugins/maps/public/layers/sources/wms_source/wms_create_source_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/wms_source/wms_create_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/wms_source/wms_create_source_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/wms_source/wms_source.js b/x-pack/plugins/maps/public/layers/sources/wms_source/wms_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/wms_source/wms_source.js rename to x-pack/plugins/maps/public/layers/sources/wms_source/wms_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.d.ts b/x-pack/plugins/maps/public/layers/sources/xyz_tms_source.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.d.ts rename to x-pack/plugins/maps/public/layers/sources/xyz_tms_source.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.js b/x-pack/plugins/maps/public/layers/sources/xyz_tms_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.js rename to x-pack/plugins/maps/public/layers/sources/xyz_tms_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.test.ts b/x-pack/plugins/maps/public/layers/sources/xyz_tms_source.test.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.test.ts rename to x-pack/plugins/maps/public/layers/sources/xyz_tms_source.test.ts diff --git a/x-pack/plugins/maps/public/layers/styles/_index.scss b/x-pack/plugins/maps/public/layers/styles/_index.scss new file mode 100644 index 0000000000000..a1c4c297a3ac1 --- /dev/null +++ b/x-pack/plugins/maps/public/layers/styles/_index.scss @@ -0,0 +1,4 @@ +@import 'components/color_gradient'; +@import 'vector/components/style_prop_editor'; +@import 'vector/components/color/color_stops'; +@import 'vector/components/symbol/icon_select'; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/abstract_style.js b/x-pack/plugins/maps/public/layers/styles/abstract_style.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/abstract_style.js rename to x-pack/plugins/maps/public/layers/styles/abstract_style.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/color_utils.js b/x-pack/plugins/maps/public/layers/styles/color_utils.js similarity index 98% rename from x-pack/legacy/plugins/maps/public/layers/styles/color_utils.js rename to x-pack/plugins/maps/public/layers/styles/color_utils.js index 09c7d76db1691..23b61b07bf871 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/color_utils.js +++ b/x-pack/plugins/maps/public/layers/styles/color_utils.js @@ -10,7 +10,7 @@ import chroma from 'chroma-js'; import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; import { ColorGradient } from './components/color_gradient'; import { COLOR_PALETTE_MAX_SIZE } from '../../../common/constants'; -import { vislibColorMaps } from '../../../../../../../src/plugins/charts/public'; +import { vislibColorMaps } from '../../../../../../src/plugins/charts/public'; const GRADIENT_INTERVALS = 8; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/color_utils.test.js b/x-pack/plugins/maps/public/layers/styles/color_utils.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/color_utils.test.js rename to x-pack/plugins/maps/public/layers/styles/color_utils.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/components/_color_gradient.scss b/x-pack/plugins/maps/public/layers/styles/components/_color_gradient.scss similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/components/_color_gradient.scss rename to x-pack/plugins/maps/public/layers/styles/components/_color_gradient.scss diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/components/color_gradient.js b/x-pack/plugins/maps/public/layers/styles/components/color_gradient.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/components/color_gradient.js rename to x-pack/plugins/maps/public/layers/styles/components/color_gradient.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/components/ranged_style_legend_row.js b/x-pack/plugins/maps/public/layers/styles/components/ranged_style_legend_row.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/components/ranged_style_legend_row.js rename to x-pack/plugins/maps/public/layers/styles/components/ranged_style_legend_row.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/__snapshots__/heatmap_style_editor.test.js.snap b/x-pack/plugins/maps/public/layers/styles/heatmap/components/__snapshots__/heatmap_style_editor.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/__snapshots__/heatmap_style_editor.test.js.snap rename to x-pack/plugins/maps/public/layers/styles/heatmap/components/__snapshots__/heatmap_style_editor.test.js.snap diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/heatmap_constants.js b/x-pack/plugins/maps/public/layers/styles/heatmap/components/heatmap_constants.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/heatmap_constants.js rename to x-pack/plugins/maps/public/layers/styles/heatmap/components/heatmap_constants.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/heatmap_style_editor.js b/x-pack/plugins/maps/public/layers/styles/heatmap/components/heatmap_style_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/heatmap_style_editor.js rename to x-pack/plugins/maps/public/layers/styles/heatmap/components/heatmap_style_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/heatmap_style_editor.test.js b/x-pack/plugins/maps/public/layers/styles/heatmap/components/heatmap_style_editor.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/heatmap_style_editor.test.js rename to x-pack/plugins/maps/public/layers/styles/heatmap/components/heatmap_style_editor.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/legend/heatmap_legend.js b/x-pack/plugins/maps/public/layers/styles/heatmap/components/legend/heatmap_legend.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/legend/heatmap_legend.js rename to x-pack/plugins/maps/public/layers/styles/heatmap/components/legend/heatmap_legend.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/heatmap/heatmap_style.js b/x-pack/plugins/maps/public/layers/styles/heatmap/heatmap_style.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/heatmap/heatmap_style.js rename to x-pack/plugins/maps/public/layers/styles/heatmap/heatmap_style.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/_style_prop_editor.scss b/x-pack/plugins/maps/public/layers/styles/vector/components/_style_prop_editor.scss similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/_style_prop_editor.scss rename to x-pack/plugins/maps/public/layers/styles/vector/components/_style_prop_editor.scss diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/_color_stops.scss b/x-pack/plugins/maps/public/layers/styles/vector/components/color/_color_stops.scss similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/_color_stops.scss rename to x-pack/plugins/maps/public/layers/styles/vector/components/color/_color_stops.scss diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js b/x-pack/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops.js b/x-pack/plugins/maps/public/layers/styles/vector/components/color/color_stops.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/color/color_stops.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_categorical.js b/x-pack/plugins/maps/public/layers/styles/vector/components/color/color_stops_categorical.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_categorical.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/color/color_stops_categorical.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_ordinal.js b/x-pack/plugins/maps/public/layers/styles/vector/components/color/color_stops_ordinal.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_ordinal.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/color/color_stops_ordinal.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_utils.js b/x-pack/plugins/maps/public/layers/styles/vector/components/color/color_stops_utils.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_utils.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/color/color_stops_utils.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js b/x-pack/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/mb_validated_color_picker.tsx b/x-pack/plugins/maps/public/layers/styles/vector/components/color/mb_validated_color_picker.tsx similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/mb_validated_color_picker.tsx rename to x-pack/plugins/maps/public/layers/styles/vector/components/color/mb_validated_color_picker.tsx diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/static_color_form.js b/x-pack/plugins/maps/public/layers/styles/vector/components/color/static_color_form.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/static_color_form.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/color/static_color_form.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/vector_style_color_editor.js b/x-pack/plugins/maps/public/layers/styles/vector/components/color/vector_style_color_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/vector_style_color_editor.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/color/vector_style_color_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_meta/categorical_field_meta_popover.tsx b/x-pack/plugins/maps/public/layers/styles/vector/components/field_meta/categorical_field_meta_popover.tsx similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_meta/categorical_field_meta_popover.tsx rename to x-pack/plugins/maps/public/layers/styles/vector/components/field_meta/categorical_field_meta_popover.tsx diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_meta/field_meta_popover.tsx b/x-pack/plugins/maps/public/layers/styles/vector/components/field_meta/field_meta_popover.tsx similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_meta/field_meta_popover.tsx rename to x-pack/plugins/maps/public/layers/styles/vector/components/field_meta/field_meta_popover.tsx diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_meta/ordinal_field_meta_popover.tsx b/x-pack/plugins/maps/public/layers/styles/vector/components/field_meta/ordinal_field_meta_popover.tsx similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_meta/ordinal_field_meta_popover.tsx rename to x-pack/plugins/maps/public/layers/styles/vector/components/field_meta/ordinal_field_meta_popover.tsx diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_select.js b/x-pack/plugins/maps/public/layers/styles/vector/components/field_select.js similarity index 97% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_select.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/field_select.js index 2f5de507657a5..ed2e7a4eab7ec 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_select.js +++ b/x-pack/plugins/maps/public/layers/styles/vector/components/field_select.js @@ -10,7 +10,7 @@ import React from 'react'; import { EuiComboBox, EuiHighlight, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { FIELD_ORIGIN } from '../../../../../common/constants'; import { i18n } from '@kbn/i18n'; -import { FieldIcon } from '../../../../../../../../../src/plugins/kibana_react/public'; +import { FieldIcon } from '../../../../../../../../src/plugins/kibana_react/public'; function renderOption(option, searchValue, contentClassName) { return ( diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/get_vector_style_label.js b/x-pack/plugins/maps/public/layers/styles/vector/components/get_vector_style_label.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/get_vector_style_label.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/get_vector_style_label.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_form.js b/x-pack/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_form.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_form.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_form.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/static_label_form.js b/x-pack/plugins/maps/public/layers/styles/vector/components/label/static_label_form.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/static_label_form.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/label/static_label_form.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_border_size_editor.js b/x-pack/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_border_size_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_border_size_editor.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_border_size_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_editor.js b/x-pack/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_editor.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/__snapshots__/vector_icon.test.js.snap b/x-pack/plugins/maps/public/layers/styles/vector/components/legend/__snapshots__/vector_icon.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/__snapshots__/vector_icon.test.js.snap rename to x-pack/plugins/maps/public/layers/styles/vector/components/legend/__snapshots__/vector_icon.test.js.snap diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/category.js b/x-pack/plugins/maps/public/layers/styles/vector/components/legend/category.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/category.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/legend/category.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/circle_icon.js b/x-pack/plugins/maps/public/layers/styles/vector/components/legend/circle_icon.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/circle_icon.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/legend/circle_icon.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/extract_color_from_style_property.js b/x-pack/plugins/maps/public/layers/styles/vector/components/legend/extract_color_from_style_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/extract_color_from_style_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/legend/extract_color_from_style_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/line_icon.js b/x-pack/plugins/maps/public/layers/styles/vector/components/legend/line_icon.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/line_icon.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/legend/line_icon.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/polygon_icon.js b/x-pack/plugins/maps/public/layers/styles/vector/components/legend/polygon_icon.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/polygon_icon.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/legend/polygon_icon.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/symbol_icon.js b/x-pack/plugins/maps/public/layers/styles/vector/components/legend/symbol_icon.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/symbol_icon.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/legend/symbol_icon.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.js b/x-pack/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.test.js b/x-pack/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.test.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js b/x-pack/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_form.js b/x-pack/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_form.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_form.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_form.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/orientation_editor.js b/x-pack/plugins/maps/public/layers/styles/vector/components/orientation/orientation_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/orientation_editor.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/orientation/orientation_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_form.js b/x-pack/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_form.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_form.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_form.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_form.js b/x-pack/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_form.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_form.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_form.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js b/x-pack/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js similarity index 92% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js index 5de7b462136e1..ec847e2a5384e 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js +++ b/x-pack/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js @@ -6,7 +6,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { ValidatedDualRange } from '../../../../../../../../../../src/plugins/kibana_react/public'; +import { ValidatedDualRange } from '../../../../../../../../../src/plugins/kibana_react/public'; import { MIN_SIZE, MAX_SIZE } from '../../vector_style_defaults'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/static_size_form.js b/x-pack/plugins/maps/public/layers/styles/vector/components/size/static_size_form.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/static_size_form.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/size/static_size_form.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/vector_style_size_editor.js b/x-pack/plugins/maps/public/layers/styles/vector/components/size/vector_style_size_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/vector_style_size_editor.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/size/vector_style_size_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/stop_input.js b/x-pack/plugins/maps/public/layers/styles/vector/components/stop_input.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/stop_input.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/stop_input.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_map_select.js b/x-pack/plugins/maps/public/layers/styles/vector/components/style_map_select.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_map_select.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/style_map_select.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_option_shapes.js b/x-pack/plugins/maps/public/layers/styles/vector/components/style_option_shapes.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_option_shapes.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/style_option_shapes.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_prop_editor.js b/x-pack/plugins/maps/public/layers/styles/vector/components/style_prop_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_prop_editor.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/style_prop_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/__snapshots__/icon_select.test.js.snap b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/__snapshots__/icon_select.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/__snapshots__/icon_select.test.js.snap rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/__snapshots__/icon_select.test.js.snap diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/_icon_select.scss b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/_icon_select.scss similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/_icon_select.scss rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/_icon_select.scss diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/dynamic_icon_form.js b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/dynamic_icon_form.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/dynamic_icon_form.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/dynamic_icon_form.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_map_select.js b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/icon_map_select.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_map_select.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/icon_map_select.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.js b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.test.js b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.test.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.js b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.test.js b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.test.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/static_icon_form.js b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/static_icon_form.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/static_icon_form.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/static_icon_form.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_icon_editor.js b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_icon_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_icon_editor.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_icon_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_symbolize_as_editor.js b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_symbolize_as_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_symbolize_as_editor.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_symbolize_as_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js b/x-pack/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap b/x-pack/plugins/maps/public/layers/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap rename to x-pack/plugins/maps/public/layers/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/categorical_legend.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/components/categorical_legend.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/categorical_legend.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/components/categorical_legend.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/ordinal_legend.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/components/ordinal_legend.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/ordinal_legend.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/components/ordinal_legend.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_icon_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_icon_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_icon_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_icon_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_orientation_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_orientation_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_orientation_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_orientation_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.d.ts b/x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.d.ts rename to x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_text_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_text_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_text_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_text_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/label_border_size_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/label_border_size_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/label_border_size_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/label_border_size_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_color_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/static_color_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_color_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/static_color_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_icon_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/static_icon_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_icon_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/static_icon_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_orientation_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/static_orientation_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_orientation_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/static_orientation_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_size_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/static_size_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_size_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/static_size_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_style_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/static_style_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_style_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/static_style_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_text_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/static_text_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_text_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/static_text_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/style_property.ts b/x-pack/plugins/maps/public/layers/styles/vector/properties/style_property.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/style_property.ts rename to x-pack/plugins/maps/public/layers/styles/vector/properties/style_property.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/symbolize_as_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/symbolize_as_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/symbolize_as_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/symbolize_as_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_meta.ts b/x-pack/plugins/maps/public/layers/styles/vector/style_meta.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/style_meta.ts rename to x-pack/plugins/maps/public/layers/styles/vector/style_meta.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.js b/x-pack/plugins/maps/public/layers/styles/vector/style_util.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.js rename to x-pack/plugins/maps/public/layers/styles/vector/style_util.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.test.js b/x-pack/plugins/maps/public/layers/styles/vector/style_util.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.test.js rename to x-pack/plugins/maps/public/layers/styles/vector/style_util.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/symbol_utils.js b/x-pack/plugins/maps/public/layers/styles/vector/symbol_utils.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/symbol_utils.js rename to x-pack/plugins/maps/public/layers/styles/vector/symbol_utils.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/symbol_utils.test.js b/x-pack/plugins/maps/public/layers/styles/vector/symbol_utils.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/symbol_utils.test.js rename to x-pack/plugins/maps/public/layers/styles/vector/symbol_utils.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.d.ts b/x-pack/plugins/maps/public/layers/styles/vector/vector_style.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.d.ts rename to x-pack/plugins/maps/public/layers/styles/vector/vector_style.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js b/x-pack/plugins/maps/public/layers/styles/vector/vector_style.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js rename to x-pack/plugins/maps/public/layers/styles/vector/vector_style.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.test.js b/x-pack/plugins/maps/public/layers/styles/vector/vector_style.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.test.js rename to x-pack/plugins/maps/public/layers/styles/vector/vector_style.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.ts b/x-pack/plugins/maps/public/layers/styles/vector/vector_style_defaults.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.ts rename to x-pack/plugins/maps/public/layers/styles/vector/vector_style_defaults.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/tile_layer.d.ts b/x-pack/plugins/maps/public/layers/tile_layer.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/tile_layer.d.ts rename to x-pack/plugins/maps/public/layers/tile_layer.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/tile_layer.js b/x-pack/plugins/maps/public/layers/tile_layer.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/tile_layer.js rename to x-pack/plugins/maps/public/layers/tile_layer.js diff --git a/x-pack/legacy/plugins/maps/public/layers/tile_layer.test.ts b/x-pack/plugins/maps/public/layers/tile_layer.test.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/tile_layer.test.ts rename to x-pack/plugins/maps/public/layers/tile_layer.test.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/tooltips/es_agg_tooltip_property.ts b/x-pack/plugins/maps/public/layers/tooltips/es_agg_tooltip_property.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/tooltips/es_agg_tooltip_property.ts rename to x-pack/plugins/maps/public/layers/tooltips/es_agg_tooltip_property.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/tooltips/es_tooltip_property.ts b/x-pack/plugins/maps/public/layers/tooltips/es_tooltip_property.ts similarity index 95% rename from x-pack/legacy/plugins/maps/public/layers/tooltips/es_tooltip_property.ts rename to x-pack/plugins/maps/public/layers/tooltips/es_tooltip_property.ts index 8fd7e173435ce..5c35009881920 100644 --- a/x-pack/legacy/plugins/maps/public/layers/tooltips/es_tooltip_property.ts +++ b/x-pack/plugins/maps/public/layers/tooltips/es_tooltip_property.ts @@ -7,8 +7,8 @@ import _ from 'lodash'; import { ITooltipProperty } from './tooltip_property'; import { IField } from '../fields/field'; -import { esFilters, IFieldType, IndexPattern } from '../../../../../../../src/plugins/data/public'; -import { PhraseFilter } from '../../../../../../../src/plugins/data/public'; +import { esFilters, IFieldType, IndexPattern } from '../../../../../../src/plugins/data/public'; +import { PhraseFilter } from '../../../../../../src/plugins/data/public'; export class ESTooltipProperty implements ITooltipProperty { private readonly _tooltipProperty: ITooltipProperty; diff --git a/x-pack/legacy/plugins/maps/public/layers/tooltips/join_tooltip_property.ts b/x-pack/plugins/maps/public/layers/tooltips/join_tooltip_property.ts similarity index 96% rename from x-pack/legacy/plugins/maps/public/layers/tooltips/join_tooltip_property.ts rename to x-pack/plugins/maps/public/layers/tooltips/join_tooltip_property.ts index 02f0920ce3c61..4af236f6e9e36 100644 --- a/x-pack/legacy/plugins/maps/public/layers/tooltips/join_tooltip_property.ts +++ b/x-pack/plugins/maps/public/layers/tooltips/join_tooltip_property.ts @@ -6,7 +6,7 @@ import { ITooltipProperty } from './tooltip_property'; import { IJoin } from '../joins/join'; -import { PhraseFilter } from '../../../../../../../src/plugins/data/public'; +import { PhraseFilter } from '../../../../../../src/plugins/data/public'; export class JoinTooltipProperty implements ITooltipProperty { private readonly _tooltipProperty: ITooltipProperty; diff --git a/x-pack/legacy/plugins/maps/public/layers/tooltips/tooltip_property.ts b/x-pack/plugins/maps/public/layers/tooltips/tooltip_property.ts similarity index 92% rename from x-pack/legacy/plugins/maps/public/layers/tooltips/tooltip_property.ts rename to x-pack/plugins/maps/public/layers/tooltips/tooltip_property.ts index 46e27bbd770a1..7d680dfe9cae0 100644 --- a/x-pack/legacy/plugins/maps/public/layers/tooltips/tooltip_property.ts +++ b/x-pack/plugins/maps/public/layers/tooltips/tooltip_property.ts @@ -5,8 +5,8 @@ */ import _ from 'lodash'; -import { PhraseFilter } from '../../../../../../../src/plugins/data/public'; -import { TooltipFeature } from '../../../../../../plugins/maps/common/descriptor_types'; +import { PhraseFilter } from '../../../../../../src/plugins/data/public'; +import { TooltipFeature } from '../../../../../plugins/maps/common/descriptor_types'; export interface ITooltipProperty { getPropertyKey(): string; diff --git a/x-pack/legacy/plugins/maps/public/layers/util/assign_feature_ids.test.ts b/x-pack/plugins/maps/public/layers/util/assign_feature_ids.test.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/util/assign_feature_ids.test.ts rename to x-pack/plugins/maps/public/layers/util/assign_feature_ids.test.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/util/assign_feature_ids.ts b/x-pack/plugins/maps/public/layers/util/assign_feature_ids.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/util/assign_feature_ids.ts rename to x-pack/plugins/maps/public/layers/util/assign_feature_ids.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.test.js b/x-pack/plugins/maps/public/layers/util/can_skip_fetch.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.test.js rename to x-pack/plugins/maps/public/layers/util/can_skip_fetch.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.ts b/x-pack/plugins/maps/public/layers/util/can_skip_fetch.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.ts rename to x-pack/plugins/maps/public/layers/util/can_skip_fetch.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/util/data_request.ts b/x-pack/plugins/maps/public/layers/util/data_request.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/util/data_request.ts rename to x-pack/plugins/maps/public/layers/util/data_request.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/util/es_agg_utils.test.ts b/x-pack/plugins/maps/public/layers/util/es_agg_utils.test.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/util/es_agg_utils.test.ts rename to x-pack/plugins/maps/public/layers/util/es_agg_utils.test.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/util/es_agg_utils.ts b/x-pack/plugins/maps/public/layers/util/es_agg_utils.ts similarity index 95% rename from x-pack/legacy/plugins/maps/public/layers/util/es_agg_utils.ts rename to x-pack/plugins/maps/public/layers/util/es_agg_utils.ts index 9d4f24f80d6cd..329a2a6fc64fb 100644 --- a/x-pack/legacy/plugins/maps/public/layers/util/es_agg_utils.ts +++ b/x-pack/plugins/maps/public/layers/util/es_agg_utils.ts @@ -5,7 +5,7 @@ */ import { i18n } from '@kbn/i18n'; import _ from 'lodash'; -import { IndexPattern, IFieldType } from '../../../../../../../src/plugins/data/public'; +import { IndexPattern, IFieldType } from '../../../../../../src/plugins/data/public'; import { TOP_TERM_PERCENTAGE_SUFFIX } from '../../../common/constants'; export function getField(indexPattern: IndexPattern, fieldName: string) { diff --git a/x-pack/legacy/plugins/maps/public/layers/util/is_metric_countable.ts b/x-pack/plugins/maps/public/layers/util/is_metric_countable.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/util/is_metric_countable.ts rename to x-pack/plugins/maps/public/layers/util/is_metric_countable.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/util/is_refresh_only_query.ts b/x-pack/plugins/maps/public/layers/util/is_refresh_only_query.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/util/is_refresh_only_query.ts rename to x-pack/plugins/maps/public/layers/util/is_refresh_only_query.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/util/mb_filter_expressions.ts b/x-pack/plugins/maps/public/layers/util/mb_filter_expressions.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/util/mb_filter_expressions.ts rename to x-pack/plugins/maps/public/layers/util/mb_filter_expressions.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/vector_layer.d.ts b/x-pack/plugins/maps/public/layers/vector_layer.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/vector_layer.d.ts rename to x-pack/plugins/maps/public/layers/vector_layer.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js b/x-pack/plugins/maps/public/layers/vector_layer.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/vector_layer.js rename to x-pack/plugins/maps/public/layers/vector_layer.js diff --git a/x-pack/legacy/plugins/maps/public/layers/vector_tile_layer.js b/x-pack/plugins/maps/public/layers/vector_tile_layer.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/vector_tile_layer.js rename to x-pack/plugins/maps/public/layers/vector_tile_layer.js diff --git a/x-pack/legacy/plugins/maps/public/meta.js b/x-pack/plugins/maps/public/meta.js similarity index 76% rename from x-pack/legacy/plugins/maps/public/meta.js rename to x-pack/plugins/maps/public/meta.js index 4d81785ff7a0a..d4612554cf00b 100644 --- a/x-pack/legacy/plugins/maps/public/meta.js +++ b/x-pack/plugins/maps/public/meta.js @@ -11,20 +11,19 @@ import { EMS_GLYPHS_PATH, EMS_APP_NAME, } from '../common/constants'; -import chrome from 'ui/chrome'; import { i18n } from '@kbn/i18n'; import { EMSClient } from '@elastic/ems-client'; -import { getLicenseId } from './kibana_services'; +import { getInjectedVarFunc, getLicenseId } from './kibana_services'; import fetch from 'node-fetch'; const GIS_API_RELATIVE = `../${GIS_API_PATH}`; export function getKibanaRegionList() { - return chrome.getInjected('regionmapLayers'); + return getInjectedVarFunc()('regionmapLayers'); } export function getKibanaTileMap() { - return chrome.getInjected('tilemap'); + return getInjectedVarFunc()('tilemap'); } function relativeToAbsolute(url) { @@ -41,27 +40,27 @@ let emsClient = null; let latestLicenseId = null; export function getEMSClient() { if (!emsClient) { - const isEmsEnabled = chrome.getInjected('isEmsEnabled', true); + const isEmsEnabled = getInjectedVarFunc()('isEmsEnabled', true); if (isEmsEnabled) { - const proxyElasticMapsServiceInMaps = chrome.getInjected( + const proxyElasticMapsServiceInMaps = getInjectedVarFunc()( 'proxyElasticMapsServiceInMaps', false ); const proxyPath = ''; const tileApiUrl = proxyElasticMapsServiceInMaps ? relativeToAbsolute(`${GIS_API_RELATIVE}/${EMS_TILES_CATALOGUE_PATH}`) - : chrome.getInjected('emsTileApiUrl'); + : getInjectedVarFunc()('emsTileApiUrl'); const fileApiUrl = proxyElasticMapsServiceInMaps ? relativeToAbsolute(`${GIS_API_RELATIVE}/${EMS_FILES_CATALOGUE_PATH}`) - : chrome.getInjected('emsFileApiUrl'); + : getInjectedVarFunc()('emsFileApiUrl'); emsClient = new EMSClient({ language: i18n.getLocale(), - appVersion: chrome.getInjected('kbnPkgVersion'), + appVersion: getInjectedVarFunc()('kbnPkgVersion'), appName: EMS_APP_NAME, tileApiUrl, fileApiUrl, - landingPageUrl: chrome.getInjected('emsLandingPageUrl'), + landingPageUrl: getInjectedVarFunc()('emsLandingPageUrl'), fetchFunction: fetchFunction, //import this from client-side, so the right instance is returned (bootstrapped from common/* would not work proxyPath, }); @@ -87,13 +86,13 @@ export function getEMSClient() { } export function getGlyphUrl() { - if (!chrome.getInjected('isEmsEnabled', true)) { + if (!getInjectedVarFunc()('isEmsEnabled', true)) { return ''; } - return chrome.getInjected('proxyElasticMapsServiceInMaps', false) + return getInjectedVarFunc()('proxyElasticMapsServiceInMaps', false) ? relativeToAbsolute(`../${GIS_API_PATH}/${EMS_TILES_CATALOGUE_PATH}/${EMS_GLYPHS_PATH}`) + `/{fontstack}/{range}` - : chrome.getInjected('emsFontLibraryUrl', true); + : getInjectedVarFunc()('emsFontLibraryUrl', true); } export function isRetina() { diff --git a/x-pack/legacy/plugins/maps/public/meta.test.js b/x-pack/plugins/maps/public/meta.test.js similarity index 57% rename from x-pack/legacy/plugins/maps/public/meta.test.js rename to x-pack/plugins/maps/public/meta.test.js index 64dd73fe109ff..d83f2adb35ef7 100644 --- a/x-pack/legacy/plugins/maps/public/meta.test.js +++ b/x-pack/plugins/maps/public/meta.test.js @@ -9,39 +9,24 @@ import { getEMSClient } from './meta'; jest.mock('@elastic/ems-client'); -jest.mock('ui/chrome', () => ({ - getBasePath: () => { - return ''; - }, - getInjected(key) { - if (key === 'proxyElasticMapsServiceInMaps') { - return false; - } else if (key === 'isEmsEnabled') { - return true; - } else if (key === 'emsFileApiUrl') { - return 'https://file-api'; - } else if (key === 'emsTileApiUrl') { - return 'https://tile-api'; - } - }, - getUiSettingsClient: () => { - return { - get: () => { - return ''; - }, +describe('default use without proxy', () => { + beforeEach(() => { + require('./kibana_services').getInjectedVarFunc = () => key => { + if (key === 'proxyElasticMapsServiceInMaps') { + return false; + } else if (key === 'isEmsEnabled') { + return true; + } else if (key === 'emsFileApiUrl') { + return 'https://file-api'; + } else if (key === 'emsTileApiUrl') { + return 'https://tile-api'; + } }; - }, -})); - -jest.mock('./kibana_services', () => { - return { - getLicenseId() { + require('./kibana_services').getLicenseId = () => { return 'foobarlicenseid'; - }, - }; -}); + }; + }); -describe('default use without proxy', () => { it('should construct EMSClient with absolute file and tile API urls', async () => { getEMSClient(); const mockEmsClientCall = EMSClient.mock.calls[0]; diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts index 506b0c426f0fa..9437c2512ded4 100644 --- a/x-pack/plugins/maps/public/plugin.ts +++ b/x-pack/plugins/maps/public/plugin.ts @@ -8,6 +8,20 @@ import { Plugin, CoreSetup, CoreStart } from 'src/core/public'; import { Setup as InspectorSetupContract } from 'src/plugins/inspector/public'; // @ts-ignore import { MapView } from './inspector/views/map_view'; +import { + setAutocompleteService, + setFileUpload, + setHttp, + setIndexPatternSelect, + setIndexPatternService, + setInjectedVarFunc, + setInspector, + setLicenseId, + setTimeFilter, + setToasts, + setUiSettings, + // @ts-ignore +} from './kibana_services'; export interface MapsPluginSetupDependencies { inspector: InspectorSetupContract; @@ -15,6 +29,29 @@ export interface MapsPluginSetupDependencies { // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface MapsPluginStartDependencies {} +export const bindSetupCoreAndPlugins = (core: CoreSetup, plugins: any) => { + const { licensing } = plugins; + const { injectedMetadata, http } = core; + if (licensing) { + licensing.license$.subscribe(({ uid }: { uid: string }) => setLicenseId(uid)); + } + setInjectedVarFunc(injectedMetadata.getInjectedVar); + setHttp(http); + setUiSettings(core.uiSettings); + setInjectedVarFunc(core.injectedMetadata.getInjectedVar); + setToasts(core.notifications.toasts); +}; + +export const bindStartCoreAndPlugins = (core: CoreStart, plugins: any) => { + const { file_upload, data, inspector } = plugins; + setInspector(inspector); + setFileUpload(file_upload); + setIndexPatternSelect(data.ui.IndexPatternSelect); + setTimeFilter(data.query.timefilter.timefilter); + setIndexPatternService(data.indexPatterns); + setAutocompleteService(data.autocomplete); +}; + /** * These are the interfaces with your public contracts. You should export these * for other plugins to use in _their_ `SetupDeps`/`StartDeps` interfaces. From 809ec9764929aca307e769c4fc31a8941cd33fef Mon Sep 17 00:00:00 2001 From: Kaarina Tungseth Date: Mon, 6 Apr 2020 15:37:45 -0500 Subject: [PATCH 18/36] [DOCS] Removed references to left (#60807) * [DOCS] Removed references to left * Fixed broken build --- docs/apm/transactions.asciidoc | 4 +--- docs/canvas/canvas-elements.asciidoc | 4 +++- docs/canvas/canvas-present-workpad.asciidoc | 4 ++-- docs/canvas/canvas-share-workpad.asciidoc | 8 ++++---- .../searchprofiler/getting-started.asciidoc | 12 ++++++------ docs/discover/document-data.asciidoc | 2 +- docs/getting-started/tutorial-visualizing.asciidoc | 2 +- docs/infrastructure/view-metrics.asciidoc | 8 -------- docs/management/managing-licenses.asciidoc | 3 +-- docs/spaces/index.asciidoc | 4 ++-- docs/user/dashboard.asciidoc | 2 +- docs/user/discover.asciidoc | 4 ++-- docs/visualize/lens.asciidoc | 12 ++++-------- 13 files changed, 28 insertions(+), 41 deletions(-) diff --git a/docs/apm/transactions.asciidoc b/docs/apm/transactions.asciidoc index 536ab2ec29c80..5c92afa55109d 100644 --- a/docs/apm/transactions.asciidoc +++ b/docs/apm/transactions.asciidoc @@ -103,9 +103,7 @@ The number of requests per bucket is displayed when hovering over the graph, and [role="screenshot"] image::apm/images/apm-transaction-duration-dist.png[Example view of transactions duration distribution graph] -Most of the requests fall into buckets on the left side of the graph, -with a long tail of smaller buckets to the right. -This is a typical distribution, and indicates most of our requests were served quickly - awesome! +This graph shows a typical distribution, and indicates most of our requests were served quickly - awesome! It's the requests on the right, the ones taking longer than average, that we probably want to focus on. When you select one of these buckets, you're presented with up to ten trace samples. diff --git a/docs/canvas/canvas-elements.asciidoc b/docs/canvas/canvas-elements.asciidoc index 163579d5763b2..a25460a20eb50 100644 --- a/docs/canvas/canvas-elements.asciidoc +++ b/docs/canvas/canvas-elements.asciidoc @@ -138,7 +138,9 @@ To apply CSS overrides: . Next to *Element style*, click *+*, then select *CSS*. -. Enter the *CSS*. For example, to center the Markdown element, enter: +. Enter the *CSS*. ++ +For example, to center the Markdown element, enter: + [source,text] -------------------------------------------------- diff --git a/docs/canvas/canvas-present-workpad.asciidoc b/docs/canvas/canvas-present-workpad.asciidoc index 486686cd857b5..9cd4ecc9519e1 100644 --- a/docs/canvas/canvas-present-workpad.asciidoc +++ b/docs/canvas/canvas-present-workpad.asciidoc @@ -8,7 +8,7 @@ When you are ready to present your workpad, use and enable the presentation opti [[view-fullscreen-mode]] ==== View your workpad in fullscreen mode -In the upper left corner, click the *Enter fullscreen mode* icon. +Click the *Enter fullscreen mode* icon. [role="screenshot"] image::images/canvas-fullscreen.png[Fullscreen mode] @@ -19,7 +19,7 @@ image::images/canvas-fullscreen.png[Fullscreen mode] Automatically cycle through your workpads pages in fullscreen mode. -. In the upper left corner, click the *Control settings* icon. +. Click the *Control settings* icon. . Under *Change cycling interval*, select the interval you want to use. + diff --git a/docs/canvas/canvas-share-workpad.asciidoc b/docs/canvas/canvas-share-workpad.asciidoc index dbba12865b8ca..ee29926914ad6 100644 --- a/docs/canvas/canvas-share-workpad.asciidoc +++ b/docs/canvas/canvas-share-workpad.asciidoc @@ -10,7 +10,7 @@ When you've finished your workpad, you can share it outside of {kib}. Create a JSON file of your workpad that you can export outside of {kib}. -. From your workpad, click the *Share workpad* icon in the upper left corner. +. From your workpad, click the *Share workpad* icon. . Select *Download as JSON*. + @@ -27,7 +27,7 @@ If you have a license that supports the {report-features}, you can create a PDF For more information, refer to <>. -. From your workpad, click the *Share workpad* icon in the upper left corner, then select *PDF reports*. +. From your workpad, click the *Share workpad* icon, then select *PDF reports*. . Click *Generate PDF*. + @@ -42,7 +42,7 @@ If you have a license that supports the {report-features}, you can create a POST For more information, refer to <>. -. From your workpad, click the *Share workpad* icon in the upper left corner, then select *PDF reports*. +. From your workpad, click the *Share workpad* icon, then select *PDF reports*. . Click *Copy POST URL*. + @@ -55,7 +55,7 @@ image::images/canvas-create-URL.gif[Create POST URL] beta[] Canvas allows you to create _shareables_, which are workpads that you download and securely share on any website. To customize the behavior of the workpad on your website, you can choose to autoplay the pages or hide the workpad toolbar. -. From your workpad, click the *Share this workpad* icon in the upper left corner, then select *Share on a website*. +. From your workpad, click the *Share this workpad* icon, then select *Share on a website*. . On the *Share on a website* pane, follow the instructions. diff --git a/docs/dev-tools/searchprofiler/getting-started.asciidoc b/docs/dev-tools/searchprofiler/getting-started.asciidoc index 2360e4c28ff15..4a87d4b84b783 100644 --- a/docs/dev-tools/searchprofiler/getting-started.asciidoc +++ b/docs/dev-tools/searchprofiler/getting-started.asciidoc @@ -3,10 +3,10 @@ === Getting Started The {searchprofiler} is automatically enabled in {kib}. Go to *Dev Tools > Search Profiler* -to get started. +to get started. {searchprofiler} displays the names of the indices searched, the shards in each index, -and how long it took for the query to complete. To try it out, replace the default `match_all` query +and how long it took for the query to complete. To try it out, replace the default `match_all` query with the query you want to profile and click *Profile*. The following example shows the results of profiling the `match_all` query. @@ -29,8 +29,8 @@ While the Cumulative Time metric is useful for comparing the performance of your indices and shards, it doesn't necessarily represent the actual physical query times. ==== -You can select the name of the shard and then click *View details* to see more profiling information, -including details about the query component(s) that ran on the shard, as well as the timing +You can select the name of the shard and then click *View details* to see more profiling information, +including details about the query component(s) that ran on the shard, as well as the timing breakdown of low-level Lucene methods. For more information, see {ref}/search-profile.html#profiling-queries[Profiling queries]. [float] @@ -40,10 +40,10 @@ By default, all queries executed by the {searchprofiler} are sent to `GET /_search`. It searches across your entire cluster (all indices, all types). If you need to query a specific index or type (or several), you can use the Index -and Type filters at the top left. +and Type filters. In the following example, the query is executed against the indices `test` and `kibana_1` and the type `my_type`. This is equivalent making a request to `GET /test,kibana_1/my_type/_search`. [role="screenshot"] -image::dev-tools/searchprofiler/images/filter.png["Filtering by index and type"] \ No newline at end of file +image::dev-tools/searchprofiler/images/filter.png["Filtering by index and type"] diff --git a/docs/discover/document-data.asciidoc b/docs/discover/document-data.asciidoc index 6e9218d66c115..477c2ec90e95c 100644 --- a/docs/discover/document-data.asciidoc +++ b/docs/discover/document-data.asciidoc @@ -26,7 +26,7 @@ and click image:images/sort-icon.png[]. The first click sorts by ascending order, the second click sorts by descending order, and the third click removes the field from the sorted fields. -Move a field column:: Hover over the column header and click the move left (<<) or move right icon (>>). +Move a field column:: Hover over the column header and click the (<<) or (>>) icons. Remove a field column :: Hover over the list of *Specified fields* and then click *remove*. Or, use the (x) control in the column header. diff --git a/docs/getting-started/tutorial-visualizing.asciidoc b/docs/getting-started/tutorial-visualizing.asciidoc index a16343aa4850a..acd4d6d908fd4 100644 --- a/docs/getting-started/tutorial-visualizing.asciidoc +++ b/docs/getting-started/tutorial-visualizing.asciidoc @@ -180,5 +180,5 @@ The map now looks like this: image::images/tutorial-visualize-map-2.png[] . Navigate the map by clicking and dragging. Use the controls -on the left to zoom the map and set filters. +to zoom the map and set filters. . *Save* this map with the name `Map Example`. diff --git a/docs/infrastructure/view-metrics.asciidoc b/docs/infrastructure/view-metrics.asciidoc index bbb981acc3ad6..1bd64dde76ee1 100644 --- a/docs/infrastructure/view-metrics.asciidoc +++ b/docs/infrastructure/view-metrics.asciidoc @@ -30,11 +30,3 @@ For complete control over the start and end times, click the start time or end t === Refresh the metrics You can click *Refresh* to manually refresh the metrics. - -[float] -[[infra-view-go-to-chart]] -=== Go to a specific chart - -The charts available for this component are shown in a list on the left of the page. Click a chart in the list to quickly go to that chart. - - diff --git a/docs/management/managing-licenses.asciidoc b/docs/management/managing-licenses.asciidoc index 72accdb5fe2aa..a7ed4e942f3f6 100644 --- a/docs/management/managing-licenses.asciidoc +++ b/docs/management/managing-licenses.asciidoc @@ -15,8 +15,7 @@ already activated a trial for 6.0, you cannot start a new trial until 7.0. You can, however, contact `info@elastic.co` to request an extended trial license. -When you activate a new license level, new features appear in the left sidebar -of the *Management* page. +When you activate a new license level, new features appear in *Management*. [role="screenshot"] image::images/management-license.png[] diff --git a/docs/spaces/index.asciidoc b/docs/spaces/index.asciidoc index fb5ef670692dc..990af3a018b1f 100644 --- a/docs/spaces/index.asciidoc +++ b/docs/spaces/index.asciidoc @@ -9,10 +9,10 @@ the dashboards and saved objects that belong to that space. {kib} creates a default space for you. After you create your own spaces, you're asked to choose a space when you log in to Kibana. You can change your -current space at any time by using the menu in the upper left. +current space at any time by using the menu. [role="screenshot"] -image::spaces/images/change-space.png["Change current space"] +image::spaces/images/change-space.png["Change current space menu"] Kibana supports spaces in several ways. You can: diff --git a/docs/user/dashboard.asciidoc b/docs/user/dashboard.asciidoc index 490edb9d26338..a17e46c5b3542 100644 --- a/docs/user/dashboard.asciidoc +++ b/docs/user/dashboard.asciidoc @@ -93,7 +93,7 @@ In *Edit* mode, you can move, resize, customize, and delete panels to suit your * To resize a panel, click the resize control on the lower right and drag to the new dimensions. -* To toggle the use of margins and panel titles, use the *Options* menu in the upper left. +* To toggle the use of margins and panel titles, use the *Options* menu. * To delete a panel, open the panel menu and select *Delete from dashboard.* Deleting a panel from a dashboard does *not* delete the saved visualization or search. diff --git a/docs/user/discover.asciidoc b/docs/user/discover.asciidoc index 7de7d73bf1664..4222ba40debb7 100644 --- a/docs/user/discover.asciidoc +++ b/docs/user/discover.asciidoc @@ -24,7 +24,7 @@ image::images/Discover-Start.png[Discover] === Set up your index pattern The first thing to do in *Discover* is to select an <>, which -defines the data you want to explore and visualize. The current index pattern is in the upper left. +defines the data you want to explore and visualize. If you haven't yet created an index pattern, you can add a <>, which has a pre-built index pattern. @@ -69,7 +69,7 @@ image::images/filter-field.png[height=317] The sortable documents table lists the documents that match your search. By default, the table includes columns for the time field and the document `_source`. -To zero in on a specific field, click *add* next to the field name in the left sidebar. +To zero in on a specific field, click *add* next to the field name. For example, if you add the `currency`, `customer_last_name`, and `day_of_week` fields, the document table includes columns for those three fields. diff --git a/docs/visualize/lens.asciidoc b/docs/visualize/lens.asciidoc index e3f61565453b5..35570ea7ca1dc 100644 --- a/docs/visualize/lens.asciidoc +++ b/docs/visualize/lens.asciidoc @@ -32,7 +32,7 @@ Lens supports the following aggregations: [[drag-drop]] === Drag and drop -The data panel in the left column shows the data fields for the selected time period. When +The panel shows the data fields for the selected time period. When you drag a field from the data panel, Lens highlights where you can drop that field. The first time you drag a data field, you'll see two places highlighted in green: @@ -57,7 +57,7 @@ Lens shows you fields based on the <> you have d {kib}, and the current time range. When you change the index pattern or time filter, the list of fields are updated. -To narrow the list of fields you see in the left panel, you can: +To narrow the list of fields, you can: * Enter the field name in *Search field names*. @@ -100,11 +100,7 @@ still allows you to make the change. Lens allows some customizations of the data for each visualization. -. Change the index pattern. - -.. In the left column, click the index pattern name. - -.. Select the new index pattern. +. Click the index pattern name, then select the new index pattern. + If there is a match, Lens displays the new data. All fields that do not match the index pattern are removed. @@ -147,7 +143,7 @@ Drag and drop your data onto the visualization builder pane. . On the *New Visualization* window, click *Lens*. -. In the left column, select the *kibana_sample_data_ecommerce* index. +. Select the *kibana_sample_data_ecommerce* index. . Click image:images/time-filter-calendar.png[], then click *Last 7 days*. The list of data fields are updated. From e16885c3ad2b3bf6aac3ba0a9dc0548dc5020741 Mon Sep 17 00:00:00 2001 From: Ahmad Bamieh Date: Tue, 7 Apr 2020 00:36:15 +0300 Subject: [PATCH 19/36] [Telemetry] update crypto packages (#62469) * update crypto packages * as type for return value * get default export * add if checks * wrap errors in i18n Co-authored-by: Elastic Machine --- package.json | 2 +- typings/elastic__node_crypto.d.ts | 20 ---------------- .../common/execute_job/decrypt_job_headers.ts | 13 ++++++++--- .../export_types/csv/server/execute_job.ts | 14 ++++++++--- .../server/execute_job.ts | 15 +++++++++++- .../plugins/reporting/server/lib/crypto.ts | 6 ++++- x-pack/legacy/plugins/reporting/types.d.ts | 4 ---- x-pack/package.json | 2 +- .../encrypted_saved_objects_service.test.ts | 7 +++--- .../crypto/encrypted_saved_objects_service.ts | 12 ++++------ x-pack/typings/elastic__node_crypto.d.ts | 7 ------ yarn.lock | 23 ++++++++----------- 12 files changed, 59 insertions(+), 66 deletions(-) delete mode 100644 typings/elastic__node_crypto.d.ts delete mode 100644 x-pack/typings/elastic__node_crypto.d.ts diff --git a/package.json b/package.json index e807cd4d95198..4c5db5321c282 100644 --- a/package.json +++ b/package.json @@ -126,7 +126,7 @@ "@elastic/filesaver": "1.1.2", "@elastic/good": "8.1.1-kibana2", "@elastic/numeral": "2.4.0", - "@elastic/request-crypto": "^1.0.2", + "@elastic/request-crypto": "1.1.2", "@elastic/ui-ace": "0.2.3", "@hapi/good-squeeze": "5.2.1", "@hapi/wreck": "^15.0.2", diff --git a/typings/elastic__node_crypto.d.ts b/typings/elastic__node_crypto.d.ts deleted file mode 100644 index 8d4b47da96b73..0000000000000 --- a/typings/elastic__node_crypto.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -declare module '@elastic/node-crypto'; diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.ts b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.ts index 6f415d7ee5ea9..0436f5d5bc843 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.ts @@ -6,7 +6,7 @@ import { i18n } from '@kbn/i18n'; import { cryptoFactory } from '../../../server/lib/crypto'; -import { CryptoFactory, Logger } from '../../../types'; +import { Logger } from '../../../types'; interface HasEncryptedHeaders { headers?: string; @@ -25,9 +25,16 @@ export const decryptJobHeaders = async < job: JobDocPayloadType; logger: Logger; }): Promise> => { - const crypto: CryptoFactory = cryptoFactory(encryptionKey); try { - const decryptedHeaders: Record = await crypto.decrypt(job.headers); + if (typeof job.headers !== 'string') { + throw new Error( + i18n.translate('xpack.reporting.exportTypes.common.missingJobHeadersErrorMessage', { + defaultMessage: 'Job headers are missing', + }) + ); + } + const crypto = cryptoFactory(encryptionKey); + const decryptedHeaders = (await crypto.decrypt(job.headers)) as Record; return decryptedHeaders; } catch (err) { logger.error(err); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts index d78d8a8a8010d..3a282eb0b2974 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts @@ -43,9 +43,18 @@ export const executeJobFactory: ExecuteJobFactory { - let decryptedHeaders; try { - decryptedHeaders = await crypto.decrypt(headers); + if (typeof headers !== 'string') { + throw new Error( + i18n.translate( + 'xpack.reporting.exportTypes.csv.executeJob.missingJobHeadersErrorMessage', + { + defaultMessage: 'Job headers are missing', + } + ) + ); + } + return await crypto.decrypt(headers); } catch (err) { logger.error(err); throw new Error( @@ -58,7 +67,6 @@ export const executeJobFactory: ExecuteJobFactory; const serializedEncryptedHeaders = job.headers; try { - decryptedHeaders = await crypto.decrypt(serializedEncryptedHeaders); + if (typeof serializedEncryptedHeaders !== 'string') { + throw new Error( + i18n.translate( + 'xpack.reporting.exportTypes.csv_from_savedobject.executeJob.missingJobHeadersErrorMessage', + { + defaultMessage: 'Job headers are missing', + } + ) + ); + } + decryptedHeaders = (await crypto.decrypt(serializedEncryptedHeaders)) as Record< + string, + unknown + >; } catch (err) { jobLogger.error(err); throw new Error( diff --git a/x-pack/legacy/plugins/reporting/server/lib/crypto.ts b/x-pack/legacy/plugins/reporting/server/lib/crypto.ts index 97876529ecfa7..0394c8ed1fbad 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/crypto.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/crypto.ts @@ -6,6 +6,10 @@ import nodeCrypto from '@elastic/node-crypto'; -export function cryptoFactory(encryptionKey: string | undefined) { +export function cryptoFactory(encryptionKey?: string) { + if (typeof encryptionKey !== 'string') { + throw new Error('Encryption Key required.'); + } + return nodeCrypto({ encryptionKey }); } diff --git a/x-pack/legacy/plugins/reporting/types.d.ts b/x-pack/legacy/plugins/reporting/types.d.ts index 09d53278941c9..7334a859005e0 100644 --- a/x-pack/legacy/plugins/reporting/types.d.ts +++ b/x-pack/legacy/plugins/reporting/types.d.ts @@ -116,10 +116,6 @@ export interface ConditionalHeadersConditions { basePath: string; } -export interface CryptoFactory { - decrypt: (headers?: string) => any; -} - export interface IndexPatternSavedObject { attributes: { fieldFormatMap: string; diff --git a/x-pack/package.json b/x-pack/package.json index 24b23256bf18e..b2ec4c3dc3f6f 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -185,7 +185,7 @@ "@elastic/eui": "21.0.1", "@elastic/filesaver": "1.1.2", "@elastic/maki": "6.2.0", - "@elastic/node-crypto": "^1.0.0", + "@elastic/node-crypto": "1.1.1", "@elastic/numeral": "2.4.0", "@kbn/babel-preset": "1.0.0", "@kbn/config-schema": "1.0.0", diff --git a/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.test.ts b/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.test.ts index e1e1a8224aa7b..be33238a26ee7 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.test.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.test.ts @@ -19,9 +19,10 @@ beforeEach(() => { mockAuditLogger = encryptedSavedObjectsAuditLoggerMock.create(); // Call actual `@elastic/node-crypto` by default, but allow to override implementation in tests. - jest - .requireMock('@elastic/node-crypto') - .mockImplementation((...args: any[]) => jest.requireActual('@elastic/node-crypto')(...args)); + jest.requireMock('@elastic/node-crypto').mockImplementation((...args: any[]) => { + const { default: nodeCrypto } = jest.requireActual('@elastic/node-crypto'); + return nodeCrypto(...args); + }); service = new EncryptedSavedObjectsService( 'encryption-key-abc', diff --git a/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.ts b/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.ts index 94c1684529577..eea2b12354d9b 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore -import nodeCrypto from '@elastic/node-crypto'; +import nodeCrypto, { Crypto } from '@elastic/node-crypto'; import stringify from 'json-stable-stringify'; import typeDetect from 'type-detect'; import { Logger } from 'src/core/server'; @@ -49,10 +48,7 @@ export function descriptorToArray(descriptor: SavedObjectDescriptor) { * attributes. */ export class EncryptedSavedObjectsService { - private readonly crypto: Readonly<{ - encrypt(valueToEncrypt: T, aad?: string): Promise; - decrypt(valueToDecrypt: string, aad?: string): Promise; - }>; + private readonly crypto: Readonly; /** * Map of all registered saved object types where the `key` is saved object type and the `value` @@ -229,10 +225,10 @@ export class EncryptedSavedObjectsService { } try { - decryptedAttributes[attributeName] = await this.crypto.decrypt( + decryptedAttributes[attributeName] = (await this.crypto.decrypt( attributeValue, encryptionAAD - ); + )) as string; } catch (err) { this.logger.error(`Failed to decrypt "${attributeName}" attribute: ${err.message || err}`); this.audit.decryptAttributeFailure(attributeName, descriptor); diff --git a/x-pack/typings/elastic__node_crypto.d.ts b/x-pack/typings/elastic__node_crypto.d.ts deleted file mode 100644 index 463b662d5b207..0000000000000 --- a/x-pack/typings/elastic__node_crypto.d.ts +++ /dev/null @@ -1,7 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -declare module '@elastic/node-crypto'; diff --git a/yarn.lock b/yarn.lock index d9edb55a32039..77ab69c715573 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1343,27 +1343,22 @@ resolved "https://registry.yarnpkg.com/@elastic/maki/-/maki-6.2.0.tgz#d0a85aa248bdc14dca44e1f9430c0b670f65e489" integrity sha512-QkmRNpEY4Dy6eqwDimR5X9leMgdPFjdANmpEIwEW1XVUG2U4YtB2BXhDxsnMmNTUrJUjtnjnwgwBUyg0pU0FTg== -"@elastic/node-crypto@^0.1.2": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@elastic/node-crypto/-/node-crypto-0.1.2.tgz#c18ac282f635e88f041cc1555d806e492ca8f3b1" - integrity sha1-wYrCgvY16I8EHMFVXYBuSSyo87E= - -"@elastic/node-crypto@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@elastic/node-crypto/-/node-crypto-1.0.0.tgz#4d325df333fe1319556bb4d54214098ada1171d4" - integrity sha512-bbjbEyILPRTRt0xnda18OttLtlkJBPuXx3CjISUSn9jhWqHoFMzfOaZ73D5jxZE2SaFZUrJYfPpqXP6qqPufAQ== +"@elastic/node-crypto@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@elastic/node-crypto/-/node-crypto-1.1.1.tgz#619b70322c9cce4a7ee5fbf8f678b1baa7f06095" + integrity sha512-F6tIk8Txdqjg8Siv60iAvXzO9ZdQI87K3sS/fh5xd2XaWK+T5ZfqeTvsT7srwG6fr6uCBfuQEJV1KBBl+JpLZA== "@elastic/numeral@2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@elastic/numeral/-/numeral-2.4.0.tgz#883197b7f4bf3c2dd994f53b274769ddfa2bf79a" integrity sha512-uGBKGCNghTgUZPHClji/00v+AKt5nidPTGOIbcT+lbTPVxNB6QPpPLGWtXyrg3QZAxobPM/LAZB1mAqtJeq44Q== -"@elastic/request-crypto@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@elastic/request-crypto/-/request-crypto-1.0.2.tgz#bf27bf009227166f3eeb2b5193a108752335ebd3" - integrity sha512-8FtGYl7LebhmJmEDWiGn3MorvNiGWSYPqhvgRlKXjNakEuLoPBBe0DHxbwLkj08CMLWczXcO2ixqBPY7fEhJpA== +"@elastic/request-crypto@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@elastic/request-crypto/-/request-crypto-1.1.2.tgz#2e323550f546f6286994126d462a9ea480a3bfb1" + integrity sha512-i73wjj1Qi8dGJIy170Z8xyJ760mFNjTbdmcp/nEczqWD0miNW6I5wZ5MNrv7M6CXn2m1wMXiT6qzDYd93Hv1Dw== dependencies: - "@elastic/node-crypto" "^0.1.2" + "@elastic/node-crypto" "1.1.1" "@types/node-jose" "1.1.0" node-jose "1.1.0" From 29c1aad2bb878b58b61ad8b0c88f106cb2f6976d Mon Sep 17 00:00:00 2001 From: liza-mae Date: Mon, 6 Apr 2020 16:48:37 -0600 Subject: [PATCH 20/36] Fix visual tests (#62660) --- test/scripts/jenkins_visual_regression.sh | 2 +- test/scripts/jenkins_xpack_visual_regression.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/scripts/jenkins_visual_regression.sh b/test/scripts/jenkins_visual_regression.sh index 4fdd197147eac..c6fefd45b005d 100755 --- a/test/scripts/jenkins_visual_regression.sh +++ b/test/scripts/jenkins_visual_regression.sh @@ -12,7 +12,7 @@ tar -xzf "$linuxBuild" -C "$installDir" --strip=1 echo " -> running visual regression tests from kibana directory" checks-reporter-with-killswitch "X-Pack visual regression tests" \ - yarn percy exec -t 500 \ + yarn percy exec -t 500 -- -- \ node scripts/functional_tests \ --debug --bail \ --kibana-install-dir "$installDir" \ diff --git a/test/scripts/jenkins_xpack_visual_regression.sh b/test/scripts/jenkins_xpack_visual_regression.sh index 73e92da3bad63..96521ccc8f787 100755 --- a/test/scripts/jenkins_xpack_visual_regression.sh +++ b/test/scripts/jenkins_xpack_visual_regression.sh @@ -14,7 +14,7 @@ tar -xzf "$linuxBuild" -C "$installDir" --strip=1 echo " -> running visual regression tests from x-pack directory" cd "$XPACK_DIR" checks-reporter-with-killswitch "X-Pack visual regression tests" \ - yarn percy exec -t 500 \ + yarn percy exec -t 500 -- -- \ node scripts/functional_tests \ --debug --bail \ --kibana-install-dir "$installDir" \ From f1bd3bdacb263b2754c04000070fe0f79ab5ed72 Mon Sep 17 00:00:00 2001 From: spalger Date: Mon, 6 Apr 2020 17:05:09 -0700 Subject: [PATCH 21/36] Revert "[Monitoring] Cluster state watch to Kibana alerting (#61685)" This reverts commit ab0cc8894a924dda18fc8664cf903fdf7a2d9920. --- .../plugins/monitoring/common/constants.ts | 8 +- .../public/components/alerts/alerts.js | 44 +- .../public/components/alerts/status.test.tsx | 8 +- .../public/components/alerts/status.tsx | 2 +- .../cluster/overview/alerts_panel.js | 81 +-- .../monitoring/public/views/alerts/index.js | 30 +- x-pack/plugins/monitoring/common/constants.ts | 8 +- .../server/alerts/cluster_state.test.ts | 186 ------ .../monitoring/server/alerts/cluster_state.ts | 134 ---- .../plugins/monitoring/server/alerts/enums.ts | 16 - .../server/alerts/license_expiration.test.ts | 572 +++++++++++++----- .../server/alerts/license_expiration.ts | 127 ++-- .../monitoring/server/alerts/types.d.ts | 62 +- .../lib/alerts/cluster_state.lib.test.ts | 70 --- .../server/lib/alerts/cluster_state.lib.ts | 88 --- .../lib/alerts/fetch_cluster_state.test.ts | 39 -- .../server/lib/alerts/fetch_cluster_state.ts | 53 -- .../server/lib/alerts/fetch_clusters.test.ts | 46 +- .../server/lib/alerts/fetch_clusters.ts | 41 +- .../server/lib/alerts/fetch_licenses.test.ts | 67 +- .../server/lib/alerts/fetch_licenses.ts | 16 +- .../server/lib/alerts/fetch_status.test.ts | 122 ---- .../server/lib/alerts/fetch_status.ts | 100 +-- .../lib/alerts/get_prepared_alert.test.ts | 163 ----- .../server/lib/alerts/get_prepared_alert.ts | 87 --- .../lib/alerts/license_expiration.lib.test.ts | 23 +- .../lib/alerts/license_expiration.lib.ts | 56 +- .../lib/cluster/get_clusters_from_request.js | 12 +- x-pack/plugins/monitoring/server/plugin.ts | 12 - .../server/routes/api/v1/alerts/alerts.js | 53 +- 30 files changed, 756 insertions(+), 1570 deletions(-) delete mode 100644 x-pack/plugins/monitoring/server/alerts/cluster_state.test.ts delete mode 100644 x-pack/plugins/monitoring/server/alerts/cluster_state.ts delete mode 100644 x-pack/plugins/monitoring/server/alerts/enums.ts delete mode 100644 x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.test.ts delete mode 100644 x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.ts delete mode 100644 x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.test.ts delete mode 100644 x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.ts delete mode 100644 x-pack/plugins/monitoring/server/lib/alerts/fetch_status.test.ts delete mode 100644 x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.test.ts delete mode 100644 x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.ts diff --git a/x-pack/legacy/plugins/monitoring/common/constants.ts b/x-pack/legacy/plugins/monitoring/common/constants.ts index 3a4c7b71dcd03..9a4030f3eb214 100644 --- a/x-pack/legacy/plugins/monitoring/common/constants.ts +++ b/x-pack/legacy/plugins/monitoring/common/constants.ts @@ -239,15 +239,11 @@ export const ALERT_TYPE_PREFIX = 'monitoring_'; * This is the alert type id for the license expiration alert */ export const ALERT_TYPE_LICENSE_EXPIRATION = `${ALERT_TYPE_PREFIX}alert_type_license_expiration`; -/** - * This is the alert type id for the cluster state alert - */ -export const ALERT_TYPE_CLUSTER_STATE = `${ALERT_TYPE_PREFIX}alert_type_cluster_state`; /** * A listing of all alert types */ -export const ALERT_TYPES = [ALERT_TYPE_LICENSE_EXPIRATION, ALERT_TYPE_CLUSTER_STATE]; +export const ALERT_TYPES = [ALERT_TYPE_LICENSE_EXPIRATION]; /** * Matches the id for the built-in in email action type @@ -258,7 +254,7 @@ export const ALERT_ACTION_TYPE_EMAIL = '.email'; /** * The number of alerts that have been migrated */ -export const NUMBER_OF_MIGRATED_ALERTS = 2; +export const NUMBER_OF_MIGRATED_ALERTS = 1; /** * The advanced settings config name for the email address diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/alerts.js b/x-pack/legacy/plugins/monitoring/public/components/alerts/alerts.js index 95c1af5549198..11fcef73a4b97 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/alerts/alerts.js +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/alerts.js @@ -6,15 +6,10 @@ import React from 'react'; import chrome from '../../np_imports/ui/chrome'; -import { capitalize, get } from 'lodash'; +import { capitalize } from 'lodash'; import { formatDateTimeLocal } from '../../../common/formatting'; import { formatTimestampToDuration } from '../../../common'; -import { - CALCULATE_DURATION_SINCE, - EUI_SORT_DESCENDING, - ALERT_TYPE_LICENSE_EXPIRATION, - ALERT_TYPE_CLUSTER_STATE, -} from '../../../common/constants'; +import { CALCULATE_DURATION_SINCE, EUI_SORT_DESCENDING } from '../../../common/constants'; import { mapSeverity } from './map_severity'; import { FormattedAlert } from 'plugins/monitoring/components/alerts/formatted_alert'; import { EuiMonitoringTable } from 'plugins/monitoring/components/table'; @@ -26,8 +21,6 @@ const linkToCategories = { 'elasticsearch/indices': 'Elasticsearch Indices', 'kibana/instances': 'Kibana Instances', 'logstash/instances': 'Logstash Nodes', - [ALERT_TYPE_LICENSE_EXPIRATION]: 'License expiration', - [ALERT_TYPE_CLUSTER_STATE]: 'Cluster state', }; const getColumns = (kbnUrl, scope, timezone) => [ { @@ -101,22 +94,19 @@ const getColumns = (kbnUrl, scope, timezone) => [ }), field: 'message', sortable: true, - render: (_message, alert) => { - const message = get(alert, 'message.text', get(alert, 'message', '')); - return ( - { - scope.$evalAsync(() => { - kbnUrl.changePath(target); - }); - }} - /> - ); - }, + render: (message, alert) => ( + { + scope.$evalAsync(() => { + kbnUrl.changePath(target); + }); + }} + /> + ), }, { name: i18n.translate('xpack.monitoring.alerts.categoryColumnTitle', { @@ -158,8 +148,8 @@ const getColumns = (kbnUrl, scope, timezone) => [ export const Alerts = ({ alerts, angular, sorting, pagination, onTableChange }) => { const alertsFlattened = alerts.map(alert => ({ ...alert, - status: get(alert, 'metadata.severity', get(alert, 'severity', 0)), - category: get(alert, 'metadata.link', get(alert, 'type', null)), + status: alert.metadata.severity, + category: alert.metadata.link, })); const injector = chrome.dangerouslyGetActiveInjector(); diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/status.test.tsx b/x-pack/legacy/plugins/monitoring/public/components/alerts/status.test.tsx index d3cf4b463a2cc..258a5b68db372 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/alerts/status.test.tsx +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/status.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { kfetch } from 'ui/kfetch'; import { AlertsStatus, AlertsStatusProps } from './status'; -import { ALERT_TYPES } from '../../../common/constants'; +import { ALERT_TYPE_PREFIX } from '../../../common/constants'; import { getSetupModeState } from '../../lib/setup_mode'; import { mockUseEffects } from '../../jest.helpers'; @@ -63,7 +63,11 @@ describe('Status', () => { it('should render a success message if all alerts have been migrated and in setup mode', async () => { (kfetch as jest.Mock).mockReturnValue({ - data: ALERT_TYPES.map(type => ({ alertTypeId: type })), + data: [ + { + alertTypeId: ALERT_TYPE_PREFIX, + }, + ], }); (getSetupModeState as jest.Mock).mockReturnValue({ diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/status.tsx b/x-pack/legacy/plugins/monitoring/public/components/alerts/status.tsx index 5f5329bf7fff8..072a98b123452 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/alerts/status.tsx +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/status.tsx @@ -142,7 +142,7 @@ export const AlertsStatus: React.FC = (props: AlertsStatusPro ); } - const allMigrated = kibanaAlerts.length >= NUMBER_OF_MIGRATED_ALERTS; + const allMigrated = kibanaAlerts.length === NUMBER_OF_MIGRATED_ALERTS; if (allMigrated) { if (setupModeEnabled) { return ( diff --git a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/alerts_panel.js b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/alerts_panel.js index d87ff98e79be0..8455fb8cf3088 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/alerts_panel.js +++ b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/alerts_panel.js @@ -6,12 +6,14 @@ import React, { Fragment } from 'react'; import moment from 'moment-timezone'; +import chrome from '../../../np_imports/ui/chrome'; import { FormattedAlert } from 'plugins/monitoring/components/alerts/formatted_alert'; import { mapSeverity } from 'plugins/monitoring/components/alerts/map_severity'; import { formatTimestampToDuration } from '../../../../common/format_timestamp_to_duration'; import { CALCULATE_DURATION_SINCE, KIBANA_ALERTING_ENABLED, + ALERT_TYPE_LICENSE_EXPIRATION, CALCULATE_DURATION_UNTIL, } from '../../../../common/constants'; import { formatDateTimeLocal } from '../../../../common/formatting'; @@ -29,37 +31,6 @@ import { EuiLink, } from '@elastic/eui'; -function replaceTokens(alert) { - if (!alert.message.tokens) { - return alert.message.text; - } - - let text = alert.message.text; - - for (const token of alert.message.tokens) { - if (token.type === 'time') { - text = text.replace( - token.startToken, - token.isRelative - ? formatTimestampToDuration(alert.expirationTime, CALCULATE_DURATION_UNTIL) - : moment.tz(alert.expirationTime, moment.tz.guess()).format('LLL z') - ); - } else if (token.type === 'link') { - const linkPart = new RegExp(`${token.startToken}(.+?)${token.endToken}`).exec(text); - // TODO: we assume this is at the end, which works for now but will not always work - const nonLinkText = text.replace(linkPart[0], ''); - text = ( - - {nonLinkText} - {linkPart[1]} - - ); - } - } - - return text; -} - export function AlertsPanel({ alerts, changeUrl }) { const goToAlerts = () => changeUrl('/alerts'); @@ -87,6 +58,9 @@ export function AlertsPanel({ alerts, changeUrl }) { severityIcon.iconType = 'check'; } + const injector = chrome.dangerouslyGetActiveInjector(); + const timezone = injector.get('config').get('dateFormat:tz'); + return ( @@ -122,7 +96,14 @@ export function AlertsPanel({ alerts, changeUrl }) { const alertsList = KIBANA_ALERTING_ENABLED ? alerts.map((alert, idx) => { const callOutProps = mapSeverity(alert.severity); - const message = replaceTokens(alert); + let message = alert.message + // scan message prefix and replace relative times + // \w: Matches any alphanumeric character from the basic Latin alphabet, including the underscore. Equivalent to [A-Za-z0-9_]. + .replace( + '#relative', + formatTimestampToDuration(alert.expirationTime, CALCULATE_DURATION_UNTIL) + ) + .replace('#absolute', moment.tz(alert.expirationTime, moment.tz.guess()).format('LLL z')); if (!alert.isFiring) { callOutProps.title = i18n.translate( @@ -137,30 +118,22 @@ export function AlertsPanel({ alerts, changeUrl }) { ); callOutProps.color = 'success'; callOutProps.iconType = 'check'; + } else { + if (alert.type === ALERT_TYPE_LICENSE_EXPIRATION) { + message = ( + + {message} +   + Please update your license + + ); + } } return ( - - -

{message}

- -

- -

-
-
- -
+ +

{message}

+
); }) : alerts.map((item, index) => ( diff --git a/x-pack/legacy/plugins/monitoring/public/views/alerts/index.js b/x-pack/legacy/plugins/monitoring/public/views/alerts/index.js index 62cc985887e9f..7c065a78a8af9 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/alerts/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/alerts/index.js @@ -18,37 +18,25 @@ import { Alerts } from '../../components/alerts'; import { MonitoringViewBaseEuiTableController } from '../base_eui_table_controller'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiPage, EuiPageBody, EuiPageContent, EuiSpacer, EuiLink } from '@elastic/eui'; -import { CODE_PATH_ALERTS, KIBANA_ALERTING_ENABLED } from '../../../common/constants'; +import { CODE_PATH_ALERTS } from '../../../common/constants'; function getPageData($injector) { const globalState = $injector.get('globalState'); const $http = $injector.get('$http'); const Private = $injector.get('Private'); - const url = KIBANA_ALERTING_ENABLED - ? `../api/monitoring/v1/alert_status` - : `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/legacy_alerts`; + const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/legacy_alerts`; const timeBounds = timefilter.getBounds(); - const data = { - timeRange: { - min: timeBounds.min.toISOString(), - max: timeBounds.max.toISOString(), - }, - }; - - if (!KIBANA_ALERTING_ENABLED) { - data.ccs = globalState.ccs; - } return $http - .post(url, data) - .then(response => { - const result = get(response, 'data', []); - if (KIBANA_ALERTING_ENABLED) { - return result.alerts; - } - return result; + .post(url, { + ccs: globalState.ccs, + timeRange: { + min: timeBounds.min.toISOString(), + max: timeBounds.max.toISOString(), + }, }) + .then(response => get(response, 'data', [])) .catch(err => { const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); return ajaxErrorHandlers(err); diff --git a/x-pack/plugins/monitoring/common/constants.ts b/x-pack/plugins/monitoring/common/constants.ts index 3a4c7b71dcd03..9a4030f3eb214 100644 --- a/x-pack/plugins/monitoring/common/constants.ts +++ b/x-pack/plugins/monitoring/common/constants.ts @@ -239,15 +239,11 @@ export const ALERT_TYPE_PREFIX = 'monitoring_'; * This is the alert type id for the license expiration alert */ export const ALERT_TYPE_LICENSE_EXPIRATION = `${ALERT_TYPE_PREFIX}alert_type_license_expiration`; -/** - * This is the alert type id for the cluster state alert - */ -export const ALERT_TYPE_CLUSTER_STATE = `${ALERT_TYPE_PREFIX}alert_type_cluster_state`; /** * A listing of all alert types */ -export const ALERT_TYPES = [ALERT_TYPE_LICENSE_EXPIRATION, ALERT_TYPE_CLUSTER_STATE]; +export const ALERT_TYPES = [ALERT_TYPE_LICENSE_EXPIRATION]; /** * Matches the id for the built-in in email action type @@ -258,7 +254,7 @@ export const ALERT_ACTION_TYPE_EMAIL = '.email'; /** * The number of alerts that have been migrated */ -export const NUMBER_OF_MIGRATED_ALERTS = 2; +export const NUMBER_OF_MIGRATED_ALERTS = 1; /** * The advanced settings config name for the email address diff --git a/x-pack/plugins/monitoring/server/alerts/cluster_state.test.ts b/x-pack/plugins/monitoring/server/alerts/cluster_state.test.ts deleted file mode 100644 index 6a9ca88437347..0000000000000 --- a/x-pack/plugins/monitoring/server/alerts/cluster_state.test.ts +++ /dev/null @@ -1,186 +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; - * you may not use this file except in compliance with the Elastic License. - */ -import { Logger } from 'src/core/server'; -import { savedObjectsClientMock } from 'src/core/server/mocks'; -import { getClusterState } from './cluster_state'; -import { AlertServices } from '../../../alerting/server'; -import { ALERT_TYPE_CLUSTER_STATE } from '../../common/constants'; -import { AlertCommonParams, AlertCommonState, AlertClusterStatePerClusterState } from './types'; -import { getPreparedAlert } from '../lib/alerts/get_prepared_alert'; -import { executeActions } from '../lib/alerts/cluster_state.lib'; -import { AlertClusterStateState } from './enums'; - -jest.mock('../lib/alerts/cluster_state.lib', () => ({ - executeActions: jest.fn(), - getUiMessage: jest.fn(), -})); - -jest.mock('../lib/alerts/get_prepared_alert', () => ({ - getPreparedAlert: jest.fn(() => { - return { - emailAddress: 'foo@foo.com', - }; - }), -})); - -interface MockServices { - callCluster: jest.Mock; - alertInstanceFactory: jest.Mock; - savedObjectsClient: jest.Mock; -} - -describe('getClusterState', () => { - const services: MockServices | AlertServices = { - callCluster: jest.fn(), - alertInstanceFactory: jest.fn(), - savedObjectsClient: savedObjectsClientMock.create(), - }; - - const params: AlertCommonParams = { - dateFormat: 'YYYY', - timezone: 'UTC', - }; - - const emailAddress = 'foo@foo.com'; - const clusterUuid = 'kdksdfj434'; - const clusterName = 'monitoring_test'; - const cluster = { clusterUuid, clusterName }; - - async function setupAlert( - previousState: AlertClusterStateState, - newState: AlertClusterStateState - ): Promise { - const logger: Logger = { - warn: jest.fn(), - log: jest.fn(), - debug: jest.fn(), - trace: jest.fn(), - error: jest.fn(), - fatal: jest.fn(), - info: jest.fn(), - get: jest.fn(), - }; - const getLogger = (): Logger => logger; - const ccrEnabled = false; - (getPreparedAlert as jest.Mock).mockImplementation(() => ({ - emailAddress, - data: [ - { - state: newState, - clusterUuid, - }, - ], - clusters: [cluster], - })); - - const alert = getClusterState(null as any, null as any, getLogger, ccrEnabled); - const state: AlertCommonState = { - [clusterUuid]: { - state: previousState, - ui: { - isFiring: false, - severity: 0, - message: null, - resolvedMS: 0, - lastCheckedMS: 0, - triggeredMS: 0, - }, - } as AlertClusterStatePerClusterState, - }; - - return (await alert.executor({ services, params, state } as any)) as AlertCommonState; - } - - afterEach(() => { - (executeActions as jest.Mock).mockClear(); - }); - - it('should configure the alert properly', () => { - const alert = getClusterState(null as any, null as any, jest.fn(), false); - expect(alert.id).toBe(ALERT_TYPE_CLUSTER_STATE); - expect(alert.actionGroups).toEqual([{ id: 'default', name: 'Default' }]); - }); - - it('should alert if green -> yellow', async () => { - const result = await setupAlert(AlertClusterStateState.Green, AlertClusterStateState.Yellow); - expect(executeActions).toHaveBeenCalledWith( - undefined, - cluster, - AlertClusterStateState.Yellow, - emailAddress - ); - const clusterResult = result[clusterUuid] as AlertClusterStatePerClusterState; - expect(clusterResult.state).toBe(AlertClusterStateState.Yellow); - expect(clusterResult.ui.isFiring).toBe(true); - expect(clusterResult.ui.resolvedMS).toBe(0); - }); - - it('should alert if yellow -> green', async () => { - const result = await setupAlert(AlertClusterStateState.Yellow, AlertClusterStateState.Green); - expect(executeActions).toHaveBeenCalledWith( - undefined, - cluster, - AlertClusterStateState.Green, - emailAddress, - true - ); - const clusterResult = result[clusterUuid] as AlertClusterStatePerClusterState; - expect(clusterResult.state).toBe(AlertClusterStateState.Green); - expect(clusterResult.ui.resolvedMS).toBeGreaterThan(0); - }); - - it('should alert if green -> red', async () => { - const result = await setupAlert(AlertClusterStateState.Green, AlertClusterStateState.Red); - expect(executeActions).toHaveBeenCalledWith( - undefined, - cluster, - AlertClusterStateState.Red, - emailAddress - ); - const clusterResult = result[clusterUuid] as AlertClusterStatePerClusterState; - expect(clusterResult.state).toBe(AlertClusterStateState.Red); - expect(clusterResult.ui.isFiring).toBe(true); - expect(clusterResult.ui.resolvedMS).toBe(0); - }); - - it('should alert if red -> green', async () => { - const result = await setupAlert(AlertClusterStateState.Red, AlertClusterStateState.Green); - expect(executeActions).toHaveBeenCalledWith( - undefined, - cluster, - AlertClusterStateState.Green, - emailAddress, - true - ); - const clusterResult = result[clusterUuid] as AlertClusterStatePerClusterState; - expect(clusterResult.state).toBe(AlertClusterStateState.Green); - expect(clusterResult.ui.resolvedMS).toBeGreaterThan(0); - }); - - it('should not alert if red -> yellow', async () => { - const result = await setupAlert(AlertClusterStateState.Red, AlertClusterStateState.Yellow); - expect(executeActions).not.toHaveBeenCalled(); - const clusterResult = result[clusterUuid] as AlertClusterStatePerClusterState; - expect(clusterResult.state).toBe(AlertClusterStateState.Red); - expect(clusterResult.ui.resolvedMS).toBe(0); - }); - - it('should not alert if yellow -> red', async () => { - const result = await setupAlert(AlertClusterStateState.Yellow, AlertClusterStateState.Red); - expect(executeActions).not.toHaveBeenCalled(); - const clusterResult = result[clusterUuid] as AlertClusterStatePerClusterState; - expect(clusterResult.state).toBe(AlertClusterStateState.Yellow); - expect(clusterResult.ui.resolvedMS).toBe(0); - }); - - it('should not alert if green -> green', async () => { - const result = await setupAlert(AlertClusterStateState.Green, AlertClusterStateState.Green); - expect(executeActions).not.toHaveBeenCalled(); - const clusterResult = result[clusterUuid] as AlertClusterStatePerClusterState; - expect(clusterResult.state).toBe(AlertClusterStateState.Green); - expect(clusterResult.ui.resolvedMS).toBe(0); - }); -}); diff --git a/x-pack/plugins/monitoring/server/alerts/cluster_state.ts b/x-pack/plugins/monitoring/server/alerts/cluster_state.ts deleted file mode 100644 index 9a5805b8af7ce..0000000000000 --- a/x-pack/plugins/monitoring/server/alerts/cluster_state.ts +++ /dev/null @@ -1,134 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import moment from 'moment-timezone'; -import { i18n } from '@kbn/i18n'; -import { Logger, ICustomClusterClient, UiSettingsServiceStart } from 'src/core/server'; -import { ALERT_TYPE_CLUSTER_STATE } from '../../common/constants'; -import { AlertType } from '../../../alerting/server'; -import { executeActions, getUiMessage } from '../lib/alerts/cluster_state.lib'; -import { - AlertCommonExecutorOptions, - AlertCommonState, - AlertClusterStatePerClusterState, - AlertCommonCluster, -} from './types'; -import { AlertClusterStateState } from './enums'; -import { getPreparedAlert } from '../lib/alerts/get_prepared_alert'; -import { fetchClusterState } from '../lib/alerts/fetch_cluster_state'; - -export const getClusterState = ( - getUiSettingsService: () => Promise, - monitoringCluster: ICustomClusterClient, - getLogger: (...scopes: string[]) => Logger, - ccsEnabled: boolean -): AlertType => { - const logger = getLogger(ALERT_TYPE_CLUSTER_STATE); - return { - id: ALERT_TYPE_CLUSTER_STATE, - name: 'Monitoring Alert - Cluster Status', - actionGroups: [ - { - id: 'default', - name: i18n.translate('xpack.monitoring.alerts.clusterState.actionGroups.default', { - defaultMessage: 'Default', - }), - }, - ], - defaultActionGroupId: 'default', - async executor({ - services, - params, - state, - }: AlertCommonExecutorOptions): Promise { - logger.debug( - `Firing alert with params: ${JSON.stringify(params)} and state: ${JSON.stringify(state)}` - ); - - const preparedAlert = await getPreparedAlert( - ALERT_TYPE_CLUSTER_STATE, - getUiSettingsService, - monitoringCluster, - logger, - ccsEnabled, - services, - fetchClusterState - ); - - if (!preparedAlert) { - return state; - } - - const { emailAddress, data: states, clusters } = preparedAlert; - - const result: AlertCommonState = { ...state }; - const defaultAlertState: AlertClusterStatePerClusterState = { - state: AlertClusterStateState.Green, - ui: { - isFiring: false, - message: null, - severity: 0, - resolvedMS: 0, - triggeredMS: 0, - lastCheckedMS: 0, - }, - }; - - for (const clusterState of states) { - const alertState: AlertClusterStatePerClusterState = - (state[clusterState.clusterUuid] as AlertClusterStatePerClusterState) || - defaultAlertState; - const cluster = clusters.find( - (c: AlertCommonCluster) => c.clusterUuid === clusterState.clusterUuid - ); - if (!cluster) { - logger.warn(`Unable to find cluster for clusterUuid='${clusterState.clusterUuid}'`); - continue; - } - const isNonGreen = clusterState.state !== AlertClusterStateState.Green; - const severity = clusterState.state === AlertClusterStateState.Red ? 2100 : 1100; - - const ui = alertState.ui; - let triggered = ui.triggeredMS; - let resolved = ui.resolvedMS; - let message = ui.message || {}; - let lastState = alertState.state; - const instance = services.alertInstanceFactory(ALERT_TYPE_CLUSTER_STATE); - - if (isNonGreen) { - if (lastState === AlertClusterStateState.Green) { - logger.debug(`Cluster state changed from green to ${clusterState.state}`); - executeActions(instance, cluster, clusterState.state, emailAddress); - lastState = clusterState.state; - triggered = moment().valueOf(); - } - message = getUiMessage(clusterState.state); - resolved = 0; - } else if (!isNonGreen && lastState !== AlertClusterStateState.Green) { - logger.debug(`Cluster state changed from ${lastState} to green`); - executeActions(instance, cluster, clusterState.state, emailAddress, true); - lastState = clusterState.state; - message = getUiMessage(clusterState.state, true); - resolved = moment().valueOf(); - } - - result[clusterState.clusterUuid] = { - state: lastState, - ui: { - message, - isFiring: isNonGreen, - severity, - resolvedMS: resolved, - triggeredMS: triggered, - lastCheckedMS: moment().valueOf(), - }, - } as AlertClusterStatePerClusterState; - } - - return result; - }, - }; -}; diff --git a/x-pack/plugins/monitoring/server/alerts/enums.ts b/x-pack/plugins/monitoring/server/alerts/enums.ts deleted file mode 100644 index ccff588743af1..0000000000000 --- a/x-pack/plugins/monitoring/server/alerts/enums.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export enum AlertClusterStateState { - Green = 'green', - Red = 'red', - Yellow = 'yellow', -} - -export enum AlertCommonPerClusterMessageTokenType { - Time = 'time', - Link = 'link', -} diff --git a/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts b/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts index 92047e300bc1f..0773af6e7f070 100644 --- a/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts @@ -6,31 +6,42 @@ import moment from 'moment-timezone'; import { getLicenseExpiration } from './license_expiration'; -import { ALERT_TYPE_LICENSE_EXPIRATION } from '../../common/constants'; +import { + ALERT_TYPE_LICENSE_EXPIRATION, + MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS, +} from '../../common/constants'; import { Logger } from 'src/core/server'; -import { AlertServices } from '../../../alerting/server'; +import { AlertServices, AlertInstance } from '../../../alerting/server'; import { savedObjectsClientMock } from 'src/core/server/mocks'; import { - AlertCommonParams, - AlertCommonState, - AlertLicensePerClusterState, - AlertLicense, + AlertState, + AlertClusterState, + AlertParams, + LicenseExpirationAlertExecutorOptions, } from './types'; -import { executeActions } from '../lib/alerts/license_expiration.lib'; -import { PreparedAlert, getPreparedAlert } from '../lib/alerts/get_prepared_alert'; - -jest.mock('../lib/alerts/license_expiration.lib', () => ({ - executeActions: jest.fn(), - getUiMessage: jest.fn(), -})); - -jest.mock('../lib/alerts/get_prepared_alert', () => ({ - getPreparedAlert: jest.fn(() => { - return { - emailAddress: 'foo@foo.com', - }; - }), -})); +import { SavedObject, SavedObjectAttributes } from 'src/core/server'; +import { SavedObjectsClientContract } from 'src/core/server'; + +function fillLicense(license: any, clusterUuid?: string) { + return { + hits: { + hits: [ + { + _source: { + license, + cluster_uuid: clusterUuid, + }, + }, + ], + }, + }; +} + +const clusterUuid = 'a4545jhjb'; +const params: AlertParams = { + dateFormat: 'YYYY', + timezone: 'UTC', +}; interface MockServices { callCluster: jest.Mock; @@ -38,169 +49,428 @@ interface MockServices { savedObjectsClient: jest.Mock; } -describe('getLicenseExpiration', () => { - const services: MockServices | AlertServices = { - callCluster: jest.fn(), - alertInstanceFactory: jest.fn(), - savedObjectsClient: savedObjectsClientMock.create(), - }; - - const params: AlertCommonParams = { - dateFormat: 'YYYY', - timezone: 'UTC', - }; +const alertExecutorOptions: LicenseExpirationAlertExecutorOptions = { + alertId: '', + startedAt: new Date(), + services: { + callCluster: (path: string, opts: any) => new Promise(resolve => resolve()), + alertInstanceFactory: (id: string) => new AlertInstance(), + savedObjectsClient: {} as jest.Mocked, + }, + params: {}, + state: {}, + spaceId: '', + name: '', + tags: [], + previousStartedAt: null, + createdBy: null, + updatedBy: null, +}; +describe('getLicenseExpiration', () => { const emailAddress = 'foo@foo.com'; - const clusterUuid = 'kdksdfj434'; - const clusterName = 'monitoring_test'; - const dateFormat = 'YYYY-MM-DD'; - const cluster = { clusterUuid, clusterName }; - const defaultUiState = { - isFiring: false, - severity: 0, - message: null, - resolvedMS: 0, - lastCheckedMS: 0, - triggeredMS: 0, + const getUiSettingsService: any = () => ({ + asScopedToClient: (): any => ({ + get: () => new Promise(resolve => resolve(emailAddress)), + }), + }); + const monitoringCluster: any = null; + const logger: Logger = { + warn: jest.fn(), + log: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), + error: jest.fn(), + fatal: jest.fn(), + info: jest.fn(), + get: jest.fn(), }; - - async function setupAlert( - license: AlertLicense | null, - expiredCheckDateMS: number, - preparedAlertResponse: PreparedAlert | null | undefined = undefined - ): Promise { - const logger: Logger = { - warn: jest.fn(), - log: jest.fn(), - debug: jest.fn(), - trace: jest.fn(), - error: jest.fn(), - fatal: jest.fn(), - info: jest.fn(), - get: jest.fn(), - }; - const getLogger = (): Logger => logger; - const ccrEnabled = false; - (getPreparedAlert as jest.Mock).mockImplementation(() => { - if (preparedAlertResponse !== undefined) { - return preparedAlertResponse; - } - - return { - emailAddress, - data: [license], - clusters: [cluster], - dateFormat, - }; - }); - - const alert = getLicenseExpiration(null as any, null as any, getLogger, ccrEnabled); - const state: AlertCommonState = { - [clusterUuid]: { - expiredCheckDateMS, - ui: { ...defaultUiState }, - } as AlertLicensePerClusterState, - }; - - return (await alert.executor({ services, params, state } as any)) as AlertCommonState; - } + const getLogger = (): Logger => logger; + const ccrEnabled = false; afterEach(() => { - (executeActions as jest.Mock).mockClear(); - (getPreparedAlert as jest.Mock).mockClear(); + (logger.warn as jest.Mock).mockClear(); }); it('should have the right id and actionGroups', () => { - const alert = getLicenseExpiration(null as any, null as any, jest.fn(), false); + const alert = getLicenseExpiration( + getUiSettingsService, + monitoringCluster, + getLogger, + ccrEnabled + ); expect(alert.id).toBe(ALERT_TYPE_LICENSE_EXPIRATION); expect(alert.actionGroups).toEqual([{ id: 'default', name: 'Default' }]); }); it('should return the state if no license is provided', async () => { - const result = await setupAlert(null, 0, null); - expect(result[clusterUuid].ui).toEqual(defaultUiState); + const alert = getLicenseExpiration( + getUiSettingsService, + monitoringCluster, + getLogger, + ccrEnabled + ); + + const services: MockServices | AlertServices = { + callCluster: jest.fn(), + alertInstanceFactory: jest.fn(), + savedObjectsClient: savedObjectsClientMock.create(), + }; + const state = { foo: 1 }; + + const result = await alert.executor({ + ...alertExecutorOptions, + services, + params, + state, + }); + + expect(result).toEqual(state); + }); + + it('should log a warning if no email is provided', async () => { + const customGetUiSettingsService: any = () => ({ + asScopedToClient: () => ({ + get: () => null, + }), + }); + const alert = getLicenseExpiration( + customGetUiSettingsService, + monitoringCluster, + getLogger, + ccrEnabled + ); + + const services = { + callCluster: jest.fn( + (method: string, { filterPath }): Promise => { + return new Promise(resolve => { + if (filterPath.includes('hits.hits._source.license.*')) { + resolve( + fillLicense({ + status: 'good', + type: 'basic', + expiry_date_in_millis: moment() + .add(7, 'days') + .valueOf(), + }) + ); + } + resolve({}); + }); + } + ), + alertInstanceFactory: jest.fn(), + savedObjectsClient: savedObjectsClientMock.create(), + }; + + const state = {}; + + await alert.executor({ + ...alertExecutorOptions, + services, + params, + state, + }); + + expect((logger.warn as jest.Mock).mock.calls.length).toBe(1); + expect(logger.warn).toHaveBeenCalledWith( + `Unable to send email for ${ALERT_TYPE_LICENSE_EXPIRATION} because there is no email configured.` + ); }); it('should fire actions if going to expire', async () => { - const expiryDateMS = moment() - .add(7, 'days') - .valueOf(); - const license = { - status: 'active', - type: 'gold', - expiryDateMS, - clusterUuid, + const scheduleActions = jest.fn(); + const alertInstanceFactory = jest.fn( + (id: string): AlertInstance => { + const instance = new AlertInstance(); + instance.scheduleActions = scheduleActions; + return instance; + } + ); + + const alert = getLicenseExpiration( + getUiSettingsService, + monitoringCluster, + getLogger, + ccrEnabled + ); + + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.get.mockReturnValue( + new Promise(resolve => { + const savedObject: SavedObject = { + id: '', + type: '', + references: [], + attributes: { + [MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS]: emailAddress, + }, + }; + resolve(savedObject); + }) + ); + const services = { + callCluster: jest.fn( + (method: string, { filterPath }): Promise => { + return new Promise(resolve => { + if (filterPath.includes('hits.hits._source.license.*')) { + resolve( + fillLicense( + { + status: 'active', + type: 'gold', + expiry_date_in_millis: moment() + .add(7, 'days') + .valueOf(), + }, + clusterUuid + ) + ); + } + resolve({}); + }); + } + ), + alertInstanceFactory, + savedObjectsClient, }; - const result = await setupAlert(license, 0); - const newState = result[clusterUuid] as AlertLicensePerClusterState; + + const state = {}; + + const result: AlertState = (await alert.executor({ + ...alertExecutorOptions, + services, + params, + state, + })) as AlertState; + + const newState: AlertClusterState = result[clusterUuid] as AlertClusterState; + expect(newState.expiredCheckDateMS > 0).toBe(true); - expect(executeActions).toHaveBeenCalledWith( - undefined, - cluster, - moment.utc(expiryDateMS), - dateFormat, - emailAddress + expect(scheduleActions.mock.calls.length).toBe(1); + expect(scheduleActions.mock.calls[0][1].subject).toBe( + 'NEW X-Pack Monitoring: License Expiration' ); + expect(scheduleActions.mock.calls[0][1].to).toBe(emailAddress); }); it('should fire actions if the user fixed their license', async () => { - const expiryDateMS = moment() - .add(365, 'days') - .valueOf(); - const license = { - status: 'active', - type: 'gold', - expiryDateMS, - clusterUuid, + const scheduleActions = jest.fn(); + const alertInstanceFactory = jest.fn( + (id: string): AlertInstance => { + const instance = new AlertInstance(); + instance.scheduleActions = scheduleActions; + return instance; + } + ); + const alert = getLicenseExpiration( + getUiSettingsService, + monitoringCluster, + getLogger, + ccrEnabled + ); + + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.get.mockReturnValue( + new Promise(resolve => { + const savedObject: SavedObject = { + id: '', + type: '', + references: [], + attributes: { + [MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS]: emailAddress, + }, + }; + resolve(savedObject); + }) + ); + const services = { + callCluster: jest.fn( + (method: string, { filterPath }): Promise => { + return new Promise(resolve => { + if (filterPath.includes('hits.hits._source.license.*')) { + resolve( + fillLicense( + { + status: 'active', + type: 'gold', + expiry_date_in_millis: moment() + .add(120, 'days') + .valueOf(), + }, + clusterUuid + ) + ); + } + resolve({}); + }); + } + ), + alertInstanceFactory, + savedObjectsClient, + }; + + const state: AlertState = { + [clusterUuid]: { + expiredCheckDateMS: moment() + .subtract(1, 'day') + .valueOf(), + ui: { isFiring: true, severity: 0, message: null, resolvedMS: 0, expirationTime: 0 }, + }, }; - const result = await setupAlert(license, 100); - const newState = result[clusterUuid] as AlertLicensePerClusterState; + + const result: AlertState = (await alert.executor({ + ...alertExecutorOptions, + services, + params, + state, + })) as AlertState; + + const newState: AlertClusterState = result[clusterUuid] as AlertClusterState; expect(newState.expiredCheckDateMS).toBe(0); - expect(executeActions).toHaveBeenCalledWith( - undefined, - cluster, - moment.utc(expiryDateMS), - dateFormat, - emailAddress, - true + expect(scheduleActions.mock.calls.length).toBe(1); + expect(scheduleActions.mock.calls[0][1].subject).toBe( + 'RESOLVED X-Pack Monitoring: License Expiration' ); + expect(scheduleActions.mock.calls[0][1].to).toBe(emailAddress); }); it('should not fire actions for trial license that expire in more than 14 days', async () => { - const expiryDateMS = moment() - .add(20, 'days') - .valueOf(); - const license = { - status: 'active', - type: 'trial', - expiryDateMS, - clusterUuid, + const scheduleActions = jest.fn(); + const alertInstanceFactory = jest.fn( + (id: string): AlertInstance => { + const instance = new AlertInstance(); + instance.scheduleActions = scheduleActions; + return instance; + } + ); + const alert = getLicenseExpiration( + getUiSettingsService, + monitoringCluster, + getLogger, + ccrEnabled + ); + + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.get.mockReturnValue( + new Promise(resolve => { + const savedObject: SavedObject = { + id: '', + type: '', + references: [], + attributes: { + [MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS]: emailAddress, + }, + }; + resolve(savedObject); + }) + ); + const services = { + callCluster: jest.fn( + (method: string, { filterPath }): Promise => { + return new Promise(resolve => { + if (filterPath.includes('hits.hits._source.license.*')) { + resolve( + fillLicense( + { + status: 'active', + type: 'trial', + expiry_date_in_millis: moment() + .add(15, 'days') + .valueOf(), + }, + clusterUuid + ) + ); + } + resolve({}); + }); + } + ), + alertInstanceFactory, + savedObjectsClient, }; - const result = await setupAlert(license, 0); - const newState = result[clusterUuid] as AlertLicensePerClusterState; - expect(newState.expiredCheckDateMS).toBe(0); - expect(executeActions).not.toHaveBeenCalled(); + + const state = {}; + const result: AlertState = (await alert.executor({ + ...alertExecutorOptions, + services, + params, + state, + })) as AlertState; + + const newState: AlertClusterState = result[clusterUuid] as AlertClusterState; + expect(newState.expiredCheckDateMS).toBe(undefined); + expect(scheduleActions).not.toHaveBeenCalled(); }); it('should fire actions for trial license that in 14 days or less', async () => { - const expiryDateMS = moment() - .add(7, 'days') - .valueOf(); - const license = { - status: 'active', - type: 'trial', - expiryDateMS, - clusterUuid, + const scheduleActions = jest.fn(); + const alertInstanceFactory = jest.fn( + (id: string): AlertInstance => { + const instance = new AlertInstance(); + instance.scheduleActions = scheduleActions; + return instance; + } + ); + const alert = getLicenseExpiration( + getUiSettingsService, + monitoringCluster, + getLogger, + ccrEnabled + ); + + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.get.mockReturnValue( + new Promise(resolve => { + const savedObject: SavedObject = { + id: '', + type: '', + references: [], + attributes: { + [MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS]: emailAddress, + }, + }; + resolve(savedObject); + }) + ); + const services = { + callCluster: jest.fn( + (method: string, { filterPath }): Promise => { + return new Promise(resolve => { + if (filterPath.includes('hits.hits._source.license.*')) { + resolve( + fillLicense( + { + status: 'active', + type: 'trial', + expiry_date_in_millis: moment() + .add(13, 'days') + .valueOf(), + }, + clusterUuid + ) + ); + } + resolve({}); + }); + } + ), + alertInstanceFactory, + savedObjectsClient, }; - const result = await setupAlert(license, 0); - const newState = result[clusterUuid] as AlertLicensePerClusterState; + + const state = {}; + const result: AlertState = (await alert.executor({ + ...alertExecutorOptions, + services, + params, + state, + })) as AlertState; + + const newState: AlertClusterState = result[clusterUuid] as AlertClusterState; expect(newState.expiredCheckDateMS > 0).toBe(true); - expect(executeActions).toHaveBeenCalledWith( - undefined, - cluster, - moment.utc(expiryDateMS), - dateFormat, - emailAddress - ); + expect(scheduleActions.mock.calls.length).toBe(1); }); }); diff --git a/x-pack/plugins/monitoring/server/alerts/license_expiration.ts b/x-pack/plugins/monitoring/server/alerts/license_expiration.ts index 2e5356150086b..93397ff3641ae 100644 --- a/x-pack/plugins/monitoring/server/alerts/license_expiration.ts +++ b/x-pack/plugins/monitoring/server/alerts/license_expiration.ts @@ -5,20 +5,24 @@ */ import moment from 'moment-timezone'; +import { get } from 'lodash'; import { Logger, ICustomClusterClient, UiSettingsServiceStart } from 'src/core/server'; import { i18n } from '@kbn/i18n'; -import { ALERT_TYPE_LICENSE_EXPIRATION } from '../../common/constants'; +import { ALERT_TYPE_LICENSE_EXPIRATION, INDEX_PATTERN_ELASTICSEARCH } from '../../common/constants'; import { AlertType } from '../../../../plugins/alerting/server'; import { fetchLicenses } from '../lib/alerts/fetch_licenses'; +import { fetchDefaultEmailAddress } from '../lib/alerts/fetch_default_email_address'; +import { fetchClusters } from '../lib/alerts/fetch_clusters'; +import { fetchAvailableCcs } from '../lib/alerts/fetch_available_ccs'; import { - AlertCommonState, - AlertLicensePerClusterState, - AlertCommonExecutorOptions, - AlertCommonCluster, - AlertLicensePerClusterUiState, + AlertLicense, + AlertState, + AlertClusterState, + AlertClusterUiState, + LicenseExpirationAlertExecutorOptions, } from './types'; +import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern'; import { executeActions, getUiMessage } from '../lib/alerts/license_expiration.lib'; -import { getPreparedAlert } from '../lib/alerts/get_prepared_alert'; const EXPIRES_DAYS = [60, 30, 14, 7]; @@ -28,6 +32,14 @@ export const getLicenseExpiration = ( getLogger: (...scopes: string[]) => Logger, ccsEnabled: boolean ): AlertType => { + async function getCallCluster(services: any): Promise { + if (!monitoringCluster) { + return services.callCluster; + } + + return monitoringCluster.callAsInternalUser; + } + const logger = getLogger(ALERT_TYPE_LICENSE_EXPIRATION); return { id: ALERT_TYPE_LICENSE_EXPIRATION, @@ -41,50 +53,54 @@ export const getLicenseExpiration = ( }, ], defaultActionGroupId: 'default', - async executor({ services, params, state }: AlertCommonExecutorOptions): Promise { + async executor({ + services, + params, + state, + }: LicenseExpirationAlertExecutorOptions): Promise { logger.debug( `Firing alert with params: ${JSON.stringify(params)} and state: ${JSON.stringify(state)}` ); - const preparedAlert = await getPreparedAlert( - ALERT_TYPE_LICENSE_EXPIRATION, - getUiSettingsService, - monitoringCluster, - logger, - ccsEnabled, - services, - fetchLicenses - ); + const callCluster = await getCallCluster(services); + + // Support CCS use cases by querying to find available remote clusters + // and then adding those to the index pattern we are searching against + let esIndexPattern = INDEX_PATTERN_ELASTICSEARCH; + if (ccsEnabled) { + const availableCcs = await fetchAvailableCcs(callCluster); + if (availableCcs.length > 0) { + esIndexPattern = getCcsIndexPattern(esIndexPattern, availableCcs); + } + } + + const clusters = await fetchClusters(callCluster, esIndexPattern); - if (!preparedAlert) { + // Fetch licensing information from cluster_stats documents + const licenses: AlertLicense[] = await fetchLicenses(callCluster, clusters, esIndexPattern); + if (licenses.length === 0) { + logger.warn(`No license found for ${ALERT_TYPE_LICENSE_EXPIRATION}.`); return state; } - const { emailAddress, data: licenses, clusters, dateFormat } = preparedAlert; + const uiSettings = (await getUiSettingsService()).asScopedToClient( + services.savedObjectsClient + ); + const dateFormat: string = await uiSettings.get('dateFormat'); + const timezone: string = await uiSettings.get('dateFormat:tz'); + const emailAddress = await fetchDefaultEmailAddress(uiSettings); + if (!emailAddress) { + // TODO: we can do more here + logger.warn( + `Unable to send email for ${ALERT_TYPE_LICENSE_EXPIRATION} because there is no email configured.` + ); + return; + } - const result: AlertCommonState = { ...state }; - const defaultAlertState: AlertLicensePerClusterState = { - expiredCheckDateMS: 0, - ui: { - isFiring: false, - message: null, - severity: 0, - resolvedMS: 0, - lastCheckedMS: 0, - triggeredMS: 0, - }, - }; + const result: AlertState = { ...state }; for (const license of licenses) { - const alertState: AlertLicensePerClusterState = - (state[license.clusterUuid] as AlertLicensePerClusterState) || defaultAlertState; - const cluster = clusters.find( - (c: AlertCommonCluster) => c.clusterUuid === license.clusterUuid - ); - if (!cluster) { - logger.warn(`Unable to find cluster for clusterUuid='${license.clusterUuid}'`); - continue; - } + const licenseState: AlertClusterState = state[license.clusterUuid] || {}; const $expiry = moment.utc(license.expiryDateMS); let isExpired = false; let severity = 0; @@ -107,26 +123,31 @@ export const getLicenseExpiration = ( } } - const ui = alertState.ui; - let triggered = ui.triggeredMS; + const ui: AlertClusterUiState = get(licenseState, 'ui', { + isFiring: false, + message: null, + severity: 0, + resolvedMS: 0, + expirationTime: 0, + }); let resolved = ui.resolvedMS; let message = ui.message; - let expiredCheckDate = alertState.expiredCheckDateMS; + let expiredCheckDate = licenseState.expiredCheckDateMS; const instance = services.alertInstanceFactory(ALERT_TYPE_LICENSE_EXPIRATION); if (isExpired) { - if (!alertState.expiredCheckDateMS) { + if (!licenseState.expiredCheckDateMS) { logger.debug(`License will expire soon, sending email`); - executeActions(instance, cluster, $expiry, dateFormat, emailAddress); - expiredCheckDate = triggered = moment().valueOf(); + executeActions(instance, license, $expiry, dateFormat, emailAddress); + expiredCheckDate = moment().valueOf(); } - message = getUiMessage(); + message = getUiMessage(license, timezone); resolved = 0; - } else if (!isExpired && alertState.expiredCheckDateMS) { + } else if (!isExpired && licenseState.expiredCheckDateMS) { logger.debug(`License expiration has been resolved, sending email`); - executeActions(instance, cluster, $expiry, dateFormat, emailAddress, true); + executeActions(instance, license, $expiry, dateFormat, emailAddress, true); expiredCheckDate = 0; - message = getUiMessage(true); + message = getUiMessage(license, timezone, true); resolved = moment().valueOf(); } @@ -138,10 +159,8 @@ export const getLicenseExpiration = ( isFiring: expiredCheckDate > 0, severity, resolvedMS: resolved, - triggeredMS: triggered, - lastCheckedMS: moment().valueOf(), - } as AlertLicensePerClusterUiState, - } as AlertLicensePerClusterState; + }, + }; } return result; diff --git a/x-pack/plugins/monitoring/server/alerts/types.d.ts b/x-pack/plugins/monitoring/server/alerts/types.d.ts index b689d008b51a7..ff47d6f2ad4dc 100644 --- a/x-pack/plugins/monitoring/server/alerts/types.d.ts +++ b/x-pack/plugins/monitoring/server/alerts/types.d.ts @@ -5,79 +5,41 @@ */ import { Moment } from 'moment'; import { AlertExecutorOptions } from '../../../alerting/server'; -import { AlertClusterStateState, AlertCommonPerClusterMessageTokenType } from './enums'; export interface AlertLicense { status: string; type: string; expiryDateMS: number; clusterUuid: string; + clusterName: string; } -export interface AlertClusterState { - state: AlertClusterStateState; - clusterUuid: string; -} - -export interface AlertCommonState { - [clusterUuid: string]: AlertCommonPerClusterState; -} - -export interface AlertCommonPerClusterState { - ui: AlertCommonPerClusterUiState; -} - -export interface AlertClusterStatePerClusterState extends AlertCommonPerClusterState { - state: AlertClusterStateState; +export interface AlertState { + [clusterUuid: string]: AlertClusterState; } -export interface AlertLicensePerClusterState extends AlertCommonPerClusterState { - expiredCheckDateMS: number; +export interface AlertClusterState { + expiredCheckDateMS: number | Moment; + ui: AlertClusterUiState; } -export interface AlertCommonPerClusterUiState { +export interface AlertClusterUiState { isFiring: boolean; severity: number; - message: AlertCommonPerClusterMessage | null; + message: string | null; resolvedMS: number; - lastCheckedMS: number; - triggeredMS: number; -} - -export interface AlertCommonPerClusterMessage { - text: string; // Do this. #link this is a link #link - tokens?: AlertCommonPerClusterMessageToken[]; -} - -export interface AlertCommonPerClusterMessageToken { - startToken: string; - endToken?: string; - type: AlertCommonPerClusterMessageTokenType; -} - -export interface AlertCommonPerClusterMessageLinkToken extends AlertCommonPerClusterMessageToken { - url?: string; -} - -export interface AlertCommonPerClusterMessageTimeToken extends AlertCommonPerClusterMessageToken { - isRelative: boolean; - isAbsolute: boolean; -} - -export interface AlertLicensePerClusterUiState extends AlertCommonPerClusterUiState { expirationTime: number; } -export interface AlertCommonCluster { +export interface AlertCluster { clusterUuid: string; - clusterName: string; } -export interface AlertCommonExecutorOptions extends AlertExecutorOptions { - state: AlertCommonState; +export interface LicenseExpirationAlertExecutorOptions extends AlertExecutorOptions { + state: AlertState; } -export interface AlertCommonParams { +export interface AlertParams { dateFormat: string; timezone: string; } diff --git a/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.test.ts deleted file mode 100644 index 81e375734cc50..0000000000000 --- a/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.test.ts +++ /dev/null @@ -1,70 +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; - * you may not use this file except in compliance with the Elastic License. - */ -import { executeActions, getUiMessage } from './cluster_state.lib'; -import { AlertClusterStateState } from '../../alerts/enums'; -import { AlertCommonPerClusterMessageLinkToken } from '../../alerts/types'; - -describe('clusterState lib', () => { - describe('executeActions', () => { - const clusterName = 'clusterA'; - const instance: any = { scheduleActions: jest.fn() }; - const license: any = { clusterName }; - const status = AlertClusterStateState.Green; - const emailAddress = 'test@test.com'; - - beforeEach(() => { - instance.scheduleActions.mockClear(); - }); - - it('should schedule actions when firing', () => { - executeActions(instance, license, status, emailAddress, false); - expect(instance.scheduleActions).toHaveBeenCalledWith('default', { - subject: 'NEW X-Pack Monitoring: Cluster Status', - message: `Allocate missing replica shards for cluster '${clusterName}'`, - to: emailAddress, - }); - }); - - it('should have a different message for red state', () => { - executeActions(instance, license, AlertClusterStateState.Red, emailAddress, false); - expect(instance.scheduleActions).toHaveBeenCalledWith('default', { - subject: 'NEW X-Pack Monitoring: Cluster Status', - message: `Allocate missing primary and replica shards for cluster '${clusterName}'`, - to: emailAddress, - }); - }); - - it('should schedule actions when resolved', () => { - executeActions(instance, license, status, emailAddress, true); - expect(instance.scheduleActions).toHaveBeenCalledWith('default', { - subject: 'RESOLVED X-Pack Monitoring: Cluster Status', - message: `This cluster alert has been resolved: Allocate missing replica shards for cluster '${clusterName}'`, - to: emailAddress, - }); - }); - }); - - describe('getUiMessage', () => { - it('should return a message when firing', () => { - const message = getUiMessage(AlertClusterStateState.Red, false); - expect(message.text).toBe( - `Elasticsearch cluster status is red. #start_linkAllocate missing primary and replica shards#end_link` - ); - expect(message.tokens && message.tokens.length).toBe(1); - expect(message.tokens && message.tokens[0].startToken).toBe('#start_link'); - expect(message.tokens && message.tokens[0].endToken).toBe('#end_link'); - expect( - message.tokens && (message.tokens[0] as AlertCommonPerClusterMessageLinkToken).url - ).toBe('elasticsearch/indices'); - }); - - it('should return a message when resolved', () => { - const message = getUiMessage(AlertClusterStateState.Green, true); - expect(message.text).toBe(`Elasticsearch cluster status is green.`); - expect(message.tokens).not.toBeDefined(); - }); - }); -}); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.ts b/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.ts deleted file mode 100644 index ae66d603507ca..0000000000000 --- a/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.ts +++ /dev/null @@ -1,88 +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; - * you may not use this file except in compliance with the Elastic License. - */ -import { i18n } from '@kbn/i18n'; -import { AlertInstance } from '../../../../alerting/server'; -import { - AlertCommonCluster, - AlertCommonPerClusterMessage, - AlertCommonPerClusterMessageLinkToken, -} from '../../alerts/types'; -import { AlertClusterStateState, AlertCommonPerClusterMessageTokenType } from '../../alerts/enums'; - -const RESOLVED_SUBJECT = i18n.translate('xpack.monitoring.alerts.clusterStatus.resolvedSubject', { - defaultMessage: 'RESOLVED X-Pack Monitoring: Cluster Status', -}); - -const NEW_SUBJECT = i18n.translate('xpack.monitoring.alerts.clusterStatus.newSubject', { - defaultMessage: 'NEW X-Pack Monitoring: Cluster Status', -}); - -const RED_STATUS_MESSAGE = i18n.translate('xpack.monitoring.alerts.clusterStatus.redMessage', { - defaultMessage: 'Allocate missing primary and replica shards', -}); - -const YELLOW_STATUS_MESSAGE = i18n.translate( - 'xpack.monitoring.alerts.clusterStatus.yellowMessage', - { - defaultMessage: 'Allocate missing replica shards', - } -); - -export function executeActions( - instance: AlertInstance, - cluster: AlertCommonCluster, - status: AlertClusterStateState, - emailAddress: string, - resolved: boolean = false -) { - const message = - status === AlertClusterStateState.Red ? RED_STATUS_MESSAGE : YELLOW_STATUS_MESSAGE; - if (resolved) { - instance.scheduleActions('default', { - subject: RESOLVED_SUBJECT, - message: `This cluster alert has been resolved: ${message} for cluster '${cluster.clusterName}'`, - to: emailAddress, - }); - } else { - instance.scheduleActions('default', { - subject: NEW_SUBJECT, - message: `${message} for cluster '${cluster.clusterName}'`, - to: emailAddress, - }); - } -} - -export function getUiMessage( - status: AlertClusterStateState, - resolved: boolean = false -): AlertCommonPerClusterMessage { - if (resolved) { - return { - text: i18n.translate('xpack.monitoring.alerts.clusterStatus.ui.resolvedMessage', { - defaultMessage: `Elasticsearch cluster status is green.`, - }), - }; - } - const message = - status === AlertClusterStateState.Red ? RED_STATUS_MESSAGE : YELLOW_STATUS_MESSAGE; - return { - text: i18n.translate('xpack.monitoring.alerts.clusterStatus.ui.firingMessage', { - defaultMessage: `Elasticsearch cluster status is {status}. #start_link{message}#end_link`, - values: { - status, - message, - }, - }), - tokens: [ - { - startToken: '#start_link', - endToken: '#end_link', - type: AlertCommonPerClusterMessageTokenType.Link, - url: 'elasticsearch/indices', - } as AlertCommonPerClusterMessageLinkToken, - ], - }; -} diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.test.ts deleted file mode 100644 index 642ae3c39a027..0000000000000 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.test.ts +++ /dev/null @@ -1,39 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { fetchClusterState } from './fetch_cluster_state'; - -describe('fetchClusterState', () => { - it('should return the cluster state', async () => { - const status = 'green'; - const clusterUuid = 'sdfdsaj34434'; - const callCluster = jest.fn(() => ({ - hits: { - hits: [ - { - _source: { - cluster_state: { - status, - }, - cluster_uuid: clusterUuid, - }, - }, - ], - }, - })); - - const clusters = [{ clusterUuid, clusterName: 'foo' }]; - const index = '.monitoring-es-*'; - - const state = await fetchClusterState(callCluster, clusters, index); - expect(state).toEqual([ - { - state: status, - clusterUuid, - }, - ]); - }); -}); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.ts deleted file mode 100644 index 66ea30d5f2e96..0000000000000 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.ts +++ /dev/null @@ -1,53 +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; - * you may not use this file except in compliance with the Elastic License. - */ -import { get } from 'lodash'; -import { AlertCommonCluster, AlertClusterState } from '../../alerts/types'; - -export async function fetchClusterState( - callCluster: any, - clusters: AlertCommonCluster[], - index: string -): Promise { - const params = { - index, - filterPath: ['hits.hits._source.cluster_state.status', 'hits.hits._source.cluster_uuid'], - body: { - size: 1, - sort: [{ timestamp: { order: 'desc' } }], - query: { - bool: { - filter: [ - { - terms: { - cluster_uuid: clusters.map(cluster => cluster.clusterUuid), - }, - }, - { - term: { - type: 'cluster_stats', - }, - }, - { - range: { - timestamp: { - gte: 'now-2m', - }, - }, - }, - ], - }, - }, - }, - }; - - const response = await callCluster('search', params); - return get(response, 'hits.hits', []).map((hit: any) => { - return { - state: get(hit, '_source.cluster_state.status'), - clusterUuid: get(hit, '_source.cluster_uuid'), - }; - }); -} diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.test.ts index 7a9b61f37707b..78eb9773df15f 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.test.ts @@ -6,51 +6,21 @@ import { fetchClusters } from './fetch_clusters'; describe('fetchClusters', () => { - const clusterUuid = '1sdfds734'; - const clusterName = 'monitoring'; - it('return a list of clusters', async () => { const callCluster = jest.fn().mockImplementation(() => ({ - hits: { - hits: [ - { - _source: { - cluster_uuid: clusterUuid, - cluster_name: clusterName, - }, - }, - ], - }, - })); - const index = '.monitoring-es-*'; - const result = await fetchClusters(callCluster, index); - expect(result).toEqual([{ clusterUuid, clusterName }]); - }); - - it('return the metadata name if available', async () => { - const metadataName = 'custom-monitoring'; - const callCluster = jest.fn().mockImplementation(() => ({ - hits: { - hits: [ - { - _source: { - cluster_uuid: clusterUuid, - cluster_name: clusterName, - cluster_settings: { - cluster: { - metadata: { - display_name: metadataName, - }, - }, - }, + aggregations: { + clusters: { + buckets: [ + { + key: 'clusterA', }, - }, - ], + ], + }, }, })); const index = '.monitoring-es-*'; const result = await fetchClusters(callCluster, index); - expect(result).toEqual([{ clusterUuid, clusterName: metadataName }]); + expect(result).toEqual([{ clusterUuid: 'clusterA' }]); }); it('should limit the time period in the query', async () => { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts index d1513ac16fb15..8ef7339618a2c 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts @@ -4,21 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ import { get } from 'lodash'; -import { AlertCommonCluster } from '../../alerts/types'; +import { AlertCluster } from '../../alerts/types'; -export async function fetchClusters( - callCluster: any, - index: string -): Promise { +interface AggregationResult { + key: string; +} + +export async function fetchClusters(callCluster: any, index: string): Promise { const params = { index, - filterPath: [ - 'hits.hits._source.cluster_settings.cluster.metadata.display_name', - 'hits.hits._source.cluster_uuid', - 'hits.hits._source.cluster_name', - ], + filterPath: 'aggregations.clusters.buckets', body: { - size: 1000, + size: 0, query: { bool: { filter: [ @@ -37,21 +34,19 @@ export async function fetchClusters( ], }, }, - collapse: { - field: 'cluster_uuid', + aggs: { + clusters: { + terms: { + field: 'cluster_uuid', + size: 1000, + }, + }, }, }, }; const response = await callCluster('search', params); - return get(response, 'hits.hits', []).map((hit: any) => { - const clusterName: string = - get(hit, '_source.cluster_settings.cluster.metadata.display_name') || - get(hit, '_source.cluster_name') || - get(hit, '_source.cluster_uuid'); - return { - clusterUuid: get(hit, '_source.cluster_uuid'), - clusterName, - }; - }); + return get(response, 'aggregations.clusters.buckets', []).map((bucket: AggregationResult) => ({ + clusterUuid: bucket.key, + })); } diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.test.ts index 9dcb4ffb82a5f..dd6c074e68b1f 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.test.ts @@ -6,28 +6,28 @@ import { fetchLicenses } from './fetch_licenses'; describe('fetchLicenses', () => { - const clusterName = 'MyCluster'; - const clusterUuid = 'clusterA'; - const license = { - status: 'active', - expiry_date_in_millis: 1579532493876, - type: 'basic', - }; - it('return a list of licenses', async () => { + const clusterName = 'MyCluster'; + const clusterUuid = 'clusterA'; + const license = { + status: 'active', + expiry_date_in_millis: 1579532493876, + type: 'basic', + }; const callCluster = jest.fn().mockImplementation(() => ({ hits: { hits: [ { _source: { license, + cluster_name: clusterName, cluster_uuid: clusterUuid, }, }, ], }, })); - const clusters = [{ clusterUuid, clusterName }]; + const clusters = [{ clusterUuid }]; const index = '.monitoring-es-*'; const result = await fetchLicenses(callCluster, clusters, index); expect(result).toEqual([ @@ -36,13 +36,15 @@ describe('fetchLicenses', () => { type: license.type, expiryDateMS: license.expiry_date_in_millis, clusterUuid, + clusterName, }, ]); }); it('should only search for the clusters provided', async () => { + const clusterUuid = 'clusterA'; const callCluster = jest.fn(); - const clusters = [{ clusterUuid, clusterName }]; + const clusters = [{ clusterUuid }]; const index = '.monitoring-es-*'; await fetchLicenses(callCluster, clusters, index); const params = callCluster.mock.calls[0][1]; @@ -50,11 +52,54 @@ describe('fetchLicenses', () => { }); it('should limit the time period in the query', async () => { + const clusterUuid = 'clusterA'; const callCluster = jest.fn(); - const clusters = [{ clusterUuid, clusterName }]; + const clusters = [{ clusterUuid }]; const index = '.monitoring-es-*'; await fetchLicenses(callCluster, clusters, index); const params = callCluster.mock.calls[0][1]; expect(params.body.query.bool.filter[2].range.timestamp.gte).toBe('now-2m'); }); + + it('should give priority to the metadata name', async () => { + const clusterName = 'MyCluster'; + const clusterUuid = 'clusterA'; + const license = { + status: 'active', + expiry_date_in_millis: 1579532493876, + type: 'basic', + }; + const callCluster = jest.fn().mockImplementation(() => ({ + hits: { + hits: [ + { + _source: { + license, + cluster_name: 'fakeName', + cluster_uuid: clusterUuid, + cluster_settings: { + cluster: { + metadata: { + display_name: clusterName, + }, + }, + }, + }, + }, + ], + }, + })); + const clusters = [{ clusterUuid }]; + const index = '.monitoring-es-*'; + const result = await fetchLicenses(callCluster, clusters, index); + expect(result).toEqual([ + { + status: license.status, + type: license.type, + expiryDateMS: license.expiry_date_in_millis, + clusterUuid, + clusterName, + }, + ]); + }); }); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts index 5b05c907e796e..31a68e8aa9c3e 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts @@ -4,16 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ import { get } from 'lodash'; -import { AlertLicense, AlertCommonCluster } from '../../alerts/types'; +import { AlertLicense, AlertCluster } from '../../alerts/types'; export async function fetchLicenses( callCluster: any, - clusters: AlertCommonCluster[], + clusters: AlertCluster[], index: string ): Promise { const params = { index, - filterPath: ['hits.hits._source.license.*', 'hits.hits._source.cluster_uuid'], + filterPath: [ + 'hits.hits._source.license.*', + 'hits.hits._source.cluster_settings.cluster.metadata.display_name', + 'hits.hits._source.cluster_uuid', + 'hits.hits._source.cluster_name', + ], body: { size: 1, sort: [{ timestamp: { order: 'desc' } }], @@ -45,12 +50,17 @@ export async function fetchLicenses( const response = await callCluster('search', params); return get(response, 'hits.hits', []).map((hit: any) => { + const clusterName: string = + get(hit, '_source.cluster_settings.cluster.metadata.display_name') || + get(hit, '_source.cluster_name') || + get(hit, '_source.cluster_uuid'); const rawLicense: any = get(hit, '_source.license', {}); const license: AlertLicense = { status: rawLicense.status, type: rawLicense.type, expiryDateMS: rawLicense.expiry_date_in_millis, clusterUuid: get(hit, '_source.cluster_uuid'), + clusterName, }; return license; }); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.test.ts deleted file mode 100644 index a3bcb61afacd6..0000000000000 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.test.ts +++ /dev/null @@ -1,122 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { fetchStatus } from './fetch_status'; -import { AlertCommonPerClusterState } from '../../alerts/types'; - -describe('fetchStatus', () => { - const alertType = 'monitoringTest'; - const log = { warn: jest.fn() }; - const start = 0; - const end = 0; - const id = 1; - const defaultUiState = { - isFiring: false, - severity: 0, - message: null, - resolvedMS: 0, - lastCheckedMS: 0, - triggeredMS: 0, - }; - const alertsClient = { - find: jest.fn(() => ({ - total: 1, - data: [ - { - id, - }, - ], - })), - getAlertState: jest.fn(() => ({ - alertTypeState: { - state: { - ui: defaultUiState, - } as AlertCommonPerClusterState, - }, - })), - }; - - afterEach(() => { - (alertsClient.find as jest.Mock).mockClear(); - (alertsClient.getAlertState as jest.Mock).mockClear(); - }); - - it('should fetch from the alerts client', async () => { - const status = await fetchStatus(alertsClient as any, [alertType], start, end, log as any); - expect(status).toEqual([]); - }); - - it('should return alerts that are firing', async () => { - alertsClient.getAlertState = jest.fn(() => ({ - alertTypeState: { - state: { - ui: { - ...defaultUiState, - isFiring: true, - }, - } as AlertCommonPerClusterState, - }, - })); - - const status = await fetchStatus(alertsClient as any, [alertType], start, end, log as any); - expect(status.length).toBe(1); - expect(status[0].type).toBe(alertType); - expect(status[0].isFiring).toBe(true); - }); - - it('should return alerts that have been resolved in the time period', async () => { - alertsClient.getAlertState = jest.fn(() => ({ - alertTypeState: { - state: { - ui: { - ...defaultUiState, - resolvedMS: 1500, - }, - } as AlertCommonPerClusterState, - }, - })); - - const customStart = 1000; - const customEnd = 2000; - - const status = await fetchStatus( - alertsClient as any, - [alertType], - customStart, - customEnd, - log as any - ); - expect(status.length).toBe(1); - expect(status[0].type).toBe(alertType); - expect(status[0].isFiring).toBe(false); - }); - - it('should pass in the right filter to the alerts client', async () => { - await fetchStatus(alertsClient as any, [alertType], start, end, log as any); - expect((alertsClient.find as jest.Mock).mock.calls[0][0].options.filter).toBe( - `alert.attributes.alertTypeId:${alertType}` - ); - }); - - it('should return nothing if no alert state is found', async () => { - alertsClient.getAlertState = jest.fn(() => ({ - alertTypeState: null, - })) as any; - - const status = await fetchStatus(alertsClient as any, [alertType], start, end, log as any); - expect(status).toEqual([]); - }); - - it('should return nothing if no alerts are found', async () => { - alertsClient.find = jest.fn(() => ({ - total: 0, - data: [], - })) as any; - - const status = await fetchStatus(alertsClient as any, [alertType], start, end, log as any); - expect(status).toEqual([]); - }); -}); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts index bf6ee965d3b2f..9f7c1d5a994d2 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts @@ -4,53 +4,81 @@ * you may not use this file except in compliance with the Elastic License. */ import moment from 'moment'; -import { Logger } from '../../../../../../src/core/server'; -import { AlertCommonPerClusterState } from '../../alerts/types'; -import { AlertsClient } from '../../../../alerting/server'; +import { get } from 'lodash'; +import { AlertClusterState } from '../../alerts/types'; +import { ALERT_TYPES, LOGGING_TAG } from '../../../common/constants'; export async function fetchStatus( - alertsClient: AlertsClient, - alertTypes: string[], + callCluster: any, start: number, end: number, - log: Logger + clusterUuid: string, + server: any ): Promise { + // TODO: this shouldn't query task manager directly but rather + // use an api exposed by the alerting/actions plugin + // See https://github.com/elastic/kibana/issues/48442 const statuses = await Promise.all( - alertTypes.map( + ALERT_TYPES.map( type => new Promise(async (resolve, reject) => { - // We need to get the id from the alertTypeId - const alerts = await alertsClient.find({ - options: { - filter: `alert.attributes.alertTypeId:${type}`, - }, - }); - if (alerts.total === 0) { - return resolve(false); - } - - if (alerts.total !== 1) { - log.warn(`Found more than one alert for type ${type} which is unexpected.`); - } + try { + const params = { + index: '.kibana_task_manager', + filterPath: ['hits.hits._source.task.state'], + body: { + size: 1, + sort: [{ updated_at: { order: 'desc' } }], + query: { + bool: { + filter: [ + { + term: { + 'task.taskType': `alerting:${type}`, + }, + }, + ], + }, + }, + }, + }; - const id = alerts.data[0].id; - - // Now that we have the id, we can get the state - const states = await alertsClient.getAlertState({ id }); - if (!states || !states.alertTypeState) { - log.warn(`No alert states found for type ${type} which is unexpected.`); + const response = await callCluster('search', params); + const state = get(response, 'hits.hits[0]._source.task.state', '{}'); + const clusterState: AlertClusterState = get( + JSON.parse(state), + `alertTypeState.${clusterUuid}`, + { + expiredCheckDateMS: 0, + ui: { + isFiring: false, + message: null, + severity: 0, + resolvedMS: 0, + expirationTime: 0, + }, + } + ); + const isInBetween = moment(clusterState.ui.resolvedMS).isBetween(start, end); + if (clusterState.ui.isFiring || isInBetween) { + return resolve({ + type, + ...clusterState.ui, + }); + } + return resolve(false); + } catch (err) { + const reason = get(err, 'body.error.type'); + if (reason === 'index_not_found_exception') { + server.log( + ['error', LOGGING_TAG], + `Unable to fetch alerts. Alerts depends on task manager, which has not been started yet.` + ); + } else { + server.log(['error', LOGGING_TAG], err.message); + } return resolve(false); } - - const state = Object.values(states.alertTypeState)[0] as AlertCommonPerClusterState; - const isInBetween = moment(state.ui.resolvedMS).isBetween(start, end); - if (state.ui.isFiring || isInBetween) { - return resolve({ - type, - ...state.ui, - }); - } - return resolve(false); }) ) ); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.test.ts deleted file mode 100644 index 1840a2026a753..0000000000000 --- a/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.test.ts +++ /dev/null @@ -1,163 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { getPreparedAlert } from './get_prepared_alert'; -import { fetchClusters } from './fetch_clusters'; -import { fetchDefaultEmailAddress } from './fetch_default_email_address'; - -jest.mock('./fetch_clusters', () => ({ - fetchClusters: jest.fn(), -})); - -jest.mock('./fetch_default_email_address', () => ({ - fetchDefaultEmailAddress: jest.fn(), -})); - -describe('getPreparedAlert', () => { - const uiSettings = { get: jest.fn() }; - const alertType = 'test'; - const getUiSettingsService = async () => ({ - asScopedToClient: () => uiSettings, - }); - const monitoringCluster = null; - const logger = { warn: jest.fn() }; - const ccsEnabled = false; - const services = { - callCluster: jest.fn(), - savedObjectsClient: null, - }; - const emailAddress = 'foo@foo.com'; - const data = [{ foo: 1 }]; - const dataFetcher = () => data; - const clusterName = 'MonitoringCluster'; - const clusterUuid = 'sdf34sdf'; - const clusters = [{ clusterName, clusterUuid }]; - - afterEach(() => { - (uiSettings.get as jest.Mock).mockClear(); - (services.callCluster as jest.Mock).mockClear(); - (fetchClusters as jest.Mock).mockClear(); - (fetchDefaultEmailAddress as jest.Mock).mockClear(); - }); - - beforeEach(() => { - (fetchClusters as jest.Mock).mockImplementation(() => clusters); - (fetchDefaultEmailAddress as jest.Mock).mockImplementation(() => emailAddress); - }); - - it('should return fields as expected', async () => { - (uiSettings.get as jest.Mock).mockImplementation(() => { - return emailAddress; - }); - - const alert = await getPreparedAlert( - alertType, - getUiSettingsService as any, - monitoringCluster as any, - logger as any, - ccsEnabled, - services as any, - dataFetcher as any - ); - - expect(alert && alert.emailAddress).toBe(emailAddress); - expect(alert && alert.data).toBe(data); - }); - - it('should add ccs if specified', async () => { - const ccsClusterName = 'remoteCluster'; - (services.callCluster as jest.Mock).mockImplementation(() => { - return { - [ccsClusterName]: { - connected: true, - }, - }; - }); - - await getPreparedAlert( - alertType, - getUiSettingsService as any, - monitoringCluster as any, - logger as any, - true, - services as any, - dataFetcher as any - ); - - expect((fetchClusters as jest.Mock).mock.calls[0][1].includes(ccsClusterName)).toBe(true); - }); - - it('should ignore ccs if no remote clusters are available', async () => { - const ccsClusterName = 'remoteCluster'; - (services.callCluster as jest.Mock).mockImplementation(() => { - return { - [ccsClusterName]: { - connected: false, - }, - }; - }); - - await getPreparedAlert( - alertType, - getUiSettingsService as any, - monitoringCluster as any, - logger as any, - true, - services as any, - dataFetcher as any - ); - - expect((fetchClusters as jest.Mock).mock.calls[0][1].includes(ccsClusterName)).toBe(false); - }); - - it('should pass in the clusters into the data fetcher', async () => { - const customDataFetcher = jest.fn(() => data); - - await getPreparedAlert( - alertType, - getUiSettingsService as any, - monitoringCluster as any, - logger as any, - true, - services as any, - customDataFetcher as any - ); - - expect((customDataFetcher as jest.Mock).mock.calls[0][1]).toBe(clusters); - }); - - it('should return nothing if the data fetcher returns nothing', async () => { - const customDataFetcher = jest.fn(() => []); - - const result = await getPreparedAlert( - alertType, - getUiSettingsService as any, - monitoringCluster as any, - logger as any, - true, - services as any, - customDataFetcher as any - ); - - expect(result).toBe(null); - }); - - it('should return nothing if there is no email address', async () => { - (fetchDefaultEmailAddress as jest.Mock).mockImplementation(() => null); - - const result = await getPreparedAlert( - alertType, - getUiSettingsService as any, - monitoringCluster as any, - logger as any, - true, - services as any, - dataFetcher as any - ); - - expect(result).toBe(null); - }); -}); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.ts b/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.ts deleted file mode 100644 index 83a9e26e4c589..0000000000000 --- a/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.ts +++ /dev/null @@ -1,87 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Logger, ICustomClusterClient, UiSettingsServiceStart } from 'kibana/server'; -import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; -import { AlertServices } from '../../../../alerting/server'; -import { AlertCommonCluster } from '../../alerts/types'; -import { INDEX_PATTERN_ELASTICSEARCH } from '../../../common/constants'; -import { fetchAvailableCcs } from './fetch_available_ccs'; -import { getCcsIndexPattern } from './get_ccs_index_pattern'; -import { fetchClusters } from './fetch_clusters'; -import { fetchDefaultEmailAddress } from './fetch_default_email_address'; - -export interface PreparedAlert { - emailAddress: string; - clusters: AlertCommonCluster[]; - data: any[]; - timezone: string; - dateFormat: string; -} - -async function getCallCluster( - monitoringCluster: ICustomClusterClient, - services: Pick -): Promise { - if (!monitoringCluster) { - return services.callCluster; - } - - return monitoringCluster.callAsInternalUser; -} - -export async function getPreparedAlert( - alertType: string, - getUiSettingsService: () => Promise, - monitoringCluster: ICustomClusterClient, - logger: Logger, - ccsEnabled: boolean, - services: Pick, - dataFetcher: ( - callCluster: CallCluster, - clusters: AlertCommonCluster[], - esIndexPattern: string - ) => Promise -): Promise { - const callCluster = await getCallCluster(monitoringCluster, services); - - // Support CCS use cases by querying to find available remote clusters - // and then adding those to the index pattern we are searching against - let esIndexPattern = INDEX_PATTERN_ELASTICSEARCH; - if (ccsEnabled) { - const availableCcs = await fetchAvailableCcs(callCluster); - if (availableCcs.length > 0) { - esIndexPattern = getCcsIndexPattern(esIndexPattern, availableCcs); - } - } - - const clusters = await fetchClusters(callCluster, esIndexPattern); - - // Fetch the specific data - const data = await dataFetcher(callCluster, clusters, esIndexPattern); - if (data.length === 0) { - logger.warn(`No data found for ${alertType}.`); - return null; - } - - const uiSettings = (await getUiSettingsService()).asScopedToClient(services.savedObjectsClient); - const dateFormat: string = await uiSettings.get('dateFormat'); - const timezone: string = await uiSettings.get('dateFormat:tz'); - const emailAddress = await fetchDefaultEmailAddress(uiSettings); - if (!emailAddress) { - // TODO: we can do more here - logger.warn(`Unable to send email for ${alertType} because there is no email configured.`); - return null; - } - - return { - emailAddress, - data, - clusters, - dateFormat, - timezone, - }; -} diff --git a/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.test.ts index 6c0301b6cc347..1a2eb1e44be84 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.test.ts @@ -39,26 +39,17 @@ describe('licenseExpiration lib', () => { }); describe('getUiMessage', () => { + const timezone = 'Europe/London'; + const license: any = { expiryDateMS: moment.tz('2020-01-20 08:00:00', timezone).utc() }; + it('should return a message when firing', () => { - const message = getUiMessage(false); - expect(message.text).toBe( - `This cluster's license is going to expire in #relative at #absolute. #start_linkPlease update your license#end_link` - ); - // LOL How do I avoid this in TS???? - if (!message.tokens) { - return expect(false).toBe(true); - } - expect(message.tokens.length).toBe(3); - expect(message.tokens[0].startToken).toBe('#relative'); - expect(message.tokens[1].startToken).toBe('#absolute'); - expect(message.tokens[2].startToken).toBe('#start_link'); - expect(message.tokens[2].endToken).toBe('#end_link'); + const message = getUiMessage(license, timezone, false); + expect(message).toBe(`This cluster's license is going to expire in #relative at #absolute.`); }); it('should return a message when resolved', () => { - const message = getUiMessage(true); - expect(message.text).toBe(`This cluster's license is active.`); - expect(message.tokens).not.toBeDefined(); + const message = getUiMessage(license, timezone, true); + expect(message).toBe(`This cluster's license is active.`); }); }); }); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts b/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts index a590021a2f29b..41b68d69bbd25 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts @@ -6,13 +6,7 @@ import { Moment } from 'moment-timezone'; import { i18n } from '@kbn/i18n'; import { AlertInstance } from '../../../../alerting/server'; -import { - AlertCommonPerClusterMessageLinkToken, - AlertCommonPerClusterMessageTimeToken, - AlertCommonCluster, - AlertCommonPerClusterMessage, -} from '../../alerts/types'; -import { AlertCommonPerClusterMessageTokenType } from '../../alerts/enums'; +import { AlertLicense } from '../../alerts/types'; const RESOLVED_SUBJECT = i18n.translate( 'xpack.monitoring.alerts.licenseExpiration.resolvedSubject', @@ -27,7 +21,7 @@ const NEW_SUBJECT = i18n.translate('xpack.monitoring.alerts.licenseExpiration.ne export function executeActions( instance: AlertInstance, - cluster: AlertCommonCluster, + license: AlertLicense, $expiry: Moment, dateFormat: string, emailAddress: string, @@ -37,14 +31,14 @@ export function executeActions( instance.scheduleActions('default', { subject: RESOLVED_SUBJECT, message: `This cluster alert has been resolved: Cluster '${ - cluster.clusterName + license.clusterName }' license was going to expire on ${$expiry.format(dateFormat)}.`, to: emailAddress, }); } else { instance.scheduleActions('default', { subject: NEW_SUBJECT, - message: `Cluster '${cluster.clusterName}' license is going to expire on ${$expiry.format( + message: `Cluster '${license.clusterName}' license is going to expire on ${$expiry.format( dateFormat )}. Please update your license.`, to: emailAddress, @@ -52,43 +46,13 @@ export function executeActions( } } -export function getUiMessage(resolved: boolean = false): AlertCommonPerClusterMessage { +export function getUiMessage(license: AlertLicense, timezone: string, resolved: boolean = false) { if (resolved) { - return { - text: i18n.translate('xpack.monitoring.alerts.licenseExpiration.ui.resolvedMessage', { - defaultMessage: `This cluster's license is active.`, - }), - }; + return i18n.translate('xpack.monitoring.alerts.licenseExpiration.ui.resolvedMessage', { + defaultMessage: `This cluster's license is active.`, + }); } - const linkText = i18n.translate('xpack.monitoring.alerts.licenseExpiration.linkText', { - defaultMessage: 'Please update your license', + return i18n.translate('xpack.monitoring.alerts.licenseExpiration.ui.firingMessage', { + defaultMessage: `This cluster's license is going to expire in #relative at #absolute.`, }); - return { - text: i18n.translate('xpack.monitoring.alerts.licenseExpiration.ui.firingMessage', { - defaultMessage: `This cluster's license is going to expire in #relative at #absolute. #start_link{linkText}#end_link`, - values: { - linkText, - }, - }), - tokens: [ - { - startToken: '#relative', - type: AlertCommonPerClusterMessageTokenType.Time, - isRelative: true, - isAbsolute: false, - } as AlertCommonPerClusterMessageTimeToken, - { - startToken: '#absolute', - type: AlertCommonPerClusterMessageTokenType.Time, - isAbsolute: true, - isRelative: false, - } as AlertCommonPerClusterMessageTimeToken, - { - startToken: '#start_link', - endToken: '#end_link', - type: AlertCommonPerClusterMessageTokenType.Link, - url: 'license', - } as AlertCommonPerClusterMessageLinkToken, - ], - }; } diff --git a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js index 1bddede52207b..c5091c36c3bbe 100644 --- a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js +++ b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js @@ -29,7 +29,6 @@ import { CODE_PATH_BEATS, CODE_PATH_APM, KIBANA_ALERTING_ENABLED, - ALERT_TYPES, } from '../../../common/constants'; import { getApmsForClusters } from '../apm/get_apms_for_clusters'; import { i18n } from '@kbn/i18n'; @@ -103,8 +102,15 @@ export async function getClustersFromRequest( if (isInCodePath(codePaths, [CODE_PATH_ALERTS])) { if (KIBANA_ALERTING_ENABLED) { - const alertsClient = req.getAlertsClient ? req.getAlertsClient() : null; - cluster.alerts = await fetchStatus(alertsClient, ALERT_TYPES, start, end, req.logger); + const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); + const callCluster = (...args) => callWithRequest(req, ...args); + cluster.alerts = await fetchStatus( + callCluster, + start, + end, + cluster.cluster_uuid, + req.server + ); } else { cluster.alerts = await alertsClusterSearch( req, diff --git a/x-pack/plugins/monitoring/server/plugin.ts b/x-pack/plugins/monitoring/server/plugin.ts index 784226dca66fe..24d8bcaa4397c 100644 --- a/x-pack/plugins/monitoring/server/plugin.ts +++ b/x-pack/plugins/monitoring/server/plugin.ts @@ -47,7 +47,6 @@ import { PluginSetupContract as AlertingPluginSetupContract, } from '../../alerting/server'; import { getLicenseExpiration } from './alerts/license_expiration'; -import { getClusterState } from './alerts/cluster_state'; import { InfraPluginSetup } from '../../infra/server'; export interface LegacyAPI { @@ -155,17 +154,6 @@ export class Plugin { config.ui.ccs.enabled ) ); - plugins.alerting.registerType( - getClusterState( - async () => { - const coreStart = (await core.getStartServices())[0]; - return coreStart.uiSettings; - }, - cluster, - this.getLogger, - config.ui.ccs.enabled - ) - ); } // Initialize telemetry diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/alerts/alerts.js b/x-pack/plugins/monitoring/server/routes/api/v1/alerts/alerts.js index d5a43d32f600a..56922bd8e87e2 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/alerts/alerts.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/alerts/alerts.js @@ -8,12 +8,8 @@ import { schema } from '@kbn/config-schema'; import { isFunction } from 'lodash'; import { ALERT_TYPE_LICENSE_EXPIRATION, - ALERT_TYPE_CLUSTER_STATE, MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS, - ALERT_TYPES, } from '../../../../../common/constants'; -import { handleError } from '../../../../lib/errors'; -import { fetchStatus } from '../../../../lib/alerts/fetch_status'; async function createAlerts(req, alertsClient, { selectedEmailActionId }) { const createdAlerts = []; @@ -21,21 +17,7 @@ async function createAlerts(req, alertsClient, { selectedEmailActionId }) { // Create alerts const ALERT_TYPES = { [ALERT_TYPE_LICENSE_EXPIRATION]: { - schedule: { interval: '1m' }, - actions: [ - { - group: 'default', - id: selectedEmailActionId, - params: { - subject: '{{context.subject}}', - message: `{{context.message}}`, - to: ['{{context.to}}'], - }, - }, - ], - }, - [ALERT_TYPE_CLUSTER_STATE]: { - schedule: { interval: '1m' }, + schedule: { interval: '10s' }, actions: [ { group: 'default', @@ -104,37 +86,4 @@ export function createKibanaAlertsRoute(server) { return { alerts, emailResponse }; }, }); - - server.route({ - method: 'POST', - path: '/api/monitoring/v1/alert_status', - config: { - validate: { - payload: schema.object({ - timeRange: schema.object({ - min: schema.string(), - max: schema.string(), - }), - }), - }, - }, - async handler(req, headers) { - const alertsClient = isFunction(req.getAlertsClient) ? req.getAlertsClient() : null; - if (!alertsClient) { - return headers.response().code(404); - } - - const start = req.payload.timeRange.min; - const end = req.payload.timeRange.max; - let alerts; - - try { - alerts = await fetchStatus(alertsClient, ALERT_TYPES, start, end, req.logger); - } catch (err) { - throw handleError(err, req); - } - - return { alerts }; - }, - }); } From b65de274580e01314d63700ce06c9c3779dad8f3 Mon Sep 17 00:00:00 2001 From: The SpaceCake Project Date: Mon, 6 Apr 2020 22:45:39 -0400 Subject: [PATCH 22/36] bc5 siem rules merge (#62679) * bc5 rule merge version changes field changes to endpoint rules removed max_signals from 7 rules * Fixing monitoring i18n (#62715) * Updates esarchiver test data with the latest rules (#62723) * Remove CR, only CRLF for rules * delete two files for Garrett * deletes delete 2 files (for Garrett) * Revert "deletes" This reverts commit cc2ac1e05fdfbaa72dd57e8dbfb21811cee5e1bc. * Revert "Fixing monitoring i18n (#62715)" This reverts commit 028574037aca6be5286f5a7c8738c81035a1c327. Co-authored-by: Elastic Machine Co-authored-by: Garrett Spong Co-authored-by: Ross Wolf <31489089+rw-access@users.noreply.github.com> --- .../403_response_to_a_post.json | 2 +- .../405_response_method_not_allowed.json | 2 +- ..._security_adversary_behavior_detected.json | 4 +- ...dpoint_security_cred_dumping_detected.json | 4 +- ...point_security_cred_dumping_prevented.json | 4 +- ...t_security_cred_manipulation_detected.json | 4 +- ..._security_cred_manipulation_prevented.json | 4 +- ...ic_endpoint_security_exploit_detected.json | 4 +- ...c_endpoint_security_exploit_prevented.json | 4 +- ...ic_endpoint_security_malware_detected.json | 4 +- ...c_endpoint_security_malware_prevented.json | 4 +- ...nt_security_permission_theft_detected.json | 4 +- ...t_security_permission_theft_prevented.json | 4 +- ...t_security_process_injection_detected.json | 4 +- ..._security_process_injection_prevented.json | 4 +- ...endpoint_security_ransomware_detected.json | 4 +- ...ndpoint_security_ransomware_prevented.json | 4 +- ...den_file_attribute_with_via_attribexe.json | 2 +- .../eql_clearing_windows_event_logs.json | 2 +- ...delete_volume_usn_journal_with_fsutil.json | 2 +- ...deleting_backup_catalogs_with_wbadmin.json | 2 +- .../eql_direct_outbound_smb_connection.json | 2 +- ...ble_windows_firewall_rules_with_netsh.json | 2 +- ...coding_or_decoding_files_via_certutil.json | 2 +- .../eql_local_scheduled_task_commands.json | 2 +- .../eql_local_service_commands.json | 2 +- ...ql_msbuild_making_network_connections.json | 2 +- .../eql_mshta_making_network_connections.json | 2 +- .../eql_psexec_lateral_movement_command.json | 2 +- .../eql_system_shells_via_services.json | 2 +- ...usual_network_connection_via_rundll32.json | 2 +- .../eql_unusual_parentchild_relationship.json | 2 +- ...ql_unusual_process_network_connection.json | 2 +- .../eql_user_account_creation.json | 2 +- ...ume_shadow_copy_deletion_via_vssadmin.json | 2 +- ..._volume_shadow_copy_deletion_via_wmic.json | 2 +- ...l_windows_script_executing_powershell.json | 2 +- .../linux_hping_activity.json | 2 +- .../linux_iodine_activity.json | 2 +- .../linux_kernel_module_activity.json | 2 +- .../linux_mknod_activity.json | 2 +- .../linux_netcat_network_connection.json | 2 +- .../linux_nmap_activity.json | 2 +- .../linux_nping_activity.json | 2 +- ...nux_process_started_in_temp_directory.json | 2 +- .../linux_shell_activity_by_web_server.json | 2 +- .../linux_socat_activity.json | 2 +- .../linux_strace_activity.json | 2 +- .../linux_tcpdump_activity.json | 2 +- .../linux_whoami_commmand.json | 2 +- .../prepackaged_rules/null_user_agent.json | 2 +- .../prepackaged_rules/sqlmap_user_agent.json | 2 +- .../windows_certutil_network_connection.json | 1 - ...and_prompt_connecting_to_the_internet.json | 2 +- ...s_command_shell_started_by_powershell.json | 2 +- ...dows_command_shell_started_by_svchost.json | 2 +- .../windows_credential_dumping_msbuild.json | 2 +- .../windows_cve_2020_0601.json | 1 - ...ws_defense_evasion_via_filter_manager.json | 2 +- ...dows_execution_via_compiled_html_file.json | 2 +- ...dows_execution_via_net_com_assemblies.json | 1 - ...ution_via_trusted_developer_utilities.json | 2 +- ...le_program_connecting_to_the_internet.json | 2 +- ...isc_lolbin_connecting_to_the_internet.json | 2 +- .../windows_modification_of_boot_config.json | 1 - .../windows_msxsl_network.json | 1 - .../windows_net_command_system_account.json | 1 - ..._persistence_via_application_shimming.json | 2 +- ...escalation_via_accessibility_features.json | 2 +- ...rocess_discovery_via_tasklist_command.json | 2 +- ...er_program_connecting_to_the_internet.json | 2 +- .../windows_uac_bypass_event_viewer.json | 1 - .../windows_whoami_command_activity.json | 2 +- .../prebuilt_rules_loaded/data.json.gz | Bin 40255 -> 41865 bytes .../prebuilt_rules_loaded/mappings.json | 3026 +---------------- 75 files changed, 224 insertions(+), 2971 deletions(-) diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/403_response_to_a_post.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/403_response_to_a_post.json index 3b043439759c1..d4118d0686b11 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/403_response_to_a_post.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/403_response_to_a_post.json @@ -20,5 +20,5 @@ "Elastic" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/405_response_method_not_allowed.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/405_response_method_not_allowed.json index 12c6a5feabebb..da27f0a71d281 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/405_response_method_not_allowed.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/405_response_method_not_allowed.json @@ -20,5 +20,5 @@ "Elastic" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_adversary_behavior_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_adversary_behavior_detected.json index a3302896b7e98..cfc322788d4be 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_adversary_behavior_detected.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_adversary_behavior_detected.json @@ -7,7 +7,7 @@ "interval": "10m", "language": "kuery", "name": "Adversary Behavior - Detected - Elastic Endpoint", - "query": "event.kind:alert and event.module:endgame and event.action:rules_engine_event", + "query": "event.kind:alert and event.module:endgame and (event.action:rules_engine_event or endgame.event_subtype_full:rules_engine_event)", "risk_score": 47, "rule_id": "77a3c3df-8ec4-4da4-b758-878f551dee69", "severity": "medium", @@ -16,5 +16,5 @@ "Endpoint" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_dumping_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_dumping_detected.json index 8c2c5f32feab7..0647fe9c9ce10 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_dumping_detected.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_dumping_detected.json @@ -7,7 +7,7 @@ "interval": "10m", "language": "kuery", "name": "Credential Dumping - Detected - Elastic Endpoint", - "query": "event.kind:alert and event.module:endgame and event.action:cred_theft_event and endgame.metadata.type:detection", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:detection and (event.action:cred_theft_event or endgame.event_subtype_full:cred_theft_event)", "risk_score": 73, "rule_id": "571afc56-5ed9-465d-a2a9-045f099f6e7e", "severity": "high", @@ -16,5 +16,5 @@ "Endpoint" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_dumping_prevented.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_dumping_prevented.json index 6a96da3218bf2..036c88688d9bd 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_dumping_prevented.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_dumping_prevented.json @@ -7,7 +7,7 @@ "interval": "10m", "language": "kuery", "name": "Credential Dumping - Prevented - Elastic Endpoint", - "query": "event.kind:alert and event.module:endgame and event.action:cred_theft_event and endgame.metadata.type:prevention", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:prevention and (event.action:cred_theft_event or endgame.event_subtype_full:cred_theft_event)", "risk_score": 47, "rule_id": "db8c33a8-03cd-4988-9e2c-d0a4863adb13", "severity": "medium", @@ -16,5 +16,5 @@ "Endpoint" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_manipulation_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_manipulation_detected.json index 954e35ccd644a..0fe610d551152 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_manipulation_detected.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_manipulation_detected.json @@ -7,7 +7,7 @@ "interval": "10m", "language": "kuery", "name": "Credential Manipulation - Detected - Elastic Endpoint", - "query": "event.kind:alert and event.module:endgame and event.action:token_manipulation_event and endgame.metadata.type:detection", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:detection and (event.action:token_manipulation_event or endgame.event_subtype_full:token_manipulation_event)", "risk_score": 73, "rule_id": "c0be5f31-e180-48ed-aa08-96b36899d48f", "severity": "high", @@ -16,5 +16,5 @@ "Endpoint" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_manipulation_prevented.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_manipulation_prevented.json index 0de35891a3e81..a317c77bcd90a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_manipulation_prevented.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_manipulation_prevented.json @@ -7,7 +7,7 @@ "interval": "10m", "language": "kuery", "name": "Credential Manipulation - Prevented - Elastic Endpoint", - "query": "event.kind:alert and event.module:endgame and event.action:token_manipulation_event and endgame.metadata.type:prevention", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:prevention and (event.action:token_manipulation_event or endgame.event_subtype_full:token_manipulation_event)", "risk_score": 47, "rule_id": "c9e38e64-3f4c-4bf3-ad48-0e61a60ea1fa", "severity": "medium", @@ -16,5 +16,5 @@ "Endpoint" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_exploit_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_exploit_detected.json index 3652b7068ecd2..97640c0cea9b2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_exploit_detected.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_exploit_detected.json @@ -7,7 +7,7 @@ "interval": "10m", "language": "kuery", "name": "Exploit - Detected - Elastic Endpoint", - "query": "event.kind:alert and event.module:endgame and event.action:exploit_event and endgame.metadata.type:detection", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:detection and (event.action:exploit_event or endgame.event_subtype_full:exploit_event)", "risk_score": 73, "rule_id": "2003cdc8-8d83-4aa5-b132-1f9a8eb48514", "severity": "high", @@ -16,5 +16,5 @@ "Endpoint" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_exploit_prevented.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_exploit_prevented.json index dbc910c3002a7..069687a5af00f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_exploit_prevented.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_exploit_prevented.json @@ -7,7 +7,7 @@ "interval": "10m", "language": "kuery", "name": "Exploit - Prevented - Elastic Endpoint", - "query": "event.kind:alert and event.module:endgame and event.action:exploit_event and endgame.metadata.type:prevention", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:prevention and (event.action:exploit_event or endgame.event_subtype_full:exploit_event)", "risk_score": 47, "rule_id": "2863ffeb-bf77-44dd-b7a5-93ef94b72036", "severity": "medium", @@ -16,5 +16,5 @@ "Endpoint" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_malware_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_malware_detected.json index efe2806532be0..a7d3371190ced 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_malware_detected.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_malware_detected.json @@ -7,7 +7,7 @@ "interval": "10m", "language": "kuery", "name": "Malware - Detected - Elastic Endpoint", - "query": "event.kind:alert and event.module:endgame and event.action:file_classification_event and endgame.metadata.type:detection", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:detection and (event.action:file_classification_event or endgame.event_subtype_full:file_classification_event)", "risk_score": 99, "rule_id": "0a97b20f-4144-49ea-be32-b540ecc445de", "severity": "critical", @@ -16,5 +16,5 @@ "Endpoint" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_malware_prevented.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_malware_prevented.json index 51028b9dbeeb3..dd7bf72c34f90 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_malware_prevented.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_malware_prevented.json @@ -7,7 +7,7 @@ "interval": "10m", "language": "kuery", "name": "Malware - Prevented - Elastic Endpoint", - "query": "event.kind:alert and event.module:endgame and event.action:file_classification_event and endgame.metadata.type:prevention", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:prevention and (event.action:file_classification_event or endgame.event_subtype_full:file_classification_event)", "risk_score": 73, "rule_id": "3b382770-efbb-44f4-beed-f5e0a051b895", "severity": "high", @@ -16,5 +16,5 @@ "Endpoint" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_permission_theft_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_permission_theft_detected.json index c30ca0632f410..a8e102cc4619d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_permission_theft_detected.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_permission_theft_detected.json @@ -7,7 +7,7 @@ "interval": "10m", "language": "kuery", "name": "Permission Theft - Detected - Elastic Endpoint", - "query": "event.kind:alert and event.module:endgame and event.action:token_protection_event and endgame.metadata.type:detection", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:detection and (event.action:token_protection_event or endgame.event_subtype_full:token_protection_event)", "risk_score": 73, "rule_id": "c3167e1b-f73c-41be-b60b-87f4df707fe3", "severity": "high", @@ -16,5 +16,5 @@ "Endpoint" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_permission_theft_prevented.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_permission_theft_prevented.json index ed0c714254743..c97330f2349eb 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_permission_theft_prevented.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_permission_theft_prevented.json @@ -7,7 +7,7 @@ "interval": "10m", "language": "kuery", "name": "Permission Theft - Prevented - Elastic Endpoint", - "query": "event.kind:alert and event.module:endgame and event.action:token_protection_event and endgame.metadata.type:prevention", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:prevention and (event.action:token_protection_event or endgame.event_subtype_full:token_protection_event)", "risk_score": 47, "rule_id": "453f659e-0429-40b1-bfdb-b6957286e04b", "severity": "medium", @@ -16,5 +16,5 @@ "Endpoint" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_process_injection_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_process_injection_detected.json index 63b008849487a..e644c0e8d66eb 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_process_injection_detected.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_process_injection_detected.json @@ -7,7 +7,7 @@ "interval": "10m", "language": "kuery", "name": "Process Injection - Detected - Elastic Endpoint", - "query": "event.kind:alert and event.module:endgame and event.action:kernel_shellcode_event and endgame.metadata.type:detection", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:detection and (event.action:kernel_shellcode_event or endgame.event_subtype_full:kernel_shellcode_event)", "risk_score": 73, "rule_id": "80c52164-c82a-402c-9964-852533d58be1", "severity": "high", @@ -16,5 +16,5 @@ "Endpoint" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_process_injection_prevented.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_process_injection_prevented.json index 135b4a95e8005..61cbe267f9a46 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_process_injection_prevented.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_process_injection_prevented.json @@ -7,7 +7,7 @@ "interval": "10m", "language": "kuery", "name": "Process Injection - Prevented - Elastic Endpoint", - "query": "event.kind:alert and event.module:endgame and event.action:kernel_shellcode_event and endgame.metadata.type:prevention", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:prevention and (event.action:kernel_shellcode_event or endgame.event_subtype_full:kernel_shellcode_event)", "risk_score": 47, "rule_id": "990838aa-a953-4f3e-b3cb-6ddf7584de9e", "severity": "medium", @@ -16,5 +16,5 @@ "Endpoint" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_ransomware_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_ransomware_detected.json index d4042a5e6b9e1..0e88b26cb2c75 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_ransomware_detected.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_ransomware_detected.json @@ -7,7 +7,7 @@ "interval": "10m", "language": "kuery", "name": "Ransomware - Detected - Elastic Endpoint", - "query": "event.kind:alert and event.module:endgame and event.action:ransomware_event and endgame.metadata.type:detection", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:detection and (event.action:ransomware_event or endgame.event_subtype_full:ransomware_event)", "risk_score": 99, "rule_id": "8cb4f625-7743-4dfb-ae1b-ad92be9df7bd", "severity": "critical", @@ -16,5 +16,5 @@ "Endpoint" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_ransomware_prevented.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_ransomware_prevented.json index befdf611da223..ba341f059f26d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_ransomware_prevented.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_ransomware_prevented.json @@ -7,7 +7,7 @@ "interval": "10m", "language": "kuery", "name": "Ransomware - Prevented - Elastic Endpoint", - "query": "event.kind:alert and event.module:endgame and event.action:ransomware_event and endgame.metadata.type:prevention", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:prevention and (event.action:ransomware_event or endgame.event_subtype_full:ransomware_event)", "risk_score": 73, "rule_id": "e3c5d5cb-41d5-4206-805c-f30561eae3ac", "severity": "high", @@ -16,5 +16,5 @@ "Endpoint" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adding_the_hidden_file_attribute_with_via_attribexe.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adding_the_hidden_file_attribute_with_via_attribexe.json index 6c9b54b8ddb02..25d2232d3f6dc 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adding_the_hidden_file_attribute_with_via_attribexe.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adding_the_hidden_file_attribute_with_via_attribexe.json @@ -46,5 +46,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_clearing_windows_event_logs.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_clearing_windows_event_logs.json index 244d329cc4bb7..1c73d6c276ce6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_clearing_windows_event_logs.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_clearing_windows_event_logs.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_delete_volume_usn_journal_with_fsutil.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_delete_volume_usn_journal_with_fsutil.json index 4087542816588..0bfa18398eada 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_delete_volume_usn_journal_with_fsutil.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_delete_volume_usn_journal_with_fsutil.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_deleting_backup_catalogs_with_wbadmin.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_deleting_backup_catalogs_with_wbadmin.json index eca06723e68b8..e7293eda6390f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_deleting_backup_catalogs_with_wbadmin.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_deleting_backup_catalogs_with_wbadmin.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_direct_outbound_smb_connection.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_direct_outbound_smb_connection.json index e37c877c62889..2896d27e19112 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_direct_outbound_smb_connection.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_direct_outbound_smb_connection.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_disable_windows_firewall_rules_with_netsh.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_disable_windows_firewall_rules_with_netsh.json index f6b4bc67ed9b1..42fe51f4e0373 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_disable_windows_firewall_rules_with_netsh.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_disable_windows_firewall_rules_with_netsh.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_encoding_or_decoding_files_via_certutil.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_encoding_or_decoding_files_via_certutil.json index 38162889737ff..eef112503da5b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_encoding_or_decoding_files_via_certutil.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_encoding_or_decoding_files_via_certutil.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_scheduled_task_commands.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_scheduled_task_commands.json index 42007f153bd55..dbacb2537e60f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_scheduled_task_commands.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_scheduled_task_commands.json @@ -34,5 +34,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_service_commands.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_service_commands.json index 9559baabe0e40..648e83b4a5267 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_service_commands.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_service_commands.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msbuild_making_network_connections.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msbuild_making_network_connections.json index 3e34aacf605c7..5e8b260d44b55 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msbuild_making_network_connections.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msbuild_making_network_connections.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_mshta_making_network_connections.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_mshta_making_network_connections.json index 769614e8faf53..88bd248e258d8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_mshta_making_network_connections.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_mshta_making_network_connections.json @@ -34,5 +34,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_psexec_lateral_movement_command.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_psexec_lateral_movement_command.json index ac170665042f6..f763d2aa03363 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_psexec_lateral_movement_command.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_psexec_lateral_movement_command.json @@ -49,5 +49,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_system_shells_via_services.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_system_shells_via_services.json index 1c001caa1539c..f1b1879fc2652 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_system_shells_via_services.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_system_shells_via_services.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_network_connection_via_rundll32.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_network_connection_via_rundll32.json index 0165f4d7512e4..2a7960c939d01 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_network_connection_via_rundll32.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_network_connection_via_rundll32.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_parentchild_relationship.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_parentchild_relationship.json index 0b4bf9ff32945..9a28c87c77089 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_parentchild_relationship.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_parentchild_relationship.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_process_network_connection.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_process_network_connection.json index 2c88a2061844c..43a3d6f6af0b2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_process_network_connection.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_process_network_connection.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_account_creation.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_account_creation.json index 240df34419132..7054e7f67c358 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_account_creation.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_account_creation.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_vssadmin.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_vssadmin.json index e12c2e70138c9..24f1cb72504f3 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_vssadmin.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_vssadmin.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_wmic.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_wmic.json index 94b8846741e3e..bad3c65024e42 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_wmic.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_wmic.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_windows_script_executing_powershell.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_windows_script_executing_powershell.json index b0a754a662c0e..52323b169cb22 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_windows_script_executing_powershell.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_windows_script_executing_powershell.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_hping_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_hping_activity.json index bb8e8983661e6..04a56241ea6f6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_hping_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_hping_activity.json @@ -20,5 +20,5 @@ "Linux" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_iodine_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_iodine_activity.json index 4e49702855a76..80358cc775e3b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_iodine_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_iodine_activity.json @@ -20,5 +20,5 @@ "Linux" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_kernel_module_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_kernel_module_activity.json index cf8cd72b7aa6f..b50fcc4c9980b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_kernel_module_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_kernel_module_activity.json @@ -37,5 +37,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_mknod_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_mknod_activity.json index 3bd3848c07581..d65440e95ff17 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_mknod_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_mknod_activity.json @@ -20,5 +20,5 @@ "Linux" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_netcat_network_connection.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_netcat_network_connection.json index cd523b6594ccd..df8e46be7a1c3 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_netcat_network_connection.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_netcat_network_connection.json @@ -22,5 +22,5 @@ "Linux" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_nmap_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_nmap_activity.json index 604cfa172fd84..2e5c899ebc625 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_nmap_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_nmap_activity.json @@ -20,5 +20,5 @@ "Linux" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_nping_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_nping_activity.json index 8e71b5b906711..168b30121c4bb 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_nping_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_nping_activity.json @@ -20,5 +20,5 @@ "Linux" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_process_started_in_temp_directory.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_process_started_in_temp_directory.json index c50026d7736ae..0865ac6c70cb2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_process_started_in_temp_directory.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_process_started_in_temp_directory.json @@ -17,5 +17,5 @@ "Linux" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_shell_activity_by_web_server.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_shell_activity_by_web_server.json index 01f117e0a225b..e9c4c95bb9284 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_shell_activity_by_web_server.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_shell_activity_by_web_server.json @@ -37,5 +37,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_socat_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_socat_activity.json index a16b164e9ee4a..404fea63aff94 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_socat_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_socat_activity.json @@ -20,5 +20,5 @@ "Linux" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_strace_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_strace_activity.json index 9b18039b63fd0..fbdfa9e66682d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_strace_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_strace_activity.json @@ -20,5 +20,5 @@ "Linux" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_tcpdump_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_tcpdump_activity.json index 5ae48c8db9984..82771074e7c29 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_tcpdump_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_tcpdump_activity.json @@ -49,5 +49,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_whoami_commmand.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_whoami_commmand.json index 7fef4e813da98..7e7f041581eb0 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_whoami_commmand.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_whoami_commmand.json @@ -34,5 +34,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/null_user_agent.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/null_user_agent.json index afbbb2a34d545..01246de5595e9 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/null_user_agent.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/null_user_agent.json @@ -38,5 +38,5 @@ "Elastic" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/sqlmap_user_agent.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/sqlmap_user_agent.json index fd240262d021f..10412c19da1b1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/sqlmap_user_agent.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/sqlmap_user_agent.json @@ -20,5 +20,5 @@ "Elastic" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_certutil_network_connection.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_certutil_network_connection.json index 2cda21cf7d5ef..52a373e3aeb77 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_certutil_network_connection.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_certutil_network_connection.json @@ -4,7 +4,6 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, "name": "Network Connection via Certutil", "query": "process.name:certutil.exe and event.action:\"Network connection detected (rule: NetworkConnect)\" and not destination.ip:(10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16)", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_prompt_connecting_to_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_prompt_connecting_to_the_internet.json index 2427ab4d7cc55..2bee265a74e11 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_prompt_connecting_to_the_internet.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_prompt_connecting_to_the_internet.json @@ -49,5 +49,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_shell_started_by_powershell.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_shell_started_by_powershell.json index f8e5bd22576a4..d8f91dba7dd89 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_shell_started_by_powershell.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_shell_started_by_powershell.json @@ -46,5 +46,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_shell_started_by_svchost.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_shell_started_by_svchost.json index 71aafa9984ecb..6fd194ee2fa22 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_shell_started_by_svchost.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_shell_started_by_svchost.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_credential_dumping_msbuild.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_credential_dumping_msbuild.json index 4ff7891438554..43050e2769a24 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_credential_dumping_msbuild.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_credential_dumping_msbuild.json @@ -35,4 +35,4 @@ ], "type": "query", "version": 1 -} +} \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_cve_2020_0601.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_cve_2020_0601.json index c08bb7b3315f5..f5eb37c70d268 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_cve_2020_0601.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_cve_2020_0601.json @@ -4,7 +4,6 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, "name": "Windows CryptoAPI Spoofing Vulnerability (CVE-2020-0601 - CurveBall)", "query": "event.provider:\"Microsoft-Windows-Audit-CVE\" and message:\"[CVE-2020-0601]\"", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_defense_evasion_via_filter_manager.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_defense_evasion_via_filter_manager.json index 3f97f7aca74f6..0e8c5a5f2f631 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_defense_evasion_via_filter_manager.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_defense_evasion_via_filter_manager.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_compiled_html_file.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_compiled_html_file.json index 2b6e1fb3daaec..7755ff0233f7c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_compiled_html_file.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_compiled_html_file.json @@ -49,5 +49,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_net_com_assemblies.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_net_com_assemblies.json index c397c955fe64f..d6acb81c10e3f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_net_com_assemblies.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_net_com_assemblies.json @@ -4,7 +4,6 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, "name": "Execution via Regsvcs/Regasm", "query": "process.name:(RegAsm.exe or RegSvcs.exe) and event.action:\"Process Create (rule: ProcessCreate)\"", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_trusted_developer_utilities.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_trusted_developer_utilities.json index f60a986996d6f..87e38febb0743 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_trusted_developer_utilities.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_trusted_developer_utilities.json @@ -49,5 +49,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_html_help_executable_program_connecting_to_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_html_help_executable_program_connecting_to_the_internet.json index 4b3efead776d2..6c8cd0673256a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_html_help_executable_program_connecting_to_the_internet.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_html_help_executable_program_connecting_to_the_internet.json @@ -46,5 +46,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_misc_lolbin_connecting_to_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_misc_lolbin_connecting_to_the_internet.json index 0cd68ba5c1ed8..a0e311d8eb154 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_misc_lolbin_connecting_to_the_internet.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_misc_lolbin_connecting_to_the_internet.json @@ -46,5 +46,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_modification_of_boot_config.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_modification_of_boot_config.json index d761226276496..045a9789b1260 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_modification_of_boot_config.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_modification_of_boot_config.json @@ -4,7 +4,6 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, "name": "Modification of Boot Configuration", "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:bcdedit.exe and process.args:(/set and (bootstatuspolicy and ignoreallfailures or no and recoveryenabled))", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_msxsl_network.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_msxsl_network.json index 9b45d03aae375..e80dcde1e398d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_msxsl_network.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_msxsl_network.json @@ -4,7 +4,6 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, "name": "Network Connection via MsXsl", "query": "process.name:msxsl.exe and event.action:\"Network connection detected (rule: NetworkConnect)\" and not destination.ip:(10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16)", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_net_command_system_account.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_net_command_system_account.json index 390c9c278905c..c2379142df002 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_net_command_system_account.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_net_command_system_account.json @@ -4,7 +4,6 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, "name": "Net command via SYSTEM account", "query": "(process.name:net.exe or process.name:net1.exe and not process.parent.name:net.exe) and user.name:SYSTEM and event.action:\"Process Create (rule: ProcessCreate)\"", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_persistence_via_application_shimming.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_persistence_via_application_shimming.json index 0488667d06c82..2f44727f9e6f0 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_persistence_via_application_shimming.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_persistence_via_application_shimming.json @@ -46,5 +46,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_priv_escalation_via_accessibility_features.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_priv_escalation_via_accessibility_features.json index 26f0a0bcc245c..aeff071ed4514 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_priv_escalation_via_accessibility_features.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_priv_escalation_via_accessibility_features.json @@ -46,5 +46,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_process_discovery_via_tasklist_command.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_process_discovery_via_tasklist_command.json index 28ebdb44fddd2..3a883fa51b763 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_process_discovery_via_tasklist_command.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_process_discovery_via_tasklist_command.json @@ -34,5 +34,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_register_server_program_connecting_to_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_register_server_program_connecting_to_the_internet.json index 920ff28a9a9cd..1e061f2ef9463 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_register_server_program_connecting_to_the_internet.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_register_server_program_connecting_to_the_internet.json @@ -49,5 +49,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_uac_bypass_event_viewer.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_uac_bypass_event_viewer.json index 0d4168640bc60..df7a6fe1285d1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_uac_bypass_event_viewer.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_uac_bypass_event_viewer.json @@ -4,7 +4,6 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, "name": "Bypass UAC via Event Viewer", "query": "process.parent.name:eventvwr.exe and event.action:\"Process Create (rule: ProcessCreate)\" and not process.executable:(\"C:\\Windows\\SysWOW64\\mmc.exe\" or \"C:\\Windows\\System32\\mmc.exe\")", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_whoami_command_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_whoami_command_activity.json index 46af0c5b586a5..93ce1f83dd64e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_whoami_command_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_whoami_command_activity.json @@ -34,5 +34,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/test/siem_cypress/es_archives/prebuilt_rules_loaded/data.json.gz b/x-pack/test/siem_cypress/es_archives/prebuilt_rules_loaded/data.json.gz index 90d4cfe0f2734823f0acccd4ba4ea977fe939f4d..573c006d1507d9c84c581c9abab7d60d96bfebe3 100644 GIT binary patch literal 41865 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 ULoqGFCDyS252ti$R7;5k0Bqt4ng9R* diff --git a/x-pack/test/siem_cypress/es_archives/prebuilt_rules_loaded/mappings.json b/x-pack/test/siem_cypress/es_archives/prebuilt_rules_loaded/mappings.json index 99ef5863ba6c8..f4278c4d4318f 100644 --- a/x-pack/test/siem_cypress/es_archives/prebuilt_rules_loaded/mappings.json +++ b/x-pack/test/siem_cypress/es_archives/prebuilt_rules_loaded/mappings.json @@ -11,12 +11,13 @@ "migrationMappingPropertyHashes": { "action": "6e96ac5e648f57523879661ea72525b7", "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "agent_actions": "ed270b46812f0fa1439366c428a2cf17", "agent_configs": "38abaf89513877745c359e7700c0c66a", "agent_events": "3231653fafe4ef3196fe3b32ab774bf2", - "agents": "75c0f4a11560dbc38b65e5e1d98fc9da", + "agents": "c3eeb7b9d97176f15f6d126370ab23c7", "alert": "7b44fba6773e37c806ce290ea9b7024e", "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", - "apm-telemetry": "e8619030e08b671291af04c4603b4944", + "apm-telemetry": "3525d7c22c42bc80f5e6e9cb3f2b26a2", "application_usage_totals": "c897e4310c5f24b07caaff3db53ae2c1", "application_usage_transactional": "965839e75f809fefe04f92dc4d99722a", "canvas-element": "7390014e1091044523666d97247392fc", @@ -29,7 +30,7 @@ "dashboard": "d00f614b29a80360e1190193fd333bab", "datasources": "d4bc0c252b2b5683ff21ea32d00acffc", "enrollment_api_keys": "28b91e20b105b6f928e2012600085d8f", - "epm-package": "75d12cd13c867fd713d7dfb27366bc20", + "epm-package": "0be91c6758421dd5d0f1a58e9e5bc7c3", "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", "index-pattern": "66eccb05066c5a89924f48a9e9736499", @@ -50,6 +51,7 @@ "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", "search": "181661168bbadd1eff5902361e2a0d5c", "server": "ec97f1c5da1a19609a60874e5af1100c", + "siem-detection-engine-rule-actions": "90eee2e4635260f4be0a1da8f5bc0aa0", "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0", "siem-ui-timeline": "ac8020190f5950dd3250b6499144e7fb", "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", @@ -106,6 +108,25 @@ } } }, + "agent_actions": { + "properties": { + "agent_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "data": { + "type": "flattened" + }, + "sent_at": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, "agent_configs": { "properties": { "datasources": { @@ -179,26 +200,6 @@ "access_api_key_id": { "type": "keyword" }, - "actions": { - "properties": { - "created_at": { - "type": "date" - }, - "data": { - "type": "text" - }, - "id": { - "type": "keyword" - }, - "sent_at": { - "type": "date" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, "active": { "type": "boolean" }, @@ -361,7 +362,7 @@ "agent": { "properties": { "version": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" } } @@ -371,23 +372,31 @@ "framework": { "properties": { "composite": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "name": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "version": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" } } }, "language": { "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, "name": { - "ignore_above": 256, + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, "type": "keyword" } } @@ -395,15 +404,15 @@ "runtime": { "properties": { "composite": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "name": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "version": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" } } @@ -417,7 +426,7 @@ "agent": { "properties": { "version": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" } } @@ -427,15 +436,15 @@ "framework": { "properties": { "composite": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "name": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "version": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" } } @@ -443,15 +452,15 @@ "language": { "properties": { "composite": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "name": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "version": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" } } @@ -459,15 +468,15 @@ "runtime": { "properties": { "composite": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "name": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "version": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" } } @@ -481,7 +490,7 @@ "agent": { "properties": { "version": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" } } @@ -491,15 +500,15 @@ "framework": { "properties": { "composite": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "name": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "version": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" } } @@ -507,15 +516,15 @@ "language": { "properties": { "composite": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "name": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "version": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" } } @@ -523,15 +532,15 @@ "runtime": { "properties": { "composite": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "name": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "version": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" } } @@ -545,7 +554,7 @@ "agent": { "properties": { "version": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" } } @@ -555,15 +564,15 @@ "framework": { "properties": { "composite": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "name": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "version": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" } } @@ -571,15 +580,15 @@ "language": { "properties": { "composite": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "name": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "version": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" } } @@ -587,15 +596,15 @@ "runtime": { "properties": { "composite": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "name": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "version": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" } } @@ -609,7 +618,7 @@ "agent": { "properties": { "version": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" } } @@ -619,15 +628,15 @@ "framework": { "properties": { "composite": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "name": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "version": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" } } @@ -635,15 +644,15 @@ "language": { "properties": { "composite": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "name": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "version": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" } } @@ -651,15 +660,15 @@ "runtime": { "properties": { "composite": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "name": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "version": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" } } @@ -673,7 +682,7 @@ "agent": { "properties": { "version": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" } } @@ -683,15 +692,15 @@ "framework": { "properties": { "composite": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "name": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "version": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" } } @@ -699,15 +708,15 @@ "language": { "properties": { "composite": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "name": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "version": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" } } @@ -715,15 +724,15 @@ "runtime": { "properties": { "composite": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "name": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "version": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" } } @@ -737,7 +746,7 @@ "agent": { "properties": { "version": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" } } @@ -747,15 +756,15 @@ "framework": { "properties": { "composite": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "name": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "version": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" } } @@ -763,15 +772,15 @@ "language": { "properties": { "composite": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "name": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "version": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" } } @@ -779,15 +788,15 @@ "runtime": { "properties": { "composite": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "name": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "version": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" } } @@ -801,7 +810,7 @@ "agent": { "properties": { "version": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" } } @@ -811,15 +820,15 @@ "framework": { "properties": { "composite": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "name": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "version": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" } } @@ -827,15 +836,15 @@ "language": { "properties": { "composite": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "name": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "version": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" } } @@ -843,15 +852,15 @@ "runtime": { "properties": { "composite": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "name": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" }, "version": { - "ignore_above": 256, + "ignore_above": 1024, "type": "keyword" } } @@ -1565,15 +1574,6 @@ "properties": { "buildNum": { "type": "keyword" - }, - "dateFormat:tz": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" } } }, @@ -1750,6 +1750,9 @@ }, "type": "nested" }, + "internal": { + "type": "boolean" + }, "name": { "type": "keyword" }, @@ -2332,6 +2335,36 @@ } } }, + "siem-detection-engine-rule-actions": { + "properties": { + "actions": { + "properties": { + "action_type_id": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "params": { + "dynamic": "true", + "type": "object" + } + } + }, + "alertThrottle": { + "type": "keyword" + }, + "ruleAlertId": { + "type": "keyword" + }, + "ruleThrottle": { + "type": "keyword" + } + } + }, "siem-detection-engine-rule-status": { "properties": { "alertId": { @@ -2708,9 +2741,6 @@ } } }, - "spaceId": { - "type": "keyword" - }, "telemetry": { "properties": { "allowChangingOptInStatus": { @@ -2931,2774 +2961,4 @@ } } } -} - -{ - "type": "index", - "value": { - "aliases": { - ".siem-signals-default": { - "is_write_index": true - } - }, - "index": ".siem-signals-default-000001", - "mappings": { - "dynamic": "false", - "properties": { - "@timestamp": { - "type": "date" - }, - "agent": { - "properties": { - "ephemeral_id": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "as": { - "properties": { - "number": { - "type": "long" - }, - "organization": { - "properties": { - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "client": { - "properties": { - "address": { - "ignore_above": 1024, - "type": "keyword" - }, - "as": { - "properties": { - "number": { - "type": "long" - }, - "organization": { - "properties": { - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "bytes": { - "type": "long" - }, - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "geo": { - "properties": { - "city_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "continent_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "location": { - "type": "geo_point" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "ip": { - "type": "ip" - }, - "mac": { - "ignore_above": 1024, - "type": "keyword" - }, - "nat": { - "properties": { - "ip": { - "type": "ip" - }, - "port": { - "type": "long" - } - } - }, - "packets": { - "type": "long" - }, - "port": { - "type": "long" - }, - "registered_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "top_level_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "user": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "email": { - "ignore_above": 1024, - "type": "keyword" - }, - "full_name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "group": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "hash": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "cloud": { - "properties": { - "account": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "availability_zone": { - "ignore_above": 1024, - "type": "keyword" - }, - "instance": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "machine": { - "properties": { - "type": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "provider": { - "ignore_above": 1024, - "type": "keyword" - }, - "region": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "container": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "image": { - "properties": { - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "tag": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "labels": { - "type": "object" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "runtime": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "destination": { - "properties": { - "address": { - "ignore_above": 1024, - "type": "keyword" - }, - "as": { - "properties": { - "number": { - "type": "long" - }, - "organization": { - "properties": { - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "bytes": { - "type": "long" - }, - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "geo": { - "properties": { - "city_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "continent_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "location": { - "type": "geo_point" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "ip": { - "type": "ip" - }, - "mac": { - "ignore_above": 1024, - "type": "keyword" - }, - "nat": { - "properties": { - "ip": { - "type": "ip" - }, - "port": { - "type": "long" - } - } - }, - "packets": { - "type": "long" - }, - "port": { - "type": "long" - }, - "registered_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "top_level_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "user": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "email": { - "ignore_above": 1024, - "type": "keyword" - }, - "full_name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "group": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "hash": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "dns": { - "properties": { - "answers": { - "properties": { - "class": { - "ignore_above": 1024, - "type": "keyword" - }, - "data": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "ttl": { - "type": "long" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "header_flags": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "op_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "question": { - "properties": { - "class": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "registered_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "subdomain": { - "ignore_above": 1024, - "type": "keyword" - }, - "top_level_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "resolved_ip": { - "type": "ip" - }, - "response_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "ecs": { - "properties": { - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "error": { - "properties": { - "code": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "message": { - "norms": false, - "type": "text" - }, - "stack_trace": { - "doc_values": false, - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "index": false, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "event": { - "properties": { - "action": { - "ignore_above": 1024, - "type": "keyword" - }, - "category": { - "ignore_above": 1024, - "type": "keyword" - }, - "code": { - "ignore_above": 1024, - "type": "keyword" - }, - "created": { - "type": "date" - }, - "dataset": { - "ignore_above": 1024, - "type": "keyword" - }, - "duration": { - "type": "long" - }, - "end": { - "type": "date" - }, - "hash": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "ingested": { - "type": "date" - }, - "kind": { - "ignore_above": 1024, - "type": "keyword" - }, - "module": { - "ignore_above": 1024, - "type": "keyword" - }, - "original": { - "doc_values": false, - "ignore_above": 1024, - "index": false, - "type": "keyword" - }, - "outcome": { - "ignore_above": 1024, - "type": "keyword" - }, - "provider": { - "ignore_above": 1024, - "type": "keyword" - }, - "risk_score": { - "type": "float" - }, - "risk_score_norm": { - "type": "float" - }, - "sequence": { - "type": "long" - }, - "severity": { - "type": "long" - }, - "start": { - "type": "date" - }, - "timezone": { - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "file": { - "properties": { - "accessed": { - "type": "date" - }, - "attributes": { - "ignore_above": 1024, - "type": "keyword" - }, - "created": { - "type": "date" - }, - "ctime": { - "type": "date" - }, - "device": { - "ignore_above": 1024, - "type": "keyword" - }, - "directory": { - "ignore_above": 1024, - "type": "keyword" - }, - "drive_letter": { - "ignore_above": 1, - "type": "keyword" - }, - "extension": { - "ignore_above": 1024, - "type": "keyword" - }, - "gid": { - "ignore_above": 1024, - "type": "keyword" - }, - "group": { - "ignore_above": 1024, - "type": "keyword" - }, - "hash": { - "properties": { - "md5": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha1": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha256": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha512": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "inode": { - "ignore_above": 1024, - "type": "keyword" - }, - "mode": { - "ignore_above": 1024, - "type": "keyword" - }, - "mtime": { - "type": "date" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "owner": { - "ignore_above": 1024, - "type": "keyword" - }, - "path": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "size": { - "type": "long" - }, - "target_path": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - }, - "uid": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "geo": { - "properties": { - "city_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "continent_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "location": { - "type": "geo_point" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "group": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "hash": { - "properties": { - "md5": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha1": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha256": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha512": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "host": { - "properties": { - "architecture": { - "ignore_above": 1024, - "type": "keyword" - }, - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "geo": { - "properties": { - "city_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "continent_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "location": { - "type": "geo_point" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "hostname": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "ip": { - "type": "ip" - }, - "mac": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "os": { - "properties": { - "family": { - "ignore_above": 1024, - "type": "keyword" - }, - "full": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "kernel": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "platform": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - }, - "uptime": { - "type": "long" - }, - "user": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "email": { - "ignore_above": 1024, - "type": "keyword" - }, - "full_name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "group": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "hash": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "http": { - "properties": { - "request": { - "properties": { - "body": { - "properties": { - "bytes": { - "type": "long" - }, - "content": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "bytes": { - "type": "long" - }, - "method": { - "ignore_above": 1024, - "type": "keyword" - }, - "referrer": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "response": { - "properties": { - "body": { - "properties": { - "bytes": { - "type": "long" - }, - "content": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "bytes": { - "type": "long" - }, - "status_code": { - "type": "long" - } - } - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "labels": { - "type": "object" - }, - "log": { - "properties": { - "level": { - "ignore_above": 1024, - "type": "keyword" - }, - "logger": { - "ignore_above": 1024, - "type": "keyword" - }, - "origin": { - "properties": { - "file": { - "properties": { - "line": { - "type": "integer" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "function": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "original": { - "doc_values": false, - "ignore_above": 1024, - "index": false, - "type": "keyword" - }, - "syslog": { - "properties": { - "facility": { - "properties": { - "code": { - "type": "long" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "priority": { - "type": "long" - }, - "severity": { - "properties": { - "code": { - "type": "long" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - } - } - }, - "message": { - "norms": false, - "type": "text" - }, - "network": { - "properties": { - "application": { - "ignore_above": 1024, - "type": "keyword" - }, - "bytes": { - "type": "long" - }, - "community_id": { - "ignore_above": 1024, - "type": "keyword" - }, - "direction": { - "ignore_above": 1024, - "type": "keyword" - }, - "forwarded_ip": { - "type": "ip" - }, - "iana_number": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "packets": { - "type": "long" - }, - "protocol": { - "ignore_above": 1024, - "type": "keyword" - }, - "transport": { - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "observer": { - "properties": { - "geo": { - "properties": { - "city_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "continent_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "location": { - "type": "geo_point" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "hostname": { - "ignore_above": 1024, - "type": "keyword" - }, - "ip": { - "type": "ip" - }, - "mac": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "os": { - "properties": { - "family": { - "ignore_above": 1024, - "type": "keyword" - }, - "full": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "kernel": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "platform": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "product": { - "ignore_above": 1024, - "type": "keyword" - }, - "serial_number": { - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - }, - "vendor": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "organization": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "os": { - "properties": { - "family": { - "ignore_above": 1024, - "type": "keyword" - }, - "full": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "kernel": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "platform": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "package": { - "properties": { - "architecture": { - "ignore_above": 1024, - "type": "keyword" - }, - "build_version": { - "ignore_above": 1024, - "type": "keyword" - }, - "checksum": { - "ignore_above": 1024, - "type": "keyword" - }, - "description": { - "ignore_above": 1024, - "type": "keyword" - }, - "install_scope": { - "ignore_above": 1024, - "type": "keyword" - }, - "installed": { - "type": "date" - }, - "license": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "path": { - "ignore_above": 1024, - "type": "keyword" - }, - "reference": { - "ignore_above": 1024, - "type": "keyword" - }, - "size": { - "type": "long" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "process": { - "properties": { - "args": { - "ignore_above": 1024, - "type": "keyword" - }, - "args_count": { - "type": "long" - }, - "command_line": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "executable": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "exit_code": { - "type": "long" - }, - "hash": { - "properties": { - "md5": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha1": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha256": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha512": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "parent": { - "properties": { - "args": { - "ignore_above": 1024, - "type": "keyword" - }, - "args_count": { - "type": "long" - }, - "command_line": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "executable": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "exit_code": { - "type": "long" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "pgid": { - "type": "long" - }, - "pid": { - "type": "long" - }, - "ppid": { - "type": "long" - }, - "start": { - "type": "date" - }, - "thread": { - "properties": { - "id": { - "type": "long" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "title": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "uptime": { - "type": "long" - }, - "working_directory": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "pgid": { - "type": "long" - }, - "pid": { - "type": "long" - }, - "ppid": { - "type": "long" - }, - "start": { - "type": "date" - }, - "thread": { - "properties": { - "id": { - "type": "long" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "title": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "uptime": { - "type": "long" - }, - "working_directory": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "registry": { - "properties": { - "data": { - "properties": { - "bytes": { - "ignore_above": 1024, - "type": "keyword" - }, - "strings": { - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "hive": { - "ignore_above": 1024, - "type": "keyword" - }, - "key": { - "ignore_above": 1024, - "type": "keyword" - }, - "path": { - "ignore_above": 1024, - "type": "keyword" - }, - "value": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "related": { - "properties": { - "hash": { - "ignore_above": 1024, - "type": "keyword" - }, - "ip": { - "type": "ip" - }, - "user": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "rule": { - "properties": { - "category": { - "ignore_above": 1024, - "type": "keyword" - }, - "description": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "reference": { - "ignore_above": 1024, - "type": "keyword" - }, - "ruleset": { - "ignore_above": 1024, - "type": "keyword" - }, - "uuid": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "server": { - "properties": { - "address": { - "ignore_above": 1024, - "type": "keyword" - }, - "as": { - "properties": { - "number": { - "type": "long" - }, - "organization": { - "properties": { - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "bytes": { - "type": "long" - }, - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "geo": { - "properties": { - "city_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "continent_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "location": { - "type": "geo_point" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "ip": { - "type": "ip" - }, - "mac": { - "ignore_above": 1024, - "type": "keyword" - }, - "nat": { - "properties": { - "ip": { - "type": "ip" - }, - "port": { - "type": "long" - } - } - }, - "packets": { - "type": "long" - }, - "port": { - "type": "long" - }, - "registered_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "top_level_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "user": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "email": { - "ignore_above": 1024, - "type": "keyword" - }, - "full_name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "group": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "hash": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "service": { - "properties": { - "ephemeral_id": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "node": { - "properties": { - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "state": { - "ignore_above": 1024, - "type": "keyword" - }, - "type": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "signal": { - "properties": { - "ancestors": { - "properties": { - "depth": { - "type": "long" - }, - "id": { - "type": "keyword" - }, - "rule": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "original_event": { - "properties": { - "action": { - "type": "keyword" - }, - "category": { - "type": "keyword" - }, - "code": { - "type": "keyword" - }, - "created": { - "type": "date" - }, - "dataset": { - "type": "keyword" - }, - "duration": { - "type": "long" - }, - "end": { - "type": "date" - }, - "hash": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "kind": { - "type": "keyword" - }, - "module": { - "type": "keyword" - }, - "original": { - "doc_values": false, - "index": false, - "type": "keyword" - }, - "outcome": { - "type": "keyword" - }, - "provider": { - "type": "keyword" - }, - "risk_score": { - "type": "float" - }, - "risk_score_norm": { - "type": "float" - }, - "sequence": { - "type": "long" - }, - "severity": { - "type": "long" - }, - "start": { - "type": "date" - }, - "timezone": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "original_time": { - "type": "date" - }, - "parent": { - "properties": { - "depth": { - "type": "long" - }, - "id": { - "type": "keyword" - }, - "index": { - "type": "keyword" - }, - "rule": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "rule": { - "properties": { - "created_at": { - "type": "date" - }, - "created_by": { - "type": "keyword" - }, - "description": { - "type": "keyword" - }, - "enabled": { - "type": "keyword" - }, - "false_positives": { - "type": "keyword" - }, - "filters": { - "type": "object" - }, - "from": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "immutable": { - "type": "keyword" - }, - "index": { - "type": "keyword" - }, - "interval": { - "type": "keyword" - }, - "language": { - "type": "keyword" - }, - "max_signals": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "note": { - "type": "text" - }, - "output_index": { - "type": "keyword" - }, - "query": { - "type": "keyword" - }, - "references": { - "type": "keyword" - }, - "risk_score": { - "type": "keyword" - }, - "rule_id": { - "type": "keyword" - }, - "saved_id": { - "type": "keyword" - }, - "severity": { - "type": "keyword" - }, - "size": { - "type": "keyword" - }, - "tags": { - "type": "keyword" - }, - "threat": { - "properties": { - "framework": { - "type": "keyword" - }, - "tactic": { - "properties": { - "id": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "reference": { - "type": "keyword" - } - } - }, - "technique": { - "properties": { - "id": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "reference": { - "type": "keyword" - } - } - } - } - }, - "timeline_id": { - "type": "keyword" - }, - "timeline_title": { - "type": "keyword" - }, - "to": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - }, - "status": { - "type": "keyword" - } - } - }, - "source": { - "properties": { - "address": { - "ignore_above": 1024, - "type": "keyword" - }, - "as": { - "properties": { - "number": { - "type": "long" - }, - "organization": { - "properties": { - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "bytes": { - "type": "long" - }, - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "geo": { - "properties": { - "city_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "continent_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "country_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "location": { - "type": "geo_point" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_iso_code": { - "ignore_above": 1024, - "type": "keyword" - }, - "region_name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "ip": { - "type": "ip" - }, - "mac": { - "ignore_above": 1024, - "type": "keyword" - }, - "nat": { - "properties": { - "ip": { - "type": "ip" - }, - "port": { - "type": "long" - } - } - }, - "packets": { - "type": "long" - }, - "port": { - "type": "long" - }, - "registered_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "top_level_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "user": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "email": { - "ignore_above": 1024, - "type": "keyword" - }, - "full_name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "group": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "hash": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "tags": { - "ignore_above": 1024, - "type": "keyword" - }, - "threat": { - "properties": { - "framework": { - "ignore_above": 1024, - "type": "keyword" - }, - "tactic": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "reference": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "technique": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "reference": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "tls": { - "properties": { - "cipher": { - "ignore_above": 1024, - "type": "keyword" - }, - "client": { - "properties": { - "certificate": { - "ignore_above": 1024, - "type": "keyword" - }, - "certificate_chain": { - "ignore_above": 1024, - "type": "keyword" - }, - "hash": { - "properties": { - "md5": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha1": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha256": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "issuer": { - "ignore_above": 1024, - "type": "keyword" - }, - "ja3": { - "ignore_above": 1024, - "type": "keyword" - }, - "not_after": { - "type": "date" - }, - "not_before": { - "type": "date" - }, - "server_name": { - "ignore_above": 1024, - "type": "keyword" - }, - "subject": { - "ignore_above": 1024, - "type": "keyword" - }, - "supported_ciphers": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "curve": { - "ignore_above": 1024, - "type": "keyword" - }, - "established": { - "type": "boolean" - }, - "next_protocol": { - "ignore_above": 1024, - "type": "keyword" - }, - "resumed": { - "type": "boolean" - }, - "server": { - "properties": { - "certificate": { - "ignore_above": 1024, - "type": "keyword" - }, - "certificate_chain": { - "ignore_above": 1024, - "type": "keyword" - }, - "hash": { - "properties": { - "md5": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha1": { - "ignore_above": 1024, - "type": "keyword" - }, - "sha256": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "issuer": { - "ignore_above": 1024, - "type": "keyword" - }, - "ja3s": { - "ignore_above": 1024, - "type": "keyword" - }, - "not_after": { - "type": "date" - }, - "not_before": { - "type": "date" - }, - "subject": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - }, - "version_protocol": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "trace": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "transaction": { - "properties": { - "id": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "url": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "extension": { - "ignore_above": 1024, - "type": "keyword" - }, - "fragment": { - "ignore_above": 1024, - "type": "keyword" - }, - "full": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "original": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "password": { - "ignore_above": 1024, - "type": "keyword" - }, - "path": { - "ignore_above": 1024, - "type": "keyword" - }, - "port": { - "type": "long" - }, - "query": { - "ignore_above": 1024, - "type": "keyword" - }, - "registered_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "scheme": { - "ignore_above": 1024, - "type": "keyword" - }, - "top_level_domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "username": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "user": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "email": { - "ignore_above": 1024, - "type": "keyword" - }, - "full_name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "group": { - "properties": { - "domain": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "hash": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "user_agent": { - "properties": { - "device": { - "properties": { - "name": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "original": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "os": { - "properties": { - "family": { - "ignore_above": 1024, - "type": "keyword" - }, - "full": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "kernel": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "platform": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "vulnerability": { - "properties": { - "category": { - "ignore_above": 1024, - "type": "keyword" - }, - "classification": { - "ignore_above": 1024, - "type": "keyword" - }, - "description": { - "fields": { - "text": { - "norms": false, - "type": "text" - } - }, - "ignore_above": 1024, - "type": "keyword" - }, - "enumeration": { - "ignore_above": 1024, - "type": "keyword" - }, - "id": { - "ignore_above": 1024, - "type": "keyword" - }, - "reference": { - "ignore_above": 1024, - "type": "keyword" - }, - "report_id": { - "ignore_above": 1024, - "type": "keyword" - }, - "scanner": { - "properties": { - "vendor": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "score": { - "properties": { - "base": { - "type": "float" - }, - "environmental": { - "type": "float" - }, - "temporal": { - "type": "float" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "severity": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - }, - "settings": { - "index": { - "lifecycle": { - "name": ".siem-signals-default", - "rollover_alias": ".siem-signals-default" - }, - "number_of_replicas": "1", - "number_of_shards": "1" - } - } - } } \ No newline at end of file From 7882e0c3a817bafc9716647add8d8555c20899f8 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 6 Apr 2020 21:26:10 -0600 Subject: [PATCH 23/36] [Maps] Always show current zoom level (#62684) Co-authored-by: Elastic Machine --- .../view_control/view_control.js | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/view_control/view_control.js b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/view_control/view_control.js index 445e5be542ffb..19b60221ead36 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/view_control/view_control.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/view_control/view_control.js @@ -5,28 +5,33 @@ */ import _ from 'lodash'; -import React from 'react'; +import React, { Fragment } from 'react'; import { EuiText } from '@elastic/eui'; import { DECIMAL_DEGREES_PRECISION } from '../../../../common/constants'; import { FormattedMessage } from '@kbn/i18n/react'; export function ViewControl({ mouseCoordinates, zoom }) { - if (!mouseCoordinates) { - return null; + let latLon; + if (mouseCoordinates) { + latLon = ( + + + + {' '} + {_.round(mouseCoordinates.lat, DECIMAL_DEGREES_PRECISION)},{' '} + + + {' '} + {_.round(mouseCoordinates.lon, DECIMAL_DEGREES_PRECISION)},{' '} + + ); } return (
- - - {' '} - {_.round(mouseCoordinates.lat, DECIMAL_DEGREES_PRECISION)},{' '} - - - {' '} - {_.round(mouseCoordinates.lon, DECIMAL_DEGREES_PRECISION)},{' '} + {latLon} {' '} From 3f11d9c84b3463127858dee244660a7b07880e81 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 6 Apr 2020 21:26:55 -0600 Subject: [PATCH 24/36] [Maps] do not show circle border when symbol size is zero (#62644) Co-authored-by: Elastic Machine --- .../styles/vector/properties/dynamic_size_property.js | 10 +++++++--- .../styles/vector/properties/static_size_property.js | 8 ++++++-- .../maps/public/layers/styles/vector/vector_style.js | 7 +++++-- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js index 8b3f670bfa528..71ac25f0c6e61 100644 --- a/x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js +++ b/x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js @@ -99,9 +99,13 @@ export class DynamicSizeProperty extends DynamicStyleProperty { } } - syncCircleStrokeWidthWithMb(mbLayerId, mbMap) { - const lineWidth = this.getMbSizeExpression(); - mbMap.setPaintProperty(mbLayerId, 'circle-stroke-width', lineWidth); + syncCircleStrokeWidthWithMb(mbLayerId, mbMap, hasNoRadius) { + if (hasNoRadius) { + mbMap.setPaintProperty(mbLayerId, 'circle-stroke-width', 0); + } else { + const lineWidth = this.getMbSizeExpression(); + mbMap.setPaintProperty(mbLayerId, 'circle-stroke-width', lineWidth); + } } syncCircleRadiusWithMb(mbLayerId, mbMap) { diff --git a/x-pack/plugins/maps/public/layers/styles/vector/properties/static_size_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/static_size_property.js index 2383a5932cb9b..d86556c6218cf 100644 --- a/x-pack/plugins/maps/public/layers/styles/vector/properties/static_size_property.js +++ b/x-pack/plugins/maps/public/layers/styles/vector/properties/static_size_property.js @@ -35,8 +35,12 @@ export class StaticSizeProperty extends StaticStyleProperty { mbMap.setLayoutProperty(symbolLayerId, 'icon-size', this._options.size / halfIconPixels); } - syncCircleStrokeWidthWithMb(mbLayerId, mbMap) { - mbMap.setPaintProperty(mbLayerId, 'circle-stroke-width', this._options.size); + syncCircleStrokeWidthWithMb(mbLayerId, mbMap, hasNoRadius) { + if (hasNoRadius) { + mbMap.setPaintProperty(mbLayerId, 'circle-stroke-width', 0); + } else { + mbMap.setPaintProperty(mbLayerId, 'circle-stroke-width', this._options.size); + } } syncCircleRadiusWithMb(mbLayerId, mbMap) { diff --git a/x-pack/plugins/maps/public/layers/styles/vector/vector_style.js b/x-pack/plugins/maps/public/layers/styles/vector/vector_style.js index ae5d148e43cfd..b044c98d44d41 100644 --- a/x-pack/plugins/maps/public/layers/styles/vector/vector_style.js +++ b/x-pack/plugins/maps/public/layers/styles/vector/vector_style.js @@ -543,8 +543,11 @@ export class VectorStyle extends AbstractStyle { setMBPaintPropertiesForPoints({ alpha, mbMap, pointLayerId }) { this._fillColorStyleProperty.syncCircleColorWithMb(pointLayerId, mbMap, alpha); this._lineColorStyleProperty.syncCircleStrokeWithMb(pointLayerId, mbMap, alpha); - this._lineWidthStyleProperty.syncCircleStrokeWidthWithMb(pointLayerId, mbMap); - this._iconSizeStyleProperty.syncCircleRadiusWithMb(pointLayerId, mbMap); + const hasNoRadius = + !this._iconSizeStyleProperty.isDynamic() && + this._iconSizeStyleProperty.getOptions().size === 0; + this._lineWidthStyleProperty.syncCircleStrokeWidthWithMb(pointLayerId, mbMap, hasNoRadius); + this._iconSizeStyleProperty.syncCircleRadiusWithMb(pointLayerId, mbMap, hasNoRadius); } setMBPropertiesForLabelText({ alpha, mbMap, textLayerId }) { From dc013cb80f4256f7ffbcb8b814efee8590fc4a8a Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 6 Apr 2020 21:27:47 -0600 Subject: [PATCH 25/36] [Maps] Allow updating requestType for ESGeoGridSource (#62365) * [Maps] Allow updating requestType for ESGeoGridSource * re-add import removed from last merge Co-authored-by: Elastic Machine --- .../data_request_descriptor_types.d.ts | 17 ++++++++++++++++- .../es_geo_grid_source/create_source_editor.js | 3 +-- .../es_geo_grid_source/es_geo_grid_source.js | 6 ++++++ .../es_geo_grid_source/render_as_select.tsx | 7 +++++++ .../es_geo_grid_source/update_source_editor.js | 10 ++++++++++ .../maps/public/layers/sources/source.js | 4 ---- .../public/layers/sources/vector_source.d.ts | 3 +++ .../maps/public/layers/sources/vector_source.js | 4 ++++ 8 files changed, 47 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.d.ts b/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.d.ts index ca0e474491780..ceba2fe56db12 100644 --- a/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.d.ts +++ b/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.d.ts @@ -5,6 +5,7 @@ */ /* eslint-disable @typescript-eslint/consistent-type-definitions */ +import { RENDER_AS, SORT_ORDER, SCALING_TYPES } from '../constants'; import { MapExtent, MapQuery } from './map_descriptor'; // Global map state passed to every layer. @@ -18,12 +19,26 @@ export type MapFilters = { zoom: number; }; +type ESSearchSourceSyncMeta = { + sortField: string; + sortOrder: SORT_ORDER; + scalingType: SCALING_TYPES; + topHitsSplitField: string; + topHitsSize: number; +}; + +type ESGeoGridSourceSyncMeta = { + requestType: RENDER_AS; +}; + +export type VectorSourceSyncMeta = ESSearchSourceSyncMeta | ESGeoGridSourceSyncMeta; + export type VectorSourceRequestMeta = MapFilters & { applyGlobalQuery: boolean; fieldNames: string[]; geogridPrecision: number; sourceQuery: MapQuery; - sourceMeta: unknown; + sourceMeta: VectorSourceSyncMeta; }; export type VectorStyleRequestMeta = MapFilters & { diff --git a/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js index 4aec390bec745..265606dc87e0f 100644 --- a/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js +++ b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js @@ -9,7 +9,6 @@ import React, { Fragment, Component } from 'react'; import PropTypes from 'prop-types'; import { SingleFieldSelect } from '../../../components/single_field_select'; -import { RENDER_AS } from '../../../../common/constants'; import { getIndexPatternService, getIndexPatternSelectComponent } from '../../../kibana_services'; import { NoIndexPatternCallout } from '../../../components/no_index_pattern_callout'; import { i18n } from '@kbn/i18n'; @@ -155,7 +154,7 @@ export class CreateSourceEditor extends Component { } _renderRenderAsSelect() { - if (this.state.requestType === RENDER_AS.HEATMAP || !this.state.indexPattern) { + if (!this.state.indexPattern) { return null; } diff --git a/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js index dec802ac3cf1a..04f944396ab35 100644 --- a/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js +++ b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js @@ -70,6 +70,12 @@ export class ESGeoGridSource extends AbstractESAggSource { ); } + getSyncMeta() { + return { + requestType: this._descriptor.requestType, + }; + } + async getImmutableProperties() { let indexPatternTitle = this.getIndexPatternId(); try { diff --git a/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/render_as_select.tsx b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/render_as_select.tsx index c82781ede186f..899f4a797ea75 100644 --- a/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/render_as_select.tsx +++ b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/render_as_select.tsx @@ -27,7 +27,12 @@ const options = [ export function RenderAsSelect(props: { renderAs: RENDER_AS; onChange: (newValue: RENDER_AS) => void; + isColumnCompressed?: boolean; }) { + if (props.renderAs === RENDER_AS.HEATMAP) { + return null; + } + function onChange(selectedOptions: Array>) { if (!selectedOptions || !selectedOptions.length) { return; @@ -46,6 +51,7 @@ export function RenderAsSelect(props: { label={i18n.translate('xpack.maps.source.esGeoGrid.showAsLabel', { defaultMessage: 'Show as', })} + display={props.isColumnCompressed ? 'columnCompressed' : 'row'} > ); diff --git a/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js index cd494db3897fb..c0d6cba3a024a 100644 --- a/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js +++ b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js @@ -15,6 +15,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; import { isMetricCountable } from '../../util/is_metric_countable'; import { indexPatterns } from '../../../../../../../src/plugins/data/public'; +import { RenderAsSelect } from './render_as_select'; export class UpdateSourceEditor extends Component { state = { @@ -65,6 +66,10 @@ export class UpdateSourceEditor extends Component { this.props.onChange({ propName: 'resolution', value: e }); }; + _onRequestTypeSelect = requestType => { + this.props.onChange({ propName: 'requestType', value: requestType }); + }; + _renderMetricsPanel() { const metricsFilter = this.props.renderAs === RENDER_AS.HEATMAP @@ -113,6 +118,11 @@ export class UpdateSourceEditor extends Component { resolution={this.props.resolution} onChange={this._onResolutionChange} /> + diff --git a/x-pack/plugins/maps/public/layers/sources/source.js b/x-pack/plugins/maps/public/layers/sources/source.js index 368de421e23ce..3029a5c091202 100644 --- a/x-pack/plugins/maps/public/layers/sources/source.js +++ b/x-pack/plugins/maps/public/layers/sources/source.js @@ -111,10 +111,6 @@ export class AbstractSource { return 0; } - getSyncMeta() { - return {}; - } - isJoinable() { return false; } diff --git a/x-pack/plugins/maps/public/layers/sources/vector_source.d.ts b/x-pack/plugins/maps/public/layers/sources/vector_source.d.ts index 1400654297e01..d597e64277186 100644 --- a/x-pack/plugins/maps/public/layers/sources/vector_source.d.ts +++ b/x-pack/plugins/maps/public/layers/sources/vector_source.d.ts @@ -12,6 +12,7 @@ import { ESSearchSourceResponseMeta, MapExtent, VectorSourceRequestMeta, + VectorSourceSyncMeta, } from '../../../common/descriptor_types'; export type GeoJsonFetchMeta = ESSearchSourceResponseMeta; @@ -31,6 +32,7 @@ export interface IVectorSource extends ISource { getFields(): Promise; getFieldByName(fieldName: string): IField; + getSyncMeta(): VectorSourceSyncMeta; } export class AbstractVectorSource extends AbstractSource implements IVectorSource { @@ -43,4 +45,5 @@ export class AbstractVectorSource extends AbstractSource implements IVectorSourc getFields(): Promise; getFieldByName(fieldName: string): IField; + getSyncMeta(): VectorSourceSyncMeta; } diff --git a/x-pack/plugins/maps/public/layers/sources/vector_source.js b/x-pack/plugins/maps/public/layers/sources/vector_source.js index 7ff1c735c8613..7f97b1b21d189 100644 --- a/x-pack/plugins/maps/public/layers/sources/vector_source.js +++ b/x-pack/plugins/maps/public/layers/sources/vector_source.js @@ -151,4 +151,8 @@ export class AbstractVectorSource extends AbstractSource { getSourceTooltipContent(/* sourceDataRequest */) { return { tooltipContent: null, areResultsTrimmed: false }; } + + getSyncMeta() { + return {}; + } } From 04586263791175a14805680602e9310cc3dbe508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Tue, 7 Apr 2020 08:08:00 +0100 Subject: [PATCH 26/36] fixing bug (#62577) --- .../app/Settings/CustomizeUI/CustomLink/Title.tsx | 2 +- .../components/app/Settings/CustomizeUI/index.tsx | 4 ++-- .../apm/public/components/app/Settings/index.tsx | 4 ++-- .../TransactionActionMenu/CustomLink/index.test.tsx | 2 +- .../shared/TransactionActionMenu/CustomLink/index.tsx | 2 +- .../TransactionActionMenu/TransactionActionMenu.tsx | 11 +++++++---- 6 files changed, 14 insertions(+), 11 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/Title.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/Title.tsx index 17ec42b3e2016..07af7b0c0e7db 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/Title.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/Title.tsx @@ -28,7 +28,7 @@ export const Title = () => ( 'xpack.apm.settings.customizeUI.customLink.info', { defaultMessage: - "These links will be shown in the 'Actions' context menu for the transaction detail." + 'These links will be shown in the Actions context menu for transactions.' } )} /> diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/index.tsx index 1cd1298fdd549..350f2185fb3c8 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/index.tsx @@ -14,8 +14,8 @@ export const CustomizeUI = () => { <>

- {i18n.translate('xpack.apm.settings.customizeUI', { - defaultMessage: 'Customize UI' + {i18n.translate('xpack.apm.settings.customizeApp', { + defaultMessage: 'Customize app' })}

diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/index.tsx index f33bb17decd4e..2bb85876686bf 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/index.tsx @@ -57,8 +57,8 @@ export const Settings: React.FC = props => { isSelected: pathname === '/settings/apm-indices' }, { - name: i18n.translate('xpack.apm.settings.customizeUI', { - defaultMessage: 'Customize UI' + name: i18n.translate('xpack.apm.settings.customizeApp', { + defaultMessage: 'Customize app' }), id: '3', href: getAPMHref('/settings/customize-ui', search), diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/index.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/index.test.tsx index 2dab8d63f99b2..9d1eeb9a3136d 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/index.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/index.test.tsx @@ -28,7 +28,7 @@ describe('Custom links', () => { ); expectTextsInDocument(component, [ - 'No custom links found. Set up your own custom links i.e. a link to a specific Dashboard or external link.' + 'No custom links found. Set up your own custom links, e.g., a link to a specific Dashboard or external link.' ]); expectTextsNotInDocument(component, ['Create']); }); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/index.tsx index b32d8f0d9582c..38b672a181fce 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/index.tsx @@ -55,7 +55,7 @@ export const CustomLink = ({ {i18n.translate('xpack.apm.customLink.empty', { defaultMessage: - 'No custom links found. Set up your own custom links i.e. a link to a specific Dashboard or external link.' + 'No custom links found. Set up your own custom links, e.g., a link to a specific Dashboard or external link.' })} diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx index 048ed662ec502..e3fbcf8485d54 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx @@ -85,7 +85,13 @@ export const TransactionActionMenu: FunctionComponent = ({ urlParams }); + const closePopover = () => { + setIsActionPopoverOpen(false); + setIsCustomLinksPopoverOpen(false); + }; + const toggleCustomLinkFlyout = () => { + closePopover(); setIsCustomLinkFlyoutOpen(isOpen => !isOpen); }; @@ -111,10 +117,7 @@ export const TransactionActionMenu: FunctionComponent = ({ )} { - setIsActionPopoverOpen(false); - setIsCustomLinksPopoverOpen(false); - }} + closePopover={closePopover} isOpen={isActionPopoverOpen} anchorPosition="downRight" button={ From 2e0d9002576490dc05310aebc643f24f020f6dea Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 7 Apr 2020 09:16:45 +0200 Subject: [PATCH 27/36] =?UTF-8?q?[Uptime]Abstracted=20'access:uptime-read'?= =?UTF-8?q?=20tag=20into=20a=20wrapper=20for=E2=80=A6=20(#62576)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * abstract access headers * updated code --- .../server/rest_api/create_route_with_auth.ts | 2 +- .../server/rest_api/dynamic_settings.ts | 4 +-- .../rest_api/index_state/get_index_pattern.ts | 3 -- .../rest_api/index_state/get_index_status.ts | 3 -- .../uptime/server/rest_api/monitors/index.ts | 2 +- .../rest_api/monitors/monitor_locations.ts | 3 -- .../monitors/{status.ts => monitor_status.ts} | 3 -- .../rest_api/monitors/monitors_details.ts | 3 -- .../rest_api/monitors/monitors_durations.ts | 3 -- .../overview_filters/get_overview_filters.ts | 4 --- .../uptime/server/rest_api/pings/get_all.ts | 3 -- .../rest_api/pings/get_ping_histogram.ts | 3 -- .../uptime/server/rest_api/pings/get_pings.ts | 3 -- .../rest_api/snapshot/get_snapshot_count.ts | 5 +-- .../rest_api/telemetry/log_page_view.ts | 3 -- .../plugins/uptime/server/rest_api/types.ts | 1 + .../server/rest_api/uptime_route_wrapper.ts | 35 +++++++++---------- 17 files changed, 22 insertions(+), 61 deletions(-) rename x-pack/plugins/uptime/server/rest_api/monitors/{status.ts => monitor_status.ts} (95%) diff --git a/x-pack/plugins/uptime/server/rest_api/create_route_with_auth.ts b/x-pack/plugins/uptime/server/rest_api/create_route_with_auth.ts index 41527d76432cb..966dc20e27a7e 100644 --- a/x-pack/plugins/uptime/server/rest_api/create_route_with_auth.ts +++ b/x-pack/plugins/uptime/server/rest_api/create_route_with_auth.ts @@ -16,7 +16,7 @@ export const createRouteWithAuth = ( const licenseCheckHandler: UMRouteHandler = async (customParams, context, request, response) => { const { statusCode, message } = libs.license(context.licensing.license); if (statusCode === 200) { - return await handler(customParams, context, request, response); + return handler(customParams, context, request, response); } switch (statusCode) { case 400: diff --git a/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts b/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts index 2235379ba6f03..3f4e2fc345182 100644 --- a/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts +++ b/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts @@ -19,9 +19,6 @@ export const createGetDynamicSettingsRoute: UMRestApiRouteFactory = (libs: UMSer method: 'GET', path: '/api/uptime/dynamic_settings', validate: false, - options: { - tags: ['access:uptime-read'], - }, handler: async ({ dynamicSettings }, _context, _request, response): Promise => { return response.ok({ body: dynamicSettings, @@ -35,6 +32,7 @@ export const createPostDynamicSettingsRoute: UMRestApiRouteFactory = (libs: UMSe validate: { body: schema.object({}, { unknowns: 'allow' }), }, + writeAccess: true, options: { tags: ['access:uptime-write'], }, diff --git a/x-pack/plugins/uptime/server/rest_api/index_state/get_index_pattern.ts b/x-pack/plugins/uptime/server/rest_api/index_state/get_index_pattern.ts index cec5bb1be245f..689a75c5903a6 100644 --- a/x-pack/plugins/uptime/server/rest_api/index_state/get_index_pattern.ts +++ b/x-pack/plugins/uptime/server/rest_api/index_state/get_index_pattern.ts @@ -12,9 +12,6 @@ export const createGetIndexPatternRoute: UMRestApiRouteFactory = (libs: UMServer method: 'GET', path: API_URLS.INDEX_PATTERN, validate: false, - options: { - tags: ['access:uptime-read'], - }, handler: async ({ callES, dynamicSettings }, _context, _request, response): Promise => { try { return response.ok({ diff --git a/x-pack/plugins/uptime/server/rest_api/index_state/get_index_status.ts b/x-pack/plugins/uptime/server/rest_api/index_state/get_index_status.ts index 9c94ef92f9b6e..8ed73d90b2389 100644 --- a/x-pack/plugins/uptime/server/rest_api/index_state/get_index_status.ts +++ b/x-pack/plugins/uptime/server/rest_api/index_state/get_index_status.ts @@ -12,9 +12,6 @@ export const createGetIndexStatusRoute: UMRestApiRouteFactory = (libs: UMServerL method: 'GET', path: API_URLS.INDEX_STATUS, validate: false, - options: { - tags: ['access:uptime-read'], - }, handler: async ({ callES, dynamicSettings }, _context, _request, response): Promise => { try { return response.ok({ diff --git a/x-pack/plugins/uptime/server/rest_api/monitors/index.ts b/x-pack/plugins/uptime/server/rest_api/monitors/index.ts index 7da717b50c149..51b39037049b5 100644 --- a/x-pack/plugins/uptime/server/rest_api/monitors/index.ts +++ b/x-pack/plugins/uptime/server/rest_api/monitors/index.ts @@ -6,4 +6,4 @@ export { createGetMonitorDetailsRoute } from './monitors_details'; export { createGetMonitorLocationsRoute } from './monitor_locations'; -export { createGetStatusBarRoute } from './status'; +export { createGetStatusBarRoute } from './monitor_status'; diff --git a/x-pack/plugins/uptime/server/rest_api/monitors/monitor_locations.ts b/x-pack/plugins/uptime/server/rest_api/monitors/monitor_locations.ts index befa5fd7e0e55..66ce9871506d4 100644 --- a/x-pack/plugins/uptime/server/rest_api/monitors/monitor_locations.ts +++ b/x-pack/plugins/uptime/server/rest_api/monitors/monitor_locations.ts @@ -19,9 +19,6 @@ export const createGetMonitorLocationsRoute: UMRestApiRouteFactory = (libs: UMSe dateEnd: schema.string(), }), }, - options: { - tags: ['access:uptime-read'], - }, handler: async ({ callES, dynamicSettings }, _context, request, response): Promise => { const { monitorId, dateStart, dateEnd } = request.query; diff --git a/x-pack/plugins/uptime/server/rest_api/monitors/status.ts b/x-pack/plugins/uptime/server/rest_api/monitors/monitor_status.ts similarity index 95% rename from x-pack/plugins/uptime/server/rest_api/monitors/status.ts rename to x-pack/plugins/uptime/server/rest_api/monitors/monitor_status.ts index 9bccd64c4bd65..9cf1340fb9409 100644 --- a/x-pack/plugins/uptime/server/rest_api/monitors/status.ts +++ b/x-pack/plugins/uptime/server/rest_api/monitors/monitor_status.ts @@ -20,9 +20,6 @@ export const createGetStatusBarRoute: UMRestApiRouteFactory = (libs: UMServerLib dateEnd: schema.string(), }), }, - options: { - tags: ['access:uptime-read'], - }, handler: async ({ callES, dynamicSettings }, _context, request, response): Promise => { const { monitorId, dateStart, dateEnd } = request.query; const result = await libs.requests.getLatestMonitor({ diff --git a/x-pack/plugins/uptime/server/rest_api/monitors/monitors_details.ts b/x-pack/plugins/uptime/server/rest_api/monitors/monitors_details.ts index b14eb2c138a75..1cc010781457e 100644 --- a/x-pack/plugins/uptime/server/rest_api/monitors/monitors_details.ts +++ b/x-pack/plugins/uptime/server/rest_api/monitors/monitors_details.ts @@ -19,9 +19,6 @@ export const createGetMonitorDetailsRoute: UMRestApiRouteFactory = (libs: UMServ dateEnd: schema.maybe(schema.string()), }), }, - options: { - tags: ['access:uptime-read'], - }, handler: async ({ callES, dynamicSettings }, _context, request, response): Promise => { const { monitorId, dateStart, dateEnd } = request.query; return response.ok({ diff --git a/x-pack/plugins/uptime/server/rest_api/monitors/monitors_durations.ts b/x-pack/plugins/uptime/server/rest_api/monitors/monitors_durations.ts index 10008c4f6c7ea..9743ced13350a 100644 --- a/x-pack/plugins/uptime/server/rest_api/monitors/monitors_durations.ts +++ b/x-pack/plugins/uptime/server/rest_api/monitors/monitors_durations.ts @@ -20,9 +20,6 @@ export const createGetMonitorDurationRoute: UMRestApiRouteFactory = (libs: UMSer dateEnd: schema.string(), }), }, - options: { - tags: ['access:uptime-read'], - }, handler: async ({ callES, dynamicSettings }, _context, request, response): Promise => { const { monitorId, dateStart, dateEnd } = request.query; return response.ok({ diff --git a/x-pack/plugins/uptime/server/rest_api/overview_filters/get_overview_filters.ts b/x-pack/plugins/uptime/server/rest_api/overview_filters/get_overview_filters.ts index 05376f061c05f..deac05f36c8dc 100644 --- a/x-pack/plugins/uptime/server/rest_api/overview_filters/get_overview_filters.ts +++ b/x-pack/plugins/uptime/server/rest_api/overview_filters/get_overview_filters.ts @@ -28,10 +28,6 @@ export const createGetOverviewFilters: UMRestApiRouteFactory = (libs: UMServerLi tags: arrayOrStringType, }), }, - - options: { - tags: ['access:uptime-read'], - }, handler: async ({ callES, dynamicSettings }, _context, request, response) => { const { dateRangeStart, dateRangeEnd, locations, schemes, search, ports, tags } = request.query; diff --git a/x-pack/plugins/uptime/server/rest_api/pings/get_all.ts b/x-pack/plugins/uptime/server/rest_api/pings/get_all.ts index d64c76fc18a80..c76892103da6b 100644 --- a/x-pack/plugins/uptime/server/rest_api/pings/get_all.ts +++ b/x-pack/plugins/uptime/server/rest_api/pings/get_all.ts @@ -23,9 +23,6 @@ export const createGetAllRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => status: schema.maybe(schema.string()), }), }, - options: { - tags: ['access:uptime-read'], - }, handler: async ({ callES, dynamicSettings }, _context, request, response): Promise => { const { dateRangeStart, dateRangeEnd, location, monitorId, size, sort, status } = request.query; diff --git a/x-pack/plugins/uptime/server/rest_api/pings/get_ping_histogram.ts b/x-pack/plugins/uptime/server/rest_api/pings/get_ping_histogram.ts index cbd9ada027b31..dceef5ecb7848 100644 --- a/x-pack/plugins/uptime/server/rest_api/pings/get_ping_histogram.ts +++ b/x-pack/plugins/uptime/server/rest_api/pings/get_ping_histogram.ts @@ -21,9 +21,6 @@ export const createGetPingHistogramRoute: UMRestApiRouteFactory = (libs: UMServe filters: schema.maybe(schema.string()), }), }, - options: { - tags: ['access:uptime-read'], - }, handler: async ({ callES, dynamicSettings }, _context, request, response): Promise => { const { dateStart, dateEnd, statusFilter, monitorId, filters } = request.query; diff --git a/x-pack/plugins/uptime/server/rest_api/pings/get_pings.ts b/x-pack/plugins/uptime/server/rest_api/pings/get_pings.ts index 8129ad70e6d7d..cde9a8c4e47ea 100644 --- a/x-pack/plugins/uptime/server/rest_api/pings/get_pings.ts +++ b/x-pack/plugins/uptime/server/rest_api/pings/get_pings.ts @@ -23,9 +23,6 @@ export const createGetPingsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) = status: schema.maybe(schema.string()), }), }, - options: { - tags: ['access:uptime-read'], - }, handler: async ({ callES, dynamicSettings }, _context, request, response): Promise => { const { dateRangeStart, dateRangeEnd, location, monitorId, size, sort, status } = request.query; diff --git a/x-pack/plugins/uptime/server/rest_api/snapshot/get_snapshot_count.ts b/x-pack/plugins/uptime/server/rest_api/snapshot/get_snapshot_count.ts index 4fda95bbf86da..d870f49280117 100644 --- a/x-pack/plugins/uptime/server/rest_api/snapshot/get_snapshot_count.ts +++ b/x-pack/plugins/uptime/server/rest_api/snapshot/get_snapshot_count.ts @@ -7,7 +7,7 @@ import { schema } from '@kbn/config-schema'; import { UMServerLibs } from '../../lib/lib'; import { UMRestApiRouteFactory } from '../types'; -import { API_URLS } from '../../../../../legacy/plugins/uptime/common/constants/rest_api'; +import { API_URLS } from '../../../../../legacy/plugins/uptime/common/constants'; export const createGetSnapshotCount: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ method: 'GET', @@ -20,9 +20,6 @@ export const createGetSnapshotCount: UMRestApiRouteFactory = (libs: UMServerLibs statusFilter: schema.maybe(schema.string()), }), }, - options: { - tags: ['access:uptime-read'], - }, handler: async ({ callES, dynamicSettings }, _context, request, response): Promise => { const { dateRangeStart, dateRangeEnd, filters, statusFilter } = request.query; const result = await libs.requests.getSnapshotCount({ diff --git a/x-pack/plugins/uptime/server/rest_api/telemetry/log_page_view.ts b/x-pack/plugins/uptime/server/rest_api/telemetry/log_page_view.ts index 1f6f052019870..331c76ca31db5 100644 --- a/x-pack/plugins/uptime/server/rest_api/telemetry/log_page_view.ts +++ b/x-pack/plugins/uptime/server/rest_api/telemetry/log_page_view.ts @@ -27,7 +27,4 @@ export const createLogPageViewRoute: UMRestApiRouteFactory = () => ({ body: result, }); }, - options: { - tags: ['access:uptime-read'], - }, }); diff --git a/x-pack/plugins/uptime/server/rest_api/types.ts b/x-pack/plugins/uptime/server/rest_api/types.ts index 8bb1e8a6a86c0..aecb099b7bed5 100644 --- a/x-pack/plugins/uptime/server/rest_api/types.ts +++ b/x-pack/plugins/uptime/server/rest_api/types.ts @@ -24,6 +24,7 @@ import { UMServerLibs } from '../lib/lib'; */ export interface UMServerRoute { method: string; + writeAccess?: boolean; handler: T; } diff --git a/x-pack/plugins/uptime/server/rest_api/uptime_route_wrapper.ts b/x-pack/plugins/uptime/server/rest_api/uptime_route_wrapper.ts index 676aced23a25e..7ede9d39c5f2c 100644 --- a/x-pack/plugins/uptime/server/rest_api/uptime_route_wrapper.ts +++ b/x-pack/plugins/uptime/server/rest_api/uptime_route_wrapper.ts @@ -7,21 +7,20 @@ import { UMKibanaRouteWrapper } from './types'; import { savedObjectsAdapter } from '../lib/saved_objects'; -export const uptimeRouteWrapper: UMKibanaRouteWrapper = uptimeRoute => { - return { - ...uptimeRoute, - handler: async (context, request, response) => { - const { callAsCurrentUser: callES } = context.core.elasticsearch.dataClient; - const { client: savedObjectsClient } = context.core.savedObjects; - const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings( - savedObjectsClient - ); - return await uptimeRoute.handler( - { callES, savedObjectsClient, dynamicSettings }, - context, - request, - response - ); - }, - }; -}; +export const uptimeRouteWrapper: UMKibanaRouteWrapper = uptimeRoute => ({ + ...uptimeRoute, + options: { + tags: ['access:uptime-read', ...(uptimeRoute?.writeAccess ? ['access:uptime-write'] : [])], + }, + handler: async (context, request, response) => { + const { callAsCurrentUser: callES } = context.core.elasticsearch.dataClient; + const { client: savedObjectsClient } = context.core.savedObjects; + const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings(savedObjectsClient); + return uptimeRoute.handler( + { callES, savedObjectsClient, dynamicSettings }, + context, + request, + response + ); + }, +}); From 91a0010668345bb85d21bd127b4876047c957769 Mon Sep 17 00:00:00 2001 From: MadameSheema Date: Tue, 7 Apr 2020 09:19:08 +0200 Subject: [PATCH 28/36] [SIEM] Adds sort rules Cypress test (#62700) * adds 'singal detection rules' spec * adds 'Sorts by activated rules' test * implements 'Sort by activated rules' * refactors code * fixes index --- .../signal_detection_rules.spec.ts | 62 +++++++++++++++++++ .../cypress/screens/signal_detection_rules.ts | 12 ++++ .../cypress/tasks/signal_detection_rules.ts | 23 ++++++- 3 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 x-pack/legacy/plugins/siem/cypress/integration/signal_detection_rules.spec.ts diff --git a/x-pack/legacy/plugins/siem/cypress/integration/signal_detection_rules.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/signal_detection_rules.spec.ts new file mode 100644 index 0000000000000..1559285d508ed --- /dev/null +++ b/x-pack/legacy/plugins/siem/cypress/integration/signal_detection_rules.spec.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { + FIFTH_RULE, + FIRST_RULE, + RULE_NAME, + SECOND_RULE, + SEVENTH_RULE, +} from '../screens/signal_detection_rules'; + +import { goToManageSignalDetectionRules } from '../tasks/detections'; +import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; +import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; +import { + activateRule, + sortByActivatedRules, + waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, + waitForRuleToBeActivated, +} from '../tasks/signal_detection_rules'; + +import { DETECTIONS } from '../urls/navigation'; + +describe('Signal detection rules', () => { + before(() => { + esArchiverLoad('prebuilt_rules_loaded'); + }); + + after(() => { + esArchiverUnload('prebuilt_rules_loaded'); + }); + + it('Sorts by activated rules', () => { + loginAndWaitForPageWithoutDateRange(DETECTIONS); + goToManageSignalDetectionRules(); + waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); + cy.get(RULE_NAME) + .eq(FIFTH_RULE) + .invoke('text') + .then(fifthRuleName => { + activateRule(FIFTH_RULE); + waitForRuleToBeActivated(); + cy.get(RULE_NAME) + .eq(SEVENTH_RULE) + .invoke('text') + .then(seventhRuleName => { + activateRule(SEVENTH_RULE); + waitForRuleToBeActivated(); + sortByActivatedRules(); + + cy.get(RULE_NAME) + .eq(FIRST_RULE) + .should('have.text', fifthRuleName); + cy.get(RULE_NAME) + .eq(SECOND_RULE) + .should('have.text', seventhRuleName); + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/cypress/screens/signal_detection_rules.ts b/x-pack/legacy/plugins/siem/cypress/screens/signal_detection_rules.ts index 09fbc2132302c..f74f5c26ddc2e 100644 --- a/x-pack/legacy/plugins/siem/cypress/screens/signal_detection_rules.ts +++ b/x-pack/legacy/plugins/siem/cypress/screens/signal_detection_rules.ts @@ -18,6 +18,10 @@ export const DELETE_RULE_BULK_BTN = '[data-test-subj="deleteRuleBulk"]'; export const ELASTIC_RULES_BTN = '[data-test-subj="show-elastic-rules-filter-button"]'; +export const FIFTH_RULE = 4; + +export const FIRST_RULE = 0; + export const LOAD_PREBUILT_RULES_BTN = '[data-test-subj="load-prebuilt-rules"]'; export const LOADING_INITIAL_PREBUILT_RULES_TABLE = @@ -31,18 +35,26 @@ export const RISK_SCORE = '[data-test-subj="riskScore"]'; export const RELOAD_PREBUILT_RULES_BTN = '[data-test-subj="reloadPrebuiltRulesBtn"]'; +export const SECOND_RULE = 1; + export const RULE_CHECKBOX = '.euiTableRow .euiCheckbox__input'; export const RULE_NAME = '[data-test-subj="ruleName"]'; export const RULE_SWITCH = '[data-test-subj="rule-switch"]'; +export const RULE_SWITCH_LOADER = '[data-test-subj="rule-switch-loader"]'; + export const RULES_TABLE = '[data-test-subj="rules-table"]'; export const RULES_ROW = '.euiTableRow'; +export const SEVENTH_RULE = 6; + export const SEVERITY = '[data-test-subj="severity"]'; export const SHOWING_RULES_TEXT = '[data-test-subj="showingRules"]'; +export const SORT_RULES_BTN = '[data-test-subj="tableHeaderSortButton"]'; + export const THREE_HUNDRED_ROWS = '[data-test-subj="tablePagination-300-rows"]'; diff --git a/x-pack/legacy/plugins/siem/cypress/tasks/signal_detection_rules.ts b/x-pack/legacy/plugins/siem/cypress/tasks/signal_detection_rules.ts index cfc490526d84e..a404f1142cba7 100644 --- a/x-pack/legacy/plugins/siem/cypress/tasks/signal_detection_rules.ts +++ b/x-pack/legacy/plugins/siem/cypress/tasks/signal_detection_rules.ts @@ -15,13 +15,22 @@ import { LOADING_INITIAL_PREBUILT_RULES_TABLE, LOADING_SPINNER, PAGINATION_POPOVER_BTN, + RELOAD_PREBUILT_RULES_BTN, RULE_CHECKBOX, RULE_NAME, + RULE_SWITCH, + RULE_SWITCH_LOADER, RULES_TABLE, + SORT_RULES_BTN, THREE_HUNDRED_ROWS, - RELOAD_PREBUILT_RULES_BTN, } from '../screens/signal_detection_rules'; +export const activateRule = (rulePosition: number) => { + cy.get(RULE_SWITCH) + .eq(rulePosition) + .click({ force: true }); +}; + export const changeToThreeHundredRowsPerPage = () => { cy.get(PAGINATION_POPOVER_BTN).click({ force: true }); cy.get(THREE_HUNDRED_ROWS).click(); @@ -71,6 +80,13 @@ export const selectNumberOfRules = (numberOfRules: number) => { } }; +export const sortByActivatedRules = () => { + cy.get(SORT_RULES_BTN).click({ force: true }); + waitForRulesToBeLoaded(); + cy.get(SORT_RULES_BTN).click({ force: true }); + waitForRulesToBeLoaded(); +}; + export const waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded = () => { cy.get(LOADING_INITIAL_PREBUILT_RULES_TABLE).should('exist'); cy.get(LOADING_INITIAL_PREBUILT_RULES_TABLE).should('not.exist'); @@ -81,6 +97,11 @@ export const waitForPrebuiltDetectionRulesToBeLoaded = () => { cy.get(RULES_TABLE).should('exist'); }; +export const waitForRuleToBeActivated = () => { + cy.get(RULE_SWITCH_LOADER).should('exist'); + cy.get(RULE_SWITCH_LOADER).should('not.exist'); +}; + export const waitForRulesToBeLoaded = () => { cy.get(LOADING_SPINNER).should('exist'); cy.get(LOADING_SPINNER).should('not.exist'); From 355cacff7c4a6b9ad70fd2b2651cb8be412085b0 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 7 Apr 2020 09:19:36 +0200 Subject: [PATCH 29/36] [Uptime] Improve Telemetry test (#62428) * removed unnecessary filter * update condition * added a unit test for mix state * fix types * fix type * updated test * update * updates test * updates tests * updates tests * updated type Co-authored-by: Elastic Machine --- .../uptime/common/constants/rest_api.ts | 2 +- .../uptime/public/hooks/use_telemetry.ts | 2 +- .../telemetry/kibana_telemetry_adapter.ts | 26 ++-- .../server/lib/adapters/telemetry/types.ts | 1 + .../rest_api/telemetry/log_page_view.ts | 17 ++- .../api_integration/apis/uptime/rest/index.ts | 1 + .../apis/uptime/rest/telemetry_collectors.ts | 129 ++++++++++++++++++ .../apis/uptime/telemetry_collectors.ts | 21 --- 8 files changed, 162 insertions(+), 37 deletions(-) create mode 100644 x-pack/test/api_integration/apis/uptime/rest/telemetry_collectors.ts delete mode 100644 x-pack/test/api_integration/apis/uptime/telemetry_collectors.ts diff --git a/x-pack/legacy/plugins/uptime/common/constants/rest_api.ts b/x-pack/legacy/plugins/uptime/common/constants/rest_api.ts index 86e2b03e13f22..dffa131870db1 100644 --- a/x-pack/legacy/plugins/uptime/common/constants/rest_api.ts +++ b/x-pack/legacy/plugins/uptime/common/constants/rest_api.ts @@ -15,7 +15,7 @@ export enum API_URLS { PING_HISTOGRAM = `/api/uptime/ping/histogram`, SNAPSHOT_COUNT = `/api/uptime/snapshot/count`, FILTERS = `/api/uptime/filters`, - logPageView = `/api/uptime/logPageView`, + LOG_PAGE_VIEW = `/api/uptime/log_page_view`, ML_MODULE_JOBS = `/api/ml/modules/jobs_exist/`, ML_SETUP_MODULE = '/api/ml/modules/setup/', diff --git a/x-pack/legacy/plugins/uptime/public/hooks/use_telemetry.ts b/x-pack/legacy/plugins/uptime/public/hooks/use_telemetry.ts index fc0e0ce1c3e88..13fe523332ae5 100644 --- a/x-pack/legacy/plugins/uptime/public/hooks/use_telemetry.ts +++ b/x-pack/legacy/plugins/uptime/public/hooks/use_telemetry.ts @@ -30,6 +30,6 @@ export const useUptimeTelemetry = (page?: UptimePage) => { dateEnd: dateRangeEnd, autoRefreshEnabled: !autorefreshIsPaused, }; - apiService.post(API_URLS.logPageView, params); + apiService.post(API_URLS.LOG_PAGE_VIEW, params); }, [autorefreshInterval, autorefreshIsPaused, dateRangeEnd, dateRangeStart, page]); }; diff --git a/x-pack/plugins/uptime/server/lib/adapters/telemetry/kibana_telemetry_adapter.ts b/x-pack/plugins/uptime/server/lib/adapters/telemetry/kibana_telemetry_adapter.ts index 3b2696ba23f7c..fa792429f102e 100644 --- a/x-pack/plugins/uptime/server/lib/adapters/telemetry/kibana_telemetry_adapter.ts +++ b/x-pack/plugins/uptime/server/lib/adapters/telemetry/kibana_telemetry_adapter.ts @@ -5,7 +5,7 @@ */ import moment from 'moment'; -import { ISavedObjectsRepository } from 'kibana/server'; +import { ISavedObjectsRepository, SavedObjectsClientContract } from 'kibana/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { PageViewParams, UptimeTelemetry } from './types'; import { APICaller } from '../framework'; @@ -54,6 +54,9 @@ export class KibanaTelemetryAdapter { } public static countPageView(pageView: PageViewParams) { + if (pageView.refreshTelemetryHistory) { + this.collector = {}; + } const bucketId = this.getBucketToIncrement(); const bucket = this.collector[bucketId]; if (pageView.page === 'Overview') { @@ -94,7 +97,7 @@ export class KibanaTelemetryAdapter { public static async countNoOfUniqueMonitorAndLocations( callCluster: APICaller, - savedObjectsClient: ISavedObjectsRepository + savedObjectsClient: ISavedObjectsRepository | SavedObjectsClientContract ) { const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings(savedObjectsClient); const params = { @@ -161,24 +164,27 @@ export class KibanaTelemetryAdapter { const monitorNameStats: any = result?.aggregations?.monitor_name; const locationNameStats: any = result?.aggregations?.observer_loc_name; const uniqueMonitors: any = result?.aggregations?.monitors.buckets; - const bucket = this.getBucketToIncrement(); - this.collector[bucket].no_of_unique_monitors = numberOfUniqueMonitors; - this.collector[bucket].no_of_unique_observer_locations = numberOfUniqueLocations; - this.collector[bucket].no_of_unique_observer_locations = numberOfUniqueLocations; - this.collector[bucket].monitor_name_stats = { + const bucketId = this.getBucketToIncrement(); + const bucket = this.collector[bucketId]; + + bucket.no_of_unique_monitors = numberOfUniqueMonitors; + bucket.no_of_unique_observer_locations = numberOfUniqueLocations; + bucket.no_of_unique_observer_locations = numberOfUniqueLocations; + bucket.monitor_name_stats = { min_length: monitorNameStats?.min_length ?? 0, max_length: monitorNameStats?.max_length ?? 0, - avg_length: +monitorNameStats?.avg_length.toFixed(2), + avg_length: +(monitorNameStats?.avg_length?.toFixed(2) ?? 0), }; - this.collector[bucket].observer_location_name_stats = { + bucket.observer_location_name_stats = { min_length: locationNameStats?.min_length ?? 0, max_length: locationNameStats?.max_length ?? 0, avg_length: +(locationNameStats?.avg_length?.toFixed(2) ?? 0), }; - this.collector[bucket].monitor_frequency = this.getMonitorsFrequency(uniqueMonitors); + bucket.monitor_frequency = this.getMonitorsFrequency(uniqueMonitors); + return bucket; } private static getMonitorsFrequency(uniqueMonitors = []) { diff --git a/x-pack/plugins/uptime/server/lib/adapters/telemetry/types.ts b/x-pack/plugins/uptime/server/lib/adapters/telemetry/types.ts index 059bea6cc3215..ee3360ecc41b1 100644 --- a/x-pack/plugins/uptime/server/lib/adapters/telemetry/types.ts +++ b/x-pack/plugins/uptime/server/lib/adapters/telemetry/types.ts @@ -10,6 +10,7 @@ export interface PageViewParams { dateEnd: string; autoRefreshEnabled: boolean; autorefreshInterval: number; + refreshTelemetryHistory?: boolean; } export interface Stats { diff --git a/x-pack/plugins/uptime/server/rest_api/telemetry/log_page_view.ts b/x-pack/plugins/uptime/server/rest_api/telemetry/log_page_view.ts index 331c76ca31db5..4b2db71037071 100644 --- a/x-pack/plugins/uptime/server/rest_api/telemetry/log_page_view.ts +++ b/x-pack/plugins/uptime/server/rest_api/telemetry/log_page_view.ts @@ -8,10 +8,11 @@ import { schema } from '@kbn/config-schema'; import { KibanaTelemetryAdapter } from '../../lib/adapters/telemetry'; import { UMRestApiRouteFactory } from '../types'; import { PageViewParams } from '../../lib/adapters/telemetry/types'; +import { API_URLS } from '../../../../../legacy/plugins/uptime/common/constants'; export const createLogPageViewRoute: UMRestApiRouteFactory = () => ({ method: 'POST', - path: '/api/uptime/logPageView', + path: API_URLS.LOG_PAGE_VIEW, validate: { body: schema.object({ page: schema.string(), @@ -19,12 +20,20 @@ export const createLogPageViewRoute: UMRestApiRouteFactory = () => ({ dateEnd: schema.string(), autoRefreshEnabled: schema.boolean(), autorefreshInterval: schema.number(), + refreshTelemetryHistory: schema.maybe(schema.boolean()), }), }, - handler: async ({ callES, dynamicSettings }, _context, _request, response): Promise => { - const result = KibanaTelemetryAdapter.countPageView(_request.body as PageViewParams); + handler: async ( + { savedObjectsClient, callES, dynamicSettings }, + _context, + request, + response + ): Promise => { + await KibanaTelemetryAdapter.countNoOfUniqueMonitorAndLocations(callES, savedObjectsClient); + const pageViewResult = KibanaTelemetryAdapter.countPageView(request.body as PageViewParams); + return response.ok({ - body: result, + body: pageViewResult, }); }, }); diff --git a/x-pack/test/api_integration/apis/uptime/rest/index.ts b/x-pack/test/api_integration/apis/uptime/rest/index.ts index f89bec589847e..9b0cd61c22462 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/index.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/index.ts @@ -42,6 +42,7 @@ export default function({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./snapshot')); loadTestFile(require.resolve('./dynamic_settings')); + loadTestFile(require.resolve('./telemetry_collectors')); }); describe('with real-world data', () => { before('load heartbeat data', async () => await esArchiver.load('uptime/full_heartbeat')); diff --git a/x-pack/test/api_integration/apis/uptime/rest/telemetry_collectors.ts b/x-pack/test/api_integration/apis/uptime/rest/telemetry_collectors.ts new file mode 100644 index 0000000000000..b2ec96be0f288 --- /dev/null +++ b/x-pack/test/api_integration/apis/uptime/rest/telemetry_collectors.ts @@ -0,0 +1,129 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { API_URLS } from '../../../../../legacy/plugins/uptime/common/constants'; +import { makeChecksWithStatus } from '../graphql/helpers/make_checks'; + +export default function({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const es = getService('legacyEs'); + + describe('telemetry collectors', () => { + before('generating data', async () => { + await getService('esArchiver').load('uptime/blank'); + + const observer = { + geo: { + name: 'US-East', + location: '40.7128, -74.0060', + }, + }; + + const observer2 = { + geo: { + name: 'US', + location: '40.7128, -74.0060', + }, + }; + + await makeChecksWithStatus( + es, + 'upMonitorId', + 1, + 1, + 60 * 1000, + { + observer: {}, + monitor: { + name: 'Elastic', + }, + }, + 'up' + ); + + await makeChecksWithStatus( + es, + 'downMonitorId', + 1, + 1, + 120 * 1000, + { + observer, + monitor: { + name: 'Long Name with 22 Char', + }, + }, + 'down' + ); + + await makeChecksWithStatus(es, 'noGeoNameMonitor', 1, 1, 60 * 1000, { observer: {} }, 'down'); + await makeChecksWithStatus( + es, + 'downMonitorId', + 1, + 1, + 1, + { + observer, + monitor: { + name: 'Elastic', + }, + }, + 'down' + ); + + await makeChecksWithStatus(es, 'mixMonitorId', 1, 1, 1, { observer: observer2 }, 'down'); + }); + + after('unload heartbeat index', () => getService('esArchiver').unload('uptime/blank')); + + it('should receive expected results after calling monitor/overview logging', async () => { + // call monitor page + await supertest + .post(API_URLS.LOG_PAGE_VIEW) + .set('kbn-xsrf', 'true') + .send({ + page: 'Monitor', + autorefreshInterval: 100, + dateStart: 'now/d', + dateEnd: 'now/d', + autoRefreshEnabled: true, + refreshTelemetryHistory: true, + }) + .expect(200); + + // call overview page + const { body: result } = await supertest + .post(API_URLS.LOG_PAGE_VIEW) + .set('kbn-xsrf', 'true') + .send({ + page: 'Overview', + autorefreshInterval: 60, + dateStart: 'now/d', + dateEnd: 'now-30', + autoRefreshEnabled: true, + }) + .expect(200); + + expect(result).to.eql({ + overview_page: 1, + monitor_page: 1, + no_of_unique_monitors: 4, + settings_page: 0, + monitor_frequency: [120, 0.001, 60, 60], + monitor_name_stats: { min_length: 7, max_length: 22, avg_length: 12 }, + no_of_unique_observer_locations: 3, + observer_location_name_stats: { min_length: 2, max_length: 7, avg_length: 4.8 }, + dateRangeStart: ['now/d', 'now/d'], + dateRangeEnd: ['now/d', 'now-30'], + autoRefreshEnabled: true, + autorefreshInterval: [100, 60], + }); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/uptime/telemetry_collectors.ts b/x-pack/test/api_integration/apis/uptime/telemetry_collectors.ts deleted file mode 100644 index e33c6120557bb..0000000000000 --- a/x-pack/test/api_integration/apis/uptime/telemetry_collectors.ts +++ /dev/null @@ -1,21 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { FtrProviderContext } from '../../ftr_provider_context'; - -export default function({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - - describe('telemetry collectors', () => { - it('should receive a 200 for overview page logging', async () => { - await supertest.get('/api/uptime/logOverview').expect(200); - }); - - it('should receive a 200 for monitor page logging', async () => { - await supertest.get('/api/uptime/logMonitor').expect(200); - }); - }); -} From 8429a8ede93c19934ef70424259010c35c68bc3c Mon Sep 17 00:00:00 2001 From: Tim Roes Date: Tue, 7 Apr 2020 09:24:21 +0200 Subject: [PATCH 30/36] Fix old pathes in eslintrc (#62580) --- .eslintrc.js | 31 +++++-------------------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index e66331594b4ae..3c173e5244009 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -552,29 +552,6 @@ module.exports = { }, }, - /** - * Graph overrides - */ - { - files: ['x-pack/legacy/plugins/graph/**/*.js'], - globals: { - angular: true, - $: true, - }, - rules: { - 'block-scoped-var': 'off', - camelcase: 'off', - eqeqeq: 'off', - 'guard-for-in': 'off', - 'new-cap': 'off', - 'no-loop-func': 'off', - 'no-redeclare': 'off', - 'no-shadow': 'off', - 'no-unused-vars': 'off', - 'one-var': 'off', - }, - }, - /** * ML overrides */ @@ -771,7 +748,7 @@ module.exports = { * Lens overrides */ { - files: ['x-pack/legacy/plugins/lens/**/*.ts', 'x-pack/legacy/plugins/lens/**/*.tsx'], + files: ['x-pack/legacy/plugins/lens/**/*.{ts,tsx}', 'x-pack/plugins/lens/**/*.{ts,tsx}'], rules: { '@typescript-eslint/no-explicit-any': 'error', }, @@ -885,8 +862,10 @@ module.exports = { * TSVB overrides */ { - files: ['src/legacy/core_plugins/metrics/**/*.js'], - excludedFiles: 'src/legacy/core_plugins/metrics/index.js', + files: [ + 'src/plugins/vis_type_timeseries/**/*.{js,ts,tsx}', + 'src/legacy/core_plugins/vis_type_timeseries/**/*.{js,ts,tsx}', + ], rules: { 'import/no-default-export': 'error', }, From 64f27ca34e0daaaacb63240676e836a4d8e187cc Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Tue, 7 Apr 2020 08:47:39 +0100 Subject: [PATCH 31/36] [ML] Show better file structure finder explanations (#62316) * [ML] Show better file structure finder explanations * more typescript changes * changing function format * fixing some types * fixing translation id * fix boom error reporting * changes based on review Co-authored-by: Elastic Machine --- x-pack/plugins/ml/common/types/errors.ts | 1 + .../ml/common/types/file_datavisualizer.ts | 65 ++++++++++++++ .../{about_panel.js => about_panel.tsx} | 14 ++-- .../about_panel/{index.js => index.ts} | 0 ...welcome_content.js => welcome_content.tsx} | 25 +++--- ...alysis_summary.js => analysis_summary.tsx} | 11 +-- .../analysis_summary/{index.js => index.ts} | 0 ...mental_badge.js => experimental_badge.tsx} | 6 +- .../experimental_badge/{index.js => index.ts} | 0 .../explanation_flyout/explanation_flyout.tsx | 82 ++++++++++++++++++ .../index.js => explanation_flyout/index.ts} | 2 +- .../{file_contents.js => file_contents.tsx} | 16 ++-- .../file_contents/{index.js => index.ts} | 0 .../file_datavisualizer_view.js | 45 ++++++---- ...or_callouts.js => file_error_callouts.tsx} | 45 ++++++++-- .../import_errors/{errors.js => errors.tsx} | 36 ++++---- .../import_errors/{index.js => index.ts} | 0 ...import_progress.js => import_progress.tsx} | 34 +++++--- .../components/import_progress/index.ts | 7 ++ .../{advanced.js => advanced.tsx} | 46 +++++++--- ...import_settings.js => import_settings.tsx} | 22 ++++- .../import_settings/{index.js => index.ts} | 0 .../import_settings/{simple.js => simple.tsx} | 13 ++- .../{import_summary.js => import_summary.tsx} | 46 +++++++--- .../import_summary/{index.js => index.ts} | 0 .../components/import_view/import_view.js | 1 - .../importer/{importer.js => importer.ts} | 84 +++++++++++++------ ...mporter_factory.js => importer_factory.ts} | 8 +- .../importer/{index.js => index.ts} | 0 ...essage_importer.js => message_importer.ts} | 31 ++++--- ...{ndjson_importer.js => ndjson_importer.ts} | 11 +-- .../results_view/{index.js => index.ts} | 0 .../{results_view.js => results_view.tsx} | 50 +++++++++-- .../components/utils/{index.js => index.ts} | 0 .../file_based/components/utils/overrides.js | 21 ----- .../components/utils/{utils.js => utils.ts} | 40 ++++++--- .../services/ml_api_service/datavisualizer.ts | 5 +- .../ml/server/client/elasticsearch_ml.ts | 4 +- .../plugins/ml/server/client/error_wrapper.ts | 8 +- .../file_data_visualizer.ts | 37 ++------ .../file_data_visualizer/import_data.ts | 39 +++------ .../models/file_data_visualizer/index.ts | 9 +- .../ml/server/routes/file_data_visualizer.ts | 12 +-- 43 files changed, 609 insertions(+), 267 deletions(-) rename x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/{about_panel.js => about_panel.tsx} (91%) rename x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/{index.js => index.ts} (100%) rename x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/{welcome_content.js => welcome_content.tsx} (90%) rename x-pack/plugins/ml/public/application/datavisualizer/file_based/components/analysis_summary/{analysis_summary.js => analysis_summary.tsx} (89%) rename x-pack/plugins/ml/public/application/datavisualizer/file_based/components/analysis_summary/{index.js => index.ts} (100%) rename x-pack/plugins/ml/public/application/datavisualizer/file_based/components/experimental_badge/{experimental_badge.js => experimental_badge.tsx} (84%) rename x-pack/plugins/ml/public/application/datavisualizer/file_based/components/experimental_badge/{index.js => index.ts} (100%) create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/file_based/components/explanation_flyout/explanation_flyout.tsx rename x-pack/plugins/ml/public/application/datavisualizer/file_based/components/{import_progress/index.js => explanation_flyout/index.ts} (78%) rename x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/{file_contents.js => file_contents.tsx} (83%) rename x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/{index.js => index.ts} (100%) rename x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/{file_error_callouts.js => file_error_callouts.tsx} (73%) rename x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_errors/{errors.js => errors.tsx} (85%) rename x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_errors/{index.js => index.ts} (100%) rename x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_progress/{import_progress.js => import_progress.tsx} (92%) create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_progress/index.ts rename x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/{advanced.js => advanced.tsx} (85%) rename x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/{import_settings.js => import_settings.tsx} (82%) rename x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/{index.js => index.ts} (100%) rename x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/{simple.js => simple.tsx} (88%) rename x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/{import_summary.js => import_summary.tsx} (86%) rename x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/{index.js => index.ts} (100%) rename x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/{importer.js => importer.ts} (73%) rename x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/{importer_factory.js => importer_factory.ts} (76%) rename x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/{index.js => index.ts} (100%) rename x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/{message_importer.js => message_importer.ts} (76%) rename x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/{ndjson_importer.js => ndjson_importer.ts} (71%) rename x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/{index.js => index.ts} (100%) rename x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/{results_view.js => results_view.tsx} (58%) rename x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/{index.js => index.ts} (100%) delete mode 100644 x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/overrides.js rename x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/{utils.js => utils.ts} (79%) diff --git a/x-pack/plugins/ml/common/types/errors.ts b/x-pack/plugins/ml/common/types/errors.ts index 63e222490082b..284250bd5ce55 100644 --- a/x-pack/plugins/ml/common/types/errors.ts +++ b/x-pack/plugins/ml/common/types/errors.ts @@ -9,6 +9,7 @@ export interface ErrorResponse { statusCode: number; error: string; message: string; + attributes?: any; }; name: string; } diff --git a/x-pack/plugins/ml/common/types/file_datavisualizer.ts b/x-pack/plugins/ml/common/types/file_datavisualizer.ts index bc03f82673a1f..f771547b97811 100644 --- a/x-pack/plugins/ml/common/types/file_datavisualizer.ts +++ b/x-pack/plugins/ml/common/types/file_datavisualizer.ts @@ -4,6 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ +export interface InputOverrides { + [key: string]: string; +} + +export type FormattedOverrides = InputOverrides & { + column_names: string[]; + has_header_row: boolean; + should_trim_fields: boolean; +}; + +export interface AnalysisResult { + results: FindFileStructureResponse; + overrides?: FormattedOverrides; +} + export interface FindFileStructureResponse { charset: string; has_header_row: boolean; @@ -28,4 +43,54 @@ export interface FindFileStructureResponse { need_client_timezone: boolean; num_lines_analyzed: number; column_names: string[]; + explanation?: string[]; + grok_pattern?: string; + multiline_start_pattern?: string; + exclude_lines_pattern?: string; + java_timestamp_formats?: string[]; + joda_timestamp_formats?: string[]; + timestamp_field?: string; + should_trim_fields?: boolean; +} + +export interface ImportResponse { + success: boolean; + id: string; + index?: string; + pipelineId?: string; + docCount: number; + failures: ImportFailure[]; + error?: any; + ingestError?: boolean; +} + +export interface ImportFailure { + item: number; + reason: string; + doc: Doc; +} + +export interface Doc { + message: string; +} + +export interface Settings { + pipeline?: string; + index: string; + body: any[]; + [key: string]: any; +} + +export interface Mappings { + [key: string]: any; +} + +export interface IngestPipelineWrapper { + id: string; + pipeline: IngestPipeline; +} + +export interface IngestPipeline { + description: string; + processors: any[]; } diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/about_panel.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/about_panel.tsx similarity index 91% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/about_panel.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/about_panel.tsx index edecc925591d3..2bddf0de0499d 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/about_panel.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/about_panel.tsx @@ -6,7 +6,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; +import React, { FC } from 'react'; import { EuiFlexGroup, @@ -23,7 +23,11 @@ import { import { WelcomeContent } from './welcome_content'; -export function AboutPanel({ onFilePickerChange }) { +interface Props { + onFilePickerChange(files: FileList | null): void; +} + +export const AboutPanel: FC = ({ onFilePickerChange }) => { return ( @@ -54,9 +58,9 @@ export function AboutPanel({ onFilePickerChange }) { ); -} +}; -export function LoadingPanel() { +export const LoadingPanel: FC = () => { return ( @@ -79,4 +83,4 @@ export function LoadingPanel() { ); -} +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/index.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/index.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/welcome_content.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/welcome_content.tsx similarity index 90% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/welcome_content.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/welcome_content.tsx index 73d12122879f8..c56ab021e2f2f 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/welcome_content.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/welcome_content.tsx @@ -5,7 +5,8 @@ */ import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; +import React, { FC } from 'react'; +import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, @@ -19,7 +20,14 @@ import { import { ExperimentalBadge } from '../experimental_badge'; -export function WelcomeContent() { +export const WelcomeContent: FC = () => { + const toolTipContent = i18n.translate( + 'xpack.ml.fileDatavisualizer.welcomeContent.experimentalFeatureTooltip', + { + defaultMessage: "Experimental feature. We'd love to hear your feedback.", + } + ); + return ( @@ -32,16 +40,7 @@ export function WelcomeContent() { id="xpack.ml.fileDatavisualizer.welcomeContent.visualizeDataFromLogFileTitle" defaultMessage="Visualize data from a log file {experimentalBadge}" values={{ - experimentalBadge: ( - - } - /> - ), + experimentalBadge: , }} /> @@ -144,4 +143,4 @@ export function WelcomeContent() { ); -} +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/analysis_summary/analysis_summary.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/analysis_summary/analysis_summary.tsx similarity index 89% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/analysis_summary/analysis_summary.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/analysis_summary/analysis_summary.tsx index b3bf4ea4daf83..b80db3b27fa7e 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/analysis_summary/analysis_summary.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/analysis_summary/analysis_summary.tsx @@ -5,11 +5,12 @@ */ import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; +import React, { FC } from 'react'; import { EuiTitle, EuiSpacer, EuiDescriptionList } from '@elastic/eui'; +import { FindFileStructureResponse } from '../../../../../../common/types/file_datavisualizer'; -export function AnalysisSummary({ results }) { +export const AnalysisSummary: FC<{ results: FindFileStructureResponse }> = ({ results }) => { const items = createDisplayItems(results); return ( @@ -28,10 +29,10 @@ export function AnalysisSummary({ results }) { ); -} +}; -function createDisplayItems(results) { - const items = [ +function createDisplayItems(results: FindFileStructureResponse) { + const items: Array<{ title: any; description: string | number }> = [ { title: ( = ({ tooltipContent }) => { return ( ); -} +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/experimental_badge/index.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/experimental_badge/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/experimental_badge/index.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/experimental_badge/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/explanation_flyout/explanation_flyout.tsx b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/explanation_flyout/explanation_flyout.tsx new file mode 100644 index 0000000000000..8e44a296126b6 --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/explanation_flyout/explanation_flyout.tsx @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiFlyout, + EuiFlyoutFooter, + EuiFlexGroup, + EuiFlexItem, + EuiFlyoutHeader, + EuiButtonEmpty, + EuiTitle, + EuiFlyoutBody, + EuiSpacer, + EuiText, + EuiSubSteps, +} from '@elastic/eui'; +import { FindFileStructureResponse } from '../../../../../../common/types/file_datavisualizer'; + +interface Props { + results: FindFileStructureResponse; + closeFlyout(): void; +} +export const ExplanationFlyout: FC = ({ results, closeFlyout }) => { + const explanation = results.explanation!; + return ( + + + +

+ +

+
+
+ + + + + + + + + + + + +
+ ); +}; + +const Content: FC<{ explanation: string[] }> = ({ explanation }) => ( + <> + + + + + +
    + {explanation.map((e, i) => ( +
  • + {e} + +
  • + ))} +
+
+
+ +); diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_progress/index.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/explanation_flyout/index.ts similarity index 78% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_progress/index.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/explanation_flyout/index.ts index f75d869f4667c..e288050403887 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_progress/index.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/explanation_flyout/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { ImportProgress, IMPORT_STATUS } from './import_progress'; +export { ExplanationFlyout } from './explanation_flyout'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/file_contents.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/file_contents.tsx similarity index 83% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/file_contents.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/file_contents.tsx index ed5ab57a2588d..6564b9a1f4d83 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/file_contents.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/file_contents.tsx @@ -5,13 +5,19 @@ */ import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; +import React, { FC } from 'react'; import { EuiTitle, EuiSpacer } from '@elastic/eui'; import { MLJobEditor, ML_EDITOR_MODE } from '../../../../jobs/jobs_list/components/ml_job_editor'; -export function FileContents({ data, format, numberOfLines }) { +interface Props { + data: string; + format: string; + numberOfLines: number; +} + +export const FileContents: FC = ({ data, format, numberOfLines }) => { let mode = ML_EDITOR_MODE.TEXT; if (format === ML_EDITOR_MODE.JSON) { mode = ML_EDITOR_MODE.JSON; @@ -35,7 +41,7 @@ export function FileContents({ data, format, numberOfLines }) { id="xpack.ml.fileDatavisualizer.fileContents.firstLinesDescription" defaultMessage="First {numberOfLines, plural, zero {# line} one {# line} other {# lines}}" values={{ - numberOfLines: numberOfLines, + numberOfLines, }} />
@@ -51,9 +57,9 @@ export function FileContents({ data, format, numberOfLines }) { /> ); -} +}; -function limitByNumberOfLines(data, numberOfLines) { +function limitByNumberOfLines(data: string, numberOfLines: number) { return data .split('\n') .slice(0, numberOfLines) diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/index.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/index.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/file_datavisualizer_view.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/file_datavisualizer_view.js index 12e5a14b51871..02f14a9e4553e 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/file_datavisualizer_view.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/file_datavisualizer_view.js @@ -17,9 +17,9 @@ import { BottomBar } from '../bottom_bar'; import { ResultsView } from '../results_view'; import { FileCouldNotBeRead, FileTooLarge } from './file_error_callouts'; import { EditFlyout } from '../edit_flyout'; +import { ExplanationFlyout } from '../explanation_flyout'; import { ImportView } from '../import_view'; import { MAX_BYTES } from '../../../../../../common/constants/file_datavisualizer'; -import { isErrorResponse } from '../../../../../../common/types/errors'; import { readFile, createUrlOverrides, @@ -42,12 +42,14 @@ export class FileDataVisualizerView extends Component { fileSize: 0, fileTooLarge: false, fileCouldNotBeRead: false, - serverErrorMessage: '', + serverError: null, loading: false, loaded: false, results: undefined, + explanation: undefined, mode: MODE.READ, isEditFlyoutVisible: false, + isExplanationFlyoutVisible: false, bottomBarVisible: false, hasPermissionToImport: false, }; @@ -78,8 +80,9 @@ export class FileDataVisualizerView extends Component { fileSize: 0, fileTooLarge: false, fileCouldNotBeRead: false, - serverErrorMessage: '', + serverError: null, results: undefined, + explanation: undefined, }, () => { if (files.length) { @@ -128,7 +131,7 @@ export class FileDataVisualizerView extends Component { console.log('overrides', overrides); const { analyzeFile } = ml.fileDatavisualizer; const resp = await analyzeFile(lessData, overrides); - const serverSettings = processResults(resp.results); + const serverSettings = processResults(resp); const serverOverrides = resp.overrides; this.previousOverrides = this.overrides; @@ -172,6 +175,7 @@ export class FileDataVisualizerView extends Component { this.setState({ results: resp.results, + explanation: resp.explanation, loaded: true, loading: false, fileCouldNotBeRead: isRetry, @@ -179,19 +183,13 @@ export class FileDataVisualizerView extends Component { } catch (error) { console.error(error); - let serverErrorMsg; - if (isErrorResponse(error) === true) { - serverErrorMsg = `${error.body.error}: ${error.body.message}`; - } else { - serverErrorMsg = JSON.stringify(error, null, 2); - } - this.setState({ results: undefined, + explanation: undefined, loaded: false, loading: false, fileCouldNotBeRead: true, - serverErrorMessage: serverErrorMsg, + serverError: error, }); // as long as the previous overrides are different to the current overrides, @@ -216,6 +214,16 @@ export class FileDataVisualizerView extends Component { this.hideBottomBar(); }; + closeExplanationFlyout = () => { + this.setState({ isExplanationFlyoutVisible: false }); + this.showBottomBar(); + }; + + showExplanationFlyout = () => { + this.setState({ isExplanationFlyoutVisible: true }); + this.hideBottomBar(); + }; + showBottomBar = () => { this.setState({ bottomBarVisible: true }); }; @@ -252,14 +260,16 @@ export class FileDataVisualizerView extends Component { loading, loaded, results, + explanation, fileContents, fileName, fileSize, fileTooLarge, fileCouldNotBeRead, - serverErrorMessage, + serverError, mode, isEditFlyoutVisible, + isExplanationFlyoutVisible, bottomBarVisible, hasPermissionToImport, } = this.state; @@ -281,7 +291,7 @@ export class FileDataVisualizerView extends Component { {fileCouldNotBeRead && loading === false && ( - + )} @@ -289,9 +299,12 @@ export class FileDataVisualizerView extends Component { {loaded && ( this.showEditFlyout()} + showExplanationFlyout={() => this.showExplanationFlyout()} + disableButtons={isEditFlyoutVisible || isExplanationFlyoutVisible} /> )} + {isExplanationFlyoutVisible && ( + + )} + {bottomBarVisible && loaded && ( = ({ fileSize, maxFileSize }) => { const fileSizeFormatted = numeral(fileSize).format(FILE_SIZE_DISPLAY_FORMAT); const maxFileSizeFormatted = numeral(maxFileSize).format(FILE_SIZE_DISPLAY_FORMAT); @@ -67,9 +73,15 @@ export function FileTooLarge({ fileSize, maxFileSize }) { {errorText}
); +}; + +interface FileCouldNotBeReadProps { + error: ErrorResponse; + loaded: boolean; } -export function FileCouldNotBeRead({ error, loaded }) { +export const FileCouldNotBeRead: FC = ({ error, loaded }) => { + const message = error.body.message; return ( - {error !== undefined &&

{error}

} + {message} + {loaded && ( -

+ <> + -

+ )}
); -} +}; + +export const Explanation: FC<{ error: ErrorResponse }> = ({ error }) => { + if (!error.body.attributes?.body?.error?.suppressed?.length) { + return null; + } + const reason: string = error.body.attributes.body.error.suppressed[0].reason; + return ( + <> + + {reason.split('\n').map((m, i) => ( +
{m}
+ ))} + + ); +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_errors/errors.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_errors/errors.tsx similarity index 85% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_errors/errors.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_errors/errors.tsx index 6629c0109feaf..f723ad1a752bf 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_errors/errors.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_errors/errors.tsx @@ -5,13 +5,24 @@ */ import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; +import React, { FC } from 'react'; +import { i18n } from '@kbn/i18n'; import { EuiCallOut, EuiAccordion } from '@elastic/eui'; -import { IMPORT_STATUS } from '../import_progress'; +import { IMPORT_STATUS, Statuses } from '../import_progress'; -export function ImportErrors({ errors, statuses }) { +interface ImportError { + msg: string; + more?: string; +} + +interface Props { + errors: any[]; + statuses: Statuses; +} + +export const ImportErrors: FC = ({ errors, statuses }) => { return ( {errors.map((e, i) => ( @@ -19,9 +30,9 @@ export function ImportErrors({ errors, statuses }) { ))} ); -} +}; -function title(statuses) { +function title(statuses: Statuses) { switch (IMPORT_STATUS.FAILED) { case statuses.readStatus: return ( @@ -82,7 +93,7 @@ function title(statuses) { } } -function ImportError(error, key) { +function ImportError(error: any, key: number) { const errorObj = toString(error); return ( @@ -106,7 +117,7 @@ function ImportError(error, key) { ); } -function toString(error) { +function toString(error: any): ImportError { if (typeof error === 'string') { return { msg: error }; } @@ -118,7 +129,7 @@ function toString(error) { if (typeof error.error === 'object') { if (error.error.msg !== undefined) { // this will catch a bulk ingest failure - const errorObj = { msg: error.error.msg }; + const errorObj: ImportError = { msg: error.error.msg }; if (error.error.body !== undefined) { errorObj.more = error.error.response; } @@ -139,11 +150,8 @@ function toString(error) { } return { - msg: ( - - ), + msg: i18n.translate('xpack.ml.fileDatavisualizer.importErrors.unknownErrorMessage', { + defaultMessage: 'Unknown error', + }), }; } diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_errors/index.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_errors/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_errors/index.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_errors/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_progress/import_progress.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_progress/import_progress.tsx similarity index 92% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_progress/import_progress.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_progress/import_progress.tsx index 272ec2979ad2f..533fec4ac50c8 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_progress/import_progress.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_progress/import_progress.tsx @@ -6,17 +6,31 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; +import React, { FC } from 'react'; import { EuiStepsHorizontal, EuiProgress, EuiSpacer } from '@elastic/eui'; -export const IMPORT_STATUS = { - INCOMPLETE: 'incomplete', - COMPLETE: 'complete', - FAILED: 'danger', -}; +export enum IMPORT_STATUS { + INCOMPLETE = 'incomplete', + COMPLETE = 'complete', + FAILED = 'danger', +} + +export interface Statuses { + reading: boolean; + readStatus: IMPORT_STATUS; + parseJSONStatus: IMPORT_STATUS; + indexCreatedStatus: IMPORT_STATUS; + ingestPipelineCreatedStatus: IMPORT_STATUS; + indexPatternCreatedStatus: IMPORT_STATUS; + uploadProgress: number; + uploadStatus: IMPORT_STATUS; + createIndexPattern: boolean; + createPipeline: boolean; + permissionCheckStatus: IMPORT_STATUS; +} -export function ImportProgress({ statuses }) { +export const ImportProgress: FC<{ statuses: Statuses }> = ({ statuses }) => { const { reading, readStatus, @@ -271,9 +285,9 @@ export function ImportProgress({ statuses }) { )} ); -} +}; -function UploadFunctionProgress({ progress }) { +const UploadFunctionProgress: FC<{ progress: number }> = ({ progress }) => { return (

@@ -290,4 +304,4 @@ function UploadFunctionProgress({ progress }) { )} ); -} +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_progress/index.ts b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_progress/index.ts new file mode 100644 index 0000000000000..9b0c6ca2264cb --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_progress/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { ImportProgress, IMPORT_STATUS, Statuses } from './import_progress'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/advanced.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/advanced.tsx similarity index 85% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/advanced.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/advanced.tsx index 14cbe67662ed6..a79a7d36f3294 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/advanced.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/advanced.tsx @@ -6,7 +6,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; +import React, { FC } from 'react'; import { EuiFieldText, @@ -20,7 +20,25 @@ import { import { MLJobEditor, ML_EDITOR_MODE } from '../../../../jobs/jobs_list/components/ml_job_editor'; const EDITOR_HEIGHT = '300px'; -export function AdvancedSettings({ +interface Props { + index: string; + indexPattern: string; + initialized: boolean; + onIndexChange(): void; + createIndexPattern: boolean; + onCreateIndexPatternChange(): void; + onIndexPatternChange(): void; + indexSettingsString: string; + mappingsString: string; + pipelineString: string; + onIndexSettingsStringChange(): void; + onMappingsStringChange(): void; + onPipelineStringChange(): void; + indexNameError: string; + indexPatternNameError: string; +} + +export const AdvancedSettings: FC = ({ index, indexPattern, initialized, @@ -36,7 +54,7 @@ export function AdvancedSettings({ onPipelineStringChange, indexNameError, indexPatternNameError, -}) { +}) => { return ( } - disabled={createIndexPattern === false || initialized === true} isInvalid={indexPatternNameError !== ''} error={[indexPatternNameError]} > @@ -133,9 +150,15 @@ export function AdvancedSettings({ ); +}; + +interface JsonEditorProps { + initialized: boolean; + data: string; + onChange(): void; } -function IndexSettings({ initialized, data, onChange }) { +const IndexSettings: FC = ({ initialized, data, onChange }) => { return ( } - disabled={initialized === true} fullWidth > ); -} +}; -function Mappings({ initialized, data, onChange }) { +const Mappings: FC = ({ initialized, data, onChange }) => { return ( } - disabled={initialized === true} fullWidth > ); -} +}; -function IngestPipeline({ initialized, data, onChange }) { +const IngestPipeline: FC = ({ initialized, data, onChange }) => { return ( } - disabled={initialized === true} fullWidth > ); -} +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/import_settings.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/import_settings.tsx similarity index 82% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/import_settings.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/import_settings.tsx index ba637c472333d..02cf647caaf16 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/import_settings.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/import_settings.tsx @@ -5,14 +5,32 @@ */ import { i18n } from '@kbn/i18n'; -import React from 'react'; +import React, { FC } from 'react'; import { EuiTabbedContent, EuiSpacer } from '@elastic/eui'; import { SimpleSettings } from './simple'; import { AdvancedSettings } from './advanced'; -export const ImportSettings = ({ +interface Props { + index: string; + indexPattern: string; + initialized: boolean; + onIndexChange(): void; + createIndexPattern: boolean; + onCreateIndexPatternChange(): void; + onIndexPatternChange(): void; + indexSettingsString: string; + mappingsString: string; + pipelineString: string; + onIndexSettingsStringChange(): void; + onMappingsStringChange(): void; + onPipelineStringChange(): void; + indexNameError: string; + indexPatternNameError: string; +} + +export const ImportSettings: FC = ({ index, indexPattern, initialized, diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/index.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/index.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/simple.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/simple.tsx similarity index 88% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/simple.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/simple.tsx index 271b9493aa1f3..1e716824729e3 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/simple.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/simple.tsx @@ -6,11 +6,20 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; +import React, { FC } from 'react'; import { EuiFieldText, EuiFormRow, EuiCheckbox, EuiSpacer } from '@elastic/eui'; -export const SimpleSettings = ({ +interface Props { + index: string; + initialized: boolean; + onIndexChange(): void; + createIndexPattern: boolean; + onCreateIndexPatternChange(): void; + indexNameError: string; +} + +export const SimpleSettings: FC = ({ index, initialized, onIndexChange, diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/import_summary.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/import_summary.tsx similarity index 86% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/import_summary.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/import_summary.tsx index 0e67807a39fd9..6ee17d401bd70 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/import_summary.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/import_summary.tsx @@ -5,11 +5,29 @@ */ import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; +import React, { FC } from 'react'; import { EuiSpacer, EuiDescriptionList, EuiCallOut, EuiAccordion } from '@elastic/eui'; -export function ImportSummary({ +interface Props { + index: string; + indexPattern: string; + ingestPipelineId: string; + docCount: number; + importFailures: DocFailure[]; + createIndexPattern: boolean; + createPipeline: boolean; +} + +interface DocFailure { + item: number; + reason: string; + doc: { + message: string; + }; +} + +export const ImportSummary: FC = ({ index, indexPattern, ingestPipelineId, @@ -17,7 +35,7 @@ export function ImportSummary({ importFailures, createIndexPattern, createPipeline, -}) { +}) => { const items = createDisplayItems( index, indexPattern, @@ -75,9 +93,13 @@ export function ImportSummary({ )} ); +}; + +interface FailuresProps { + failedDocs: DocFailure[]; } -function Failures({ failedDocs }) { +const Failures: FC = ({ failedDocs }) => { return ( ); -} +}; function createDisplayItems( - index, - indexPattern, - ingestPipelineId, - docCount, - importFailures, - createIndexPattern, - createPipeline + index: string, + indexPattern: string, + ingestPipelineId: string, + docCount: number, + importFailures: DocFailure[], + createIndexPattern: boolean, + createPipeline: boolean ) { const items = [ { diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/index.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/index.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js index 0a58153e374df..4c9579bfd4b46 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js @@ -623,7 +623,6 @@ async function createKibanaIndexPattern( id, }; } catch (error) { - console.error(error); return { success: false, error, diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer.ts similarity index 73% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer.ts index 27899a58beed2..c97f1c147c454 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer.ts @@ -4,30 +4,53 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ml } from '../../../../../services/ml_api_service'; import { chunk } from 'lodash'; import moment from 'moment'; import { i18n } from '@kbn/i18n'; +import { ml } from '../../../../../services/ml_api_service'; +import { + Doc, + ImportFailure, + ImportResponse, + Mappings, + Settings, + IngestPipeline, +} from '../../../../../../../common/types/file_datavisualizer'; const CHUNK_SIZE = 5000; const MAX_CHUNK_CHAR_COUNT = 1000000; const IMPORT_RETRIES = 5; +export interface ImportConfig { + settings: Settings; + mappings: Mappings; + pipeline: IngestPipeline; +} + +export interface ImportResults { + success: boolean; + failures?: any[]; + docCount?: number; + error?: any; +} + export class Importer { - constructor({ settings, mappings, pipeline }) { - this.settings = settings; - this.mappings = mappings; - this.pipeline = pipeline; - - this.data = []; - this.docArray = []; - this.docSizeArray = []; + private _settings: Settings; + private _mappings: Mappings; + private _pipeline: IngestPipeline; + + protected _docArray: Doc[] = []; + + constructor({ settings, mappings, pipeline }: ImportConfig) { + this._settings = settings; + this._mappings = mappings; + this._pipeline = pipeline; } - async initializeImport(index) { - const settings = this.settings; - const mappings = this.mappings; - const pipeline = this.pipeline; + async initializeImport(index: string) { + const settings = this._settings; + const mappings = this._mappings; + const pipeline = this._pipeline; updatePipelineTimezone(pipeline); // if no pipeline has been supplied, @@ -52,7 +75,12 @@ export class Importer { return createIndexResp; } - async import(id, index, pipelineId, setImportProgress) { + async import( + id: string, + index: string, + pipelineId: string, + setImportProgress: (progress: number) => void + ): Promise { if (!id || !index) { return { success: false, @@ -65,14 +93,14 @@ export class Importer { }; } - const chunks = createDocumentChunks(this.docArray); + const chunks = createDocumentChunks(this._docArray); const ingestPipeline = { id: pipelineId, }; let success = true; - const failures = []; + const failures: ImportFailure[] = []; let error; for (let i = 0; i < chunks.length; i++) { @@ -86,10 +114,13 @@ export class Importer { }; let retries = IMPORT_RETRIES; - let resp = { + let resp: ImportResponse = { success: false, failures: [], docCount: 0, + id: '', + index: '', + pipelineId: '', }; while (resp.success === false && retries > 0) { @@ -97,12 +128,14 @@ export class Importer { resp = await ml.fileDatavisualizer.import(aggs); if (retries < IMPORT_RETRIES) { + // eslint-disable-next-line no-console console.log(`Retrying import ${IMPORT_RETRIES - retries}`); } retries--; } catch (err) { - resp = { success: false, error: err }; + resp.success = false; + resp.error = err; retries = 0; } } @@ -110,6 +143,7 @@ export class Importer { if (resp.success) { setImportProgress(((i + 1) / chunks.length) * 100); } else { + // eslint-disable-next-line no-console console.error(resp); success = false; error = resp.error; @@ -120,10 +154,10 @@ export class Importer { populateFailures(resp, failures, i); } - const result = { + const result: ImportResults = { success, failures, - docCount: this.docArray.length, + docCount: this._docArray.length, }; if (success) { @@ -136,7 +170,7 @@ export class Importer { } } -function populateFailures(error, failures, chunkCount) { +function populateFailures(error: ImportResponse, failures: ImportFailure[], chunkCount: number) { if (error.failures && error.failures.length) { // update the item value to include the chunk count // e.g. item 3 in chunk 2 is actually item 20003 @@ -155,10 +189,10 @@ function populateFailures(error, failures, chunkCount) { // But it's not sending every single field that Filebeat would add, so the ingest pipeline // cannot look for a event.timezone variable in each input record. // Therefore we need to replace {{ event.timezone }} with the actual browser timezone -function updatePipelineTimezone(ingestPipeline) { +function updatePipelineTimezone(ingestPipeline: IngestPipeline) { if (ingestPipeline !== undefined && ingestPipeline.processors && ingestPipeline.processors) { const dateProcessor = ingestPipeline.processors.find( - p => p.date !== undefined && p.date.timezone === '{{ event.timezone }}' + (p: any) => p.date !== undefined && p.date.timezone === '{{ event.timezone }}' ); if (dateProcessor) { @@ -167,8 +201,8 @@ function updatePipelineTimezone(ingestPipeline) { } } -function createDocumentChunks(docArray) { - const chunks = []; +function createDocumentChunks(docArray: Doc[]) { + const chunks: Doc[][] = []; // chop docArray into 5000 doc chunks const tempChunks = chunk(docArray, CHUNK_SIZE); diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer_factory.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer_factory.ts similarity index 76% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer_factory.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer_factory.ts index 381e8ef604452..a656b19220368 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer_factory.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer_factory.ts @@ -6,8 +6,14 @@ import { MessageImporter } from './message_importer'; import { NdjsonImporter } from './ndjson_importer'; +import { ImportConfig } from './importer'; +import { FindFileStructureResponse } from '../../../../../../../common/types/file_datavisualizer'; -export function importerFactory(format, results, settings) { +export function importerFactory( + format: string, + results: FindFileStructureResponse, + settings: ImportConfig +) { switch (format) { // delimited and semi-structured text are both handled by splitting the // file into messages, then sending these to ES for further processing diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/index.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/index.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/message_importer.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/message_importer.ts similarity index 76% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/message_importer.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/message_importer.ts index c2d3ac69f0963..7ccc5a8d673f4 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/message_importer.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/message_importer.ts @@ -4,17 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Importer } from './importer'; +import { Importer, ImportConfig } from './importer'; +import { + Doc, + FindFileStructureResponse, +} from '../../../../../../../common/types/file_datavisualizer'; export class MessageImporter extends Importer { - constructor(results, settings) { + private _excludeLinesRegex: RegExp | null; + private _multilineStartRegex: RegExp | null; + + constructor(results: FindFileStructureResponse, settings: ImportConfig) { super(settings); - this.excludeLinesRegex = + this._excludeLinesRegex = results.exclude_lines_pattern === undefined ? null : new RegExp(results.exclude_lines_pattern); - this.multilineStartRegex = + this._multilineStartRegex = results.multiline_start_pattern === undefined ? null : new RegExp(results.multiline_start_pattern); @@ -26,9 +33,9 @@ export class MessageImporter extends Importer { // multiline_start_pattern regex // if it does, it is a legitimate end of line and can be pushed into the list, // if not, it must be a newline char inside a field value, so keep looking. - read(text) { + read(text: string) { try { - const data = []; + const data: Doc[] = []; let message = ''; let line = ''; @@ -57,14 +64,12 @@ export class MessageImporter extends Importer { data.shift(); } - this.data = data; - this.docArray = this.data; + this._docArray = data; return { success: true, }; } catch (error) { - console.error(error); return { success: false, error, @@ -72,9 +77,9 @@ export class MessageImporter extends Importer { } } - processLine(data, message, line) { - if (this.excludeLinesRegex === null || line.match(this.excludeLinesRegex) === null) { - if (this.multilineStartRegex === null || line.match(this.multilineStartRegex) !== null) { + processLine(data: Doc[], message: string, line: string) { + if (this._excludeLinesRegex === null || line.match(this._excludeLinesRegex) === null) { + if (this._multilineStartRegex === null || line.match(this._multilineStartRegex) !== null) { this.addMessage(data, message); message = ''; } else if (data.length === 0) { @@ -90,7 +95,7 @@ export class MessageImporter extends Importer { return message; } - addMessage(data, message) { + addMessage(data: Doc[], message: string) { // if the message ended \r\n (Windows line endings) // then omit the \r as well as the \n for consistency message = message.replace(/\r$/, ''); diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/ndjson_importer.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/ndjson_importer.ts similarity index 71% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/ndjson_importer.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/ndjson_importer.ts index 887bf1a41200a..7f5f37abc5246 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/ndjson_importer.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/ndjson_importer.ts @@ -4,18 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Importer } from './importer'; +import { Importer, ImportConfig } from './importer'; +import { FindFileStructureResponse } from '../../../../../../../common/types/file_datavisualizer'; export class NdjsonImporter extends Importer { - constructor(results, settings) { + constructor(results: FindFileStructureResponse, settings: ImportConfig) { super(settings); } - read(json) { + read(json: string) { try { const splitJson = json.split(/}\s*\n/); - const ndjson = []; + const ndjson: any[] = []; for (let i = 0; i < splitJson.length; i++) { if (splitJson[i] !== '') { // note the extra } at the end of the line, adding back @@ -24,7 +25,7 @@ export class NdjsonImporter extends Importer { } } - this.docArray = ndjson; + this._docArray = ndjson; return { success: true, diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/index.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/index.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/results_view.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/results_view.tsx similarity index 58% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/results_view.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/results_view.tsx index 96116e5cefa01..f9de03c119d28 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/results_view.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/results_view.tsx @@ -7,10 +7,10 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; - +import React, { FC } from 'react'; import { EuiButton, + EuiButtonEmpty, EuiPage, EuiPageBody, EuiPageContentHeader, @@ -18,13 +18,33 @@ import { EuiTabbedContent, EuiSpacer, EuiTitle, + EuiFlexGroup, + EuiFlexItem, } from '@elastic/eui'; +import { FindFileStructureResponse } from '../../../../../../common/types/file_datavisualizer'; import { FileContents } from '../file_contents'; import { AnalysisSummary } from '../analysis_summary'; +// @ts-ignore import { FieldsStats } from '../fields_stats'; -export const ResultsView = ({ data, fileName, results, showEditFlyout }) => { +interface Props { + data: string; + fileName: string; + results: FindFileStructureResponse; + showEditFlyout(): void; + showExplanationFlyout(): void; + disableButtons: boolean; +} + +export const ResultsView: FC = ({ + data, + fileName, + results, + showEditFlyout, + showExplanationFlyout, + disableButtons, +}) => { const tabs = [ { id: 'file-stats', @@ -60,12 +80,24 @@ export const ResultsView = ({ data, fileName, results, showEditFlyout }) => { - showEditFlyout()}> - - + + + showEditFlyout()} disabled={disableButtons}> + + + + + showExplanationFlyout()} disabled={disableButtons}> + + + + diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/index.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/index.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/overrides.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/overrides.js deleted file mode 100644 index f8a90c87b9dc8..0000000000000 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/overrides.js +++ /dev/null @@ -1,21 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export const DEFAULT_LINES_TO_SAMPLE = 1000; - -export const overrideDefaults = { - timestampFormat: undefined, - timestampField: undefined, - format: undefined, - delimiter: undefined, - quote: undefined, - hasHeaderRow: undefined, - charset: undefined, - columnNames: undefined, - shouldTrimFields: undefined, - grokPattern: undefined, - linesToSample: undefined, -}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.ts similarity index 79% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.ts index 39cd25ba87d8c..5048065ae60fa 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.ts @@ -4,11 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ -import { overrideDefaults, DEFAULT_LINES_TO_SAMPLE } from './overrides'; import { isEqual } from 'lodash'; import { ml } from '../../../../services/ml_api_service'; - -export function readFile(file) { +import { AnalysisResult, InputOverrides } from '../../../../../../common/types/file_datavisualizer'; + +const DEFAULT_LINES_TO_SAMPLE = 1000; + +const overrideDefaults = { + timestampFormat: undefined, + timestampField: undefined, + format: undefined, + delimiter: undefined, + quote: undefined, + hasHeaderRow: undefined, + charset: undefined, + columnNames: undefined, + shouldTrimFields: undefined, + grokPattern: undefined, + linesToSample: undefined, +}; + +export function readFile(file: File) { return new Promise((resolve, reject) => { if (file && file.size) { const reader = new FileReader(); @@ -23,14 +39,14 @@ export function readFile(file) { resolve({ data }); } }; - })(file); + })(); } else { reject(); } }); } -export function reduceData(data, mb) { +export function reduceData(data: string, mb: number) { // assuming ascii characters in the file where 1 char is 1 byte // TODO - change this when other non UTF-8 formats are // supported for the read data @@ -38,8 +54,8 @@ export function reduceData(data, mb) { return data.length >= size ? data.slice(0, size) : data; } -export function createUrlOverrides(overrides, originalSettings) { - const formattedOverrides = {}; +export function createUrlOverrides(overrides: InputOverrides, originalSettings: InputOverrides) { + const formattedOverrides: InputOverrides = {}; for (const o in overrideDefaults) { if (overrideDefaults.hasOwnProperty(o)) { let value = overrides[o]; @@ -93,15 +109,15 @@ export function createUrlOverrides(overrides, originalSettings) { return formattedOverrides; } -export function processResults(results) { +export function processResults({ results, overrides }: AnalysisResult) { const timestampFormat = results.java_timestamp_formats !== undefined && results.java_timestamp_formats.length ? results.java_timestamp_formats[0] : undefined; const linesToSample = - results.overrides !== undefined && results.overrides.lines_to_sample !== undefined - ? results.overrides.lines_to_sample + overrides !== undefined && overrides.lines_to_sample !== undefined + ? overrides.lines_to_sample : DEFAULT_LINES_TO_SAMPLE; return { @@ -125,8 +141,8 @@ export function processResults(results) { * @param {string} indexName * @returns {Promise} */ -export async function hasImportPermission(indexName) { - const priv = { +export async function hasImportPermission(indexName: string) { + const priv: { cluster: string[]; index?: any } = { cluster: ['cluster:monitor/nodes/info', 'cluster:admin/ingest/pipeline/put'], }; diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/datavisualizer.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/datavisualizer.ts index 9b492530d303d..20332546d9cde 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/datavisualizer.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/datavisualizer.ts @@ -7,6 +7,7 @@ import { http } from '../http_service'; import { basePath } from './index'; +import { ImportResponse } from '../../../../common/types/file_datavisualizer'; export const fileDatavisualizer = { analyzeFile(file: string, params: Record = {}) { @@ -27,7 +28,7 @@ export const fileDatavisualizer = { mappings, ingestPipeline, }: { - id: string; + id: string | undefined; index: string; data: any; settings: any; @@ -43,7 +44,7 @@ export const fileDatavisualizer = { ingestPipeline, }); - return http({ + return http({ path: `${basePath()}/file_data_visualizer/import`, method: 'POST', query, diff --git a/x-pack/plugins/ml/server/client/elasticsearch_ml.ts b/x-pack/plugins/ml/server/client/elasticsearch_ml.ts index caedaed92e5b1..d5c7882a30d20 100644 --- a/x-pack/plugins/ml/server/client/elasticsearch_ml.ts +++ b/x-pack/plugins/ml/server/client/elasticsearch_ml.ts @@ -740,7 +740,7 @@ export const elasticsearchJsPlugin = (Client: any, config: any, components: any) urls: [ { fmt: - '/_ml/find_file_structure?&charset=<%=charset%>&format=<%=format%>&has_header_row=<%=has_header_row%>&column_names=<%=column_names%>&delimiter=<%=delimiter%>"e=<%=quote%>&should_trim_fields=<%=should_trim_fields%>&grok_pattern=<%=grok_pattern%>×tamp_field=<%=timestamp_field%>×tamp_format=<%=timestamp_format%>&lines_to_sample=<%=lines_to_sample%>', + '/_ml/find_file_structure?&explain=true&charset=<%=charset%>&format=<%=format%>&has_header_row=<%=has_header_row%>&column_names=<%=column_names%>&delimiter=<%=delimiter%>"e=<%=quote%>&should_trim_fields=<%=should_trim_fields%>&grok_pattern=<%=grok_pattern%>×tamp_field=<%=timestamp_field%>×tamp_format=<%=timestamp_format%>&lines_to_sample=<%=lines_to_sample%>', req: { charset: { type: 'string', @@ -778,7 +778,7 @@ export const elasticsearchJsPlugin = (Client: any, config: any, components: any) }, }, { - fmt: '/_ml/find_file_structure', + fmt: '/_ml/find_file_structure?&explain=true', }, ], needBody: true, diff --git a/x-pack/plugins/ml/server/client/error_wrapper.ts b/x-pack/plugins/ml/server/client/error_wrapper.ts index 7f69173295482..de53e4d4345a9 100644 --- a/x-pack/plugins/ml/server/client/error_wrapper.ts +++ b/x-pack/plugins/ml/server/client/error_wrapper.ts @@ -9,9 +9,13 @@ import { ResponseError, CustomHttpResponseOptions } from 'kibana/server'; export function wrapError(error: any): CustomHttpResponseOptions { const boom = isBoom(error) ? error : boomify(error, { statusCode: error.status }); + const statusCode = boom.output.statusCode; return { - body: boom, + body: { + message: boom, + ...(statusCode !== 500 && error.body ? { attributes: { body: error.body } } : {}), + }, headers: boom.output.headers, - statusCode: boom.output.statusCode, + statusCode, }; } diff --git a/x-pack/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts b/x-pack/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts index 9af755c6918fb..d53378b886a99 100644 --- a/x-pack/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts +++ b/x-pack/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts @@ -4,40 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; import { APICaller } from 'kibana/server'; -import { FindFileStructureResponse } from '../../../common/types/file_datavisualizer'; +import { + AnalysisResult, + FormattedOverrides, + InputOverrides, +} from '../../../common/types/file_datavisualizer'; export type InputData = any[]; -export interface InputOverrides { - [key: string]: string; -} - -export type FormattedOverrides = InputOverrides & { - column_names: string[]; - has_header_row: boolean; - should_trim_fields: boolean; -}; - -export interface AnalysisResult { - results: FindFileStructureResponse; - overrides?: FormattedOverrides; -} - export function fileDataVisualizerProvider(callAsCurrentUser: APICaller) { async function analyzeFile(data: any, overrides: any): Promise { - let results = []; - - try { - results = await callAsCurrentUser('ml.fileStructure', { - body: data, - ...overrides, - }); - } catch (error) { - const err = error.message !== undefined ? error.message : error; - throw Boom.badRequest(err); - } + const results = await callAsCurrentUser('ml.fileStructure', { + body: data, + ...overrides, + }); const { hasOverrides, reducedOverrides } = formatOverrides(overrides); diff --git a/x-pack/plugins/ml/server/models/file_data_visualizer/import_data.ts b/x-pack/plugins/ml/server/models/file_data_visualizer/import_data.ts index ab8c702cbb12a..9d7009955124f 100644 --- a/x-pack/plugins/ml/server/models/file_data_visualizer/import_data.ts +++ b/x-pack/plugins/ml/server/models/file_data_visualizer/import_data.ts @@ -6,39 +6,24 @@ import { APICaller } from 'kibana/server'; import { INDEX_META_DATA_CREATED_BY } from '../../../common/constants/file_datavisualizer'; +import { + ImportResponse, + ImportFailure, + Settings, + Mappings, + IngestPipelineWrapper, +} from '../../../common/types/file_datavisualizer'; import { InputData } from './file_data_visualizer'; -export interface Settings { - pipeline?: string; - index: string; - body: any[]; - [key: string]: any; -} - -export interface Mappings { - [key: string]: any; -} - -export interface InjectPipeline { - id: string; - pipeline: any; -} - -interface Failure { - item: number; - reason: string; - doc: any; -} - export function importDataProvider(callAsCurrentUser: APICaller) { async function importData( id: string, index: string, settings: Settings, mappings: Mappings, - ingestPipeline: InjectPipeline, + ingestPipeline: IngestPipelineWrapper, data: InputData - ) { + ): Promise { let createdIndex; let createdPipelineId; const docCount = data.length; @@ -66,7 +51,7 @@ export function importDataProvider(callAsCurrentUser: APICaller) { createdPipelineId = pipelineId; } - let failures: Failure[] = []; + let failures: ImportFailure[] = []; if (data.length) { const resp = await indexData(index, createdPipelineId, data); if (resp.success === false) { @@ -144,7 +129,7 @@ export function importDataProvider(callAsCurrentUser: APICaller) { }; } } catch (error) { - let failures: Failure[] = []; + let failures: ImportFailure[] = []; let ingestError = false; if (error.errors !== undefined && Array.isArray(error.items)) { // an expected error where some or all of the bulk request @@ -169,7 +154,7 @@ export function importDataProvider(callAsCurrentUser: APICaller) { return await callAsCurrentUser('ingest.putPipeline', { id, body: pipeline }); } - function getFailures(items: any[], data: InputData): Failure[] { + function getFailures(items: any[], data: InputData): ImportFailure[] { const failures = []; for (let i = 0; i < items.length; i++) { const item = items[i]; diff --git a/x-pack/plugins/ml/server/models/file_data_visualizer/index.ts b/x-pack/plugins/ml/server/models/file_data_visualizer/index.ts index 94529dc111696..f8a27fdcd7e1a 100644 --- a/x-pack/plugins/ml/server/models/file_data_visualizer/index.ts +++ b/x-pack/plugins/ml/server/models/file_data_visualizer/index.ts @@ -4,11 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export { - fileDataVisualizerProvider, - InputOverrides, - InputData, - AnalysisResult, -} from './file_data_visualizer'; +export { fileDataVisualizerProvider, InputData } from './file_data_visualizer'; -export { importDataProvider, Settings, InjectPipeline, Mappings } from './import_data'; +export { importDataProvider } from './import_data'; diff --git a/x-pack/plugins/ml/server/routes/file_data_visualizer.ts b/x-pack/plugins/ml/server/routes/file_data_visualizer.ts index fcfd6e121c9f1..b915d13aa9720 100644 --- a/x-pack/plugins/ml/server/routes/file_data_visualizer.ts +++ b/x-pack/plugins/ml/server/routes/file_data_visualizer.ts @@ -7,15 +7,17 @@ import { schema } from '@kbn/config-schema'; import { RequestHandlerContext } from 'kibana/server'; import { MAX_BYTES } from '../../common/constants/file_datavisualizer'; -import { wrapError } from '../client/error_wrapper'; import { InputOverrides, + Settings, + IngestPipelineWrapper, + Mappings, +} from '../../common/types/file_datavisualizer'; +import { wrapError } from '../client/error_wrapper'; +import { InputData, fileDataVisualizerProvider, importDataProvider, - Settings, - InjectPipeline, - Mappings, } from '../models/file_data_visualizer'; import { RouteInitialization } from '../types'; @@ -32,7 +34,7 @@ function importData( index: string, settings: Settings, mappings: Mappings, - ingestPipeline: InjectPipeline, + ingestPipeline: IngestPipelineWrapper, data: InputData ) { const { importData: importDataFunc } = importDataProvider(context.ml!.mlClient.callAsCurrentUser); From d1911ec843d6de590b0c24feee3942face2d4935 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Tue, 7 Apr 2020 09:52:22 +0200 Subject: [PATCH 32/36] Add label for ace editor (#62588) --- .../public/application/editor/editor.tsx | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/searchprofiler/public/application/editor/editor.tsx b/x-pack/plugins/searchprofiler/public/application/editor/editor.tsx index ece22905c64d9..27f040f3e9eec 100644 --- a/x-pack/plugins/searchprofiler/public/application/editor/editor.tsx +++ b/x-pack/plugins/searchprofiler/public/application/editor/editor.tsx @@ -5,6 +5,8 @@ */ import React, { memo, useRef, useEffect, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiScreenReaderOnly } from '@elastic/eui'; import { Editor as AceEditor } from 'brace'; import { initializeEditor } from './init_editor'; @@ -31,6 +33,8 @@ const createEditorShim = (aceEditor: AceEditor) => { }; }; +const EDITOR_INPUT_ID = 'SearchProfilerTextArea'; + export const Editor = memo(({ licenseEnabled, initialValue, onEditorReady }: Props) => { const containerRef = useRef(null as any); const editorInstanceRef = useRef(null as any); @@ -43,10 +47,25 @@ export const Editor = memo(({ licenseEnabled, initialValue, onEditorReady }: Pro const divEl = containerRef.current; editorInstanceRef.current = initializeEditor({ el: divEl, licenseEnabled }); editorInstanceRef.current.setValue(initialValue, 1); + const textarea = divEl.querySelector('textarea'); + if (textarea) { + textarea.setAttribute('id', EDITOR_INPUT_ID); + } setTextArea(licenseEnabled ? containerRef.current!.querySelector('textarea') : null); onEditorReady(createEditorShim(editorInstanceRef.current)); }, [initialValue, onEditorReady, licenseEnabled]); - return

; + return ( + <> + + + +
+ + ); }); From 3a9e7be8bfae330489c9782e34e6327a9866ba1c Mon Sep 17 00:00:00 2001 From: Tim Roes Date: Tue, 7 Apr 2020 11:08:54 +0200 Subject: [PATCH 33/36] [Lens] Remove all legacy imports (#62596) * Remove all legacy imports * Fix import --- .../public/editor_frame_service/service.test.tsx | 1 - .../lens/public/helpers/url_helper.test.ts | 2 +- .../plugins/lens/public/helpers/url_helper.ts | 2 +- x-pack/legacy/plugins/lens/public/legacy.ts | 2 -- .../legacy/plugins/lens/public/legacy_imports.ts | 10 ---------- x-pack/legacy/plugins/lens/public/plugin.tsx | 15 ++++----------- 6 files changed, 6 insertions(+), 26 deletions(-) delete mode 100644 x-pack/legacy/plugins/lens/public/legacy_imports.ts diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/service.test.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_service/service.test.tsx index 47fd810bb4c53..42a1fcc055a1e 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_service/service.test.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_service/service.test.tsx @@ -17,7 +17,6 @@ import { CoreSetup } from 'kibana/public'; jest.mock('ui/new_platform'); // mock away actual dependencies to prevent all of it being loaded -jest.mock('../../../../../../src/legacy/core_plugins/interpreter/public/registries', () => {}); jest.mock('./embeddable/embeddable_factory', () => ({ EmbeddableFactory: class Mock {}, })); diff --git a/x-pack/legacy/plugins/lens/public/helpers/url_helper.test.ts b/x-pack/legacy/plugins/lens/public/helpers/url_helper.test.ts index 9c59c9a96d00f..ef960fb52952b 100644 --- a/x-pack/legacy/plugins/lens/public/helpers/url_helper.test.ts +++ b/x-pack/legacy/plugins/lens/public/helpers/url_helper.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('../legacy_imports', () => ({ +jest.mock('../../../../../../src/plugins/dashboard/public', () => ({ DashboardConstants: { ADD_EMBEDDABLE_ID: 'addEmbeddableId', ADD_EMBEDDABLE_TYPE: 'addEmbeddableType', diff --git a/x-pack/legacy/plugins/lens/public/helpers/url_helper.ts b/x-pack/legacy/plugins/lens/public/helpers/url_helper.ts index fca44195b98c4..3495c15118ce7 100644 --- a/x-pack/legacy/plugins/lens/public/helpers/url_helper.ts +++ b/x-pack/legacy/plugins/lens/public/helpers/url_helper.ts @@ -5,7 +5,7 @@ */ import { parseUrl, stringify } from 'query-string'; -import { DashboardConstants } from '../legacy_imports'; +import { DashboardConstants } from '../../../../../../src/plugins/dashboard/public'; type UrlVars = Record; diff --git a/x-pack/legacy/plugins/lens/public/legacy.ts b/x-pack/legacy/plugins/lens/public/legacy.ts index b7d47644c7f31..3b7b6a7a1b510 100644 --- a/x-pack/legacy/plugins/lens/public/legacy.ts +++ b/x-pack/legacy/plugins/lens/public/legacy.ts @@ -5,7 +5,6 @@ */ import { npSetup, npStart } from 'ui/new_platform'; -import { visualizations } from './legacy_imports'; export * from './types'; @@ -14,6 +13,5 @@ import { plugin } from './index'; const pluginInstance = plugin(); pluginInstance.setup(npSetup.core, { ...npSetup.plugins, - __LEGACY: { visualizations }, }); pluginInstance.start(npStart.core, npStart.plugins); diff --git a/x-pack/legacy/plugins/lens/public/legacy_imports.ts b/x-pack/legacy/plugins/lens/public/legacy_imports.ts deleted file mode 100644 index 0f37e460e2957..0000000000000 --- a/x-pack/legacy/plugins/lens/public/legacy_imports.ts +++ /dev/null @@ -1,10 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { npSetup } from 'ui/new_platform'; -export const { visualizations } = npSetup.plugins; -export { VisualizationsSetup } from '../../../../../src/plugins/visualizations/public'; -export { DashboardConstants } from '../../../../../src/plugins/dashboard/public'; diff --git a/x-pack/legacy/plugins/lens/public/plugin.tsx b/x-pack/legacy/plugins/lens/public/plugin.tsx index 45817fdc3c05f..b426a12d07f9b 100644 --- a/x-pack/legacy/plugins/lens/public/plugin.tsx +++ b/x-pack/legacy/plugins/lens/public/plugin.tsx @@ -15,7 +15,9 @@ import { AppMountParameters, CoreSetup, CoreStart } from 'src/core/public'; import { DataPublicPluginSetup, DataPublicPluginStart } from 'src/plugins/data/public'; import { EmbeddableSetup, EmbeddableStart } from 'src/plugins/embeddable/public'; import { ExpressionsSetup, ExpressionsStart } from 'src/plugins/expressions/public'; +import { VisualizationsSetup } from 'src/plugins/visualizations/public'; import { KibanaLegacySetup } from 'src/plugins/kibana_legacy/public'; +import { DashboardConstants } from '../../../../../src/plugins/dashboard/public'; import { Storage } from '../../../../../src/plugins/kibana_utils/public'; import { EditorFrameService } from './editor_frame_service'; import { IndexPatternDatasource } from './indexpattern_datasource'; @@ -37,16 +39,13 @@ import { NOT_INTERNATIONALIZED_PRODUCT_NAME } from '../../../../plugins/lens/com import { addEmbeddableToDashboardUrl, getUrlVars } from './helpers'; import { EditorFrameStart } from './types'; import { getLensAliasConfig } from './vis_type_alias'; -import { VisualizationsSetup, DashboardConstants } from './legacy_imports'; export interface LensPluginSetupDependencies { kibanaLegacy: KibanaLegacySetup; expressions: ExpressionsSetup; data: DataPublicPluginSetup; embeddable: EmbeddableSetup; - __LEGACY: { - visualizations: VisualizationsSetup; - }; + visualizations: VisualizationsSetup; } export interface LensPluginStartDependencies { @@ -77,13 +76,7 @@ export class LensPlugin { setup( core: CoreSetup, - { - kibanaLegacy, - expressions, - data, - embeddable, - __LEGACY: { visualizations }, - }: LensPluginSetupDependencies + { kibanaLegacy, expressions, data, embeddable, visualizations }: LensPluginSetupDependencies ) { const editorFrameSetupInterface = this.editorFrameService.setup(core, { data, From afb8d8d7ce5f8f92f17f050b05215671f85432a7 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Tue, 7 Apr 2020 12:19:21 +0200 Subject: [PATCH 34/36] =?UTF-8?q?fix:=20=F0=9F=90=9B=20correctly=20create?= =?UTF-8?q?=20error=20on=20no=5Fmatching=5Findices=20(#61257)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 🐛 correctly create error on no_matching_indices * feat: 🎸 improve error type checking Co-authored-by: Elastic Machine --- .../index_patterns/index_patterns_api_client.ts | 2 +- .../data/server/index_patterns/routes.ts | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts index 4d4e8d8827b48..0007d1780c25b 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts @@ -45,7 +45,7 @@ export class IndexPatternsApiClient { query, }) .catch((resp: any) => { - if (resp.body.statusCode === 404 && resp.body.statuscode === 'no_matching_indices') { + if (resp.body.statusCode === 404 && resp.body.attributes?.code === 'no_matching_indices') { throw new IndexPatternMissingIndices(resp.body.message); } diff --git a/src/plugins/data/server/index_patterns/routes.ts b/src/plugins/data/server/index_patterns/routes.ts index 8f017a73083ec..8b9fa28c77165 100644 --- a/src/plugins/data/server/index_patterns/routes.ts +++ b/src/plugins/data/server/index_patterns/routes.ts @@ -70,7 +70,22 @@ export function registerRoutes(http: HttpServiceSetup) { }, }); } catch (error) { - return response.notFound(); + if ( + typeof error === 'object' && + !!error?.isBoom && + !!error?.output?.payload && + typeof error?.output?.payload === 'object' + ) { + const payload = error?.output?.payload; + return response.notFound({ + body: { + message: payload.message, + attributes: payload, + }, + }); + } else { + return response.notFound(); + } } } ); From fb0d0a583487a18985fe1c5725524faf59b83129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Tue, 7 Apr 2020 11:27:22 +0100 Subject: [PATCH 35/36] fixing APM internationalization (#62757) --- x-pack/plugins/translations/translations/ja-JP.json | 1 - x-pack/plugins/translations/translations/zh-CN.json | 1 - 2 files changed, 2 deletions(-) diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 79c1bbc49810b..2ae29202ede43 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -4531,7 +4531,6 @@ "xpack.apm.settings.apmIndices.spanIndicesLabel": "スパンインデックス", "xpack.apm.settings.apmIndices.title": "インデックス", "xpack.apm.settings.apmIndices.transactionIndicesLabel": "トランザクションインデックス", - "xpack.apm.settings.customizeUI": "UI をカスタマイズ", "xpack.apm.settings.customizeUI.customLink": "カスタムリンク", "xpack.apm.settings.customizeUI.customLink.create.failed": "リンクを保存できませんでした!", "xpack.apm.settings.customizeUI.customLink.create.failed.message": "リンクを保存するときに問題が発生しました。エラー: 「{errorMessage}」", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 77bf8f1467783..7f5df15dec83a 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -4532,7 +4532,6 @@ "xpack.apm.settings.apmIndices.spanIndicesLabel": "跨度索引", "xpack.apm.settings.apmIndices.title": "索引", "xpack.apm.settings.apmIndices.transactionIndicesLabel": "事务索引", - "xpack.apm.settings.customizeUI": "定制 UI", "xpack.apm.settings.customizeUI.customLink": "定制链接", "xpack.apm.settings.customizeUI.customLink.create.failed": "链接无法保存!", "xpack.apm.settings.customizeUI.customLink.create.failed.message": "保存链接时出现了问题。错误:“{errorMessage}”", From 008b0fda64ba340c8ec9dcbb3f7c57c5b07a8a8a Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Tue, 7 Apr 2020 13:39:30 +0300 Subject: [PATCH 36/36] [data.search.aggs] Remove service getters from agg types (#61628) * [data.search.aggs] Remove service getters from agg types Part of #60333 * new portion of changes * pass dependencies to MetricAgg Type through constructor * update docs * refactoring buckets * remove unused mockDataServices * Remove service getters from metrics * Some fixes * remove temporary code * moved notifications to the getInternalStartServices * fixed karma lock * update docs * Fixed tests * fix broken CI * fix PR comment * fix typo Co-authored-by: Elastic Machine Co-authored-by: Uladzislau Lasitsa --- ...ibana-plugin-plugins-data-public.search.md | 2 +- .../new_platform/new_platform.karma_mock.js | 8 +- .../data/common/field_formats/mocks.ts | 17 +- .../data/public/field_formats/mocks.ts | 41 ++ src/plugins/data/public/mocks.ts | 8 +- src/plugins/data/public/plugin.ts | 9 + src/plugins/data/public/public.api.md | 20 +- .../public/search/aggs/agg_config.test.ts | 15 - .../public/search/aggs/agg_params.test.ts | 18 +- .../data/public/search/aggs/agg_params.ts | 6 +- .../data/public/search/aggs/agg_type.test.ts | 135 +++--- .../data/public/search/aggs/agg_type.ts | 30 +- .../data/public/search/aggs/agg_types.ts | 132 +++--- .../search/aggs/agg_types_registry.test.ts | 2 +- .../public/search/aggs/agg_types_registry.ts | 2 +- .../search/aggs/buckets/_interval_options.ts | 2 +- .../_terms_other_bucket_helper.test.ts | 8 +- .../buckets/_terms_other_bucket_helper.ts | 2 +- ..._bucket_agg_type.ts => bucket_agg_type.ts} | 12 +- .../create_filter/date_histogram.test.ts | 11 +- .../buckets/create_filter/date_range.test.ts | 11 +- .../aggs/buckets/create_filter/date_range.ts | 2 +- .../buckets/create_filter/filters.test.ts | 12 +- .../aggs/buckets/create_filter/filters.ts | 2 +- .../buckets/create_filter/histogram.test.ts | 14 +- .../aggs/buckets/create_filter/histogram.ts | 2 +- .../buckets/create_filter/ip_range.test.ts | 19 +- .../aggs/buckets/create_filter/ip_range.ts | 2 +- .../aggs/buckets/create_filter/range.test.ts | 21 +- .../aggs/buckets/create_filter/range.ts | 2 +- .../aggs/buckets/create_filter/terms.test.ts | 31 +- .../aggs/buckets/create_filter/terms.ts | 2 +- .../search/aggs/buckets/date_histogram.ts | 378 ++++++++-------- .../search/aggs/buckets/date_range.test.ts | 7 +- .../public/search/aggs/buckets/date_range.ts | 143 ++++--- .../data/public/search/aggs/buckets/filter.ts | 25 +- .../public/search/aggs/buckets/filters.ts | 125 +++--- .../search/aggs/buckets/geo_hash.test.ts | 32 +- .../public/search/aggs/buckets/geo_hash.ts | 161 +++---- .../public/search/aggs/buckets/geo_tile.ts | 82 ++-- .../search/aggs/buckets/histogram.test.ts | 56 +-- .../public/search/aggs/buckets/histogram.ts | 304 ++++++------- .../public/search/aggs/buckets/ip_range.ts | 126 +++--- .../buckets/migrate_include_exclude_format.ts | 2 +- .../public/search/aggs/buckets/range.test.ts | 18 +- .../data/public/search/aggs/buckets/range.ts | 137 +++--- .../aggs/buckets/significant_terms.test.ts | 33 +- .../search/aggs/buckets/significant_terms.ts | 101 +++-- .../public/search/aggs/buckets/terms.test.ts | 2 +- .../data/public/search/aggs/buckets/terms.ts | 402 +++++++++--------- .../data/public/search/aggs/index.test.ts | 14 +- .../data/public/search/aggs/metrics/avg.ts | 42 +- .../public/search/aggs/metrics/bucket_avg.ts | 50 ++- .../public/search/aggs/metrics/bucket_max.ts | 30 +- .../public/search/aggs/metrics/bucket_min.ts | 30 +- .../public/search/aggs/metrics/bucket_sum.ts | 30 +- .../public/search/aggs/metrics/cardinality.ts | 58 ++- .../data/public/search/aggs/metrics/count.ts | 56 ++- .../search/aggs/metrics/cumulative_sum.ts | 30 +- .../public/search/aggs/metrics/derivative.ts | 34 +- .../public/search/aggs/metrics/geo_bounds.ts | 34 +- .../search/aggs/metrics/geo_centroid.ts | 40 +- .../data/public/search/aggs/metrics/max.ts | 42 +- .../public/search/aggs/metrics/median.test.ts | 12 +- .../data/public/search/aggs/metrics/median.ts | 60 ++- .../search/aggs/metrics/metric_agg_type.ts | 19 +- .../data/public/search/aggs/metrics/min.ts | 42 +- .../public/search/aggs/metrics/moving_avg.ts | 70 +-- .../aggs/metrics/parent_pipeline.test.ts | 43 +- .../aggs/metrics/percentile_ranks.test.ts | 22 +- .../search/aggs/metrics/percentile_ranks.ts | 128 +++--- .../search/aggs/metrics/percentiles.test.ts | 18 +- .../public/search/aggs/metrics/percentiles.ts | 80 ++-- .../public/search/aggs/metrics/serial_diff.ts | 30 +- .../aggs/metrics/sibling_pipeline.test.ts | 42 +- .../search/aggs/metrics/std_deviation.test.ts | 20 +- .../search/aggs/metrics/std_deviation.ts | 66 +-- .../data/public/search/aggs/metrics/sum.ts | 48 ++- .../search/aggs/metrics/top_hit.test.ts | 49 ++- .../public/search/aggs/metrics/top_hit.ts | 392 +++++++++-------- .../search/aggs/param_types/field.test.ts | 55 ++- .../public/search/aggs/param_types/field.ts | 13 +- .../test_helpers/mock_agg_types_registry.ts | 10 +- .../data/public/search/search_service.ts | 6 +- src/plugins/data/public/types.ts | 9 + 85 files changed, 2596 insertions(+), 1862 deletions(-) create mode 100644 src/plugins/data/public/field_formats/mocks.ts rename src/plugins/data/public/search/aggs/buckets/{_bucket_agg_type.ts => bucket_agg_type.ts} (86%) diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md index afb6ea88f9fad..78ac05b9fd386 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md @@ -19,7 +19,7 @@ search: { intervalOptions: ({ display: string; val: string; - enabled(agg: import("./search/aggs/buckets/_bucket_agg_type").IBucketAggConfig): boolean | "" | undefined; + enabled(agg: import("./search/aggs/buckets/bucket_agg_type").IBucketAggConfig): boolean | "" | undefined; } | { display: string; val: string; diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js index 25647e4a08897..f70ef069dd134 100644 --- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js +++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js @@ -88,6 +88,9 @@ const mockCoreStart = { get: sinon.fake.returns(''), }, }, + notifications: { + toasts: {}, + }, i18n: {}, overlays: {}, savedObjects: { @@ -164,8 +167,11 @@ const mockAggTypesRegistry = () => { const registrySetup = registry.setup(); const aggTypes = getAggTypes({ uiSettings: mockCoreSetup.uiSettings, - notifications: mockCoreStart.notifications, query: querySetup, + getInternalStartServices: () => ({ + fieldFormats: getFieldFormatsRegistry(mockCoreStart), + notifications: mockCoreStart.notifications, + }), }); aggTypes.buckets.forEach(type => registrySetup.registerBucket(type)); aggTypes.metrics.forEach(type => registrySetup.registerMetric(type)); diff --git a/src/plugins/data/common/field_formats/mocks.ts b/src/plugins/data/common/field_formats/mocks.ts index bc38374e147cf..394d4c383032f 100644 --- a/src/plugins/data/common/field_formats/mocks.ts +++ b/src/plugins/data/common/field_formats/mocks.ts @@ -17,23 +17,14 @@ * under the License. */ -import { FieldFormat, IFieldFormatsRegistry } from '.'; - -const fieldFormatMock = ({ - convert: jest.fn(), - getConverterFor: jest.fn(), - getParamDefaults: jest.fn(), - param: jest.fn(), - params: jest.fn(), - toJSON: jest.fn(), - type: jest.fn(), - setupContentType: jest.fn(), -} as unknown) as FieldFormat; +import { IFieldFormatsRegistry } from '.'; export const fieldFormatsMock: IFieldFormatsRegistry = { getByFieldType: jest.fn(), getDefaultConfig: jest.fn(), - getDefaultInstance: jest.fn().mockImplementation(() => fieldFormatMock) as any, + getDefaultInstance: jest.fn().mockImplementation(() => ({ + getConverterFor: jest.fn().mockImplementation(() => (t: string) => t), + })) as any, getDefaultInstanceCacheResolver: jest.fn(), getDefaultInstancePlain: jest.fn(), getDefaultType: jest.fn(), diff --git a/src/plugins/data/public/field_formats/mocks.ts b/src/plugins/data/public/field_formats/mocks.ts new file mode 100644 index 0000000000000..ec1233a085bce --- /dev/null +++ b/src/plugins/data/public/field_formats/mocks.ts @@ -0,0 +1,41 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { FieldFormatsStart, FieldFormatsSetup, FieldFormatsService } from '.'; +import { fieldFormatsMock } from '../../common/field_formats/mocks'; + +type FieldFormatsServiceClientContract = PublicMethodsOf; + +const createSetupContractMock = () => fieldFormatsMock as FieldFormatsSetup; +const createStartContractMock = () => fieldFormatsMock as FieldFormatsStart; + +const createMock = () => { + const mocked: jest.Mocked = { + setup: jest.fn().mockReturnValue(createSetupContractMock()), + start: jest.fn().mockReturnValue(createStartContractMock()), + }; + + return mocked; +}; + +export const fieldFormatsServiceMock = { + create: createMock, + createSetupContract: createSetupContractMock, + createStartContract: createStartContractMock, +}; diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts index e3fc0e97af09b..ea1c27550867e 100644 --- a/src/plugins/data/public/mocks.ts +++ b/src/plugins/data/public/mocks.ts @@ -17,8 +17,8 @@ * under the License. */ -import { Plugin, DataPublicPluginSetup, DataPublicPluginStart, IndexPatternsContract } from '.'; -import { fieldFormatsMock } from '../common/field_formats/mocks'; +import { Plugin, IndexPatternsContract } from '.'; +import { fieldFormatsServiceMock } from './field_formats/mocks'; import { searchSetupMock, searchStartMock } from './search/mocks'; import { queryServiceMock } from './query/mocks'; @@ -36,7 +36,7 @@ const createSetupContract = (): Setup => { return { autocomplete: autocompleteMock, search: searchSetupMock, - fieldFormats: fieldFormatsMock as DataPublicPluginSetup['fieldFormats'], + fieldFormats: fieldFormatsServiceMock.createSetupContract(), query: querySetupMock, }; }; @@ -49,7 +49,7 @@ const createStartContract = (): Start => { }, autocomplete: autocompleteMock, search: searchStartMock, - fieldFormats: fieldFormatsMock as DataPublicPluginStart['fieldFormats'], + fieldFormats: fieldFormatsServiceMock.createStartContract(), query: queryStartMock, ui: { IndexPatternSelect: jest.fn(), diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index 26587470adfd9..15067077afc43 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -30,6 +30,7 @@ import { DataPublicPluginStart, DataSetupDependencies, DataStartDependencies, + GetInternalStartServicesFn, } from './types'; import { AutocompleteService } from './autocomplete'; import { SearchService } from './search/search_service'; @@ -47,6 +48,8 @@ import { setQueryService, setSearchService, setUiSettings, + getFieldFormats, + getNotifications, } from './services'; import { createSearchBar } from './ui/search_bar/create_search_bar'; import { esaggs } from './search/expressions'; @@ -100,6 +103,11 @@ export class DataPublicPlugin implements Plugin ({ + fieldFormats: getFieldFormats(), + notifications: getNotifications(), + }); + const queryService = this.queryService.setup({ uiSettings: core.uiSettings, storage: this.storage, @@ -122,6 +130,7 @@ export class DataPublicPlugin implements Plugin { +export interface IDataPluginServices extends Partial { // (undocumented) appName: string; // (undocumented) data: DataPublicPluginStart; // (undocumented) - http: CoreStart_2['http']; + http: CoreStart['http']; // (undocumented) - notifications: CoreStart_2['notifications']; + notifications: CoreStart['notifications']; // (undocumented) - savedObjects: CoreStart_2['savedObjects']; + savedObjects: CoreStart['savedObjects']; // (undocumented) storage: IStorageWrapper; // (undocumented) - uiSettings: CoreStart_2['uiSettings']; + uiSettings: CoreStart['uiSettings']; } // Warning: (ae-missing-release-tag) "IEsSearchRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -1094,7 +1094,7 @@ export type ISearch = // @public (undocumented) export interface ISearchContext { // (undocumented) - core: CoreStart; + core: CoreStart_2; // (undocumented) getSearchStrategy: (name: T) => TSearchStrategyProvider; } @@ -1307,7 +1307,7 @@ export class Plugin implements Plugin_2 { let indexPattern: IndexPattern; @@ -400,13 +398,6 @@ describe('AggConfig', () => { describe('#fieldFormatter - custom getFormat handler', () => { it('returns formatter from getFormat handler', () => { - setFieldFormats({ - ...dataPluginMock.createStartContract().fieldFormats, - getDefaultInstance: jest.fn().mockImplementation(() => ({ - getConverterFor: jest.fn().mockImplementation(() => (t: string) => t), - })) as any, - }); - const ac = new AggConfigs(indexPattern, [], { typesRegistry }); const configStates = { enabled: true, @@ -429,12 +420,6 @@ describe('AggConfig', () => { let aggConfig: AggConfig; beforeEach(() => { - setFieldFormats({ - ...dataPluginMock.createStartContract().fieldFormats, - getDefaultInstance: jest.fn().mockImplementation(() => ({ - getConverterFor: (t?: string) => t || identity, - })) as any, - }); indexPattern.fields.getByName = name => ({ format: { diff --git a/src/plugins/data/public/search/aggs/agg_params.test.ts b/src/plugins/data/public/search/aggs/agg_params.test.ts index b08fcf309e9ed..784be803e2644 100644 --- a/src/plugins/data/public/search/aggs/agg_params.test.ts +++ b/src/plugins/data/public/search/aggs/agg_params.test.ts @@ -22,12 +22,22 @@ import { BaseParamType } from './param_types/base'; import { FieldParamType } from './param_types/field'; import { OptionedParamType } from './param_types/optioned'; import { AggParamType } from '../aggs/param_types/agg'; +import { fieldFormatsServiceMock } from '../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../src/core/public/mocks'; +import { AggTypeDependencies } from './agg_type'; describe('AggParams class', () => { + const aggTypesDependencies: AggTypeDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; + describe('constructor args', () => { it('accepts an array of param defs', () => { const params = [{ name: 'one' }, { name: 'two' }] as AggParamType[]; - const aggParams = initParams(params); + const aggParams = initParams(params, aggTypesDependencies); expect(aggParams).toHaveLength(params.length); expect(Array.isArray(aggParams)).toBeTruthy(); @@ -37,7 +47,7 @@ describe('AggParams class', () => { describe('AggParam creation', () => { it('Uses the FieldParamType class for params with the name "field"', () => { const params = [{ name: 'field', type: 'field' }] as AggParamType[]; - const aggParams = initParams(params); + const aggParams = initParams(params, aggTypesDependencies); expect(aggParams).toHaveLength(params.length); expect(aggParams[0] instanceof FieldParamType).toBeTruthy(); @@ -50,7 +60,7 @@ describe('AggParams class', () => { type: 'optioned', }, ] as AggParamType[]; - const aggParams = initParams(params); + const aggParams = initParams(params, aggTypesDependencies); expect(aggParams).toHaveLength(params.length); expect(aggParams[0] instanceof OptionedParamType).toBeTruthy(); @@ -72,7 +82,7 @@ describe('AggParams class', () => { }, ] as AggParamType[]; - const aggParams = initParams(params); + const aggParams = initParams(params, aggTypesDependencies); expect(aggParams).toHaveLength(params.length); diff --git a/src/plugins/data/public/search/aggs/agg_params.ts b/src/plugins/data/public/search/aggs/agg_params.ts index 551cb81529a0a..e7b2f72bae656 100644 --- a/src/plugins/data/public/search/aggs/agg_params.ts +++ b/src/plugins/data/public/search/aggs/agg_params.ts @@ -26,6 +26,7 @@ import { BaseParamType } from './param_types/base'; import { AggConfig } from './agg_config'; import { IAggConfigs } from './agg_configs'; +import { AggTypeDependencies } from './agg_type'; const paramTypeMap = { field: FieldParamType, @@ -45,12 +46,13 @@ export interface AggParamOption { } export const initParams = ( - params: TAggParam[] + params: TAggParam[], + { getInternalStartServices }: AggTypeDependencies ): TAggParam[] => params.map((config: TAggParam) => { const Class = paramTypeMap[config.type] || paramTypeMap._default; - return new Class(config); + return new Class(config, { getInternalStartServices }); }) as TAggParam[]; /** diff --git a/src/plugins/data/public/search/aggs/agg_type.test.ts b/src/plugins/data/public/search/aggs/agg_type.test.ts index 3fb03dc31e2b2..0c9e110c34ae6 100644 --- a/src/plugins/data/public/search/aggs/agg_type.test.ts +++ b/src/plugins/data/public/search/aggs/agg_type.test.ts @@ -17,40 +17,49 @@ * under the License. */ -import { AggType, AggTypeConfig } from './agg_type'; +import { AggType, AggTypeConfig, AggTypeDependencies } from './agg_type'; import { IAggConfig } from './agg_config'; -import { mockDataServices } from './test_helpers'; -import { dataPluginMock } from '../../../public/mocks'; -import { setFieldFormats } from '../../../public/services'; +import { fieldFormatsServiceMock } from '../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../src/core/public/mocks'; describe('AggType Class', () => { + let dependencies: AggTypeDependencies; + beforeEach(() => { - mockDataServices(); + dependencies = { + getInternalStartServices: () => ({ + fieldFormats: { + ...fieldFormatsServiceMock.createStartContract(), + getDefaultInstance: jest.fn(() => 'default') as any, + }, + notifications: notificationServiceMock.createStartContract(), + }), + }; }); describe('constructor', () => { - it("requires a valid config object as it's first param", () => { + test("requires a valid config object as it's first param", () => { expect(() => { const aggConfig: AggTypeConfig = (undefined as unknown) as AggTypeConfig; - new AggType(aggConfig); + new AggType(aggConfig, dependencies); }).toThrowError(); }); describe('application of config properties', () => { - it('assigns the config value to itself', () => { + test('assigns the config value to itself', () => { const config: AggTypeConfig = { name: 'name', title: 'title', }; - const aggType = new AggType(config); + const aggType = new AggType(config, dependencies); expect(aggType.name).toBe('name'); expect(aggType.title).toBe('title'); }); describe('makeLabel', () => { - it('makes a function when the makeLabel config is not specified', () => { + test('makes a function when the makeLabel config is not specified', () => { const makeLabel = () => 'label'; const aggConfig = {} as IAggConfig; const config: AggTypeConfig = { @@ -59,7 +68,7 @@ describe('AggType Class', () => { makeLabel, }; - const aggType = new AggType(config); + const aggType = new AggType(config, dependencies); expect(aggType.makeLabel).toBe(makeLabel); expect(aggType.makeLabel(aggConfig)).toBe('label'); @@ -67,26 +76,32 @@ describe('AggType Class', () => { }); describe('getResponseAggs/getRequestAggs', () => { - it('copies the value', () => { + test('copies the value', () => { const testConfig = (aggConfig: IAggConfig) => [aggConfig]; - const aggType = new AggType({ - name: 'name', - title: 'title', - getResponseAggs: testConfig, - getRequestAggs: testConfig, - }); + const aggType = new AggType( + { + name: 'name', + title: 'title', + getResponseAggs: testConfig, + getRequestAggs: testConfig, + }, + dependencies + ); expect(aggType.getResponseAggs).toBe(testConfig); expect(aggType.getResponseAggs).toBe(testConfig); }); - it('defaults to noop', () => { + test('defaults to noop', () => { const aggConfig = {} as IAggConfig; - const aggType = new AggType({ - name: 'name', - title: 'title', - }); + const aggType = new AggType( + { + name: 'name', + title: 'title', + }, + dependencies + ); const responseAggs = aggType.getRequestAggs(aggConfig); expect(responseAggs).toBe(undefined); @@ -94,11 +109,14 @@ describe('AggType Class', () => { }); describe('params', () => { - it('defaults to AggParams object with JSON param', () => { - const aggType = new AggType({ - name: 'smart agg', - title: 'title', - }); + test('defaults to AggParams object with JSON param', () => { + const aggType = new AggType( + { + name: 'smart agg', + title: 'title', + }, + dependencies + ); expect(Array.isArray(aggType.params)).toBeTruthy(); expect(aggType.params.length).toBe(2); @@ -106,26 +124,32 @@ describe('AggType Class', () => { expect(aggType.params[1].name).toBe('customLabel'); }); - it('can disable customLabel', () => { - const aggType = new AggType({ - name: 'smart agg', - title: 'title', - customLabels: false, - }); + test('can disable customLabel', () => { + const aggType = new AggType( + { + name: 'smart agg', + title: 'title', + customLabels: false, + }, + dependencies + ); expect(aggType.params.length).toBe(1); expect(aggType.params[0].name).toBe('json'); }); - it('passes the params arg directly to the AggParams constructor', () => { + test('passes the params arg directly to the AggParams constructor', () => { const params = [{ name: 'one' }, { name: 'two' }]; const paramLength = params.length + 2; // json and custom label are always appended - const aggType = new AggType({ - name: 'bucketeer', - title: 'title', - params, - }); + const aggType = new AggType( + { + name: 'bucketeer', + title: 'title', + params, + }, + dependencies + ); expect(Array.isArray(aggType.params)).toBeTruthy(); expect(aggType.params.length).toBe(paramLength); @@ -143,11 +167,14 @@ describe('AggType Class', () => { } as unknown) as IAggConfig; }); - it('returns the formatter for the aggConfig', () => { - const aggType = new AggType({ - name: 'name', - title: 'title', - }); + test('returns the formatter for the aggConfig', () => { + const aggType = new AggType( + { + name: 'name', + title: 'title', + }, + dependencies + ); field = { format: 'format', @@ -156,16 +183,14 @@ describe('AggType Class', () => { expect(aggType.getFormat(aggConfig)).toBe('format'); }); - it('returns default formatter', () => { - setFieldFormats({ - ...dataPluginMock.createStartContract().fieldFormats, - getDefaultInstance: jest.fn(() => 'default') as any, - }); - - const aggType = new AggType({ - name: 'name', - title: 'title', - }); + test('returns default formatter', () => { + const aggType = new AggType( + { + name: 'name', + title: 'title', + }, + dependencies + ); field = undefined; diff --git a/src/plugins/data/public/search/aggs/agg_type.ts b/src/plugins/data/public/search/aggs/agg_type.ts index a63d01e196612..70c116d560c6f 100644 --- a/src/plugins/data/public/search/aggs/agg_type.ts +++ b/src/plugins/data/public/search/aggs/agg_type.ts @@ -28,7 +28,7 @@ import { BaseParamType } from './param_types/base'; import { AggParamType } from './param_types/agg'; import { KBN_FIELD_TYPES, IFieldFormat } from '../../../common'; import { ISearchSource } from '../search_source'; -import { getFieldFormats } from '../../../public/services'; +import { GetInternalStartServicesFn } from '../../types'; export interface AggTypeConfig< TAggConfig extends AggConfig = AggConfig, @@ -60,16 +60,13 @@ export interface AggTypeConfig< getKey?: (bucket: any, key: any, agg: TAggConfig) => any; } -const getFormat = (agg: AggConfig) => { - const field = agg.getField(); - const fieldFormatsService = getFieldFormats(); - - return field ? field.format : fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.STRING); -}; - // TODO need to make a more explicit interface for this export type IAggType = AggType; +export interface AggTypeDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + export class AggType< TAggConfig extends AggConfig = AggConfig, TParam extends AggParamType = AggParamType @@ -215,7 +212,10 @@ export class AggType< * @private * @param {object} config - used to set the properties of the AggType */ - constructor(config: AggTypeConfig) { + constructor( + config: AggTypeConfig, + { getInternalStartServices }: AggTypeDependencies + ) { this.name = config.name; this.type = config.type || 'metrics'; this.dslName = config.dslName || config.name; @@ -251,14 +251,22 @@ export class AggType< }); } - this.params = initParams(params); + this.params = initParams(params, { getInternalStartServices }); } this.getRequestAggs = config.getRequestAggs || noop; this.getResponseAggs = config.getResponseAggs || (() => {}); this.decorateAggConfig = config.decorateAggConfig || (() => ({})); this.postFlightRequest = config.postFlightRequest || identity; - this.getFormat = config.getFormat || getFormat; + + this.getFormat = + config.getFormat || + ((agg: TAggConfig) => { + const field = agg.getField(); + const { fieldFormats } = getInternalStartServices(); + + return field ? field.format : fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.STRING); + }); this.getValue = config.getValue || ((agg: TAggConfig, bucket: any) => {}); } } diff --git a/src/plugins/data/public/search/aggs/agg_types.ts b/src/plugins/data/public/search/aggs/agg_types.ts index 556f6b0c93c41..4b154c338d48c 100644 --- a/src/plugins/data/public/search/aggs/agg_types.ts +++ b/src/plugins/data/public/search/aggs/agg_types.ts @@ -17,83 +17,89 @@ * under the License. */ -import { IUiSettingsClient, NotificationsSetup } from 'src/core/public'; +import { IUiSettingsClient } from 'src/core/public'; import { QuerySetup } from '../../query/query_service'; -import { countMetricAgg } from './metrics/count'; -import { avgMetricAgg } from './metrics/avg'; -import { sumMetricAgg } from './metrics/sum'; -import { medianMetricAgg } from './metrics/median'; -import { minMetricAgg } from './metrics/min'; -import { maxMetricAgg } from './metrics/max'; -import { topHitMetricAgg } from './metrics/top_hit'; -import { stdDeviationMetricAgg } from './metrics/std_deviation'; -import { cardinalityMetricAgg } from './metrics/cardinality'; -import { percentilesMetricAgg } from './metrics/percentiles'; -import { geoBoundsMetricAgg } from './metrics/geo_bounds'; -import { geoCentroidMetricAgg } from './metrics/geo_centroid'; -import { percentileRanksMetricAgg } from './metrics/percentile_ranks'; -import { derivativeMetricAgg } from './metrics/derivative'; -import { cumulativeSumMetricAgg } from './metrics/cumulative_sum'; -import { movingAvgMetricAgg } from './metrics/moving_avg'; -import { serialDiffMetricAgg } from './metrics/serial_diff'; +import { getCountMetricAgg } from './metrics/count'; +import { getAvgMetricAgg } from './metrics/avg'; +import { getSumMetricAgg } from './metrics/sum'; +import { getMedianMetricAgg } from './metrics/median'; +import { getMinMetricAgg } from './metrics/min'; +import { getMaxMetricAgg } from './metrics/max'; +import { getTopHitMetricAgg } from './metrics/top_hit'; +import { getStdDeviationMetricAgg } from './metrics/std_deviation'; +import { getCardinalityMetricAgg } from './metrics/cardinality'; +import { getPercentilesMetricAgg } from './metrics/percentiles'; +import { getGeoBoundsMetricAgg } from './metrics/geo_bounds'; +import { getGeoCentroidMetricAgg } from './metrics/geo_centroid'; +import { getPercentileRanksMetricAgg } from './metrics/percentile_ranks'; +import { getDerivativeMetricAgg } from './metrics/derivative'; +import { getCumulativeSumMetricAgg } from './metrics/cumulative_sum'; +import { getMovingAvgMetricAgg } from './metrics/moving_avg'; +import { getSerialDiffMetricAgg } from './metrics/serial_diff'; import { getDateHistogramBucketAgg } from './buckets/date_histogram'; import { getHistogramBucketAgg } from './buckets/histogram'; -import { rangeBucketAgg } from './buckets/range'; +import { getRangeBucketAgg } from './buckets/range'; import { getDateRangeBucketAgg } from './buckets/date_range'; -import { ipRangeBucketAgg } from './buckets/ip_range'; -import { termsBucketAgg } from './buckets/terms'; -import { filterBucketAgg } from './buckets/filter'; +import { getIpRangeBucketAgg } from './buckets/ip_range'; +import { getTermsBucketAgg } from './buckets/terms'; +import { getFilterBucketAgg } from './buckets/filter'; import { getFiltersBucketAgg } from './buckets/filters'; -import { significantTermsBucketAgg } from './buckets/significant_terms'; -import { geoHashBucketAgg } from './buckets/geo_hash'; -import { geoTileBucketAgg } from './buckets/geo_tile'; -import { bucketSumMetricAgg } from './metrics/bucket_sum'; -import { bucketAvgMetricAgg } from './metrics/bucket_avg'; -import { bucketMinMetricAgg } from './metrics/bucket_min'; -import { bucketMaxMetricAgg } from './metrics/bucket_max'; +import { getSignificantTermsBucketAgg } from './buckets/significant_terms'; +import { getGeoHashBucketAgg } from './buckets/geo_hash'; +import { getGeoTitleBucketAgg } from './buckets/geo_tile'; +import { getBucketSumMetricAgg } from './metrics/bucket_sum'; +import { getBucketAvgMetricAgg } from './metrics/bucket_avg'; +import { getBucketMinMetricAgg } from './metrics/bucket_min'; +import { getBucketMaxMetricAgg } from './metrics/bucket_max'; + +import { GetInternalStartServicesFn } from '../../types'; export interface AggTypesDependencies { - notifications: NotificationsSetup; uiSettings: IUiSettingsClient; query: QuerySetup; + getInternalStartServices: GetInternalStartServicesFn; } -export const getAggTypes = ({ notifications, uiSettings, query }: AggTypesDependencies) => ({ +export const getAggTypes = ({ + uiSettings, + query, + getInternalStartServices, +}: AggTypesDependencies) => ({ metrics: [ - countMetricAgg, - avgMetricAgg, - sumMetricAgg, - medianMetricAgg, - minMetricAgg, - maxMetricAgg, - stdDeviationMetricAgg, - cardinalityMetricAgg, - percentilesMetricAgg, - percentileRanksMetricAgg, - topHitMetricAgg, - derivativeMetricAgg, - cumulativeSumMetricAgg, - movingAvgMetricAgg, - serialDiffMetricAgg, - bucketAvgMetricAgg, - bucketSumMetricAgg, - bucketMinMetricAgg, - bucketMaxMetricAgg, - geoBoundsMetricAgg, - geoCentroidMetricAgg, + getCountMetricAgg({ getInternalStartServices }), + getAvgMetricAgg({ getInternalStartServices }), + getSumMetricAgg({ getInternalStartServices }), + getMedianMetricAgg({ getInternalStartServices }), + getMinMetricAgg({ getInternalStartServices }), + getMaxMetricAgg({ getInternalStartServices }), + getStdDeviationMetricAgg({ getInternalStartServices }), + getCardinalityMetricAgg({ getInternalStartServices }), + getPercentilesMetricAgg({ getInternalStartServices }), + getPercentileRanksMetricAgg({ getInternalStartServices }), + getTopHitMetricAgg({ getInternalStartServices }), + getDerivativeMetricAgg({ getInternalStartServices }), + getCumulativeSumMetricAgg({ getInternalStartServices }), + getMovingAvgMetricAgg({ getInternalStartServices }), + getSerialDiffMetricAgg({ getInternalStartServices }), + getBucketAvgMetricAgg({ getInternalStartServices }), + getBucketSumMetricAgg({ getInternalStartServices }), + getBucketMinMetricAgg({ getInternalStartServices }), + getBucketMaxMetricAgg({ getInternalStartServices }), + getGeoBoundsMetricAgg({ getInternalStartServices }), + getGeoCentroidMetricAgg({ getInternalStartServices }), ], buckets: [ - getDateHistogramBucketAgg({ uiSettings, query }), - getHistogramBucketAgg({ uiSettings, notifications }), - rangeBucketAgg, - getDateRangeBucketAgg({ uiSettings }), - ipRangeBucketAgg, - termsBucketAgg, - filterBucketAgg, - getFiltersBucketAgg({ uiSettings }), - significantTermsBucketAgg, - geoHashBucketAgg, - geoTileBucketAgg, + getDateHistogramBucketAgg({ uiSettings, query, getInternalStartServices }), + getHistogramBucketAgg({ uiSettings, getInternalStartServices }), + getRangeBucketAgg({ getInternalStartServices }), + getDateRangeBucketAgg({ uiSettings, getInternalStartServices }), + getIpRangeBucketAgg({ getInternalStartServices }), + getTermsBucketAgg({ getInternalStartServices }), + getFilterBucketAgg({ getInternalStartServices }), + getFiltersBucketAgg({ uiSettings, getInternalStartServices }), + getSignificantTermsBucketAgg({ getInternalStartServices }), + getGeoHashBucketAgg({ getInternalStartServices }), + getGeoTitleBucketAgg({ getInternalStartServices }), ], }); diff --git a/src/plugins/data/public/search/aggs/agg_types_registry.test.ts b/src/plugins/data/public/search/aggs/agg_types_registry.test.ts index 405f83e237de8..58d1a07d965e2 100644 --- a/src/plugins/data/public/search/aggs/agg_types_registry.test.ts +++ b/src/plugins/data/public/search/aggs/agg_types_registry.test.ts @@ -22,7 +22,7 @@ import { AggTypesRegistrySetup, AggTypesRegistryStart, } from './agg_types_registry'; -import { BucketAggType } from './buckets/_bucket_agg_type'; +import { BucketAggType } from './buckets/bucket_agg_type'; import { MetricAggType } from './metrics/metric_agg_type'; const bucketType = { name: 'terms', type: 'bucket' } as BucketAggType; diff --git a/src/plugins/data/public/search/aggs/agg_types_registry.ts b/src/plugins/data/public/search/aggs/agg_types_registry.ts index 8a8746106ae58..5a0c58120d810 100644 --- a/src/plugins/data/public/search/aggs/agg_types_registry.ts +++ b/src/plugins/data/public/search/aggs/agg_types_registry.ts @@ -17,7 +17,7 @@ * under the License. */ -import { BucketAggType } from './buckets/_bucket_agg_type'; +import { BucketAggType } from './buckets/bucket_agg_type'; import { MetricAggType } from './metrics/metric_agg_type'; export type AggTypesRegistrySetup = ReturnType; diff --git a/src/plugins/data/public/search/aggs/buckets/_interval_options.ts b/src/plugins/data/public/search/aggs/buckets/_interval_options.ts index 393d3b745250f..1c4c04c40a5c1 100644 --- a/src/plugins/data/public/search/aggs/buckets/_interval_options.ts +++ b/src/plugins/data/public/search/aggs/buckets/_interval_options.ts @@ -18,7 +18,7 @@ */ import { i18n } from '@kbn/i18n'; -import { IBucketAggConfig } from './_bucket_agg_type'; +import { IBucketAggConfig } from './bucket_agg_type'; export const intervalOptions = [ { diff --git a/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.test.ts b/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.test.ts index 9e4b93035384f..c664325a168b1 100644 --- a/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.test.ts @@ -24,8 +24,8 @@ import { } from './_terms_other_bucket_helper'; import { AggConfigs, CreateAggConfigParams } from '../agg_configs'; import { BUCKET_TYPES } from './bucket_agg_types'; -import { IBucketAggConfig } from './_bucket_agg_type'; -import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; +import { IBucketAggConfig } from './bucket_agg_type'; +import { mockAggTypesRegistry } from '../test_helpers'; const indexPattern = { id: '1234', @@ -223,10 +223,6 @@ describe('Terms Agg Other bucket helper', () => { return new AggConfigs(indexPattern, [...aggs], { typesRegistry }); }; - beforeEach(() => { - mockDataServices(); - }); - describe('buildOtherBucketAgg', () => { test('returns a function', () => { const aggConfigs = getAggConfigs(singleTerm.aggs); diff --git a/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.ts b/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.ts index 4fd988e7b7e66..abda6b5fc5980 100644 --- a/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.ts +++ b/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.ts @@ -21,7 +21,7 @@ import { isNumber, keys, values, find, each, cloneDeep, flatten } from 'lodash'; import { buildExistsFilter, buildPhrasesFilter, buildQueryFromFilters } from '../../../../common'; import { AggGroupNames } from '../agg_groups'; import { IAggConfigs } from '../agg_configs'; -import { IBucketAggConfig } from './_bucket_agg_type'; +import { IBucketAggConfig } from './bucket_agg_type'; /** * walks the aggregation DSL and returns DSL starting at aggregation with id of startFromAggId diff --git a/src/plugins/data/public/search/aggs/buckets/_bucket_agg_type.ts b/src/plugins/data/public/search/aggs/buckets/bucket_agg_type.ts similarity index 86% rename from src/plugins/data/public/search/aggs/buckets/_bucket_agg_type.ts rename to src/plugins/data/public/search/aggs/buckets/bucket_agg_type.ts index 03629c3189cbb..f3c95b444dee9 100644 --- a/src/plugins/data/public/search/aggs/buckets/_bucket_agg_type.ts +++ b/src/plugins/data/public/search/aggs/buckets/bucket_agg_type.ts @@ -21,6 +21,7 @@ import { IAggConfig } from '../agg_config'; import { KBN_FIELD_TYPES } from '../../../../common'; import { AggType, AggTypeConfig } from '../agg_type'; import { AggParamType } from '../param_types/agg'; +import { GetInternalStartServicesFn } from '../../../types'; export interface IBucketAggConfig extends IAggConfig { type: InstanceType; @@ -39,6 +40,10 @@ interface BucketAggTypeConfig getKey?: (bucket: any, key: any, agg: IAggConfig) => any; } +interface BucketAggTypeDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + export class BucketAggType extends AggType< TBucketAggConfig, BucketAggParam @@ -46,8 +51,11 @@ export class BucketAggType any; type = bucketType; - constructor(config: BucketAggTypeConfig) { - super(config); + constructor( + config: BucketAggTypeConfig, + dependencies: BucketAggTypeDependencies + ) { + super(config, dependencies); this.getKey = config.getKey || diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts index def354c4557cb..97c940b4ff4b1 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts @@ -29,8 +29,9 @@ import { } from '../date_histogram'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { RangeFilter } from '../../../../../common'; -import { coreMock } from '../../../../../../../core/public/mocks'; +import { coreMock, notificationServiceMock } from '../../../../../../../core/public/mocks'; import { queryServiceMock } from '../../../../query/mocks'; +import { fieldFormatsServiceMock } from '../../../../field_formats/mocks'; describe('AggConfig Filters', () => { describe('date_histogram', () => { @@ -46,6 +47,10 @@ describe('AggConfig Filters', () => { aggTypesDependencies = { uiSettings, query: queryServiceMock.createSetupContract(), + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), }; mockDataServices(); @@ -90,7 +95,7 @@ describe('AggConfig Filters', () => { filter = createFilterDateHistogram(agg, bucketKey); }; - it('creates a valid range filter', () => { + test('creates a valid range filter', () => { init(); expect(filter).toHaveProperty('range'); @@ -110,7 +115,7 @@ describe('AggConfig Filters', () => { expect(filter.meta).toHaveProperty('index', '1234'); }); - it('extends the filter edge to 1ms before the next bucket for all interval options', () => { + test('extends the filter edge to 1ms before the next bucket for all interval options', () => { intervalOptions.forEach(option => { let duration; if (option.val !== 'custom' && moment(1, option.val).isValid()) { diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts index 6a03176959a83..8c0466b769a7e 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts @@ -25,8 +25,9 @@ import { DateFormat } from '../../../../field_formats'; import { AggConfigs } from '../../agg_configs'; import { mockAggTypesRegistry } from '../../test_helpers'; import { BUCKET_TYPES } from '../bucket_agg_types'; -import { IBucketAggConfig } from '../_bucket_agg_type'; -import { coreMock } from '../../../../../../../core/public/mocks'; +import { IBucketAggConfig } from '../bucket_agg_type'; +import { coreMock, notificationServiceMock } from '../../../../../../../core/public/mocks'; +import { fieldFormatsServiceMock } from '../../../../field_formats/mocks'; describe('AggConfig Filters', () => { describe('Date range', () => { @@ -37,6 +38,10 @@ describe('AggConfig Filters', () => { aggTypesDependencies = { uiSettings, + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), }; }); @@ -71,7 +76,7 @@ describe('AggConfig Filters', () => { ); }; - it('should return a range filter for date_range agg', () => { + test('should return a range filter for date_range agg', () => { const aggConfigs = getAggConfigs(); const from = new Date('1 Feb 2015'); const to = new Date('7 Feb 2015'); diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.ts index 9bfded0ce9729..118e9b26e87d5 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.ts @@ -18,7 +18,7 @@ */ import moment from 'moment'; -import { IBucketAggConfig } from '../_bucket_agg_type'; +import { IBucketAggConfig } from '../bucket_agg_type'; import { DateRangeKey } from '../lib/date_range'; import { buildRangeFilter, RangeFilterParams } from '../../../../../common'; diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts index 32ada8d57c768..f5a0b5a7b9094 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts @@ -21,8 +21,9 @@ import { getFiltersBucketAgg, FiltersBucketAggDependencies } from '../filters'; import { createFilterFilters } from './filters'; import { AggConfigs } from '../../agg_configs'; import { mockAggTypesRegistry } from '../../test_helpers'; -import { IBucketAggConfig } from '../_bucket_agg_type'; -import { coreMock } from '../../../../../../../core/public/mocks'; +import { IBucketAggConfig } from '../bucket_agg_type'; +import { coreMock, notificationServiceMock } from '../../../../../../../core/public/mocks'; +import { fieldFormatsServiceMock } from '../../../../field_formats/mocks'; describe('AggConfig Filters', () => { describe('filters', () => { @@ -33,6 +34,10 @@ describe('AggConfig Filters', () => { aggTypesDependencies = { uiSettings, + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), }; }); @@ -67,7 +72,8 @@ describe('AggConfig Filters', () => { { typesRegistry: mockAggTypesRegistry([getFiltersBucketAgg(aggTypesDependencies)]) } ); }; - it('should return a filters filter', () => { + + test('should return a filters filter', () => { const aggConfigs = getAggConfigs(); const filter = createFilterFilters(aggConfigs.aggs[0] as IBucketAggConfig, 'type:nginx'); diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/filters.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/filters.ts index 3b568d805f7c0..1999b759a23d0 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/filters.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/filters.ts @@ -18,7 +18,7 @@ */ import { get } from 'lodash'; -import { IBucketAggConfig } from '../_bucket_agg_type'; +import { IBucketAggConfig } from '../bucket_agg_type'; import { buildQueryFilter } from '../../../../../common'; export const createFilterFilters = (aggConfig: IBucketAggConfig, key: string) => { diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts index dc8414d80c024..18b388be74877 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts @@ -19,19 +19,13 @@ import { createFilterHistogram } from './histogram'; import { AggConfigs } from '../../agg_configs'; -import { mockDataServices, mockAggTypesRegistry } from '../../test_helpers'; +import { mockAggTypesRegistry } from '../../test_helpers'; import { BUCKET_TYPES } from '../bucket_agg_types'; -import { IBucketAggConfig } from '../_bucket_agg_type'; +import { IBucketAggConfig } from '../bucket_agg_type'; import { BytesFormat, FieldFormatsGetConfigFn } from '../../../../../common'; describe('AggConfig Filters', () => { describe('histogram', () => { - beforeEach(() => { - mockDataServices(); - }); - - const typesRegistry = mockAggTypesRegistry(); - const getConfig = (() => {}) as FieldFormatsGetConfigFn; const getAggConfigs = () => { const field = { @@ -61,11 +55,11 @@ describe('AggConfig Filters', () => { }, }, ], - { typesRegistry } + { typesRegistry: mockAggTypesRegistry() } ); }; - it('should return an range filter for histogram', () => { + test('should return an range filter for histogram', () => { const aggConfigs = getAggConfigs(); const filter = createFilterHistogram(aggConfigs.aggs[0] as IBucketAggConfig, '2048'); diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.ts index d4c00a0991fe2..f8e7747d49147 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.ts @@ -17,7 +17,7 @@ * under the License. */ -import { IBucketAggConfig } from '../_bucket_agg_type'; +import { IBucketAggConfig } from '../bucket_agg_type'; import { buildRangeFilter, RangeFilterParams } from '../../../../../common'; export const createFilterHistogram = (aggConfig: IBucketAggConfig, key: string) => { diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts index ca51094da2f58..b528313b080d0 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts @@ -17,17 +17,26 @@ * under the License. */ -import { ipRangeBucketAgg } from '../ip_range'; +import { getIpRangeBucketAgg } from '../ip_range'; import { createFilterIpRange } from './ip_range'; import { AggConfigs, CreateAggConfigParams } from '../../agg_configs'; import { mockAggTypesRegistry } from '../../test_helpers'; import { IpFormat } from '../../../../../common'; import { BUCKET_TYPES } from '../bucket_agg_types'; -import { IBucketAggConfig } from '../_bucket_agg_type'; +import { IBucketAggConfig } from '../bucket_agg_type'; +import { fieldFormatsServiceMock } from '../../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../core/public/mocks'; describe('AggConfig Filters', () => { describe('IP range', () => { - const typesRegistry = mockAggTypesRegistry([ipRangeBucketAgg]); + const typesRegistry = mockAggTypesRegistry([ + getIpRangeBucketAgg({ + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }), + ]); const getAggConfigs = (aggs: CreateAggConfigParams[]) => { const field = { name: 'ip', @@ -46,7 +55,7 @@ describe('AggConfig Filters', () => { return new AggConfigs(indexPattern, aggs, { typesRegistry }); }; - it('should return a range filter for ip_range agg', () => { + test('should return a range filter for ip_range agg', () => { const aggConfigs = getAggConfigs([ { type: BUCKET_TYPES.IP_RANGE, @@ -75,7 +84,7 @@ describe('AggConfig Filters', () => { expect(filter.range.ip).toHaveProperty('lte', '1.1.1.1'); }); - it('should return a range filter for ip_range agg using a CIDR mask', () => { + test('should return a range filter for ip_range agg using a CIDR mask', () => { const aggConfigs = getAggConfigs([ { type: BUCKET_TYPES.IP_RANGE, diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.ts index 2d34c45aaab9d..aae212783b873 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.ts @@ -18,7 +18,7 @@ */ import { CidrMask } from '../lib/cidr_mask'; -import { IBucketAggConfig } from '../_bucket_agg_type'; +import { IBucketAggConfig } from '../bucket_agg_type'; import { IpRangeKey } from '../lib/ip_range'; import { buildRangeFilter, RangeFilterParams } from '../../../../../common'; diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/range.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/range.test.ts index 3a6f8b36a9d96..14a7538aa95a4 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/range.test.ts @@ -17,22 +17,31 @@ * under the License. */ -import { rangeBucketAgg } from '../range'; +import { getRangeBucketAgg, RangeBucketAggDependencies } from '../range'; import { createFilterRange } from './range'; import { BytesFormat, FieldFormatsGetConfigFn } from '../../../../../common'; import { AggConfigs } from '../../agg_configs'; import { mockDataServices, mockAggTypesRegistry } from '../../test_helpers'; import { BUCKET_TYPES } from '../bucket_agg_types'; -import { IBucketAggConfig } from '../_bucket_agg_type'; +import { IBucketAggConfig } from '../bucket_agg_type'; +import { fieldFormatsServiceMock } from '../../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../core/public/mocks'; describe('AggConfig Filters', () => { describe('range', () => { + let aggTypesDependencies: RangeBucketAggDependencies; + beforeEach(() => { + aggTypesDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; + mockDataServices(); }); - const typesRegistry = mockAggTypesRegistry([rangeBucketAgg]); - const getConfig = (() => {}) as FieldFormatsGetConfigFn; const getAggConfigs = () => { const field = { @@ -62,11 +71,11 @@ describe('AggConfig Filters', () => { }, }, ], - { typesRegistry } + { typesRegistry: mockAggTypesRegistry([getRangeBucketAgg(aggTypesDependencies)]) } ); }; - it('should return a range filter for range agg', () => { + test('should return a range filter for range agg', () => { const aggConfigs = getAggConfigs(); const filter = createFilterRange(aggConfigs.aggs[0] as IBucketAggConfig, { gte: 1024, diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/range.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/range.ts index d3d85f2441a8b..cbad8742bfab6 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/range.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/range.ts @@ -17,7 +17,7 @@ * under the License. */ -import { IBucketAggConfig } from '../_bucket_agg_type'; +import { IBucketAggConfig } from '../bucket_agg_type'; import { buildRangeFilter } from '../../../../../common'; export const createFilterRange = (aggConfig: IBucketAggConfig, params: any) => { diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts index 511af450b0113..c11a7d1a4e6b8 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts @@ -17,17 +17,30 @@ * under the License. */ -import { termsBucketAgg } from '../terms'; +import { getTermsBucketAgg } from '../terms'; import { createFilterTerms } from './terms'; import { AggConfigs, CreateAggConfigParams } from '../../agg_configs'; import { mockAggTypesRegistry } from '../../test_helpers'; import { BUCKET_TYPES } from '../bucket_agg_types'; -import { IBucketAggConfig } from '../_bucket_agg_type'; +import { IBucketAggConfig } from '../bucket_agg_type'; import { Filter, ExistsFilter } from '../../../../../common'; +import { RangeBucketAggDependencies } from '../range'; +import { fieldFormatsServiceMock } from '../../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../core/public/mocks'; describe('AggConfig Filters', () => { describe('terms', () => { - const typesRegistry = mockAggTypesRegistry([termsBucketAgg]); + let aggTypesDependencies: RangeBucketAggDependencies; + + beforeEach(() => { + aggTypesDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; + }); + const getAggConfigs = (aggs: CreateAggConfigParams[]) => { const indexPattern = { id: '1234', @@ -43,10 +56,12 @@ describe('AggConfig Filters', () => { indexPattern, }; - return new AggConfigs(indexPattern, aggs, { typesRegistry }); + return new AggConfigs(indexPattern, aggs, { + typesRegistry: mockAggTypesRegistry([getTermsBucketAgg(aggTypesDependencies)]), + }); }; - it('should return a match_phrase filter for terms', () => { + test('should return a match_phrase filter for terms', () => { const aggConfigs = getAggConfigs([ { type: BUCKET_TYPES.TERMS, schema: 'segment', params: { field: 'field' } }, ]); @@ -65,7 +80,7 @@ describe('AggConfig Filters', () => { expect(filter.meta).toHaveProperty('index', '1234'); }); - it('should set query to true or false for boolean filter', () => { + test('should set query to true or false for boolean filter', () => { const aggConfigs = getAggConfigs([ { type: BUCKET_TYPES.TERMS, schema: 'segment', params: { field: 'field' } }, ]); @@ -93,7 +108,7 @@ describe('AggConfig Filters', () => { expect(filterTrue.query.match_phrase.field).toBeTruthy(); }); - it('should generate correct __missing__ filter', () => { + test('should generate correct __missing__ filter', () => { const aggConfigs = getAggConfigs([ { type: BUCKET_TYPES.TERMS, schema: 'segment', params: { field: 'field' } }, ]); @@ -110,7 +125,7 @@ describe('AggConfig Filters', () => { expect(filter.meta).toHaveProperty('negate', true); }); - it('should generate correct __other__ filter', () => { + test('should generate correct __other__ filter', () => { const aggConfigs = getAggConfigs([ { type: BUCKET_TYPES.TERMS, schema: 'segment', params: { field: 'field' } }, ]); diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/terms.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/terms.ts index 43ebfc0e90db2..95de19b96abd4 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/terms.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/terms.ts @@ -17,7 +17,7 @@ * under the License. */ -import { IBucketAggConfig } from '../_bucket_agg_type'; +import { IBucketAggConfig } from '../bucket_agg_type'; import { buildPhrasesFilter, buildExistsFilter, diff --git a/src/plugins/data/public/search/aggs/buckets/date_histogram.ts b/src/plugins/data/public/search/aggs/buckets/date_histogram.ts index 7701f1bbcb4d0..e6fd259fabc92 100644 --- a/src/plugins/data/public/search/aggs/buckets/date_histogram.ts +++ b/src/plugins/data/public/search/aggs/buckets/date_histogram.ts @@ -23,7 +23,7 @@ import { i18n } from '@kbn/i18n'; import { IUiSettingsClient } from 'src/core/public'; import { TimeBuckets } from './lib/time_buckets'; -import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; +import { BucketAggType, IBucketAggConfig } from './bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; import { createFilterDateHistogram } from './create_filter/date_histogram'; import { intervalOptions } from './_interval_options'; @@ -33,8 +33,8 @@ import { isMetricAggType } from '../metrics/metric_agg_type'; import { FIELD_FORMAT_IDS, KBN_FIELD_TYPES } from '../../../../common'; import { TimefilterContract } from '../../../query'; -import { getFieldFormats } from '../../../../public/services'; import { QuerySetup } from '../../../query/query_service'; +import { GetInternalStartServicesFn } from '../../../types'; const detectedTimezone = moment.tz.guess(); const tzOffset = moment().format('Z'); @@ -61,6 +61,7 @@ interface ITimeBuckets { export interface DateHistogramBucketAggDependencies { uiSettings: IUiSettingsClient; query: QuerySetup; + getInternalStartServices: GetInternalStartServicesFn; } export interface IBucketDateHistogramAggConfig extends IBucketAggConfig { @@ -74,211 +75,218 @@ export function isDateHistogramBucketAggConfig(agg: any): agg is IBucketDateHist export const getDateHistogramBucketAgg = ({ uiSettings, query, + getInternalStartServices, }: DateHistogramBucketAggDependencies) => - new BucketAggType({ - name: BUCKET_TYPES.DATE_HISTOGRAM, - title: i18n.translate('data.search.aggs.buckets.dateHistogramTitle', { - defaultMessage: 'Date Histogram', - }), - ordered: { - date: true, - }, - makeLabel(agg) { - let output: Record = {}; + new BucketAggType( + { + name: BUCKET_TYPES.DATE_HISTOGRAM, + title: i18n.translate('data.search.aggs.buckets.dateHistogramTitle', { + defaultMessage: 'Date Histogram', + }), + ordered: { + date: true, + }, + makeLabel(agg) { + let output: Record = {}; - if (this.params) { - output = writeParams(this.params, agg); - } + if (this.params) { + output = writeParams(this.params, agg); + } - const field = agg.getFieldDisplayName(); - return i18n.translate('data.search.aggs.buckets.dateHistogramLabel', { - defaultMessage: '{fieldName} per {intervalDescription}', - values: { - fieldName: field, - intervalDescription: output.metricScaleText || output.bucketInterval.description, - }, - }); - }, - createFilter: createFilterDateHistogram, - decorateAggConfig() { - let buckets: any; + const field = agg.getFieldDisplayName(); + return i18n.translate('data.search.aggs.buckets.dateHistogramLabel', { + defaultMessage: '{fieldName} per {intervalDescription}', + values: { + fieldName: field, + intervalDescription: output.metricScaleText || output.bucketInterval.description, + }, + }); + }, + createFilter: createFilterDateHistogram, + decorateAggConfig() { + let buckets: any; - return { - buckets: { - configurable: true, - get() { - if (buckets) return buckets; + return { + buckets: { + configurable: true, + get() { + if (buckets) return buckets; - const { timefilter } = query.timefilter; - buckets = new TimeBuckets({ uiSettings }); - updateTimeBuckets(this, timefilter, buckets); + const { timefilter } = query.timefilter; + buckets = new TimeBuckets({ uiSettings }); + updateTimeBuckets(this, timefilter, buckets); - return buckets; - }, - } as any, - }; - }, - getFormat(agg) { - const DateFieldFormat = getFieldFormats().getType(FIELD_FORMAT_IDS.DATE); + return buckets; + }, + } as any, + }; + }, + getFormat(agg) { + const { fieldFormats } = getInternalStartServices(); + const DateFieldFormat = fieldFormats.getType(FIELD_FORMAT_IDS.DATE); - if (!DateFieldFormat) { - throw new Error('Unable to retrieve Date Field Format'); - } + if (!DateFieldFormat) { + throw new Error('Unable to retrieve Date Field Format'); + } - return new DateFieldFormat( + return new DateFieldFormat( + { + pattern: agg.buckets.getScaledDateFormat(), + }, + (key: string) => uiSettings.get(key) + ); + }, + params: [ { - pattern: agg.buckets.getScaledDateFormat(), + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.DATE, + default(agg: IBucketDateHistogramAggConfig) { + return agg.getIndexPattern().timeFieldName; + }, + onChange(agg: IBucketDateHistogramAggConfig) { + if (get(agg, 'params.interval') === 'auto' && !agg.fieldIsTimeField()) { + delete agg.params.interval; + } + }, }, - (key: string) => uiSettings.get(key) - ); - }, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.DATE, - default(agg: IBucketDateHistogramAggConfig) { - return agg.getIndexPattern().timeFieldName; + { + name: 'timeRange', + default: null, + write: noop, }, - onChange(agg: IBucketDateHistogramAggConfig) { - if (get(agg, 'params.interval') === 'auto' && !agg.fieldIsTimeField()) { - delete agg.params.interval; - } + { + name: 'useNormalizedEsInterval', + default: true, + write: noop, }, - }, - { - name: 'timeRange', - default: null, - write: noop, - }, - { - name: 'useNormalizedEsInterval', - default: true, - write: noop, - }, - { - name: 'scaleMetricValues', - default: false, - write: noop, - advanced: true, - }, - { - name: 'interval', - deserialize(state: any, agg) { - // For upgrading from 7.0.x to 7.1.x - intervals are now stored as key of options or custom value - if (state === 'custom') { - return get(agg, 'params.customInterval'); - } + { + name: 'scaleMetricValues', + default: false, + write: noop, + advanced: true, + }, + { + name: 'interval', + deserialize(state: any, agg) { + // For upgrading from 7.0.x to 7.1.x - intervals are now stored as key of options or custom value + if (state === 'custom') { + return get(agg, 'params.customInterval'); + } - const interval = find(intervalOptions, { val: state }); + const interval = find(intervalOptions, { val: state }); - // For upgrading from 4.0.x to 4.1.x - intervals are now stored as 'y' instead of 'year', - // but this maps the old values to the new values - if (!interval && state === 'year') { - return 'y'; - } - return state; - }, - default: 'auto', - options: intervalOptions, - write(agg, output, aggs) { - const { timefilter } = query.timefilter; - updateTimeBuckets(agg, timefilter); + // For upgrading from 4.0.x to 4.1.x - intervals are now stored as 'y' instead of 'year', + // but this maps the old values to the new values + if (!interval && state === 'year') { + return 'y'; + } + return state; + }, + default: 'auto', + options: intervalOptions, + write(agg, output, aggs) { + const { timefilter } = query.timefilter; + updateTimeBuckets(agg, timefilter); - const { useNormalizedEsInterval, scaleMetricValues } = agg.params; - const interval = agg.buckets.getInterval(useNormalizedEsInterval); - output.bucketInterval = interval; - if (interval.expression === '0ms') { - // We are hitting this code a couple of times while configuring in editor - // with an interval of 0ms because the overall time range has not yet been - // set. Since 0ms is not a valid ES interval, we cannot pass it through dateHistogramInterval - // below, since it would throw an exception. So in the cases we still have an interval of 0ms - // here we simply skip the rest of the method and never write an interval into the DSL, since - // this DSL will anyway not be used before we're passing this code with an actual interval. - return; - } - output.params = { - ...output.params, - ...dateHistogramInterval(interval.expression), - }; + const { useNormalizedEsInterval, scaleMetricValues } = agg.params; + const interval = agg.buckets.getInterval(useNormalizedEsInterval); + output.bucketInterval = interval; + if (interval.expression === '0ms') { + // We are hitting this code a couple of times while configuring in editor + // with an interval of 0ms because the overall time range has not yet been + // set. Since 0ms is not a valid ES interval, we cannot pass it through dateHistogramInterval + // below, since it would throw an exception. So in the cases we still have an interval of 0ms + // here we simply skip the rest of the method and never write an interval into the DSL, since + // this DSL will anyway not be used before we're passing this code with an actual interval. + return; + } + output.params = { + ...output.params, + ...dateHistogramInterval(interval.expression), + }; - const scaleMetrics = scaleMetricValues && interval.scaled && interval.scale < 1; - if (scaleMetrics && aggs) { - const metrics = aggs.aggs.filter(a => isMetricAggType(a.type)); - const all = every(metrics, (a: IBucketAggConfig) => { - const { type } = a; + const scaleMetrics = scaleMetricValues && interval.scaled && interval.scale < 1; + if (scaleMetrics && aggs) { + const metrics = aggs.aggs.filter(a => isMetricAggType(a.type)); + const all = every(metrics, (a: IBucketAggConfig) => { + const { type } = a; - if (isMetricAggType(type)) { - return type.isScalable(); + if (isMetricAggType(type)) { + return type.isScalable(); + } + }); + if (all) { + output.metricScale = interval.scale; + output.metricScaleText = interval.preScaled.description; } - }); - if (all) { - output.metricScale = interval.scale; - output.metricScaleText = interval.preScaled.description; } - } + }, }, - }, - { - name: 'time_zone', - default: undefined, - // We don't ever want this parameter to be serialized out (when saving or to URLs) - // since we do all the logic handling it "on the fly" in the `write` method, to prevent - // time_zones being persisted into saved_objects - serialize: noop, - write(agg, output) { - // If a time_zone has been set explicitly always prefer this. - let tz = agg.params.time_zone; - if (!tz && agg.params.field) { - // If a field has been configured check the index pattern's typeMeta if a date_histogram on that - // field requires a specific time_zone - tz = get(agg.getIndexPattern(), [ - 'typeMeta', - 'aggs', - 'date_histogram', - agg.params.field.name, - 'time_zone', - ]); - } - if (!tz) { - // If the index pattern typeMeta data, didn't had a time zone assigned for the selected field use the configured tz - const isDefaultTimezone = uiSettings.isDefault('dateFormat:tz'); - tz = isDefaultTimezone ? detectedTimezone || tzOffset : uiSettings.get('dateFormat:tz'); - } - output.params.time_zone = tz; + { + name: 'time_zone', + default: undefined, + // We don't ever want this parameter to be serialized out (when saving or to URLs) + // since we do all the logic handling it "on the fly" in the `write` method, to prevent + // time_zones being persisted into saved_objects + serialize: noop, + write(agg, output) { + // If a time_zone has been set explicitly always prefer this. + let tz = agg.params.time_zone; + if (!tz && agg.params.field) { + // If a field has been configured check the index pattern's typeMeta if a date_histogram on that + // field requires a specific time_zone + tz = get(agg.getIndexPattern(), [ + 'typeMeta', + 'aggs', + 'date_histogram', + agg.params.field.name, + 'time_zone', + ]); + } + if (!tz) { + // If the index pattern typeMeta data, didn't had a time zone assigned for the selected field use the configured tz + const isDefaultTimezone = uiSettings.isDefault('dateFormat:tz'); + tz = isDefaultTimezone + ? detectedTimezone || tzOffset + : uiSettings.get('dateFormat:tz'); + } + output.params.time_zone = tz; + }, }, - }, - { - name: 'drop_partials', - default: false, - write: noop, - shouldShow: agg => { - const field = agg.params.field; - return field && field.name && field.name === agg.getIndexPattern().timeFieldName; + { + name: 'drop_partials', + default: false, + write: noop, + shouldShow: agg => { + const field = agg.params.field; + return field && field.name && field.name === agg.getIndexPattern().timeFieldName; + }, }, - }, - { - name: 'format', - }, - { - name: 'min_doc_count', - default: 1, - }, - { - name: 'extended_bounds', - default: {}, - write(agg, output) { - const val = agg.params.extended_bounds; + { + name: 'format', + }, + { + name: 'min_doc_count', + default: 1, + }, + { + name: 'extended_bounds', + default: {}, + write(agg, output) { + const val = agg.params.extended_bounds; - if (val.min != null || val.max != null) { - output.params.extended_bounds = { - min: moment(val.min).valueOf(), - max: moment(val.max).valueOf(), - }; + if (val.min != null || val.max != null) { + output.params.extended_bounds = { + min: moment(val.min).valueOf(), + max: moment(val.max).valueOf(), + }; - return; - } + return; + } + }, }, - }, - ], - }); + ], + }, + { getInternalStartServices } + ); diff --git a/src/plugins/data/public/search/aggs/buckets/date_range.test.ts b/src/plugins/data/public/search/aggs/buckets/date_range.test.ts index 4ea550492fa09..c050620c3a856 100644 --- a/src/plugins/data/public/search/aggs/buckets/date_range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/date_range.test.ts @@ -17,11 +17,12 @@ * under the License. */ -import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { coreMock, notificationServiceMock } from '../../../../../../../src/core/public/mocks'; import { getDateRangeBucketAgg, DateRangeBucketAggDependencies } from './date_range'; import { AggConfigs } from '../agg_configs'; import { mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; describe('date_range params', () => { let aggTypesDependencies: DateRangeBucketAggDependencies; @@ -31,6 +32,10 @@ describe('date_range params', () => { aggTypesDependencies = { uiSettings, + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), }; }); diff --git a/src/plugins/data/public/search/aggs/buckets/date_range.ts b/src/plugins/data/public/search/aggs/buckets/date_range.ts index 8133a47ec7248..07d927e64a943 100644 --- a/src/plugins/data/public/search/aggs/buckets/date_range.ts +++ b/src/plugins/data/public/search/aggs/buckets/date_range.ts @@ -23,12 +23,12 @@ import { i18n } from '@kbn/i18n'; import { IUiSettingsClient } from 'src/core/public'; import { BUCKET_TYPES } from './bucket_agg_types'; -import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; +import { BucketAggType, IBucketAggConfig } from './bucket_agg_type'; import { createFilterDateRange } from './create_filter/date_range'; import { convertDateRangeToString, DateRangeKey } from './lib/date_range'; import { KBN_FIELD_TYPES, FieldFormat, TEXT_CONTEXT_TYPE } from '../../../../common'; -import { getFieldFormats } from '../../../../public/services'; +import { GetInternalStartServicesFn } from '../../../types'; const dateRangeTitle = i18n.translate('data.search.aggs.buckets.dateRangeTitle', { defaultMessage: 'Date Range', @@ -36,76 +36,85 @@ const dateRangeTitle = i18n.translate('data.search.aggs.buckets.dateRangeTitle', export interface DateRangeBucketAggDependencies { uiSettings: IUiSettingsClient; + getInternalStartServices: GetInternalStartServicesFn; } -export const getDateRangeBucketAgg = ({ uiSettings }: DateRangeBucketAggDependencies) => - new BucketAggType({ - name: BUCKET_TYPES.DATE_RANGE, - title: dateRangeTitle, - createFilter: createFilterDateRange, - getKey({ from, to }): DateRangeKey { - return { from, to }; - }, - getFormat(agg) { - const fieldFormatsService = getFieldFormats(); +export const getDateRangeBucketAgg = ({ + uiSettings, + getInternalStartServices, +}: DateRangeBucketAggDependencies) => + new BucketAggType( + { + name: BUCKET_TYPES.DATE_RANGE, + title: dateRangeTitle, + createFilter: createFilterDateRange, + getKey({ from, to }): DateRangeKey { + return { from, to }; + }, + getFormat(agg) { + const { fieldFormats } = getInternalStartServices(); - const formatter = agg.fieldOwnFormatter( - TEXT_CONTEXT_TYPE, - fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.DATE) - ); - const DateRangeFormat = FieldFormat.from(function(range: DateRangeKey) { - return convertDateRangeToString(range, formatter); - }); - return new DateRangeFormat(); - }, - makeLabel(aggConfig) { - return aggConfig.getFieldDisplayName() + ' date ranges'; - }, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.DATE, - default(agg: IBucketAggConfig) { - return agg.getIndexPattern().timeFieldName; - }, + const formatter = agg.fieldOwnFormatter( + TEXT_CONTEXT_TYPE, + fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.DATE) + ); + const DateRangeFormat = FieldFormat.from(function(range: DateRangeKey) { + return convertDateRangeToString(range, formatter); + }); + return new DateRangeFormat(); }, - { - name: 'ranges', - default: [ - { - from: 'now-1w/w', - to: 'now', - }, - ], + makeLabel(aggConfig) { + return aggConfig.getFieldDisplayName() + ' date ranges'; }, - { - name: 'time_zone', - default: undefined, - // Implimentation method is the same as that of date_histogram - serialize: () => undefined, - write: (agg, output) => { - const field = agg.getParam('field'); - let tz = agg.getParam('time_zone'); + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.DATE, + default(agg: IBucketAggConfig) { + return agg.getIndexPattern().timeFieldName; + }, + }, + { + name: 'ranges', + default: [ + { + from: 'now-1w/w', + to: 'now', + }, + ], + }, + { + name: 'time_zone', + default: undefined, + // Implimentation method is the same as that of date_histogram + serialize: () => undefined, + write: (agg, output) => { + const field = agg.getParam('field'); + let tz = agg.getParam('time_zone'); - if (!tz && field) { - tz = get(agg.getIndexPattern(), [ - 'typeMeta', - 'aggs', - 'date_range', - field.name, - 'time_zone', - ]); - } - if (!tz) { - const detectedTimezone = moment.tz.guess(); - const tzOffset = moment().format('Z'); - const isDefaultTimezone = uiSettings.isDefault('dateFormat:tz'); + if (!tz && field) { + tz = get(agg.getIndexPattern(), [ + 'typeMeta', + 'aggs', + 'date_range', + field.name, + 'time_zone', + ]); + } + if (!tz) { + const detectedTimezone = moment.tz.guess(); + const tzOffset = moment().format('Z'); + const isDefaultTimezone = uiSettings.isDefault('dateFormat:tz'); - tz = isDefaultTimezone ? detectedTimezone || tzOffset : uiSettings.get('dateFormat:tz'); - } - output.params.time_zone = tz; + tz = isDefaultTimezone + ? detectedTimezone || tzOffset + : uiSettings.get('dateFormat:tz'); + } + output.params.time_zone = tz; + }, }, - }, - ], - }); + ], + }, + { getInternalStartServices } + ); diff --git a/src/plugins/data/public/search/aggs/buckets/filter.ts b/src/plugins/data/public/search/aggs/buckets/filter.ts index 80efc0cf92071..accbdf4dd783d 100644 --- a/src/plugins/data/public/search/aggs/buckets/filter.ts +++ b/src/plugins/data/public/search/aggs/buckets/filter.ts @@ -18,19 +18,28 @@ */ import { i18n } from '@kbn/i18n'; -import { BucketAggType } from './_bucket_agg_type'; +import { BucketAggType } from './bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; const filterTitle = i18n.translate('data.search.aggs.buckets.filterTitle', { defaultMessage: 'Filter', }); -export const filterBucketAgg = new BucketAggType({ - name: BUCKET_TYPES.FILTER, - title: filterTitle, - params: [ +export interface FilterBucketAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + +export const getFilterBucketAgg = ({ getInternalStartServices }: FilterBucketAggDependencies) => + new BucketAggType( { - name: 'geo_bounding_box', + name: BUCKET_TYPES.FILTER, + title: filterTitle, + params: [ + { + name: 'geo_bounding_box', + }, + ], }, - ], -}); + { getInternalStartServices } + ); diff --git a/src/plugins/data/public/search/aggs/buckets/filters.ts b/src/plugins/data/public/search/aggs/buckets/filters.ts index 8b9aca87f8735..a42cb70a62b7d 100644 --- a/src/plugins/data/public/search/aggs/buckets/filters.ts +++ b/src/plugins/data/public/search/aggs/buckets/filters.ts @@ -23,11 +23,12 @@ import { IUiSettingsClient } from 'src/core/public'; import { createFilterFilters } from './create_filter/filters'; import { toAngularJSON } from '../utils'; -import { BucketAggType } from './_bucket_agg_type'; +import { BucketAggType } from './bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; import { Storage } from '../../../../../../plugins/kibana_utils/public'; import { getEsQueryConfig, buildEsQuery, Query } from '../../../../common'; import { getQueryLog } from '../../../query'; +import { GetInternalStartServicesFn } from '../../../types'; const filtersTitle = i18n.translate('data.search.aggs.buckets.filtersTitle', { defaultMessage: 'Filters', @@ -43,69 +44,81 @@ interface FilterValue { export interface FiltersBucketAggDependencies { uiSettings: IUiSettingsClient; + getInternalStartServices: GetInternalStartServicesFn; } -export const getFiltersBucketAgg = ({ uiSettings }: FiltersBucketAggDependencies) => - new BucketAggType({ - name: BUCKET_TYPES.FILTERS, - title: filtersTitle, - createFilter: createFilterFilters, - customLabels: false, - params: [ - { - name: 'filters', - default: [ - { input: { query: '', language: uiSettings.get('search:queryLanguage') }, label: '' }, - ], - write(aggConfig, output) { - const inFilters: FilterValue[] = aggConfig.params.filters; - if (!size(inFilters)) return; +export const getFiltersBucketAgg = ({ + uiSettings, + getInternalStartServices, +}: FiltersBucketAggDependencies) => + new BucketAggType( + { + name: BUCKET_TYPES.FILTERS, + title: filtersTitle, + createFilter: createFilterFilters, + customLabels: false, + params: [ + { + name: 'filters', + default: [ + { input: { query: '', language: uiSettings.get('search:queryLanguage') }, label: '' }, + ], + write(aggConfig, output) { + const inFilters: FilterValue[] = aggConfig.params.filters; + if (!size(inFilters)) return; - inFilters.forEach(filter => { - const persistedLog = getQueryLog( - uiSettings, - new Storage(window.localStorage), - 'vis_default_editor', - filter.input.language - ); - persistedLog.add(filter.input.query); - }); + inFilters.forEach(filter => { + const persistedLog = getQueryLog( + uiSettings, + new Storage(window.localStorage), + 'vis_default_editor', + filter.input.language + ); + persistedLog.add(filter.input.query); + }); - const outFilters = transform( - inFilters, - function(filters, filter) { - const input = cloneDeep(filter.input); + const outFilters = transform( + inFilters, + function(filters, filter) { + const input = cloneDeep(filter.input); - if (!input) { - console.log('malformed filter agg params, missing "input" query'); // eslint-disable-line no-console - return; - } + if (!input) { + console.log('malformed filter agg params, missing "input" query'); // eslint-disable-line no-console + return; + } - const esQueryConfigs = getEsQueryConfig(uiSettings); - const query = buildEsQuery(aggConfig.getIndexPattern(), [input], [], esQueryConfigs); + const esQueryConfigs = getEsQueryConfig(uiSettings); + const query = buildEsQuery( + aggConfig.getIndexPattern(), + [input], + [], + esQueryConfigs + ); - if (!query) { - console.log('malformed filter agg params, missing "query" on input'); // eslint-disable-line no-console - return; - } + if (!query) { + console.log('malformed filter agg params, missing "query" on input'); // eslint-disable-line no-console + return; + } - const matchAllLabel = filter.input.query === '' ? '*' : ''; - const label = - filter.label || - matchAllLabel || - (typeof filter.input.query === 'string' - ? filter.input.query - : toAngularJSON(filter.input.query)); - filters[label] = { query }; - }, - {} - ); + const matchAllLabel = filter.input.query === '' ? '*' : ''; + const label = + filter.label || + matchAllLabel || + (typeof filter.input.query === 'string' + ? filter.input.query + : toAngularJSON(filter.input.query)); + filters[label] = { query }; + }, + {} + ); - if (!size(outFilters)) return; + if (!size(outFilters)) return; - const params = output.params || (output.params = {}); - params.filters = outFilters; + const params = output.params || (output.params = {}); + params.filters = outFilters; + }, }, - }, - ], - }); + ], + }, + { getInternalStartServices } + ); diff --git a/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts b/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts index 408cdf22bcbc2..24270dd33a576 100644 --- a/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts @@ -17,13 +17,29 @@ * under the License. */ -import { geoHashBucketAgg } from './geo_hash'; +import { getGeoHashBucketAgg, GeoHashBucketAggDependencies } from './geo_hash'; import { AggConfigs, IAggConfigs } from '../agg_configs'; import { mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; -import { IBucketAggConfig } from './_bucket_agg_type'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { BucketAggType, IBucketAggConfig } from './bucket_agg_type'; describe('Geohash Agg', () => { + let aggTypesDependencies: GeoHashBucketAggDependencies; + let geoHashBucketAgg: BucketAggType; + + beforeEach(() => { + aggTypesDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; + + geoHashBucketAgg = getGeoHashBucketAgg(aggTypesDependencies); + }); + const getAggConfigs = (params?: Record) => { const indexPattern = { id: '1234', @@ -74,7 +90,7 @@ describe('Geohash Agg', () => { precisionParam = geoHashBucketAgg.params[PRECISION_PARAM_INDEX]; }); - it('should select precision parameter', () => { + test('should select precision parameter', () => { expect(precisionParam.name).toEqual('precision'); }); }); @@ -89,7 +105,7 @@ describe('Geohash Agg', () => { geoHashGridAgg = aggConfigs.aggs[0] as IBucketAggConfig; }); - it('should create filter, geohash_grid, and geo_centroid aggregations', () => { + test('should create filter, geohash_grid, and geo_centroid aggregations', () => { const requestAggs = geoHashBucketAgg.getRequestAggs(geoHashGridAgg) as IBucketAggConfig[]; expect(requestAggs.length).toEqual(3); @@ -101,7 +117,7 @@ describe('Geohash Agg', () => { }); describe('aggregation options', () => { - it('should only create geohash_grid and geo_centroid aggregations when isFilteredByCollar is false', () => { + test('should only create geohash_grid and geo_centroid aggregations when isFilteredByCollar is false', () => { const aggConfigs = getAggConfigs({ isFilteredByCollar: false }); const requestAggs = geoHashBucketAgg.getRequestAggs( aggConfigs.aggs[0] as IBucketAggConfig @@ -112,7 +128,7 @@ describe('Geohash Agg', () => { expect(requestAggs[1].type.name).toEqual('geo_centroid'); }); - it('should only create filter and geohash_grid aggregations when useGeocentroid is false', () => { + test('should only create filter and geohash_grid aggregations when useGeocentroid is false', () => { const aggConfigs = getAggConfigs({ useGeocentroid: false }); const requestAggs = geoHashBucketAgg.getRequestAggs( aggConfigs.aggs[0] as IBucketAggConfig @@ -138,7 +154,7 @@ describe('Geohash Agg', () => { ) as IBucketAggConfig[]; }); - it('should change geo_bounding_box filter aggregation and vis session state when map movement is outside map collar', () => { + test('should change geo_bounding_box filter aggregation and vis session state when map movement is outside map collar', () => { const [, geoBoxingBox] = geoHashBucketAgg.getRequestAggs( getAggConfigs({ boundingBox: { @@ -151,7 +167,7 @@ describe('Geohash Agg', () => { expect(originalRequestAggs[1].params).not.toEqual(geoBoxingBox.params); }); - it('should not change geo_bounding_box filter aggregation and vis session state when map movement is within map collar', () => { + test('should not change geo_bounding_box filter aggregation and vis session state when map movement is within map collar', () => { const [, geoBoxingBox] = geoHashBucketAgg.getRequestAggs( getAggConfigs({ boundingBox: { diff --git a/src/plugins/data/public/search/aggs/buckets/geo_hash.ts b/src/plugins/data/public/search/aggs/buckets/geo_hash.ts index 3ffec09a84387..eab10edad60f6 100644 --- a/src/plugins/data/public/search/aggs/buckets/geo_hash.ts +++ b/src/plugins/data/public/search/aggs/buckets/geo_hash.ts @@ -18,9 +18,10 @@ */ import { i18n } from '@kbn/i18n'; -import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; +import { BucketAggType, IBucketAggConfig } from './bucket_agg_type'; import { KBN_FIELD_TYPES } from '../../../../common'; import { BUCKET_TYPES } from './bucket_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; const defaultBoundingBox = { top_left: { lat: 1, lon: 1 }, @@ -33,83 +34,91 @@ const geohashGridTitle = i18n.translate('data.search.aggs.buckets.geohashGridTit defaultMessage: 'Geohash', }); -export const geoHashBucketAgg = new BucketAggType({ - name: BUCKET_TYPES.GEOHASH_GRID, - title: geohashGridTitle, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.GEO_POINT, - }, - { - name: 'autoPrecision', - default: true, - write: () => {}, - }, - { - name: 'precision', - default: defaultPrecision, - write(aggConfig, output) { - output.params.precision = aggConfig.params.precision; - }, - }, - { - name: 'useGeocentroid', - default: true, - write: () => {}, - }, - { - name: 'isFilteredByCollar', - default: true, - write: () => {}, - }, +export interface GeoHashBucketAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + +export const getGeoHashBucketAgg = ({ getInternalStartServices }: GeoHashBucketAggDependencies) => + new BucketAggType( { - name: 'boundingBox', - default: null, - write: () => {}, - }, - ], - getRequestAggs(agg) { - const aggs = []; - const params = agg.params; + name: BUCKET_TYPES.GEOHASH_GRID, + title: geohashGridTitle, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.GEO_POINT, + }, + { + name: 'autoPrecision', + default: true, + write: () => {}, + }, + { + name: 'precision', + default: defaultPrecision, + write(aggConfig, output) { + output.params.precision = aggConfig.params.precision; + }, + }, + { + name: 'useGeocentroid', + default: true, + write: () => {}, + }, + { + name: 'isFilteredByCollar', + default: true, + write: () => {}, + }, + { + name: 'boundingBox', + default: null, + write: () => {}, + }, + ], + getRequestAggs(agg) { + const aggs = []; + const params = agg.params; - if (params.isFilteredByCollar && agg.getField()) { - aggs.push( - agg.aggConfigs.createAggConfig( - { - type: 'filter', - id: 'filter_agg', - enabled: true, - params: { - geo_bounding_box: { - ignore_unmapped: true, - [agg.getField().name]: params.boundingBox || defaultBoundingBox, - }, - }, - } as any, - { addToAggConfigs: false } - ) - ); - } + if (params.isFilteredByCollar && agg.getField()) { + aggs.push( + agg.aggConfigs.createAggConfig( + { + type: 'filter', + id: 'filter_agg', + enabled: true, + params: { + geo_bounding_box: { + ignore_unmapped: true, + [agg.getField().name]: params.boundingBox || defaultBoundingBox, + }, + }, + } as any, + { addToAggConfigs: false } + ) + ); + } - aggs.push(agg); + aggs.push(agg); - if (params.useGeocentroid) { - aggs.push( - agg.aggConfigs.createAggConfig( - { - type: 'geo_centroid', - enabled: true, - params: { - field: agg.getField(), - }, - }, - { addToAggConfigs: false } - ) - ); - } + if (params.useGeocentroid) { + aggs.push( + agg.aggConfigs.createAggConfig( + { + type: 'geo_centroid', + enabled: true, + params: { + field: agg.getField(), + }, + }, + { addToAggConfigs: false } + ) + ); + } - return aggs; - }, -}); + return aggs; + }, + }, + { getInternalStartServices } + ); diff --git a/src/plugins/data/public/search/aggs/buckets/geo_tile.ts b/src/plugins/data/public/search/aggs/buckets/geo_tile.ts index 759601fc0c180..c981e8400f9a1 100644 --- a/src/plugins/data/public/search/aggs/buckets/geo_tile.ts +++ b/src/plugins/data/public/search/aggs/buckets/geo_tile.ts @@ -20,53 +20,61 @@ import { i18n } from '@kbn/i18n'; import { noop } from 'lodash'; -import { BucketAggType } from './_bucket_agg_type'; +import { BucketAggType, IBucketAggConfig } from './bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; -import { IBucketAggConfig } from './_bucket_agg_type'; import { METRIC_TYPES } from '../metrics/metric_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface GeoTitleBucketAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const geotileGridTitle = i18n.translate('data.search.aggs.buckets.geotileGridTitle', { defaultMessage: 'Geotile', }); -export const geoTileBucketAgg = new BucketAggType({ - name: BUCKET_TYPES.GEOTILE_GRID, - title: geotileGridTitle, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.GEO_POINT, - }, +export const getGeoTitleBucketAgg = ({ getInternalStartServices }: GeoTitleBucketAggDependencies) => + new BucketAggType( { - name: 'useGeocentroid', - default: true, - write: noop, - }, - { - name: 'precision', - default: 0, - }, - ], - getRequestAggs(agg) { - const aggs = []; - const useGeocentroid = agg.getParam('useGeocentroid'); + name: BUCKET_TYPES.GEOTILE_GRID, + title: geotileGridTitle, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.GEO_POINT, + }, + { + name: 'useGeocentroid', + default: true, + write: noop, + }, + { + name: 'precision', + default: 0, + }, + ], + getRequestAggs(agg) { + const aggs = []; + const useGeocentroid = agg.getParam('useGeocentroid'); - aggs.push(agg); + aggs.push(agg); - if (useGeocentroid) { - const aggConfig = { - type: METRIC_TYPES.GEO_CENTROID, - enabled: true, - params: { - field: agg.getField(), - }, - }; + if (useGeocentroid) { + const aggConfig = { + type: METRIC_TYPES.GEO_CENTROID, + enabled: true, + params: { + field: agg.getField(), + }, + }; - aggs.push(agg.aggConfigs.createAggConfig(aggConfig, { addToAggConfigs: false })); - } + aggs.push(agg.aggConfigs.createAggConfig(aggConfig, { addToAggConfigs: false })); + } - return aggs as IBucketAggConfig[]; - }, -}); + return aggs as IBucketAggConfig[]; + }, + }, + { getInternalStartServices } + ); diff --git a/src/plugins/data/public/search/aggs/buckets/histogram.test.ts b/src/plugins/data/public/search/aggs/buckets/histogram.test.ts index c61b4ff37935a..bbfc263df4268 100644 --- a/src/plugins/data/public/search/aggs/buckets/histogram.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/histogram.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { coreMock, notificationServiceMock } from '../../../../../../../src/core/public/mocks'; import { AggConfigs } from '../agg_configs'; import { mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; @@ -27,17 +27,21 @@ import { AutoBounds, HistogramBucketAggDependencies, } from './histogram'; -import { BucketAggType } from './_bucket_agg_type'; +import { BucketAggType } from './bucket_agg_type'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; describe('Histogram Agg', () => { let aggTypesDependencies: HistogramBucketAggDependencies; beforeEach(() => { - const { uiSettings, notifications } = coreMock.createSetup(); + const { uiSettings } = coreMock.createSetup(); aggTypesDependencies = { uiSettings, - notifications, + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), }; }); @@ -87,18 +91,18 @@ describe('Histogram Agg', () => { histogramType = getHistogramBucketAgg(aggTypesDependencies); }); - it('is ordered', () => { + test('is ordered', () => { expect(histogramType.ordered).toBeDefined(); }); - it('is not ordered by date', () => { + test('is not ordered by date', () => { expect(histogramType.ordered).not.toHaveProperty('date'); }); }); describe('params', () => { describe('intervalBase', () => { - it('should not be written to the DSL', () => { + test('should not be written to the DSL', () => { const aggConfigs = getAggConfigs({ intervalBase: 100, field: { @@ -112,7 +116,7 @@ describe('Histogram Agg', () => { }); describe('interval', () => { - it('accepts a whole number', () => { + test('accepts a whole number', () => { const params = getParams({ interval: 100, }); @@ -120,7 +124,7 @@ describe('Histogram Agg', () => { expect(params).toHaveProperty('interval', 100); }); - it('accepts a decimal number', function() { + test('accepts a decimal number', () => { const params = getParams({ interval: 0.1, }); @@ -128,7 +132,7 @@ describe('Histogram Agg', () => { expect(params).toHaveProperty('interval', 0.1); }); - it('accepts a decimal number string', function() { + test('accepts a decimal number string', () => { const params = getParams({ interval: '0.1', }); @@ -136,7 +140,7 @@ describe('Histogram Agg', () => { expect(params).toHaveProperty('interval', 0.1); }); - it('accepts a whole number string', function() { + test('accepts a whole number string', () => { const params = getParams({ interval: '10', }); @@ -144,7 +148,7 @@ describe('Histogram Agg', () => { expect(params).toHaveProperty('interval', 10); }); - it('fails on non-numeric values', function() { + test('fails on non-numeric values', () => { const params = getParams({ interval: [], }); @@ -181,7 +185,7 @@ describe('Histogram Agg', () => { return aggConfig.write(aggConfigs).params; }; - it('will respect the histogram:maxBars setting', () => { + test('will respect the histogram:maxBars setting', () => { const params = getInterval( 5, { interval: 5 }, @@ -194,19 +198,19 @@ describe('Histogram Agg', () => { expect(params).toHaveProperty('interval', 2000); }); - it('will return specified interval, if bars are below histogram:maxBars config', () => { + test('will return specified interval, if bars are below histogram:maxBars config', () => { const params = getInterval(100, { interval: 5 }); expect(params).toHaveProperty('interval', 5); }); - it('will set to intervalBase if interval is below base', () => { + test('will set to intervalBase if interval is below base', () => { const params = getInterval(1000, { interval: 3, intervalBase: 8 }); expect(params).toHaveProperty('interval', 8); }); - it('will round to nearest intervalBase multiple if interval is above base', () => { + test('will round to nearest intervalBase multiple if interval is above base', () => { const roundUp = getInterval(1000, { interval: 46, intervalBase: 10 }); expect(roundUp).toHaveProperty('interval', 50); @@ -214,13 +218,13 @@ describe('Histogram Agg', () => { expect(roundDown).toHaveProperty('interval', 40); }); - it('will not change interval if it is a multiple of base', () => { + test('will not change interval if it is a multiple of base', () => { const output = getInterval(1000, { interval: 35, intervalBase: 5 }); expect(output).toHaveProperty('interval', 35); }); - it('will round to intervalBase after scaling histogram:maxBars', () => { + test('will round to intervalBase after scaling histogram:maxBars', () => { const output = getInterval(100, { interval: 5, intervalBase: 6 }, { min: 0, max: 1000 }); // 100 buckets in 0 to 1000 would result in an interval of 10, so we should @@ -232,7 +236,7 @@ describe('Histogram Agg', () => { describe('min_doc_count', () => { let output: Record; - it('casts true values to 0', () => { + test('casts true values to 0', () => { output = getParams({ min_doc_count: true }); expect(output).toHaveProperty('min_doc_count', 0); @@ -246,7 +250,7 @@ describe('Histogram Agg', () => { expect(output).toHaveProperty('min_doc_count', 0); }); - it('writes 1 for falsy values', () => { + test('writes 1 for falsy values', () => { output = getParams({ min_doc_count: '' }); expect(output).toHaveProperty('min_doc_count', 1); @@ -258,8 +262,8 @@ describe('Histogram Agg', () => { }); }); - describe('extended_bounds', function() { - it('does not write when only eb.min is set', function() { + describe('extended_bounds', () => { + test('does not write when only eb.min is set', () => { const output = getParams({ has_extended_bounds: true, extended_bounds: { min: 0 }, @@ -267,7 +271,7 @@ describe('Histogram Agg', () => { expect(output).not.toHaveProperty('extended_bounds'); }); - it('does not write when only eb.max is set', function() { + test('does not write when only eb.max is set', () => { const output = getParams({ has_extended_bounds: true, extended_bounds: { max: 0 }, @@ -276,7 +280,7 @@ describe('Histogram Agg', () => { expect(output).not.toHaveProperty('extended_bounds'); }); - it('writes when both eb.min and eb.max are set', function() { + test('writes when both eb.min and eb.max are set', () => { const output = getParams({ has_extended_bounds: true, extended_bounds: { min: 99, max: 100 }, @@ -286,7 +290,7 @@ describe('Histogram Agg', () => { expect(output.extended_bounds).toHaveProperty('max', 100); }); - it('does not write when nothing is set', function() { + test('does not write when nothing is set', () => { const output = getParams({ has_extended_bounds: true, extended_bounds: {}, @@ -295,7 +299,7 @@ describe('Histogram Agg', () => { expect(output).not.toHaveProperty('extended_bounds'); }); - it('does not write when has_extended_bounds is false', function() { + test('does not write when has_extended_bounds is false', () => { const output = getParams({ has_extended_bounds: false, extended_bounds: { min: 99, max: 100 }, diff --git a/src/plugins/data/public/search/aggs/buckets/histogram.ts b/src/plugins/data/public/search/aggs/buckets/histogram.ts index bbffc0912bf0d..f8e8720d24ea9 100644 --- a/src/plugins/data/public/search/aggs/buckets/histogram.ts +++ b/src/plugins/data/public/search/aggs/buckets/histogram.ts @@ -19,12 +19,13 @@ import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { IUiSettingsClient, NotificationsSetup } from 'src/core/public'; +import { IUiSettingsClient } from 'src/core/public'; -import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; +import { BucketAggType, IBucketAggConfig } from './bucket_agg_type'; import { createFilterHistogram } from './create_filter/histogram'; import { BUCKET_TYPES } from './bucket_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; export interface AutoBounds { min: number; @@ -33,7 +34,7 @@ export interface AutoBounds { export interface HistogramBucketAggDependencies { uiSettings: IUiSettingsClient; - notifications: NotificationsSetup; + getInternalStartServices: GetInternalStartServicesFn; } export interface IBucketHistogramAggConfig extends IBucketAggConfig { @@ -43,164 +44,167 @@ export interface IBucketHistogramAggConfig extends IBucketAggConfig { export const getHistogramBucketAgg = ({ uiSettings, - notifications, + getInternalStartServices, }: HistogramBucketAggDependencies) => - new BucketAggType({ - name: BUCKET_TYPES.HISTOGRAM, - title: i18n.translate('data.search.aggs.buckets.histogramTitle', { - defaultMessage: 'Histogram', - }), - ordered: {}, - makeLabel(aggConfig) { - return aggConfig.getFieldDisplayName(); - }, - createFilter: createFilterHistogram, - decorateAggConfig() { - let autoBounds: AutoBounds; - - return { - setAutoBounds: { - configurable: true, - value(newValue: AutoBounds) { - autoBounds = newValue; + new BucketAggType( + { + name: BUCKET_TYPES.HISTOGRAM, + title: i18n.translate('data.search.aggs.buckets.histogramTitle', { + defaultMessage: 'Histogram', + }), + ordered: {}, + makeLabel(aggConfig) { + return aggConfig.getFieldDisplayName(); + }, + createFilter: createFilterHistogram, + decorateAggConfig() { + let autoBounds: AutoBounds; + + return { + setAutoBounds: { + configurable: true, + value(newValue: AutoBounds) { + autoBounds = newValue; + }, }, - }, - getAutoBounds: { - configurable: true, - value() { - return autoBounds; + getAutoBounds: { + configurable: true, + value() { + return autoBounds; + }, }, - }, - }; - }, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.NUMBER, - }, - { - /* - * This parameter can be set if you want the auto scaled interval to always - * be a multiple of a specific base. - */ - name: 'intervalBase', - default: null, - write: () => {}, + }; }, - { - name: 'interval', - modifyAggConfigOnSearchRequestStart( - aggConfig: IBucketHistogramAggConfig, - searchSource: any, - options: any - ) { - const field = aggConfig.getField(); - const aggBody = field.scripted - ? { script: { source: field.script, lang: field.lang } } - : { field: field.name }; - - const childSearchSource = searchSource - .createChild() - .setField('size', 0) - .setField('aggs', { - maxAgg: { - max: aggBody, - }, - minAgg: { - min: aggBody, - }, - }); - - return childSearchSource - .fetch(options) - .then((resp: any) => { - aggConfig.setAutoBounds({ - min: get(resp, 'aggregations.minAgg.value'), - max: get(resp, 'aggregations.maxAgg.value'), - }); - }) - .catch((e: Error) => { - if (e.name === 'AbortError') return; - notifications.toasts.addWarning( - i18n.translate('data.search.aggs.histogram.missingMaxMinValuesWarning', { - defaultMessage: - 'Unable to retrieve max and min values to auto-scale histogram buckets. This may lead to poor visualization performance.', - }) - ); - }); + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.NUMBER, + }, + { + /* + * This parameter can be set if you want the auto scaled interval to always + * be a multiple of a specific base. + */ + name: 'intervalBase', + default: null, + write: () => {}, }, - write(aggConfig, output) { - let interval = parseFloat(aggConfig.params.interval); - if (interval <= 0) { - interval = 1; - } - const autoBounds = aggConfig.getAutoBounds(); - - // ensure interval does not create too many buckets and crash browser - if (autoBounds) { - const range = autoBounds.max - autoBounds.min; - const bars = range / interval; - - if (bars > uiSettings.get('histogram:maxBars')) { - const minInterval = range / uiSettings.get('histogram:maxBars'); - - // Round interval by order of magnitude to provide clean intervals - // Always round interval up so there will always be less buckets than histogram:maxBars - const orderOfMagnitude = Math.pow(10, Math.floor(Math.log10(minInterval))); - let roundInterval = orderOfMagnitude; - - while (roundInterval < minInterval) { - roundInterval += orderOfMagnitude; + { + name: 'interval', + modifyAggConfigOnSearchRequestStart( + aggConfig: IBucketHistogramAggConfig, + searchSource: any, + options: any + ) { + const field = aggConfig.getField(); + const aggBody = field.scripted + ? { script: { source: field.script, lang: field.lang } } + : { field: field.name }; + + const childSearchSource = searchSource + .createChild() + .setField('size', 0) + .setField('aggs', { + maxAgg: { + max: aggBody, + }, + minAgg: { + min: aggBody, + }, + }); + + return childSearchSource + .fetch(options) + .then((resp: any) => { + aggConfig.setAutoBounds({ + min: get(resp, 'aggregations.minAgg.value'), + max: get(resp, 'aggregations.maxAgg.value'), + }); + }) + .catch((e: Error) => { + if (e.name === 'AbortError') return; + getInternalStartServices().notifications.toasts.addWarning( + i18n.translate('data.search.aggs.histogram.missingMaxMinValuesWarning', { + defaultMessage: + 'Unable to retrieve max and min values to auto-scale histogram buckets. This may lead to poor visualization performance.', + }) + ); + }); + }, + write(aggConfig, output) { + let interval = parseFloat(aggConfig.params.interval); + if (interval <= 0) { + interval = 1; + } + const autoBounds = aggConfig.getAutoBounds(); + + // ensure interval does not create too many buckets and crash browser + if (autoBounds) { + const range = autoBounds.max - autoBounds.min; + const bars = range / interval; + + if (bars > uiSettings.get('histogram:maxBars')) { + const minInterval = range / uiSettings.get('histogram:maxBars'); + + // Round interval by order of magnitude to provide clean intervals + // Always round interval up so there will always be less buckets than histogram:maxBars + const orderOfMagnitude = Math.pow(10, Math.floor(Math.log10(minInterval))); + let roundInterval = orderOfMagnitude; + + while (roundInterval < minInterval) { + roundInterval += orderOfMagnitude; + } + interval = roundInterval; } - interval = roundInterval; } - } - const base = aggConfig.params.intervalBase; - - if (base) { - if (interval < base) { - // In case the specified interval is below the base, just increase it to it's base - interval = base; - } else if (interval % base !== 0) { - // In case the interval is not a multiple of the base round it to the next base - interval = Math.round(interval / base) * base; + const base = aggConfig.params.intervalBase; + + if (base) { + if (interval < base) { + // In case the specified interval is below the base, just increase it to it's base + interval = base; + } else if (interval % base !== 0) { + // In case the interval is not a multiple of the base round it to the next base + interval = Math.round(interval / base) * base; + } } - } - output.params.interval = interval; + output.params.interval = interval; + }, }, - }, - { - name: 'min_doc_count', - default: false, - write(aggConfig, output) { - if (aggConfig.params.min_doc_count) { - output.params.min_doc_count = 0; - } else { - output.params.min_doc_count = 1; - } + { + name: 'min_doc_count', + default: false, + write(aggConfig, output) { + if (aggConfig.params.min_doc_count) { + output.params.min_doc_count = 0; + } else { + output.params.min_doc_count = 1; + } + }, }, - }, - { - name: 'has_extended_bounds', - default: false, - write: () => {}, - }, - { - name: 'extended_bounds', - default: { - min: '', - max: '', + { + name: 'has_extended_bounds', + default: false, + write: () => {}, }, - write(aggConfig, output) { - const { min, max } = aggConfig.params.extended_bounds; + { + name: 'extended_bounds', + default: { + min: '', + max: '', + }, + write(aggConfig, output) { + const { min, max } = aggConfig.params.extended_bounds; - if (aggConfig.params.has_extended_bounds && (min || min === 0) && (max || max === 0)) { - output.params.extended_bounds = { min, max }; - } + if (aggConfig.params.has_extended_bounds && (min || min === 0) && (max || max === 0)) { + output.params.extended_bounds = { min, max }; + } + }, + shouldShow: (aggConfig: IBucketAggConfig) => aggConfig.params.has_extended_bounds, }, - shouldShow: (aggConfig: IBucketAggConfig) => aggConfig.params.has_extended_bounds, - }, - ], - }); + ], + }, + { getInternalStartServices } + ); diff --git a/src/plugins/data/public/search/aggs/buckets/ip_range.ts b/src/plugins/data/public/search/aggs/buckets/ip_range.ts index da6866d40a52f..bde347d6e673d 100644 --- a/src/plugins/data/public/search/aggs/buckets/ip_range.ts +++ b/src/plugins/data/public/search/aggs/buckets/ip_range.ts @@ -19,77 +19,85 @@ import { noop, map, omit, isNull } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { BucketAggType } from './_bucket_agg_type'; +import { BucketAggType } from './bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; import { createFilterIpRange } from './create_filter/ip_range'; import { IpRangeKey, convertIPRangeToString } from './lib/ip_range'; import { KBN_FIELD_TYPES, FieldFormat, TEXT_CONTEXT_TYPE } from '../../../../common'; -import { getFieldFormats } from '../../../../public/services'; +import { GetInternalStartServicesFn } from '../../../types'; const ipRangeTitle = i18n.translate('data.search.aggs.buckets.ipRangeTitle', { defaultMessage: 'IPv4 Range', }); -export const ipRangeBucketAgg = new BucketAggType({ - name: BUCKET_TYPES.IP_RANGE, - title: ipRangeTitle, - createFilter: createFilterIpRange, - getKey(bucket, key, agg): IpRangeKey { - if (agg.params.ipRangeType === 'mask') { - return { type: 'mask', mask: key }; - } - return { type: 'range', from: bucket.from, to: bucket.to }; - }, - getFormat(agg) { - const fieldFormatsService = getFieldFormats(); - const formatter = agg.fieldOwnFormatter( - TEXT_CONTEXT_TYPE, - fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.IP) - ); - const IpRangeFormat = FieldFormat.from(function(range: IpRangeKey) { - return convertIPRangeToString(range, formatter); - }); - return new IpRangeFormat(); - }, - makeLabel(aggConfig) { - return i18n.translate('data.search.aggs.buckets.ipRangeLabel', { - defaultMessage: '{fieldName} IP ranges', - values: { - fieldName: aggConfig.getFieldDisplayName(), - }, - }); - }, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.IP, - }, - { - name: 'ipRangeType', - default: 'fromTo', - write: noop, - }, +export interface IpRangeBucketAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + +export const getIpRangeBucketAgg = ({ getInternalStartServices }: IpRangeBucketAggDependencies) => + new BucketAggType( { - name: 'ranges', - default: { - fromTo: [ - { from: '0.0.0.0', to: '127.255.255.255' }, - { from: '128.0.0.0', to: '191.255.255.255' }, - ], - mask: [{ mask: '0.0.0.0/1' }, { mask: '128.0.0.0/2' }], + name: BUCKET_TYPES.IP_RANGE, + title: ipRangeTitle, + createFilter: createFilterIpRange, + getKey(bucket, key, agg): IpRangeKey { + if (agg.params.ipRangeType === 'mask') { + return { type: 'mask', mask: key }; + } + return { type: 'range', from: bucket.from, to: bucket.to }; + }, + getFormat(agg) { + const { fieldFormats } = getInternalStartServices(); + const formatter = agg.fieldOwnFormatter( + TEXT_CONTEXT_TYPE, + fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.IP) + ); + const IpRangeFormat = FieldFormat.from(function(range: IpRangeKey) { + return convertIPRangeToString(range, formatter); + }); + return new IpRangeFormat(); + }, + makeLabel(aggConfig) { + return i18n.translate('data.search.aggs.buckets.ipRangeLabel', { + defaultMessage: '{fieldName} IP ranges', + values: { + fieldName: aggConfig.getFieldDisplayName(), + }, + }); }, - write(aggConfig, output) { - const ipRangeType = aggConfig.params.ipRangeType; - let ranges = aggConfig.params.ranges[ipRangeType]; + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.IP, + }, + { + name: 'ipRangeType', + default: 'fromTo', + write: noop, + }, + { + name: 'ranges', + default: { + fromTo: [ + { from: '0.0.0.0', to: '127.255.255.255' }, + { from: '128.0.0.0', to: '191.255.255.255' }, + ], + mask: [{ mask: '0.0.0.0/1' }, { mask: '128.0.0.0/2' }], + }, + write(aggConfig, output) { + const ipRangeType = aggConfig.params.ipRangeType; + let ranges = aggConfig.params.ranges[ipRangeType]; - if (ipRangeType === 'fromTo') { - ranges = map(ranges, (range: any) => omit(range, isNull)); - } + if (ipRangeType === 'fromTo') { + ranges = map(ranges, (range: any) => omit(range, isNull)); + } - output.params.ranges = ranges; - }, + output.params.ranges = ranges; + }, + }, + ], }, - ], -}); + { getInternalStartServices } + ); diff --git a/src/plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts b/src/plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts index d94477b588f8d..0beeb1c372275 100644 --- a/src/plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts +++ b/src/plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts @@ -18,7 +18,7 @@ */ import { isString, isObject } from 'lodash'; -import { IBucketAggConfig, BucketAggType, BucketAggParam } from './_bucket_agg_type'; +import { IBucketAggConfig, BucketAggType, BucketAggParam } from './bucket_agg_type'; import { IAggConfig } from '../agg_config'; export const isType = (type: string) => { diff --git a/src/plugins/data/public/search/aggs/buckets/range.test.ts b/src/plugins/data/public/search/aggs/buckets/range.test.ts index bf3711543ae88..a1f0ab6a2326a 100644 --- a/src/plugins/data/public/search/aggs/buckets/range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/range.test.ts @@ -17,11 +17,13 @@ * under the License. */ -import { rangeBucketAgg } from './range'; +import { getRangeBucketAgg, RangeBucketAggDependencies } from './range'; import { AggConfigs } from '../agg_configs'; import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; import { FieldFormatsGetConfigFn, NumberFormat } from '../../../../common'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; const buckets = [ { @@ -44,7 +46,16 @@ const buckets = [ ]; describe('Range Agg', () => { + let aggTypesDependencies: RangeBucketAggDependencies; + beforeEach(() => { + aggTypesDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; + mockDataServices(); }); @@ -84,15 +95,14 @@ describe('Range Agg', () => { }, }, ], - { typesRegistry: mockAggTypesRegistry([rangeBucketAgg]) } + { typesRegistry: mockAggTypesRegistry([getRangeBucketAgg(aggTypesDependencies)]) } ); }; describe('formating', () => { - it('formats bucket keys properly', () => { + test('formats bucket keys properly', () => { const aggConfigs = getAggConfigs(); const agg = aggConfigs.aggs[0]; - const format = (val: any) => agg.fieldFormatter()(agg.getKey(val)); expect(format(buckets[0])).toBe('≥ -∞ and < 1 KB'); diff --git a/src/plugins/data/public/search/aggs/buckets/range.ts b/src/plugins/data/public/search/aggs/buckets/range.ts index 036a0d4c1e8da..2c1303814a88a 100644 --- a/src/plugins/data/public/search/aggs/buckets/range.ts +++ b/src/plugins/data/public/search/aggs/buckets/range.ts @@ -18,11 +18,12 @@ */ import { i18n } from '@kbn/i18n'; -import { BucketAggType } from './_bucket_agg_type'; +import { BucketAggType } from './bucket_agg_type'; import { FieldFormat, KBN_FIELD_TYPES } from '../../../../common'; import { RangeKey } from './range_key'; import { createFilterRange } from './create_filter/range'; import { BUCKET_TYPES } from './bucket_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; const keyCaches = new WeakMap(); const formats = new WeakMap(); @@ -31,76 +32,84 @@ const rangeTitle = i18n.translate('data.search.aggs.buckets.rangeTitle', { defaultMessage: 'Range', }); -export const rangeBucketAgg = new BucketAggType({ - name: BUCKET_TYPES.RANGE, - title: rangeTitle, - createFilter: createFilterRange, - makeLabel(aggConfig) { - return i18n.translate('data.search.aggs.aggTypesLabel', { - defaultMessage: '{fieldName} ranges', - values: { - fieldName: aggConfig.getFieldDisplayName(), +export interface RangeBucketAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + +export const getRangeBucketAgg = ({ getInternalStartServices }: RangeBucketAggDependencies) => + new BucketAggType( + { + name: BUCKET_TYPES.RANGE, + title: rangeTitle, + createFilter: createFilterRange, + makeLabel(aggConfig) { + return i18n.translate('data.search.aggs.aggTypesLabel', { + defaultMessage: '{fieldName} ranges', + values: { + fieldName: aggConfig.getFieldDisplayName(), + }, + }); }, - }); - }, - getKey(bucket, key, agg) { - let keys = keyCaches.get(agg); + getKey(bucket, key, agg) { + let keys = keyCaches.get(agg); - if (!keys) { - keys = new Map(); - keyCaches.set(agg, keys); - } + if (!keys) { + keys = new Map(); + keyCaches.set(agg, keys); + } - const id = RangeKey.idBucket(bucket); + const id = RangeKey.idBucket(bucket); - key = keys.get(id); - if (!key) { - key = new RangeKey(bucket); - keys.set(id, key); - } + key = keys.get(id); + if (!key) { + key = new RangeKey(bucket); + keys.set(id, key); + } - return key; - }, - getFormat(agg) { - let aggFormat = formats.get(agg); - if (aggFormat) return aggFormat; + return key; + }, + getFormat(agg) { + let aggFormat = formats.get(agg); + if (aggFormat) return aggFormat; - const RangeFormat = FieldFormat.from((range: any) => { - const format = agg.fieldOwnFormatter(); - const gte = '\u2265'; - const lt = '\u003c'; - return i18n.translate('data.search.aggs.aggTypes.rangesFormatMessage', { - defaultMessage: '{gte} {from} and {lt} {to}', - values: { - gte, - from: format(range.gte), - lt, - to: format(range.lt), - }, - }); - }); + const RangeFormat = FieldFormat.from((range: any) => { + const format = agg.fieldOwnFormatter(); + const gte = '\u2265'; + const lt = '\u003c'; + return i18n.translate('data.search.aggs.aggTypes.rangesFormatMessage', { + defaultMessage: '{gte} {from} and {lt} {to}', + values: { + gte, + from: format(range.gte), + lt, + to: format(range.lt), + }, + }); + }); - aggFormat = new RangeFormat(); + aggFormat = new RangeFormat(); - formats.set(agg, aggFormat); - return aggFormat; - }, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: [KBN_FIELD_TYPES.NUMBER], - }, - { - name: 'ranges', - default: [ - { from: 0, to: 1000 }, - { from: 1000, to: 2000 }, - ], - write(aggConfig, output) { - output.params.ranges = aggConfig.params.ranges; - output.params.keyed = true; + formats.set(agg, aggFormat); + return aggFormat; }, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: [KBN_FIELD_TYPES.NUMBER], + }, + { + name: 'ranges', + default: [ + { from: 0, to: 1000 }, + { from: 1000, to: 2000 }, + ], + write(aggConfig, output) { + output.params.ranges = aggConfig.params.ranges; + output.params.keyed = true; + }, + }, + ], }, - ], -}); + { getInternalStartServices } + ); diff --git a/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts b/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts index 1c221126c35e0..761d0ced6a114 100644 --- a/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts @@ -20,12 +20,27 @@ import { AggConfigs, IAggConfigs } from '../agg_configs'; import { mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; -import { significantTermsBucketAgg } from './significant_terms'; -import { IBucketAggConfig } from './_bucket_agg_type'; +import { + getSignificantTermsBucketAgg, + SignificantTermsBucketAggDependencies, +} from './significant_terms'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; describe('Significant Terms Agg', () => { describe('order agg editor UI', () => { describe('convert include/exclude from old format', () => { + let aggTypesDependencies: SignificantTermsBucketAggDependencies; + + beforeEach(() => { + aggTypesDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; + }); + const getAggConfigs = (params: Record = {}) => { const indexPattern = { id: '1234', @@ -51,7 +66,11 @@ describe('Significant Terms Agg', () => { params, }, ], - { typesRegistry: mockAggTypesRegistry([significantTermsBucketAgg]) } + { + typesRegistry: mockAggTypesRegistry([ + getSignificantTermsBucketAgg(aggTypesDependencies), + ]), + } ); }; @@ -64,19 +83,19 @@ describe('Significant Terms Agg', () => { expect(params.exclude).toBe('400'); }; - it('should generate correct label', () => { + test('should generate correct label', () => { const aggConfigs = getAggConfigs({ size: 'SIZE', field: { name: 'FIELD', }, }); - const label = significantTermsBucketAgg.makeLabel(aggConfigs.aggs[0] as IBucketAggConfig); + const label = aggConfigs.aggs[0].makeLabel(); expect(label).toBe('Top SIZE unusual terms in FIELD'); }); - it('should doesnt do anything with string type', () => { + test('should doesnt do anything with string type', () => { const aggConfigs = getAggConfigs({ include: '404', exclude: '400', @@ -89,7 +108,7 @@ describe('Significant Terms Agg', () => { testSerializeAndWrite(aggConfigs); }); - it('should converts object to string type', () => { + test('should converts object to string type', () => { const aggConfigs = getAggConfigs({ include: { pattern: '404', diff --git a/src/plugins/data/public/search/aggs/buckets/significant_terms.ts b/src/plugins/data/public/search/aggs/buckets/significant_terms.ts index f12ebe58e2de2..49d797f3afbc9 100644 --- a/src/plugins/data/public/search/aggs/buckets/significant_terms.ts +++ b/src/plugins/data/public/search/aggs/buckets/significant_terms.ts @@ -18,59 +18,72 @@ */ import { i18n } from '@kbn/i18n'; -import { BucketAggType } from './_bucket_agg_type'; +import { BucketAggType } from './bucket_agg_type'; import { createFilterTerms } from './create_filter/terms'; import { isStringType, migrateIncludeExcludeFormat } from './migrate_include_exclude_format'; import { BUCKET_TYPES } from './bucket_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; const significantTermsTitle = i18n.translate('data.search.aggs.buckets.significantTermsTitle', { defaultMessage: 'Significant Terms', }); -export const significantTermsBucketAgg = new BucketAggType({ - name: BUCKET_TYPES.SIGNIFICANT_TERMS, - title: significantTermsTitle, - makeLabel(aggConfig) { - return i18n.translate('data.search.aggs.buckets.significantTermsLabel', { - defaultMessage: 'Top {size} unusual terms in {fieldName}', - values: { - size: aggConfig.params.size, - fieldName: aggConfig.getFieldDisplayName(), - }, - }); - }, - createFilter: createFilterTerms, - params: [ - { - name: 'field', - type: 'field', - scriptable: false, - filterFieldTypes: KBN_FIELD_TYPES.STRING, - }, - { - name: 'size', - default: '', - }, +export interface SignificantTermsBucketAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + +export const getSignificantTermsBucketAgg = ({ + getInternalStartServices, +}: SignificantTermsBucketAggDependencies) => + new BucketAggType( { - name: 'exclude', - displayName: i18n.translate('data.search.aggs.buckets.significantTerms.excludeLabel', { - defaultMessage: 'Exclude', - }), - type: 'string', - advanced: true, - shouldShow: isStringType, - ...migrateIncludeExcludeFormat, + name: BUCKET_TYPES.SIGNIFICANT_TERMS, + title: significantTermsTitle, + makeLabel(aggConfig) { + return i18n.translate('data.search.aggs.buckets.significantTermsLabel', { + defaultMessage: 'Top {size} unusual terms in {fieldName}', + values: { + size: aggConfig.params.size, + fieldName: aggConfig.getFieldDisplayName(), + }, + }); + }, + createFilter: createFilterTerms, + params: [ + { + name: 'field', + type: 'field', + scriptable: false, + filterFieldTypes: KBN_FIELD_TYPES.STRING, + }, + { + name: 'size', + default: '', + }, + { + name: 'exclude', + displayName: i18n.translate('data.search.aggs.buckets.significantTerms.excludeLabel', { + defaultMessage: 'Exclude', + }), + type: 'string', + advanced: true, + shouldShow: isStringType, + ...migrateIncludeExcludeFormat, + }, + { + name: 'include', + displayName: i18n.translate('data.search.aggs.buckets.significantTerms.includeLabel', { + defaultMessage: 'Include', + }), + type: 'string', + advanced: true, + shouldShow: isStringType, + ...migrateIncludeExcludeFormat, + }, + ], }, { - name: 'include', - displayName: i18n.translate('data.search.aggs.buckets.significantTerms.includeLabel', { - defaultMessage: 'Include', - }), - type: 'string', - advanced: true, - shouldShow: isStringType, - ...migrateIncludeExcludeFormat, - }, - ], -}); + getInternalStartServices, + } + ); diff --git a/src/plugins/data/public/search/aggs/buckets/terms.test.ts b/src/plugins/data/public/search/aggs/buckets/terms.test.ts index 280d78f6620bd..5afe7d0b0c35c 100644 --- a/src/plugins/data/public/search/aggs/buckets/terms.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/terms.test.ts @@ -51,7 +51,7 @@ describe('Terms Agg', () => { ); }; - it('converts object to string type', function() { + test('converts object to string type', () => { const aggConfigs = getAggConfigs({ include: { pattern: '404', diff --git a/src/plugins/data/public/search/aggs/buckets/terms.ts b/src/plugins/data/public/search/aggs/buckets/terms.ts index 813c657934a76..5baa38af0e8d6 100644 --- a/src/plugins/data/public/search/aggs/buckets/terms.ts +++ b/src/plugins/data/public/search/aggs/buckets/terms.ts @@ -19,9 +19,8 @@ import { noop } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { BucketAggType } from './_bucket_agg_type'; +import { BucketAggType, IBucketAggConfig } from './bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; -import { IBucketAggConfig } from './_bucket_agg_type'; import { createFilterTerms } from './create_filter/terms'; import { isStringType, migrateIncludeExcludeFormat } from './migrate_include_exclude_format'; import { IAggConfigs } from '../agg_configs'; @@ -36,6 +35,7 @@ import { mergeOtherBucketAggResponse, updateMissingBucket, } from './_terms_other_bucket_helper'; +import { GetInternalStartServicesFn } from '../../../types'; export const termsAggFilter = [ '!top_hits', @@ -56,220 +56,230 @@ const termsTitle = i18n.translate('data.search.aggs.buckets.termsTitle', { defaultMessage: 'Terms', }); -export const termsBucketAgg = new BucketAggType({ - name: BUCKET_TYPES.TERMS, - title: termsTitle, - makeLabel(agg) { - const params = agg.params; - return agg.getFieldDisplayName() + ': ' + params.order.text; - }, - getFormat(bucket): IFieldFormat { - return { - getConverterFor: (type: FieldFormatsContentType) => { - return (val: any) => { - if (val === '__other__') { - return bucket.params.otherBucketLabel; - } - if (val === '__missing__') { - return bucket.params.missingBucketLabel; - } +export interface TermsBucketAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} - return bucket.params.field.format.convert(val, type); - }; +export const getTermsBucketAgg = ({ getInternalStartServices }: TermsBucketAggDependencies) => + new BucketAggType( + { + name: BUCKET_TYPES.TERMS, + title: termsTitle, + makeLabel(agg) { + const params = agg.params; + return agg.getFieldDisplayName() + ': ' + params.order.text; }, - } as IFieldFormat; - }, - createFilter: createFilterTerms, - postFlightRequest: async ( - resp: any, - aggConfigs: IAggConfigs, - aggConfig: IBucketAggConfig, - searchSource: ISearchSource, - inspectorAdapters: Adapters, - abortSignal?: AbortSignal - ) => { - if (!resp.aggregations) return resp; - const nestedSearchSource = searchSource.createChild(); - if (aggConfig.params.otherBucket) { - const filterAgg = buildOtherBucketAgg(aggConfigs, aggConfig, resp); - if (!filterAgg) return resp; + getFormat(bucket): IFieldFormat { + return { + getConverterFor: (type: FieldFormatsContentType) => { + return (val: any) => { + if (val === '__other__') { + return bucket.params.otherBucketLabel; + } + if (val === '__missing__') { + return bucket.params.missingBucketLabel; + } - nestedSearchSource.setField('aggs', filterAgg); + return bucket.params.field.format.convert(val, type); + }; + }, + } as IFieldFormat; + }, + createFilter: createFilterTerms, + postFlightRequest: async ( + resp: any, + aggConfigs: IAggConfigs, + aggConfig: IBucketAggConfig, + searchSource: ISearchSource, + inspectorAdapters: Adapters, + abortSignal?: AbortSignal + ) => { + if (!resp.aggregations) return resp; + const nestedSearchSource = searchSource.createChild(); + if (aggConfig.params.otherBucket) { + const filterAgg = buildOtherBucketAgg(aggConfigs, aggConfig, resp); + if (!filterAgg) return resp; - const request = inspectorAdapters.requests.start( - i18n.translate('data.search.aggs.buckets.terms.otherBucketTitle', { - defaultMessage: 'Other bucket', - }), - { - description: i18n.translate('data.search.aggs.buckets.terms.otherBucketDescription', { - defaultMessage: - 'This request counts the number of documents that fall ' + - 'outside the criterion of the data buckets.', - }), - } - ); - nestedSearchSource.getSearchRequestBody().then((body: string) => { - request.json(body); - }); - request.stats(getRequestInspectorStats(nestedSearchSource)); + nestedSearchSource.setField('aggs', filterAgg); - const response = await nestedSearchSource.fetch({ abortSignal }); - request.stats(getResponseInspectorStats(nestedSearchSource, response)).ok({ json: response }); - resp = mergeOtherBucketAggResponse(aggConfigs, resp, response, aggConfig, filterAgg()); - } - if (aggConfig.params.missingBucket) { - resp = updateMissingBucket(resp, aggConfigs, aggConfig); - } - return resp; - }, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: [ - KBN_FIELD_TYPES.NUMBER, - KBN_FIELD_TYPES.BOOLEAN, - KBN_FIELD_TYPES.DATE, - KBN_FIELD_TYPES.IP, - KBN_FIELD_TYPES.STRING, - ], - }, - { - name: 'orderBy', - write: noop, // prevent default write, it's handled by orderAgg - }, - { - name: 'orderAgg', - type: 'agg', - allowedAggs: termsAggFilter, - default: null, - makeAgg(termsAgg, state) { - state = state || {}; - state.schema = 'orderAgg'; - const orderAgg = termsAgg.aggConfigs.createAggConfig(state, { - addToAggConfigs: false, - }); - orderAgg.id = termsAgg.id + '-orderAgg'; + const request = inspectorAdapters.requests.start( + i18n.translate('data.search.aggs.buckets.terms.otherBucketTitle', { + defaultMessage: 'Other bucket', + }), + { + description: i18n.translate('data.search.aggs.buckets.terms.otherBucketDescription', { + defaultMessage: + 'This request counts the number of documents that fall ' + + 'outside the criterion of the data buckets.', + }), + } + ); + nestedSearchSource.getSearchRequestBody().then((body: string) => { + request.json(body); + }); + request.stats(getRequestInspectorStats(nestedSearchSource)); - return orderAgg; + const response = await nestedSearchSource.fetch({ abortSignal }); + request + .stats(getResponseInspectorStats(nestedSearchSource, response)) + .ok({ json: response }); + resp = mergeOtherBucketAggResponse(aggConfigs, resp, response, aggConfig, filterAgg()); + } + if (aggConfig.params.missingBucket) { + resp = updateMissingBucket(resp, aggConfigs, aggConfig); + } + return resp; }, - write(agg, output, aggs) { - const dir = agg.params.order.value; - const order: Record = (output.params.order = {}); + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: [ + KBN_FIELD_TYPES.NUMBER, + KBN_FIELD_TYPES.BOOLEAN, + KBN_FIELD_TYPES.DATE, + KBN_FIELD_TYPES.IP, + KBN_FIELD_TYPES.STRING, + ], + }, + { + name: 'orderBy', + write: noop, // prevent default write, it's handled by orderAgg + }, + { + name: 'orderAgg', + type: 'agg', + allowedAggs: termsAggFilter, + default: null, + makeAgg(termsAgg, state) { + state = state || {}; + state.schema = 'orderAgg'; + const orderAgg = termsAgg.aggConfigs.createAggConfig(state, { + addToAggConfigs: false, + }); + orderAgg.id = termsAgg.id + '-orderAgg'; - let orderAgg = agg.params.orderAgg || aggs!.getResponseAggById(agg.params.orderBy); + return orderAgg; + }, + write(agg, output, aggs) { + const dir = agg.params.order.value; + const order: Record = (output.params.order = {}); - // TODO: This works around an Elasticsearch bug the always casts terms agg scripts to strings - // thus causing issues with filtering. This probably causes other issues since float might not - // be able to contain the number on the elasticsearch side - if (output.params.script) { - output.params.value_type = - agg.getField().type === 'number' ? 'float' : agg.getField().type; - } + let orderAgg = agg.params.orderAgg || aggs!.getResponseAggById(agg.params.orderBy); - if (agg.params.missingBucket && agg.params.field.type === 'string') { - output.params.missing = '__missing__'; - } + // TODO: This works around an Elasticsearch bug the always casts terms agg scripts to strings + // thus causing issues with filtering. This probably causes other issues since float might not + // be able to contain the number on the elasticsearch side + if (output.params.script) { + output.params.value_type = + agg.getField().type === 'number' ? 'float' : agg.getField().type; + } - if (!orderAgg) { - order[agg.params.orderBy || '_count'] = dir; - return; - } + if (agg.params.missingBucket && agg.params.field.type === 'string') { + output.params.missing = '__missing__'; + } - if (orderAgg.type.name === 'count') { - order._count = dir; - return; - } + if (!orderAgg) { + order[agg.params.orderBy || '_count'] = dir; + return; + } - const orderAggId = orderAgg.id; + if (orderAgg.type.name === 'count') { + order._count = dir; + return; + } - if (orderAgg.parentId && aggs) { - orderAgg = aggs.byId(orderAgg.parentId); - } + const orderAggId = orderAgg.id; - output.subAggs = (output.subAggs || []).concat(orderAgg); - order[orderAggId] = dir; - }, - }, - { - name: 'order', - type: 'optioned', - default: 'desc', - options: [ + if (orderAgg.parentId && aggs) { + orderAgg = aggs.byId(orderAgg.parentId); + } + + output.subAggs = (output.subAggs || []).concat(orderAgg); + order[orderAggId] = dir; + }, + }, { - text: i18n.translate('data.search.aggs.buckets.terms.orderDescendingTitle', { - defaultMessage: 'Descending', + name: 'order', + type: 'optioned', + default: 'desc', + options: [ + { + text: i18n.translate('data.search.aggs.buckets.terms.orderDescendingTitle', { + defaultMessage: 'Descending', + }), + value: 'desc', + }, + { + text: i18n.translate('data.search.aggs.buckets.terms.orderAscendingTitle', { + defaultMessage: 'Ascending', + }), + value: 'asc', + }, + ], + write: noop, // prevent default write, it's handled by orderAgg + }, + { + name: 'size', + default: 5, + }, + { + name: 'otherBucket', + default: false, + write: noop, + }, + { + name: 'otherBucketLabel', + type: 'string', + default: i18n.translate('data.search.aggs.buckets.terms.otherBucketLabel', { + defaultMessage: 'Other', + }), + displayName: i18n.translate('data.search.aggs.otherBucket.labelForOtherBucketLabel', { + defaultMessage: 'Label for other bucket', }), - value: 'desc', + shouldShow: agg => agg.getParam('otherBucket'), + write: noop, }, { - text: i18n.translate('data.search.aggs.buckets.terms.orderAscendingTitle', { - defaultMessage: 'Ascending', + name: 'missingBucket', + default: false, + write: noop, + }, + { + name: 'missingBucketLabel', + default: i18n.translate('data.search.aggs.buckets.terms.missingBucketLabel', { + defaultMessage: 'Missing', + description: `Default label used in charts when documents are missing a field. + Visible when you create a chart with a terms aggregation and enable "Show missing values"`, + }), + type: 'string', + displayName: i18n.translate('data.search.aggs.otherBucket.labelForMissingValuesLabel', { + defaultMessage: 'Label for missing values', }), - value: 'asc', + shouldShow: agg => agg.getParam('missingBucket'), + write: noop, + }, + { + name: 'exclude', + displayName: i18n.translate('data.search.aggs.buckets.terms.excludeLabel', { + defaultMessage: 'Exclude', + }), + type: 'string', + advanced: true, + shouldShow: isStringType, + ...migrateIncludeExcludeFormat, + }, + { + name: 'include', + displayName: i18n.translate('data.search.aggs.buckets.terms.includeLabel', { + defaultMessage: 'Include', + }), + type: 'string', + advanced: true, + shouldShow: isStringType, + ...migrateIncludeExcludeFormat, }, ], - write: noop, // prevent default write, it's handled by orderAgg - }, - { - name: 'size', - default: 5, - }, - { - name: 'otherBucket', - default: false, - write: noop, - }, - { - name: 'otherBucketLabel', - type: 'string', - default: i18n.translate('data.search.aggs.buckets.terms.otherBucketLabel', { - defaultMessage: 'Other', - }), - displayName: i18n.translate('data.search.aggs.otherBucket.labelForOtherBucketLabel', { - defaultMessage: 'Label for other bucket', - }), - shouldShow: agg => agg.getParam('otherBucket'), - write: noop, - }, - { - name: 'missingBucket', - default: false, - write: noop, }, - { - name: 'missingBucketLabel', - default: i18n.translate('data.search.aggs.buckets.terms.missingBucketLabel', { - defaultMessage: 'Missing', - description: `Default label used in charts when documents are missing a field. - Visible when you create a chart with a terms aggregation and enable "Show missing values"`, - }), - type: 'string', - displayName: i18n.translate('data.search.aggs.otherBucket.labelForMissingValuesLabel', { - defaultMessage: 'Label for missing values', - }), - shouldShow: agg => agg.getParam('missingBucket'), - write: noop, - }, - { - name: 'exclude', - displayName: i18n.translate('data.search.aggs.buckets.terms.excludeLabel', { - defaultMessage: 'Exclude', - }), - type: 'string', - advanced: true, - shouldShow: isStringType, - ...migrateIncludeExcludeFormat, - }, - { - name: 'include', - displayName: i18n.translate('data.search.aggs.buckets.terms.includeLabel', { - defaultMessage: 'Include', - }), - type: 'string', - advanced: true, - shouldShow: isStringType, - ...migrateIncludeExcludeFormat, - }, - ], -}); + { getInternalStartServices } + ); diff --git a/src/plugins/data/public/search/aggs/index.test.ts b/src/plugins/data/public/search/aggs/index.test.ts index 8c0e47763c295..419c3fdab1caf 100644 --- a/src/plugins/data/public/search/aggs/index.test.ts +++ b/src/plugins/data/public/search/aggs/index.test.ts @@ -20,16 +20,22 @@ import { coreMock } from '../../../../../../src/core/public/mocks'; import { getAggTypes } from './index'; -import { isBucketAggType } from './buckets/_bucket_agg_type'; +import { isBucketAggType } from './buckets/bucket_agg_type'; import { isMetricAggType } from './metrics/metric_agg_type'; import { QueryStart } from '../../query'; +import { FieldFormatsStart } from '../../field_formats'; describe('AggTypesComponent', () => { - const core = coreMock.createSetup(); + const coreSetup = coreMock.createSetup(); + const coreStart = coreMock.createSetup(); + const aggTypes = getAggTypes({ - uiSettings: core.uiSettings, - notifications: core.notifications, + uiSettings: coreSetup.uiSettings, query: {} as QueryStart, + getInternalStartServices: () => ({ + notifications: coreStart.notifications, + fieldFormats: {} as FieldFormatsStart, + }), }); const { buckets, metrics } = aggTypes; diff --git a/src/plugins/data/public/search/aggs/metrics/avg.ts b/src/plugins/data/public/search/aggs/metrics/avg.ts index 008dede3e1985..d53ce8d3fc489 100644 --- a/src/plugins/data/public/search/aggs/metrics/avg.ts +++ b/src/plugins/data/public/search/aggs/metrics/avg.ts @@ -21,25 +21,37 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; const averageTitle = i18n.translate('data.search.aggs.metrics.averageTitle', { defaultMessage: 'Average', }); -export const avgMetricAgg = new MetricAggType({ - name: METRIC_TYPES.AVG, - title: averageTitle, - makeLabel: aggConfig => { - return i18n.translate('data.search.aggs.metrics.averageLabel', { - defaultMessage: 'Average {field}', - values: { field: aggConfig.getFieldDisplayName() }, - }); - }, - params: [ +export interface AvgMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + +export const getAvgMetricAgg = ({ getInternalStartServices }: AvgMetricAggDependencies) => { + return new MetricAggType( { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.NUMBER, + name: METRIC_TYPES.AVG, + title: averageTitle, + makeLabel: aggConfig => { + return i18n.translate('data.search.aggs.metrics.averageLabel', { + defaultMessage: 'Average {field}', + values: { field: aggConfig.getFieldDisplayName() }, + }); + }, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.NUMBER, + }, + ], }, - ], -}); + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_avg.ts b/src/plugins/data/public/search/aggs/metrics/bucket_avg.ts index 11bb559274729..2c32ebc671539 100644 --- a/src/plugins/data/public/search/aggs/metrics/bucket_avg.ts +++ b/src/plugins/data/public/search/aggs/metrics/bucket_avg.ts @@ -23,6 +23,11 @@ import { MetricAggType } from './metric_agg_type'; import { makeNestedLabel } from './lib/make_nested_label'; import { siblingPipelineAggHelper } from './lib/sibling_pipeline_agg_helper'; import { METRIC_TYPES } from './metric_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface BucketAvgMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const overallAverageLabel = i18n.translate('data.search.aggs.metrics.overallAverageLabel', { defaultMessage: 'overall average', @@ -32,25 +37,34 @@ const averageBucketTitle = i18n.translate('data.search.aggs.metrics.averageBucke defaultMessage: 'Average Bucket', }); -export const bucketAvgMetricAgg = new MetricAggType({ - name: METRIC_TYPES.AVG_BUCKET, - title: averageBucketTitle, - makeLabel: agg => makeNestedLabel(agg, overallAverageLabel), - subtype: siblingPipelineAggHelper.subtype, - params: [...siblingPipelineAggHelper.params()], - getFormat: siblingPipelineAggHelper.getFormat, - getValue(agg, bucket) { - const customMetric = agg.getParam('customMetric'); - const customBucket = agg.getParam('customBucket'); - const scaleMetrics = customMetric.type && customMetric.type.isScalable(); +export const getBucketAvgMetricAgg = ({ + getInternalStartServices, +}: BucketAvgMetricAggDependencies) => { + return new MetricAggType( + { + name: METRIC_TYPES.AVG_BUCKET, + title: averageBucketTitle, + makeLabel: agg => makeNestedLabel(agg, overallAverageLabel), + subtype: siblingPipelineAggHelper.subtype, + params: [...siblingPipelineAggHelper.params()], + getFormat: siblingPipelineAggHelper.getFormat, + getValue(agg, bucket) { + const customMetric = agg.getParam('customMetric'); + const customBucket = agg.getParam('customBucket'); + const scaleMetrics = customMetric.type && customMetric.type.isScalable(); - let value = bucket[agg.id] && bucket[agg.id].value; + let value = bucket[agg.id] && bucket[agg.id].value; - if (scaleMetrics && customBucket.type.name === 'date_histogram') { - const aggInfo = customBucket.write(); + if (scaleMetrics && customBucket.type.name === 'date_histogram') { + const aggInfo = customBucket.write(); - value *= get(aggInfo, 'bucketInterval.scale', 1); + value *= get(aggInfo, 'bucketInterval.scale', 1); + } + return value; + }, + }, + { + getInternalStartServices, } - return value; - }, -}); + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_max.ts b/src/plugins/data/public/search/aggs/metrics/bucket_max.ts index 0668a9bcf57a8..1e57a2dd8e38e 100644 --- a/src/plugins/data/public/search/aggs/metrics/bucket_max.ts +++ b/src/plugins/data/public/search/aggs/metrics/bucket_max.ts @@ -22,6 +22,11 @@ import { MetricAggType } from './metric_agg_type'; import { makeNestedLabel } from './lib/make_nested_label'; import { siblingPipelineAggHelper } from './lib/sibling_pipeline_agg_helper'; import { METRIC_TYPES } from './metric_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface BucketMaxMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const overallMaxLabel = i18n.translate('data.search.aggs.metrics.overallMaxLabel', { defaultMessage: 'overall max', @@ -31,11 +36,20 @@ const maxBucketTitle = i18n.translate('data.search.aggs.metrics.maxBucketTitle', defaultMessage: 'Max Bucket', }); -export const bucketMaxMetricAgg = new MetricAggType({ - name: METRIC_TYPES.MAX_BUCKET, - title: maxBucketTitle, - makeLabel: agg => makeNestedLabel(agg, overallMaxLabel), - subtype: siblingPipelineAggHelper.subtype, - params: [...siblingPipelineAggHelper.params()], - getFormat: siblingPipelineAggHelper.getFormat, -}); +export const getBucketMaxMetricAgg = ({ + getInternalStartServices, +}: BucketMaxMetricAggDependencies) => { + return new MetricAggType( + { + name: METRIC_TYPES.MAX_BUCKET, + title: maxBucketTitle, + makeLabel: agg => makeNestedLabel(agg, overallMaxLabel), + subtype: siblingPipelineAggHelper.subtype, + params: [...siblingPipelineAggHelper.params()], + getFormat: siblingPipelineAggHelper.getFormat, + }, + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_min.ts b/src/plugins/data/public/search/aggs/metrics/bucket_min.ts index 8f728cb5e7e42..0484af23a7141 100644 --- a/src/plugins/data/public/search/aggs/metrics/bucket_min.ts +++ b/src/plugins/data/public/search/aggs/metrics/bucket_min.ts @@ -22,6 +22,11 @@ import { MetricAggType } from './metric_agg_type'; import { makeNestedLabel } from './lib/make_nested_label'; import { siblingPipelineAggHelper } from './lib/sibling_pipeline_agg_helper'; import { METRIC_TYPES } from './metric_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface BucketMinMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const overallMinLabel = i18n.translate('data.search.aggs.metrics.overallMinLabel', { defaultMessage: 'overall min', @@ -31,11 +36,20 @@ const minBucketTitle = i18n.translate('data.search.aggs.metrics.minBucketTitle', defaultMessage: 'Min Bucket', }); -export const bucketMinMetricAgg = new MetricAggType({ - name: METRIC_TYPES.MIN_BUCKET, - title: minBucketTitle, - makeLabel: agg => makeNestedLabel(agg, overallMinLabel), - subtype: siblingPipelineAggHelper.subtype, - params: [...siblingPipelineAggHelper.params()], - getFormat: siblingPipelineAggHelper.getFormat, -}); +export const getBucketMinMetricAgg = ({ + getInternalStartServices, +}: BucketMinMetricAggDependencies) => { + return new MetricAggType( + { + name: METRIC_TYPES.MIN_BUCKET, + title: minBucketTitle, + makeLabel: agg => makeNestedLabel(agg, overallMinLabel), + subtype: siblingPipelineAggHelper.subtype, + params: [...siblingPipelineAggHelper.params()], + getFormat: siblingPipelineAggHelper.getFormat, + }, + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_sum.ts b/src/plugins/data/public/search/aggs/metrics/bucket_sum.ts index 1f9392c5bec35..0a4d29a18a980 100644 --- a/src/plugins/data/public/search/aggs/metrics/bucket_sum.ts +++ b/src/plugins/data/public/search/aggs/metrics/bucket_sum.ts @@ -22,6 +22,11 @@ import { MetricAggType } from './metric_agg_type'; import { makeNestedLabel } from './lib/make_nested_label'; import { siblingPipelineAggHelper } from './lib/sibling_pipeline_agg_helper'; import { METRIC_TYPES } from './metric_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface BucketSumMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const overallSumLabel = i18n.translate('data.search.aggs.metrics.overallSumLabel', { defaultMessage: 'overall sum', @@ -31,11 +36,20 @@ const sumBucketTitle = i18n.translate('data.search.aggs.metrics.sumBucketTitle', defaultMessage: 'Sum Bucket', }); -export const bucketSumMetricAgg = new MetricAggType({ - name: METRIC_TYPES.SUM_BUCKET, - title: sumBucketTitle, - makeLabel: agg => makeNestedLabel(agg, overallSumLabel), - subtype: siblingPipelineAggHelper.subtype, - params: [...siblingPipelineAggHelper.params()], - getFormat: siblingPipelineAggHelper.getFormat, -}); +export const getBucketSumMetricAgg = ({ + getInternalStartServices, +}: BucketSumMetricAggDependencies) => { + return new MetricAggType( + { + name: METRIC_TYPES.SUM_BUCKET, + title: sumBucketTitle, + makeLabel: agg => makeNestedLabel(agg, overallSumLabel), + subtype: siblingPipelineAggHelper.subtype, + params: [...siblingPipelineAggHelper.params()], + getFormat: siblingPipelineAggHelper.getFormat, + }, + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/cardinality.ts b/src/plugins/data/public/search/aggs/metrics/cardinality.ts index 88cdf3175665e..10b6b5aff1abd 100644 --- a/src/plugins/data/public/search/aggs/metrics/cardinality.ts +++ b/src/plugins/data/public/search/aggs/metrics/cardinality.ts @@ -18,36 +18,48 @@ */ import { i18n } from '@kbn/i18n'; -import { MetricAggType } from './metric_agg_type'; +import { MetricAggType, IMetricAggConfig } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; -import { getFieldFormats } from '../../../../public/services'; +import { GetInternalStartServicesFn } from '../../../types'; const uniqueCountTitle = i18n.translate('data.search.aggs.metrics.uniqueCountTitle', { defaultMessage: 'Unique Count', }); -export const cardinalityMetricAgg = new MetricAggType({ - name: METRIC_TYPES.CARDINALITY, - title: uniqueCountTitle, - makeLabel(aggConfig) { - return i18n.translate('data.search.aggs.metrics.uniqueCountLabel', { - defaultMessage: 'Unique count of {field}', - values: { field: aggConfig.getFieldDisplayName() }, - }); - }, - getFormat() { - const fieldFormatsService = getFieldFormats(); +export interface CardinalityMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} - return fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); - }, - params: [ +export const getCardinalityMetricAgg = ({ + getInternalStartServices, +}: CardinalityMetricAggDependencies) => + new MetricAggType( { - name: 'field', - type: 'field', - filterFieldTypes: Object.values(KBN_FIELD_TYPES).filter( - type => type !== KBN_FIELD_TYPES.HISTOGRAM - ), + name: METRIC_TYPES.CARDINALITY, + title: uniqueCountTitle, + makeLabel(aggConfig: IMetricAggConfig) { + return i18n.translate('data.search.aggs.metrics.uniqueCountLabel', { + defaultMessage: 'Unique count of {field}', + values: { field: aggConfig.getFieldDisplayName() }, + }); + }, + getFormat() { + const { fieldFormats } = getInternalStartServices(); + + return fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); + }, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: Object.values(KBN_FIELD_TYPES).filter( + type => type !== KBN_FIELD_TYPES.HISTOGRAM + ), + }, + ], }, - ], -}); + { + getInternalStartServices, + } + ); diff --git a/src/plugins/data/public/search/aggs/metrics/count.ts b/src/plugins/data/public/search/aggs/metrics/count.ts index 3ec1e18d66ab9..bd0b83798c7db 100644 --- a/src/plugins/data/public/search/aggs/metrics/count.ts +++ b/src/plugins/data/public/search/aggs/metrics/count.ts @@ -21,28 +21,38 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; -import { getFieldFormats } from '../../../../public/services'; +import { GetInternalStartServicesFn } from '../../../types'; -export const countMetricAgg = new MetricAggType({ - name: METRIC_TYPES.COUNT, - title: i18n.translate('data.search.aggs.metrics.countTitle', { - defaultMessage: 'Count', - }), - hasNoDsl: true, - makeLabel() { - return i18n.translate('data.search.aggs.metrics.countLabel', { - defaultMessage: 'Count', - }); - }, - getFormat() { - const fieldFormatsService = getFieldFormats(); +export interface CountMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} - return fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); - }, - getValue(agg, bucket) { - return bucket.doc_count; - }, - isScalable() { - return true; - }, -}); +export const getCountMetricAgg = ({ getInternalStartServices }: CountMetricAggDependencies) => + new MetricAggType( + { + name: METRIC_TYPES.COUNT, + title: i18n.translate('data.search.aggs.metrics.countTitle', { + defaultMessage: 'Count', + }), + hasNoDsl: true, + makeLabel() { + return i18n.translate('data.search.aggs.metrics.countLabel', { + defaultMessage: 'Count', + }); + }, + getFormat() { + const { fieldFormats } = getInternalStartServices(); + + return fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); + }, + getValue(agg, bucket) { + return bucket.doc_count; + }, + isScalable() { + return true; + }, + }, + { + getInternalStartServices, + } + ); diff --git a/src/plugins/data/public/search/aggs/metrics/cumulative_sum.ts b/src/plugins/data/public/search/aggs/metrics/cumulative_sum.ts index a5d02459900bb..8ca922e144a1f 100644 --- a/src/plugins/data/public/search/aggs/metrics/cumulative_sum.ts +++ b/src/plugins/data/public/search/aggs/metrics/cumulative_sum.ts @@ -22,6 +22,11 @@ import { MetricAggType } from './metric_agg_type'; import { parentPipelineAggHelper } from './lib/parent_pipeline_agg_helper'; import { makeNestedLabel } from './lib/make_nested_label'; import { METRIC_TYPES } from './metric_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface CumulativeSumMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const cumulativeSumLabel = i18n.translate('data.search.aggs.metrics.cumulativeSumLabel', { defaultMessage: 'cumulative sum', @@ -31,11 +36,20 @@ const cumulativeSumTitle = i18n.translate('data.search.aggs.metrics.cumulativeSu defaultMessage: 'Cumulative Sum', }); -export const cumulativeSumMetricAgg = new MetricAggType({ - name: METRIC_TYPES.CUMULATIVE_SUM, - title: cumulativeSumTitle, - subtype: parentPipelineAggHelper.subtype, - makeLabel: agg => makeNestedLabel(agg, cumulativeSumLabel), - params: [...parentPipelineAggHelper.params()], - getFormat: parentPipelineAggHelper.getFormat, -}); +export const getCumulativeSumMetricAgg = ({ + getInternalStartServices, +}: CumulativeSumMetricAggDependencies) => { + return new MetricAggType( + { + name: METRIC_TYPES.CUMULATIVE_SUM, + title: cumulativeSumTitle, + subtype: parentPipelineAggHelper.subtype, + makeLabel: agg => makeNestedLabel(agg, cumulativeSumLabel), + params: [...parentPipelineAggHelper.params()], + getFormat: parentPipelineAggHelper.getFormat, + }, + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/derivative.ts b/src/plugins/data/public/search/aggs/metrics/derivative.ts index 1169a527b0668..5752a72c846aa 100644 --- a/src/plugins/data/public/search/aggs/metrics/derivative.ts +++ b/src/plugins/data/public/search/aggs/metrics/derivative.ts @@ -22,6 +22,11 @@ import { MetricAggType } from './metric_agg_type'; import { parentPipelineAggHelper } from './lib/parent_pipeline_agg_helper'; import { makeNestedLabel } from './lib/make_nested_label'; import { METRIC_TYPES } from './metric_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface DerivativeMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const derivativeLabel = i18n.translate('data.search.aggs.metrics.derivativeLabel', { defaultMessage: 'derivative', @@ -31,13 +36,22 @@ const derivativeTitle = i18n.translate('data.search.aggs.metrics.derivativeTitle defaultMessage: 'Derivative', }); -export const derivativeMetricAgg = new MetricAggType({ - name: METRIC_TYPES.DERIVATIVE, - title: derivativeTitle, - subtype: parentPipelineAggHelper.subtype, - makeLabel(agg) { - return makeNestedLabel(agg, derivativeLabel); - }, - params: [...parentPipelineAggHelper.params()], - getFormat: parentPipelineAggHelper.getFormat, -}); +export const getDerivativeMetricAgg = ({ + getInternalStartServices, +}: DerivativeMetricAggDependencies) => { + return new MetricAggType( + { + name: METRIC_TYPES.DERIVATIVE, + title: derivativeTitle, + subtype: parentPipelineAggHelper.subtype, + makeLabel(agg) { + return makeNestedLabel(agg, derivativeLabel); + }, + params: [...parentPipelineAggHelper.params()], + getFormat: parentPipelineAggHelper.getFormat, + }, + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/geo_bounds.ts b/src/plugins/data/public/search/aggs/metrics/geo_bounds.ts index 8a9f66f4b22a8..00927ebba56bf 100644 --- a/src/plugins/data/public/search/aggs/metrics/geo_bounds.ts +++ b/src/plugins/data/public/search/aggs/metrics/geo_bounds.ts @@ -21,6 +21,11 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface GeoBoundsMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const geoBoundsTitle = i18n.translate('data.search.aggs.metrics.geoBoundsTitle', { defaultMessage: 'Geo Bounds', @@ -30,15 +35,24 @@ const geoBoundsLabel = i18n.translate('data.search.aggs.metrics.geoBoundsLabel', defaultMessage: 'Geo Bounds', }); -export const geoBoundsMetricAgg = new MetricAggType({ - name: METRIC_TYPES.GEO_BOUNDS, - title: geoBoundsTitle, - makeLabel: () => geoBoundsLabel, - params: [ +export const getGeoBoundsMetricAgg = ({ + getInternalStartServices, +}: GeoBoundsMetricAggDependencies) => { + return new MetricAggType( { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.GEO_POINT, + name: METRIC_TYPES.GEO_BOUNDS, + title: geoBoundsTitle, + makeLabel: () => geoBoundsLabel, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.GEO_POINT, + }, + ], }, - ], -}); + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/geo_centroid.ts b/src/plugins/data/public/search/aggs/metrics/geo_centroid.ts index a4e4413843bdd..a4b084f794a5d 100644 --- a/src/plugins/data/public/search/aggs/metrics/geo_centroid.ts +++ b/src/plugins/data/public/search/aggs/metrics/geo_centroid.ts @@ -21,6 +21,11 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface GeoCentroidMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const geoCentroidTitle = i18n.translate('data.search.aggs.metrics.geoCentroidTitle', { defaultMessage: 'Geo Centroid', @@ -30,18 +35,27 @@ const geoCentroidLabel = i18n.translate('data.search.aggs.metrics.geoCentroidLab defaultMessage: 'Geo Centroid', }); -export const geoCentroidMetricAgg = new MetricAggType({ - name: METRIC_TYPES.GEO_CENTROID, - title: geoCentroidTitle, - makeLabel: () => geoCentroidLabel, - params: [ +export const getGeoCentroidMetricAgg = ({ + getInternalStartServices, +}: GeoCentroidMetricAggDependencies) => { + return new MetricAggType( { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.GEO_POINT, + name: METRIC_TYPES.GEO_CENTROID, + title: geoCentroidTitle, + makeLabel: () => geoCentroidLabel, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.GEO_POINT, + }, + ], + getValue(agg, bucket) { + return bucket[agg.id] && bucket[agg.id].location; + }, }, - ], - getValue(agg, bucket) { - return bucket[agg.id] && bucket[agg.id].location; - }, -}); + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/max.ts b/src/plugins/data/public/search/aggs/metrics/max.ts index 0cfb7be699a95..88e8b485cb73f 100644 --- a/src/plugins/data/public/search/aggs/metrics/max.ts +++ b/src/plugins/data/public/search/aggs/metrics/max.ts @@ -21,25 +21,37 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; const maxTitle = i18n.translate('data.search.aggs.metrics.maxTitle', { defaultMessage: 'Max', }); -export const maxMetricAgg = new MetricAggType({ - name: METRIC_TYPES.MAX, - title: maxTitle, - makeLabel(aggConfig) { - return i18n.translate('data.search.aggs.metrics.maxLabel', { - defaultMessage: 'Max {field}', - values: { field: aggConfig.getFieldDisplayName() }, - }); - }, - params: [ +export interface MaxMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + +export const getMaxMetricAgg = ({ getInternalStartServices }: MaxMetricAggDependencies) => { + return new MetricAggType( { - name: 'field', - type: 'field', - filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.DATE], + name: METRIC_TYPES.MAX, + title: maxTitle, + makeLabel(aggConfig) { + return i18n.translate('data.search.aggs.metrics.maxLabel', { + defaultMessage: 'Max {field}', + values: { field: aggConfig.getFieldDisplayName() }, + }); + }, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.DATE], + }, + ], }, - ], -}); + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/median.test.ts b/src/plugins/data/public/search/aggs/metrics/median.test.ts index ad55837ec9a30..f80c46026f50a 100644 --- a/src/plugins/data/public/search/aggs/metrics/median.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/median.test.ts @@ -17,16 +17,24 @@ * under the License. */ -import { medianMetricAgg } from './median'; +import { getMedianMetricAgg, MedianMetricAggDependencies } from './median'; import { AggConfigs, IAggConfigs } from '../agg_configs'; import { mockAggTypesRegistry } from '../test_helpers'; import { METRIC_TYPES } from './metric_agg_types'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; describe('AggTypeMetricMedianProvider class', () => { let aggConfigs: IAggConfigs; + const aggTypesDependencies: MedianMetricAggDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; beforeEach(() => { - const typesRegistry = mockAggTypesRegistry([medianMetricAgg]); + const typesRegistry = mockAggTypesRegistry([getMedianMetricAgg(aggTypesDependencies)]); const field = { name: 'bytes', }; diff --git a/src/plugins/data/public/search/aggs/metrics/median.ts b/src/plugins/data/public/search/aggs/metrics/median.ts index faa0694cd5312..a398f017602b0 100644 --- a/src/plugins/data/public/search/aggs/metrics/median.ts +++ b/src/plugins/data/public/search/aggs/metrics/median.ts @@ -21,33 +21,49 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; const medianTitle = i18n.translate('data.search.aggs.metrics.medianTitle', { defaultMessage: 'Median', }); -export const medianMetricAgg = new MetricAggType({ - name: METRIC_TYPES.MEDIAN, - dslName: 'percentiles', - title: medianTitle, - makeLabel(aggConfig) { - return i18n.translate('data.search.aggs.metrics.medianLabel', { - defaultMessage: 'Median {field}', - values: { field: aggConfig.getFieldDisplayName() }, - }); - }, - params: [ +export interface MedianMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + +export const getMedianMetricAgg = ({ getInternalStartServices }: MedianMetricAggDependencies) => { + return new MetricAggType( { - name: 'field', - type: 'field', - filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.DATE, KBN_FIELD_TYPES.HISTOGRAM], - write(agg, output) { - output.params.field = agg.getParam('field').name; - output.params.percents = [50]; + name: METRIC_TYPES.MEDIAN, + dslName: 'percentiles', + title: medianTitle, + makeLabel(aggConfig) { + return i18n.translate('data.search.aggs.metrics.medianLabel', { + defaultMessage: 'Median {field}', + values: { field: aggConfig.getFieldDisplayName() }, + }); + }, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: [ + KBN_FIELD_TYPES.NUMBER, + KBN_FIELD_TYPES.DATE, + KBN_FIELD_TYPES.HISTOGRAM, + ], + write(agg, output) { + output.params.field = agg.getParam('field').name; + output.params.percents = [50]; + }, + }, + ], + getValue(agg, bucket) { + return bucket[agg.id].values['50.0']; }, }, - ], - getValue(agg, bucket) { - return bucket[agg.id].values['50.0']; - }, -}); + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/metric_agg_type.ts b/src/plugins/data/public/search/aggs/metrics/metric_agg_type.ts index 05c4cb3de4bdf..bb16cba1bee62 100644 --- a/src/plugins/data/public/search/aggs/metrics/metric_agg_type.ts +++ b/src/plugins/data/public/search/aggs/metrics/metric_agg_type.ts @@ -23,8 +23,8 @@ import { AggParamType } from '../param_types/agg'; import { AggConfig } from '../agg_config'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; -import { getFieldFormats } from '../../../../public/services'; import { FieldTypes } from '../param_types'; +import { GetInternalStartServicesFn } from '../../../types'; export interface IMetricAggConfig extends AggConfig { type: InstanceType; @@ -44,6 +44,10 @@ interface MetricAggTypeConfig subtype?: string; } +interface MetricAggTypeDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + // TODO need to make a more explicit interface for this export type IMetricAggType = MetricAggType; @@ -57,8 +61,11 @@ export class MetricAggType {}; - constructor(config: MetricAggTypeConfig) { - super(config); + constructor( + config: MetricAggTypeConfig, + dependencies: MetricAggTypeDependencies + ) { + super(config, dependencies); this.getValue = config.getValue || @@ -78,11 +85,9 @@ export class MetricAggType { - const fieldFormatsService = getFieldFormats(); + const { fieldFormats } = dependencies.getInternalStartServices(); const field = agg.getField(); - return field - ? field.format - : fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); + return field ? field.format : fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); }); this.subtype = diff --git a/src/plugins/data/public/search/aggs/metrics/min.ts b/src/plugins/data/public/search/aggs/metrics/min.ts index 0a9abf1edcd04..aae16f357186c 100644 --- a/src/plugins/data/public/search/aggs/metrics/min.ts +++ b/src/plugins/data/public/search/aggs/metrics/min.ts @@ -21,25 +21,37 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; const minTitle = i18n.translate('data.search.aggs.metrics.minTitle', { defaultMessage: 'Min', }); -export const minMetricAgg = new MetricAggType({ - name: METRIC_TYPES.MIN, - title: minTitle, - makeLabel(aggConfig) { - return i18n.translate('data.search.aggs.metrics.minLabel', { - defaultMessage: 'Min {field}', - values: { field: aggConfig.getFieldDisplayName() }, - }); - }, - params: [ +export interface MinMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + +export const getMinMetricAgg = ({ getInternalStartServices }: MinMetricAggDependencies) => { + return new MetricAggType( { - name: 'field', - type: 'field', - filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.DATE], + name: METRIC_TYPES.MIN, + title: minTitle, + makeLabel(aggConfig) { + return i18n.translate('data.search.aggs.metrics.minLabel', { + defaultMessage: 'Min {field}', + values: { field: aggConfig.getFieldDisplayName() }, + }); + }, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.DATE], + }, + ], }, - ], -}); + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/moving_avg.ts b/src/plugins/data/public/search/aggs/metrics/moving_avg.ts index cb733507858bc..94b9b1d8cd487 100644 --- a/src/plugins/data/public/search/aggs/metrics/moving_avg.ts +++ b/src/plugins/data/public/search/aggs/metrics/moving_avg.ts @@ -22,6 +22,11 @@ import { MetricAggType } from './metric_agg_type'; import { parentPipelineAggHelper } from './lib/parent_pipeline_agg_helper'; import { makeNestedLabel } from './lib/make_nested_label'; import { METRIC_TYPES } from './metric_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface MovingAvgMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const movingAvgTitle = i18n.translate('data.search.aggs.metrics.movingAvgTitle', { defaultMessage: 'Moving Avg', @@ -31,34 +36,43 @@ const movingAvgLabel = i18n.translate('data.search.aggs.metrics.movingAvgLabel', defaultMessage: 'moving avg', }); -export const movingAvgMetricAgg = new MetricAggType({ - name: METRIC_TYPES.MOVING_FN, - dslName: 'moving_fn', - title: movingAvgTitle, - subtype: parentPipelineAggHelper.subtype, - makeLabel: agg => makeNestedLabel(agg, movingAvgLabel), - params: [ - ...parentPipelineAggHelper.params(), +export const getMovingAvgMetricAgg = ({ + getInternalStartServices, +}: MovingAvgMetricAggDependencies) => { + return new MetricAggType( { - name: 'window', - default: 5, + name: METRIC_TYPES.MOVING_FN, + dslName: 'moving_fn', + title: movingAvgTitle, + subtype: parentPipelineAggHelper.subtype, + makeLabel: agg => makeNestedLabel(agg, movingAvgLabel), + params: [ + ...parentPipelineAggHelper.params(), + { + name: 'window', + default: 5, + }, + { + name: 'script', + default: 'MovingFunctions.unweightedAvg(values)', + }, + ], + getValue(agg, bucket) { + /** + * The previous implementation using `moving_avg` did not + * return any bucket in case there are no documents or empty window. + * The `moving_fn` aggregation returns buckets with the value null if the + * window is empty or doesn't return any value if the sibiling metric + * is null. Since our generic MetricAggType.getValue implementation + * would return the value 0 for null buckets, we need a specific + * implementation here, that preserves the null value. + */ + return bucket[agg.id] ? bucket[agg.id].value : null; + }, + getFormat: parentPipelineAggHelper.getFormat, }, { - name: 'script', - default: 'MovingFunctions.unweightedAvg(values)', - }, - ], - getValue(agg, bucket) { - /** - * The previous implementation using `moving_avg` did not - * return any bucket in case there are no documents or empty window. - * The `moving_fn` aggregation returns buckets with the value null if the - * window is empty or doesn't return any value if the sibiling metric - * is null. Since our generic MetricAggType.getValue implementation - * would return the value 0 for null buckets, we need a specific - * implementation here, that preserves the null value. - */ - return bucket[agg.id] ? bucket[agg.id].value : null; - }, - getFormat: parentPipelineAggHelper.getFormat, -}); + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts b/src/plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts index 02e63f653f94f..af983a50f6c23 100644 --- a/src/plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts @@ -17,26 +17,47 @@ * under the License. */ -import { derivativeMetricAgg } from './derivative'; -import { cumulativeSumMetricAgg } from './cumulative_sum'; -import { movingAvgMetricAgg } from './moving_avg'; -import { serialDiffMetricAgg } from './serial_diff'; +import { getDerivativeMetricAgg } from './derivative'; +import { getCumulativeSumMetricAgg } from './cumulative_sum'; +import { getMovingAvgMetricAgg } from './moving_avg'; +import { getSerialDiffMetricAgg } from './serial_diff'; import { AggConfigs } from '../agg_configs'; -import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; +import { mockAggTypesRegistry } from '../test_helpers'; import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { GetInternalStartServicesFn } from '../../../types'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; describe('parent pipeline aggs', function() { - beforeEach(() => { - mockDataServices(); + const getInternalStartServices: GetInternalStartServicesFn = () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), }); const typesRegistry = mockAggTypesRegistry(); const metrics = [ - { name: 'derivative', title: 'Derivative', provider: derivativeMetricAgg }, - { name: 'cumulative_sum', title: 'Cumulative Sum', provider: cumulativeSumMetricAgg }, - { name: 'moving_avg', title: 'Moving Avg', provider: movingAvgMetricAgg, dslName: 'moving_fn' }, - { name: 'serial_diff', title: 'Serial Diff', provider: serialDiffMetricAgg }, + { + name: 'derivative', + title: 'Derivative', + provider: getDerivativeMetricAgg({ getInternalStartServices }), + }, + { + name: 'cumulative_sum', + title: 'Cumulative Sum', + provider: getCumulativeSumMetricAgg({ getInternalStartServices }), + }, + { + name: 'moving_avg', + title: 'Moving Avg', + provider: getMovingAvgMetricAgg({ getInternalStartServices }), + dslName: 'moving_fn', + }, + { + name: 'serial_diff', + title: 'Serial Diff', + provider: getSerialDiffMetricAgg({ getInternalStartServices }), + }, ]; metrics.forEach(metric => { diff --git a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts b/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts index 628f1cd204ee5..2944fc8c11b23 100644 --- a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts @@ -17,18 +17,28 @@ * under the License. */ -import { IPercentileRanksAggConfig, percentileRanksMetricAgg } from './percentile_ranks'; +import { + IPercentileRanksAggConfig, + getPercentileRanksMetricAgg, + PercentileRanksMetricAggDependencies, +} from './percentile_ranks'; import { AggConfigs, IAggConfigs } from '../agg_configs'; -import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; +import { mockAggTypesRegistry } from '../test_helpers'; import { METRIC_TYPES } from './metric_agg_types'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; describe('AggTypesMetricsPercentileRanksProvider class', function() { let aggConfigs: IAggConfigs; + const aggTypesDependencies: PercentileRanksMetricAggDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; beforeEach(() => { - mockDataServices(); - - const typesRegistry = mockAggTypesRegistry([percentileRanksMetricAgg]); + const typesRegistry = mockAggTypesRegistry([getPercentileRanksMetricAgg(aggTypesDependencies)]); const field = { name: 'bytes', }; @@ -65,7 +75,7 @@ describe('AggTypesMetricsPercentileRanksProvider class', function() { }); it('uses the custom label if it is set', function() { - const responseAggs: any = percentileRanksMetricAgg.getResponseAggs( + const responseAggs: any = getPercentileRanksMetricAgg(aggTypesDependencies).getResponseAggs( aggConfigs.aggs[0] as IPercentileRanksAggConfig ); diff --git a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.ts b/src/plugins/data/public/search/aggs/metrics/percentile_ranks.ts index 7dc0f70ea7b80..0d79665ff9c4e 100644 --- a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.ts +++ b/src/plugins/data/public/search/aggs/metrics/percentile_ranks.ts @@ -23,68 +23,86 @@ import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_respons import { getPercentileValue } from './percentiles_get_value'; import { METRIC_TYPES } from './metric_agg_types'; import { FIELD_FORMAT_IDS, KBN_FIELD_TYPES } from '../../../../common'; -import { getFieldFormats } from '../../../../public/services'; +import { GetInternalStartServicesFn } from '../../../types'; // required by the values editor export type IPercentileRanksAggConfig = IResponseAggConfig; -const valueProps = { - makeLabel(this: IPercentileRanksAggConfig) { - const fieldFormatsService = getFieldFormats(); - const field = this.getField(); - const format = - (field && field.format) || fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); - const customLabel = this.getParam('customLabel'); - const label = customLabel || this.getFieldDisplayName(); +export interface PercentileRanksMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} - return i18n.translate('data.search.aggs.metrics.percentileRanks.valuePropsLabel', { - defaultMessage: 'Percentile rank {format} of "{label}"', - values: { format: format.convert(this.key, 'text'), label }, - }); - }, -}; +const getValueProps = (getInternalStartServices: GetInternalStartServicesFn) => { + return { + makeLabel(this: IPercentileRanksAggConfig) { + const { fieldFormats } = getInternalStartServices(); + const field = this.getField(); + const format = + (field && field.format) || fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); + const customLabel = this.getParam('customLabel'); + const label = customLabel || this.getFieldDisplayName(); -export const percentileRanksMetricAgg = new MetricAggType({ - name: METRIC_TYPES.PERCENTILE_RANKS, - title: i18n.translate('data.search.aggs.metrics.percentileRanksTitle', { - defaultMessage: 'Percentile Ranks', - }), - makeLabel(agg) { - return i18n.translate('data.search.aggs.metrics.percentileRanksLabel', { - defaultMessage: 'Percentile ranks of {field}', - values: { field: agg.getFieldDisplayName() }, - }); - }, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.HISTOGRAM], - }, - { - name: 'values', - default: [], + return i18n.translate('data.search.aggs.metrics.percentileRanks.valuePropsLabel', { + defaultMessage: 'Percentile rank {format} of "{label}"', + values: { format: format.convert(this.key, 'text'), label }, + }); }, + }; +}; + +export const getPercentileRanksMetricAgg = ({ + getInternalStartServices, +}: PercentileRanksMetricAggDependencies) => { + return new MetricAggType( { - write(agg, output) { - output.params.keyed = false; + name: METRIC_TYPES.PERCENTILE_RANKS, + title: i18n.translate('data.search.aggs.metrics.percentileRanksTitle', { + defaultMessage: 'Percentile Ranks', + }), + makeLabel(agg) { + return i18n.translate('data.search.aggs.metrics.percentileRanksLabel', { + defaultMessage: 'Percentile ranks of {field}', + values: { field: agg.getFieldDisplayName() }, + }); }, - }, - ], - getResponseAggs(agg) { - const ValueAggConfig = getResponseAggConfigClass(agg, valueProps); - const values = agg.getParam('values'); + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.HISTOGRAM], + }, + { + name: 'values', + default: [], + }, + { + write(agg, output) { + output.params.keyed = false; + }, + }, + ], + getResponseAggs(agg) { + const ValueAggConfig = getResponseAggConfigClass( + agg, + getValueProps(getInternalStartServices) + ); + const values = agg.getParam('values'); - return values.map((value: any) => new ValueAggConfig(value)); - }, - getFormat() { - const fieldFormatsService = getFieldFormats(); - return ( - fieldFormatsService.getInstance(FIELD_FORMAT_IDS.PERCENT) || - fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.NUMBER) - ); - }, - getValue(agg, bucket) { - return getPercentileValue(agg, bucket) / 100; - }, -}); + return values.map((value: any) => new ValueAggConfig(value)); + }, + getFormat() { + const { fieldFormats } = getInternalStartServices(); + return ( + fieldFormats.getInstance(FIELD_FORMAT_IDS.PERCENT) || + fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.NUMBER) + ); + }, + getValue(agg, bucket) { + return getPercentileValue(agg, bucket) / 100; + }, + }, + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/percentiles.test.ts b/src/plugins/data/public/search/aggs/metrics/percentiles.test.ts index e077bc0f8c773..33bd42df74cc7 100644 --- a/src/plugins/data/public/search/aggs/metrics/percentiles.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/percentiles.test.ts @@ -17,16 +17,28 @@ * under the License. */ -import { IPercentileAggConfig, percentilesMetricAgg } from './percentiles'; +import { + IPercentileAggConfig, + getPercentilesMetricAgg, + PercentilesMetricAggDependencies, +} from './percentiles'; import { AggConfigs, IAggConfigs } from '../agg_configs'; import { mockAggTypesRegistry } from '../test_helpers'; import { METRIC_TYPES } from './metric_agg_types'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; describe('AggTypesMetricsPercentilesProvider class', () => { let aggConfigs: IAggConfigs; + const aggTypesDependencies: PercentilesMetricAggDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; beforeEach(() => { - const typesRegistry = mockAggTypesRegistry([percentilesMetricAgg]); + const typesRegistry = mockAggTypesRegistry([getPercentilesMetricAgg(aggTypesDependencies)]); const field = { name: 'bytes', }; @@ -63,7 +75,7 @@ describe('AggTypesMetricsPercentilesProvider class', () => { }); it('uses the custom label if it is set', () => { - const responseAggs: any = percentilesMetricAgg.getResponseAggs( + const responseAggs: any = getPercentilesMetricAgg(aggTypesDependencies).getResponseAggs( aggConfigs.aggs[0] as IPercentileAggConfig ); diff --git a/src/plugins/data/public/search/aggs/metrics/percentiles.ts b/src/plugins/data/public/search/aggs/metrics/percentiles.ts index a39d68248d608..040a52588dd94 100644 --- a/src/plugins/data/public/search/aggs/metrics/percentiles.ts +++ b/src/plugins/data/public/search/aggs/metrics/percentiles.ts @@ -24,9 +24,14 @@ import { KBN_FIELD_TYPES } from '../../../../common'; import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_response_agg_config_class'; import { getPercentileValue } from './percentiles_get_value'; import { ordinalSuffix } from './lib/ordinal_suffix'; +import { GetInternalStartServicesFn } from '../../../types'; export type IPercentileAggConfig = IResponseAggConfig; +export interface PercentilesMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + const valueProps = { makeLabel(this: IPercentileAggConfig) { const customLabel = this.getParam('customLabel'); @@ -39,38 +44,51 @@ const valueProps = { }, }; -export const percentilesMetricAgg = new MetricAggType({ - name: METRIC_TYPES.PERCENTILES, - title: i18n.translate('data.search.aggs.metrics.percentilesTitle', { - defaultMessage: 'Percentiles', - }), - makeLabel(agg) { - return i18n.translate('data.search.aggs.metrics.percentilesLabel', { - defaultMessage: 'Percentiles of {field}', - values: { field: agg.getFieldDisplayName() }, - }); - }, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.DATE, KBN_FIELD_TYPES.HISTOGRAM], - }, - { - name: 'percents', - default: [1, 5, 25, 50, 75, 95, 99], - }, +export const getPercentilesMetricAgg = ({ + getInternalStartServices, +}: PercentilesMetricAggDependencies) => { + return new MetricAggType( { - write(agg, output) { - output.params.keyed = false; + name: METRIC_TYPES.PERCENTILES, + title: i18n.translate('data.search.aggs.metrics.percentilesTitle', { + defaultMessage: 'Percentiles', + }), + makeLabel(agg) { + return i18n.translate('data.search.aggs.metrics.percentilesLabel', { + defaultMessage: 'Percentiles of {field}', + values: { field: agg.getFieldDisplayName() }, + }); }, - }, - ], - getResponseAggs(agg) { - const ValueAggConfig = getResponseAggConfigClass(agg, valueProps); + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: [ + KBN_FIELD_TYPES.NUMBER, + KBN_FIELD_TYPES.DATE, + KBN_FIELD_TYPES.HISTOGRAM, + ], + }, + { + name: 'percents', + default: [1, 5, 25, 50, 75, 95, 99], + }, + { + write(agg, output) { + output.params.keyed = false; + }, + }, + ], + getResponseAggs(agg) { + const ValueAggConfig = getResponseAggConfigClass(agg, valueProps); - return agg.getParam('percents').map((percent: any) => new ValueAggConfig(percent)); - }, + return agg.getParam('percents').map((percent: any) => new ValueAggConfig(percent)); + }, - getValue: getPercentileValue, -}); + getValue: getPercentileValue, + }, + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/serial_diff.ts b/src/plugins/data/public/search/aggs/metrics/serial_diff.ts index 5af6e1952d135..2b1498560f862 100644 --- a/src/plugins/data/public/search/aggs/metrics/serial_diff.ts +++ b/src/plugins/data/public/search/aggs/metrics/serial_diff.ts @@ -22,6 +22,11 @@ import { MetricAggType } from './metric_agg_type'; import { parentPipelineAggHelper } from './lib/parent_pipeline_agg_helper'; import { makeNestedLabel } from './lib/make_nested_label'; import { METRIC_TYPES } from './metric_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface SerialDiffMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const serialDiffTitle = i18n.translate('data.search.aggs.metrics.serialDiffTitle', { defaultMessage: 'Serial Diff', @@ -31,11 +36,20 @@ const serialDiffLabel = i18n.translate('data.search.aggs.metrics.serialDiffLabel defaultMessage: 'serial diff', }); -export const serialDiffMetricAgg = new MetricAggType({ - name: METRIC_TYPES.SERIAL_DIFF, - title: serialDiffTitle, - subtype: parentPipelineAggHelper.subtype, - makeLabel: agg => makeNestedLabel(agg, serialDiffLabel), - params: [...parentPipelineAggHelper.params()], - getFormat: parentPipelineAggHelper.getFormat, -}); +export const getSerialDiffMetricAgg = ({ + getInternalStartServices, +}: SerialDiffMetricAggDependencies) => { + return new MetricAggType( + { + name: METRIC_TYPES.SERIAL_DIFF, + title: serialDiffTitle, + subtype: parentPipelineAggHelper.subtype, + makeLabel: agg => makeNestedLabel(agg, serialDiffLabel), + params: [...parentPipelineAggHelper.params()], + getFormat: parentPipelineAggHelper.getFormat, + }, + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts b/src/plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts index 8389ed8262ce5..ab480fe44227e 100644 --- a/src/plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts @@ -17,27 +17,47 @@ * under the License. */ -import { bucketSumMetricAgg } from './bucket_sum'; -import { bucketAvgMetricAgg } from './bucket_avg'; -import { bucketMinMetricAgg } from './bucket_min'; -import { bucketMaxMetricAgg } from './bucket_max'; +import { getBucketSumMetricAgg } from './bucket_sum'; +import { getBucketAvgMetricAgg } from './bucket_avg'; +import { getBucketMinMetricAgg } from './bucket_min'; +import { getBucketMaxMetricAgg } from './bucket_max'; import { AggConfigs } from '../agg_configs'; import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; -import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; +import { mockAggTypesRegistry } from '../test_helpers'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { GetInternalStartServicesFn } from '../../../types'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; describe('sibling pipeline aggs', () => { - beforeEach(() => { - mockDataServices(); + const getInternalStartServices: GetInternalStartServicesFn = () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), }); const typesRegistry = mockAggTypesRegistry(); const metrics = [ - { name: 'sum_bucket', title: 'Overall Sum', provider: bucketSumMetricAgg }, - { name: 'avg_bucket', title: 'Overall Average', provider: bucketAvgMetricAgg }, - { name: 'min_bucket', title: 'Overall Min', provider: bucketMinMetricAgg }, - { name: 'max_bucket', title: 'Overall Max', provider: bucketMaxMetricAgg }, + { + name: 'sum_bucket', + title: 'Overall Sum', + provider: getBucketSumMetricAgg({ getInternalStartServices }), + }, + { + name: 'avg_bucket', + title: 'Overall Average', + provider: getBucketAvgMetricAgg({ getInternalStartServices }), + }, + { + name: 'min_bucket', + title: 'Overall Min', + provider: getBucketMinMetricAgg({ getInternalStartServices }), + }, + { + name: 'max_bucket', + title: 'Overall Max', + provider: getBucketMaxMetricAgg({ getInternalStartServices }), + }, ]; metrics.forEach(metric => { diff --git a/src/plugins/data/public/search/aggs/metrics/std_deviation.test.ts b/src/plugins/data/public/search/aggs/metrics/std_deviation.test.ts index 0679831b1e6ac..6bbff3009cc11 100644 --- a/src/plugins/data/public/search/aggs/metrics/std_deviation.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/std_deviation.test.ts @@ -17,13 +17,25 @@ * under the License. */ -import { IStdDevAggConfig, stdDeviationMetricAgg } from './std_deviation'; +import { + IStdDevAggConfig, + getStdDeviationMetricAgg, + StdDeviationMetricAggDependencies, +} from './std_deviation'; import { AggConfigs } from '../agg_configs'; import { mockAggTypesRegistry } from '../test_helpers'; import { METRIC_TYPES } from './metric_agg_types'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; describe('AggTypeMetricStandardDeviationProvider class', () => { - const typesRegistry = mockAggTypesRegistry([stdDeviationMetricAgg]); + const aggTypesDependencies: StdDeviationMetricAggDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; + const typesRegistry = mockAggTypesRegistry([getStdDeviationMetricAgg(aggTypesDependencies)]); const getAggConfigs = (customLabel?: string) => { const field = { name: 'memory', @@ -58,7 +70,7 @@ describe('AggTypeMetricStandardDeviationProvider class', () => { it('uses the custom label if it is set', () => { const aggConfigs = getAggConfigs('custom label'); - const responseAggs: any = stdDeviationMetricAgg.getResponseAggs( + const responseAggs: any = getStdDeviationMetricAgg(aggTypesDependencies).getResponseAggs( aggConfigs.aggs[0] as IStdDevAggConfig ); @@ -72,7 +84,7 @@ describe('AggTypeMetricStandardDeviationProvider class', () => { it('uses the default labels if custom label is not set', () => { const aggConfigs = getAggConfigs(); - const responseAggs: any = stdDeviationMetricAgg.getResponseAggs( + const responseAggs: any = getStdDeviationMetricAgg(aggTypesDependencies).getResponseAggs( aggConfigs.aggs[0] as IStdDevAggConfig ); diff --git a/src/plugins/data/public/search/aggs/metrics/std_deviation.ts b/src/plugins/data/public/search/aggs/metrics/std_deviation.ts index 5e069e317e052..e972132542ceb 100644 --- a/src/plugins/data/public/search/aggs/metrics/std_deviation.ts +++ b/src/plugins/data/public/search/aggs/metrics/std_deviation.ts @@ -23,6 +23,7 @@ import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_response_agg_config_class'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; interface ValProp { valProp: string[]; @@ -34,6 +35,10 @@ export interface IStdDevAggConfig extends IResponseAggConfig { valProp: () => ValProp; } +export interface StdDeviationMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + const responseAggConfigProps = { valProp(this: IStdDevAggConfig) { const customLabel = this.getParam('customLabel'); @@ -75,33 +80,42 @@ const responseAggConfigProps = { }, }; -export const stdDeviationMetricAgg = new MetricAggType({ - name: METRIC_TYPES.STD_DEV, - dslName: 'extended_stats', - title: i18n.translate('data.search.aggs.metrics.standardDeviationTitle', { - defaultMessage: 'Standard Deviation', - }), - makeLabel(agg) { - return i18n.translate('data.search.aggs.metrics.standardDeviationLabel', { - defaultMessage: 'Standard Deviation of {field}', - values: { field: agg.getFieldDisplayName() }, - }); - }, - params: [ +export const getStdDeviationMetricAgg = ({ + getInternalStartServices, +}: StdDeviationMetricAggDependencies) => { + return new MetricAggType( { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.NUMBER, - }, - ], + name: METRIC_TYPES.STD_DEV, + dslName: 'extended_stats', + title: i18n.translate('data.search.aggs.metrics.standardDeviationTitle', { + defaultMessage: 'Standard Deviation', + }), + makeLabel(agg) { + return i18n.translate('data.search.aggs.metrics.standardDeviationLabel', { + defaultMessage: 'Standard Deviation of {field}', + values: { field: agg.getFieldDisplayName() }, + }); + }, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.NUMBER, + }, + ], - getResponseAggs(agg) { - const ValueAggConfig = getResponseAggConfigClass(agg, responseAggConfigProps); + getResponseAggs(agg) { + const ValueAggConfig = getResponseAggConfigClass(agg, responseAggConfigProps); - return [new ValueAggConfig('std_lower'), new ValueAggConfig('std_upper')]; - }, + return [new ValueAggConfig('std_lower'), new ValueAggConfig('std_upper')]; + }, - getValue(agg, bucket) { - return get(bucket[agg.parentId], agg.valProp()); - }, -}); + getValue(agg, bucket) { + return get(bucket[agg.parentId], agg.valProp()); + }, + }, + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/sum.ts b/src/plugins/data/public/search/aggs/metrics/sum.ts index ffb117dda0839..545c6d6a4939e 100644 --- a/src/plugins/data/public/search/aggs/metrics/sum.ts +++ b/src/plugins/data/public/search/aggs/metrics/sum.ts @@ -21,28 +21,40 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; const sumTitle = i18n.translate('data.search.aggs.metrics.sumTitle', { defaultMessage: 'Sum', }); -export const sumMetricAgg = new MetricAggType({ - name: METRIC_TYPES.SUM, - title: sumTitle, - makeLabel(aggConfig) { - return i18n.translate('data.search.aggs.metrics.sumLabel', { - defaultMessage: 'Sum of {field}', - values: { field: aggConfig.getFieldDisplayName() }, - }); - }, - isScalable() { - return true; - }, - params: [ +export interface SumMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + +export const getSumMetricAgg = ({ getInternalStartServices }: SumMetricAggDependencies) => { + return new MetricAggType( { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.NUMBER, + name: METRIC_TYPES.SUM, + title: sumTitle, + makeLabel(aggConfig) { + return i18n.translate('data.search.aggs.metrics.sumLabel', { + defaultMessage: 'Sum of {field}', + values: { field: aggConfig.getFieldDisplayName() }, + }); + }, + isScalable() { + return true; + }, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.NUMBER, + }, + ], }, - ], -}); + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts b/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts index c65a714f26247..8294ad09bae22 100644 --- a/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts @@ -18,15 +18,23 @@ */ import { dropRight, last } from 'lodash'; -import { topHitMetricAgg } from './top_hit'; +import { getTopHitMetricAgg, TopHitMetricAggDependencies } from './top_hit'; import { AggConfigs } from '../agg_configs'; import { mockAggTypesRegistry } from '../test_helpers'; import { IMetricAggConfig } from './metric_agg_type'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; describe('Top hit metric', () => { let aggDsl: Record; let aggConfig: IMetricAggConfig; + const aggTypesDependencies: TopHitMetricAggDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; const init = ({ fieldName = 'field', @@ -36,7 +44,7 @@ describe('Top hit metric', () => { fieldType = KBN_FIELD_TYPES.NUMBER, size = 1, }: any) => { - const typesRegistry = mockAggTypesRegistry([topHitMetricAgg]); + const typesRegistry = mockAggTypesRegistry([getTopHitMetricAgg(aggTypesDependencies)]); const field = { name: fieldName, displayName: fieldName, @@ -91,7 +99,7 @@ describe('Top hit metric', () => { it('should return a label prefixed with Last if sorting in descending order', () => { init({ fieldName: 'bytes' }); - expect(topHitMetricAgg.makeLabel(aggConfig)).toEqual('Last bytes'); + expect(getTopHitMetricAgg(aggTypesDependencies).makeLabel(aggConfig)).toEqual('Last bytes'); }); it('should return a label prefixed with First if sorting in ascending order', () => { @@ -99,7 +107,7 @@ describe('Top hit metric', () => { fieldName: 'bytes', sortOrder: 'asc', }); - expect(topHitMetricAgg.makeLabel(aggConfig)).toEqual('First bytes'); + expect(getTopHitMetricAgg(aggTypesDependencies).makeLabel(aggConfig)).toEqual('First bytes'); }); it('should request the _source field', () => { @@ -140,7 +148,7 @@ describe('Top hit metric', () => { }; init({ fieldName: '@tags' }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).toBe(null); + expect(getTopHitMetricAgg(aggTypesDependencies).getValue(aggConfig, bucket)).toBe(null); }); // it('should return undefined if the field does not appear in the source', () => { @@ -159,7 +167,7 @@ describe('Top hit metric', () => { }; init({ fieldName: '@tags' }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).toBe(undefined); + expect(getTopHitMetricAgg(aggTypesDependencies).getValue(aggConfig, bucket)).toBe(undefined); }); it('should return the field value from the top hit', () => { @@ -178,7 +186,7 @@ describe('Top hit metric', () => { }; init({ fieldName: '@tags' }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).toBe('aaa'); + expect(getTopHitMetricAgg(aggTypesDependencies).getValue(aggConfig, bucket)).toBe('aaa'); }); it('should return the object if the field value is an object', () => { @@ -200,7 +208,9 @@ describe('Top hit metric', () => { init({ fieldName: '@tags' }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).toEqual({ label: 'aaa' }); + expect(getTopHitMetricAgg(aggTypesDependencies).getValue(aggConfig, bucket)).toEqual({ + label: 'aaa', + }); }); it('should return an array if the field has more than one values', () => { @@ -219,7 +229,10 @@ describe('Top hit metric', () => { }; init({ fieldName: '@tags' }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).toEqual(['aaa', 'bbb']); + expect(getTopHitMetricAgg(aggTypesDependencies).getValue(aggConfig, bucket)).toEqual([ + 'aaa', + 'bbb', + ]); }); it('should return undefined if the field is not in the source nor in the doc_values field', () => { @@ -241,7 +254,7 @@ describe('Top hit metric', () => { }; init({ fieldName: 'machine.os.raw', readFromDocValues: true }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).toBe(undefined); + expect(getTopHitMetricAgg(aggTypesDependencies).getValue(aggConfig, bucket)).toBe(undefined); }); describe('Multivalued field and first/last X docs', () => { @@ -250,7 +263,9 @@ describe('Top hit metric', () => { fieldName: 'bytes', size: 2, }); - expect(topHitMetricAgg.makeLabel(aggConfig)).toEqual('Last 2 bytes'); + expect(getTopHitMetricAgg(aggTypesDependencies).makeLabel(aggConfig)).toEqual( + 'Last 2 bytes' + ); }); it('should return a label prefixed with First X docs if sorting in ascending order', () => { @@ -259,7 +274,9 @@ describe('Top hit metric', () => { size: 2, sortOrder: 'asc', }); - expect(topHitMetricAgg.makeLabel(aggConfig)).toEqual('First 2 bytes'); + expect(getTopHitMetricAgg(aggTypesDependencies).makeLabel(aggConfig)).toEqual( + 'First 2 bytes' + ); }); [ @@ -334,7 +351,9 @@ describe('Top hit metric', () => { }; init({ fieldName: 'bytes', aggregate: agg.type }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).toEqual(agg.result); + expect(getTopHitMetricAgg(aggTypesDependencies).getValue(aggConfig, bucket)).toEqual( + agg.result + ); }); it(`should return the result of the ${agg.type} aggregation over the last X docs - ${agg.description}`, () => { @@ -358,7 +377,9 @@ describe('Top hit metric', () => { }; init({ fieldName: 'bytes', aggregate: agg.type }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).toEqual(agg.result); + expect(getTopHitMetricAgg(aggTypesDependencies).getValue(aggConfig, bucket)).toEqual( + agg.result + ); }); }); }); diff --git a/src/plugins/data/public/search/aggs/metrics/top_hit.ts b/src/plugins/data/public/search/aggs/metrics/top_hit.ts index d0c668c577e62..15da2b485aee7 100644 --- a/src/plugins/data/public/search/aggs/metrics/top_hit.ts +++ b/src/plugins/data/public/search/aggs/metrics/top_hit.ts @@ -22,6 +22,11 @@ import { i18n } from '@kbn/i18n'; import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface TopHitMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const isNumericFieldSelected = (agg: IMetricAggConfig) => { const field = agg.getParam('field'); @@ -29,214 +34,225 @@ const isNumericFieldSelected = (agg: IMetricAggConfig) => { return field && field.type && field.type === KBN_FIELD_TYPES.NUMBER; }; -export const topHitMetricAgg = new MetricAggType({ - name: METRIC_TYPES.TOP_HITS, - title: i18n.translate('data.search.aggs.metrics.topHitTitle', { - defaultMessage: 'Top Hit', - }), - makeLabel(aggConfig) { - const lastPrefixLabel = i18n.translate('data.search.aggs.metrics.topHit.lastPrefixLabel', { - defaultMessage: 'Last', - }); - const firstPrefixLabel = i18n.translate('data.search.aggs.metrics.topHit.firstPrefixLabel', { - defaultMessage: 'First', - }); - - let prefix = - aggConfig.getParam('sortOrder').value === 'desc' ? lastPrefixLabel : firstPrefixLabel; - - const size = aggConfig.getParam('size'); - - if (size !== 1) { - prefix += ` ${size}`; - } - - const field = aggConfig.getParam('field'); - - return `${prefix} ${field ? field.displayName : ''}`; - }, - params: [ +export const getTopHitMetricAgg = ({ getInternalStartServices }: TopHitMetricAggDependencies) => { + return new MetricAggType( { - name: 'field', - type: 'field', - onlyAggregatable: false, - filterFieldTypes: Object.values(KBN_FIELD_TYPES).filter( - type => type !== KBN_FIELD_TYPES.HISTOGRAM - ), - write(agg, output) { - const field = agg.getParam('field'); - output.params = {}; - - if (field.scripted) { - output.params.script_fields = { - [field.name]: { - script: { - source: field.script, - lang: field.lang, - }, - }, - }; - } else { - if (field.readFromDocValues) { - // always format date fields as date_time to avoid - // displaying unformatted dates like epoch_millis - // or other not-accepted momentjs formats - const format = field.type === KBN_FIELD_TYPES.DATE ? 'date_time' : 'use_field_mapping'; - output.params.docvalue_fields = [{ field: field.name, format }]; + name: METRIC_TYPES.TOP_HITS, + title: i18n.translate('data.search.aggs.metrics.topHitTitle', { + defaultMessage: 'Top Hit', + }), + makeLabel(aggConfig) { + const lastPrefixLabel = i18n.translate('data.search.aggs.metrics.topHit.lastPrefixLabel', { + defaultMessage: 'Last', + }); + const firstPrefixLabel = i18n.translate( + 'data.search.aggs.metrics.topHit.firstPrefixLabel', + { + defaultMessage: 'First', } - output.params._source = field.name === '_source' ? true : field.name; + ); + + let prefix = + aggConfig.getParam('sortOrder').value === 'desc' ? lastPrefixLabel : firstPrefixLabel; + + const size = aggConfig.getParam('size'); + + if (size !== 1) { + prefix += ` ${size}`; } + + const field = aggConfig.getParam('field'); + + return `${prefix} ${field ? field.displayName : ''}`; }, - }, - { - name: 'aggregate', - type: 'optioned', - options: [ + params: [ { - text: i18n.translate('data.search.aggs.metrics.topHit.minLabel', { - defaultMessage: 'Min', - }), - isCompatible: isNumericFieldSelected, - disabled: true, - value: 'min', - }, - { - text: i18n.translate('data.search.aggs.metrics.topHit.maxLabel', { - defaultMessage: 'Max', - }), - isCompatible: isNumericFieldSelected, - disabled: true, - value: 'max', + name: 'field', + type: 'field', + onlyAggregatable: false, + filterFieldTypes: Object.values(KBN_FIELD_TYPES).filter( + type => type !== KBN_FIELD_TYPES.HISTOGRAM + ), + write(agg, output) { + const field = agg.getParam('field'); + output.params = {}; + + if (field.scripted) { + output.params.script_fields = { + [field.name]: { + script: { + source: field.script, + lang: field.lang, + }, + }, + }; + } else { + if (field.readFromDocValues) { + // always format date fields as date_time to avoid + // displaying unformatted dates like epoch_millis + // or other not-accepted momentjs formats + const format = + field.type === KBN_FIELD_TYPES.DATE ? 'date_time' : 'use_field_mapping'; + output.params.docvalue_fields = [{ field: field.name, format }]; + } + output.params._source = field.name === '_source' ? true : field.name; + } + }, }, { - text: i18n.translate('data.search.aggs.metrics.topHit.sumLabel', { - defaultMessage: 'Sum', - }), - isCompatible: isNumericFieldSelected, - disabled: true, - value: 'sum', + name: 'aggregate', + type: 'optioned', + options: [ + { + text: i18n.translate('data.search.aggs.metrics.topHit.minLabel', { + defaultMessage: 'Min', + }), + isCompatible: isNumericFieldSelected, + disabled: true, + value: 'min', + }, + { + text: i18n.translate('data.search.aggs.metrics.topHit.maxLabel', { + defaultMessage: 'Max', + }), + isCompatible: isNumericFieldSelected, + disabled: true, + value: 'max', + }, + { + text: i18n.translate('data.search.aggs.metrics.topHit.sumLabel', { + defaultMessage: 'Sum', + }), + isCompatible: isNumericFieldSelected, + disabled: true, + value: 'sum', + }, + { + text: i18n.translate('data.search.aggs.metrics.topHit.averageLabel', { + defaultMessage: 'Average', + }), + isCompatible: isNumericFieldSelected, + disabled: true, + value: 'average', + }, + { + text: i18n.translate('data.search.aggs.metrics.topHit.concatenateLabel', { + defaultMessage: 'Concatenate', + }), + isCompatible(aggConfig: IMetricAggConfig) { + return _.get(aggConfig.params, 'field.filterFieldTypes', '*') === '*'; + }, + disabled: true, + value: 'concat', + }, + ], + write: _.noop, }, { - text: i18n.translate('data.search.aggs.metrics.topHit.averageLabel', { - defaultMessage: 'Average', - }), - isCompatible: isNumericFieldSelected, - disabled: true, - value: 'average', + name: 'size', + default: 1, }, { - text: i18n.translate('data.search.aggs.metrics.topHit.concatenateLabel', { - defaultMessage: 'Concatenate', - }), - isCompatible(aggConfig: IMetricAggConfig) { - return _.get(aggConfig.params, 'field.filterFieldTypes', '*') === '*'; + name: 'sortField', + type: 'field', + filterFieldTypes: [ + KBN_FIELD_TYPES.NUMBER, + KBN_FIELD_TYPES.DATE, + KBN_FIELD_TYPES.IP, + KBN_FIELD_TYPES.STRING, + ], + default(agg: IMetricAggConfig) { + return agg.getIndexPattern().timeFieldName; }, - disabled: true, - value: 'concat', - }, - ], - write: _.noop, - }, - { - name: 'size', - default: 1, - }, - { - name: 'sortField', - type: 'field', - filterFieldTypes: [ - KBN_FIELD_TYPES.NUMBER, - KBN_FIELD_TYPES.DATE, - KBN_FIELD_TYPES.IP, - KBN_FIELD_TYPES.STRING, - ], - default(agg: IMetricAggConfig) { - return agg.getIndexPattern().timeFieldName; - }, - write: _.noop, // prevent default write, it is handled below - }, - { - name: 'sortOrder', - type: 'optioned', - default: 'desc', - options: [ - { - text: i18n.translate('data.search.aggs.metrics.topHit.descendingLabel', { - defaultMessage: 'Descending', - }), - value: 'desc', + write: _.noop, // prevent default write, it is handled below }, { - text: i18n.translate('data.search.aggs.metrics.topHit.ascendingLabel', { - defaultMessage: 'Ascending', - }), - value: 'asc', - }, - ], - write(agg, output) { - const sortField = agg.params.sortField; - const sortOrder = agg.params.sortOrder; - - if (sortField.scripted) { - output.params.sort = [ + name: 'sortOrder', + type: 'optioned', + default: 'desc', + options: [ { - _script: { - script: { - source: sortField.script, - lang: sortField.lang, - }, - type: sortField.type, - order: sortOrder.value, - }, + text: i18n.translate('data.search.aggs.metrics.topHit.descendingLabel', { + defaultMessage: 'Descending', + }), + value: 'desc', }, - ]; - } else { - output.params.sort = [ { - [sortField.name]: { - order: sortOrder.value, - }, + text: i18n.translate('data.search.aggs.metrics.topHit.ascendingLabel', { + defaultMessage: 'Ascending', + }), + value: 'asc', }, - ]; + ], + write(agg, output) { + const sortField = agg.params.sortField; + const sortOrder = agg.params.sortOrder; + + if (sortField.scripted) { + output.params.sort = [ + { + _script: { + script: { + source: sortField.script, + lang: sortField.lang, + }, + type: sortField.type, + order: sortOrder.value, + }, + }, + ]; + } else { + output.params.sort = [ + { + [sortField.name]: { + order: sortOrder.value, + }, + }, + ]; + } + }, + }, + ], + getValue(agg, bucket) { + const hits: any[] = _.get(bucket, `${agg.id}.hits.hits`); + if (!hits || !hits.length) { + return null; } - }, - }, - ], - getValue(agg, bucket) { - const hits: any[] = _.get(bucket, `${agg.id}.hits.hits`); - if (!hits || !hits.length) { - return null; - } - const path = agg.getParam('field').name; + const path = agg.getParam('field').name; - let values = _.flatten( - hits.map(hit => - path === '_source' ? hit._source : agg.getIndexPattern().flattenHit(hit, true)[path] - ) - ); + let values = _.flatten( + hits.map(hit => + path === '_source' ? hit._source : agg.getIndexPattern().flattenHit(hit, true)[path] + ) + ); - if (values.length === 1) { - values = values[0]; - } + if (values.length === 1) { + values = values[0]; + } + + if (Array.isArray(values)) { + if (!_.compact(values).length) { + return null; + } + + const aggregate = agg.getParam('aggregate'); - if (Array.isArray(values)) { - if (!_.compact(values).length) { - return null; - } - - const aggregate = agg.getParam('aggregate'); - - switch (aggregate.value) { - case 'max': - return _.max(values); - case 'min': - return _.min(values); - case 'sum': - return _.sum(values); - case 'average': - return _.sum(values) / values.length; - } + switch (aggregate.value) { + case 'max': + return _.max(values); + case 'min': + return _.min(values); + case 'sum': + return _.sum(values); + case 'average': + return _.sum(values) / values.length; + } + } + return values; + }, + }, + { + getInternalStartServices, } - return values; - }, -}); + ); +}; diff --git a/src/plugins/data/public/search/aggs/param_types/field.test.ts b/src/plugins/data/public/search/aggs/param_types/field.test.ts index 0182471392910..ea7931130b84a 100644 --- a/src/plugins/data/public/search/aggs/param_types/field.test.ts +++ b/src/plugins/data/public/search/aggs/param_types/field.test.ts @@ -18,11 +18,20 @@ */ import { BaseParamType } from './base'; -import { FieldParamType } from './field'; +import { FieldParamType, FieldParamTypeDependencies } from './field'; import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '../../../../common'; import { IAggConfig } from '../agg_config'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; describe('Field', () => { + const fieldParamTypeDependencies: FieldParamTypeDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; + const indexPattern = { id: '1234', title: 'logstash-*', @@ -52,10 +61,13 @@ describe('Field', () => { describe('constructor', () => { it('it is an instance of BaseParamType', () => { - const aggParam = new FieldParamType({ - name: 'field', - type: 'field', - }); + const aggParam = new FieldParamType( + { + name: 'field', + type: 'field', + }, + fieldParamTypeDependencies + ); expect(aggParam instanceof BaseParamType).toBeTruthy(); }); @@ -63,10 +75,13 @@ describe('Field', () => { describe('getAvailableFields', () => { it('should return only aggregatable fields by default', () => { - const aggParam = new FieldParamType({ - name: 'field', - type: 'field', - }); + const aggParam = new FieldParamType( + { + name: 'field', + type: 'field', + }, + fieldParamTypeDependencies + ); const fields = aggParam.getAvailableFields(agg); @@ -78,10 +93,13 @@ describe('Field', () => { }); it('should return all fields if onlyAggregatable is false', () => { - const aggParam = new FieldParamType({ - name: 'field', - type: 'field', - }); + const aggParam = new FieldParamType( + { + name: 'field', + type: 'field', + }, + fieldParamTypeDependencies + ); aggParam.onlyAggregatable = false; @@ -91,10 +109,13 @@ describe('Field', () => { }); it('should return all fields if filterFieldTypes was not specified', () => { - const aggParam = new FieldParamType({ - name: 'field', - type: 'field', - }); + const aggParam = new FieldParamType( + { + name: 'field', + type: 'field', + }, + fieldParamTypeDependencies + ); indexPattern.fields[1].aggregatable = true; diff --git a/src/plugins/data/public/search/aggs/param_types/field.ts b/src/plugins/data/public/search/aggs/param_types/field.ts index 34b77e14a3a71..4d67f41905c5a 100644 --- a/src/plugins/data/public/search/aggs/param_types/field.ts +++ b/src/plugins/data/public/search/aggs/param_types/field.ts @@ -24,7 +24,7 @@ import { BaseParamType } from './base'; import { propFilter } from '../filter'; import { isNestedField, KBN_FIELD_TYPES } from '../../../../common'; import { Field as IndexPatternField } from '../../../index_patterns'; -import { getNotifications } from '../../../../public/services'; +import { GetInternalStartServicesFn } from '../../../types'; const filterByType = propFilter('type'); @@ -32,13 +32,20 @@ export type FieldTypes = KBN_FIELD_TYPES | KBN_FIELD_TYPES[] | '*'; // TODO need to make a more explicit interface for this export type IFieldParamType = FieldParamType; +export interface FieldParamTypeDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + export class FieldParamType extends BaseParamType { required = true; scriptable = true; filterFieldTypes: FieldTypes; onlyAggregatable: boolean; - constructor(config: Record) { + constructor( + config: Record, + { getInternalStartServices }: FieldParamTypeDependencies + ) { super(config); this.filterFieldTypes = config.filterFieldTypes || '*'; @@ -87,7 +94,7 @@ export class FieldParamType extends BaseParamType { // @ts-ignore const validField = this.getAvailableFields(aggConfig).find((f: any) => f.name === fieldName); if (!validField) { - getNotifications().toasts.addDanger( + getInternalStartServices().notifications.toasts.addDanger( i18n.translate( 'data.search.aggs.paramTypes.field.invalidSavedFieldParameterErrorMessage', { diff --git a/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts b/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts index 57d27b7da6313..2383affa2a8c5 100644 --- a/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts +++ b/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts @@ -18,12 +18,13 @@ */ // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { coreMock, notificationServiceMock } from '../../../../../../../src/core/public/mocks'; import { AggTypesRegistry, AggTypesRegistryStart } from '../agg_types_registry'; import { getAggTypes } from '../agg_types'; -import { BucketAggType } from '../buckets/_bucket_agg_type'; +import { BucketAggType } from '../buckets/bucket_agg_type'; import { MetricAggType } from '../metrics/metric_agg_type'; import { queryServiceMock } from '../../../query/mocks'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; /** * Testing utility which creates a new instance of AggTypesRegistry, @@ -55,8 +56,11 @@ export function mockAggTypesRegistry | MetricAggTyp const core = coreMock.createSetup(); const aggTypes = getAggTypes({ uiSettings: core.uiSettings, - notifications: core.notifications, query: queryServiceMock.createSetupContract(), + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), }); aggTypes.buckets.forEach(type => registrySetup.registerBucket(type)); diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts index dc1c99f76d59a..42f31ef450d28 100644 --- a/src/plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/search/search_service.ts @@ -26,6 +26,7 @@ import { getEsClient, LegacyApiCaller } from './es_client'; import { ES_SEARCH_STRATEGY, DEFAULT_SEARCH_STRATEGY } from '../../common/search'; import { esSearchStrategyProvider } from './es_search/es_search_strategy'; import { QuerySetup } from '../query/query_service'; +import { GetInternalStartServicesFn } from '../types'; import { SearchInterceptor } from './search_interceptor'; import { getAggTypes, @@ -44,6 +45,7 @@ import { interface SearchServiceSetupDependencies { packageInfo: PackageInfo; query: QuerySetup; + getInternalStartServices: GetInternalStartServicesFn; } /** @@ -81,7 +83,7 @@ export class SearchService implements Plugin { public setup( core: CoreSetup, - { packageInfo, query }: SearchServiceSetupDependencies + { packageInfo, query, getInternalStartServices }: SearchServiceSetupDependencies ): ISearchSetup { this.esClient = getEsClient(core.injectedMetadata, core.http, packageInfo); this.registerSearchStrategyProvider(SYNC_SEARCH_STRATEGY, syncSearchStrategyProvider); @@ -91,7 +93,7 @@ export class SearchService implements Plugin { const aggTypes = getAggTypes({ query, uiSettings: core.uiSettings, - notifications: core.notifications, + getInternalStartServices, }); aggTypes.buckets.forEach(b => aggTypesSetup.registerBucket(b)); diff --git a/src/plugins/data/public/types.ts b/src/plugins/data/public/types.ts index 45160cbf30179..e24e01d241278 100644 --- a/src/plugins/data/public/types.ts +++ b/src/plugins/data/public/types.ts @@ -71,3 +71,12 @@ export interface IDataPluginServices extends Partial { storage: IStorageWrapper; data: DataPublicPluginStart; } + +/** @internal **/ +export interface InternalStartServices { + fieldFormats: FieldFormatsStart; + notifications: CoreStart['notifications']; +} + +/** @internal **/ +export type GetInternalStartServicesFn = () => InternalStartServices;