From 4dc21e5589067fe68ca2767204759e8579914c16 Mon Sep 17 00:00:00 2001
From: Apoorva Joshi <30438249+ajosh0504@users.noreply.github.com>
Date: Mon, 24 Apr 2023 06:51:06 -0700
Subject: [PATCH 07/36] Updates to pre-built Security ML jobs (#154596)
## Summary
This PR makes the following updates to the pre-built Security ML jobs:
- Making the `security-packetbeat` compatible with Agent
- Removing superfluous fields from the job configurations to make them
consistent
- Updating the `detector_description` field for almost all jobs
- Adding influencers where missing and/or relevant
- Adding a `job_revision` custom setting similar to the Logs
[jobs](https://github.com/elastic/kibana/blob/main/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/ml/log_entry_rate.json#L29).
Moving forward, this number will be updated each time a job is updated.
We are starting with 4 since the `linux` and `windows` jobs are at v3
right now
- Adding a `managed`: `true` tag to indicate that these jobs are
pre-configured by Elastic and so users will see the warnings added in
[this](https://github.com/elastic/kibana/pull/122305) PR if users choose
to delete, or modify these jobs
---------
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../modules/security_auth/manifest.json | 4 +-
.../ml/auth_high_count_logon_events.json | 14 ++---
...gh_count_logon_events_for_a_source_ip.json | 18 ++----
.../ml/auth_high_count_logon_fails.json | 14 ++---
.../ml/auth_rare_hour_for_a_user.json | 18 +++---
.../ml/auth_rare_source_ip_for_a_user.json | 18 +++---
.../security_auth/ml/auth_rare_user.json | 18 +++---
.../datafeed_suspicious_login_activity.json | 9 +--
.../ml/suspicious_login_activity.json | 27 +++------
.../modules/security_cloudtrail/manifest.json | 12 ++--
.../ml/high_distinct_count_error_message.json | 21 +++----
.../ml/rare_error_code.json | 20 +++----
.../ml/rare_method_for_a_city.json | 20 +++----
.../ml/rare_method_for_a_country.json | 20 +++----
.../ml/rare_method_for_a_username.json | 17 +++---
.../modules/security_linux/manifest.json | 7 +--
.../v3_linux_anomalous_network_activity.json | 49 +++--------------
...linux_anomalous_network_port_activity.json | 49 +++--------------
.../v3_linux_anomalous_process_all_hosts.json | 49 +++--------------
.../ml/v3_linux_anomalous_user_name.json | 48 +++-------------
...linux_network_configuration_discovery.json | 51 +++--------------
...v3_linux_network_connection_discovery.json | 51 +++--------------
.../ml/v3_linux_rare_metadata_process.json | 30 +++-------
.../ml/v3_linux_rare_metadata_user.json | 29 +++-------
.../ml/v3_linux_rare_sudo_user.json | 51 +++--------------
.../ml/v3_linux_rare_user_compiler.json | 43 +++------------
...v3_linux_system_information_discovery.json | 51 +++--------------
.../ml/v3_linux_system_process_discovery.json | 51 +++--------------
.../ml/v3_linux_system_user_discovery.json | 49 +++--------------
.../ml/v3_rare_process_by_host_linux.json | 48 +++-------------
.../modules/security_network/manifest.json | 4 +-
.../ml/high_count_by_destination_country.json | 14 ++---
.../ml/high_count_network_denies.json | 14 ++---
.../ml/high_count_network_events.json | 14 ++---
.../ml/rare_destination_country.json | 11 ++--
.../modules/security_packetbeat/manifest.json | 10 ++--
.../ml/datafeed_packetbeat_dns_tunneling.json | 16 +++---
...datafeed_packetbeat_rare_dns_question.json | 16 +++---
.../datafeed_packetbeat_rare_user_agent.json | 16 +++---
.../ml/packetbeat_dns_tunneling.json | 29 +++-------
.../ml/packetbeat_rare_dns_question.json | 22 ++------
.../ml/packetbeat_rare_server_domain.json | 24 ++------
.../ml/packetbeat_rare_urls.json | 23 ++------
.../ml/packetbeat_rare_user_agent.json | 23 ++------
.../ml/v3_rare_process_by_host_windows.json | 53 +++---------------
...v3_windows_anomalous_network_activity.json | 53 +++---------------
.../v3_windows_anomalous_path_activity.json | 52 +++---------------
...3_windows_anomalous_process_all_hosts.json | 55 +++----------------
...v3_windows_anomalous_process_creation.json | 53 +++---------------
.../ml/v3_windows_anomalous_script.json | 42 +++-----------
.../ml/v3_windows_anomalous_service.json | 37 +++----------
.../ml/v3_windows_anomalous_user_name.json | 53 +++---------------
.../ml/v3_windows_rare_metadata_process.json | 34 +++---------
.../ml/v3_windows_rare_metadata_user.json | 33 +++--------
.../ml/v3_windows_rare_user_runas_event.json | 46 ++--------------
...windows_rare_user_type10_remote_login.json | 46 ++--------------
56 files changed, 386 insertions(+), 1313 deletions(-)
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/manifest.json
index b3395d82a9c29..d600e4a637acf 100755
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/manifest.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/manifest.json
@@ -2,7 +2,7 @@
"id": "security_auth",
"title": "Security: Authentication",
"description": "Detect anomalous activity in your ECS-compatible authentication logs.",
- "type": "auth data",
+ "type": "Auth data",
"logoFile": "logo.json",
"defaultIndexPattern": "auditbeat-*,logs-*,filebeat-*,winlogbeat-*",
"query": {
@@ -14,7 +14,7 @@
}
}
],
- "must_not": { "terms": { "_tier": [ "data_frozen", "data_cold" ] } }
+ "must_not": { "terms": { "_tier": ["data_frozen", "data_cold"] } }
}
},
"jobs": [
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_high_count_logon_events.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_high_count_logon_events.json
index 7ca7a5ebd71e4..ac50e2f53535c 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_high_count_logon_events.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_high_count_logon_events.json
@@ -1,20 +1,16 @@
{
"description": "Security: Authentication - Looks for an unusually large spike in successful authentication events. This can be due to password spraying, user enumeration, or brute force activity.",
- "groups": [
- "security",
- "authentication"
- ],
+ "groups": ["security", "authentication"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "high count of logon events",
+ "detector_description": "Detects high count of logon events.",
"function": "high_non_zero_count",
"detector_index": 0
}
],
- "influencers": [],
- "model_prune_window": "30d"
+ "influencers": ["source.ip", "winlog.event_data.LogonType", "user.name", "host.name"]
},
"allow_lazy_open": true,
"analysis_limits": {
@@ -25,6 +21,8 @@
},
"custom_settings": {
"created_by": "ml-module-security-auth",
- "security_app_display_name": "Spike in Logon Events"
+ "security_app_display_name": "Spike in Logon Events",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_high_count_logon_events_for_a_source_ip.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_high_count_logon_events_for_a_source_ip.json
index 47096f4c6413f..d23f8df88ef6a 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_high_count_logon_events_for_a_source_ip.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_high_count_logon_events_for_a_source_ip.json
@@ -1,25 +1,17 @@
{
"description": "Security: Authentication - Looks for an unusually large spike in successful authentication events from a particular source IP address. This can be due to password spraying, user enumeration, or brute force activity.",
- "groups": [
- "security",
- "authentication"
- ],
+ "groups": ["security", "authentication"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "high count of auth events for a source IP",
+ "detector_description": "Detects high count of auth events for a source IP.",
"function": "high_non_zero_count",
"by_field_name": "source.ip",
"detector_index": 0
}
],
- "influencers": [
- "source.ip",
- "winlog.event_data.LogonType",
- "user.name"
- ],
- "model_prune_window": "30d"
+ "influencers": ["source.ip", "winlog.event_data.LogonType", "user.name", "host.name"]
},
"allow_lazy_open": true,
"analysis_limits": {
@@ -30,6 +22,8 @@
},
"custom_settings": {
"created_by": "ml-module-security-auth",
- "security_app_display_name": "Spike in Logon Events from a Source IP"
+ "security_app_display_name": "Spike in Logon Events from a Source IP",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_high_count_logon_fails.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_high_count_logon_fails.json
index 48586ef642ca6..db2db5ea00832 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_high_count_logon_fails.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_high_count_logon_fails.json
@@ -1,20 +1,16 @@
{
"description": "Security: Authentication - Looks for an unusually large spike in authentication failure events. This can be due to password spraying, user enumeration, or brute force activity and may be a precursor to account takeover or credentialed access.",
- "groups": [
- "security",
- "authentication"
- ],
+ "groups": ["security", "authentication"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "high count of logon fails",
+ "detector_description": "Detects high count of logon fails.",
"function": "high_non_zero_count",
"detector_index": 0
}
],
- "influencers": [],
- "model_prune_window": "30d"
+ "influencers": ["source.ip", "user.name", "host.name"]
},
"allow_lazy_open": true,
"analysis_limits": {
@@ -25,6 +21,8 @@
},
"custom_settings": {
"created_by": "ml-module-security-auth",
- "security_app_display_name": "Spike in Failed Logon Events"
+ "security_app_display_name": "Spike in Failed Logon Events",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_rare_hour_for_a_user.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_rare_hour_for_a_user.json
index 1f421ed298b9f..57477497aeb62 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_rare_hour_for_a_user.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_rare_hour_for_a_user.json
@@ -1,23 +1,17 @@
{
- "description": "Security: Authentication - looks for a user logging in at a time of day that is unusual for the user. This can be due to credentialed access via a compromised account when the user and the threat actor are in different time zones. In addition, unauthorized user activity often takes place during non-business hours.",
- "groups": [
- "security",
- "authentication"
- ],
+ "description": "Security: Authentication - Looks for a user logging in at a time of day that is unusual for the user. This can be due to credentialed access via a compromised account when the user and the threat actor are in different time zones. In addition, unauthorized user activity often takes place during non-business hours.",
+ "groups": ["security", "authentication"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "rare hour for a user",
+ "detector_description": "Detects rare hour for a user.",
"function": "time_of_day",
"by_field_name": "user.name",
"detector_index": 0
}
],
- "influencers": [
- "source.ip",
- "user.name"
- ]
+ "influencers": ["source.ip", "user.name", "host.name"]
},
"allow_lazy_open": true,
"analysis_limits": {
@@ -28,6 +22,8 @@
},
"custom_settings": {
"created_by": "ml-module-security-auth",
- "security_app_display_name": "Unusual Hour for a User to Logon"
+ "security_app_display_name": "Unusual Hour for a User to Logon",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_rare_source_ip_for_a_user.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_rare_source_ip_for_a_user.json
index 98a249074a67a..81185ef5039c7 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_rare_source_ip_for_a_user.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_rare_source_ip_for_a_user.json
@@ -1,24 +1,18 @@
{
- "description": "Security: Authentication - looks for a user logging in from an IP address that is unusual for the user. This can be due to credentialed access via a compromised account when the user and the threat actor are in different locations. An unusual source IP address for a username could also be due to lateral movement when a compromised account is used to pivot between hosts.",
- "groups": [
- "security",
- "authentication"
- ],
+ "description": "Security: Authentication - Looks for a user logging in from an IP address that is unusual for the user. This can be due to credentialed access via a compromised account when the user and the threat actor are in different locations. An unusual source IP address for a username could also be due to lateral movement when a compromised account is used to pivot between hosts.",
+ "groups": ["security", "authentication"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "rare source IP for a user",
+ "detector_description": "Detects rare source IP for a user.",
"function": "rare",
"by_field_name": "source.ip",
"partition_field_name": "user.name",
"detector_index": 0
}
],
- "influencers": [
- "source.ip",
- "user.name"
- ]
+ "influencers": ["source.ip", "user.name", "host.name"]
},
"allow_lazy_open": true,
"analysis_limits": {
@@ -29,6 +23,8 @@
},
"custom_settings": {
"created_by": "ml-module-security-auth",
- "security_app_display_name": "Unusual Source IP for a User to Logon from"
+ "security_app_display_name": "Unusual Source IP for a User to Logon from",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_rare_user.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_rare_user.json
index e2488480e61d1..58530fe085014 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_rare_user.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_rare_user.json
@@ -1,23 +1,17 @@
{
- "description": "Security: Authentication - looks for an unusual user name in the authentication logs. An unusual user name is one way of detecting credentialed access by means of a new or dormant user account. A user account that is normally inactive, because the user has left the organization, which becomes active, may be due to credentialed access using a compromised account password. Threat actors will sometimes also create new users as a means of persisting in a compromised web application.",
- "groups": [
- "security",
- "authentication"
- ],
+ "description": "Security: Authentication - Looks for an unusual user name in the authentication logs. An unusual user name is one way of detecting credentialed access by means of a new or dormant user account. A user account that is normally inactive, because the user has left the organization, which becomes active, may be due to credentialed access using a compromised account password. Threat actors will sometimes also create new users as a means of persisting in a compromised web application.",
+ "groups": ["security", "authentication"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "rare user",
+ "detector_description": "Detects rare user authentication.",
"function": "rare",
"by_field_name": "user.name",
"detector_index": 0
}
],
- "influencers": [
- "source.ip",
- "user.name"
- ]
+ "influencers": ["source.ip", "user.name", "host.name"]
},
"allow_lazy_open": true,
"analysis_limits": {
@@ -28,6 +22,8 @@
},
"custom_settings": {
"created_by": "ml-module-security-auth",
- "security_app_display_name": "Rare User Logon"
+ "security_app_display_name": "Rare User Logon",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/datafeed_suspicious_login_activity.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/datafeed_suspicious_login_activity.json
index 386b9fab25667..59a9129e7b7bf 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/datafeed_suspicious_login_activity.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/datafeed_suspicious_login_activity.json
@@ -1,15 +1,10 @@
{
"job_id": "JOB_ID",
- "indices": [
- "INDEX_PATTERN_NAME"
- ],
+ "indices": ["INDEX_PATTERN_NAME"],
"max_empty_searches": 10,
"query": {
"bool": {
- "filter": [
- {"term": { "event.category": "authentication" }},
- {"term": { "agent.type": "auditbeat" }}
- ]
+ "filter": [{ "term": { "event.category": "authentication" } }]
}
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/suspicious_login_activity.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/suspicious_login_activity.json
index 00e810b5348e7..bbe420b3ec0eb 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/suspicious_login_activity.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/suspicious_login_activity.json
@@ -1,24 +1,17 @@
{
- "description": "Security: Auditbeat - Detect unusually high number of authentication attempts.",
- "groups": [
- "security",
- "auditbeat",
- "authentication"
- ],
+ "description": "Security: Authentication - Detects unusually high number of authentication attempts.",
+ "groups": ["security", "authentication"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "high number of authentication attempts",
+ "detector_description": "Detects high number of authentication attempts for a host.",
"function": "high_non_zero_count",
- "partition_field_name": "host.name"
+ "partition_field_name": "host.name",
+ "detector_index": 0
}
],
- "influencers": [
- "host.name",
- "user.name",
- "source.ip"
- ],
+ "influencers": ["host.name", "user.name", "source.ip"],
"model_prune_window": "30d"
},
"allow_lazy_open": true,
@@ -31,11 +24,7 @@
"custom_settings": {
"created_by": "ml-module-security-auth",
"security_app_display_name": "Unusual Login Activity",
- "custom_urls": [
- {
- "url_name": "IP Address Details",
- "url_value": "security/network/ml-network/ip/$source.ip$?_g=()&query=!n&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- }
- ]
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_cloudtrail/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_cloudtrail/manifest.json
index 93797b9e3e758..52b406a0da7cb 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_cloudtrail/manifest.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_cloudtrail/manifest.json
@@ -1,16 +1,14 @@
{
"id": "security_cloudtrail",
"title": "Security: Cloudtrail",
- "description": "Detect suspicious activity recorded in your cloudtrail logs.",
- "type": "Filebeat data",
+ "description": "Detect suspicious activity recorded in Cloudtrail logs.",
+ "type": "Cloudtrail data",
"logoFile": "logo.json",
- "defaultIndexPattern": "filebeat-*",
+ "defaultIndexPattern": "logs-*,filebeat-*",
"query": {
"bool": {
- "filter": [
- {"term": {"event.dataset": "aws.cloudtrail"}}
- ],
- "must_not": { "terms": { "_tier": [ "data_frozen", "data_cold" ] } }
+ "filter": [{ "term": { "event.dataset": "aws.cloudtrail" } }],
+ "must_not": { "terms": { "_tier": ["data_frozen", "data_cold"] } }
}
},
"jobs": [
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_cloudtrail/ml/high_distinct_count_error_message.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_cloudtrail/ml/high_distinct_count_error_message.json
index 11b5f4625a484..2ba7c4fdf4085 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_cloudtrail/ml/high_distinct_count_error_message.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_cloudtrail/ml/high_distinct_count_error_message.json
@@ -1,24 +1,17 @@
{
"description": "Security: Cloudtrail - Looks for a spike in the rate of an error message which may simply indicate an impending service failure but these can also be byproducts of attempted or successful persistence, privilege escalation, defense evasion, discovery, lateral movement, or collection activity by a threat actor.",
- "groups": [
- "security",
- "cloudtrail"
- ],
+ "groups": ["security", "cloudtrail"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "high_distinct_count(\"aws.cloudtrail.error_message\")",
+ "detector_description": "Detects high distinct count of Cloudtrail error messages.",
"function": "high_distinct_count",
- "field_name": "aws.cloudtrail.error_message"
+ "field_name": "aws.cloudtrail.error_message",
+ "detector_index": 0
}
],
- "influencers": [
- "aws.cloudtrail.user_identity.arn",
- "source.ip",
- "source.geo.city_name"
- ],
- "model_prune_window": "30d"
+ "influencers": ["aws.cloudtrail.user_identity.arn", "source.ip", "source.geo.city_name"]
},
"allow_lazy_open": true,
"analysis_limits": {
@@ -29,6 +22,8 @@
},
"custom_settings": {
"created_by": "ml-module-security-cloudtrail",
- "security_app_display_name": "Spike in AWS Error Messages"
+ "security_app_display_name": "Spike in AWS Error Messages",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_cloudtrail/ml/rare_error_code.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_cloudtrail/ml/rare_error_code.json
index c54c8e8378f2c..7752430876e3f 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_cloudtrail/ml/rare_error_code.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_cloudtrail/ml/rare_error_code.json
@@ -1,23 +1,17 @@
{
"description": "Security: Cloudtrail - Looks for unusual errors. Rare and unusual errors may simply indicate an impending service failure but they can also be byproducts of attempted or successful persistence, privilege escalation, defense evasion, discovery, lateral movement, or collection activity by a threat actor.",
- "groups": [
- "security",
- "cloudtrail"
- ],
+ "groups": ["security", "cloudtrail"],
"analysis_config": {
"bucket_span": "60m",
"detectors": [
{
- "detector_description": "rare by \"aws.cloudtrail.error_code\"",
+ "detector_description": "Detects rare Cloudtrail error codes.",
"function": "rare",
- "by_field_name": "aws.cloudtrail.error_code"
+ "by_field_name": "aws.cloudtrail.error_code",
+ "detector_index": 0
}
],
- "influencers": [
- "aws.cloudtrail.user_identity.arn",
- "source.ip",
- "source.geo.city_name"
- ]
+ "influencers": ["aws.cloudtrail.user_identity.arn", "source.ip", "source.geo.city_name"]
},
"allow_lazy_open": true,
"analysis_limits": {
@@ -28,6 +22,8 @@
},
"custom_settings": {
"created_by": "ml-module-security-cloudtrail",
- "security_app_display_name": "Rare AWS Error Code"
+ "security_app_display_name": "Rare AWS Error Code",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_cloudtrail/ml/rare_method_for_a_city.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_cloudtrail/ml/rare_method_for_a_city.json
index 2ed28884be94f..f7be6fe8cc8d7 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_cloudtrail/ml/rare_method_for_a_city.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_cloudtrail/ml/rare_method_for_a_city.json
@@ -1,24 +1,18 @@
{
"description": "Security: Cloudtrail - Looks for AWS API calls that, while not inherently suspicious or abnormal, are sourcing from a geolocation (city) that is unusual. This can be the result of compromised credentials or keys.",
- "groups": [
- "security",
- "cloudtrail"
- ],
+ "groups": ["security", "cloudtrail"],
"analysis_config": {
"bucket_span": "60m",
"detectors": [
{
- "detector_description": "rare by \"event.action\" partition by \"source.geo.city_name\"",
+ "detector_description": "Detects rare event actions for a city.",
"function": "rare",
"by_field_name": "event.action",
- "partition_field_name": "source.geo.city_name"
+ "partition_field_name": "source.geo.city_name",
+ "detector_index": 0
}
],
- "influencers": [
- "aws.cloudtrail.user_identity.arn",
- "source.ip",
- "source.geo.city_name"
- ]
+ "influencers": ["aws.cloudtrail.user_identity.arn", "source.ip", "source.geo.city_name"]
},
"allow_lazy_open": true,
"analysis_limits": {
@@ -29,6 +23,8 @@
},
"custom_settings": {
"created_by": "ml-module-security-cloudtrail",
- "security_app_display_name": "Unusual City for an AWS Command"
+ "security_app_display_name": "Unusual City for an AWS Command",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_cloudtrail/ml/rare_method_for_a_country.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_cloudtrail/ml/rare_method_for_a_country.json
index 1f14357e73444..d73f51f34de3a 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_cloudtrail/ml/rare_method_for_a_country.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_cloudtrail/ml/rare_method_for_a_country.json
@@ -1,24 +1,18 @@
{
"description": "Security: Cloudtrail - Looks for AWS API calls that, while not inherently suspicious or abnormal, are sourcing from a geolocation (country) that is unusual. This can be the result of compromised credentials or keys.",
- "groups": [
- "security",
- "cloudtrail"
- ],
+ "groups": ["security", "cloudtrail"],
"analysis_config": {
"bucket_span": "60m",
"detectors": [
{
- "detector_description": "rare by \"event.action\" partition by \"source.geo.country_iso_code\"",
+ "detector_description": "Detects rare event actions for an ISO code.",
"function": "rare",
"by_field_name": "event.action",
- "partition_field_name": "source.geo.country_iso_code"
+ "partition_field_name": "source.geo.country_iso_code",
+ "detector_index": 0
}
],
- "influencers": [
- "aws.cloudtrail.user_identity.arn",
- "source.ip",
- "source.geo.country_iso_code"
- ]
+ "influencers": ["aws.cloudtrail.user_identity.arn", "source.ip", "source.geo.country_iso_code"]
},
"allow_lazy_open": true,
"analysis_limits": {
@@ -29,6 +23,8 @@
},
"custom_settings": {
"created_by": "ml-module-security-cloudtrail",
- "security_app_display_name": "Unusual Country for an AWS Command"
+ "security_app_display_name": "Unusual Country for an AWS Command",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_cloudtrail/ml/rare_method_for_a_username.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_cloudtrail/ml/rare_method_for_a_username.json
index 76cce7fb829ca..a508028619833 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_cloudtrail/ml/rare_method_for_a_username.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_cloudtrail/ml/rare_method_for_a_username.json
@@ -1,23 +1,22 @@
{
"description": "Security: Cloudtrail - Looks for AWS API calls that, while not inherently suspicious or abnormal, are sourcing from a user context that does not normally call the method. This can be the result of compromised credentials or keys as someone uses a valid account to persist, move laterally, or exfil data.",
- "groups": [
- "security",
- "cloudtrail"
- ],
+ "groups": ["security", "cloudtrail"],
"analysis_config": {
"bucket_span": "60m",
"detectors": [
{
- "detector_description": "rare by \"event.action\" partition by \"user.name\"",
+ "detector_description": "Detects rare event actions for a user.",
"function": "rare",
"by_field_name": "event.action",
- "partition_field_name": "user.name"
+ "partition_field_name": "user.name",
+ "detector_index": 0
}
],
"influencers": [
"user.name",
"source.ip",
- "source.geo.city_name"
+ "source.geo.city_name",
+ "aws.cloudtrail.user_identity.arn"
]
},
"allow_lazy_open": true,
@@ -29,6 +28,8 @@
},
"custom_settings": {
"created_by": "ml-module-security-cloudtrail",
- "security_app_display_name": "Unusual AWS Command for a User"
+ "security_app_display_name": "Unusual AWS Command for a User",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/manifest.json
index 269f90dea4471..cfff61e304c0e 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/manifest.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/manifest.json
@@ -2,7 +2,7 @@
"id": "security_linux_v3",
"title": "Security: Linux",
"description": "Anomaly detection jobs for Linux host-based threat hunting and detection.",
- "type": "linux data",
+ "type": "Linux data",
"logoFile": "logo.json",
"defaultIndexPattern": "auditbeat-*,logs-*",
"query": {
@@ -43,10 +43,7 @@
],
"must_not": {
"terms": {
- "_tier": [
- "data_frozen",
- "data_cold"
- ]
+ "_tier": ["data_frozen", "data_cold"]
}
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_anomalous_network_activity.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_anomalous_network_activity.json
index 29f6bf1d98412..b276bcc7856ba 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_anomalous_network_activity.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_anomalous_network_activity.json
@@ -1,27 +1,17 @@
{
"description": "Security: Linux - Looks for unusual processes using the network which could indicate command-and-control, lateral movement, persistence, or data exfiltration activity.",
- "groups": [
- "auditbeat",
- "endpoint",
- "linux",
- "network",
- "security"
- ],
+ "groups": ["linux", "security"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "Detects rare process.name values.",
+ "detector_description": "Detects rare processes.",
"function": "rare",
- "by_field_name": "process.name"
+ "by_field_name": "process.name",
+ "detector_index": 0
}
],
- "influencers": [
- "host.name",
- "process.name",
- "user.name",
- "destination.ip"
- ]
+ "influencers": ["host.name", "process.name", "user.name", "destination.ip"]
},
"allow_lazy_open": true,
"analysis_limits": {
@@ -31,32 +21,9 @@
"time_field": "@timestamp"
},
"custom_settings": {
- "job_tags": {
- "euid": "4004",
- "maturity": "release",
- "author": "@randomuserid/Elastic",
- "version": "3",
- "updated_date": "5/16/2022"
- },
"created_by": "ml-module-security-linux-v3",
- "custom_urls": [
- {
- "url_name": "Host Details by process name",
- "url_value": "siem#/ml-hosts/$host.name$?_g=()&kqlQuery=(filterQuery:(expression:'process.name%20:%20%22$process.name$%22',kind:kuery),queryLocation:hosts.details,type:details)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Host Details by user name",
- "url_value": "siem#/ml-hosts/$host.name$?_g=()&kqlQuery=(filterQuery:(expression:'user.name%20:%20%22$user.name$%22',kind:kuery),queryLocation:hosts.details,type:details)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by process name",
- "url_value": "siem#/ml-hosts?_g=()&kqlQuery=(filterQuery:(expression:'process.name%20:%20%22$process.name$%22',kind:kuery),queryLocation:hosts.page,type:page)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by user name",
- "url_value": "siem#/ml-hosts?_g=()&kqlQuery=(filterQuery:(expression:'user.name%20:%20%22$user.name$%22',kind:kuery),queryLocation:hosts.page,type:page)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- }
- ],
- "security_app_display_name": "Unusual Linux Network Activity"
+ "security_app_display_name": "Unusual Linux Network Activity",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_anomalous_network_port_activity.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_anomalous_network_port_activity.json
index 34b97358260ac..a551d6c2c204f 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_anomalous_network_port_activity.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_anomalous_network_port_activity.json
@@ -1,27 +1,17 @@
{
"description": "Security: Linux - Looks for unusual destination port activity that could indicate command-and-control, persistence mechanism, or data exfiltration activity.",
- "groups": [
- "security",
- "auditbeat",
- "endpoint",
- "linux",
- "network"
- ],
+ "groups": ["security", "linux"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "Detects rare destination.port values.",
+ "detector_description": "Detects rare destination ports.",
"function": "rare",
- "by_field_name": "destination.port"
+ "by_field_name": "destination.port",
+ "detector_index": 0
}
],
- "influencers": [
- "host.name",
- "process.name",
- "user.name",
- "destination.ip"
- ]
+ "influencers": ["host.name", "process.name", "user.name", "destination.ip"]
},
"allow_lazy_open": true,
"analysis_limits": {
@@ -31,32 +21,9 @@
"time_field": "@timestamp"
},
"custom_settings": {
- "job_tags": {
- "euid": "4005",
- "maturity": "release",
- "author": "@randomuserid/Elastic",
- "version": "3",
- "updated_date": "5/16/2022"
- },
"created_by": "ml-module-security-linux-v3",
- "custom_urls": [
- {
- "url_name": "Host Details by process name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Host Details by user name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by process name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by user name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- }
- ],
- "security_app_display_name": "Unusual Linux Network Port Activity"
+ "security_app_display_name": "Unusual Linux Network Port Activity",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_anomalous_process_all_hosts.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_anomalous_process_all_hosts.json
index a20a508391fb9..dea5fa3a5db31 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_anomalous_process_all_hosts.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_anomalous_process_all_hosts.json
@@ -1,65 +1,30 @@
{
"description": "Security: Linux - Looks for processes that are unusual to all Linux hosts. Such unusual processes may indicate unauthorized software, malware, or persistence mechanisms.",
- "groups": [
- "auditbeat",
- "endpoint",
- "linux",
- "process",
- "security"
- ],
+ "groups": ["linux", "security"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "Detects rare process.name values.",
+ "detector_description": "Detects rare processes.",
"function": "rare",
"by_field_name": "process.name",
"detector_index": 0
}
],
- "influencers": [
- "host.name",
- "process.name",
- "user.name"
- ]
+ "influencers": ["host.name", "process.name", "user.name"]
},
"allow_lazy_open": true,
"analysis_limits": {
- "model_memory_limit": "512mb",
- "categorization_examples_limit": 4
-
+ "model_memory_limit": "512mb"
},
"data_description": {
"time_field": "@timestamp",
"time_format": "epoch_ms"
},
"custom_settings": {
- "job_tags": {
- "euid": "4003",
- "maturity": "release",
- "author": "@randomuserid/Elastic",
- "version": "3",
- "updated_date": "5/16/2022"
- },
"created_by": "ml-module-security-linux-v3",
- "custom_urls": [
- {
- "url_name": "Host Details by process name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Host Details by user name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by process name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by user name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- }
- ],
- "security_app_display_name": "Anomalous Process for a Linux Population"
+ "security_app_display_name": "Anomalous Process for a Linux Population",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_anomalous_user_name.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_anomalous_user_name.json
index 72be89bd79aad..05d46860b145f 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_anomalous_user_name.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_anomalous_user_name.json
@@ -1,64 +1,30 @@
{
"description": "Security: Linux - Rare and unusual users that are not normally active may indicate unauthorized changes or activity by an unauthorized user which may be credentialed access or lateral movement.",
- "groups": [
- "auditbeat",
- "endpoint",
- "linux",
- "process",
- "security"
- ],
+ "groups": ["linux", "security"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "Detects rare user.name values.",
+ "detector_description": "Detects rare usernames.",
"function": "rare",
"by_field_name": "user.name",
"detector_index": 0
}
],
- "influencers": [
- "host.name",
- "process.name",
- "user.name"
- ]
+ "influencers": ["host.name", "process.name", "user.name"]
},
"allow_lazy_open": true,
"analysis_limits": {
- "model_memory_limit": "32mb",
- "categorization_examples_limit": 4
+ "model_memory_limit": "32mb"
},
"data_description": {
"time_field": "@timestamp",
"time_format": "epoch_ms"
},
"custom_settings": {
- "job_tags": {
- "euid": "4008",
- "maturity": "release",
- "author": "@randomuserid/Elastic",
- "version": "3",
- "updated_date": "5/16/2022"
- },
"created_by": "ml-module-security-linux-v3",
- "custom_urls": [
- {
- "url_name": "Host Details by process name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Host Details by user name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by process name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by user name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- }
- ],
- "security_app_display_name": "Unusual Linux Username"
+ "security_app_display_name": "Unusual Linux Username",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_network_configuration_discovery.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_network_configuration_discovery.json
index 1481b7a03a559..fccfa9493e8c2 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_network_configuration_discovery.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_network_configuration_discovery.json
@@ -1,27 +1,17 @@
{
"description": "Security: Linux - Looks for commands related to system network configuration discovery from an unusual user context. This can be due to uncommon troubleshooting activity or due to a compromised account. A compromised account may be used by a threat actor to engage in system network configuration discovery to increase their understanding of connected networks and hosts. This information may be used to shape follow-up behaviors such as lateral movement or additional discovery.",
- "groups": [
- "security",
- "auditbeat",
- "endpoint",
- "linux",
- "process"
- ],
+ "groups": ["security", "linux"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "Detects rare user.name values.",
+ "detector_description": "Detects rare usernames.",
"function": "rare",
- "by_field_name": "user.name"
+ "by_field_name": "user.name",
+ "detector_index": 0
}
],
- "influencers": [
- "process.name",
- "host.name",
- "process.args",
- "user.name"
- ]
+ "influencers": ["process.name", "host.name", "process.args", "user.name"]
},
"allow_lazy_open": true,
"analysis_limits": {
@@ -31,32 +21,9 @@
"time_field": "@timestamp"
},
"custom_settings": {
- "job_tags": {
- "euid": "40012",
- "maturity": "release",
- "author": "@randomuserid/Elastic",
- "version": "3",
- "updated_date": "5/16/2022"
- },
"created_by": "ml-module-security-linux-v3",
- "custom_urls": [
- {
- "url_name": "Host Details by process name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Host Details by user name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by process name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by user name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- }
- ],
- "security_app_display_name": "Unusual Linux System Network Configuration Discovery"
+ "security_app_display_name": "Unusual Linux Network Configuration Discovery",
+ "managed": true,
+ "job_revision": 4
}
-}
\ No newline at end of file
+}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_network_connection_discovery.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_network_connection_discovery.json
index 2b1cf43ac94d3..32dc04c079db1 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_network_connection_discovery.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_network_connection_discovery.json
@@ -1,27 +1,17 @@
{
"description": "Security: Linux - Looks for commands related to system network connection discovery from an unusual user context. This can be due to uncommon troubleshooting activity or due to a compromised account. A compromised account may be used by a threat actor to engage in system network connection discovery to increase their understanding of connected services and systems. This information may be used to shape follow-up behaviors such as lateral movement or additional discovery.",
- "groups": [
- "security",
- "auditbeat",
- "endpoint",
- "linux",
- "process"
- ],
+ "groups": ["security", "linux"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "Detects rare user.name values.",
+ "detector_description": "Detects rare usernames.",
"function": "rare",
- "by_field_name": "user.name"
+ "by_field_name": "user.name",
+ "detector_index": 0
}
],
- "influencers": [
- "process.name",
- "host.name",
- "process.args",
- "user.name"
- ]
+ "influencers": ["process.name", "host.name", "process.args", "user.name"]
},
"allow_lazy_open": true,
"analysis_limits": {
@@ -31,32 +21,9 @@
"time_field": "@timestamp"
},
"custom_settings": {
- "job_tags": {
- "euid": "4013",
- "maturity": "release",
- "author": "@randomuserid/Elastic",
- "version": "3",
- "updated_date": "5/16/2022"
- },
"created_by": "ml-module-security-linux-v3",
- "custom_urls": [
- {
- "url_name": "Host Details by process name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Host Details by user name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by process name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by user name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- }
- ],
- "security_app_display_name": "Unusual Linux Network Connection Discovery"
+ "security_app_display_name": "Unusual Linux Network Connection Discovery",
+ "managed": true,
+ "job_revision": 4
}
-}
\ No newline at end of file
+}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_rare_metadata_process.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_rare_metadata_process.json
index fcec32acd69b5..6897876ad6ba3 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_rare_metadata_process.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_rare_metadata_process.json
@@ -1,46 +1,30 @@
{
"description": "Security: Linux - Looks for anomalous access to the metadata service by an unusual process. The metadata service may be targeted in order to harvest credentials or user data scripts containing secrets.",
- "groups": [
- "auditbeat",
- "endpoint",
- "linux",
- "process",
- "security"
- ],
+ "groups": ["linux", "security"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "Detects rare process.name values.",
+ "detector_description": "Detects rare processes.",
"function": "rare",
"by_field_name": "process.name",
"detector_index": 0
}
],
- "influencers": [
- "host.name",
- "user.name",
- "process.name"
- ]
+ "influencers": ["host.name", "user.name", "process.name"]
},
"allow_lazy_open": true,
"analysis_limits": {
- "model_memory_limit": "32mb",
- "categorization_examples_limit": 4
+ "model_memory_limit": "32mb"
},
"data_description": {
"time_field": "@timestamp",
"time_format": "epoch_ms"
},
"custom_settings": {
- "job_tags": {
- "euid": "4009",
- "maturity": "release",
- "author": "@randomuserid/Elastic",
- "version": "3",
- "updated_date": "5/16/2022"
- },
"created_by": "ml-module-security-linux-v3",
- "security_app_display_name": "Unusual Linux Process Calling the Metadata Service"
+ "security_app_display_name": "Unusual Linux Process Calling the Metadata Service",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_rare_metadata_user.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_rare_metadata_user.json
index d8414c8bf22bd..ad81023d69383 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_rare_metadata_user.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_rare_metadata_user.json
@@ -1,45 +1,30 @@
{
"description": "Security: Linux - Looks for anomalous access to the metadata service by an unusual user. The metadata service may be targeted in order to harvest credentials or user data scripts containing secrets.",
- "groups": [
- "auditbeat",
- "endpoint",
- "linux",
- "process",
- "security"
- ],
+ "groups": ["linux", "security"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "Detects rare user.name values.",
+ "detector_description": "Detects rare usernames.",
"function": "rare",
"by_field_name": "user.name",
"detector_index": 0
}
],
- "influencers": [
- "host.name",
- "user.name"
- ]
+ "influencers": ["host.name", "user.name"]
},
"allow_lazy_open": true,
"analysis_limits": {
- "model_memory_limit": "32mb",
- "categorization_examples_limit": 4
+ "model_memory_limit": "32mb"
},
"data_description": {
"time_field": "@timestamp",
"time_format": "epoch_ms"
},
"custom_settings": {
- "job_tags": {
- "euid": "4010",
- "maturity": "release",
- "author": "@randomuserid/Elastic",
- "version": "3",
- "updated_date": "5/16/2022"
- },
"created_by": "ml-module-security-linux-v3",
- "security_app_display_name": "Unusual Linux User Calling the Metadata Service"
+ "security_app_display_name": "Unusual Linux User Calling the Metadata Service",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_rare_sudo_user.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_rare_sudo_user.json
index a99e5f95572f7..11be6277c4220 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_rare_sudo_user.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_rare_sudo_user.json
@@ -1,27 +1,17 @@
{
"description": "Security: Linux - Looks for sudo activity from an unusual user context. Unusual user context changes can be due to privilege escalation.",
- "groups": [
- "security",
- "auditbeat",
- "endpoint",
- "linux",
- "process"
- ],
+ "groups": ["security", "linux"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "Detects rare user.name values.",
+ "detector_description": "Detects rare usernames.",
"function": "rare",
- "by_field_name": "user.name"
+ "by_field_name": "user.name",
+ "detector_index": 0
}
],
- "influencers": [
- "process.name",
- "host.name",
- "process.args",
- "user.name"
- ]
+ "influencers": ["process.name", "host.name", "process.args", "user.name"]
},
"allow_lazy_open": true,
"analysis_limits": {
@@ -31,32 +21,9 @@
"time_field": "@timestamp"
},
"custom_settings": {
- "job_tags": {
- "euid": "4017",
- "maturity": "release",
- "author": "@randomuserid/Elastic",
- "version": "3",
- "updated_date": "5/16/2022"
- },
"created_by": "ml-module-security-linux-v3",
- "custom_urls": [
- {
- "url_name": "Host Details by process name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Host Details by user name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by process name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by user name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- }
- ],
- "security_app_display_name": "Unusual Sudo Activity"
+ "security_app_display_name": "Unusual Sudo Activity",
+ "managed": true,
+ "job_revision": 4
}
-}
\ No newline at end of file
+}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_rare_user_compiler.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_rare_user_compiler.json
index 9c8ca5316ace3..08dbbc60d02f7 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_rare_user_compiler.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_rare_user_compiler.json
@@ -1,27 +1,17 @@
{
"description": "Security: Linux - Looks for compiler activity by a user context which does not normally run compilers. This can be ad-hoc software changes or unauthorized software deployment. This can also be due to local privilege elevation via locally run exploits or malware activity.",
- "groups": [
- "security",
- "auditbeat",
- "endpoint",
- "linux",
- "process"
- ],
+ "groups": ["security", "linux"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "Detects rare user.name values.",
+ "detector_description": "Detects rare usernames.",
"function": "rare",
- "by_field_name": "user.name"
+ "by_field_name": "user.name",
+ "detector_index": 0
}
],
- "influencers": [
- "process.title",
- "host.name",
- "process.working_directory",
- "user.name"
- ]
+ "influencers": ["process.title", "host.name", "process.working_directory", "user.name"]
},
"allow_lazy_open": true,
"analysis_limits": {
@@ -31,24 +21,9 @@
"time_field": "@timestamp"
},
"custom_settings": {
- "job_tags": {
- "euid": "4018",
- "maturity": "release",
- "author": "@randomuserid/Elastic",
- "version": "3",
- "updated_date": "5/16/2022"
- },
"created_by": "ml-module-security-linux-v3",
- "custom_urls": [
- {
- "url_name": "Host Details by user name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by user name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- }
- ],
- "security_app_display_name": "Anomalous Linux Compiler Activity"
+ "security_app_display_name": "Anomalous Linux Compiler Activity",
+ "managed": true,
+ "job_revision": 4
}
-}
\ No newline at end of file
+}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_system_information_discovery.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_system_information_discovery.json
index 0202854934285..255d0347654b0 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_system_information_discovery.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_system_information_discovery.json
@@ -1,27 +1,17 @@
{
"description": "Security: Linux - Looks for commands related to system information discovery from an unusual user context. This can be due to uncommon troubleshooting activity or due to a compromised account. A compromised account may be used to engage in system information discovery to gather detailed information about system configuration and software versions. This may be a precursor to the selection of a persistence mechanism or a method of privilege elevation.",
- "groups": [
- "security",
- "auditbeat",
- "endpoint",
- "linux",
- "process"
- ],
+ "groups": ["security", "linux"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "Detects rare user.name values.",
+ "detector_description": "Detects rare usernames.",
"function": "rare",
- "by_field_name": "user.name"
+ "by_field_name": "user.name",
+ "detector_index": 0
}
],
- "influencers": [
- "process.name",
- "host.name",
- "process.args",
- "user.name"
- ]
+ "influencers": ["process.name", "host.name", "process.args", "user.name"]
},
"allow_lazy_open": true,
"analysis_limits": {
@@ -31,32 +21,9 @@
"time_field": "@timestamp"
},
"custom_settings": {
- "job_tags": {
- "euid": "4014",
- "maturity": "release",
- "author": "@randomuserid/Elastic",
- "version": "3",
- "updated_date": "5/16/2022"
- },
"created_by": "ml-module-security-linux-v3",
- "custom_urls": [
- {
- "url_name": "Host Details by process name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Host Details by user name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by process name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by user name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- }
- ],
- "security_app_display_name": "Unusual Linux System Information Discovery Activity"
+ "security_app_display_name": "Unusual Linux System Information Discovery Activity",
+ "managed": true,
+ "job_revision": 4
}
-}
\ No newline at end of file
+}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_system_process_discovery.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_system_process_discovery.json
index 23e6e607ccf08..03e57ce2237af 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_system_process_discovery.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_system_process_discovery.json
@@ -1,27 +1,17 @@
{
"description": "Security: Linux - Looks for commands related to system process discovery from an unusual user context. This can be due to uncommon troubleshooting activity or due to a compromised account. A compromised account may be used to engage in system process discovery to increase their understanding of software applications running on a target host or network. This may be a precursor to the selection of a persistence mechanism or a method of privilege elevation.",
- "groups": [
- "security",
- "auditbeat",
- "endpoint",
- "linux",
- "process"
- ],
+ "groups": ["security", "linux"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "Detects rare user.name values.",
+ "detector_description": "Detects rare usernames.",
"function": "rare",
- "by_field_name": "user.name"
+ "by_field_name": "user.name",
+ "detector_index": 0
}
],
- "influencers": [
- "process.name",
- "host.name",
- "process.args",
- "user.name"
- ]
+ "influencers": ["process.name", "host.name", "process.args", "user.name"]
},
"allow_lazy_open": true,
"analysis_limits": {
@@ -31,32 +21,9 @@
"time_field": "@timestamp"
},
"custom_settings": {
- "job_tags": {
- "euid": "4015",
- "maturity": "release",
- "author": "@randomuserid/Elastic",
- "version": "3",
- "updated_date": "5/16/2022"
- },
"created_by": "ml-module-security-linux-v3",
- "custom_urls": [
- {
- "url_name": "Host Details by process name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Host Details by user name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by process name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by user name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- }
- ],
- "security_app_display_name": "Unusual Linux Process Discovery Activity"
+ "security_app_display_name": "Unusual Linux Process Discovery Activity",
+ "managed": true,
+ "job_revision": 4
}
-}
\ No newline at end of file
+}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_system_user_discovery.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_system_user_discovery.json
index 8659e7a8f1f91..2b1c4dc595777 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_system_user_discovery.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_linux_system_user_discovery.json
@@ -1,27 +1,17 @@
{
"description": "Security: Linux - Looks for commands related to system user or owner discovery from an unusual user context. This can be due to uncommon troubleshooting activity or due to a compromised account. A compromised account may be used to engage in system owner or user discovery to identify currently active or primary users of a system. This may be a precursor to additional discovery, credential dumping, or privilege elevation activity.",
- "groups": [
- "security",
- "auditbeat",
- "endpoint",
- "linux",
- "process"
- ],
+ "groups": ["security", "linux"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "Detects rare user.name values.",
+ "detector_description": "Detects rare usernames.",
"function": "rare",
- "by_field_name": "user.name"
+ "by_field_name": "user.name",
+ "detector_index": 0
}
],
- "influencers": [
- "process.name",
- "host.name",
- "process.args",
- "user.name"
- ]
+ "influencers": ["process.name", "host.name", "process.args", "user.name"]
},
"allow_lazy_open": true,
"analysis_limits": {
@@ -31,32 +21,9 @@
"time_field": "@timestamp"
},
"custom_settings": {
- "job_tags": {
- "euid": "4016",
- "maturity": "release",
- "author": "@randomuserid/Elastic",
- "version": "3",
- "updated_date": "5/16/2022"
- },
"created_by": "ml-module-security-linux-v3",
- "custom_urls": [
- {
- "url_name": "Host Details by process name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Host Details by user name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by process name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by user name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- }
- ],
- "security_app_display_name": "Unusual Linux System Owner or User Discovery Activity"
+ "security_app_display_name": "Unusual Linux User Discovery Activity",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_rare_process_by_host_linux.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_rare_process_by_host_linux.json
index a072007a0f13c..ce0e7f413f676 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_rare_process_by_host_linux.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_linux/ml/v3_rare_process_by_host_linux.json
@@ -1,65 +1,31 @@
{
"description": "Security: Linux - Looks for processes that are unusual to a particular Linux host. Such unusual processes may indicate unauthorized software, malware, or persistence mechanisms.",
- "groups": [
- "auditbeat",
- "endpoint",
- "linux",
- "process",
- "security"
- ],
+ "groups": ["linux", "security"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "For each host.name, detects rare process.name values.",
+ "detector_description": "Detects rare processes for a host.",
"function": "rare",
"by_field_name": "process.name",
"partition_field_name": "host.name",
"detector_index": 0
}
],
- "influencers": [
- "host.name",
- "process.name",
- "user.name"
- ]
+ "influencers": ["host.name", "process.name", "user.name"]
},
"allow_lazy_open": true,
"analysis_limits": {
- "model_memory_limit": "256mb",
- "categorization_examples_limit": 4
+ "model_memory_limit": "256mb"
},
"data_description": {
"time_field": "@timestamp",
"time_format": "epoch_ms"
},
"custom_settings": {
- "job_tags": {
- "euid": "4002",
- "maturity": "release",
- "author": "@randomuserid/Elastic",
- "version": "3",
- "updated_date": "5/16/2022"
- },
"created_by": "ml-module-security-linux-v3",
- "custom_urls": [
- {
- "url_name": "Host Details by process name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Host Details by user name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by process name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by user name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- }
- ],
- "security_app_display_name": "Unusual Process for a Linux Host"
+ "security_app_display_name": "Unusual Process for a Linux Host",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/manifest.json
index bed522d4e954a..edf6c66a213bd 100755
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/manifest.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/manifest.json
@@ -2,7 +2,7 @@
"id": "security_network",
"title": "Security: Network",
"description": "Detect anomalous network activity in your ECS-compatible network logs.",
- "type": "network data",
+ "type": "Network data",
"logoFile": "logo.json",
"defaultIndexPattern": "logs-*,filebeat-*,packetbeat-*",
"query": {
@@ -14,7 +14,7 @@
}
}
],
- "must_not": { "terms": { "_tier": [ "data_frozen", "data_cold" ] } }
+ "must_not": { "terms": { "_tier": ["data_frozen", "data_cold"] } }
}
},
"jobs": [
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_by_destination_country.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_by_destination_country.json
index 4479fe8f8c662..b19a3f0e27812 100755
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_by_destination_country.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_by_destination_country.json
@@ -1,14 +1,11 @@
{
"description": "Security: Network - Looks for an unusually large spike in network activity to one destination country in the network logs. This could be due to unusually large amounts of reconnaissance or enumeration traffic. Data exfiltration activity may also produce such a surge in traffic to a destination country which does not normally appear in network traffic or business work-flows. Malware instances and persistence mechanisms may communicate with command-and-control (C2) infrastructure in their country of origin, which may be an unusual destination country for the source network.",
- "groups": [
- "security",
- "network"
- ],
+ "groups": ["security", "network"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "high_non_zero_count by \"destination.geo.country_name\"",
+ "detector_description": "Detects high count by country.",
"function": "high_non_zero_count",
"by_field_name": "destination.geo.country_name",
"detector_index": 0
@@ -19,8 +16,7 @@
"destination.as.organization.name",
"source.ip",
"destination.ip"
- ],
- "model_prune_window": "30d"
+ ]
},
"allow_lazy_open": true,
"analysis_limits": {
@@ -31,6 +27,8 @@
},
"custom_settings": {
"created_by": "ml-module-security-network",
- "security_app_display_name": "Spike in Network Traffic to a Country"
+ "security_app_display_name": "Spike in Network Traffic to a Country",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_network_denies.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_network_denies.json
index 984bfea22fa2d..1477e951d3ce9 100755
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_network_denies.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_network_denies.json
@@ -1,14 +1,11 @@
{
"description": "Security: Network - Looks for an unusually large spike in network traffic that was denied by network ACLs or firewall rules. Such a burst of denied traffic is usually either 1) a misconfigured application or firewall or 2) suspicious or malicious activity. Unsuccessful attempts at network transit, in order to connect to command-and-control (C2), or engage in data exfiltration, may produce a burst of failed connections. This could also be due to unusually large amounts of reconnaissance or enumeration traffic. Denial-of-service attacks or traffic floods may also produce such a surge in traffic.",
- "groups": [
- "security",
- "network"
- ],
+ "groups": ["security", "network"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "high_count",
+ "detector_description": "Detects high count of network denies.",
"function": "high_count",
"detector_index": 0
}
@@ -18,8 +15,7 @@
"destination.as.organization.name",
"source.ip",
"destination.port"
- ],
- "model_prune_window": "30d"
+ ]
},
"allow_lazy_open": true,
"analysis_limits": {
@@ -30,6 +26,8 @@
},
"custom_settings": {
"created_by": "ml-module-security-network",
- "security_app_display_name": "Spike in Firewall Denies"
+ "security_app_display_name": "Spike in Firewall Denies",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_network_events.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_network_events.json
index ba740d581a27e..81b516204fbc1 100755
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_network_events.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_network_events.json
@@ -1,14 +1,11 @@
{
"description": "Security: Network - Looks for an unusually large spike in network traffic. Such a burst of traffic, if not caused by a surge in business activity, can be due to suspicious or malicious activity. Large-scale data exfiltration may produce a burst of network traffic; this could also be due to unusually large amounts of reconnaissance or enumeration traffic. Denial-of-service attacks or traffic floods may also produce such a surge in traffic.",
- "groups": [
- "security",
- "network"
- ],
+ "groups": ["security", "network"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "high_count",
+ "detector_description": "Detects high count of network events.",
"function": "high_count",
"detector_index": 0
}
@@ -18,8 +15,7 @@
"destination.as.organization.name",
"source.ip",
"destination.ip"
- ],
- "model_prune_window": "30d"
+ ]
},
"allow_lazy_open": true,
"analysis_limits": {
@@ -30,6 +26,8 @@
},
"custom_settings": {
"created_by": "ml-module-security-network",
- "security_app_display_name": "Spike in Network Traffic"
+ "security_app_display_name": "Spike in Network Traffic",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/rare_destination_country.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/rare_destination_country.json
index 123b802c475fb..4b8799d65b746 100755
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/rare_destination_country.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/rare_destination_country.json
@@ -1,14 +1,11 @@
{
"description": "Security: Network - looks for an unusual destination country name in the network logs. This can be due to initial access, persistence, command-and-control, or exfiltration activity. For example, when a user clicks on a link in a phishing email or opens a malicious document, a request may be sent to download and run a payload from a server in a country which does not normally appear in network traffic or business work-flows. Malware instances and persistence mechanisms may communicate with command-and-control (C2) infrastructure in their country of origin, which may be an unusual destination country for the source network.",
- "groups": [
- "security",
- "network"
- ],
+ "groups": ["security", "network"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "rare by \"destination.geo.country_name\"",
+ "detector_description": "Detects rare country names.",
"function": "rare",
"by_field_name": "destination.geo.country_name",
"detector_index": 0
@@ -30,6 +27,8 @@
},
"custom_settings": {
"created_by": "ml-module-security-network",
- "security_app_display_name": "Network Traffic to Rare Destination Country"
+ "security_app_display_name": "Network Traffic to Rare Destination Country",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/manifest.json
index f7a65d0137f26..799363b8fbac1 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/manifest.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/manifest.json
@@ -1,16 +1,14 @@
{
"id": "security_packetbeat",
"title": "Security: Packetbeat",
- "description": "Detect suspicious network activity in Packetbeat data.",
+ "description": "Detect suspicious activity in Packetbeat data.",
"type": "Packetbeat data",
"logoFile": "logo.json",
- "defaultIndexPattern": "packetbeat-*",
+ "defaultIndexPattern": "packetbeat-*,logs-*",
"query": {
"bool": {
- "filter": [
- {"term": {"agent.type": "packetbeat"}}
- ],
- "must_not": { "terms": { "_tier": [ "data_frozen", "data_cold" ] } }
+ "filter": [{ "term": { "agent.type": "packetbeat" } }],
+ "must_not": { "terms": { "_tier": ["data_frozen", "data_cold"] } }
}
},
"jobs": [
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/datafeed_packetbeat_dns_tunneling.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/datafeed_packetbeat_dns_tunneling.json
index 449c8af238b56..334435732a07e 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/datafeed_packetbeat_dns_tunneling.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/datafeed_packetbeat_dns_tunneling.json
@@ -1,18 +1,16 @@
{
"job_id": "JOB_ID",
- "indices": [
- "INDEX_PATTERN_NAME"
- ],
+ "indices": ["INDEX_PATTERN_NAME"],
"max_empty_searches": 10,
"query": {
"bool": {
- "filter": [
- {"term": {"event.dataset": "dns"}},
- {"term": {"agent.type": "packetbeat"}}
+ "filter": [{ "term": { "agent.type": "packetbeat" } }],
+ "should": [
+ { "term": { "event.dataset": "dns" } },
+ { "term": { "event.dataset": "network_traffic.dns" } }
],
- "must_not": [
- {"bool": {"filter": {"term": {"destination.ip": "169.254.169.254"}}}}
- ]
+ "minimum_should_match": 1,
+ "must_not": [{ "bool": { "filter": { "term": { "destination.ip": "169.254.169.254" } } } }]
}
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/datafeed_packetbeat_rare_dns_question.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/datafeed_packetbeat_rare_dns_question.json
index 3a4055eb55ba0..fe87d86ee352f 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/datafeed_packetbeat_rare_dns_question.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/datafeed_packetbeat_rare_dns_question.json
@@ -1,18 +1,16 @@
{
"job_id": "JOB_ID",
- "indices": [
- "INDEX_PATTERN_NAME"
- ],
+ "indices": ["INDEX_PATTERN_NAME"],
"max_empty_searches": 10,
"query": {
"bool": {
- "filter": [
- {"term": {"event.dataset": "dns"}},
- {"term": {"agent.type": "packetbeat"}}
+ "filter": [{ "term": { "agent.type": "packetbeat" } }],
+ "should": [
+ { "term": { "event.dataset": "dns" } },
+ { "term": { "event.dataset": "network_traffic.dns" } }
],
- "must_not": [
- {"bool": {"filter": {"term": {"dns.question.type": "PTR"}}}}
- ]
+ "minimum_should_match": 1,
+ "must_not": [{ "bool": { "filter": { "term": { "dns.question.type": "PTR" } } } }]
}
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/datafeed_packetbeat_rare_user_agent.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/datafeed_packetbeat_rare_user_agent.json
index 5986c326ea80f..79a297595d8d7 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/datafeed_packetbeat_rare_user_agent.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/datafeed_packetbeat_rare_user_agent.json
@@ -1,18 +1,16 @@
{
"job_id": "JOB_ID",
- "indices": [
- "INDEX_PATTERN_NAME"
- ],
+ "indices": ["INDEX_PATTERN_NAME"],
"max_empty_searches": 10,
"query": {
"bool": {
- "filter": [
- {"term": {"event.dataset": "http"}},
- {"term": {"agent.type": "packetbeat"}}
+ "filter": [{ "term": { "agent.type": "packetbeat" } }],
+ "should": [
+ { "term": { "event.dataset": "http" } },
+ { "term": { "event.dataset": "network_traffic.http" } }
],
- "must_not": [
- {"wildcard": {"user_agent.original": {"value": "Mozilla*"}}}
- ]
+ "minimum_should_match": 1,
+ "must_not": [{ "wildcard": { "user_agent.original": { "value": "Mozilla*" } } }]
}
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/packetbeat_dns_tunneling.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/packetbeat_dns_tunneling.json
index 313bd8e1bea39..54b8ddf2e7a14 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/packetbeat_dns_tunneling.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/packetbeat_dns_tunneling.json
@@ -1,23 +1,17 @@
{
"description": "Security: Packetbeat - Looks for unusual DNS activity that could indicate command-and-control or data exfiltration activity.",
- "groups": [
- "security",
- "packetbeat",
- "dns"
- ],
+ "groups": ["security", "packetbeat", "dns"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "high_info_content(\"dns.question.name\") over tld",
+ "detector_description": "Detects high info content of DNS questions over a population of TLDs.",
"function": "high_info_content",
"field_name": "dns.question.name",
"over_field_name": "dns.question.etld_plus_one",
"custom_rules": [
{
- "actions": [
- "skip_result"
- ],
+ "actions": ["skip_result"],
"conditions": [
{
"applies_to": "actual",
@@ -29,12 +23,7 @@
]
}
],
- "influencers": [
- "destination.ip",
- "host.name",
- "dns.question.etld_plus_one"
- ],
- "model_prune_window": "30d"
+ "influencers": ["destination.ip", "host.name", "dns.question.etld_plus_one"]
},
"allow_lazy_open": true,
"analysis_limits": {
@@ -45,12 +34,8 @@
},
"custom_settings": {
"created_by": "ml-module-security-packetbeat",
- "custom_urls": [
- {
- "url_name": "Host Details",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- }
- ],
- "security_app_display_name": "DNS Tunneling"
+ "security_app_display_name": "DNS Tunneling",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/packetbeat_rare_dns_question.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/packetbeat_rare_dns_question.json
index 36c8b3acd722e..049d4e3babd23 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/packetbeat_rare_dns_question.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/packetbeat_rare_dns_question.json
@@ -1,22 +1,16 @@
{
"description": "Security: Packetbeat - Looks for unusual DNS activity that could indicate command-and-control activity.",
- "groups": [
- "security",
- "packetbeat",
- "dns"
- ],
+ "groups": ["security", "packetbeat", "dns"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "rare by \"dns.question.name\"",
+ "detector_description": "Detects rare DNS question names.",
"function": "rare",
"by_field_name": "dns.question.name"
}
],
- "influencers": [
- "host.name"
- ]
+ "influencers": ["host.name"]
},
"allow_lazy_open": true,
"analysis_limits": {
@@ -27,12 +21,8 @@
},
"custom_settings": {
"created_by": "ml-module-security-packetbeat",
- "custom_urls": [
- {
- "url_name": "Host Details",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- }
- ],
- "security_app_display_name": "Unusual DNS Activity"
+ "security_app_display_name": "Unusual DNS Activity",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/packetbeat_rare_server_domain.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/packetbeat_rare_server_domain.json
index 3f3c137e8fd34..d8df5c4986b99 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/packetbeat_rare_server_domain.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/packetbeat_rare_server_domain.json
@@ -1,24 +1,16 @@
{
"description": "Security: Packetbeat - Looks for unusual HTTP or TLS destination domain activity that could indicate execution, persistence, command-and-control or data exfiltration activity.",
- "groups": [
- "security",
- "packetbeat",
- "web"
- ],
+ "groups": ["security", "packetbeat"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "rare by \"server.domain\"",
+ "detector_description": "Detects rare server domains.",
"function": "rare",
"by_field_name": "server.domain"
}
],
- "influencers": [
- "host.name",
- "destination.ip",
- "source.ip"
- ]
+ "influencers": ["host.name", "destination.ip", "source.ip"]
},
"allow_lazy_open": true,
"analysis_limits": {
@@ -29,12 +21,8 @@
},
"custom_settings": {
"created_by": "ml-module-security-packetbeat",
- "custom_urls": [
- {
- "url_name": "Host Details",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- }
- ],
- "security_app_display_name": "Unusual Network Destination Domain Name"
+ "security_app_display_name": "Unusual Network Destination Domain Name",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/packetbeat_rare_urls.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/packetbeat_rare_urls.json
index afa430bd835f2..055204dd1c376 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/packetbeat_rare_urls.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/packetbeat_rare_urls.json
@@ -1,23 +1,16 @@
{
"description": "Security: Packetbeat - Looks for unusual web browsing URL activity that could indicate execution, persistence, command-and-control or data exfiltration activity.",
- "groups": [
- "security",
- "packetbeat",
- "web"
- ],
+ "groups": ["security", "packetbeat"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "rare by \"url.full\"",
+ "detector_description": "Detects rare URLs.",
"function": "rare",
"by_field_name": "url.full"
}
],
- "influencers": [
- "host.name",
- "destination.ip"
- ]
+ "influencers": ["host.name", "destination.ip"]
},
"allow_lazy_open": true,
"analysis_limits": {
@@ -28,12 +21,8 @@
},
"custom_settings": {
"created_by": "ml-module-security-packetbeat",
- "custom_urls": [
- {
- "url_name": "Host Details",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- }
- ],
- "security_app_display_name": "Unusual Web Request"
+ "security_app_display_name": "Unusual Web Request",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/packetbeat_rare_user_agent.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/packetbeat_rare_user_agent.json
index bb2d524b41c1f..c947e4f1d509b 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/packetbeat_rare_user_agent.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_packetbeat/ml/packetbeat_rare_user_agent.json
@@ -1,23 +1,16 @@
{
"description": "Security: Packetbeat - Looks for unusual HTTP user agent activity that could indicate execution, persistence, command-and-control or data exfiltration activity.",
- "groups": [
- "security",
- "packetbeat",
- "web"
- ],
+ "groups": ["security", "packetbeat"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "rare by \"user_agent.original\"",
+ "detector_description": "Detects rare web user agents.",
"function": "rare",
"by_field_name": "user_agent.original"
}
],
- "influencers": [
- "host.name",
- "destination.ip"
- ]
+ "influencers": ["host.name", "destination.ip"]
},
"allow_lazy_open": true,
"analysis_limits": {
@@ -28,12 +21,8 @@
},
"custom_settings": {
"created_by": "ml-module-security-packetbeat",
- "custom_urls": [
- {
- "url_name": "Host Details",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- }
- ],
- "security_app_display_name": "Unusual Web User Agent"
+ "security_app_display_name": "Unusual Web User Agent",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_rare_process_by_host_windows.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_rare_process_by_host_windows.json
index 6b7e5dcf56f1f..38fa9e2e4e904 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_rare_process_by_host_windows.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_rare_process_by_host_windows.json
@@ -1,67 +1,30 @@
{
"description": "Security: Windows - Looks for processes that are unusual to a particular Windows host. Such unusual processes may indicate unauthorized software, malware, or persistence mechanisms.",
- "groups": [
- "endpoint",
- "event-log",
- "process",
- "security",
- "sysmon",
- "windows",
- "winlogbeat"
- ],
+ "groups": ["security", "windows"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "For each host.name, detects rare process.name values.",
+ "detector_description": "Detects rare processes per host.",
"function": "rare",
"by_field_name": "process.name",
"partition_field_name": "host.name",
"detector_index": 0
}
],
- "influencers": [
- "host.name",
- "process.name",
- "user.name"
- ]
+ "influencers": ["host.name", "process.name", "user.name"]
},
"allow_lazy_open": true,
"analysis_limits": {
- "model_memory_limit": "256mb",
- "categorization_examples_limit": 4
+ "model_memory_limit": "256mb"
},
"data_description": {
- "time_field": "@timestamp",
- "time_format": "epoch_ms"
+ "time_field": "@timestamp"
},
"custom_settings": {
- "job_tags": {
- "euid": "8001",
- "maturity": "release",
- "author": "@randomuserid/Elastic",
- "version": "3",
- "updated_date": "5/16/2022"
- },
"created_by": "ml-module-security-windows-v3",
- "custom_urls": [
- {
- "url_name": "Host Details by process name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Host Details by user name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by process name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by user name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- }
- ],
- "security_app_display_name": "Unusual Process for a Windows Host"
+ "security_app_display_name": "Unusual Process for a Windows Host",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_anomalous_network_activity.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_anomalous_network_activity.json
index 04ee9912c15e3..2e04fa91be336 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_anomalous_network_activity.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_anomalous_network_activity.json
@@ -1,66 +1,29 @@
{
"description": "Security: Windows - Looks for unusual processes using the network which could indicate command-and-control, lateral movement, persistence, or data exfiltration activity.",
- "groups": [
- "endpoint",
- "network",
- "security",
- "sysmon",
- "windows",
- "winlogbeat"
- ],
+ "groups": ["security", "windows"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "Detects rare process.name values.",
+ "detector_description": "Detects rare processes.",
"function": "rare",
"by_field_name": "process.name",
"detector_index": 0
}
],
- "influencers": [
- "host.name",
- "process.name",
- "user.name",
- "destination.ip"
- ]
+ "influencers": ["host.name", "process.name", "user.name", "destination.ip"]
},
"allow_lazy_open": true,
"analysis_limits": {
- "model_memory_limit": "64mb",
- "categorization_examples_limit": 4
+ "model_memory_limit": "64mb"
},
"data_description": {
- "time_field": "@timestamp",
- "time_format": "epoch_ms"
+ "time_field": "@timestamp"
},
"custom_settings": {
- "job_tags": {
- "euid": "8003",
- "maturity": "release",
- "author": "@randomuserid/Elastic",
- "version": "3",
- "updated_date": "5/16/2022"
- },
"created_by": "ml-module-security-windows-v3",
- "custom_urls": [
- {
- "url_name": "Host Details by process name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Host Details by user name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by process name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by user name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- }
- ],
- "security_app_display_name": "Unusual Windows Network Activity"
+ "security_app_display_name": "Unusual Windows Network Activity",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_anomalous_path_activity.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_anomalous_path_activity.json
index d5c931b3c46e8..c9f0579309c6b 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_anomalous_path_activity.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_anomalous_path_activity.json
@@ -1,65 +1,29 @@
{
"description": "Security: Windows - Looks for activity in unusual paths that may indicate execution of malware or persistence mechanisms. Windows payloads often execute from user profile paths.",
- "groups": [
- "endpoint",
- "network",
- "security",
- "sysmon",
- "windows",
- "winlogbeat"
- ],
+ "groups": ["security", "windows"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "Detects rare process.working_directory values.",
+ "detector_description": "Detects rare working directories.",
"function": "rare",
"by_field_name": "process.working_directory",
"detector_index": 0
}
],
- "influencers": [
- "host.name",
- "process.name",
- "user.name"
- ]
+ "influencers": ["host.name", "process.name", "user.name"]
},
"allow_lazy_open": true,
"analysis_limits": {
- "model_memory_limit": "256mb",
- "categorization_examples_limit": 4
+ "model_memory_limit": "256mb"
},
"data_description": {
- "time_field": "@timestamp",
- "time_format": "epoch_ms"
+ "time_field": "@timestamp"
},
"custom_settings": {
- "job_tags": {
- "euid": "8004",
- "maturity": "release",
- "author": "@randomuserid/Elastic",
- "version": "3",
- "updated_date": "5/16/2022"
- },
"created_by": "ml-module-security-windows-v3",
- "custom_urls": [
- {
- "url_name": "Host Details by process name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Host Details by user name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by process name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by user name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- }
- ],
- "security_app_display_name": "Unusual Windows Path Activity"
+ "security_app_display_name": "Unusual Windows Path Activity",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_anomalous_process_all_hosts.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_anomalous_process_all_hosts.json
index 1474763cec7b9..08baa6587f9ff 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_anomalous_process_all_hosts.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_anomalous_process_all_hosts.json
@@ -1,66 +1,29 @@
{
"description": "Security: Windows - Looks for processes that are unusual to all Windows hosts. Such unusual processes may indicate execution of unauthorized software, malware, or persistence mechanisms.",
- "groups": [
- "endpoint",
- "event-log",
- "process",
- "security",
- "sysmon",
- "windows",
- "winlogbeat"
- ],
+ "groups": ["security", "windows"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "Detects rare process.executable values.",
+ "detector_description": "Detects rare process executable values.",
"function": "rare",
- "by_field_name": "process.executable",
+ "by_field_name": "process.name",
"detector_index": 0
}
],
- "influencers": [
- "host.name",
- "process.name",
- "user.name"
- ]
+ "influencers": ["host.name", "process.name", "user.name"]
},
"allow_lazy_open": true,
"analysis_limits": {
- "model_memory_limit": "256mb",
- "categorization_examples_limit": 4
+ "model_memory_limit": "256mb"
},
"data_description": {
- "time_field": "@timestamp",
- "time_format": "epoch_ms"
+ "time_field": "@timestamp"
},
"custom_settings": {
- "job_tags": {
- "euid": "8002",
- "maturity": "release",
- "author": "@randomuserid/Elastic",
- "version": "3",
- "updated_date": "5/16/2022"
- },
"created_by": "ml-module-security-windows-v3",
- "custom_urls": [
- {
- "url_name": "Host Details by process name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Host Details by user name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by process name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by user name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- }
- ],
- "security_app_display_name": "Anomalous Process for a Windows Population"
+ "security_app_display_name": "Anomalous Process for a Windows Population",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_anomalous_process_creation.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_anomalous_process_creation.json
index 2966630fad878..1bf46c2d416a9 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_anomalous_process_creation.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_anomalous_process_creation.json
@@ -1,67 +1,30 @@
{
"description": "Security: Windows - Looks for unusual process relationships which may indicate execution of malware or persistence mechanisms.",
- "groups": [
- "endpoint",
- "event-log",
- "process",
- "security",
- "sysmon",
- "windows",
- "winlogbeat"
- ],
+ "groups": ["security", "windows"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "For each process.parent.name, detects rare process.name values.",
+ "detector_description": "Detects rare processes per parent process.",
"function": "rare",
"by_field_name": "process.name",
"partition_field_name": "process.parent.name",
"detector_index": 0
}
],
- "influencers": [
- "host.name",
- "process.name",
- "user.name"
- ]
+ "influencers": ["host.name", "process.name", "user.name"]
},
"allow_lazy_open": true,
"analysis_limits": {
- "model_memory_limit": "256mb",
- "categorization_examples_limit": 4
+ "model_memory_limit": "256mb"
},
"data_description": {
- "time_field": "@timestamp",
- "time_format": "epoch_ms"
+ "time_field": "@timestamp"
},
"custom_settings": {
- "job_tags": {
- "euid": "8005",
- "maturity": "release",
- "author": "@randomuserid/Elastic",
- "version": "3",
- "updated_date": "5/16/2022"
- },
"created_by": "ml-module-security-windows-v3",
- "custom_urls": [
- {
- "url_name": "Host Details by process name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Host Details by user name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by process name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by user name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- }
- ],
- "security_app_display_name": "Anomalous Windows Process Creation"
+ "security_app_display_name": "Anomalous Windows Process Creation",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_anomalous_script.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_anomalous_script.json
index b01641b2ef3ad..5472ad77e1b70 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_anomalous_script.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_anomalous_script.json
@@ -1,28 +1,17 @@
{
"description": "Security: Windows - Looks for unusual powershell scripts that may indicate execution of malware, or persistence mechanisms.",
- "groups": [
- "endpoint",
- "event-log",
- "process",
- "windows",
- "winlogbeat",
- "powershell",
- "security"
- ],
+ "groups": ["windows", "powershell", "security"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "Detects high information content in powershell.file.script_block_text values.",
+ "detector_description": "Detects high information content in powershell scripts.",
"function": "high_info_content",
- "field_name": "powershell.file.script_block_text"
+ "field_name": "powershell.file.script_block_text",
+ "detector_index": 0
}
],
- "influencers": [
- "host.name",
- "user.name",
- "file.path"
- ]
+ "influencers": ["host.name", "user.name", "file.path"]
},
"allow_lazy_open": true,
"analysis_limits": {
@@ -32,24 +21,9 @@
"time_field": "@timestamp"
},
"custom_settings": {
- "job_tags": {
- "euid": "8006",
- "maturity": "release",
- "author": "@randomuserid/Elastic",
- "version": "3",
- "updated_date": "5/16/2022"
- },
"created_by": "ml-module-security-windows-v3",
- "custom_urls": [
- {
- "url_name": "Host Details by user name",
- "url_value": "siem#/ml-hosts/$host.name$?_g=()&kqlQuery=(filterQuery:(expression:'user.name%20:%20%22$user.name$%22',kind:kuery),queryLocation:hosts.details,type:details)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by user name",
- "url_value": "siem#/ml-hosts?_g=()&kqlQuery=(filterQuery:(expression:'user.name%20:%20%22$user.name$%22',kind:kuery),queryLocation:hosts.page,type:page)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- }
- ],
- "security_app_display_name": "Suspicious Powershell Script"
+ "security_app_display_name": "Suspicious Powershell Script",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_anomalous_service.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_anomalous_service.json
index 9716c8365e317..b2530538a9263 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_anomalous_service.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_anomalous_service.json
@@ -1,27 +1,17 @@
{
- "groups": [
- "endpoint",
- "event-log",
- "process",
- "security",
- "sysmon",
- "windows",
- "winlogbeat"
- ],
+ "groups": ["security", "windows"],
"description": "Security: Windows - Looks for rare and unusual Windows service names which may indicate execution of unauthorized services, malware, or persistence mechanisms.",
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "Detects rare winlog.event_data.ServiceName values.",
+ "detector_description": "Detects rare service names.",
"function": "rare",
- "by_field_name": "winlog.event_data.ServiceName"
+ "by_field_name": "winlog.event_data.ServiceName",
+ "detector_index": 0
}
],
- "influencers": [
- "host.name",
- "winlog.event_data.ServiceName"
- ]
+ "influencers": ["host.name", "winlog.event_data.ServiceName"]
},
"allow_lazy_open": true,
"analysis_limits": {
@@ -31,20 +21,9 @@
"time_field": "@timestamp"
},
"custom_settings": {
- "job_tags": {
- "euid": "8007",
- "maturity": "release",
- "author": "@randomuserid/Elastic",
- "version": "3",
- "updated_date": "5/16/2022"
- },
"created_by": "ml-module-security-windows-v3",
- "custom_urls": [
- {
- "url_name": "Host Details",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- }
- ],
- "security_app_display_name": "Unusual Windows Service"
+ "security_app_display_name": "Unusual Windows Service",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_anomalous_user_name.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_anomalous_user_name.json
index eda4b768b5308..659e58cfdba32 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_anomalous_user_name.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_anomalous_user_name.json
@@ -1,66 +1,29 @@
{
"description": "Security: Windows - Rare and unusual users that are not normally active may indicate unauthorized changes or activity by an unauthorized user which may be credentialed access or lateral movement.",
- "groups": [
- "endpoint",
- "event-log",
- "process",
- "security",
- "sysmon",
- "windows",
- "winlogbeat"
- ],
+ "groups": ["security", "windows"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "Detects rare user.name values.",
+ "detector_description": "Detects rare usernames.",
"function": "rare",
"by_field_name": "user.name",
"detector_index": 0
}
],
- "influencers": [
- "host.name",
- "process.name",
- "user.name"
- ]
+ "influencers": ["host.name", "process.name", "user.name"]
},
"allow_lazy_open": true,
"analysis_limits": {
- "model_memory_limit": "256mb",
- "categorization_examples_limit": 4
+ "model_memory_limit": "256mb"
},
"data_description": {
- "time_field": "@timestamp",
- "time_format": "epoch_ms"
+ "time_field": "@timestamp"
},
"custom_settings": {
- "job_tags": {
- "euid": "8008",
- "maturity": "release",
- "author": "@randomuserid/Elastic",
- "version": "3",
- "updated_date": "5/16/2022"
- },
"created_by": "ml-module-security-windows-v3",
- "custom_urls": [
- {
- "url_name": "Host Details by process name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Host Details by user name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by process name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by user name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- }
- ],
- "security_app_display_name": "Unusual Windows Username"
+ "security_app_display_name": "Unusual Windows Username",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_rare_metadata_process.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_rare_metadata_process.json
index ab4fd311d6646..953a00a8fff52 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_rare_metadata_process.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_rare_metadata_process.json
@@ -1,47 +1,29 @@
{
"description": "Security: Windows - Looks for anomalous access to the metadata service by an unusual process. The metadata service may be targeted in order to harvest credentials or user data scripts containing secrets.",
- "groups": [
- "security",
- "endpoint",
- "process",
- "sysmon",
- "windows",
- "winlogbeat"
- ],
+ "groups": ["security", "windows"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "Detects rare process.name values.",
+ "detector_description": "Detects rare process names.",
"function": "rare",
"by_field_name": "process.name",
"detector_index": 0
}
],
- "influencers": [
- "process.name",
- "host.name",
- "user.name"
- ]
+ "influencers": ["process.name", "host.name", "user.name"]
},
"allow_lazy_open": true,
"analysis_limits": {
- "model_memory_limit": "32mb",
- "categorization_examples_limit": 4
+ "model_memory_limit": "32mb"
},
"data_description": {
- "time_field": "@timestamp",
- "time_format": "epoch_ms"
+ "time_field": "@timestamp"
},
"custom_settings": {
- "job_tags": {
- "euid": "8011",
- "maturity": "release",
- "author": "@randomuserid/Elastic",
- "version": "3",
- "updated_date": "5/16/2022"
- },
"created_by": "ml-module-security-windows-v3",
- "security_app_display_name": "Unusual Windows Process Calling the Metadata Service"
+ "security_app_display_name": "Unusual Windows Process Calling the Metadata Service",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_rare_metadata_user.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_rare_metadata_user.json
index fe8a634d49921..df55cb3d67709 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_rare_metadata_user.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_rare_metadata_user.json
@@ -1,46 +1,29 @@
{
"description": "Security: Windows - Looks for anomalous access to the metadata service by an unusual user. The metadata service may be targeted in order to harvest credentials or user data scripts containing secrets.",
- "groups": [
- "endpoint",
- "process",
- "security",
- "sysmon",
- "windows",
- "winlogbeat"
- ],
+ "groups": ["security", "windows"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "Detects rare user.name values.",
+ "detector_description": "Detects rare usernames.",
"function": "rare",
"by_field_name": "user.name",
"detector_index": 0
}
],
- "influencers": [
- "host.name",
- "user.name"
- ]
+ "influencers": ["host.name", "user.name"]
},
"allow_lazy_open": true,
"analysis_limits": {
- "model_memory_limit": "32mb",
- "categorization_examples_limit": 4
+ "model_memory_limit": "32mb"
},
"data_description": {
- "time_field": "@timestamp",
- "time_format": "epoch_ms"
+ "time_field": "@timestamp"
},
"custom_settings": {
- "job_tags": {
- "euid": "8012",
- "maturity": "release",
- "author": "@randomuserid/Elastic",
- "version": "3",
- "updated_date": "5/16/2022"
- },
"created_by": "ml-module-security-windows-v3",
- "security_app_display_name": "Unusual Windows User Calling the Metadata Service"
+ "security_app_display_name": "Unusual Windows User Calling the Metadata Service",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_rare_user_runas_event.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_rare_user_runas_event.json
index b95aa1144f440..87d9d4b172f63 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_rare_user_runas_event.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_rare_user_runas_event.json
@@ -1,27 +1,16 @@
{
"description": "Security: Windows - Unusual user context switches can be due to privilege escalation.",
- "groups": [
- "endpoint",
- "event-log",
- "security",
- "windows",
- "winlogbeat",
- "authentication"
- ],
+ "groups": ["security", "windows", "authentication"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "Detects rare user.name values.",
+ "detector_description": "Detects rare usernames.",
"function": "rare",
"by_field_name": "user.name"
}
],
- "influencers": [
- "host.name",
- "process.name",
- "user.name"
- ]
+ "influencers": ["host.name", "process.name", "user.name"]
},
"allow_lazy_open": true,
"analysis_limits": {
@@ -31,32 +20,9 @@
"time_field": "@timestamp"
},
"custom_settings": {
- "job_tags": {
- "euid": "8009",
- "maturity": "release",
- "author": "@randomuserid/Elastic",
- "version": "3",
- "updated_date": "5/16/2022"
- },
"created_by": "ml-module-security-windows-v3",
- "custom_urls": [
- {
- "url_name": "Host Details by process name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Host Details by user name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by process name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by user name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- }
- ],
- "security_app_display_name": "Unusual Windows User Privilege Elevation Activity"
+ "security_app_display_name": "Unusual Windows User Privilege Elevation Activity",
+ "managed": true,
+ "job_revision": 4
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_rare_user_type10_remote_login.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_rare_user_type10_remote_login.json
index a6ec19401190f..e118f761453be 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_rare_user_type10_remote_login.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_windows/ml/v3_windows_rare_user_type10_remote_login.json
@@ -1,27 +1,16 @@
{
"description": "Security: Windows - Unusual RDP (remote desktop protocol) user logins can indicate account takeover or credentialed access.",
- "groups": [
- "endpoint",
- "event-log",
- "security",
- "windows",
- "winlogbeat",
- "authentication"
- ],
+ "groups": ["security", "windows", "authentication"],
"analysis_config": {
"bucket_span": "15m",
"detectors": [
{
- "detector_description": "Detects rare user.name values.",
+ "detector_description": "Detects rare usernames.",
"function": "rare",
"by_field_name": "user.name"
}
],
- "influencers": [
- "host.name",
- "process.name",
- "user.name"
- ]
+ "influencers": ["host.name", "process.name", "user.name"]
},
"allow_lazy_open": true,
"analysis_limits": {
@@ -31,32 +20,9 @@
"time_field": "@timestamp"
},
"custom_settings": {
- "job_tags": {
- "euid": "8013",
- "maturity": "release",
- "author": "@randomuserid/Elastic",
- "version": "3",
- "updated_date": "5/16/2022"
- },
"created_by": "ml-module-security-windows-v3",
- "custom_urls": [
- {
- "url_name": "Host Details by process name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Host Details by user name",
- "url_value": "security/hosts/ml-hosts/$host.name$?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by process name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'process.name%20:%20%22$process.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- },
- {
- "url_name": "Hosts Overview by user name",
- "url_value": "security/hosts/ml-hosts?_g=()&query=(query:'user.name%20:%20%22$user.name$%22',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')),timeline:(linkTo:!(global),timerange:(from:'$earliest$',kind:absolute,to:'$latest$')))"
- }
- ],
- "security_app_display_name": "Unusual Windows Remote User"
+ "security_app_display_name": "Unusual Windows Remote User",
+ "managed": true,
+ "job_revision": 4
}
}
From 069324a823b2f7e2306c68ab81f913e9fd80472b Mon Sep 17 00:00:00 2001
From: Navarone Feekery <13634519+navarone-feekery@users.noreply.github.com>
Date: Mon, 24 Apr 2023 15:59:36 +0200
Subject: [PATCH 08/36] [Enterprise Search] Use caching for filtered config
fields (#155608)
Moves the configurable fields filtering to the logic file so it can make
use of caching.
---
.../connector_configuration_form.tsx | 28 +-
.../connector_configuration_logic.test.ts | 348 +++++++++++++++++-
.../connector_configuration_logic.ts | 29 +-
3 files changed, 376 insertions(+), 29 deletions(-)
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_form.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_form.tsx
index 7627c5c869469..2c40eb0beafa4 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_form.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_form.tsx
@@ -24,16 +24,12 @@ import {
import { i18n } from '@kbn/i18n';
import { Status } from '../../../../../../common/types/api';
-import { DependencyLookup, DisplayType } from '../../../../../../common/types/connectors';
+import { DisplayType } from '../../../../../../common/types/connectors';
import { ConnectorConfigurationApiLogic } from '../../../api/connector/update_connector_configuration_api_logic';
import { ConnectorConfigurationField } from './connector_configuration_field';
-import {
- ConfigEntry,
- ConnectorConfigurationLogic,
- dependenciesSatisfied,
-} from './connector_configuration_logic';
+import { ConnectorConfigurationLogic } from './connector_configuration_logic';
export const ConnectorConfigurationForm = () => {
const { status } = useValues(ConnectorConfigurationApiLogic);
@@ -41,20 +37,6 @@ export const ConnectorConfigurationForm = () => {
const { localConfigView } = useValues(ConnectorConfigurationLogic);
const { saveConfig, setIsEditing } = useActions(ConnectorConfigurationLogic);
- const dependencyLookup: DependencyLookup = localConfigView.reduce(
- (prev: Record
, configEntry: ConfigEntry) => ({
- ...prev,
- [configEntry.key]: configEntry.value,
- }),
- {}
- );
-
- const filteredConfigView = localConfigView.filter(
- (configEntry) =>
- configEntry.ui_restrictions.length <= 0 &&
- dependenciesSatisfied(configEntry.depends_on, dependencyLookup)
- );
-
return (
{
@@ -63,7 +45,7 @@ export const ConnectorConfigurationForm = () => {
}}
component="form"
>
- {filteredConfigView.map((configEntry, index) => {
+ {localConfigView.map((configEntry, index) => {
const {
default_value: defaultValue,
depends_on: dependencies,
@@ -94,8 +76,8 @@ export const ConnectorConfigurationForm = () => {
if (dependencies.length > 0) {
// dynamic spacing without CSS
- const previousField = filteredConfigView[index - 1];
- const nextField = filteredConfigView[index + 1];
+ const previousField = localConfigView[index - 1];
+ const nextField = localConfigView[index + 1];
const topSpacing =
!previousField || previousField.depends_on.length <= 0 ? : <>>;
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.test.ts
index 64e7d1af9c999..f87d73b882ecd 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.test.ts
@@ -154,7 +154,7 @@ describe('ConnectorConfigurationLogic', () => {
});
});
describe('setLocalConfigEntry', () => {
- it('should set local config entry and sort keys', () => {
+ it('should set local config entry, and sort and filter keys', () => {
ConnectorConfigurationLogic.actions.setConfigState({
bar: {
default_value: '',
@@ -182,6 +182,77 @@ describe('ConnectorConfigurationLogic', () => {
ui_restrictions: [],
value: 'fourthBar',
},
+ restricted: {
+ default_value: '',
+ depends_on: [],
+ display: DisplayType.TEXTBOX,
+ label: 'Restricted',
+ options: [],
+ order: 3,
+ required: false,
+ sensitive: true,
+ tooltip: '',
+ ui_restrictions: ['advanced'],
+ value: 'I am restricted',
+ },
+ shownDependent1: {
+ default_value: '',
+ depends_on: [{ field: 'bar', value: 'foofoo' }],
+ display: DisplayType.TEXTBOX,
+ label: 'Shown Dependent',
+ options: [],
+ order: 4,
+ required: false,
+ sensitive: true,
+ tooltip: '',
+ ui_restrictions: [],
+ value: 'I should appear (one dependency)',
+ },
+ shownDependent2: {
+ default_value: '',
+ depends_on: [
+ { field: 'bar', value: 'foofoo' },
+ { field: 'password', value: 'fourthBar' },
+ ],
+ display: DisplayType.TEXTBOX,
+ label: 'Shown Dependent 1',
+ options: [],
+ order: 5,
+ required: false,
+ sensitive: true,
+ tooltip: '',
+ ui_restrictions: [],
+ value: 'I should appear (multiple dependencies)',
+ },
+ hiddenDependent1: {
+ default_value: '',
+ depends_on: [{ field: 'bar', value: 'fafa' }],
+ display: DisplayType.TEXTBOX,
+ label: 'Shown Dependent 2',
+ options: [],
+ order: 6,
+ required: false,
+ sensitive: true,
+ tooltip: '',
+ ui_restrictions: [],
+ value: 'I should hide (one dependency)',
+ },
+ hiddenDependent2: {
+ default_value: '',
+ depends_on: [
+ { field: 'bar', value: 'fafa' },
+ { field: 'password', value: 'fourthBar' },
+ ],
+ display: DisplayType.TEXTBOX,
+ label: 'Shown Dependent',
+ options: [],
+ order: 7,
+ required: false,
+ sensitive: true,
+ tooltip: '',
+ ui_restrictions: [],
+ value: 'I should hide (multiple dependencies)',
+ },
});
ConnectorConfigurationLogic.actions.setLocalConfigState({
bar: {
@@ -210,6 +281,77 @@ describe('ConnectorConfigurationLogic', () => {
ui_restrictions: [],
value: 'fourthBar',
},
+ restricted: {
+ default_value: '',
+ depends_on: [],
+ display: DisplayType.TEXTBOX,
+ label: 'Restricted',
+ options: [],
+ order: 3,
+ required: false,
+ sensitive: true,
+ tooltip: '',
+ ui_restrictions: ['advanced'],
+ value: 'I am restricted',
+ },
+ shownDependent1: {
+ default_value: '',
+ depends_on: [{ field: 'bar', value: 'foofoo' }],
+ display: DisplayType.TEXTBOX,
+ label: 'Shown Dependent',
+ options: [],
+ order: 4,
+ required: false,
+ sensitive: true,
+ tooltip: '',
+ ui_restrictions: [],
+ value: 'I should appear (one dependency)',
+ },
+ shownDependent2: {
+ default_value: '',
+ depends_on: [
+ { field: 'bar', value: 'foofoo' },
+ { field: 'password', value: 'fourthBar' },
+ ],
+ display: DisplayType.TEXTBOX,
+ label: 'Shown Dependent 1',
+ options: [],
+ order: 5,
+ required: false,
+ sensitive: true,
+ tooltip: '',
+ ui_restrictions: [],
+ value: 'I should appear (multiple dependencies)',
+ },
+ hiddenDependent1: {
+ default_value: '',
+ depends_on: [{ field: 'bar', value: 'fafa' }],
+ display: DisplayType.TEXTBOX,
+ label: 'Shown Dependent 2',
+ options: [],
+ order: 6,
+ required: false,
+ sensitive: true,
+ tooltip: '',
+ ui_restrictions: [],
+ value: 'I should hide (one dependency)',
+ },
+ hiddenDependent2: {
+ default_value: '',
+ depends_on: [
+ { field: 'bar', value: 'fafa' },
+ { field: 'password', value: 'fourthBar' },
+ ],
+ display: DisplayType.TEXTBOX,
+ label: 'Shown Dependent',
+ options: [],
+ order: 7,
+ required: false,
+ sensitive: true,
+ tooltip: '',
+ ui_restrictions: [],
+ value: 'I should hide (multiple dependencies)',
+ },
});
ConnectorConfigurationLogic.actions.setLocalConfigEntry({
default_value: '',
@@ -254,6 +396,77 @@ describe('ConnectorConfigurationLogic', () => {
ui_restrictions: [],
value: 'fourthBar',
},
+ restricted: {
+ default_value: '',
+ depends_on: [],
+ display: DisplayType.TEXTBOX,
+ label: 'Restricted',
+ options: [],
+ order: 3,
+ required: false,
+ sensitive: true,
+ tooltip: '',
+ ui_restrictions: ['advanced'],
+ value: 'I am restricted',
+ },
+ shownDependent1: {
+ default_value: '',
+ depends_on: [{ field: 'bar', value: 'foofoo' }],
+ display: DisplayType.TEXTBOX,
+ label: 'Shown Dependent',
+ options: [],
+ order: 4,
+ required: false,
+ sensitive: true,
+ tooltip: '',
+ ui_restrictions: [],
+ value: 'I should appear (one dependency)',
+ },
+ shownDependent2: {
+ default_value: '',
+ depends_on: [
+ { field: 'bar', value: 'foofoo' },
+ { field: 'password', value: 'fourthBar' },
+ ],
+ display: DisplayType.TEXTBOX,
+ label: 'Shown Dependent 1',
+ options: [],
+ order: 5,
+ required: false,
+ sensitive: true,
+ tooltip: '',
+ ui_restrictions: [],
+ value: 'I should appear (multiple dependencies)',
+ },
+ hiddenDependent1: {
+ default_value: '',
+ depends_on: [{ field: 'bar', value: 'fafa' }],
+ display: DisplayType.TEXTBOX,
+ label: 'Shown Dependent 2',
+ options: [],
+ order: 6,
+ required: false,
+ sensitive: true,
+ tooltip: '',
+ ui_restrictions: [],
+ value: 'I should hide (one dependency)',
+ },
+ hiddenDependent2: {
+ default_value: '',
+ depends_on: [
+ { field: 'bar', value: 'fafa' },
+ { field: 'password', value: 'fourthBar' },
+ ],
+ display: DisplayType.TEXTBOX,
+ label: 'Shown Dependent',
+ options: [],
+ order: 7,
+ required: false,
+ sensitive: true,
+ tooltip: '',
+ ui_restrictions: [],
+ value: 'I should hide (multiple dependencies)',
+ },
},
configView: [
{
@@ -284,6 +497,37 @@ describe('ConnectorConfigurationLogic', () => {
ui_restrictions: [],
value: 'fourthBar',
},
+ {
+ default_value: '',
+ depends_on: [{ field: 'bar', value: 'foofoo' }],
+ display: DisplayType.TEXTBOX,
+ key: 'shownDependent1',
+ label: 'Shown Dependent',
+ options: [],
+ order: 4,
+ required: false,
+ sensitive: true,
+ tooltip: '',
+ ui_restrictions: [],
+ value: 'I should appear (one dependency)',
+ },
+ {
+ default_value: '',
+ depends_on: [
+ { field: 'bar', value: 'foofoo' },
+ { field: 'password', value: 'fourthBar' },
+ ],
+ display: DisplayType.TEXTBOX,
+ key: 'shownDependent2',
+ label: 'Shown Dependent 1',
+ options: [],
+ order: 5,
+ required: false,
+ sensitive: true,
+ tooltip: '',
+ ui_restrictions: [],
+ value: 'I should appear (multiple dependencies)',
+ },
],
localConfigState: {
bar: {
@@ -312,6 +556,77 @@ describe('ConnectorConfigurationLogic', () => {
ui_restrictions: [],
value: 'fourthBar',
},
+ restricted: {
+ default_value: '',
+ depends_on: [],
+ display: DisplayType.TEXTBOX,
+ label: 'Restricted',
+ options: [],
+ order: 3,
+ required: false,
+ sensitive: true,
+ tooltip: '',
+ ui_restrictions: ['advanced'],
+ value: 'I am restricted',
+ },
+ shownDependent1: {
+ default_value: '',
+ depends_on: [{ field: 'bar', value: 'foofoo' }],
+ display: DisplayType.TEXTBOX,
+ label: 'Shown Dependent',
+ options: [],
+ order: 4,
+ required: false,
+ sensitive: true,
+ tooltip: '',
+ ui_restrictions: [],
+ value: 'I should appear (one dependency)',
+ },
+ shownDependent2: {
+ default_value: '',
+ depends_on: [
+ { field: 'bar', value: 'foofoo' },
+ { field: 'password', value: 'fourthBar' },
+ ],
+ display: DisplayType.TEXTBOX,
+ label: 'Shown Dependent 1',
+ options: [],
+ order: 5,
+ required: false,
+ sensitive: true,
+ tooltip: '',
+ ui_restrictions: [],
+ value: 'I should appear (multiple dependencies)',
+ },
+ hiddenDependent1: {
+ default_value: '',
+ depends_on: [{ field: 'bar', value: 'fafa' }],
+ display: DisplayType.TEXTBOX,
+ label: 'Shown Dependent 2',
+ options: [],
+ order: 6,
+ required: false,
+ sensitive: true,
+ tooltip: '',
+ ui_restrictions: [],
+ value: 'I should hide (one dependency)',
+ },
+ hiddenDependent2: {
+ default_value: '',
+ depends_on: [
+ { field: 'bar', value: 'fafa' },
+ { field: 'password', value: 'fourthBar' },
+ ],
+ display: DisplayType.TEXTBOX,
+ label: 'Shown Dependent',
+ options: [],
+ order: 7,
+ required: false,
+ sensitive: true,
+ tooltip: '',
+ ui_restrictions: [],
+ value: 'I should hide (multiple dependencies)',
+ },
},
localConfigView: [
{
@@ -342,6 +657,37 @@ describe('ConnectorConfigurationLogic', () => {
ui_restrictions: [],
value: 'fourthBar',
},
+ {
+ default_value: '',
+ depends_on: [{ field: 'bar', value: 'fafa' }],
+ display: DisplayType.TEXTBOX,
+ key: 'hiddenDependent1',
+ label: 'Shown Dependent 2',
+ options: [],
+ order: 6,
+ required: false,
+ sensitive: true,
+ tooltip: '',
+ ui_restrictions: [],
+ value: 'I should hide (one dependency)',
+ },
+ {
+ default_value: '',
+ depends_on: [
+ { field: 'bar', value: 'fafa' },
+ { field: 'password', value: 'fourthBar' },
+ ],
+ display: DisplayType.TEXTBOX,
+ key: 'hiddenDependent2',
+ label: 'Shown Dependent',
+ options: [],
+ order: 7,
+ required: false,
+ sensitive: true,
+ tooltip: '',
+ ui_restrictions: [],
+ value: 'I should hide (multiple dependencies)',
+ },
],
});
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.ts
index 84b0fd4d23fdb..861ab90079229 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.ts
@@ -72,12 +72,17 @@ export interface ConfigEntry {
/**
*
- * Sorts the connector configuration by specified order (if present)
+ * Sorts and filters the connector configuration
+ *
+ * Sorting is done by specified order (if present)
* otherwise by alphabetic order of keys
*
+ * Filtering is done on any fields with ui_restrictions
+ * or that have not had their dependencies met
+ *
*/
-function sortConnectorConfiguration(config: ConnectorConfiguration): ConfigEntry[] {
- return Object.keys(config)
+function sortAndFilterConnectorConfiguration(config: ConnectorConfiguration): ConfigEntry[] {
+ const sortedConfig = Object.keys(config)
.map(
(key) =>
({
@@ -98,6 +103,20 @@ function sortConnectorConfiguration(config: ConnectorConfiguration): ConfigEntry
}
return a.key.localeCompare(b.key);
});
+
+ const dependencyLookup: DependencyLookup = sortedConfig.reduce(
+ (prev: Record, configEntry: ConfigEntry) => ({
+ ...prev,
+ [configEntry.key]: configEntry.value,
+ }),
+ {}
+ );
+
+ return sortedConfig.filter(
+ (configEntry) =>
+ configEntry.ui_restrictions.length <= 0 &&
+ dependenciesSatisfied(configEntry.depends_on, dependencyLookup)
+ );
}
export function ensureStringType(value: string | number | boolean | null): string {
@@ -280,11 +299,11 @@ export const ConnectorConfigurationLogic = kea<
selectors: ({ selectors }) => ({
configView: [
() => [selectors.configState],
- (configState: ConnectorConfiguration) => sortConnectorConfiguration(configState),
+ (configState: ConnectorConfiguration) => sortAndFilterConnectorConfiguration(configState),
],
localConfigView: [
() => [selectors.localConfigState],
- (configState) => sortConnectorConfiguration(configState),
+ (configState) => sortAndFilterConnectorConfiguration(configState),
],
}),
});
From 54457b074a20da8017de03feb9ebfbe0fe6450d3 Mon Sep 17 00:00:00 2001
From: Carlos Crespo
Date: Mon, 24 Apr 2023 11:13:57 -0300
Subject: [PATCH 09/36] [Infrastructure UI] Plot metric charts data based on
current page items (#155249)
closes [#152186](https://github.com/elastic/kibana/issues/152186)
## Summary
This PR makes the metric charts show data for the hosts on the current
page. With this change, the charts will **only** load after the table
has finished loading its data - or after Snapshot API has responded
It also changes the current behavior of the table pagination and
sorting. Instead of relying on the `EuiInMemoryTable` the pagination and
sorting are done manually, and the EuiInMemoryTable has been replaced by
the `EuiBasicTable`.
The loading indicator has also been replaced.
Paginating and sorting:
https://user-images.githubusercontent.com/2767137/233161166-2bd719e1-7259-4ecc-96a7-50493bc6c0a3.mov
Open in lens
https://user-images.githubusercontent.com/2767137/233161134-621afd76-44b5-42ab-b58c-7f51ef944ac2.mov
### How to test
- Go to Hosts view
- Paginate and sort the table data
- Select a page size and check if the select has been stored in the
localStorage (`hostsView:pageSizeSelection` key)
---------
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../hosts/components/chart/chart_loader.tsx | 58 ++++++
.../hosts/components/chart/lens_wrapper.tsx | 87 ++++----
.../components/chart/metric_chart_wrapper.tsx | 53 ++---
.../metadata/metadata.test.tsx | 42 +---
.../hosts/components/hosts_container.tsx | 33 +--
.../metrics/hosts/components/hosts_table.tsx | 127 +++++-------
.../hosts/components/kpis/kpi_grid.tsx | 5 +-
.../metrics/hosts/components/kpis/tile.tsx | 15 +-
.../components/tabs/logs/logs_tab_content.tsx | 5 +-
.../components/tabs/metrics/metric_chart.tsx | 63 ++++--
.../public/pages/metrics/hosts/constants.ts | 3 +
.../hosts/hooks/use_after_loaded_state.ts | 26 +++
.../metrics/hosts/hooks/use_alerts_query.ts | 2 +-
.../hosts/hooks/use_hosts_table.test.ts | 192 +++++++++---------
.../metrics/hosts/hooks/use_hosts_table.tsx | 112 ++++++++--
.../hosts/hooks/use_hosts_table_url_state.ts | 94 +++++++++
.../hooks/use_table_properties_url_state.ts | 62 ------
.../infra/public/pages/metrics/hosts/utils.ts | 17 +-
.../test/functional/apps/infra/hosts_view.ts | 83 ++++++++
.../page_objects/infra_hosts_view.ts | 47 +++++
20 files changed, 716 insertions(+), 410 deletions(-)
create mode 100644 x-pack/plugins/infra/public/pages/metrics/hosts/components/chart/chart_loader.tsx
create mode 100644 x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_after_loaded_state.ts
create mode 100644 x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table_url_state.ts
delete mode 100644 x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_table_properties_url_state.ts
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/chart/chart_loader.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/chart/chart_loader.tsx
new file mode 100644
index 0000000000000..bbddb338ef73f
--- /dev/null
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/chart/chart_loader.tsx
@@ -0,0 +1,58 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { EuiFlexGroup, EuiProgress, EuiFlexItem, EuiLoadingChart, useEuiTheme } from '@elastic/eui';
+import { css } from '@emotion/react';
+import { euiStyled } from '@kbn/kibana-react-plugin/common';
+
+export const ChartLoader = ({
+ children,
+ loading,
+ style,
+ loadedOnce = false,
+ hasTitle = false,
+}: {
+ style?: React.CSSProperties;
+ children: React.ReactNode;
+ loadedOnce: boolean;
+ loading: boolean;
+ hasTitle?: boolean;
+}) => {
+ const { euiTheme } = useEuiTheme();
+ return (
+
+ {loading && (
+
+ )}
+ {loading && !loadedOnce ? (
+
+
+
+
+
+ ) : (
+ children
+ )}
+
+ );
+};
+
+const LoaderContainer = euiStyled.div`
+ position: relative;
+ border-radius: ${({ theme }) => theme.eui.euiSizeS};
+ overflow: hidden;
+ height: 100%;
+`;
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/chart/lens_wrapper.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/chart/lens_wrapper.tsx
index 9985db0751fd4..9a2472949f54c 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/chart/lens_wrapper.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/chart/lens_wrapper.tsx
@@ -4,18 +4,16 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import React, { useEffect, useState } from 'react';
+import React, { useEffect, useState, useRef } from 'react';
import { Action } from '@kbn/ui-actions-plugin/public';
import { ViewMode } from '@kbn/embeddable-plugin/public';
import { BrushTriggerEvent } from '@kbn/charts-plugin/public';
-import { EuiFlexGroup } from '@elastic/eui';
-import { EuiFlexItem } from '@elastic/eui';
-import { EuiLoadingChart } from '@elastic/eui';
import { Filter, Query, TimeRange } from '@kbn/es-query';
import { useKibanaContextForPlugin } from '../../../../../hooks/use_kibana';
import { useIntersectedOnce } from '../../../../../hooks/use_intersection_once';
import { LensAttributes } from '../../../../../common/visualizations';
+import { ChartLoader } from './chart_loader';
export interface Props {
id: string;
@@ -26,7 +24,10 @@ export interface Props {
extraActions: Action[];
lastReloadRequestTime?: number;
style?: React.CSSProperties;
+ loading?: boolean;
+ hasTitle?: boolean;
onBrushEnd?: (data: BrushTriggerEvent['data']) => void;
+ onLoad?: () => void;
}
export const LensWrapper = ({
@@ -39,12 +40,19 @@ export const LensWrapper = ({
style,
onBrushEnd,
lastReloadRequestTime,
+ loading = false,
+ hasTitle = false,
}: Props) => {
- const intersectionRef = React.useRef(null);
+ const intersectionRef = useRef(null);
+ const [loadedOnce, setLoadedOnce] = useState(false);
+
+ const [state, setState] = useState({
+ lastReloadRequestTime,
+ query,
+ filters,
+ dateRange,
+ });
- const [currentLastReloadRequestTime, setCurrentLastReloadRequestTime] = useState<
- number | undefined
- >(lastReloadRequestTime);
const {
services: { lens },
} = useKibanaContextForPlugin();
@@ -56,38 +64,49 @@ export const LensWrapper = ({
useEffect(() => {
if ((intersection?.intersectionRatio ?? 0) === 1) {
- setCurrentLastReloadRequestTime(lastReloadRequestTime);
+ setState({
+ lastReloadRequestTime,
+ query,
+ dateRange,
+ filters,
+ });
}
- }, [intersection?.intersectionRatio, lastReloadRequestTime]);
+ }, [dateRange, filters, intersection?.intersectionRatio, lastReloadRequestTime, query]);
const isReady = attributes && intersectedOnce;
return (
- {!isReady ? (
-
-
-
-
-
- ) : (
-
- )}
+
+ {isReady && (
+ {
+ if (!loadedOnce) {
+ setLoadedOnce(true);
+ }
+ }}
+ />
+ )}
+
);
};
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/chart/metric_chart_wrapper.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/chart/metric_chart_wrapper.tsx
index 9df937983ae1e..8d78906bd03e9 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/chart/metric_chart_wrapper.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/chart/metric_chart_wrapper.tsx
@@ -14,16 +14,11 @@ import {
} from '@elastic/charts';
import { EuiPanel } from '@elastic/eui';
import styled from 'styled-components';
-import { EuiLoadingChart } from '@elastic/eui';
-import { EuiFlexGroup } from '@elastic/eui';
-import { EuiFlexItem } from '@elastic/eui';
import { EuiToolTip } from '@elastic/eui';
-import { EuiProgress } from '@elastic/eui';
-import { css } from '@emotion/react';
-import { useEuiTheme } from '@elastic/eui';
import type { SnapshotNode, SnapshotNodeMetric } from '../../../../../../common/http_api';
import { createInventoryMetricFormatter } from '../../../inventory_view/lib/create_inventory_metric_formatter';
import type { SnapshotMetricType } from '../../../../../../common/inventory_models/types';
+import { ChartLoader } from './chart_loader';
type MetricType = keyof Pick;
@@ -65,7 +60,6 @@ export const MetricChartWrapper = ({
type,
...props
}: Props) => {
- const { euiTheme } = useEuiTheme();
const loadedOnce = useRef(false);
const metrics = useMemo(() => (nodes ?? [])[0]?.metrics ?? [], [nodes]);
const metricsTimeseries = useMemo(
@@ -109,39 +103,18 @@ export const MetricChartWrapper = ({
return (
-
- {loading && (
-
- )}
- {loading && !loadedOnce.current ? (
-
-
-
-
-
- ) : (
-
-
-
-
-
- )}
-
+
+
+
+
+
+
+
);
};
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/metadata/metadata.test.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/metadata/metadata.test.tsx
index 1c6320c142d7a..46392fa8609d1 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/metadata/metadata.test.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/metadata/metadata.test.tsx
@@ -32,42 +32,12 @@ const metadataProps: TabProps = {
name: 'host-1',
cloudProvider: 'gcp',
},
- rx: {
- name: 'rx',
- value: 0,
- max: 0,
- avg: 0,
- },
- tx: {
- name: 'tx',
- value: 0,
- max: 0,
- avg: 0,
- },
- memory: {
- name: 'memory',
- value: 0.5445920331099282,
- max: 0.5445920331099282,
- avg: 0.5445920331099282,
- },
- cpu: {
- name: 'cpu',
- value: 0.2000718443867342,
- max: 0.2000718443867342,
- avg: 0.2000718443867342,
- },
- diskLatency: {
- name: 'diskLatency',
- value: null,
- max: 0,
- avg: 0,
- },
- memoryTotal: {
- name: 'memoryTotal',
- value: 16777216,
- max: 16777216,
- avg: 16777216,
- },
+ rx: 0,
+ tx: 0,
+ memory: 0.5445920331099282,
+ cpu: 0.2000718443867342,
+ diskLatency: 0,
+ memoryTotal: 16777216,
},
};
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_container.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_container.tsx
index e8e8a8a8e7c4f..0c965feca8e9e 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_container.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_container.tsx
@@ -12,10 +12,11 @@ import { InfraLoadingPanel } from '../../../../components/loading';
import { useMetricsDataViewContext } from '../hooks/use_data_view';
import { UnifiedSearchBar } from './unified_search_bar';
import { HostsTable } from './hosts_table';
-import { HostsViewProvider } from '../hooks/use_hosts_view';
+import { KPIGrid } from './kpis/kpi_grid';
import { Tabs } from './tabs/tabs';
import { AlertsQueryProvider } from '../hooks/use_alerts_query';
-import { KPIGrid } from './kpis/kpi_grid';
+import { HostsViewProvider } from '../hooks/use_hosts_view';
+import { HostsTableProvider } from '../hooks/use_hosts_table';
export const HostContainer = () => {
const { dataView, loading, hasError } = useMetricsDataViewContext();
@@ -38,19 +39,21 @@ export const HostContainer = () => {
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
>
);
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx
index ca6f904ceea84..535afe8befff5 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx
@@ -5,93 +5,78 @@
* 2.0.
*/
-import React, { useCallback } from 'react';
-import { EuiInMemoryTable } from '@elastic/eui';
+import React from 'react';
+import { EuiBasicTable } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { isEqual } from 'lodash';
import { NoData } from '../../../../components/empty_states';
-import { InfraLoadingPanel } from '../../../../components/loading';
-import { useHostsTable } from '../hooks/use_hosts_table';
-import { useTableProperties } from '../hooks/use_table_properties_url_state';
+import { HostNodeRow, useHostsTableContext } from '../hooks/use_hosts_table';
import { useHostsViewContext } from '../hooks/use_hosts_view';
import { useUnifiedSearchContext } from '../hooks/use_unified_search';
import { Flyout } from './host_details_flyout/flyout';
+import { DEFAULT_PAGE_SIZE } from '../constants';
-export const HostsTable = () => {
- const { hostNodes, loading } = useHostsViewContext();
- const { onSubmit, searchCriteria } = useUnifiedSearchContext();
- const [properties, setProperties] = useTableProperties();
-
- const { columns, items, isFlyoutOpen, closeFlyout, clickedItem } = useHostsTable(hostNodes, {
- time: searchCriteria.dateRange,
- });
-
- const noData = items.length === 0;
-
- const onTableChange = useCallback(
- ({ page = {}, sort = {} }) => {
- const { index: pageIndex, size: pageSize } = page;
- const { field, direction } = sort;
-
- const sorting = field && direction ? { field, direction } : true;
- const pagination = pageIndex >= 0 && pageSize !== 0 ? { pageIndex, pageSize } : true;
-
- if (!isEqual(properties.sorting, sorting)) {
- setProperties({ sorting });
- }
- if (!isEqual(properties.pagination, pagination)) {
- setProperties({ pagination });
- }
- },
- [setProperties, properties.pagination, properties.sorting]
- );
+const PAGE_SIZE_OPTIONS = [5, 10, 20];
- if (loading) {
- return (
-
- );
- }
+export const HostsTable = () => {
+ const { loading } = useHostsViewContext();
+ const { onSubmit } = useUnifiedSearchContext();
- if (noData) {
- return (
- onSubmit()}
- testString="noMetricsDataPrompt"
- />
- );
- }
+ const {
+ columns,
+ items,
+ currentPage,
+ isFlyoutOpen,
+ closeFlyout,
+ clickedItem,
+ onTableChange,
+ pagination,
+ sorting,
+ } = useHostsTableContext();
return (
<>
- onSubmit()}
+ testString="noMetricsDataPrompt"
+ />
+ )
+ }
/>
{isFlyoutOpen && clickedItem && }
>
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/kpi_grid.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/kpi_grid.tsx
index 968e7462b38f4..2dbd0c4324eca 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/kpi_grid.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/kpi_grid.tsx
@@ -6,11 +6,8 @@
*/
import React from 'react';
-import { EuiFlexGroup } from '@elastic/eui';
-import { EuiFlexItem } from '@elastic/eui';
-
+import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-
import { KPIChartProps, Tile } from './tile';
import { HostsTile } from './hosts_tile';
import { ChartBaseProps } from '../chart/metric_chart_wrapper';
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/tile.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/tile.tsx
index 480e6c415dc45..a95f18b4a10ee 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/tile.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/tile.tsx
@@ -8,13 +8,16 @@ import React from 'react';
import { Action } from '@kbn/ui-actions-plugin/public';
import { BrushTriggerEvent } from '@kbn/charts-plugin/public';
-import { EuiIcon, EuiPanel } from '@elastic/eui';
-import { EuiFlexGroup } from '@elastic/eui';
-import { EuiFlexItem } from '@elastic/eui';
-import { EuiText } from '@elastic/eui';
-import { EuiI18n } from '@elastic/eui';
+import {
+ EuiIcon,
+ EuiPanel,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiText,
+ EuiI18n,
+ EuiToolTip,
+} from '@elastic/eui';
import styled from 'styled-components';
-import { EuiToolTip } from '@elastic/eui';
import { useLensAttributes } from '../../../../../hooks/use_lens_attributes';
import { useMetricsDataViewContext } from '../../hooks/use_data_view';
import { useUnifiedSearchContext } from '../../hooks/use_unified_search';
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx
index 0fad370960f22..d5cc0b0f021d7 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx
@@ -24,7 +24,10 @@ export const LogsTabContent = () => {
const { from, to } = useMemo(() => getDateRangeAsTimestamp(), [getDateRangeAsTimestamp]);
const { hostNodes, loading } = useHostsViewContext();
- const hostsFilterQuery = useMemo(() => createHostsFilter(hostNodes), [hostNodes]);
+ const hostsFilterQuery = useMemo(
+ () => createHostsFilter(hostNodes.map((p) => p.name)),
+ [hostNodes]
+ );
const logsLinkToStreamQuery = useMemo(() => {
const hostsFilterQueryParam = createHostsFilterQueryParam(hostNodes);
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/metric_chart.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/metric_chart.tsx
index 252bea5389e3a..28d07b94d9437 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/metric_chart.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/metric_chart.tsx
@@ -4,20 +4,28 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import React from 'react';
+import React, { useMemo } from 'react';
import { Action } from '@kbn/ui-actions-plugin/public';
import { BrushTriggerEvent } from '@kbn/charts-plugin/public';
-import { EuiIcon, EuiPanel } from '@elastic/eui';
-import { EuiFlexGroup } from '@elastic/eui';
-import { EuiFlexItem } from '@elastic/eui';
-import { EuiText } from '@elastic/eui';
-import { EuiI18n } from '@elastic/eui';
+import {
+ EuiIcon,
+ EuiPanel,
+ EuiI18n,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiText,
+ useEuiTheme,
+} from '@elastic/eui';
+import { css } from '@emotion/react';
import { useLensAttributes } from '../../../../../../hooks/use_lens_attributes';
import { useMetricsDataViewContext } from '../../../hooks/use_data_view';
import { useUnifiedSearchContext } from '../../../hooks/use_unified_search';
import { HostsLensLineChartFormulas } from '../../../../../../common/visualizations';
import { useHostsViewContext } from '../../../hooks/use_hosts_view';
+import { createHostsFilter } from '../../../utils';
+import { useHostsTableContext } from '../../../hooks/use_hosts_table';
import { LensWrapper } from '../../chart/lens_wrapper';
+import { useAfterLoadedState } from '../../../hooks/use_after_loaded_state';
export interface MetricChartProps {
title: string;
@@ -29,9 +37,18 @@ export interface MetricChartProps {
const MIN_HEIGHT = 300;
export const MetricChart = ({ title, type, breakdownSize }: MetricChartProps) => {
+ const { euiTheme } = useEuiTheme();
const { searchCriteria, onSubmit } = useUnifiedSearchContext();
const { dataView } = useMetricsDataViewContext();
- const { baseRequest } = useHostsViewContext();
+ const { baseRequest, loading } = useHostsViewContext();
+ const { currentPage } = useHostsTableContext();
+
+ // prevents updates on requestTs and serchCriteria states from relaoding the chart
+ // we want it to reload only once the table has finished loading
+ const { afterLoadedState } = useAfterLoadedState(loading, {
+ lastReloadRequestTime: baseRequest.requestTs,
+ ...searchCriteria,
+ });
const { attributes, getExtraActions, error } = useLensAttributes({
type,
@@ -43,11 +60,22 @@ export const MetricChart = ({ title, type, breakdownSize }: MetricChartProps) =>
visualizationType: 'lineChart',
});
- const filters = [...searchCriteria.filters, ...searchCriteria.panelFilters];
+ const hostsFilterQuery = useMemo(() => {
+ return createHostsFilter(
+ currentPage.map((p) => p.name),
+ dataView
+ );
+ }, [currentPage, dataView]);
+
+ const filters = [
+ ...afterLoadedState.filters,
+ ...afterLoadedState.panelFilters,
+ ...[hostsFilterQuery],
+ ];
const extraActionOptions = getExtraActions({
- timeRange: searchCriteria.dateRange,
+ timeRange: afterLoadedState.dateRange,
filters,
- query: searchCriteria.query,
+ query: afterLoadedState.query,
});
const extraActions: Action[] = [extraActionOptions.openInLens];
@@ -69,12 +97,15 @@ export const MetricChart = ({ title, type, breakdownSize }: MetricChartProps) =>
hasShadow={false}
hasBorder
paddingSize={error ? 'm' : 'none'}
- style={{ minHeight: MIN_HEIGHT }}
+ css={css`
+ min-height: calc(${MIN_HEIGHT} + ${euiTheme.size.l});
+ position: 'relative';
+ `}
data-test-subj={`hostsView-metricChart-${type}`}
>
{error ? (
attributes={attributes}
style={{ height: MIN_HEIGHT }}
extraActions={extraActions}
- lastReloadRequestTime={baseRequest.requestTs}
- dateRange={searchCriteria.dateRange}
+ lastReloadRequestTime={afterLoadedState.lastReloadRequestTime}
+ dateRange={afterLoadedState.dateRange}
filters={filters}
- query={searchCriteria.query}
+ query={afterLoadedState.query}
onBrushEnd={handleBrushEnd}
+ loading={loading}
+ hasTitle
/>
)}
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/constants.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/constants.ts
index 98aa8a145e3a0..b854120a86887 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/constants.ts
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/constants.ts
@@ -13,6 +13,9 @@ export const ALERT_STATUS_ALL = 'all';
export const TIMESTAMP_FIELD = '@timestamp';
export const DATA_VIEW_PREFIX = 'infra_metrics';
+export const DEFAULT_PAGE_SIZE = 10;
+export const LOCAL_STORAGE_PAGE_SIZE_KEY = 'hostsView:pageSizeSelection';
+
export const ALL_ALERTS: AlertStatusFilter = {
status: ALERT_STATUS_ALL,
label: i18n.translate('xpack.infra.hostsViewPage.tabs.alerts.alertStatusFilter.showAll', {
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_after_loaded_state.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_after_loaded_state.ts
new file mode 100644
index 0000000000000..8c9a84d4402f8
--- /dev/null
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_after_loaded_state.ts
@@ -0,0 +1,26 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { useState, useEffect, useRef } from 'react';
+
+export const useAfterLoadedState = (loading: boolean, state: T) => {
+ const ref = useRef(undefined);
+ const [internalState, setInternalState] = useState(state);
+
+ if (!ref.current || loading !== ref.current) {
+ ref.current = loading;
+ }
+
+ useEffect(() => {
+ if (!loading) {
+ setInternalState(state);
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [ref.current]);
+
+ return { afterLoadedState: internalState };
+};
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_alerts_query.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_alerts_query.ts
index 9877d61643721..7a895591d68c7 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_alerts_query.ts
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_alerts_query.ts
@@ -69,7 +69,7 @@ const createAlertsEsQuery = ({
const alertStatusFilter = createAlertStatusFilter(status);
const dateFilter = createDateFilter(dateRange);
- const hostsFilter = createHostsFilter(hostNodes);
+ const hostsFilter = createHostsFilter(hostNodes.map((p) => p.name));
const filters = [alertStatusFilter, dateFilter, hostsFilter].filter(Boolean) as Filter[];
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts
index 4ae8823adaf2e..a921a0daeb011 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts
@@ -8,68 +8,92 @@
import { useHostsTable } from './use_hosts_table';
import { renderHook } from '@testing-library/react-hooks';
import { SnapshotNode } from '../../../../../common/http_api';
+import * as useUnifiedSearchHooks from './use_unified_search';
+import * as useHostsViewHooks from './use_hosts_view';
-describe('useHostTable hook', () => {
- it('it should map the nodes returned from the snapshot api to a format matching eui table items', () => {
- const nodes: SnapshotNode[] = [
+jest.mock('./use_unified_search');
+jest.mock('./use_hosts_view');
+
+const mockUseUnifiedSearchContext =
+ useUnifiedSearchHooks.useUnifiedSearchContext as jest.MockedFunction<
+ typeof useUnifiedSearchHooks.useUnifiedSearchContext
+ >;
+const mockUseHostsViewContext = useHostsViewHooks.useHostsViewContext as jest.MockedFunction<
+ typeof useHostsViewHooks.useHostsViewContext
+>;
+
+const mockHostNode: SnapshotNode[] = [
+ {
+ metrics: [
{
- metrics: [
- {
- name: 'rx',
- avg: 252456.92916666667,
- },
- {
- name: 'tx',
- avg: 252758.425,
- },
- {
- name: 'memory',
- avg: 0.94525,
- },
- {
- name: 'cpu',
- value: 0.6353277777777777,
- },
- {
- name: 'memoryTotal',
- avg: 34359.738368,
- },
- ],
- path: [{ value: 'host-0', label: 'host-0', os: null, cloudProvider: 'aws' }],
- name: 'host-0',
+ name: 'rx',
+ avg: 252456.92916666667,
},
{
- metrics: [
- {
- name: 'rx',
- avg: 95.86339715321859,
- },
- {
- name: 'tx',
- avg: 110.38566859563191,
- },
- {
- name: 'memory',
- avg: 0.5400000214576721,
- },
- {
- name: 'cpu',
- value: 0.8647805555555556,
- },
- {
- name: 'memoryTotal',
- avg: 9.194304,
- },
- ],
- path: [
- { value: 'host-1', label: 'host-1' },
- { value: 'host-1', label: 'host-1', ip: '243.86.94.22', os: 'macOS' },
- ],
- name: 'host-1',
+ name: 'tx',
+ avg: 252758.425,
},
- ];
+ {
+ name: 'memory',
+ avg: 0.94525,
+ },
+ {
+ name: 'cpu',
+ value: 0.6353277777777777,
+ },
+ {
+ name: 'memoryTotal',
+ avg: 34359.738368,
+ },
+ ],
+ path: [{ value: 'host-0', label: 'host-0', os: null, cloudProvider: 'aws' }],
+ name: 'host-0',
+ },
+ {
+ metrics: [
+ {
+ name: 'rx',
+ avg: 95.86339715321859,
+ },
+ {
+ name: 'tx',
+ avg: 110.38566859563191,
+ },
+ {
+ name: 'memory',
+ avg: 0.5400000214576721,
+ },
+ {
+ name: 'cpu',
+ value: 0.8647805555555556,
+ },
+ {
+ name: 'memoryTotal',
+ avg: 9.194304,
+ },
+ ],
+ path: [
+ { value: 'host-1', label: 'host-1' },
+ { value: 'host-1', label: 'host-1', ip: '243.86.94.22', os: 'macOS' },
+ ],
+ name: 'host-1',
+ },
+];
+
+describe('useHostTable hook', () => {
+ beforeAll(() => {
+ mockUseUnifiedSearchContext.mockReturnValue({
+ searchCriteria: {
+ dateRange: { from: 'now-15m', to: 'now' },
+ },
+ } as ReturnType);
- const items = [
+ mockUseHostsViewContext.mockReturnValue({
+ hostNodes: mockHostNode,
+ } as ReturnType);
+ });
+ it('it should map the nodes returned from the snapshot api to a format matching eui table items', () => {
+ const expected = [
{
name: 'host-0',
os: '-',
@@ -79,27 +103,11 @@ describe('useHostTable hook', () => {
cloudProvider: 'aws',
name: 'host-0',
},
- rx: {
- name: 'rx',
- avg: 252456.92916666667,
- },
- tx: {
- name: 'tx',
- avg: 252758.425,
- },
- memory: {
- name: 'memory',
- avg: 0.94525,
- },
- cpu: {
- name: 'cpu',
- value: 0.6353277777777777,
- },
- memoryTotal: {
- name: 'memoryTotal',
-
- avg: 34359.738368,
- },
+ rx: 252456.92916666667,
+ tx: 252758.425,
+ memory: 0.94525,
+ cpu: 0.6353277777777777,
+ memoryTotal: 34359.738368,
},
{
name: 'host-1',
@@ -110,32 +118,16 @@ describe('useHostTable hook', () => {
cloudProvider: null,
name: 'host-1',
},
- rx: {
- name: 'rx',
- avg: 95.86339715321859,
- },
- tx: {
- name: 'tx',
- avg: 110.38566859563191,
- },
- memory: {
- name: 'memory',
- avg: 0.5400000214576721,
- },
- cpu: {
- name: 'cpu',
- value: 0.8647805555555556,
- },
- memoryTotal: {
- name: 'memoryTotal',
- avg: 9.194304,
- },
+ rx: 95.86339715321859,
+ tx: 110.38566859563191,
+ memory: 0.5400000214576721,
+ cpu: 0.8647805555555556,
+ memoryTotal: 9.194304,
},
];
- const time = { from: 'now-15m', to: 'now', interval: '>=1m' };
- const { result } = renderHook(() => useHostsTable(nodes, { time }));
+ const { result } = renderHook(() => useHostsTable());
- expect(result.current.items).toStrictEqual(items);
+ expect(result.current.items).toStrictEqual(expected);
});
});
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx
index 44a492f314c1c..2d2d6c9d7f8e4 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx
@@ -8,8 +8,10 @@
import React, { useCallback, useMemo } from 'react';
import { EuiBasicTableColumn, EuiText } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { TimeRange } from '@kbn/es-query';
-
+import createContainer from 'constate';
+import { isEqual } from 'lodash';
+import { CriteriaWithPagination } from '@elastic/eui';
+import { isNumber } from 'lodash/fp';
import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana';
import { createInventoryMetricFormatter } from '../../inventory_view/lib/create_inventory_metric_formatter';
import { HostsTableEntryTitle } from '../components/hosts_table_entry_title';
@@ -19,6 +21,9 @@ import type {
SnapshotMetricInput,
} from '../../../../../common/http_api';
import { useHostFlyoutOpen } from './use_host_flyout_open_url_state';
+import { Sorting, useHostsTableProperties } from './use_hosts_table_url_state';
+import { useHostsViewContext } from './use_hosts_view';
+import { useUnifiedSearchContext } from './use_unified_search';
/**
* Columns and items types
@@ -27,7 +32,7 @@ export type CloudProvider = 'gcp' | 'aws' | 'azure' | 'unknownProvider';
type HostMetric = 'cpu' | 'diskLatency' | 'rx' | 'tx' | 'memory' | 'memoryTotal';
-type HostMetrics = Record;
+type HostMetrics = Record;
export interface HostNodeRow extends HostMetrics {
os?: string | null;
@@ -38,10 +43,6 @@ export interface HostNodeRow extends HostMetrics {
id: string;
}
-interface HostTableParams {
- time: TimeRange;
-}
-
/**
* Helper functions
*/
@@ -60,12 +61,41 @@ const buildItemsList = (nodes: SnapshotNode[]) => {
cloudProvider: path.at(-1)?.cloudProvider ?? null,
},
...metrics.reduce((data, metric) => {
- data[metric.name as HostMetric] = metric;
+ data[metric.name as HostMetric] = metric.avg ?? metric.value;
return data;
}, {} as HostMetrics),
})) as HostNodeRow[];
};
+const isTitleColumn = (cell: any): cell is HostNodeRow['title'] => {
+ return typeof cell === 'object' && cell && 'name' in cell;
+};
+
+const sortValues = (aValue: any, bValue: any, { direction }: Sorting) => {
+ if (typeof aValue === 'string' && typeof bValue === 'string') {
+ return direction === 'desc' ? bValue.localeCompare(aValue) : aValue.localeCompare(bValue);
+ }
+
+ if (isNumber(aValue) && isNumber(bValue)) {
+ return direction === 'desc' ? bValue - aValue : aValue - bValue;
+ }
+
+ return 1;
+};
+
+const sortTableData =
+ ({ direction, field }: Sorting) =>
+ (a: HostNodeRow, b: HostNodeRow) => {
+ const aValue = a[field as keyof HostNodeRow];
+ const bValue = b[field as keyof HostNodeRow];
+
+ if (isTitleColumn(aValue) && isTitleColumn(bValue)) {
+ return sortValues(aValue.name, bValue.name, { direction, field });
+ }
+
+ return sortValues(aValue, bValue, { direction, field });
+ };
+
/**
* Columns translations
*/
@@ -120,7 +150,10 @@ const toggleDialogActionLabel = i18n.translate(
/**
* Build a table columns and items starting from the snapshot nodes.
*/
-export const useHostsTable = (nodes: SnapshotNode[], { time }: HostTableParams) => {
+export const useHostsTable = () => {
+ const { hostNodes } = useHostsViewContext();
+ const { searchCriteria } = useUnifiedSearchContext();
+ const [{ pagination, sorting }, setProperties] = useHostsTableProperties();
const {
services: { telemetry },
} = useKibanaContextForPlugin();
@@ -139,12 +172,38 @@ export const useHostsTable = (nodes: SnapshotNode[], { time }: HostTableParams)
[telemetry]
);
- const items = useMemo(() => buildItemsList(nodes), [nodes]);
+ const onTableChange = useCallback(
+ ({ page, sort }: CriteriaWithPagination) => {
+ const { index: pageIndex, size: pageSize } = page;
+ const { field, direction } = sort ?? {};
+
+ const currentSorting = { field: field as keyof HostNodeRow, direction };
+ const currentPagination = { pageIndex, pageSize };
+
+ if (!isEqual(sorting, currentSorting)) {
+ setProperties({ sorting: currentSorting });
+ } else if (!isEqual(pagination, currentPagination)) {
+ setProperties({ pagination: currentPagination });
+ }
+ },
+ [setProperties, pagination, sorting]
+ );
+
+ const items = useMemo(() => buildItemsList(hostNodes), [hostNodes]);
const clickedItem = useMemo(
() => items.find(({ id }) => id === hostFlyoutOpen.clickedItemId),
[hostFlyoutOpen.clickedItemId, items]
);
+ const currentPage = useMemo(() => {
+ const { pageSize = 0, pageIndex = 0 } = pagination;
+
+ const endIndex = (pageIndex + 1) * pageSize;
+ const startIndex = pageIndex * pageSize;
+
+ return items.sort(sortTableData(sorting)).slice(startIndex, endIndex);
+ }, [items, pagination, sorting]);
+
const columns: Array> = useMemo(
() => [
{
@@ -183,7 +242,7 @@ export const useHostsTable = (nodes: SnapshotNode[], { time }: HostTableParams)
render: (title: HostNodeRow['title']) => (
reportHostEntryClick(title)}
/>
),
@@ -197,7 +256,7 @@ export const useHostsTable = (nodes: SnapshotNode[], { time }: HostTableParams)
},
{
name: averageCpuUsageLabel,
- field: 'cpu.avg',
+ field: 'cpu',
sortable: true,
'data-test-subj': 'hostsView-tableRow-cpuUsage',
render: (avg: number) => formatMetric('cpu', avg),
@@ -205,7 +264,7 @@ export const useHostsTable = (nodes: SnapshotNode[], { time }: HostTableParams)
},
{
name: diskLatencyLabel,
- field: 'diskLatency.avg',
+ field: 'diskLatency',
sortable: true,
'data-test-subj': 'hostsView-tableRow-diskLatency',
render: (avg: number) => formatMetric('diskLatency', avg),
@@ -213,7 +272,7 @@ export const useHostsTable = (nodes: SnapshotNode[], { time }: HostTableParams)
},
{
name: averageRXLabel,
- field: 'rx.avg',
+ field: 'rx',
sortable: true,
'data-test-subj': 'hostsView-tableRow-rx',
render: (avg: number) => formatMetric('rx', avg),
@@ -221,7 +280,7 @@ export const useHostsTable = (nodes: SnapshotNode[], { time }: HostTableParams)
},
{
name: averageTXLabel,
- field: 'tx.avg',
+ field: 'tx',
sortable: true,
'data-test-subj': 'hostsView-tableRow-tx',
render: (avg: number) => formatMetric('tx', avg),
@@ -229,7 +288,7 @@ export const useHostsTable = (nodes: SnapshotNode[], { time }: HostTableParams)
},
{
name: averageTotalMemoryLabel,
- field: 'memoryTotal.avg',
+ field: 'memoryTotal',
sortable: true,
'data-test-subj': 'hostsView-tableRow-memoryTotal',
render: (avg: number) => formatMetric('memoryTotal', avg),
@@ -237,21 +296,34 @@ export const useHostsTable = (nodes: SnapshotNode[], { time }: HostTableParams)
},
{
name: averageMemoryUsageLabel,
- field: 'memory.avg',
+ field: 'memory',
sortable: true,
'data-test-subj': 'hostsView-tableRow-memory',
render: (avg: number) => formatMetric('memory', avg),
align: 'right',
},
],
- [hostFlyoutOpen.clickedItemId, reportHostEntryClick, setFlyoutClosed, setHostFlyoutOpen, time]
+ [
+ hostFlyoutOpen.clickedItemId,
+ reportHostEntryClick,
+ searchCriteria.dateRange,
+ setFlyoutClosed,
+ setHostFlyoutOpen,
+ ]
);
return {
columns,
- items,
clickedItem,
- isFlyoutOpen: !!hostFlyoutOpen.clickedItemId,
+ currentPage,
closeFlyout,
+ items,
+ isFlyoutOpen: !!hostFlyoutOpen.clickedItemId,
+ onTableChange,
+ pagination,
+ sorting,
};
};
+
+export const HostsTable = createContainer(useHostsTable);
+export const [HostsTableProvider, useHostsTableContext] = HostsTable;
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table_url_state.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table_url_state.ts
new file mode 100644
index 0000000000000..b4889d62f5878
--- /dev/null
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table_url_state.ts
@@ -0,0 +1,94 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import * as rt from 'io-ts';
+import { pipe } from 'fp-ts/lib/pipeable';
+import { fold } from 'fp-ts/lib/Either';
+import { constant, identity } from 'fp-ts/lib/function';
+import useLocalStorage from 'react-use/lib/useLocalStorage';
+import deepEqual from 'fast-deep-equal';
+import { useReducer } from 'react';
+import { useUrlState } from '../../../../utils/use_url_state';
+import { DEFAULT_PAGE_SIZE, LOCAL_STORAGE_PAGE_SIZE_KEY } from '../constants';
+
+export const GET_DEFAULT_TABLE_PROPERTIES: TableProperties = {
+ sorting: {
+ direction: 'asc',
+ field: 'name',
+ },
+ pagination: {
+ pageIndex: 0,
+ pageSize: DEFAULT_PAGE_SIZE,
+ },
+};
+
+const HOST_TABLE_PROPERTIES_URL_STATE_KEY = 'tableProperties';
+
+const reducer = (prevState: TableProperties, params: Payload) => {
+ const payload = Object.fromEntries(Object.entries(params).filter(([_, v]) => !!v));
+
+ return {
+ ...prevState,
+ ...payload,
+ };
+};
+
+export const useHostsTableProperties = (): [TableProperties, TablePropertiesUpdater] => {
+ const [localStoragePageSize, setLocalStoragePageSize] = useLocalStorage(
+ LOCAL_STORAGE_PAGE_SIZE_KEY,
+ DEFAULT_PAGE_SIZE
+ );
+
+ const [urlState, setUrlState] = useUrlState({
+ defaultState: {
+ ...GET_DEFAULT_TABLE_PROPERTIES,
+ pagination: {
+ ...GET_DEFAULT_TABLE_PROPERTIES.pagination,
+ pageSize: localStoragePageSize,
+ },
+ },
+
+ decodeUrlState,
+ encodeUrlState,
+ urlStateKey: HOST_TABLE_PROPERTIES_URL_STATE_KEY,
+ });
+
+ const [properties, setProperties] = useReducer(reducer, urlState);
+ if (!deepEqual(properties, urlState)) {
+ setUrlState(properties);
+ if (localStoragePageSize !== properties.pagination.pageSize) {
+ setLocalStoragePageSize(properties.pagination.pageSize);
+ }
+ }
+
+ return [properties, setProperties];
+};
+
+const PaginationRT = rt.partial({ pageIndex: rt.number, pageSize: rt.number });
+const SortingRT = rt.intersection([
+ rt.type({
+ field: rt.string,
+ }),
+ rt.partial({ direction: rt.union([rt.literal('asc'), rt.literal('desc')]) }),
+]);
+
+const TableStateRT = rt.type({
+ pagination: PaginationRT,
+ sorting: SortingRT,
+});
+
+export type TableState = rt.TypeOf;
+export type Payload = Partial;
+export type TablePropertiesUpdater = (params: Payload) => void;
+
+export type Sorting = rt.TypeOf;
+type TableProperties = rt.TypeOf;
+
+const encodeUrlState = TableStateRT.encode;
+const decodeUrlState = (value: unknown) => {
+ return pipe(TableStateRT.decode(value), fold(constant(undefined), identity));
+};
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_table_properties_url_state.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_table_properties_url_state.ts
deleted file mode 100644
index 980fdf19a684c..0000000000000
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_table_properties_url_state.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import * as rt from 'io-ts';
-import { pipe } from 'fp-ts/lib/pipeable';
-import { fold } from 'fp-ts/lib/Either';
-import { constant, identity } from 'fp-ts/lib/function';
-import { useUrlState } from '../../../../utils/use_url_state';
-
-export const GET_DEFAULT_TABLE_PROPERTIES = {
- sorting: true,
- pagination: true,
-};
-const HOST_TABLE_PROPERTIES_URL_STATE_KEY = 'tableProperties';
-
-type Action = rt.TypeOf;
-type PropertiesUpdater = (newProps: Action) => void;
-
-export const useTableProperties = (): [TableProperties, PropertiesUpdater] => {
- const [urlState, setUrlState] = useUrlState({
- defaultState: GET_DEFAULT_TABLE_PROPERTIES,
- decodeUrlState,
- encodeUrlState,
- urlStateKey: HOST_TABLE_PROPERTIES_URL_STATE_KEY,
- });
-
- const setProperties = (newProps: Action) => setUrlState({ ...urlState, ...newProps });
-
- return [urlState, setProperties];
-};
-
-const PaginationRT = rt.union([
- rt.boolean,
- rt.partial({ pageIndex: rt.number, pageSize: rt.number }),
-]);
-const SortingRT = rt.union([rt.boolean, rt.type({ field: rt.string, direction: rt.any })]);
-
-const SetSortingRT = rt.partial({
- sorting: SortingRT,
-});
-
-const SetPaginationRT = rt.partial({
- pagination: PaginationRT,
-});
-
-const ActionRT = rt.intersection([SetSortingRT, SetPaginationRT]);
-
-const TablePropertiesRT = rt.type({
- pagination: PaginationRT,
- sorting: SortingRT,
-});
-
-type TableProperties = rt.TypeOf;
-
-const encodeUrlState = TablePropertiesRT.encode;
-const decodeUrlState = (value: unknown) => {
- return pipe(TablePropertiesRT.decode(value), fold(constant(undefined), identity));
-};
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/utils.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/utils.ts
index a04fdfa46b279..5da9d36b0f587 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/utils.ts
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/utils.ts
@@ -5,16 +5,23 @@
* 2.0.
*/
-import { Filter } from '@kbn/es-query';
-import { SnapshotNode } from '../../../../common/http_api';
+import { DataViewBase, Filter } from '@kbn/es-query';
-export const createHostsFilter = (hostNodes: SnapshotNode[]): Filter => {
+export const createHostsFilter = (hostNames: string[], dataView?: DataViewBase): Filter => {
return {
query: {
terms: {
- 'host.name': hostNodes.map((p) => p.name),
+ 'host.name': hostNames,
},
},
- meta: {},
+ meta: dataView
+ ? {
+ value: hostNames.join(),
+ type: 'phrases',
+ params: hostNames,
+ index: dataView.id,
+ key: 'host.name',
+ }
+ : {},
};
};
diff --git a/x-pack/test/functional/apps/infra/hosts_view.ts b/x-pack/test/functional/apps/infra/hosts_view.ts
index 3cf0091c93bd4..e9000a9cf3e6d 100644
--- a/x-pack/test/functional/apps/infra/hosts_view.ts
+++ b/x-pack/test/functional/apps/infra/hosts_view.ts
@@ -529,6 +529,89 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
});
});
});
+
+ describe('Pagination and Sorting', () => {
+ beforeEach(async () => {
+ await pageObjects.infraHostsView.changePageSize(5);
+ });
+
+ it('should show 5 rows on the first page', async () => {
+ const hostRows = await pageObjects.infraHostsView.getHostsTableData();
+ hostRows.forEach((row, position) => {
+ pageObjects.infraHostsView
+ .getHostsRowData(row)
+ .then((hostRowData) => expect(hostRowData).to.eql(tableEntries[position]));
+ });
+ });
+
+ it('should paginate to the last page', async () => {
+ await pageObjects.infraHostsView.paginateTo(2);
+ const hostRows = await pageObjects.infraHostsView.getHostsTableData();
+ hostRows.forEach((row) => {
+ pageObjects.infraHostsView
+ .getHostsRowData(row)
+ .then((hostRowData) => expect(hostRowData).to.eql(tableEntries[5]));
+ });
+ });
+
+ it('should show all hosts on the same page', async () => {
+ await pageObjects.infraHostsView.changePageSize(10);
+ const hostRows = await pageObjects.infraHostsView.getHostsTableData();
+ hostRows.forEach((row, position) => {
+ pageObjects.infraHostsView
+ .getHostsRowData(row)
+ .then((hostRowData) => expect(hostRowData).to.eql(tableEntries[position]));
+ });
+ });
+
+ it('should sort by Disk Latency asc', async () => {
+ await pageObjects.infraHostsView.sortByDiskLatency();
+ let hostRows = await pageObjects.infraHostsView.getHostsTableData();
+ const hostDataFirtPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]);
+ expect(hostDataFirtPage).to.eql(tableEntries[0]);
+
+ await pageObjects.infraHostsView.paginateTo(2);
+ hostRows = await pageObjects.infraHostsView.getHostsTableData();
+ const hostDataLastPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]);
+ expect(hostDataLastPage).to.eql(tableEntries[1]);
+ });
+
+ it('should sort by Disk Latency desc', async () => {
+ await pageObjects.infraHostsView.sortByDiskLatency();
+ let hostRows = await pageObjects.infraHostsView.getHostsTableData();
+ const hostDataFirtPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]);
+ expect(hostDataFirtPage).to.eql(tableEntries[1]);
+
+ await pageObjects.infraHostsView.paginateTo(2);
+ hostRows = await pageObjects.infraHostsView.getHostsTableData();
+ const hostDataLastPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]);
+ expect(hostDataLastPage).to.eql(tableEntries[0]);
+ });
+
+ it('should sort by Title asc', async () => {
+ await pageObjects.infraHostsView.sortByTitle();
+ let hostRows = await pageObjects.infraHostsView.getHostsTableData();
+ const hostDataFirtPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]);
+ expect(hostDataFirtPage).to.eql(tableEntries[0]);
+
+ await pageObjects.infraHostsView.paginateTo(2);
+ hostRows = await pageObjects.infraHostsView.getHostsTableData();
+ const hostDataLastPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]);
+ expect(hostDataLastPage).to.eql(tableEntries[5]);
+ });
+
+ it('should sort by Title desc', async () => {
+ await pageObjects.infraHostsView.sortByTitle();
+ let hostRows = await pageObjects.infraHostsView.getHostsTableData();
+ const hostDataFirtPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]);
+ expect(hostDataFirtPage).to.eql(tableEntries[5]);
+
+ await pageObjects.infraHostsView.paginateTo(2);
+ hostRows = await pageObjects.infraHostsView.getHostsTableData();
+ const hostDataLastPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]);
+ expect(hostDataLastPage).to.eql(tableEntries[0]);
+ });
+ });
});
});
};
diff --git a/x-pack/test/functional/page_objects/infra_hosts_view.ts b/x-pack/test/functional/page_objects/infra_hosts_view.ts
index ae0cc601f8cc7..6478d208226ad 100644
--- a/x-pack/test/functional/page_objects/infra_hosts_view.ts
+++ b/x-pack/test/functional/page_objects/infra_hosts_view.ts
@@ -241,6 +241,7 @@ export function InfraHostsViewProvider({ getService }: FtrProviderContext) {
async typeInQueryBar(query: string) {
const queryBar = await this.getQueryBar();
+ await queryBar.clearValueWithKeyboard();
return queryBar.type(query);
},
@@ -249,5 +250,51 @@ export function InfraHostsViewProvider({ getService }: FtrProviderContext) {
await testSubjects.click('querySubmitButton');
},
+
+ // Pagination
+ getPageNumberButton(pageNumber: number) {
+ return testSubjects.find(`pagination-button-${pageNumber - 1}`);
+ },
+
+ getPageSizeSelector() {
+ return testSubjects.find('tablePaginationPopoverButton');
+ },
+
+ getPageSizeOption(pageSize: number) {
+ return testSubjects.find(`tablePagination-${pageSize}-rows`);
+ },
+
+ async changePageSize(pageSize: number) {
+ const pageSizeSelector = await this.getPageSizeSelector();
+ await pageSizeSelector.click();
+ const pageSizeOption = await this.getPageSizeOption(pageSize);
+ await pageSizeOption.click();
+ },
+
+ async paginateTo(pageNumber: number) {
+ const paginationButton = await this.getPageNumberButton(pageNumber);
+ await paginationButton.click();
+ },
+
+ // Sorting
+ getDiskLatencyHeader() {
+ return testSubjects.find('tableHeaderCell_diskLatency_4');
+ },
+
+ getTitleHeader() {
+ return testSubjects.find('tableHeaderCell_title_1');
+ },
+
+ async sortByDiskLatency() {
+ const diskLatency = await this.getDiskLatencyHeader();
+ const button = await testSubjects.findDescendant('tableHeaderSortButton', diskLatency);
+ return button.click();
+ },
+
+ async sortByTitle() {
+ const titleHeader = await this.getTitleHeader();
+ const button = await testSubjects.findDescendant('tableHeaderSortButton', titleHeader);
+ return button.click();
+ },
};
}
From 111d04f45a64cc050407bd9f892e1f77ddd8cc9f Mon Sep 17 00:00:00 2001
From: Katerina Patticha
Date: Mon, 24 Apr 2023 16:27:38 +0200
Subject: [PATCH 10/36] [APM] Add transaction name filter in failed transaction
rate rule type (#155405)
part of https://github.com/elastic/kibana/issues/152329
related work https://github.com/elastic/kibana/pull/154241
Introduces the Transaction name filter in the failed transaction rate
rule type
https://user-images.githubusercontent.com/3369346/233386404-1875b283-0321-4bf1-a7d3-66327f7d4ec5.mov
## Fixes
The regression introduces in a previous
[PR](https://github.com/elastic/kibana/pull/154241/commits/fce4ef8168429645a01434e19b0feaefba1a4f02)
Existing rule types can have empty string in their params so we need to
make sure we don't filter empty values as it will yield no results.
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
---
x-pack/plugins/apm/common/rules/schema.ts | 1 +
.../index.stories.tsx | 105 ++++++++++++++++++
.../index.tsx | 16 ++-
.../register_error_count_rule_type.ts | 8 +-
...register_transaction_duration_rule_type.ts | 12 +-
...et_transaction_error_rate_chart_preview.ts | 13 ++-
...gister_transaction_error_rate_rule_type.ts | 16 ++-
.../tests/alerts/chart_preview.spec.ts | 55 +++++++++
8 files changed, 214 insertions(+), 12 deletions(-)
create mode 100644 x-pack/plugins/apm/public/components/alerting/rule_types/transaction_error_rate_rule_type/index.stories.tsx
diff --git a/x-pack/plugins/apm/common/rules/schema.ts b/x-pack/plugins/apm/common/rules/schema.ts
index 698b4507c5b3f..ca77e76f6f156 100644
--- a/x-pack/plugins/apm/common/rules/schema.ts
+++ b/x-pack/plugins/apm/common/rules/schema.ts
@@ -52,6 +52,7 @@ export const transactionErrorRateParamsSchema = schema.object({
windowUnit: schema.string(),
threshold: schema.number(),
transactionType: schema.maybe(schema.string()),
+ transactionName: schema.maybe(schema.string()),
serviceName: schema.maybe(schema.string()),
environment: schema.string(),
});
diff --git a/x-pack/plugins/apm/public/components/alerting/rule_types/transaction_error_rate_rule_type/index.stories.tsx b/x-pack/plugins/apm/public/components/alerting/rule_types/transaction_error_rate_rule_type/index.stories.tsx
new file mode 100644
index 0000000000000..cd94439db0389
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/alerting/rule_types/transaction_error_rate_rule_type/index.stories.tsx
@@ -0,0 +1,105 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { Story } from '@storybook/react';
+import React, { ComponentType, useState } from 'react';
+import { CoreStart } from '@kbn/core/public';
+import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public';
+import { RuleParams, TransactionErrorRateRuleType } from '.';
+import { AlertMetadata } from '../../utils/helper';
+import { ENVIRONMENT_ALL } from '../../../../../common/environment_filter_values';
+
+const KibanaReactContext = createKibanaReactContext({
+ notifications: { toasts: { add: () => {} } },
+} as unknown as Partial);
+
+interface Args {
+ ruleParams: RuleParams;
+ metadata?: AlertMetadata;
+}
+
+export default {
+ title: 'alerting/TransactionErrorRateRuleType',
+ component: TransactionErrorRateRuleType,
+ decorators: [
+ (StoryComponent: ComponentType) => {
+ return (
+
+
+
+
+
+ );
+ },
+ ],
+};
+
+export const CreatingInApmServiceOverview: Story = ({
+ ruleParams,
+ metadata,
+}) => {
+ const [params, setParams] = useState(ruleParams);
+
+ function setRuleParams(property: string, value: any) {
+ setParams({ ...params, [property]: value });
+ }
+
+ return (
+ {}}
+ />
+ );
+};
+
+CreatingInApmServiceOverview.args = {
+ ruleParams: {
+ environment: 'testEnvironment',
+ serviceName: 'testServiceName',
+ threshold: 1500,
+ transactionType: 'testTransactionType',
+ transactionName: 'GET /api/customer/:id',
+ windowSize: 5,
+ windowUnit: 'm',
+ },
+ metadata: {
+ environment: ENVIRONMENT_ALL.value,
+ serviceName: undefined,
+ },
+};
+
+export const CreatingInStackManagement: Story = ({
+ ruleParams,
+ metadata,
+}) => {
+ const [params, setParams] = useState(ruleParams);
+
+ function setRuleParams(property: string, value: any) {
+ setParams({ ...params, [property]: value });
+ }
+
+ return (
+ {}}
+ />
+ );
+};
+
+CreatingInStackManagement.args = {
+ ruleParams: {
+ environment: 'testEnvironment',
+ threshold: 1500,
+ windowSize: 5,
+ windowUnit: 'm',
+ },
+ metadata: undefined,
+};
diff --git a/x-pack/plugins/apm/public/components/alerting/rule_types/transaction_error_rate_rule_type/index.tsx b/x-pack/plugins/apm/public/components/alerting/rule_types/transaction_error_rate_rule_type/index.tsx
index f9cfd6a511ef2..f161ef085b3ea 100644
--- a/x-pack/plugins/apm/public/components/alerting/rule_types/transaction_error_rate_rule_type/index.tsx
+++ b/x-pack/plugins/apm/public/components/alerting/rule_types/transaction_error_rate_rule_type/index.tsx
@@ -23,20 +23,22 @@ import {
IsAboveField,
ServiceField,
TransactionTypeField,
+ TransactionNameField,
} from '../../utils/fields';
import { AlertMetadata, getIntervalAndTimeRange } from '../../utils/helper';
import { ApmRuleParamsContainer } from '../../ui_components/apm_rule_params_container';
-interface RuleParams {
+export interface RuleParams {
windowSize?: number;
windowUnit?: string;
threshold?: number;
serviceName?: string;
transactionType?: string;
+ transactionName?: string;
environment?: string;
}
-interface Props {
+export interface Props {
ruleParams: RuleParams;
metadata?: AlertMetadata;
setRuleParams: (key: string, value: any) => void;
@@ -78,6 +80,7 @@ export function TransactionErrorRateRuleType(props: Props) {
environment: params.environment,
serviceName: params.serviceName,
transactionType: params.transactionType,
+ transactionName: params.transactionName,
interval,
start,
end,
@@ -89,6 +92,7 @@ export function TransactionErrorRateRuleType(props: Props) {
},
[
params.transactionType,
+ params.transactionName,
params.environment,
params.serviceName,
params.windowSize,
@@ -102,7 +106,8 @@ export function TransactionErrorRateRuleType(props: Props) {
onChange={(value) => {
if (value !== params.serviceName) {
setRuleParams('serviceName', value);
- setRuleParams('transactionType', '');
+ setRuleParams('transactionType', undefined);
+ setRuleParams('transactionName', undefined);
setRuleParams('environment', ENVIRONMENT_ALL.value);
}
}}
@@ -117,6 +122,11 @@ export function TransactionErrorRateRuleType(props: Props) {
onChange={(value) => setRuleParams('environment', value)}
serviceName={params.serviceName}
/>,
+ setRuleParams('transactionName', value)}
+ serviceName={params.serviceName}
+ />,
{
- const { serviceName, environment, transactionType, interval, start, end } =
- alertParams;
+ const {
+ serviceName,
+ environment,
+ transactionType,
+ interval,
+ start,
+ end,
+ transactionName,
+ } = alertParams;
const searchAggregatedTransactions = await getSearchTransactionsEvents({
config,
@@ -62,6 +70,7 @@ export async function getTransactionErrorRateChartPreview({
filter: [
...termQuery(SERVICE_NAME, serviceName),
...termQuery(TRANSACTION_TYPE, transactionType),
+ ...termQuery(TRANSACTION_NAME, transactionName),
...rangeQuery(start, end),
...environmentQuery(environment),
...getDocumentTypeFilterForTransactions(
diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts
index 7ceaf8ca78048..26b5847a205f1 100644
--- a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts
+++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts
@@ -32,6 +32,7 @@ import {
SERVICE_ENVIRONMENT,
SERVICE_NAME,
TRANSACTION_TYPE,
+ TRANSACTION_NAME,
} from '../../../../../common/es_fields/apm';
import { EventOutcome } from '../../../../../common/event_outcome';
import {
@@ -86,6 +87,7 @@ export function registerTransactionErrorRateRuleType({
apmActionVariables.interval,
apmActionVariables.reason,
apmActionVariables.serviceName,
+ apmActionVariables.transactionName,
apmActionVariables.threshold,
apmActionVariables.transactionType,
apmActionVariables.triggerValue,
@@ -142,8 +144,15 @@ export function registerTransactionErrorRateRuleType({
],
},
},
- ...termQuery(SERVICE_NAME, ruleParams.serviceName),
- ...termQuery(TRANSACTION_TYPE, ruleParams.transactionType),
+ ...termQuery(SERVICE_NAME, ruleParams.serviceName, {
+ queryEmptyString: false,
+ }),
+ ...termQuery(TRANSACTION_TYPE, ruleParams.transactionType, {
+ queryEmptyString: false,
+ }),
+ ...termQuery(TRANSACTION_NAME, ruleParams.transactionName, {
+ queryEmptyString: false,
+ }),
...environmentQuery(ruleParams.environment),
],
},
@@ -232,6 +241,7 @@ export function registerTransactionErrorRateRuleType({
serviceName,
transactionType,
environment,
+ ruleParams.transactionName,
]
.filter((name) => name)
.join('_');
@@ -255,6 +265,7 @@ export function registerTransactionErrorRateRuleType({
[SERVICE_NAME]: serviceName,
...getEnvironmentEsField(environment),
[TRANSACTION_TYPE]: transactionType,
+ [TRANSACTION_NAME]: ruleParams.transactionName,
[PROCESSOR_EVENT]: ProcessorEvent.transaction,
[ALERT_EVALUATION_VALUE]: errorRate,
[ALERT_EVALUATION_THRESHOLD]: ruleParams.threshold,
@@ -272,6 +283,7 @@ export function registerTransactionErrorRateRuleType({
serviceName,
threshold: ruleParams.threshold,
transactionType,
+ transactionName: ruleParams.transactionName,
triggerValue: asDecimalOrInteger(errorRate),
viewInAppUrl,
});
diff --git a/x-pack/test/apm_api_integration/tests/alerts/chart_preview.spec.ts b/x-pack/test/apm_api_integration/tests/alerts/chart_preview.spec.ts
index 7ec09849b7ff2..f95bb8de59a89 100644
--- a/x-pack/test/apm_api_integration/tests/alerts/chart_preview.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/alerts/chart_preview.spec.ts
@@ -83,6 +83,61 @@ export default function ApiTest({ getService }: FtrProviderContext) {
).to.equal(true);
});
+ it('transaction_error_rate with transaction name', async () => {
+ const options = {
+ params: {
+ query: {
+ start,
+ end,
+ serviceName: 'opbeans-java',
+ transactionName: 'APIRestController#product',
+ transactionType: 'request',
+ environment: 'ENVIRONMENT_ALL',
+ interval: '5m',
+ },
+ },
+ };
+
+ const response = await apmApiClient.readUser({
+ endpoint: 'GET /internal/apm/rule_types/transaction_error_rate/chart_preview',
+ ...options,
+ });
+
+ expect(response.status).to.be(200);
+ expect(response.body.errorRateChartPreview[0]).to.eql({
+ x: 1627974600000,
+ y: 1,
+ });
+ });
+
+ it('transaction_error_rate with nonexistent transaction name', async () => {
+ const options = {
+ params: {
+ query: {
+ start,
+ end,
+ serviceName: 'opbeans-java',
+ transactionName: 'foo',
+ transactionType: 'request',
+ environment: 'ENVIRONMENT_ALL',
+ interval: '5m',
+ },
+ },
+ };
+
+ const response = await apmApiClient.readUser({
+ endpoint: 'GET /internal/apm/rule_types/transaction_error_rate/chart_preview',
+ ...options,
+ });
+
+ expect(response.status).to.be(200);
+ expect(
+ response.body.errorRateChartPreview.every(
+ (item: { x: number; y: number | null }) => item.y === null
+ )
+ ).to.equal(true);
+ });
+
it('error_count (with data)', async () => {
const options = getOptions();
options.params.query.transactionType = undefined;
From 1095375fe39e960d0569d818191bef468c93a44b Mon Sep 17 00:00:00 2001
From: Antonio
Date: Mon, 24 Apr 2023 16:42:37 +0200
Subject: [PATCH 11/36] [Cases] Close FilePreview with Escape key. (#155592)
Fixes #155036
## Summary
Allow users to close the file preview in cases by using the Escape key.
(e2e coming in a different PR with other tests)
---
.../components/files/file_preview.test.tsx | 20 +++++++++++++++++++
.../public/components/files/file_preview.tsx | 18 +++++++++++++++--
2 files changed, 36 insertions(+), 2 deletions(-)
diff --git a/x-pack/plugins/cases/public/components/files/file_preview.test.tsx b/x-pack/plugins/cases/public/components/files/file_preview.test.tsx
index b02df3a82228f..c1d7fe20bee48 100644
--- a/x-pack/plugins/cases/public/components/files/file_preview.test.tsx
+++ b/x-pack/plugins/cases/public/components/files/file_preview.test.tsx
@@ -7,6 +7,7 @@
import React from 'react';
import { screen, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
import type { AppMockRenderer } from '../../common/mock';
@@ -35,4 +36,23 @@ describe('FilePreview', () => {
expect(await screen.findByTestId('cases-files-image-preview')).toBeInTheDocument();
});
+
+ it('pressing escape calls closePreview', async () => {
+ const closePreview = jest.fn();
+
+ appMockRender.render();
+
+ await waitFor(() =>
+ expect(appMockRender.getFilesClient().getDownloadHref).toHaveBeenCalledWith({
+ id: basicFileMock.id,
+ fileKind: constructFileKindIdByOwner(mockedTestProvidersOwner[0]),
+ })
+ );
+
+ expect(await screen.findByTestId('cases-files-image-preview')).toBeInTheDocument();
+
+ userEvent.keyboard('{esc}');
+
+ await waitFor(() => expect(closePreview).toHaveBeenCalled());
+ });
});
diff --git a/x-pack/plugins/cases/public/components/files/file_preview.tsx b/x-pack/plugins/cases/public/components/files/file_preview.tsx
index 1bb91c5b53ff7..09cee1320ec2a 100644
--- a/x-pack/plugins/cases/public/components/files/file_preview.tsx
+++ b/x-pack/plugins/cases/public/components/files/file_preview.tsx
@@ -4,12 +4,12 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import React from 'react';
+import React, { useEffect } from 'react';
import styled from 'styled-components';
import type { FileJSON } from '@kbn/shared-ux-file-types';
-import { EuiOverlayMask, EuiFocusTrap, EuiImage } from '@elastic/eui';
+import { EuiOverlayMask, EuiFocusTrap, EuiImage, keys } from '@elastic/eui';
import { useFilesContext } from '@kbn/shared-ux-file-context';
import type { Owner } from '../../../common/constants/types';
@@ -36,6 +36,20 @@ export const FilePreview = ({ closePreview, selectedFile }: FilePreviewProps) =>
const { client: filesClient } = useFilesContext();
const { owner } = useCasesContext();
+ useEffect(() => {
+ const keyboardListener = (event: KeyboardEvent) => {
+ if (event.key === keys.ESCAPE || event.code === 'Escape') {
+ closePreview();
+ }
+ };
+
+ window.addEventListener('keyup', keyboardListener);
+
+ return () => {
+ window.removeEventListener('keyup', keyboardListener);
+ };
+ }, [closePreview]);
+
return (
From a03d20be039d1c449b2848f46463bc423b6f5183 Mon Sep 17 00:00:00 2001
From: Tiago Costa
Date: Mon, 24 Apr 2023 15:51:36 +0100
Subject: [PATCH 12/36] skip flaky suite (#154970)
---
.../sections/alerts_table/bulk_actions/bulk_actions.test.tsx | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/bulk_actions.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/bulk_actions.test.tsx
index f9d209549da0c..23fac59fca208 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/bulk_actions.test.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/bulk_actions.test.tsx
@@ -691,7 +691,8 @@ describe('AlertsTable.BulkActions', () => {
).toBeTruthy();
});
- describe('and clear the selection is clicked', () => {
+ // FLAKY: https://github.com/elastic/kibana/issues/154970
+ describe.skip('and clear the selection is clicked', () => {
it('should turn off the toolbar', async () => {
const props = {
...tablePropsWithBulkActions,
From 2c14b584f8f736f65211b5f738f9e0d764681346 Mon Sep 17 00:00:00 2001
From: Tiago Costa
Date: Mon, 24 Apr 2023 15:55:40 +0100
Subject: [PATCH 13/36] skip flaky suite (#155222)
---
x-pack/test/functional/apps/aiops/explain_log_rate_spikes.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/test/functional/apps/aiops/explain_log_rate_spikes.ts b/x-pack/test/functional/apps/aiops/explain_log_rate_spikes.ts
index 92320dad62087..54b1baae454bd 100644
--- a/x-pack/test/functional/apps/aiops/explain_log_rate_spikes.ts
+++ b/x-pack/test/functional/apps/aiops/explain_log_rate_spikes.ts
@@ -209,7 +209,8 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
});
}
- describe('explain log rate spikes', async function () {
+ // FLAKY: https://github.com/elastic/kibana/issues/155222
+ describe.skip('explain log rate spikes', async function () {
for (const testData of explainLogRateSpikesTestData) {
describe(`with '${testData.sourceIndexOrSavedSearch}'`, function () {
before(async () => {
From 3d78370aa584e179ae9e9d30fabe080242812d22 Mon Sep 17 00:00:00 2001
From: Kathleen DeRusso
Date: Mon, 24 Apr 2023 11:02:43 -0400
Subject: [PATCH 14/36] Fix API links when generating API key snippet
(#155435)
Fixes the Search Applications API page to set an URL to the ES plugin
rather than Enterprise Search URL.
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
---
.../engine_connect/engine_api_integration.tsx | 19 ++++++++++---------
.../engine_connect/search_application_api.tsx | 14 +++++++++++---
2 files changed, 21 insertions(+), 12 deletions(-)
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_api_integration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_api_integration.tsx
index b61614838d7a1..2fe691e262b64 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_api_integration.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_api_integration.tsx
@@ -12,23 +12,24 @@ import { useValues } from 'kea';
import { EuiCodeBlock, EuiSpacer, EuiText, EuiTabs, EuiTab } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { getEnterpriseSearchUrl } from '../../../../shared/enterprise_search_url';
-
+import { useCloudDetails } from '../../../../shared/cloud_details/cloud_details';
import { EngineViewLogic } from '../engine_view_logic';
import { EngineApiLogic } from './engine_api_logic';
-const SearchUISnippet = (enterpriseSearchUrl: string, engineName: string, apiKey: string) => `
+import { elasticsearchUrl } from './search_application_api';
+
+const SearchUISnippet = (esUrl: string, engineName: string, apiKey: string) => `6
import EnginesAPIConnector from "@elastic/search-ui-engines-connector";
const connector = new EnginesAPIConnector({
- host: "${enterpriseSearchUrl}",
+ host: "${esUrl}",
engineName: "${engineName}",
apiKey: "${apiKey || ''}"
});`;
-const cURLSnippet = (enterpriseSearchUrl: string, engineName: string, apiKey: string) => `
-curl --location --request GET '${enterpriseSearchUrl}/api/engines/${engineName}/_search' \\
+const cURLSnippet = (esUrl: string, engineName: string, apiKey: string) => `
+curl --location --request GET '${esUrl}/${engineName}/_search' \\
--header 'Authorization: apiKey ${apiKey || ''}' \\
--header 'Content-Type: application/json' \\
--data-raw '{
@@ -47,19 +48,19 @@ interface Tab {
export const EngineApiIntegrationStage: React.FC = () => {
const [selectedTab, setSelectedTab] = React.useState('curl');
const { engineName } = useValues(EngineViewLogic);
- const enterpriseSearchUrl = getEnterpriseSearchUrl();
const { apiKey } = useValues(EngineApiLogic);
+ const cloudContext = useCloudDetails();
const Tabs: Record = {
curl: {
- code: cURLSnippet(enterpriseSearchUrl, engineName, apiKey),
+ code: cURLSnippet(elasticsearchUrl(cloudContext), engineName, apiKey),
language: 'bash',
title: i18n.translate('xpack.enterpriseSearch.content.engine.api.step3.curlTitle', {
defaultMessage: 'cURL',
}),
},
searchui: {
- code: SearchUISnippet(enterpriseSearchUrl, engineName, apiKey),
+ code: SearchUISnippet(elasticsearchUrl(cloudContext), engineName, apiKey),
language: 'javascript',
title: i18n.translate('xpack.enterpriseSearch.content.engine.api.step3.searchUITitle', {
defaultMessage: 'Search UI',
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/search_application_api.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/search_application_api.tsx
index 9d3c27895657f..6934de4051bdb 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/search_application_api.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/search_application_api.tsx
@@ -23,9 +23,10 @@ import { i18n } from '@kbn/i18n';
import { ANALYTICS_PLUGIN } from '../../../../../../common/constants';
import { COLLECTION_INTEGRATE_PATH } from '../../../../analytics/routes';
+import { CloudDetails, useCloudDetails } from '../../../../shared/cloud_details/cloud_details';
+import { decodeCloudId } from '../../../../shared/decode_cloud_id/decode_cloud_id';
import { docLinks } from '../../../../shared/doc_links';
import { generateEncodedPath } from '../../../../shared/encode_path_params';
-import { getEnterpriseSearchUrl } from '../../../../shared/enterprise_search_url';
import { KibanaLogic } from '../../../../shared/kibana';
import { EngineViewLogic } from '../engine_view_logic';
@@ -34,12 +35,19 @@ import { EngineApiIntegrationStage } from './engine_api_integration';
import { EngineApiLogic } from './engine_api_logic';
import { GenerateEngineApiKeyModal } from './generate_engine_api_key_modal/generate_engine_api_key_modal';
+export const elasticsearchUrl = (cloudContext: CloudDetails): string => {
+ const defaultUrl = 'https://localhost:9200';
+ const url =
+ (cloudContext.cloudId && decodeCloudId(cloudContext.cloudId)?.elasticsearchUrl) || defaultUrl;
+ return url;
+};
+
export const SearchApplicationAPI = () => {
const { engineName } = useValues(EngineViewLogic);
const { isGenerateModalOpen } = useValues(EngineApiLogic);
const { openGenerateModal, closeGenerateModal } = useActions(EngineApiLogic);
- const enterpriseSearchUrl = getEnterpriseSearchUrl();
const { navigateToUrl } = useValues(KibanaLogic);
+ const cloudContext = useCloudDetails();
const steps = [
{
@@ -132,7 +140,7 @@ export const SearchApplicationAPI = () => {
- {enterpriseSearchUrl}
+ {elasticsearchUrl(cloudContext)}
From 29a10fddc9af9246f6a329a9aa2018b0a907505d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Loix?=
Date: Mon, 24 Apr 2023 16:45:41 +0100
Subject: [PATCH 15/36] [Files] Allow option to disable delete action in mgt UI
(#155179)
---
.../content-management/table_list/index.ts | 2 +-
.../table_list/src/components/table.tsx | 33 +++++-
.../table_list/src/index.ts | 2 +
.../table_list/src/table_list_view.test.tsx | 105 ++++++++++++++++++
.../table_list/src/table_list_view.tsx | 35 +++++-
.../table_list/src/types.ts | 13 +++
.../shared-ux/file/types/base_file_client.ts | 1 +
packages/shared-ux/file/types/index.ts | 16 +++
src/plugins/files/public/plugin.ts | 11 +-
.../adapters/query_filters.ts | 16 +++
.../server/file_service/file_action_types.ts | 4 +
.../integration_tests/file_service.test.ts | 21 +++-
src/plugins/files/server/routes/find.ts | 4 +-
src/plugins/files_management/public/app.tsx | 28 ++++-
.../files_management/public/context.tsx | 11 +-
.../files_management/public/i18n_texts.ts | 3 +
.../public/mount_management_section.tsx | 8 +-
src/plugins/files_management/public/types.ts | 2 +
18 files changed, 294 insertions(+), 21 deletions(-)
diff --git a/packages/content-management/table_list/index.ts b/packages/content-management/table_list/index.ts
index 532b35450d541..9a608b2d6dda3 100644
--- a/packages/content-management/table_list/index.ts
+++ b/packages/content-management/table_list/index.ts
@@ -8,5 +8,5 @@
export { TableListView, TableListViewProvider, TableListViewKibanaProvider } from './src';
-export type { UserContentCommonSchema } from './src';
+export type { UserContentCommonSchema, RowActions } from './src';
export type { TableListViewKibanaDependencies } from './src/services';
diff --git a/packages/content-management/table_list/src/components/table.tsx b/packages/content-management/table_list/src/components/table.tsx
index 330eb67be4278..3214e7bf00a72 100644
--- a/packages/content-management/table_list/src/components/table.tsx
+++ b/packages/content-management/table_list/src/components/table.tsx
@@ -17,7 +17,9 @@ import {
SearchFilterConfig,
Direction,
Query,
+ type EuiTableSelectionType,
} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { useServices } from '../services';
import type { Action } from '../actions';
@@ -26,6 +28,7 @@ import type {
Props as TableListViewProps,
UserContentCommonSchema,
} from '../table_list_view';
+import type { TableItemsRowActions } from '../types';
import { TableSortSelect } from './table_sort_select';
import { TagFilterPanel } from './tag_filter_panel';
import { useTagFilterPanel } from './use_tag_filter_panel';
@@ -51,6 +54,7 @@ interface Props extends State, TagManageme
tableColumns: Array>;
hasUpdatedAtMetadata: boolean;
deleteItems: TableListViewProps['deleteItems'];
+ tableItemsRowActions: TableItemsRowActions;
onSortChange: (column: SortColumnField, direction: Direction) => void;
onTableChange: (criteria: CriteriaWithPagination) => void;
onTableSearchChange: (arg: { query: Query | null; queryText: string }) => void;
@@ -70,6 +74,7 @@ export function Table({
entityName,
entityNamePlural,
tagsToTableItemMap,
+ tableItemsRowActions,
deleteItems,
tableCaption,
onTableChange,
@@ -105,13 +110,32 @@ export function Table({
);
}, [deleteItems, dispatch, entityName, entityNamePlural, selectedIds.length]);
- const selection = deleteItems
- ? {
+ const selection = useMemo | undefined>(() => {
+ if (deleteItems) {
+ return {
onSelectionChange: (obj: T[]) => {
dispatch({ type: 'onSelectionChange', data: obj });
},
- }
- : undefined;
+ selectable: (obj) => {
+ const actions = tableItemsRowActions[obj.id];
+ return actions?.delete?.enabled !== false;
+ },
+ selectableMessage: (selectable, obj) => {
+ if (!selectable) {
+ const actions = tableItemsRowActions[obj.id];
+ return (
+ actions?.delete?.reason ??
+ i18n.translate('contentManagement.tableList.actionsDisabledLabel', {
+ defaultMessage: 'Actions disabled for this item',
+ })
+ );
+ }
+ return '';
+ },
+ initialSelected: [],
+ };
+ }
+ }, [deleteItems, dispatch, tableItemsRowActions]);
const {
isPopoverOpen,
@@ -214,6 +238,7 @@ export function Table({
data-test-subj="itemsInMemTable"
rowHeader="attributes.title"
tableCaption={tableCaption}
+ isSelectable
/>
);
}
diff --git a/packages/content-management/table_list/src/index.ts b/packages/content-management/table_list/src/index.ts
index df0d1e22bc106..d1e83d7dd2e93 100644
--- a/packages/content-management/table_list/src/index.ts
+++ b/packages/content-management/table_list/src/index.ts
@@ -15,3 +15,5 @@ export type {
} from './table_list_view';
export { TableListViewProvider, TableListViewKibanaProvider } from './services';
+
+export type { RowActions } from './types';
diff --git a/packages/content-management/table_list/src/table_list_view.test.tsx b/packages/content-management/table_list/src/table_list_view.test.tsx
index 62c83fb5b9454..0245af450fb8a 100644
--- a/packages/content-management/table_list/src/table_list_view.test.tsx
+++ b/packages/content-management/table_list/src/table_list_view.test.tsx
@@ -1067,4 +1067,109 @@ describe('TableListView', () => {
expect(router?.history.location?.search).toBe('?sort=title&sortdir=desc');
});
});
+
+ describe('row item actions', () => {
+ const hits: UserContentCommonSchema[] = [
+ {
+ id: '123',
+ updatedAt: twoDaysAgo.toISOString(),
+ type: 'dashboard',
+ attributes: {
+ title: 'Item 1',
+ description: 'Item 1 description',
+ },
+ references: [],
+ },
+ {
+ id: '456',
+ updatedAt: yesterday.toISOString(),
+ type: 'dashboard',
+ attributes: {
+ title: 'Item 2',
+ description: 'Item 2 description',
+ },
+ references: [],
+ },
+ ];
+
+ const setupTest = async (props?: Partial) => {
+ let testBed: TestBed | undefined;
+ const deleteItems = jest.fn();
+ await act(async () => {
+ testBed = await setup({
+ findItems: jest.fn().mockResolvedValue({ total: hits.length, hits }),
+ deleteItems,
+ ...props,
+ });
+ });
+
+ testBed!.component.update();
+ return { testBed: testBed!, deleteItems };
+ };
+
+ test('should allow select items to be deleted', async () => {
+ const {
+ testBed: { table, find, exists, component, form },
+ deleteItems,
+ } = await setupTest();
+
+ const { tableCellsValues } = table.getMetaData('itemsInMemTable');
+
+ expect(tableCellsValues).toEqual([
+ ['', 'Item 2Item 2 description', yesterdayToString], // First empty col is the "checkbox"
+ ['', 'Item 1Item 1 description', twoDaysAgoToString],
+ ]);
+
+ const selectedHit = hits[1];
+
+ expect(exists('deleteSelectedItems')).toBe(false);
+ act(() => {
+ // Select the second item
+ form.selectCheckBox(`checkboxSelectRow-${selectedHit.id}`);
+ });
+ component.update();
+ // Delete button is now visible
+ expect(exists('deleteSelectedItems')).toBe(true);
+
+ // Click delete and validate that confirm modal opens
+ expect(component.exists('.euiModal--confirmation')).toBe(false);
+ act(() => {
+ find('deleteSelectedItems').simulate('click');
+ });
+ component.update();
+ expect(component.exists('.euiModal--confirmation')).toBe(true);
+
+ await act(async () => {
+ find('confirmModalConfirmButton').simulate('click');
+ });
+ expect(deleteItems).toHaveBeenCalledWith([selectedHit]);
+ });
+
+ test('should allow to disable the "delete" action for a row', async () => {
+ const reasonMessage = 'This file cannot be deleted.';
+
+ const {
+ testBed: { find },
+ } = await setupTest({
+ rowItemActions: (obj) => {
+ if (obj.id === hits[1].id) {
+ return {
+ delete: {
+ enabled: false,
+ reason: reasonMessage,
+ },
+ };
+ }
+ },
+ });
+
+ const firstCheckBox = find(`checkboxSelectRow-${hits[0].id}`);
+ const secondCheckBox = find(`checkboxSelectRow-${hits[1].id}`);
+
+ expect(firstCheckBox.props().disabled).toBe(false);
+ expect(secondCheckBox.props().disabled).toBe(true);
+ // EUI changes the check "title" from "Select this row" to the reason to disable the checkbox
+ expect(secondCheckBox.props().title).toBe(reasonMessage);
+ });
+ });
});
diff --git a/packages/content-management/table_list/src/table_list_view.tsx b/packages/content-management/table_list/src/table_list_view.tsx
index 1612649f80bda..2191a3c9b7eee 100644
--- a/packages/content-management/table_list/src/table_list_view.tsx
+++ b/packages/content-management/table_list/src/table_list_view.tsx
@@ -42,6 +42,7 @@ import { getReducer } from './reducer';
import type { SortColumnField } from './components';
import { useTags } from './use_tags';
import { useInRouterContext, useUrlState } from './use_url_state';
+import { RowActions, TableItemsRowActions } from './types';
interface ContentEditorConfig
extends Pick {
@@ -67,6 +68,11 @@ export interface Props RowActions | undefined;
children?: ReactNode | undefined;
findItems(
searchQuery: string,
@@ -241,6 +247,7 @@ function TableListViewComp({
urlStateEnabled = true,
customTableColumn,
emptyPrompt,
+ rowItemActions,
findItems,
createItem,
editItem,
@@ -580,6 +587,15 @@ function TableListViewComp({
return selectedIds.map((selectedId) => itemsById[selectedId]);
}, [selectedIds, itemsById]);
+ const tableItemsRowActions = useMemo(() => {
+ return items.reduce((acc, item) => {
+ return {
+ ...acc,
+ [item.id]: rowItemActions ? rowItemActions(item) : undefined,
+ };
+ }, {});
+ }, [items, rowItemActions]);
+
// ------------
// Callbacks
// ------------
@@ -854,6 +870,20 @@ function TableListViewComp({
};
}, []);
+ const PageTemplate = useMemo(() => {
+ return withoutPageTemplateWrapper
+ ? ((({
+ children: _children,
+ 'data-test-subj': dataTestSubj,
+ }: {
+ children: React.ReactNode;
+ ['data-test-subj']?: string;
+ }) => (
+ {_children}
+ )) as unknown as typeof KibanaPageTemplate)
+ : KibanaPageTemplate;
+ }, [withoutPageTemplateWrapper]);
+
// ------------
// Render
// ------------
@@ -861,10 +891,6 @@ function TableListViewComp({
return null;
}
- const PageTemplate = withoutPageTemplateWrapper
- ? (React.Fragment as unknown as typeof KibanaPageTemplate)
- : KibanaPageTemplate;
-
if (!showFetchError && hasNoItems) {
return (
@@ -929,6 +955,7 @@ function TableListViewComp({
tagsToTableItemMap={tagsToTableItemMap}
deleteItems={deleteItems}
tableCaption={tableListTitle}
+ tableItemsRowActions={tableItemsRowActions}
onTableChange={onTableChange}
onTableSearchChange={onTableSearchChange}
onSortChange={onSortChange}
diff --git a/packages/content-management/table_list/src/types.ts b/packages/content-management/table_list/src/types.ts
index 0e716e6d59cf3..c8e734a289451 100644
--- a/packages/content-management/table_list/src/types.ts
+++ b/packages/content-management/table_list/src/types.ts
@@ -12,3 +12,16 @@ export interface Tag {
description: string;
color: string;
}
+
+export type TableRowAction = 'delete';
+
+export type RowActions = {
+ [action in TableRowAction]?: {
+ enabled: boolean;
+ reason?: string;
+ };
+};
+
+export interface TableItemsRowActions {
+ [id: string]: RowActions | undefined;
+}
diff --git a/packages/shared-ux/file/types/base_file_client.ts b/packages/shared-ux/file/types/base_file_client.ts
index 4a00f2de00516..52d1ca09fd170 100644
--- a/packages/shared-ux/file/types/base_file_client.ts
+++ b/packages/shared-ux/file/types/base_file_client.ts
@@ -27,6 +27,7 @@ export interface BaseFilesClient {
find: (
args: {
kind?: string | string[];
+ kindToExclude?: string | string[];
status?: string | string[];
extension?: string | string[];
name?: string | string[];
diff --git a/packages/shared-ux/file/types/index.ts b/packages/shared-ux/file/types/index.ts
index 4c49124f7149f..86b9e47fdab43 100644
--- a/packages/shared-ux/file/types/index.ts
+++ b/packages/shared-ux/file/types/index.ts
@@ -250,6 +250,22 @@ export interface FileKindBrowser extends FileKindBase {
* @default 4MiB
*/
maxSizeBytes?: number;
+ /**
+ * Allowed actions that can be done in the File Management UI. If not provided, all actions are allowed
+ *
+ */
+ managementUiActions?: {
+ /** Allow files to be listed in management UI */
+ list?: {
+ enabled: boolean;
+ };
+ /** Allow files to be deleted in management UI */
+ delete?: {
+ enabled: boolean;
+ /** If delete is not enabled in management UI, specify the reason (will appear in a tooltip). */
+ reason?: string;
+ };
+ };
}
/**
diff --git a/src/plugins/files/public/plugin.ts b/src/plugins/files/public/plugin.ts
index 54646e9199f9a..13828d0ee366c 100644
--- a/src/plugins/files/public/plugin.ts
+++ b/src/plugins/files/public/plugin.ts
@@ -35,7 +35,10 @@ export interface FilesSetup {
registerFileKind(fileKind: FileKindBrowser): void;
}
-export type FilesStart = Pick;
+export type FilesStart = Pick & {
+ getFileKindDefinition: (id: string) => FileKindBrowser;
+ getAllFindKindDefinitions: () => FileKindBrowser[];
+};
/**
* Bringing files to Kibana
@@ -77,6 +80,12 @@ export class FilesPlugin implements Plugin {
start(core: CoreStart): FilesStart {
return {
filesClientFactory: this.filesClientFactory!,
+ getFileKindDefinition: (id: string): FileKindBrowser => {
+ return this.registry.get(id);
+ },
+ getAllFindKindDefinitions: (): FileKindBrowser[] => {
+ return this.registry.getAll();
+ },
};
}
}
diff --git a/src/plugins/files/server/file_client/file_metadata_client/adapters/query_filters.ts b/src/plugins/files/server/file_client/file_metadata_client/adapters/query_filters.ts
index 0f453a1b81e6a..014e57b41d2b1 100644
--- a/src/plugins/files/server/file_client/file_metadata_client/adapters/query_filters.ts
+++ b/src/plugins/files/server/file_client/file_metadata_client/adapters/query_filters.ts
@@ -24,6 +24,7 @@ export function filterArgsToKuery({
extension,
mimeType,
kind,
+ kindToExclude,
meta,
name,
status,
@@ -50,12 +51,27 @@ export function filterArgsToKuery({
}
};
+ const addExcludeFilters = (fieldName: keyof FileMetadata | string, values: string[] = []) => {
+ if (values.length) {
+ const andExpressions = values
+ .filter(Boolean)
+ .map((value) =>
+ nodeTypes.function.buildNode(
+ 'not',
+ nodeBuilder.is(`${attrPrefix}.${fieldName}`, escapeKuery(value))
+ )
+ );
+ kueryExpressions.push(nodeBuilder.and(andExpressions));
+ }
+ };
+
addFilters('name', name, true);
addFilters('FileKind', kind);
addFilters('Status', status);
addFilters('extension', extension);
addFilters('mime_type', mimeType);
addFilters('user.id', user);
+ addExcludeFilters('FileKind', kindToExclude);
if (meta) {
const addMetaFilters = pipe(
diff --git a/src/plugins/files/server/file_service/file_action_types.ts b/src/plugins/files/server/file_service/file_action_types.ts
index 4247f567802ed..96795ac93b387 100644
--- a/src/plugins/files/server/file_service/file_action_types.ts
+++ b/src/plugins/files/server/file_service/file_action_types.ts
@@ -82,6 +82,10 @@ export interface FindFileArgs extends Pagination {
* File kind(s), see {@link FileKind}.
*/
kind?: string[];
+ /**
+ * File kind(s) to exclude from search, see {@link FileKind}.
+ */
+ kindToExclude?: string[];
/**
* File name(s).
*/
diff --git a/src/plugins/files/server/integration_tests/file_service.test.ts b/src/plugins/files/server/integration_tests/file_service.test.ts
index 25d7f463de03a..3492eb8e5f12c 100644
--- a/src/plugins/files/server/integration_tests/file_service.test.ts
+++ b/src/plugins/files/server/integration_tests/file_service.test.ts
@@ -157,26 +157,39 @@ describe('FileService', () => {
createDisposableFile({ fileKind, name: 'foo-2' }),
createDisposableFile({ fileKind, name: 'foo-3' }),
createDisposableFile({ fileKind, name: 'test-3' }),
+ createDisposableFile({ fileKind: fileKindNonDefault, name: 'foo-1' }),
]);
{
const { files, total } = await fileService.find({
- kind: [fileKind],
+ kind: [fileKind, fileKindNonDefault],
name: ['foo*'],
perPage: 2,
page: 1,
});
expect(files.length).toBe(2);
- expect(total).toBe(3);
+ expect(total).toBe(4);
}
{
const { files, total } = await fileService.find({
- kind: [fileKind],
+ kind: [fileKind, fileKindNonDefault],
name: ['foo*'],
perPage: 2,
page: 2,
});
- expect(files.length).toBe(1);
+ expect(files.length).toBe(2);
+ expect(total).toBe(4);
+ }
+
+ // Filter out fileKind
+ {
+ const { files, total } = await fileService.find({
+ kindToExclude: [fileKindNonDefault],
+ name: ['foo*'],
+ perPage: 10,
+ page: 1,
+ });
+ expect(files.length).toBe(3); // foo-1 from fileKindNonDefault not returned
expect(total).toBe(3);
}
});
diff --git a/src/plugins/files/server/routes/find.ts b/src/plugins/files/server/routes/find.ts
index a81a9d2ea5220..6749e06254100 100644
--- a/src/plugins/files/server/routes/find.ts
+++ b/src/plugins/files/server/routes/find.ts
@@ -30,6 +30,7 @@ export function toArrayOrUndefined(val?: string | string[]): undefined | string[
const rt = {
body: schema.object({
kind: schema.maybe(stringOrArrayOfStrings),
+ kindToExclude: schema.maybe(stringOrArrayOfStrings),
status: schema.maybe(stringOrArrayOfStrings),
extension: schema.maybe(stringOrArrayOfStrings),
name: schema.maybe(nameStringOrArrayOfNameStrings),
@@ -50,12 +51,13 @@ export type Endpoint = CreateRouteDefinition<
const handler: CreateHandler = async ({ files }, req, res) => {
const { fileService } = await files;
const {
- body: { meta, extension, kind, name, status },
+ body: { meta, extension, kind, name, status, kindToExclude },
query,
} = req;
const { files: results, total } = await fileService.asCurrentUser().find({
kind: toArrayOrUndefined(kind),
+ kindToExclude: toArrayOrUndefined(kindToExclude),
name: toArrayOrUndefined(name),
status: toArrayOrUndefined(status),
extension: toArrayOrUndefined(extension),
diff --git a/src/plugins/files_management/public/app.tsx b/src/plugins/files_management/public/app.tsx
index becdd05fa0e2c..3ee4e5f52720c 100644
--- a/src/plugins/files_management/public/app.tsx
+++ b/src/plugins/files_management/public/app.tsx
@@ -12,20 +12,33 @@ import { EuiButtonEmpty } from '@elastic/eui';
import { TableListView, UserContentCommonSchema } from '@kbn/content-management-table-list';
import numeral from '@elastic/numeral';
import type { FileJSON } from '@kbn/files-plugin/common';
+
import { useFilesManagementContext } from './context';
import { i18nTexts } from './i18n_texts';
import { EmptyPrompt, DiagnosticsFlyout, FileFlyout } from './components';
-type FilesUserContentSchema = UserContentCommonSchema;
+type FilesUserContentSchema = Omit & {
+ attributes: {
+ title: string;
+ description?: string;
+ fileKind: string;
+ };
+};
function naivelyFuzzify(query: string): string {
return query.includes('*') ? query : `*${query}*`;
}
export const App: FunctionComponent = () => {
- const { filesClient } = useFilesManagementContext();
+ const { filesClient, getFileKindDefinition, getAllFindKindDefinitions } =
+ useFilesManagementContext();
const [showDiagnosticsFlyout, setShowDiagnosticsFlyout] = useState(false);
const [selectedFile, setSelectedFile] = useState(undefined);
+
+ const kindToExcludeFromSearch = getAllFindKindDefinitions()
+ .filter(({ managementUiActions }) => managementUiActions?.list?.enabled === false)
+ .map(({ id }) => id);
+
return (
@@ -37,7 +50,10 @@ export const App: FunctionComponent = () => {
entityNamePlural={i18nTexts.entityNamePlural}
findItems={(searchQuery) =>
filesClient
- .find({ name: searchQuery ? naivelyFuzzify(searchQuery) : undefined })
+ .find({
+ name: searchQuery ? naivelyFuzzify(searchQuery) : undefined,
+ kindToExclude: kindToExcludeFromSearch,
+ })
.then(({ files, total }) => ({
hits: files.map((file) => ({
id: file.id,
@@ -71,6 +87,12 @@ export const App: FunctionComponent = () => {
{i18nTexts.diagnosticsFlyoutTitle}
,
]}
+ rowItemActions={({ attributes }) => {
+ const definition = getFileKindDefinition(attributes.fileKind);
+ return {
+ delete: definition?.managementUiActions?.delete,
+ };
+ }}
/>
{showDiagnosticsFlyout && (
setShowDiagnosticsFlyout(false)} />
diff --git a/src/plugins/files_management/public/context.tsx b/src/plugins/files_management/public/context.tsx
index 18f031b84e5c1..0688c5a7edecb 100644
--- a/src/plugins/files_management/public/context.tsx
+++ b/src/plugins/files_management/public/context.tsx
@@ -12,9 +12,16 @@ import type { AppContext } from './types';
const FilesManagementAppContext = createContext(null as unknown as AppContext);
-export const FilesManagementAppContextProvider: FC = ({ children, filesClient }) => {
+export const FilesManagementAppContextProvider: FC = ({
+ children,
+ filesClient,
+ getFileKindDefinition,
+ getAllFindKindDefinitions,
+}) => {
return (
-
+
{children}
);
diff --git a/src/plugins/files_management/public/i18n_texts.ts b/src/plugins/files_management/public/i18n_texts.ts
index c5f4956af372f..d430038dcdddc 100644
--- a/src/plugins/files_management/public/i18n_texts.ts
+++ b/src/plugins/files_management/public/i18n_texts.ts
@@ -101,4 +101,7 @@ export const i18nTexts = {
defaultMessage: 'Upload error',
}),
} as Record,
+ rowCheckboxDisabled: i18n.translate('filesManagement.table.checkBoxDisabledLabel', {
+ defaultMessage: 'This file cannot be deleted.',
+ }),
};
diff --git a/src/plugins/files_management/public/mount_management_section.tsx b/src/plugins/files_management/public/mount_management_section.tsx
index 7dce1986237a7..9c7091516d46e 100755
--- a/src/plugins/files_management/public/mount_management_section.tsx
+++ b/src/plugins/files_management/public/mount_management_section.tsx
@@ -30,6 +30,10 @@ export const mountManagementSection = (
startDeps: StartDependencies,
{ element, history }: ManagementAppMountParams
) => {
+ const {
+ files: { filesClientFactory, getAllFindKindDefinitions, getFileKindDefinition },
+ } = startDeps;
+
ReactDOM.render(
@@ -41,7 +45,9 @@ export const mountManagementSection = (
}}
>
diff --git a/src/plugins/files_management/public/types.ts b/src/plugins/files_management/public/types.ts
index 2a73b69bea017..303d5e1c5d1a7 100755
--- a/src/plugins/files_management/public/types.ts
+++ b/src/plugins/files_management/public/types.ts
@@ -11,6 +11,8 @@ import { ManagementSetup } from '@kbn/management-plugin/public';
export interface AppContext {
filesClient: FilesClient;
+ getFileKindDefinition: FilesStart['getFileKindDefinition'];
+ getAllFindKindDefinitions: FilesStart['getAllFindKindDefinitions'];
}
export interface SetupDependencies {
From e951205f7e488ce0d2f53bae504c7f014a3bece3 Mon Sep 17 00:00:00 2001
From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com>
Date: Mon, 24 Apr 2023 12:00:32 -0400
Subject: [PATCH 16/36] [Security Solution][Endpoint] Define possible `execute`
response failure codes for use in the UI (#155316)
## Summary
- Updates the list of known Response Actions response codes with codes
for the `execute` response action
---
.../endpoint_action_generator.ts | 65 ++++++-------
.../common/endpoint/types/actions.ts | 1 +
.../integration_tests/execute_action.test.tsx | 34 +++++++
.../lib/endpoint_action_response_codes.ts | 91 +++++++++++++++++++
.../response_actions_log.test.tsx | 31 +++----
5 files changed, 173 insertions(+), 49 deletions(-)
diff --git a/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts b/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts
index 855dd3a3fe439..fdf75da0a134e 100644
--- a/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts
@@ -155,38 +155,37 @@ export class EndpointActionGenerator extends BaseDataGenerator {
TOutputType extends object = object,
TParameters extends EndpointActionDataParameterTypes = EndpointActionDataParameterTypes
>(
- overrides: Partial> = {}
+ overrides: DeepPartial> = {}
): ActionDetails {
- const details: ActionDetails = merge(
- {
- agents: ['agent-a'],
- command: 'isolate',
- completedAt: '2022-04-30T16:08:47.449Z',
- hosts: { 'agent-a': { name: 'Host-agent-a' } },
- id: '123',
- isCompleted: true,
- isExpired: false,
- wasSuccessful: true,
- errors: undefined,
- startedAt: '2022-04-27T16:08:47.449Z',
- status: 'successful',
- comment: 'thisisacomment',
- createdBy: 'auserid',
- parameters: undefined,
- outputs: {},
- agentState: {
- 'agent-a': {
- errors: undefined,
- isCompleted: true,
- completedAt: '2022-04-30T16:08:47.449Z',
- wasSuccessful: true,
- },
+ const details: ActionDetails = {
+ agents: ['agent-a'],
+ command: 'isolate',
+ completedAt: '2022-04-30T16:08:47.449Z',
+ hosts: { 'agent-a': { name: 'Host-agent-a' } },
+ id: '123',
+ isCompleted: true,
+ isExpired: false,
+ wasSuccessful: true,
+ errors: undefined,
+ startedAt: '2022-04-27T16:08:47.449Z',
+ status: 'successful',
+ comment: 'thisisacomment',
+ createdBy: 'auserid',
+ parameters: undefined,
+ outputs: {},
+ agentState: {
+ 'agent-a': {
+ errors: undefined,
+ isCompleted: true,
+ completedAt: '2022-04-30T16:08:47.449Z',
+ wasSuccessful: true,
},
},
- overrides
- );
+ };
- if (details.command === 'get-file') {
+ const command = overrides.command ?? details.command;
+
+ if (command === 'get-file') {
if (!details.parameters) {
(
details as ActionDetails<
@@ -213,7 +212,7 @@ export class EndpointActionGenerator extends BaseDataGenerator {
}
}
- if (details.command === 'execute') {
+ if (command === 'execute') {
if (!details.parameters) {
(
details as ActionDetails<
@@ -233,14 +232,17 @@ export class EndpointActionGenerator extends BaseDataGenerator {
[details.agents[0]]: this.generateExecuteActionResponseOutput({
content: {
output_file_id: getFileDownloadId(details, details.agents[0]),
- ...overrides.outputs?.[details.agents[0]].content,
+ ...(overrides.outputs?.[details.agents[0]]?.content ?? {}),
},
}),
};
}
}
- return details as unknown as ActionDetails;
+ return merge(details, overrides as ActionDetails) as unknown as ActionDetails<
+ TOutputType,
+ TParameters
+ >;
}
randomGetFileFailureCode(): string {
@@ -310,6 +312,7 @@ export class EndpointActionGenerator extends BaseDataGenerator {
{
type: 'json',
content: {
+ code: 'ra_execute_success_done',
stdout: this.randomChoice([
this.randomString(1280),
this.randomString(3580),
diff --git a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts
index d9082f8aa1d8c..f8f5da28943b8 100644
--- a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts
@@ -65,6 +65,7 @@ export interface ResponseActionGetFileOutputContent {
}
export interface ResponseActionExecuteOutputContent {
+ code: string;
/* The truncated 'tail' output of the command */
stdout: string;
/* The truncated 'tail' of any errors generated by the command */
diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/execute_action.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/execute_action.test.tsx
index c3f94871c8f67..8d5d8907924f6 100644
--- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/execute_action.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/execute_action.test.tsx
@@ -23,6 +23,8 @@ import { getEndpointAuthzInitialStateMock } from '../../../../../../common/endpo
import type { EndpointPrivileges } from '../../../../../../common/endpoint/types';
import { INSUFFICIENT_PRIVILEGES_FOR_COMMAND } from '../../../../../common/translations';
import type { HttpFetchOptionsWithPath } from '@kbn/core-http-browser';
+import { endpointActionResponseCodes } from '../../lib/endpoint_action_response_codes';
+import { EndpointActionGenerator } from '../../../../../../common/endpoint/data_generators/endpoint_action_generator';
jest.mock('../../../../../common/components/user_privileges');
jest.mock('../../../../../common/experimental_features_service');
@@ -180,4 +182,36 @@ describe('When using execute action from response actions console', () => {
);
});
});
+
+ it.each(
+ Object.keys(endpointActionResponseCodes).filter((key) => key.startsWith('ra_execute_error'))
+ )('should display known error message for response failure: %s', async (errorCode) => {
+ apiMocks.responseProvider.actionDetails.mockReturnValue({
+ data: new EndpointActionGenerator('seed').generateActionDetails({
+ command: 'execute',
+ errors: ['some error happen in endpoint'],
+ wasSuccessful: false,
+ outputs: {
+ 'agent-a': {
+ content: {
+ code: errorCode,
+ },
+ },
+ },
+ }),
+ });
+
+ const { getByTestId } = await render();
+ enterConsoleCommand(renderResult, 'execute --command="ls -l"');
+
+ await waitFor(() => {
+ expect(apiMocks.responseProvider.actionDetails).toHaveBeenCalled();
+ });
+
+ await waitFor(() => {
+ expect(getByTestId('execute-actionFailure')).toHaveTextContent(
+ endpointActionResponseCodes[errorCode]
+ );
+ });
+ });
});
diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/endpoint_action_response_codes.ts b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/endpoint_action_response_codes.ts
index 746b9201cd200..c2595c625b7fa 100644
--- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/endpoint_action_response_codes.ts
+++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/endpoint_action_response_codes.ts
@@ -123,6 +123,97 @@ const CODES = Object.freeze({
'xpack.securitySolution.endpointActionResponseCodes.killProcess.notPermittedSuccess',
{ defaultMessage: 'The provided process cannot be killed' }
),
+
+ // -----------------------------------------------------------------
+ // EXECUTE CODES
+ // -----------------------------------------------------------------
+
+ // Dev:
+ // Something interrupted preparing the zip: file read error, zip error. I think these should be rare,
+ // and should succeed on retry by the user or result in file-not-found. We might implement some retries
+ // internally but I'm leaning to the opinion that we should rather quickly send the feedback to the
+ // user to let them decide.
+ ra_execute_error_processing: i18n.translate(
+ 'xpack.securitySolution.endpointActionResponseCodes.execute.processingError',
+ {
+ defaultMessage: 'Unable to create execution output zip file.',
+ }
+ ),
+
+ // Dev:
+ // Executing timeout has been reached, the command was killed.
+ 'ra_execute_error_processing-timeout': i18n.translate(
+ 'xpack.securitySolution.endpointActionResponseCodes.execute.processingTimeout',
+ { defaultMessage: 'Command execution was terminated. It exceeded the provided timeout.' }
+ ),
+
+ // Dev:
+ // Execution was interrupted, for example: system shutdown, endpoint service stop/restart.
+ 'ra_execute_error_processing-interrupted': i18n.translate(
+ 'xpack.securitySolution.endpointActionResponseCodes.execute.processingInterrupted',
+ {
+ defaultMessage: 'Command execution was absolutely interrupted.',
+ }
+ ),
+
+ // Dev:
+ // Too many active execute actions, limit 10. Execute actions are allowed to run in parallel, we must
+ // take into account resource use impact on endpoint as customers are piky about CPU/MEM utilization.
+ 'ra_execute_error_to-many-requests': i18n.translate(
+ 'xpack.securitySolution.endpointActionResponseCodes.execute.toManyRequests',
+ {
+ defaultMessage: 'Too many concurrent command execution actions.',
+ }
+ ),
+
+ // Dev:
+ // generic failure (rare corner case, software bug, etc)
+ ra_execute_error_failure: i18n.translate(
+ 'xpack.securitySolution.endpointActionResponseCodes.execute.failure',
+ { defaultMessage: 'Unknown failure while executing command.' }
+ ),
+
+ // Dev:
+ // Max pending response zip uploads has been reached, limit 10. Endpoint can't use unlimited disk space.
+ 'ra_execute_error_disk-quota': i18n.translate(
+ 'xpack.securitySolution.endpointActionResponseCodes.execute.diskQuotaError',
+ {
+ defaultMessage: 'Too many pending command execution output zip files.',
+ }
+ ),
+
+ // Dev:
+ // The fleet upload API was unreachable (not just busy). This may mean policy misconfiguration, in which
+ // case health status in Kibana should indicate degraded, or maybe network configuration problems, or fleet
+ // server problems HTTP 500. This excludes offline status, where endpoint should just wait for network connection.
+ 'ra_execute_error_upload-api-unreachable': i18n.translate(
+ 'xpack.securitySolution.endpointActionResponseCodes.execute.uploadApiUnreachable',
+ {
+ defaultMessage:
+ 'Failed to upload command execution output zip file. Unable to reach Fleet Server upload API.',
+ }
+ ),
+
+ // Dev:
+ // Perhaps internet connection was too slow or unstable to upload all chunks before unique
+ // upload-id expired. Endpoint will re-try a bit, max 3 times.
+ 'ra_execute_error_upload-timeout': i18n.translate(
+ 'xpack.securitySolution.endpointActionResponseCodes.execute.outputUploadTimeout',
+ {
+ defaultMessage: 'Failed to upload command execution output zip file. Upload timed out',
+ }
+ ),
+
+ // DEV:
+ // Upload API could be busy, endpoint should periodically re-try (2 days = 192 x 15min, assuming
+ // that with 1Mbps 15min is enough to upload 100MB)
+ 'ra_execute_error_queue-timeout': i18n.translate(
+ 'xpack.securitySolution.endpointActionResponseCodes.execute.queueTimeout',
+ {
+ defaultMessage:
+ 'Failed to upload command execution output zip file. Timed out while queued waiting for Fleet Server',
+ }
+ ),
});
/**
diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/integration_tests/response_actions_log.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/integration_tests/response_actions_log.test.tsx
index 6360b55b1c06f..6fc62dd6cf851 100644
--- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/integration_tests/response_actions_log.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/integration_tests/response_actions_log.test.tsx
@@ -652,10 +652,14 @@ describe('Response actions history', () => {
});
it('should contain expected output accordions for `execute` action WITH execute operation privilege', async () => {
- const actionDetails = await getActionListMock({ actionCount: 1, commands: ['execute'] });
+ const actionListApiResponse = await getActionListMock({
+ actionCount: 1,
+ agentIds: ['agent-a'],
+ commands: ['execute'],
+ });
useGetEndpointActionListMock.mockReturnValue({
...getBaseMockedActionList(),
- data: actionDetails,
+ data: actionListApiResponse,
});
mockUseGetFileInfo = {
@@ -669,17 +673,7 @@ describe('Response actions history', () => {
isFetched: true,
error: null,
data: {
- data: {
- ...apiMocks.responseProvider.actionDetails({
- path: `/api/endpoint/action/${actionDetails.data[0].id}`,
- }).data,
- outputs: {
- [actionDetails.data[0].agents[0]]: {
- content: {},
- type: 'json',
- },
- },
- },
+ data: actionListApiResponse.data[0],
},
};
@@ -714,7 +708,11 @@ describe('Response actions history', () => {
});
useGetEndpointActionListMock.mockReturnValue({
...getBaseMockedActionList(),
- data: await getActionListMock({ actionCount: 1, commands: ['execute'] }),
+ data: await getActionListMock({
+ actionCount: 1,
+ commands: ['execute'],
+ agentIds: ['agent-a'],
+ }),
});
render();
@@ -723,10 +721,7 @@ describe('Response actions history', () => {
const expandButton = getByTestId(`${testPrefix}-expand-button`);
userEvent.click(expandButton);
- const executeAccordions = getByTestId(
- `${testPrefix}-actionsLogTray-executeResponseOutput-output`
- );
- expect(executeAccordions).toBeTruthy();
+ expect(getByTestId(`${testPrefix}-actionsLogTray-executeResponseOutput-output`));
});
it('should not contain full output download link in expanded row for `execute` action WITHOUT Actions Log privileges', async () => {
From 50e3ff2f23ea38396c58961d4282674b9e2da0dd Mon Sep 17 00:00:00 2001
From: Shahzad
Date: Mon, 24 Apr 2023 18:17:23 +0200
Subject: [PATCH 17/36] [Synthetics] Add license issued_to value in request
(#155600)
---
.../service_api_client.test.ts | 40 +++++++++++++++----
.../synthetics_service/service_api_client.ts | 12 +++---
.../synthetics_service/synthetics_service.ts | 12 +++---
3 files changed, 45 insertions(+), 19 deletions(-)
diff --git a/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.test.ts
index 22ec8e8a3213e..2ed86eb91ae52 100644
--- a/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.test.ts
+++ b/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.test.ts
@@ -14,6 +14,24 @@ import { UptimeServerSetup } from '../legacy_uptime/lib/adapters';
import { ServiceConfig } from '../../common/config';
import axios from 'axios';
import { LocationStatus, PublicLocations } from '../../common/runtime_types';
+import { LicenseGetResponse } from '@elastic/elasticsearch/lib/api/types';
+
+const licenseMock: LicenseGetResponse = {
+ license: {
+ status: 'active',
+ uid: '1d34eb9f-e66f-47d1-8d24-cd60d187587a',
+ type: 'trial',
+ issue_date: '2022-05-05T14:25:00.732Z',
+ issue_date_in_millis: 165176070074432,
+ expiry_date: '2022-06-04T14:25:00.732Z',
+ expiry_date_in_millis: 165435270073332,
+ max_nodes: 1000,
+ max_resource_units: null,
+ issued_to: '2c515bd215ce444441f83ffd36a9d3d2546',
+ issuer: 'elasticsearch',
+ start_date_in_millis: -1,
+ },
+};
jest.mock('axios', () => jest.fn());
jest.mock('@kbn/server-http-tools', () => ({
@@ -167,7 +185,7 @@ describe('callAPI', () => {
await apiClient.callAPI('POST', {
monitors: testMonitors,
output,
- licenseLevel: 'trial',
+ license: licenseMock.license,
});
expect(spy).toHaveBeenCalledTimes(3);
@@ -181,7 +199,7 @@ describe('callAPI', () => {
monitor.locations.some((loc: any) => loc.id === 'us_central')
),
output,
- licenseLevel: 'trial',
+ license: licenseMock.license,
},
'POST',
devUrl
@@ -195,7 +213,7 @@ describe('callAPI', () => {
monitor.locations.some((loc: any) => loc.id === 'us_central_qa')
),
output,
- licenseLevel: 'trial',
+ license: licenseMock.license,
},
'POST',
'https://qa.service.elstc.co'
@@ -209,7 +227,7 @@ describe('callAPI', () => {
monitor.locations.some((loc: any) => loc.id === 'us_central_staging')
),
output,
- licenseLevel: 'trial',
+ license: licenseMock.license,
},
'POST',
'https://qa.service.stg.co'
@@ -223,6 +241,7 @@ describe('callAPI', () => {
output,
stack_version: '8.7.0',
license_level: 'trial',
+ license_issued_to: '2c515bd215ce444441f83ffd36a9d3d2546',
},
headers: {
Authorization: 'Basic ZGV2OjEyMzQ1',
@@ -242,6 +261,7 @@ describe('callAPI', () => {
output,
stack_version: '8.7.0',
license_level: 'trial',
+ license_issued_to: '2c515bd215ce444441f83ffd36a9d3d2546',
},
headers: {
Authorization: 'Basic ZGV2OjEyMzQ1',
@@ -261,6 +281,7 @@ describe('callAPI', () => {
output,
stack_version: '8.7.0',
license_level: 'trial',
+ license_issued_to: '2c515bd215ce444441f83ffd36a9d3d2546',
},
headers: {
Authorization: 'Basic ZGV2OjEyMzQ1',
@@ -324,7 +345,7 @@ describe('callAPI', () => {
await apiClient.callAPI('POST', {
monitors: testMonitors,
output,
- licenseLevel: 'platinum',
+ license: licenseMock.license,
});
expect(axiosSpy).toHaveBeenNthCalledWith(1, {
@@ -333,7 +354,8 @@ describe('callAPI', () => {
is_edit: undefined,
output,
stack_version: '8.7.0',
- license_level: 'platinum',
+ license_level: 'trial',
+ license_issued_to: '2c515bd215ce444441f83ffd36a9d3d2546',
},
headers: {
'x-kibana-version': '8.7.0',
@@ -376,7 +398,7 @@ describe('callAPI', () => {
await apiClient.runOnce({
monitors: testMonitors,
output,
- licenseLevel: 'trial',
+ license: licenseMock.license,
});
expect(axiosSpy).toHaveBeenNthCalledWith(1, {
@@ -386,6 +408,7 @@ describe('callAPI', () => {
output,
stack_version: '8.7.0',
license_level: 'trial',
+ license_issued_to: '2c515bd215ce444441f83ffd36a9d3d2546',
},
headers: {
'x-kibana-version': '8.7.0',
@@ -428,7 +451,7 @@ describe('callAPI', () => {
await apiClient.syncMonitors({
monitors: testMonitors,
output,
- licenseLevel: 'trial',
+ license: licenseMock.license,
});
expect(axiosSpy).toHaveBeenNthCalledWith(1, {
@@ -438,6 +461,7 @@ describe('callAPI', () => {
output,
stack_version: '8.7.0',
license_level: 'trial',
+ license_issued_to: '2c515bd215ce444441f83ffd36a9d3d2546',
},
headers: {
'x-kibana-version': '8.7.0',
diff --git a/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.ts b/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.ts
index bfc872f3680a8..4fea1d04b64e4 100644
--- a/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.ts
+++ b/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.ts
@@ -11,6 +11,7 @@ import { catchError, tap } from 'rxjs/operators';
import * as https from 'https';
import { SslConfig } from '@kbn/server-http-tools';
import { Logger } from '@kbn/core/server';
+import { LicenseGetLicenseInformation } from '@elastic/elasticsearch/lib/api/types';
import { UptimeServerSetup } from '../legacy_uptime/lib/adapters';
import { sendErrorTelemetryEvents } from '../routes/telemetry/monitor_upgrade_sender';
import { MonitorFields, PublicLocations, ServiceLocationErrors } from '../../common/runtime_types';
@@ -27,7 +28,7 @@ export interface ServiceData {
};
endpoint?: 'monitors' | 'runOnce' | 'sync';
isEdit?: boolean;
- licenseLevel: string;
+ license: LicenseGetLicenseInformation;
}
export class ServiceAPIClient {
@@ -142,7 +143,7 @@ export class ServiceAPIClient {
async callAPI(
method: 'POST' | 'PUT' | 'DELETE',
- { monitors: allMonitors, output, endpoint, isEdit, licenseLevel }: ServiceData
+ { monitors: allMonitors, output, endpoint, isEdit, license }: ServiceData
) {
if (this.username === TEST_SERVICE_USERNAME) {
// we don't want to call service while local integration tests are running
@@ -159,7 +160,7 @@ export class ServiceAPIClient {
);
if (locMonitors.length > 0) {
const promise = this.callServiceEndpoint(
- { monitors: locMonitors, isEdit, endpoint, output, licenseLevel },
+ { monitors: locMonitors, isEdit, endpoint, output, license },
method,
url
);
@@ -200,7 +201,7 @@ export class ServiceAPIClient {
}
async callServiceEndpoint(
- { monitors, output, endpoint = 'monitors', isEdit, licenseLevel }: ServiceData,
+ { monitors, output, endpoint = 'monitors', isEdit, license }: ServiceData,
method: 'POST' | 'PUT' | 'DELETE',
baseUrl: string
) {
@@ -233,7 +234,8 @@ export class ServiceAPIClient {
output,
stack_version: this.stackVersion,
is_edit: isEdit,
- license_level: licenseLevel,
+ license_level: license.type,
+ license_issued_to: license.issued_to,
},
headers: authHeader,
httpsAgent: this.getHttpsAgent(baseUrl),
diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts
index bd9c0032984c3..e4ef7cbbb6e67 100644
--- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts
+++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts
@@ -307,7 +307,7 @@ export class SyntheticsService {
this.syncErrors = await this.apiClient.post({
monitors,
output,
- licenseLevel: license.type,
+ license,
});
}
return this.syncErrors;
@@ -329,7 +329,7 @@ export class SyntheticsService {
monitors,
output,
isEdit,
- licenseLevel: license.type,
+ license,
};
this.syncErrors = await this.apiClient.put(data);
@@ -372,7 +372,7 @@ export class SyntheticsService {
service.syncErrors = await this.apiClient.syncMonitors({
monitors,
output,
- licenseLevel: license.type,
+ license,
});
} catch (e) {
sendErrorTelemetryEvents(service.logger, service.server.telemetry, {
@@ -406,7 +406,7 @@ export class SyntheticsService {
return await this.apiClient.runOnce({
monitors,
output,
- licenseLevel: license.type,
+ license,
});
} catch (e) {
this.logger.error(e);
@@ -429,7 +429,7 @@ export class SyntheticsService {
const data = {
output,
monitors: this.formatConfigs(configs),
- licenseLevel: license.type,
+ license,
};
return await this.apiClient.delete(data);
}
@@ -453,7 +453,7 @@ export class SyntheticsService {
const data = {
output,
monitors,
- licenseLevel: license.type,
+ license,
};
return await this.apiClient.delete(data);
}
From 3e94b43aa28f12538547217a0eeec224e7fa4263 Mon Sep 17 00:00:00 2001
From: Walter Rafelsberger
Date: Mon, 24 Apr 2023 18:47:44 +0200
Subject: [PATCH 18/36] [ML] AIOps: Link from Explain Log Rate Spikes to Log
Pattern Analysis (#155121)
Adds table actions to Explain Log Rate Spikes to be able to drill down to Log Pattern Analysis.
---
.../public/application/utils/url_state.ts | 45 +++++
.../explain_log_rate_spikes_app_state.tsx | 30 ----
.../explain_log_rate_spikes_page.tsx | 10 +-
.../category_table/category_table.tsx | 4 +-
.../log_categorization_page.tsx | 22 ++-
.../log_categorization/use_discover_links.ts | 2 +-
.../spike_analysis_table.tsx | 4 +-
.../spike_analysis_table_groups.tsx | 4 +-
.../table_action_button.tsx | 62 +++++++
.../use_copy_to_clipboard_action.test.tsx | 26 ++-
.../use_copy_to_clipboard_action.tsx | 20 ++-
.../use_view_in_discover_action.tsx | 36 ++--
...se_view_in_log_pattern_analysis_action.tsx | 108 ++++++++++++
x-pack/plugins/aiops/public/hooks/use_data.ts | 2 +-
x-pack/plugins/aiops/tsconfig.json | 1 +
.../apps/aiops/explain_log_rate_spikes.ts | 158 +++++++++++-------
.../test/functional/apps/aiops/test_data.ts | 106 ++++++++++++
x-pack/test/functional/apps/aiops/types.ts | 12 +-
...n_log_rate_spikes_analysis_groups_table.ts | 50 ++++++
.../explain_log_rate_spikes_data_generator.ts | 8 +
.../aiops/explain_log_rate_spikes_page.ts | 8 +-
.../test/functional/services/aiops/index.ts | 3 +
.../aiops/log_pattern_analysis_page.ts | 42 +++++
23 files changed, 631 insertions(+), 132 deletions(-)
create mode 100644 x-pack/plugins/aiops/public/application/utils/url_state.ts
create mode 100644 x-pack/plugins/aiops/public/components/spike_analysis_table/table_action_button.tsx
create mode 100644 x-pack/plugins/aiops/public/components/spike_analysis_table/use_view_in_log_pattern_analysis_action.tsx
create mode 100644 x-pack/test/functional/services/aiops/log_pattern_analysis_page.ts
diff --git a/x-pack/plugins/aiops/public/application/utils/url_state.ts b/x-pack/plugins/aiops/public/application/utils/url_state.ts
new file mode 100644
index 0000000000000..9fdaa443f4c75
--- /dev/null
+++ b/x-pack/plugins/aiops/public/application/utils/url_state.ts
@@ -0,0 +1,45 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
+
+import type { Filter, Query } from '@kbn/es-query';
+import { isPopulatedObject } from '@kbn/ml-is-populated-object';
+
+import { SEARCH_QUERY_LANGUAGE, SearchQueryLanguage } from './search_utils';
+
+const defaultSearchQuery = {
+ match_all: {},
+};
+
+export interface AiOpsPageUrlState {
+ pageKey: 'AIOPS_INDEX_VIEWER';
+ pageUrlState: AiOpsIndexBasedAppState;
+}
+
+export interface AiOpsIndexBasedAppState {
+ searchString?: Query['query'];
+ searchQuery?: estypes.QueryDslQueryContainer;
+ searchQueryLanguage: SearchQueryLanguage;
+ filters?: Filter[];
+}
+
+export type AiOpsFullIndexBasedAppState = Required;
+
+export const getDefaultAiOpsListState = (
+ overrides?: Partial
+): AiOpsFullIndexBasedAppState => ({
+ searchString: '',
+ searchQuery: defaultSearchQuery,
+ searchQueryLanguage: SEARCH_QUERY_LANGUAGE.KUERY,
+ filters: [],
+ ...overrides,
+});
+
+export const isFullAiOpsListState = (arg: unknown): arg is AiOpsFullIndexBasedAppState => {
+ return isPopulatedObject(arg, Object.keys(getDefaultAiOpsListState()));
+};
diff --git a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_app_state.tsx b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_app_state.tsx
index ce69ea9fea3ae..b30e3a7d1e6fb 100644
--- a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_app_state.tsx
+++ b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_app_state.tsx
@@ -10,7 +10,6 @@ import { pick } from 'lodash';
import { EuiCallOut } from '@elastic/eui';
-import type { Filter, Query } from '@kbn/es-query';
import { i18n } from '@kbn/i18n';
import type { SavedSearch } from '@kbn/discover-plugin/public';
import type { DataView } from '@kbn/data-views-plugin/public';
@@ -21,7 +20,6 @@ import { DatePickerContextProvider } from '@kbn/ml-date-picker';
import { UI_SETTINGS } from '@kbn/data-plugin/common';
import { toMountPoint, wrapWithTheme } from '@kbn/kibana-react-plugin/public';
-import { SEARCH_QUERY_LANGUAGE, SearchQueryLanguage } from '../../application/utils/search_utils';
import type { AiopsAppDependencies } from '../../hooks/use_aiops_app_context';
import { AiopsAppContext } from '../../hooks/use_aiops_app_context';
import { DataSourceContext } from '../../hooks/use_data_source';
@@ -42,34 +40,6 @@ export interface ExplainLogRateSpikesAppStateProps {
appDependencies: AiopsAppDependencies;
}
-const defaultSearchQuery = {
- match_all: {},
-};
-
-export interface AiOpsPageUrlState {
- pageKey: 'AIOPS_INDEX_VIEWER';
- pageUrlState: AiOpsIndexBasedAppState;
-}
-
-export interface AiOpsIndexBasedAppState {
- searchString?: Query['query'];
- searchQuery?: Query['query'];
- searchQueryLanguage: SearchQueryLanguage;
- filters?: Filter[];
-}
-
-export const getDefaultAiOpsListState = (
- overrides?: Partial
-): Required => ({
- searchString: '',
- searchQuery: defaultSearchQuery,
- searchQueryLanguage: SEARCH_QUERY_LANGUAGE.KUERY,
- filters: [],
- ...overrides,
-});
-
-export const restorableDefaults = getDefaultAiOpsListState();
-
export const ExplainLogRateSpikesAppState: FC = ({
dataView,
savedSearch,
diff --git a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_page.tsx b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_page.tsx
index 80640f59901bc..fb9ce01c63391 100644
--- a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_page.tsx
+++ b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_page.tsx
@@ -7,6 +7,7 @@
import React, { useCallback, useEffect, useState, FC } from 'react';
+import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import {
EuiEmptyPrompt,
EuiFlexGroup,
@@ -28,6 +29,10 @@ import { useDataSource } from '../../hooks/use_data_source';
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
import { SearchQueryLanguage } from '../../application/utils/search_utils';
import { useData } from '../../hooks/use_data';
+import {
+ getDefaultAiOpsListState,
+ type AiOpsPageUrlState,
+} from '../../application/utils/url_state';
import { DocumentCountContent } from '../document_count_content/document_count_content';
import { SearchPanel } from '../search_panel';
@@ -35,7 +40,6 @@ import type { GroupTableItem } from '../spike_analysis_table/types';
import { useSpikeAnalysisTableRowContext } from '../spike_analysis_table/spike_analysis_table_row_provider';
import { PageHeader } from '../page_header';
-import { restorableDefaults, type AiOpsPageUrlState } from './explain_log_rate_spikes_app_state';
import { ExplainLogRateSpikesAnalysis } from './explain_log_rate_spikes_analysis';
function getDocumentCountStatsSplitLabel(
@@ -66,7 +70,7 @@ export const ExplainLogRateSpikesPage: FC = () => {
const [aiopsListState, setAiopsListState] = usePageUrlState(
'AIOPS_INDEX_VIEWER',
- restorableDefaults
+ getDefaultAiOpsListState()
);
const [globalState, setGlobalState] = useUrlState('_g');
@@ -80,7 +84,7 @@ export const ExplainLogRateSpikesPage: FC = () => {
const setSearchParams = useCallback(
(searchParams: {
- searchQuery: Query['query'];
+ searchQuery: estypes.QueryDslQueryContainer;
searchString: Query['query'];
queryLanguage: SearchQueryLanguage;
filters: Filter[];
diff --git a/x-pack/plugins/aiops/public/components/log_categorization/category_table/category_table.tsx b/x-pack/plugins/aiops/public/components/log_categorization/category_table/category_table.tsx
index c888694a7b0c3..747f90d542354 100644
--- a/x-pack/plugins/aiops/public/components/log_categorization/category_table/category_table.tsx
+++ b/x-pack/plugins/aiops/public/components/log_categorization/category_table/category_table.tsx
@@ -25,7 +25,7 @@ import {
import { useDiscoverLinks } from '../use_discover_links';
import { MiniHistogram } from '../../mini_histogram';
import { useEuiTheme } from '../../../hooks/use_eui_theme';
-import type { AiOpsIndexBasedAppState } from '../../explain_log_rate_spikes/explain_log_rate_spikes_app_state';
+import type { AiOpsFullIndexBasedAppState } from '../../../application/utils/url_state';
import type { EventRate, Category, SparkLinesPerCategory } from '../use_categorize_request';
import { useTableState } from './use_table_state';
@@ -42,7 +42,7 @@ interface Props {
dataViewId: string;
selectedField: string | undefined;
timefilter: TimefilterContract;
- aiopsListState: Required;
+ aiopsListState: AiOpsFullIndexBasedAppState;
pinnedCategory: Category | null;
setPinnedCategory: (category: Category | null) => void;
selectedCategory: Category | null;
diff --git a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx
index b673d8a498a21..7c7d0001aea42 100644
--- a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx
+++ b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx
@@ -4,8 +4,10 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
+
import React, { FC, useState, useEffect, useCallback, useMemo } from 'react';
+import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import {
EuiButton,
EuiSpacer,
@@ -21,14 +23,18 @@ import {
import { Filter, Query } from '@kbn/es-query';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
-import { useUrlState } from '@kbn/ml-url-state';
+import { usePageUrlState, useUrlState } from '@kbn/ml-url-state';
import { useDataSource } from '../../hooks/use_data_source';
import { useData } from '../../hooks/use_data';
import type { SearchQueryLanguage } from '../../application/utils/search_utils';
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
+import {
+ getDefaultAiOpsListState,
+ isFullAiOpsListState,
+ type AiOpsPageUrlState,
+} from '../../application/utils/url_state';
-import { restorableDefaults } from '../explain_log_rate_spikes/explain_log_rate_spikes_app_state';
import { SearchPanel } from '../search_panel';
import { PageHeader } from '../page_header';
@@ -47,7 +53,10 @@ export const LogCategorizationPage: FC = () => {
const { dataView, savedSearch } = useDataSource();
const { runCategorizeRequest, cancelRequest } = useCategorizeRequest();
- const [aiopsListState, setAiopsListState] = useState(restorableDefaults);
+ const [aiopsListState, setAiopsListState] = usePageUrlState(
+ 'AIOPS_INDEX_VIEWER',
+ getDefaultAiOpsListState()
+ );
const [globalState, setGlobalState] = useUrlState('_g');
const [selectedField, setSelectedField] = useState();
const [selectedCategory, setSelectedCategory] = useState(null);
@@ -76,7 +85,7 @@ export const LogCategorizationPage: FC = () => {
const setSearchParams = useCallback(
(searchParams: {
- searchQuery: Query['query'];
+ searchQuery: estypes.QueryDslQueryContainer;
searchString: Query['query'];
queryLanguage: SearchQueryLanguage;
filters: Filter[];
@@ -289,7 +298,10 @@ export const LogCategorizationPage: FC = () => {
fieldSelected={selectedField !== null}
/>
- {selectedField !== undefined && categories !== null && categories.length > 0 ? (
+ {selectedField !== undefined &&
+ categories !== null &&
+ categories.length > 0 &&
+ isFullAiOpsListState(aiopsListState) ? (
= ({
const copyToClipBoardAction = useCopyToClipboardAction();
const viewInDiscoverAction = useViewInDiscoverAction(dataViewId);
+ const viewInLogPatternAnalysisAction = useViewInLogPatternAnalysisAction(dataViewId);
const columns: Array> = [
{
@@ -238,7 +240,7 @@ export const SpikeAnalysisTable: FC = ({
name: i18n.translate('xpack.aiops.spikeAnalysisTable.actionsColumnName', {
defaultMessage: 'Actions',
}),
- actions: [viewInDiscoverAction, copyToClipBoardAction],
+ actions: [viewInDiscoverAction, viewInLogPatternAnalysisAction, copyToClipBoardAction],
width: ACTIONS_COLUMN_WIDTH,
valign: 'middle',
},
diff --git a/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_groups.tsx b/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_groups.tsx
index b319db0088d4d..ca55b43907bd6 100644
--- a/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_groups.tsx
+++ b/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_groups.tsx
@@ -40,6 +40,7 @@ import { useSpikeAnalysisTableRowContext } from './spike_analysis_table_row_prov
import type { GroupTableItem } from './types';
import { useCopyToClipboardAction } from './use_copy_to_clipboard_action';
import { useViewInDiscoverAction } from './use_view_in_discover_action';
+import { useViewInLogPatternAnalysisAction } from './use_view_in_log_pattern_analysis_action';
const NARROW_COLUMN_WIDTH = '120px';
const EXPAND_COLUMN_WIDTH = '40px';
@@ -121,6 +122,7 @@ export const SpikeAnalysisGroupsTable: FC = ({
const copyToClipBoardAction = useCopyToClipboardAction();
const viewInDiscoverAction = useViewInDiscoverAction(dataViewId);
+ const viewInLogPatternAnalysisAction = useViewInLogPatternAnalysisAction(dataViewId);
const columns: Array> = [
{
@@ -355,7 +357,7 @@ export const SpikeAnalysisGroupsTable: FC = ({
name: i18n.translate('xpack.aiops.spikeAnalysisTable.actionsColumnName', {
defaultMessage: 'Actions',
}),
- actions: [viewInDiscoverAction, copyToClipBoardAction],
+ actions: [viewInDiscoverAction, viewInLogPatternAnalysisAction, copyToClipBoardAction],
width: ACTIONS_COLUMN_WIDTH,
valign: 'top',
},
diff --git a/x-pack/plugins/aiops/public/components/spike_analysis_table/table_action_button.tsx b/x-pack/plugins/aiops/public/components/spike_analysis_table/table_action_button.tsx
new file mode 100644
index 0000000000000..16c91f8d3851f
--- /dev/null
+++ b/x-pack/plugins/aiops/public/components/spike_analysis_table/table_action_button.tsx
@@ -0,0 +1,62 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { type FC } from 'react';
+
+import { EuiLink, EuiIcon, EuiText, EuiToolTip, type IconType } from '@elastic/eui';
+
+interface TableActionButtonProps {
+ iconType: IconType;
+ dataTestSubjPostfix: string;
+ isDisabled: boolean;
+ label: string;
+ tooltipText?: string;
+ onClick: () => void;
+}
+
+export const TableActionButton: FC = ({
+ iconType,
+ dataTestSubjPostfix,
+ isDisabled,
+ label,
+ tooltipText,
+ onClick,
+}) => {
+ const buttonContent = (
+ <>
+
+ {label}
+ >
+ );
+
+ const unwrappedButton = !isDisabled ? (
+
+ {buttonContent}
+
+ ) : (
+
+ {buttonContent}
+
+ );
+
+ if (tooltipText) {
+ return {unwrappedButton};
+ }
+
+ return unwrappedButton;
+};
diff --git a/x-pack/plugins/aiops/public/components/spike_analysis_table/use_copy_to_clipboard_action.test.tsx b/x-pack/plugins/aiops/public/components/spike_analysis_table/use_copy_to_clipboard_action.test.tsx
index 82359b3d2b1aa..0984c76a4b170 100644
--- a/x-pack/plugins/aiops/public/components/spike_analysis_table/use_copy_to_clipboard_action.test.tsx
+++ b/x-pack/plugins/aiops/public/components/spike_analysis_table/use_copy_to_clipboard_action.test.tsx
@@ -30,11 +30,19 @@ describe('useCopyToClipboardAction', () => {
it('renders the action for a single significant term', async () => {
execCommandMock.mockImplementationOnce(() => true);
const { result } = renderHook(() => useCopyToClipboardAction());
- const { getByLabelText } = render((result.current as Action).render(significantTerms[0]));
+ const { findByText, getByTestId } = render(
+ (result.current as Action).render(significantTerms[0])
+ );
- const button = getByLabelText('Copy field/value pair as KQL syntax to clipboard');
+ const button = getByTestId('aiopsTableActionButtonCopyToClipboard enabled');
- expect(button).toBeInTheDocument();
+ userEvent.hover(button);
+
+ // The tooltip from EUI takes 250ms to appear, so we must
+ // use a `find*` query to asynchronously poll for it.
+ expect(
+ await findByText('Copy field/value pair as KQL syntax to clipboard')
+ ).toBeInTheDocument();
await act(async () => {
await userEvent.click(button);
@@ -50,12 +58,16 @@ describe('useCopyToClipboardAction', () => {
it('renders the action for a group of items', async () => {
execCommandMock.mockImplementationOnce(() => true);
const groupTableItems = getGroupTableItems(finalSignificantTermGroups);
- const { result } = renderHook(() => useCopyToClipboardAction());
- const { getByLabelText } = render((result.current as Action).render(groupTableItems[0]));
+ const { result } = renderHook(useCopyToClipboardAction);
+ const { findByText, getByText } = render((result.current as Action).render(groupTableItems[0]));
+
+ const button = getByText('Copy to clipboard');
- const button = getByLabelText('Copy group items as KQL syntax to clipboard');
+ userEvent.hover(button);
- expect(button).toBeInTheDocument();
+ // The tooltip from EUI takes 250ms to appear, so we must
+ // use a `find*` query to asynchronously poll for it.
+ expect(await findByText('Copy group items as KQL syntax to clipboard')).toBeInTheDocument();
await act(async () => {
await userEvent.click(button);
diff --git a/x-pack/plugins/aiops/public/components/spike_analysis_table/use_copy_to_clipboard_action.tsx b/x-pack/plugins/aiops/public/components/spike_analysis_table/use_copy_to_clipboard_action.tsx
index e9924307c1e27..1b906eb56e988 100644
--- a/x-pack/plugins/aiops/public/components/spike_analysis_table/use_copy_to_clipboard_action.tsx
+++ b/x-pack/plugins/aiops/public/components/spike_analysis_table/use_copy_to_clipboard_action.tsx
@@ -7,14 +7,22 @@
import React from 'react';
-import { EuiCopy, EuiToolTip, EuiButtonIcon } from '@elastic/eui';
+import { EuiCopy, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { isSignificantTerm, type SignificantTerm } from '@kbn/ml-agg-utils';
+import { TableActionButton } from './table_action_button';
import { getTableItemAsKQL } from './get_table_item_as_kql';
import type { GroupTableItem, TableItemAction } from './types';
+const copyToClipboardButtonLabel = i18n.translate(
+ 'xpack.aiops.spikeAnalysisTable.linksMenu.copyToClipboardButtonLabel',
+ {
+ defaultMessage: 'Copy to clipboard',
+ }
+);
+
const copyToClipboardSignificantTermMessage = i18n.translate(
'xpack.aiops.spikeAnalysisTable.linksMenu.copyToClipboardSignificantTermMessage',
{
@@ -37,7 +45,15 @@ export const useCopyToClipboardAction = (): TableItemAction => ({
return (
- {(copy) => }
+ {(copy) => (
+
+ )}
);
diff --git a/x-pack/plugins/aiops/public/components/spike_analysis_table/use_view_in_discover_action.tsx b/x-pack/plugins/aiops/public/components/spike_analysis_table/use_view_in_discover_action.tsx
index 5f30abb2f6cec..bd7741bb452bf 100644
--- a/x-pack/plugins/aiops/public/components/spike_analysis_table/use_view_in_discover_action.tsx
+++ b/x-pack/plugins/aiops/public/components/spike_analysis_table/use_view_in_discover_action.tsx
@@ -7,14 +7,13 @@
import React, { useMemo } from 'react';
-import { EuiIcon, EuiToolTip } from '@elastic/eui';
-
import { i18n } from '@kbn/i18n';
import type { SignificantTerm } from '@kbn/ml-agg-utils';
import { SEARCH_QUERY_LANGUAGE } from '../../application/utils/search_utils';
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
+import { TableActionButton } from './table_action_button';
import { getTableItemAsKQL } from './get_table_item_as_kql';
import type { GroupTableItem, TableItemAction } from './types';
@@ -83,19 +82,26 @@ export const useViewInDiscoverAction = (dataViewId?: string): TableItemAction =>
};
return {
- name: () => (
-
-
-
- ),
- description: viewInDiscoverMessage,
- type: 'button',
- onClick: async (tableItem) => {
- const openInDiscoverUrl = await generateDiscoverUrl(tableItem);
- if (typeof openInDiscoverUrl === 'string') {
- await application.navigateToUrl(openInDiscoverUrl);
- }
+ render: (tableItem: SignificantTerm | GroupTableItem) => {
+ const tooltipText = discoverUrlError ? discoverUrlError : viewInDiscoverMessage;
+
+ const clickHandler = async () => {
+ const openInDiscoverUrl = await generateDiscoverUrl(tableItem);
+ if (typeof openInDiscoverUrl === 'string') {
+ await application.navigateToUrl(openInDiscoverUrl);
+ }
+ };
+
+ return (
+
+ );
},
- enabled: () => discoverUrlError === undefined,
};
};
diff --git a/x-pack/plugins/aiops/public/components/spike_analysis_table/use_view_in_log_pattern_analysis_action.tsx b/x-pack/plugins/aiops/public/components/spike_analysis_table/use_view_in_log_pattern_analysis_action.tsx
new file mode 100644
index 0000000000000..9388cf147c8ff
--- /dev/null
+++ b/x-pack/plugins/aiops/public/components/spike_analysis_table/use_view_in_log_pattern_analysis_action.tsx
@@ -0,0 +1,108 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { useMemo } from 'react';
+
+import { SerializableRecord } from '@kbn/utility-types';
+import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query';
+import { i18n } from '@kbn/i18n';
+import type { SignificantTerm } from '@kbn/ml-agg-utils';
+
+import { SEARCH_QUERY_LANGUAGE } from '../../application/utils/search_utils';
+import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
+
+import { TableActionButton } from './table_action_button';
+import { getTableItemAsKQL } from './get_table_item_as_kql';
+import type { GroupTableItem, TableItemAction } from './types';
+
+const viewInLogPatternAnalysisMessage = i18n.translate(
+ 'xpack.aiops.spikeAnalysisTable.linksMenu.viewInLogPatternAnalysis',
+ {
+ defaultMessage: 'View in Log Pattern Analysis',
+ }
+);
+
+export const useViewInLogPatternAnalysisAction = (dataViewId?: string): TableItemAction => {
+ const { application, share, data } = useAiopsAppContext();
+
+ const mlLocator = useMemo(() => share.url.locators.get('ML_APP_LOCATOR'), [share.url.locators]);
+
+ const generateLogPatternAnalysisUrl = async (
+ groupTableItem: GroupTableItem | SignificantTerm
+ ) => {
+ if (mlLocator !== undefined) {
+ const searchString = getTableItemAsKQL(groupTableItem);
+ const ast = fromKueryExpression(searchString);
+ const searchQuery = toElasticsearchQuery(ast);
+
+ const appState = {
+ AIOPS_INDEX_VIEWER: {
+ filters: data.query.filterManager.getFilters(),
+ // QueryDslQueryContainer type triggers an error as being
+ // not working with SerializableRecord, however, it works as expected.
+ searchQuery: searchQuery as unknown,
+ searchQueryLanguage: SEARCH_QUERY_LANGUAGE.KUERY,
+ searchString: getTableItemAsKQL(groupTableItem),
+ },
+ } as SerializableRecord;
+
+ return await mlLocator.getUrl({
+ page: 'aiops/log_categorization',
+ pageState: {
+ index: dataViewId,
+ timeRange: data.query.timefilter.timefilter.getTime(),
+ appState,
+ },
+ });
+ }
+ };
+
+ const logPatternAnalysisUrlError = useMemo(() => {
+ if (!mlLocator) {
+ return i18n.translate('xpack.aiops.spikeAnalysisTable.mlLocatorMissingErrorMessage', {
+ defaultMessage: 'No locator for Log Pattern Analysis detected',
+ });
+ }
+ if (!dataViewId) {
+ return i18n.translate(
+ 'xpack.aiops.spikeAnalysisTable.autoGeneratedLogPatternAnalysisLinkErrorMessage',
+ {
+ defaultMessage:
+ 'Unable to link to Log Pattern Analysis; no data view exists for this index',
+ }
+ );
+ }
+ }, [dataViewId, mlLocator]);
+
+ return {
+ render: (tableItem: SignificantTerm | GroupTableItem) => {
+ const message = logPatternAnalysisUrlError
+ ? logPatternAnalysisUrlError
+ : viewInLogPatternAnalysisMessage;
+
+ const clickHandler = async () => {
+ const openInLogPatternAnalysisUrl = await generateLogPatternAnalysisUrl(tableItem);
+ if (typeof openInLogPatternAnalysisUrl === 'string') {
+ await application.navigateToUrl(openInLogPatternAnalysisUrl);
+ }
+ };
+
+ const isDisabled = logPatternAnalysisUrlError !== undefined;
+
+ return (
+
+ );
+ },
+ };
+};
diff --git a/x-pack/plugins/aiops/public/hooks/use_data.ts b/x-pack/plugins/aiops/public/hooks/use_data.ts
index c390582ccae1a..62f4c596cc60e 100644
--- a/x-pack/plugins/aiops/public/hooks/use_data.ts
+++ b/x-pack/plugins/aiops/public/hooks/use_data.ts
@@ -18,7 +18,7 @@ import { mlTimefilterRefresh$, useTimefilter } from '@kbn/ml-date-picker';
import { PLUGIN_ID } from '../../common';
import type { DocumentStatsSearchStrategyParams } from '../get_document_stats';
-import type { AiOpsIndexBasedAppState } from '../components/explain_log_rate_spikes/explain_log_rate_spikes_app_state';
+import type { AiOpsIndexBasedAppState } from '../application/utils/url_state';
import { getEsQueryFromSavedSearch } from '../application/utils/search_utils';
import type { GroupTableItem } from '../components/spike_analysis_table/types';
diff --git a/x-pack/plugins/aiops/tsconfig.json b/x-pack/plugins/aiops/tsconfig.json
index 89c236ba25c99..6c9aafffa26d0 100644
--- a/x-pack/plugins/aiops/tsconfig.json
+++ b/x-pack/plugins/aiops/tsconfig.json
@@ -50,6 +50,7 @@
"@kbn/ml-route-utils",
"@kbn/unified-field-list-plugin",
"@kbn/ml-random-sampler-utils",
+ "@kbn/utility-types",
"@kbn/ml-error-utils",
],
"exclude": [
diff --git a/x-pack/test/functional/apps/aiops/explain_log_rate_spikes.ts b/x-pack/test/functional/apps/aiops/explain_log_rate_spikes.ts
index 54b1baae454bd..7731eea1d9d7a 100644
--- a/x-pack/test/functional/apps/aiops/explain_log_rate_spikes.ts
+++ b/x-pack/test/functional/apps/aiops/explain_log_rate_spikes.ts
@@ -13,8 +13,8 @@ import type { FtrProviderContext } from '../../ftr_provider_context';
import { isTestDataExpectedWithSampleProbability, type TestData } from './types';
import { explainLogRateSpikesTestData } from './test_data';
-export default function ({ getPageObject, getService }: FtrProviderContext) {
- const headerPage = getPageObject('header');
+export default function ({ getPageObjects, getService }: FtrProviderContext) {
+ const PageObjects = getPageObjects(['common', 'console', 'header', 'home', 'security']);
const elasticChart = getService('elasticChart');
const aiops = getService('aiops');
@@ -58,7 +58,7 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
await aiops.explainLogRateSpikesPage.assertSamplingProbabilityMissing();
}
- await headerPage.waitUntilLoadingHasFinished();
+ await PageObjects.header.waitUntilLoadingHasFinished();
await ml.testExecution.logTestStep(
`${testData.suiteTitle} displays elements in the doc count panel correctly`
@@ -78,77 +78,78 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
await aiops.explainLogRateSpikesPage.clickDocumentCountChart(testData.chartClickCoordinates);
await aiops.explainLogRateSpikesPage.assertAnalysisSectionExists();
- await ml.testExecution.logTestStep('displays the no results found prompt');
- await aiops.explainLogRateSpikesPage.assertNoResultsFoundEmptyPromptExists();
+ if (testData.brushDeviationTargetTimestamp) {
+ await ml.testExecution.logTestStep('displays the no results found prompt');
+ await aiops.explainLogRateSpikesPage.assertNoResultsFoundEmptyPromptExists();
- await ml.testExecution.logTestStep('adjusts the brushes to get analysis results');
- await aiops.explainLogRateSpikesPage.assertRerunAnalysisButtonExists(false);
+ await ml.testExecution.logTestStep('adjusts the brushes to get analysis results');
+ await aiops.explainLogRateSpikesPage.assertRerunAnalysisButtonExists(false);
- // Get the current width of the deviation brush for later comparison.
- const brushSelectionWidthBefore = await aiops.explainLogRateSpikesPage.getBrushSelectionWidth(
- 'aiopsBrushDeviation'
- );
-
- // Get the px values for the timestamp we want to move the brush to.
- const { targetPx, intervalPx } = await aiops.explainLogRateSpikesPage.getPxForTimestamp(
- testData.brushDeviationTargetTimestamp
- );
-
- // Adjust the right brush handle
- await aiops.explainLogRateSpikesPage.adjustBrushHandler(
- 'aiopsBrushDeviation',
- 'handle--e',
- targetPx + intervalPx * testData.brushIntervalFactor
- );
-
- // Adjust the left brush handle
- await aiops.explainLogRateSpikesPage.adjustBrushHandler(
- 'aiopsBrushDeviation',
- 'handle--w',
- targetPx - intervalPx * (testData.brushIntervalFactor - 1)
- );
+ // Get the current width of the deviation brush for later comparison.
+ const brushSelectionWidthBefore =
+ await aiops.explainLogRateSpikesPage.getBrushSelectionWidth('aiopsBrushDeviation');
- if (testData.brushBaselineTargetTimestamp) {
// Get the px values for the timestamp we want to move the brush to.
- const { targetPx: targetBaselinePx } =
- await aiops.explainLogRateSpikesPage.getPxForTimestamp(
- testData.brushBaselineTargetTimestamp
- );
+ const { targetPx, intervalPx } = await aiops.explainLogRateSpikesPage.getPxForTimestamp(
+ testData.brushDeviationTargetTimestamp
+ );
// Adjust the right brush handle
await aiops.explainLogRateSpikesPage.adjustBrushHandler(
- 'aiopsBrushBaseline',
+ 'aiopsBrushDeviation',
'handle--e',
- targetBaselinePx + intervalPx * testData.brushIntervalFactor
+ targetPx + intervalPx * testData.brushIntervalFactor
);
// Adjust the left brush handle
await aiops.explainLogRateSpikesPage.adjustBrushHandler(
- 'aiopsBrushBaseline',
+ 'aiopsBrushDeviation',
'handle--w',
- targetBaselinePx - intervalPx * (testData.brushIntervalFactor - 1)
+ targetPx - intervalPx * (testData.brushIntervalFactor - 1)
);
- }
- // Get the new brush selection width for later comparison.
- const brushSelectionWidthAfter = await aiops.explainLogRateSpikesPage.getBrushSelectionWidth(
- 'aiopsBrushDeviation'
- );
+ if (testData.brushBaselineTargetTimestamp) {
+ // Get the px values for the timestamp we want to move the brush to.
+ const { targetPx: targetBaselinePx } =
+ await aiops.explainLogRateSpikesPage.getPxForTimestamp(
+ testData.brushBaselineTargetTimestamp
+ );
+
+ // Adjust the right brush handle
+ await aiops.explainLogRateSpikesPage.adjustBrushHandler(
+ 'aiopsBrushBaseline',
+ 'handle--e',
+ targetBaselinePx + intervalPx * testData.brushIntervalFactor
+ );
- // Assert the adjusted brush: The selection width should have changed and
- // we test if the selection is smaller than two bucket intervals.
- // Finally, the adjusted brush should trigger
- // a warning on the "Rerun analysis" button.
- expect(brushSelectionWidthBefore).not.to.be(brushSelectionWidthAfter);
- expect(brushSelectionWidthAfter).not.to.be.greaterThan(
- intervalPx * 2 * testData.brushIntervalFactor
- );
+ // Adjust the left brush handle
+ await aiops.explainLogRateSpikesPage.adjustBrushHandler(
+ 'aiopsBrushBaseline',
+ 'handle--w',
+ targetBaselinePx - intervalPx * (testData.brushIntervalFactor - 1)
+ );
+ }
+
+ // Get the new brush selection width for later comparison.
+ const brushSelectionWidthAfter =
+ await aiops.explainLogRateSpikesPage.getBrushSelectionWidth('aiopsBrushDeviation');
+
+ // Assert the adjusted brush: The selection width should have changed and
+ // we test if the selection is smaller than two bucket intervals.
+ // Finally, the adjusted brush should trigger
+ // a warning on the "Rerun analysis" button.
+ expect(brushSelectionWidthBefore).not.to.be(brushSelectionWidthAfter);
+ expect(brushSelectionWidthAfter).not.to.be.greaterThan(
+ intervalPx * 2 * testData.brushIntervalFactor
+ );
- await aiops.explainLogRateSpikesPage.assertRerunAnalysisButtonExists(true);
+ await aiops.explainLogRateSpikesPage.assertRerunAnalysisButtonExists(true);
- await ml.testExecution.logTestStep('rerun the analysis with adjusted settings');
+ await ml.testExecution.logTestStep('rerun the analysis with adjusted settings');
+
+ await aiops.explainLogRateSpikesPage.clickRerunAnalysisButton(true);
+ }
- await aiops.explainLogRateSpikesPage.clickRerunAnalysisButton(true);
await aiops.explainLogRateSpikesPage.assertProgressTitle('Progress: 100% — Done.');
// The group switch should be disabled by default
@@ -178,14 +179,14 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
);
}
- // Assert the field selector that allows to costumize grouping
+ await ml.testExecution.logTestStep('open the field filter');
await aiops.explainLogRateSpikesPage.assertFieldFilterPopoverButtonExists(false);
await aiops.explainLogRateSpikesPage.clickFieldFilterPopoverButton(true);
await aiops.explainLogRateSpikesPage.assertFieldSelectorFieldNameList(
testData.expected.fieldSelectorPopover
);
- // Filter fields
+ await ml.testExecution.logTestStep('filter fields');
await aiops.explainLogRateSpikesPage.setFieldSelectorSearch(testData.fieldSelectorSearch);
await aiops.explainLogRateSpikesPage.assertFieldSelectorFieldNameList([
testData.fieldSelectorSearch,
@@ -196,6 +197,7 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
);
if (testData.fieldSelectorApplyAvailable) {
+ await ml.testExecution.logTestStep('regroup results');
await aiops.explainLogRateSpikesPage.clickFieldFilterApplyButton();
if (!isTestDataExpectedWithSampleProbability(testData.expected)) {
@@ -206,6 +208,28 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
);
}
}
+
+ if (testData.action !== undefined) {
+ await ml.testExecution.logTestStep('check all table row actions are present');
+ await aiops.explainLogRateSpikesAnalysisGroupsTable.assertRowActions(
+ testData.action.tableRowId
+ );
+
+ await ml.testExecution.logTestStep('click log pattern analysis action');
+ await aiops.explainLogRateSpikesAnalysisGroupsTable.clickRowAction(
+ testData.action.tableRowId,
+ testData.action.type
+ );
+
+ await ml.testExecution.logTestStep('check log pattern analysis page loaded correctly');
+ await aiops.logPatternAnalysisPageProvider.assertLogCategorizationPageExists();
+ await aiops.logPatternAnalysisPageProvider.assertTotalDocumentCount(
+ testData.action.expected.totalDocCount
+ );
+ await aiops.logPatternAnalysisPageProvider.assertQueryInput(
+ testData.action.expected.queryBar
+ );
+ }
});
}
@@ -223,13 +247,27 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
await ml.testResources.setKibanaTimeZoneToUTC();
- await ml.securityUI.loginAsMlPowerUser();
+ if (testData.dataGenerator === 'kibana_sample_data_logs') {
+ await PageObjects.security.login('elastic', 'changeme', {
+ expectSuccess: true,
+ });
+
+ await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', {
+ useActualUrl: true,
+ });
+ await PageObjects.header.waitUntilLoadingHasFinished();
+ await PageObjects.home.addSampleDataSet('logs');
+ await PageObjects.header.waitUntilLoadingHasFinished();
+ } else {
+ await ml.securityUI.loginAsMlPowerUser();
+ }
});
after(async () => {
await elasticChart.setNewChartUiDebugFlag(false);
- await ml.testResources.deleteIndexPatternByTitle(testData.sourceIndexOrSavedSearch);
-
+ if (testData.dataGenerator !== 'kibana_sample_data_logs') {
+ await ml.testResources.deleteIndexPatternByTitle(testData.sourceIndexOrSavedSearch);
+ }
await aiops.explainLogRateSpikesDataGenerator.removeGeneratedData(testData.dataGenerator);
});
diff --git a/x-pack/test/functional/apps/aiops/test_data.ts b/x-pack/test/functional/apps/aiops/test_data.ts
index b6d3293aeba81..95e21fbfc7800 100644
--- a/x-pack/test/functional/apps/aiops/test_data.ts
+++ b/x-pack/test/functional/apps/aiops/test_data.ts
@@ -7,6 +7,111 @@
import type { TestData } from './types';
+export const kibanaLogsDataViewTestData: TestData = {
+ suiteTitle: 'kibana sample data logs',
+ dataGenerator: 'kibana_sample_data_logs',
+ isSavedSearch: false,
+ sourceIndexOrSavedSearch: 'kibana_sample_data_logs',
+ brushIntervalFactor: 1,
+ chartClickCoordinates: [235, 0],
+ fieldSelectorSearch: 'referer',
+ fieldSelectorApplyAvailable: true,
+ action: {
+ type: 'LogPatternAnalysis',
+ tableRowId: '488337254',
+ expected: {
+ queryBar:
+ 'clientip:30.156.16.164 AND host.keyword:elastic-elastic-elastic.org AND ip:30.156.16.163 AND response.keyword:404 AND machine.os.keyword:win xp AND geo.dest:IN AND geo.srcdest:US\\:IN',
+ totalDocCount: '100',
+ },
+ },
+ expected: {
+ totalDocCountFormatted: '14,074',
+ analysisGroupsTable: [
+ {
+ group:
+ '* clientip: 30.156.16.164* host.keyword: elastic-elastic-elastic.org* ip: 30.156.16.163* referer: http://www.elastic-elastic-elastic.com/success/timothy-l-kopra* response.keyword: 404Showing 5 out of 8 items. 8 items unique to this group.',
+ docCount: '100',
+ },
+ ],
+ filteredAnalysisGroupsTable: [
+ {
+ group:
+ '* clientip: 30.156.16.164* host.keyword: elastic-elastic-elastic.org* ip: 30.156.16.163* response.keyword: 404* machine.os.keyword: win xpShowing 5 out of 7 items. 7 items unique to this group.',
+ docCount: '100',
+ },
+ ],
+ analysisTable: [
+ {
+ fieldName: 'clientip',
+ fieldValue: '30.156.16.164',
+ logRate: 'Chart type:bar chart',
+ pValue: '3.10e-13',
+ impact: 'High',
+ },
+ {
+ fieldName: 'geo.dest',
+ fieldValue: 'IN',
+ logRate: 'Chart type:bar chart',
+ pValue: '0.000716',
+ impact: 'Medium',
+ },
+ {
+ fieldName: 'geo.srcdest',
+ fieldValue: 'US:IN',
+ logRate: 'Chart type:bar chart',
+ pValue: '0.000716',
+ impact: 'Medium',
+ },
+ {
+ fieldName: 'host.keyword',
+ fieldValue: 'elastic-elastic-elastic.org',
+ logRate: 'Chart type:bar chart',
+ pValue: '7.14e-9',
+ impact: 'High',
+ },
+ {
+ fieldName: 'ip',
+ fieldValue: '30.156.16.163',
+ logRate: 'Chart type:bar chart',
+ pValue: '3.28e-13',
+ impact: 'High',
+ },
+ {
+ fieldName: 'machine.os.keyword',
+ fieldValue: 'win xp',
+ logRate: 'Chart type:bar chart',
+ pValue: '0.0000997',
+ impact: 'Medium',
+ },
+ {
+ fieldName: 'referer',
+ fieldValue: 'http://www.elastic-elastic-elastic.com/success/timothy-l-kopra',
+ logRate: 'Chart type:bar chart',
+ pValue: '4.74e-13',
+ impact: 'High',
+ },
+ {
+ fieldName: 'response.keyword',
+ fieldValue: '404',
+ logRate: 'Chart type:bar chart',
+ pValue: '0.00000604',
+ impact: 'Medium',
+ },
+ ],
+ fieldSelectorPopover: [
+ 'clientip',
+ 'geo.dest',
+ 'geo.srcdest',
+ 'host.keyword',
+ 'ip',
+ 'machine.os.keyword',
+ 'referer',
+ 'response.keyword',
+ ],
+ },
+};
+
export const farequoteDataViewTestData: TestData = {
suiteTitle: 'farequote with spike',
dataGenerator: 'farequote_with_spike',
@@ -122,6 +227,7 @@ export const artificialLogDataViewTestData: TestData = {
};
export const explainLogRateSpikesTestData: TestData[] = [
+ kibanaLogsDataViewTestData,
farequoteDataViewTestData,
farequoteDataViewTestDataWithQuery,
artificialLogDataViewTestData,
diff --git a/x-pack/test/functional/apps/aiops/types.ts b/x-pack/test/functional/apps/aiops/types.ts
index 01733a8e1a2af..2093d4d961363 100644
--- a/x-pack/test/functional/apps/aiops/types.ts
+++ b/x-pack/test/functional/apps/aiops/types.ts
@@ -7,6 +7,15 @@
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
+interface TestDataTableActionLogPatternAnalysis {
+ type: 'LogPatternAnalysis';
+ tableRowId: string;
+ expected: {
+ queryBar: string;
+ totalDocCount: string;
+ };
+}
+
interface TestDataExpectedWithSampleProbability {
totalDocCountFormatted: string;
sampleProbabilityFormatted: string;
@@ -40,11 +49,12 @@ export interface TestData {
sourceIndexOrSavedSearch: string;
rowsPerPage?: 10 | 25 | 50;
brushBaselineTargetTimestamp?: number;
- brushDeviationTargetTimestamp: number;
+ brushDeviationTargetTimestamp?: number;
brushIntervalFactor: number;
chartClickCoordinates: [number, number];
fieldSelectorSearch: string;
fieldSelectorApplyAvailable: boolean;
query?: string;
+ action?: TestDataTableActionLogPatternAnalysis;
expected: TestDataExpectedWithSampleProbability | TestDataExpectedWithoutSampleProbability;
}
diff --git a/x-pack/test/functional/services/aiops/explain_log_rate_spikes_analysis_groups_table.ts b/x-pack/test/functional/services/aiops/explain_log_rate_spikes_analysis_groups_table.ts
index 18cadebbf9afd..b533c50677944 100644
--- a/x-pack/test/functional/services/aiops/explain_log_rate_spikes_analysis_groups_table.ts
+++ b/x-pack/test/functional/services/aiops/explain_log_rate_spikes_analysis_groups_table.ts
@@ -5,12 +5,17 @@
* 2.0.
*/
+import expect from '@kbn/expect';
+
import { FtrProviderContext } from '../../ftr_provider_context';
export function ExplainLogRateSpikesAnalysisGroupsTableProvider({
getService,
}: FtrProviderContext) {
+ const find = getService('find');
const testSubjects = getService('testSubjects');
+ const retry = getService('retry');
+ const browser = getService('browser');
return new (class AnalysisTable {
public async assertSpikeAnalysisTableExists() {
@@ -55,5 +60,50 @@ export function ExplainLogRateSpikesAnalysisGroupsTableProvider({
return rows;
}
+
+ public rowSelector(rowId: string, subSelector?: string) {
+ const row = `~aiopsSpikeAnalysisGroupsTable > ~row-${rowId}`;
+ return !subSelector ? row : `${row} > ${subSelector}`;
+ }
+
+ public async ensureActionsMenuOpen(rowId: string) {
+ await retry.tryForTime(30 * 1000, async () => {
+ await this.ensureActionsMenuClosed();
+
+ if (!(await find.existsByCssSelector('.euiContextMenuPanel', 1000))) {
+ await testSubjects.click(this.rowSelector(rowId, 'euiCollapsedItemActionsButton'));
+ expect(await find.existsByCssSelector('.euiContextMenuPanel', 1000)).to.eql(
+ true,
+ 'Actions popover should exist'
+ );
+ }
+ });
+ }
+
+ public async ensureActionsMenuClosed() {
+ await retry.tryForTime(30 * 1000, async () => {
+ await browser.pressKeys(browser.keys.ESCAPE);
+ expect(await find.existsByCssSelector('.euiContextMenuPanel', 1000)).to.eql(
+ false,
+ 'Actions popover should not exist'
+ );
+ });
+ }
+
+ public async assertRowActions(rowId: string) {
+ await this.ensureActionsMenuOpen(rowId);
+
+ await testSubjects.existOrFail('aiopsTableActionButtonCopyToClipboard enabled');
+ await testSubjects.existOrFail('aiopsTableActionButtonDiscover enabled');
+ await testSubjects.existOrFail('aiopsTableActionButtonLogPatternAnalysis enabled');
+
+ await this.ensureActionsMenuClosed();
+ }
+
+ public async clickRowAction(rowId: string, action: string) {
+ await this.ensureActionsMenuOpen(rowId);
+ await testSubjects.click(`aiopsTableActionButton${action} enabled`);
+ await testSubjects.missingOrFail(`aiopsTableActionButton${action} enabled`);
+ }
})();
}
diff --git a/x-pack/test/functional/services/aiops/explain_log_rate_spikes_data_generator.ts b/x-pack/test/functional/services/aiops/explain_log_rate_spikes_data_generator.ts
index 1a80ac679f29b..228d47bbc746f 100644
--- a/x-pack/test/functional/services/aiops/explain_log_rate_spikes_data_generator.ts
+++ b/x-pack/test/functional/services/aiops/explain_log_rate_spikes_data_generator.ts
@@ -122,6 +122,10 @@ export function ExplainLogRateSpikesDataGeneratorProvider({ getService }: FtrPro
return new (class DataGenerator {
public async generateData(dataGenerator: string) {
switch (dataGenerator) {
+ case 'kibana_sample_data_logs':
+ // will be added via UI
+ break;
+
case 'farequote_with_spike':
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote');
@@ -191,6 +195,10 @@ export function ExplainLogRateSpikesDataGeneratorProvider({ getService }: FtrPro
public async removeGeneratedData(dataGenerator: string) {
switch (dataGenerator) {
+ case 'kibana_sample_data_logs':
+ // do not remove
+ break;
+
case 'farequote_with_spike':
await esArchiver.unload('x-pack/test/functional/es_archives/ml/farequote');
break;
diff --git a/x-pack/test/functional/services/aiops/explain_log_rate_spikes_page.ts b/x-pack/test/functional/services/aiops/explain_log_rate_spikes_page.ts
index 3da9ed7c760b7..736437a1d3976 100644
--- a/x-pack/test/functional/services/aiops/explain_log_rate_spikes_page.ts
+++ b/x-pack/test/functional/services/aiops/explain_log_rate_spikes_page.ts
@@ -229,9 +229,11 @@ export function ExplainLogRateSpikesPageProvider({
},
async assertProgressTitle(expectedProgressTitle: string) {
- await testSubjects.existOrFail('aiopProgressTitle');
- const currentProgressTitle = await testSubjects.getVisibleText('aiopProgressTitle');
- expect(currentProgressTitle).to.be(expectedProgressTitle);
+ await retry.tryForTime(30 * 1000, async () => {
+ await testSubjects.existOrFail('aiopProgressTitle');
+ const currentProgressTitle = await testSubjects.getVisibleText('aiopProgressTitle');
+ expect(currentProgressTitle).to.be(expectedProgressTitle);
+ });
},
async navigateToIndexPatternSelection() {
diff --git a/x-pack/test/functional/services/aiops/index.ts b/x-pack/test/functional/services/aiops/index.ts
index 4816d37bcff04..8c208f182f3bd 100644
--- a/x-pack/test/functional/services/aiops/index.ts
+++ b/x-pack/test/functional/services/aiops/index.ts
@@ -11,6 +11,7 @@ import { ExplainLogRateSpikesPageProvider } from './explain_log_rate_spikes_page
import { ExplainLogRateSpikesAnalysisTableProvider } from './explain_log_rate_spikes_analysis_table';
import { ExplainLogRateSpikesAnalysisGroupsTableProvider } from './explain_log_rate_spikes_analysis_groups_table';
import { ExplainLogRateSpikesDataGeneratorProvider } from './explain_log_rate_spikes_data_generator';
+import { LogPatternAnalysisPageProvider } from './log_pattern_analysis_page';
export function AiopsProvider(context: FtrProviderContext) {
const explainLogRateSpikesPage = ExplainLogRateSpikesPageProvider(context);
@@ -18,11 +19,13 @@ export function AiopsProvider(context: FtrProviderContext) {
const explainLogRateSpikesAnalysisGroupsTable =
ExplainLogRateSpikesAnalysisGroupsTableProvider(context);
const explainLogRateSpikesDataGenerator = ExplainLogRateSpikesDataGeneratorProvider(context);
+ const logPatternAnalysisPageProvider = LogPatternAnalysisPageProvider(context);
return {
explainLogRateSpikesPage,
explainLogRateSpikesAnalysisTable,
explainLogRateSpikesAnalysisGroupsTable,
explainLogRateSpikesDataGenerator,
+ logPatternAnalysisPageProvider,
};
}
diff --git a/x-pack/test/functional/services/aiops/log_pattern_analysis_page.ts b/x-pack/test/functional/services/aiops/log_pattern_analysis_page.ts
new file mode 100644
index 0000000000000..37872b8d7c051
--- /dev/null
+++ b/x-pack/test/functional/services/aiops/log_pattern_analysis_page.ts
@@ -0,0 +1,42 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import expect from '@kbn/expect';
+
+import type { FtrProviderContext } from '../../ftr_provider_context';
+
+export function LogPatternAnalysisPageProvider({ getService, getPageObject }: FtrProviderContext) {
+ const retry = getService('retry');
+ const testSubjects = getService('testSubjects');
+
+ return {
+ async assertLogCategorizationPageExists() {
+ await retry.tryForTime(30 * 1000, async () => {
+ await testSubjects.existOrFail('aiopsLogCategorizationPage');
+ });
+ },
+
+ async assertQueryInput(expectedQueryString: string) {
+ const aiopsQueryInput = await testSubjects.find('aiopsQueryInput');
+ const actualQueryString = await aiopsQueryInput.getVisibleText();
+ expect(actualQueryString).to.eql(
+ expectedQueryString,
+ `Expected query bar text to be '${expectedQueryString}' (got '${actualQueryString}')`
+ );
+ },
+
+ async assertTotalDocumentCount(expectedFormattedTotalDocCount: string) {
+ await retry.tryForTime(5000, async () => {
+ const docCount = await testSubjects.getVisibleText('aiopsTotalDocCount');
+ expect(docCount).to.eql(
+ expectedFormattedTotalDocCount,
+ `Expected total document count to be '${expectedFormattedTotalDocCount}' (got '${docCount}')`
+ );
+ });
+ },
+ };
+}
From 06545277d7dfc379d69c2e68879fc409fa5b71fd Mon Sep 17 00:00:00 2001
From: Carlos Crespo
Date: Mon, 24 Apr 2023 15:02:44 -0300
Subject: [PATCH 19/36] [Infrastructure UI] Replace Snapshot API with
InfraMetrics API in Hosts View (#155531)
closes [#154443](https://github.com/elastic/kibana/issues/154443)
## Summary
This PR replaces the usage of the Snapshot API in favor of the new
`metrics/infra` endpoint and also includes a new control in the Search
Bar to allow users to select how many hosts they want the API to return.
https://user-images.githubusercontent.com/2767137/233728658-bccc7258-6955-47fb-8f7b-85ef6ec5d0f9.mov
Because the KPIs now needs to show an "Average (of X hosts)", they will
only start fetching the data once the table has been loaded.
The hosts count KPI tile was not converted to Lens, because the page
needs to know the total number of hosts.
### Possible follow-up
Since now everything depends on the table to be loaded, I have been
experimenting with batched requests to the new API. The idea is to fetch
at least the host names as soon as possible.
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
---
.../http_api/infra/get_infra_metrics.ts | 6 +-
.../infra/public/hooks/use_lens_attributes.ts | 8 +-
.../hosts/components/chart/lens_wrapper.tsx | 23 +++-
.../components/chart/metric_chart_wrapper.tsx | 70 ++--------
.../hosts/components/hosts_container.tsx | 2 +-
.../hosts/components/kpis/hosts_tile.tsx | 44 ++++--
.../hosts/components/kpis/kpi_grid.tsx | 62 +++------
.../metrics/hosts/components/kpis/tile.tsx | 64 +++++++--
.../{ => search_bar}/controls_content.tsx | 22 ++-
.../components/search_bar/limit_options.tsx | 81 +++++++++++
.../search_bar/unified_search_bar.tsx | 126 +++++++++++++++++
.../components/tabs/logs/logs_tab_content.tsx | 7 +-
.../components/tabs/metrics/metric_chart.tsx | 4 +-
.../components/tabs/metrics/metrics_grid.tsx | 36 ++---
.../hosts/components/unified_search_bar.tsx | 98 -------------
.../public/pages/metrics/hosts/constants.ts | 6 +-
.../metrics/hosts/hooks/use_alerts_query.ts | 4 +-
.../metrics/hosts/hooks/use_data_view.ts | 1 +
.../metrics/hosts/hooks/use_host_count.ts | 129 ++++++++++++++++++
.../hosts/hooks/use_hosts_table.test.ts | 31 +++--
.../metrics/hosts/hooks/use_hosts_table.tsx | 63 +++++----
.../metrics/hosts/hooks/use_hosts_view.ts | 91 ++++++------
.../metrics/hosts/hooks/use_unified_search.ts | 30 ++--
.../hooks/use_unified_search_url_state.ts | 18 ++-
.../infra/public/pages/metrics/hosts/types.ts | 4 +-
.../server/routes/infra/lib/constants.ts | 3 +
x-pack/plugins/infra/tsconfig.json | 2 +-
.../translations/translations/fr-FR.json | 10 +-
.../translations/translations/ja-JP.json | 10 +-
.../translations/translations/zh-CN.json | 10 +-
.../api_integration/apis/metrics_ui/infra.ts | 4 +
31 files changed, 673 insertions(+), 396 deletions(-)
rename x-pack/plugins/infra/public/pages/metrics/hosts/components/{ => search_bar}/controls_content.tsx (79%)
create mode 100644 x-pack/plugins/infra/public/pages/metrics/hosts/components/search_bar/limit_options.tsx
create mode 100644 x-pack/plugins/infra/public/pages/metrics/hosts/components/search_bar/unified_search_bar.tsx
delete mode 100644 x-pack/plugins/infra/public/pages/metrics/hosts/components/unified_search_bar.tsx
create mode 100644 x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_count.ts
diff --git a/x-pack/plugins/infra/common/http_api/infra/get_infra_metrics.ts b/x-pack/plugins/infra/common/http_api/infra/get_infra_metrics.ts
index bcfeeafcee06f..80e5e501169d6 100644
--- a/x-pack/plugins/infra/common/http_api/infra/get_infra_metrics.ts
+++ b/x-pack/plugins/infra/common/http_api/infra/get_infra_metrics.ts
@@ -23,8 +23,9 @@ export const RangeRT = rt.type({
});
export const InfraAssetMetadataTypeRT = rt.keyof({
- 'host.os.name': null,
'cloud.provider': null,
+ 'host.ip': null,
+ 'host.os.name': null,
});
export const InfraAssetMetricsRT = rt.type({
@@ -35,7 +36,7 @@ export const InfraAssetMetricsRT = rt.type({
export const InfraAssetMetadataRT = rt.type({
// keep the actual field name from the index mappings
name: InfraAssetMetadataTypeRT,
- value: rt.union([rt.string, rt.number, rt.null]),
+ value: rt.union([rt.string, rt.null]),
});
export const GetInfraMetricsRequestBodyPayloadRT = rt.intersection([
@@ -64,6 +65,7 @@ export const GetInfraMetricsResponsePayloadRT = rt.type({
export type InfraAssetMetrics = rt.TypeOf;
export type InfraAssetMetadata = rt.TypeOf;
+export type InfraAssetMetadataType = rt.TypeOf;
export type InfraAssetMetricType = rt.TypeOf;
export type InfraAssetMetricsItem = rt.TypeOf;
diff --git a/x-pack/plugins/infra/public/hooks/use_lens_attributes.ts b/x-pack/plugins/infra/public/hooks/use_lens_attributes.ts
index c9ce48c909f9a..6250d20750e29 100644
--- a/x-pack/plugins/infra/public/hooks/use_lens_attributes.ts
+++ b/x-pack/plugins/infra/public/hooks/use_lens_attributes.ts
@@ -74,11 +74,7 @@ export const useLensAttributes = ({
return visualizationAttributes;
}, [dataView, formulaAPI, options, type, visualizationType]);
- const injectFilters = (data: {
- timeRange: TimeRange;
- filters: Filter[];
- query: Query;
- }): LensAttributes | null => {
+ const injectFilters = (data: { filters: Filter[]; query: Query }): LensAttributes | null => {
if (!attributes) {
return null;
}
@@ -121,7 +117,7 @@ export const useLensAttributes = ({
return true;
},
async execute(_context: ActionExecutionContext): Promise {
- const injectedAttributes = injectFilters({ timeRange, filters, query });
+ const injectedAttributes = injectFilters({ filters, query });
if (injectedAttributes) {
navigateToPrefilledEditor(
{
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/chart/lens_wrapper.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/chart/lens_wrapper.tsx
index 9a2472949f54c..34e536aaf37d2 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/chart/lens_wrapper.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/chart/lens_wrapper.tsx
@@ -15,7 +15,7 @@ import { useIntersectedOnce } from '../../../../../hooks/use_intersection_once';
import { LensAttributes } from '../../../../../common/visualizations';
import { ChartLoader } from './chart_loader';
-export interface Props {
+export interface LensWrapperProps {
id: string;
attributes: LensAttributes | null;
dateRange: TimeRange;
@@ -42,11 +42,12 @@ export const LensWrapper = ({
lastReloadRequestTime,
loading = false,
hasTitle = false,
-}: Props) => {
+}: LensWrapperProps) => {
const intersectionRef = useRef(null);
const [loadedOnce, setLoadedOnce] = useState(false);
const [state, setState] = useState({
+ attributes,
lastReloadRequestTime,
query,
filters,
@@ -65,15 +66,23 @@ export const LensWrapper = ({
useEffect(() => {
if ((intersection?.intersectionRatio ?? 0) === 1) {
setState({
+ attributes,
lastReloadRequestTime,
query,
- dateRange,
filters,
+ dateRange,
});
}
- }, [dateRange, filters, intersection?.intersectionRatio, lastReloadRequestTime, query]);
+ }, [
+ attributes,
+ dateRange,
+ filters,
+ intersection?.intersectionRatio,
+ lastReloadRequestTime,
+ query,
+ ]);
- const isReady = attributes && intersectedOnce;
+ const isReady = state.attributes && intersectedOnce;
return (
@@ -83,11 +92,11 @@ export const LensWrapper = ({
style={style}
hasTitle={hasTitle}
>
- {isReady && (
+ {state.attributes && (
;
-
-type AcceptedType = SnapshotMetricType | 'hostsCount';
-
-export interface ChartBaseProps
- extends Pick<
- MetricWTrend,
- 'title' | 'color' | 'extra' | 'subtitle' | 'trendA11yDescription' | 'trendA11yTitle'
- > {
- type: AcceptedType;
- toolTip: string;
- metricType: MetricType;
- ['data-test-subj']?: string;
-}
-
-interface Props extends ChartBaseProps {
+export interface Props extends Pick {
id: string;
- nodes: SnapshotNode[];
loading: boolean;
- overrideValue?: number;
+ value: number;
+ toolTip: string;
+ ['data-test-subj']?: string;
}
const MIN_HEIGHT = 150;
@@ -49,23 +25,13 @@ export const MetricChartWrapper = ({
extra,
id,
loading,
- metricType,
- nodes,
- overrideValue,
+ value,
subtitle,
title,
toolTip,
- trendA11yDescription,
- trendA11yTitle,
- type,
...props
}: Props) => {
const loadedOnce = useRef(false);
- const metrics = useMemo(() => (nodes ?? [])[0]?.metrics ?? [], [nodes]);
- const metricsTimeseries = useMemo(
- () => (metrics ?? []).find((m) => m.name === type)?.timeseries,
- [metrics, type]
- );
useEffect(() => {
if (!loadedOnce.current && !loading) {
@@ -76,29 +42,13 @@ export const MetricChartWrapper = ({
};
}, [loading]);
- const metricsValue = useMemo(() => {
- if (overrideValue) {
- return overrideValue;
- }
- return (metrics ?? []).find((m) => m.name === type)?.[metricType] ?? 0;
- }, [metricType, metrics, overrideValue, type]);
-
const metricsData: MetricWNumber = {
title,
subtitle,
color,
extra,
- value: metricsValue,
- valueFormatter: (d: number) =>
- type === 'hostsCount' ? d.toString() : createInventoryMetricFormatter({ type })(d),
- ...(!!metricsTimeseries
- ? {
- trend: metricsTimeseries.rows.map((row) => ({ x: row.timestamp, y: row.metric_0 ?? 0 })),
- trendShape: MetricTrendShape.Area,
- trendA11yTitle,
- trendA11yDescription,
- }
- : {}),
+ value,
+ valueFormatter: (d: number) => d.toString(),
};
return (
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_container.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_container.tsx
index 0c965feca8e9e..d42944857af34 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_container.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_container.tsx
@@ -10,7 +10,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { InfraLoadingPanel } from '../../../../components/loading';
import { useMetricsDataViewContext } from '../hooks/use_data_view';
-import { UnifiedSearchBar } from './unified_search_bar';
+import { UnifiedSearchBar } from './search_bar/unified_search_bar';
import { HostsTable } from './hosts_table';
import { KPIGrid } from './kpis/kpi_grid';
import { Tabs } from './tabs/tabs';
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/hosts_tile.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/hosts_tile.tsx
index 396c0bd72ad71..14a617682bf25 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/hosts_tile.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/hosts_tile.tsx
@@ -4,22 +4,46 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
+import { i18n } from '@kbn/i18n';
import React from 'react';
+import { useHostCountContext } from '../../hooks/use_host_count';
+import { useUnifiedSearchContext } from '../../hooks/use_unified_search';
-import { useHostsViewContext } from '../../hooks/use_hosts_view';
-import { type ChartBaseProps, MetricChartWrapper } from '../chart/metric_chart_wrapper';
+import { type Props, MetricChartWrapper } from '../chart/metric_chart_wrapper';
-export const HostsTile = ({ type, ...props }: ChartBaseProps) => {
- const { hostNodes, loading } = useHostsViewContext();
+const HOSTS_CHART: Omit = {
+ id: `metric-hostCount`,
+ color: '#6DCCB1',
+ title: i18n.translate('xpack.infra.hostsViewPage.metricTrend.hostCount.title', {
+ defaultMessage: 'Hosts',
+ }),
+ toolTip: i18n.translate('xpack.infra.hostsViewPage.metricTrend.hostCount.tooltip', {
+ defaultMessage: 'The number of hosts returned by your current search criteria.',
+ }),
+ ['data-test-subj']: 'hostsView-metricsTrend-hosts',
+};
+
+export const HostsTile = () => {
+ const { data: hostCountData, isRequestRunning: hostCountLoading } = useHostCountContext();
+ const { searchCriteria } = useUnifiedSearchContext();
+
+ const getSubtitle = () => {
+ return searchCriteria.limit < (hostCountData?.count.value ?? 0)
+ ? i18n.translate('xpack.infra.hostsViewPage.metricTrend.subtitle.hostCount.limit', {
+ defaultMessage: 'Limited to {limit}',
+ values: {
+ limit: searchCriteria.limit,
+ },
+ })
+ : undefined;
+ };
return (
);
};
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/kpi_grid.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/kpi_grid.tsx
index 2dbd0c4324eca..c3f751d26befb 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/kpi_grid.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/kpi_grid.tsx
@@ -9,10 +9,10 @@ import React from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { KPIChartProps, Tile } from './tile';
+import { HostCountProvider } from '../../hooks/use_host_count';
import { HostsTile } from './hosts_tile';
-import { ChartBaseProps } from '../chart/metric_chart_wrapper';
-const KPI_CHARTS: KPIChartProps[] = [
+const KPI_CHARTS: Array> = [
{
type: 'cpu',
trendLine: true,
@@ -20,9 +20,6 @@ const KPI_CHARTS: KPIChartProps[] = [
title: i18n.translate('xpack.infra.hostsViewPage.metricTrend.cpu.title', {
defaultMessage: 'CPU usage',
}),
- subtitle: i18n.translate('xpack.infra.hostsViewPage.metricTrend.cpu.subtitle', {
- defaultMessage: 'Average',
- }),
toolTip: i18n.translate('xpack.infra.hostsViewPage.metricTrend.cpu.tooltip', {
defaultMessage:
'Average of percentage of CPU time spent in states other than Idle and IOWait, normalized by the number of CPU cores. Includes both time spent on user space and kernel space. 100% means all CPUs of the host are busy.',
@@ -35,9 +32,6 @@ const KPI_CHARTS: KPIChartProps[] = [
title: i18n.translate('xpack.infra.hostsViewPage.metricTrend.memory.title', {
defaultMessage: 'Memory usage',
}),
- subtitle: i18n.translate('xpack.infra.hostsViewPage.metricTrend.memory.subtitle', {
- defaultMessage: 'Average',
- }),
toolTip: i18n.translate('xpack.infra.hostsViewPage.metricTrend.memory.tooltip', {
defaultMessage:
"Average of percentage of main memory usage excluding page cache. This includes resident memory for all processes plus memory used by the kernel structures and code apart the page cache. A high level indicates a situation of memory saturation for a host. 100% means the main memory is entirely filled with memory that can't be reclaimed, except by swapping out.",
@@ -50,9 +44,6 @@ const KPI_CHARTS: KPIChartProps[] = [
title: i18n.translate('xpack.infra.hostsViewPage.metricTrend.rx.title', {
defaultMessage: 'Network inbound (RX)',
}),
- subtitle: i18n.translate('xpack.infra.hostsViewPage.metricTrend.rx.subtitle', {
- defaultMessage: 'Average',
- }),
toolTip: i18n.translate('xpack.infra.hostsViewPage.metricTrend.rx.tooltip', {
defaultMessage:
'Number of bytes which have been received per second on the public interfaces of the hosts.',
@@ -65,9 +56,6 @@ const KPI_CHARTS: KPIChartProps[] = [
title: i18n.translate('xpack.infra.hostsViewPage.metricTrend.tx.title', {
defaultMessage: 'Network outbound (TX)',
}),
- subtitle: i18n.translate('xpack.infra.hostsViewPage.metricTrend.tx.subtitle', {
- defaultMessage: 'Average',
- }),
toolTip: i18n.translate('xpack.infra.hostsViewPage.metricTrend.tx.tooltip', {
defaultMessage:
'Number of bytes which have been received per second on the public interfaces of the hosts.',
@@ -75,38 +63,24 @@ const KPI_CHARTS: KPIChartProps[] = [
},
];
-const HOSTS_CHART: ChartBaseProps = {
- type: 'hostsCount',
- color: '#6DCCB1',
- metricType: 'value',
- title: i18n.translate('xpack.infra.hostsViewPage.metricTrend.hostCount.title', {
- defaultMessage: 'Hosts',
- }),
- trendA11yTitle: i18n.translate('xpack.infra.hostsViewPage.metricTrend.hostCount.a11y.title', {
- defaultMessage: 'Hosts count.',
- }),
- toolTip: i18n.translate('xpack.infra.hostsViewPage.metricTrend.hostCount.tooltip', {
- defaultMessage: 'The number of hosts returned by your current search criteria.',
- }),
- ['data-test-subj']: 'hostsView-metricsTrend-hosts',
-};
-
export const KPIGrid = () => {
return (
-
-
-
-
- {KPI_CHARTS.map(({ ...chartProp }) => (
-
-
+
+
+
+
- ))}
-
+ {KPI_CHARTS.map(({ ...chartProp }) => (
+
+
+
+ ))}
+
+
);
};
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/tile.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/tile.tsx
index a95f18b4a10ee..89eebeefd240e 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/tile.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/tile.tsx
@@ -4,9 +4,9 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import React from 'react';
+import React, { useMemo } from 'react';
-import { Action } from '@kbn/ui-actions-plugin/public';
+import { i18n } from '@kbn/i18n';
import { BrushTriggerEvent } from '@kbn/charts-plugin/public';
import {
EuiIcon,
@@ -24,6 +24,9 @@ import { useUnifiedSearchContext } from '../../hooks/use_unified_search';
import { HostsLensMetricChartFormulas } from '../../../../../common/visualizations';
import { useHostsViewContext } from '../../hooks/use_hosts_view';
import { LensWrapper } from '../chart/lens_wrapper';
+import { createHostsFilter } from '../../utils';
+import { useHostCountContext } from '../../hooks/use_host_count';
+import { useAfterLoadedState } from '../../hooks/use_after_loaded_state';
export interface KPIChartProps {
title: string;
@@ -38,7 +41,6 @@ const MIN_HEIGHT = 150;
export const Tile = ({
title,
- subtitle,
type,
backgroundColor,
toolTip,
@@ -46,14 +48,28 @@ export const Tile = ({
}: KPIChartProps) => {
const { searchCriteria, onSubmit } = useUnifiedSearchContext();
const { dataView } = useMetricsDataViewContext();
- const { baseRequest } = useHostsViewContext();
+ const { requestTs, hostNodes, loading: hostsLoading } = useHostsViewContext();
+ const { data: hostCountData, isRequestRunning: hostCountLoading } = useHostCountContext();
+
+ const getSubtitle = () => {
+ return searchCriteria.limit < (hostCountData?.count.value ?? 0)
+ ? i18n.translate('xpack.infra.hostsViewPage.metricTrend.subtitle.average.limit', {
+ defaultMessage: 'Average (of {limit} hosts)',
+ values: {
+ limit: searchCriteria.limit,
+ },
+ })
+ : i18n.translate('xpack.infra.hostsViewPage.metricTrend.subtitle.average', {
+ defaultMessage: 'Average',
+ });
+ };
const { attributes, getExtraActions, error } = useLensAttributes({
type,
dataView,
options: {
title,
- subtitle,
+ subtitle: getSubtitle(),
backgroundColor,
showTrendLine: trendLine,
showTitle: false,
@@ -61,15 +77,24 @@ export const Tile = ({
visualizationType: 'metricChart',
});
- const filters = [...searchCriteria.filters, ...searchCriteria.panelFilters];
+ const hostsFilterQuery = useMemo(() => {
+ return createHostsFilter(
+ hostNodes.map((p) => p.name),
+ dataView
+ );
+ }, [hostNodes, dataView]);
+
+ const filters = useMemo(
+ () => [...searchCriteria.filters, ...searchCriteria.panelFilters, ...[hostsFilterQuery]],
+ [hostsFilterQuery, searchCriteria.filters, searchCriteria.panelFilters]
+ );
+
const extraActionOptions = getExtraActions({
timeRange: searchCriteria.dateRange,
filters,
query: searchCriteria.query,
});
- const extraActions: Action[] = [extraActionOptions.openInLens];
-
const handleBrushEnd = ({ range }: BrushTriggerEvent['data']) => {
const [min, max] = range;
onSubmit({
@@ -81,6 +106,14 @@ export const Tile = ({
});
};
+ const loading = hostsLoading || !attributes || hostCountLoading;
+ const { afterLoadedState } = useAfterLoadedState(loading, {
+ attributes,
+ lastReloadRequestTime: requestTs,
+ ...searchCriteria,
+ filters,
+ });
+
return (
)}
@@ -134,7 +168,7 @@ export const Tile = ({
const EuiPanelStyled = styled(EuiPanel)`
.echMetric {
- border-radius: ${(p) => p.theme.eui.euiBorderRadius};
+ border-radius: ${({ theme }) => theme.eui.euiBorderRadius};
pointer-events: none;
}
`;
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/controls_content.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/search_bar/controls_content.tsx
similarity index 79%
rename from x-pack/plugins/infra/public/pages/metrics/hosts/components/controls_content.tsx
rename to x-pack/plugins/infra/public/pages/metrics/hosts/components/search_bar/controls_content.tsx
index a3e82b9901422..e2bd7d0c74dae 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/controls_content.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/search_bar/controls_content.tsx
@@ -12,15 +12,16 @@ import {
type ControlGroupInput,
} from '@kbn/controls-plugin/public';
import { ViewMode } from '@kbn/embeddable-plugin/public';
-import type { Filter, Query, TimeRange } from '@kbn/es-query';
+import { compareFilters, COMPARE_ALL_OPTIONS, Filter, Query, TimeRange } from '@kbn/es-query';
import { DataView } from '@kbn/data-views-plugin/public';
-import { Subscription } from 'rxjs';
-import { useControlPanels } from '../hooks/use_control_panels_url_state';
+import { skipWhile, Subscription } from 'rxjs';
+import { useControlPanels } from '../../hooks/use_control_panels_url_state';
interface Props {
dataView: DataView | undefined;
timeRange: TimeRange;
filters: Filter[];
+ selectedOptions: Filter[];
query: Query;
onFiltersChange: (filters: Filter[]) => void;
}
@@ -29,6 +30,7 @@ export const ControlsContent: React.FC = ({
dataView,
filters,
query,
+ selectedOptions,
timeRange,
onFiltersChange,
}) => {
@@ -55,15 +57,21 @@ export const ControlsContent: React.FC = ({
const loadCompleteHandler = useCallback(
(controlGroup: ControlGroupAPI) => {
if (!controlGroup) return;
- inputSubscription.current = controlGroup.onFiltersPublished$.subscribe((newFilters) => {
- onFiltersChange(newFilters);
- });
+ inputSubscription.current = controlGroup.onFiltersPublished$
+ .pipe(
+ skipWhile((newFilters) =>
+ compareFilters(selectedOptions, newFilters, COMPARE_ALL_OPTIONS)
+ )
+ )
+ .subscribe((newFilters) => {
+ onFiltersChange(newFilters);
+ });
filterSubscription.current = controlGroup
.getInput$()
.subscribe(({ panels }) => setControlPanels(panels));
},
- [onFiltersChange, setControlPanels]
+ [onFiltersChange, setControlPanels, selectedOptions]
);
useEffect(() => {
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/search_bar/limit_options.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/search_bar/limit_options.tsx
new file mode 100644
index 0000000000000..1d8ce4f9c9e87
--- /dev/null
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/search_bar/limit_options.tsx
@@ -0,0 +1,81 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import {
+ EuiButtonGroup,
+ EuiButtonGroupOptionProps,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiIcon,
+ EuiToolTip,
+ EuiText,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n-react';
+import { HOST_LIMIT_OPTIONS } from '../../constants';
+import { HostLimitOptions } from '../../types';
+
+interface Props {
+ limit: HostLimitOptions;
+ onChange: (limit: number) => void;
+}
+
+export const LimitOptions = ({ limit, onChange }: Props) => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ onChange(value)}
+ />
+
+
+ );
+};
+
+const buildId = (option: number) => `hostLimit_${option}`;
+const options: EuiButtonGroupOptionProps[] = HOST_LIMIT_OPTIONS.map((option) => ({
+ id: buildId(option),
+ label: `${option}`,
+ value: option,
+ 'data-test-subj': `hostsViewLimitSelection${option}button`,
+}));
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/search_bar/unified_search_bar.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/search_bar/unified_search_bar.tsx
new file mode 100644
index 0000000000000..ef515cc018839
--- /dev/null
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/search_bar/unified_search_bar.tsx
@@ -0,0 +1,126 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { useMemo } from 'react';
+import { compareFilters, COMPARE_ALL_OPTIONS, type Filter } from '@kbn/es-query';
+import { i18n } from '@kbn/i18n';
+import {
+ EuiFlexGrid,
+ useEuiTheme,
+ EuiHorizontalRule,
+ EuiFlexGroup,
+ EuiFlexItem,
+} from '@elastic/eui';
+import { css } from '@emotion/react';
+import { METRICS_APP_DATA_TEST_SUBJ } from '../../../../../apps/metrics_app';
+import { useKibanaContextForPlugin } from '../../../../../hooks/use_kibana';
+import { useUnifiedSearchContext } from '../../hooks/use_unified_search';
+import { ControlsContent } from './controls_content';
+import { useMetricsDataViewContext } from '../../hooks/use_data_view';
+import { HostsSearchPayload } from '../../hooks/use_unified_search_url_state';
+import { LimitOptions } from './limit_options';
+import { HostLimitOptions } from '../../types';
+
+export const UnifiedSearchBar = () => {
+ const {
+ services: { unifiedSearch, application },
+ } = useKibanaContextForPlugin();
+ const { dataView } = useMetricsDataViewContext();
+ const { searchCriteria, onSubmit } = useUnifiedSearchContext();
+
+ const { SearchBar } = unifiedSearch.ui;
+
+ const onLimitChange = (limit: number) => {
+ onSubmit({ limit });
+ };
+
+ const onPanelFiltersChange = (panelFilters: Filter[]) => {
+ if (!compareFilters(searchCriteria.panelFilters, panelFilters, COMPARE_ALL_OPTIONS)) {
+ onSubmit({ panelFilters });
+ }
+ };
+
+ const handleRefresh = (payload: HostsSearchPayload, isUpdate?: boolean) => {
+ // This makes sure `onQueryChange` is only called when the submit button is clicked
+ if (isUpdate === false) {
+ onSubmit(payload);
+ }
+ };
+
+ return (
+
+
+
+ 0.5)',
+ })}
+ onQuerySubmit={handleRefresh}
+ showSaveQuery={Boolean(application?.capabilities?.visualize?.saveQuery)}
+ showDatePicker
+ showFilterBar
+ showQueryInput
+ showQueryMenu
+ useDefaultBehaviors
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const StickyContainer = (props: { children: React.ReactNode }) => {
+ const { euiTheme } = useEuiTheme();
+
+ const top = useMemo(() => {
+ const wrapper = document.querySelector(`[data-test-subj="${METRICS_APP_DATA_TEST_SUBJ}"]`);
+ if (!wrapper) {
+ return `calc(${euiTheme.size.xxxl} * 2)`;
+ }
+
+ return `${wrapper.getBoundingClientRect().top}px`;
+ }, [euiTheme]);
+
+ return (
+
+ );
+};
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx
index d5cc0b0f021d7..6813dee1caa10 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx
@@ -9,7 +9,6 @@ import React, { useMemo } from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { InfraLoadingPanel } from '../../../../../../components/loading';
-import { SnapshotNode } from '../../../../../../../common/http_api';
import { LogStream } from '../../../../../../components/log_stream';
import { useHostsViewContext } from '../../../hooks/use_hosts_view';
import { useUnifiedSearchContext } from '../../../hooks/use_unified_search';
@@ -30,7 +29,7 @@ export const LogsTabContent = () => {
);
const logsLinkToStreamQuery = useMemo(() => {
- const hostsFilterQueryParam = createHostsFilterQueryParam(hostNodes);
+ const hostsFilterQueryParam = createHostsFilterQueryParam(hostNodes.map((p) => p.name));
if (filterQuery.query && hostsFilterQueryParam) {
return `${filterQuery.query} and ${hostsFilterQueryParam}`;
@@ -83,12 +82,12 @@ export const LogsTabContent = () => {
);
};
-const createHostsFilterQueryParam = (hostNodes: SnapshotNode[]): string => {
+const createHostsFilterQueryParam = (hostNodes: string[]): string => {
if (!hostNodes.length) {
return '';
}
- const joinedHosts = hostNodes.map((p) => p.name).join(' or ');
+ const joinedHosts = hostNodes.join(' or ');
const hostsQueryParam = `host.name:(${joinedHosts})`;
return hostsQueryParam;
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/metric_chart.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/metric_chart.tsx
index 28d07b94d9437..f81228957107a 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/metric_chart.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/metric_chart.tsx
@@ -40,13 +40,13 @@ export const MetricChart = ({ title, type, breakdownSize }: MetricChartProps) =>
const { euiTheme } = useEuiTheme();
const { searchCriteria, onSubmit } = useUnifiedSearchContext();
const { dataView } = useMetricsDataViewContext();
- const { baseRequest, loading } = useHostsViewContext();
+ const { requestTs, loading } = useHostsViewContext();
const { currentPage } = useHostsTableContext();
// prevents updates on requestTs and serchCriteria states from relaoding the chart
// we want it to reload only once the table has finished loading
const { afterLoadedState } = useAfterLoadedState(loading, {
- lastReloadRequestTime: baseRequest.requestTs,
+ lastReloadRequestTime: requestTs,
...searchCriteria,
});
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/metrics_grid.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/metrics_grid.tsx
index e307dde0d09e5..7f3dac7a3af16 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/metrics_grid.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/metrics_grid.tsx
@@ -6,7 +6,7 @@
*/
import React from 'react';
-import { EuiFlexGrid, EuiFlexItem, EuiFlexGroup, EuiText, EuiI18n } from '@elastic/eui';
+import { EuiFlexGrid, EuiFlexItem } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { MetricChart, MetricChartProps } from './metric_chart';
@@ -64,32 +64,12 @@ const CHARTS_IN_ORDER: Array & { fullRo
export const MetricsGrid = React.memo(() => {
return (
-
-
-
-
-
- {DEFAULT_BREAKDOWN_SIZE},
- attribute: name,
- }}
- />
-
-
-
-
-
-
- {CHARTS_IN_ORDER.map(({ fullRow, ...chartProp }) => (
-
-
-
- ))}
-
-
-
+
+ {CHARTS_IN_ORDER.map(({ fullRow, ...chartProp }) => (
+
+
+
+ ))}
+
);
});
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/unified_search_bar.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/unified_search_bar.tsx
deleted file mode 100644
index 168f825a9d2d1..0000000000000
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/unified_search_bar.tsx
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React, { useMemo } from 'react';
-import { compareFilters, COMPARE_ALL_OPTIONS, type Filter } from '@kbn/es-query';
-import { i18n } from '@kbn/i18n';
-import { EuiFlexGrid, useEuiTheme } from '@elastic/eui';
-import { css } from '@emotion/react';
-import { EuiHorizontalRule } from '@elastic/eui';
-import { METRICS_APP_DATA_TEST_SUBJ } from '../../../../apps/metrics_app';
-import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana';
-import { useUnifiedSearchContext } from '../hooks/use_unified_search';
-import { ControlsContent } from './controls_content';
-import { useMetricsDataViewContext } from '../hooks/use_data_view';
-import { HostsSearchPayload } from '../hooks/use_unified_search_url_state';
-
-export const UnifiedSearchBar = () => {
- const {
- services: { unifiedSearch, application },
- } = useKibanaContextForPlugin();
- const { dataView } = useMetricsDataViewContext();
- const { searchCriteria, onSubmit } = useUnifiedSearchContext();
-
- const { SearchBar } = unifiedSearch.ui;
-
- const onPanelFiltersChange = (panelFilters: Filter[]) => {
- if (!compareFilters(searchCriteria.panelFilters, panelFilters, COMPARE_ALL_OPTIONS)) {
- onSubmit({ panelFilters });
- }
- };
-
- const handleRefresh = (payload: HostsSearchPayload, isUpdate?: boolean) => {
- // This makes sure `onQueryChange` is only called when the submit button is clicked
- if (isUpdate === false) {
- onSubmit(payload);
- }
- };
-
- return (
-
- 0.5)',
- })}
- onQuerySubmit={handleRefresh}
- showSaveQuery={Boolean(application?.capabilities?.visualize?.saveQuery)}
- showDatePicker
- showFilterBar
- showQueryInput
- showQueryMenu
- useDefaultBehaviors
- />
-
-
-
- );
-};
-
-const StickyContainer = (props: { children: React.ReactNode }) => {
- const { euiTheme } = useEuiTheme();
-
- const top = useMemo(() => {
- const wrapper = document.querySelector(`[data-test-subj="${METRICS_APP_DATA_TEST_SUBJ}"]`);
- if (!wrapper) {
- return `calc(${euiTheme.size.xxxl} * 2)`;
- }
-
- return `${wrapper.getBoundingClientRect().top}px`;
- }, [euiTheme]);
-
- return (
-
- );
-};
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/constants.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/constants.ts
index b854120a86887..69cfc446d0095 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/constants.ts
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/constants.ts
@@ -7,13 +7,15 @@
import { i18n } from '@kbn/i18n';
import { ALERT_STATUS, ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED } from '@kbn/rule-data-utils';
-import { AlertStatusFilter } from './types';
+import { AlertStatusFilter, HostLimitOptions } from './types';
export const ALERT_STATUS_ALL = 'all';
export const TIMESTAMP_FIELD = '@timestamp';
export const DATA_VIEW_PREFIX = 'infra_metrics';
+export const DEFAULT_HOST_LIMIT: HostLimitOptions = 100;
export const DEFAULT_PAGE_SIZE = 10;
+export const LOCAL_STORAGE_HOST_LIMIT_KEY = 'hostsView:hostLimitSelection';
export const LOCAL_STORAGE_PAGE_SIZE_KEY = 'hostsView:pageSizeSelection';
export const ALL_ALERTS: AlertStatusFilter = {
@@ -55,3 +57,5 @@ export const ALERT_STATUS_QUERY = {
[ACTIVE_ALERTS.status]: ACTIVE_ALERTS.query,
[RECOVERED_ALERTS.status]: RECOVERED_ALERTS.query,
};
+
+export const HOST_LIMIT_OPTIONS = [10, 20, 50, 100, 500] as const;
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_alerts_query.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_alerts_query.ts
index 7a895591d68c7..200bff521d86a 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_alerts_query.ts
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_alerts_query.ts
@@ -9,7 +9,7 @@ import createContainer from 'constate';
import { getTime } from '@kbn/data-plugin/common';
import { ALERT_TIME_RANGE } from '@kbn/rule-data-utils';
import { BoolQuery, buildEsQuery, Filter } from '@kbn/es-query';
-import { SnapshotNode } from '../../../../../common/http_api';
+import { InfraAssetMetricsItem } from '../../../../../common/http_api';
import { useUnifiedSearchContext } from './use_unified_search';
import { HostsState } from './use_unified_search_url_state';
import { useHostsViewContext } from './use_hosts_view';
@@ -63,7 +63,7 @@ const createAlertsEsQuery = ({
status,
}: {
dateRange: HostsState['dateRange'];
- hostNodes: SnapshotNode[];
+ hostNodes: InfraAssetMetricsItem[];
status?: AlertStatus;
}): AlertsEsQuery => {
const alertStatusFilter = createAlertStatusFilter(status);
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_data_view.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_data_view.ts
index 94e3a963075be..83fedf4292937 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_data_view.ts
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_data_view.ts
@@ -72,6 +72,7 @@ export const useDataView = ({ metricAlias }: { metricAlias: string }) => {
}, [hasError, notifications, metricAlias]);
return {
+ metricAlias,
dataView,
loading,
hasError,
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_count.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_count.ts
new file mode 100644
index 0000000000000..5575c46e621f1
--- /dev/null
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_count.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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import * as rt from 'io-ts';
+import { ES_SEARCH_STRATEGY, IKibanaSearchResponse } from '@kbn/data-plugin/common';
+import { useCallback, useEffect } from 'react';
+import { catchError, map, Observable, of, startWith } from 'rxjs';
+import createContainer from 'constate';
+import type { QueryDslQueryContainer, SearchResponse } from '@elastic/elasticsearch/lib/api/types';
+import { decodeOrThrow } from '../../../../../common/runtime_types';
+import { useDataSearch, useLatestPartialDataSearchResponse } from '../../../../utils/data_search';
+import { useMetricsDataViewContext } from './use_data_view';
+import { useUnifiedSearchContext } from './use_unified_search';
+
+export const useHostCount = () => {
+ const { dataView, metricAlias } = useMetricsDataViewContext();
+ const { buildQuery, getParsedDateRange } = useUnifiedSearchContext();
+
+ const { search: fetchHostCount, requests$ } = useDataSearch({
+ getRequest: useCallback(() => {
+ const query = buildQuery();
+ const dateRange = getParsedDateRange();
+
+ const filters: QueryDslQueryContainer = {
+ bool: {
+ ...query.bool,
+ filter: [
+ ...query.bool.filter,
+ {
+ exists: {
+ field: 'host.name',
+ },
+ },
+ {
+ range: {
+ [dataView?.timeFieldName ?? '@timestamp']: {
+ gte: dateRange.from,
+ lte: dateRange.to,
+ format: 'strict_date_optional_time',
+ },
+ },
+ },
+ ],
+ },
+ };
+
+ return {
+ request: {
+ params: {
+ allow_no_indices: true,
+ ignore_unavailable: true,
+ index: metricAlias,
+ size: 0,
+ track_total_hits: false,
+ body: {
+ query: filters,
+ aggs: {
+ count: {
+ cardinality: {
+ field: 'host.name',
+ },
+ },
+ },
+ },
+ },
+ },
+ options: { strategy: ES_SEARCH_STRATEGY },
+ };
+ }, [buildQuery, dataView, getParsedDateRange, metricAlias]),
+ parseResponses: normalizeDataSearchResponse,
+ });
+
+ const { isRequestRunning, isResponsePartial, latestResponseData, latestResponseErrors } =
+ useLatestPartialDataSearchResponse(requests$);
+
+ useEffect(() => {
+ fetchHostCount();
+ }, [fetchHostCount]);
+
+ return {
+ errors: latestResponseErrors,
+ isRequestRunning,
+ isResponsePartial,
+ data: latestResponseData ?? null,
+ };
+};
+
+export const HostCount = createContainer(useHostCount);
+export const [HostCountProvider, useHostCountContext] = HostCount;
+
+const INITIAL_STATE = {
+ data: null,
+ errors: [],
+ isPartial: true,
+ isRunning: true,
+ loaded: 0,
+ total: undefined,
+};
+const normalizeDataSearchResponse = (
+ response$: Observable>>>
+) =>
+ response$.pipe(
+ map((response) => ({
+ data: decodeOrThrow(HostCountResponseRT)(response.rawResponse.aggregations),
+ errors: [],
+ isPartial: response.isPartial ?? false,
+ isRunning: response.isRunning ?? false,
+ loaded: response.loaded,
+ total: response.total,
+ })),
+ startWith(INITIAL_STATE),
+ catchError((error) =>
+ of({
+ ...INITIAL_STATE,
+ errors: [error.message ?? error],
+ isRunning: false,
+ })
+ )
+ );
+
+const HostCountResponseRT = rt.type({
+ count: rt.type({
+ value: rt.number,
+ }),
+});
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts
index a921a0daeb011..5619a788b19a7 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts
@@ -7,7 +7,7 @@
import { useHostsTable } from './use_hosts_table';
import { renderHook } from '@testing-library/react-hooks';
-import { SnapshotNode } from '../../../../../common/http_api';
+import { InfraAssetMetricsItem } from '../../../../../common/http_api';
import * as useUnifiedSearchHooks from './use_unified_search';
import * as useHostsViewHooks from './use_hosts_view';
@@ -22,20 +22,20 @@ const mockUseHostsViewContext = useHostsViewHooks.useHostsViewContext as jest.Mo
typeof useHostsViewHooks.useHostsViewContext
>;
-const mockHostNode: SnapshotNode[] = [
+const mockHostNode: InfraAssetMetricsItem[] = [
{
metrics: [
{
name: 'rx',
- avg: 252456.92916666667,
+ value: 252456.92916666667,
},
{
name: 'tx',
- avg: 252758.425,
+ value: 252758.425,
},
{
name: 'memory',
- avg: 0.94525,
+ value: 0.94525,
},
{
name: 'cpu',
@@ -43,25 +43,28 @@ const mockHostNode: SnapshotNode[] = [
},
{
name: 'memoryTotal',
- avg: 34359.738368,
+ value: 34359.738368,
},
],
- path: [{ value: 'host-0', label: 'host-0', os: null, cloudProvider: 'aws' }],
+ metadata: [
+ { name: 'host.os.name', value: null },
+ { name: 'cloud.provider', value: 'aws' },
+ ],
name: 'host-0',
},
{
metrics: [
{
name: 'rx',
- avg: 95.86339715321859,
+ value: 95.86339715321859,
},
{
name: 'tx',
- avg: 110.38566859563191,
+ value: 110.38566859563191,
},
{
name: 'memory',
- avg: 0.5400000214576721,
+ value: 0.5400000214576721,
},
{
name: 'cpu',
@@ -69,12 +72,12 @@ const mockHostNode: SnapshotNode[] = [
},
{
name: 'memoryTotal',
- avg: 9.194304,
+ value: 9.194304,
},
],
- path: [
- { value: 'host-1', label: 'host-1' },
- { value: 'host-1', label: 'host-1', ip: '243.86.94.22', os: 'macOS' },
+ metadata: [
+ { name: 'host.os.name', value: 'macOS' },
+ { name: 'host.ip', value: '243.86.94.22' },
],
name: 'host-1',
},
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx
index 2d2d6c9d7f8e4..7350f402c57ec 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx
@@ -15,10 +15,10 @@ import { isNumber } from 'lodash/fp';
import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana';
import { createInventoryMetricFormatter } from '../../inventory_view/lib/create_inventory_metric_formatter';
import { HostsTableEntryTitle } from '../components/hosts_table_entry_title';
-import type {
- SnapshotNode,
- SnapshotNodeMetric,
- SnapshotMetricInput,
+import {
+ InfraAssetMetadataType,
+ InfraAssetMetricsItem,
+ InfraAssetMetricType,
} from '../../../../../common/http_api';
import { useHostFlyoutOpen } from './use_host_flyout_open_url_state';
import { Sorting, useHostsTableProperties } from './use_hosts_table_url_state';
@@ -29,42 +29,55 @@ import { useUnifiedSearchContext } from './use_unified_search';
* Columns and items types
*/
export type CloudProvider = 'gcp' | 'aws' | 'azure' | 'unknownProvider';
+type HostMetrics = Record;
-type HostMetric = 'cpu' | 'diskLatency' | 'rx' | 'tx' | 'memory' | 'memoryTotal';
-
-type HostMetrics = Record;
-
-export interface HostNodeRow extends HostMetrics {
+interface HostMetadata {
os?: string | null;
ip?: string | null;
servicesOnHost?: number | null;
title: { name: string; cloudProvider?: CloudProvider | null };
- name: string;
id: string;
}
+export type HostNodeRow = HostMetadata &
+ HostMetrics & {
+ name: string;
+ };
/**
* Helper functions
*/
-const formatMetric = (type: SnapshotMetricInput['type'], value: number | undefined | null) => {
+const formatMetric = (type: InfraAssetMetricType, value: number | undefined | null) => {
return value || value === 0 ? createInventoryMetricFormatter({ type })(value) : 'N/A';
};
-const buildItemsList = (nodes: SnapshotNode[]) => {
- return nodes.map(({ metrics, path, name }) => ({
- id: `${name}-${path.at(-1)?.os ?? '-'}`,
- name,
- os: path.at(-1)?.os ?? '-',
- ip: path.at(-1)?.ip ?? '',
- title: {
+const buildItemsList = (nodes: InfraAssetMetricsItem[]): HostNodeRow[] => {
+ return nodes.map(({ metrics, metadata, name }) => {
+ const metadataKeyValue = metadata.reduce(
+ (acc, curr) => ({
+ ...acc,
+ [curr.name]: curr.value,
+ }),
+ {} as Record
+ );
+
+ return {
name,
- cloudProvider: path.at(-1)?.cloudProvider ?? null,
- },
- ...metrics.reduce((data, metric) => {
- data[metric.name as HostMetric] = metric.avg ?? metric.value;
- return data;
- }, {} as HostMetrics),
- })) as HostNodeRow[];
+ id: `${name}-${metadataKeyValue['host.os.name'] ?? '-'}`,
+ title: {
+ name,
+ cloudProvider: (metadataKeyValue['cloud.provider'] as CloudProvider) ?? null,
+ },
+ os: metadataKeyValue['host.os.name'] ?? '-',
+ ip: metadataKeyValue['host.ip'] ?? '',
+ ...metrics.reduce(
+ (acc, curr) => ({
+ ...acc,
+ [curr.name]: curr.value ?? 0,
+ }),
+ {} as HostMetrics
+ ),
+ };
+ });
};
const isTitleColumn = (cell: any): cell is HostNodeRow['title'] => {
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_view.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_view.ts
index d0df961dc7ef9..f84acf5931ea1 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_view.ts
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_view.ts
@@ -12,16 +12,21 @@
* 2.0.
*/
-import { useMemo } from 'react';
+import { useEffect, useMemo, useRef } from 'react';
import createContainer from 'constate';
import { BoolQuery } from '@kbn/es-query';
-import { SnapshotMetricType } from '../../../../../common/inventory_models/types';
+import useAsyncFn from 'react-use/lib/useAsyncFn';
+import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana';
import { useSourceContext } from '../../../../containers/metrics_source';
-import { useSnapshot, type UseSnapshotRequest } from '../../inventory_view/hooks/use_snaphot';
import { useUnifiedSearchContext } from './use_unified_search';
-import { StringDateRangeTimestamp } from './use_unified_search_url_state';
+import {
+ GetInfraMetricsRequestBodyPayload,
+ GetInfraMetricsResponsePayload,
+ InfraAssetMetricType,
+} from '../../../../../common/http_api';
+import { StringDateRange } from './use_unified_search_url_state';
-const HOST_TABLE_METRICS: Array<{ type: SnapshotMetricType }> = [
+const HOST_TABLE_METRICS: Array<{ type: InfraAssetMetricType }> = [
{ type: 'rx' },
{ type: 'tx' },
{ type: 'memory' },
@@ -30,40 +35,52 @@ const HOST_TABLE_METRICS: Array<{ type: SnapshotMetricType }> = [
{ type: 'memoryTotal' },
];
+const BASE_INFRA_METRICS_PATH = '/api/metrics/infra';
+
export const useHostsView = () => {
const { sourceId } = useSourceContext();
- const { buildQuery, getDateRangeAsTimestamp } = useUnifiedSearchContext();
+ const {
+ services: { http },
+ } = useKibanaContextForPlugin();
+ const { buildQuery, getParsedDateRange, searchCriteria } = useUnifiedSearchContext();
+ const abortCtrlRef = useRef(new AbortController());
const baseRequest = useMemo(
() =>
- createSnapshotRequest({
- dateRange: getDateRangeAsTimestamp(),
+ createInfraMetricsRequest({
+ dateRange: getParsedDateRange(),
esQuery: buildQuery(),
sourceId,
+ limit: searchCriteria.limit,
}),
- [buildQuery, getDateRangeAsTimestamp, sourceId]
+ [buildQuery, getParsedDateRange, sourceId, searchCriteria.limit]
);
- // Snapshot endpoint internally uses the indices stored in source.configuration.metricAlias.
- // For the Unified Search, we create a data view, which for now will be built off of source.configuration.metricAlias too
- // if we introduce data view selection, we'll have to change this hook and the endpoint to accept a new parameter for the indices
- const {
- loading,
- error,
- nodes: hostNodes,
- } = useSnapshot(
- {
- ...baseRequest,
- metrics: HOST_TABLE_METRICS,
+ const [state, refetch] = useAsyncFn(
+ () => {
+ abortCtrlRef.current.abort();
+ abortCtrlRef.current = new AbortController();
+
+ return http.post(`${BASE_INFRA_METRICS_PATH}`, {
+ signal: abortCtrlRef.current.signal,
+ body: JSON.stringify(baseRequest),
+ });
},
- { abortable: true }
+ [baseRequest, http],
+ { loading: true }
);
+ useEffect(() => {
+ refetch();
+ }, [refetch]);
+
+ const { value, error, loading } = state;
+
return {
- baseRequest,
+ requestTs: baseRequest.requestTs,
loading,
error,
- hostNodes,
+ hostNodes: value?.nodes ?? [],
};
};
@@ -73,30 +90,26 @@ export const [HostsViewProvider, useHostsViewContext] = HostsView;
/**
* Helpers
*/
-const createSnapshotRequest = ({
+
+const createInfraMetricsRequest = ({
esQuery,
sourceId,
dateRange,
+ limit,
}: {
esQuery: { bool: BoolQuery };
sourceId: string;
- dateRange: StringDateRangeTimestamp;
-}): UseSnapshotRequest => ({
- filterQuery: JSON.stringify(esQuery),
- metrics: [],
- groupBy: [],
- nodeType: 'host',
- sourceId,
- currentTime: dateRange.to,
- includeTimeseries: false,
- sendRequestImmediately: true,
- timerange: {
- interval: '1m',
+ dateRange: StringDateRange;
+ limit: number;
+}): GetInfraMetricsRequestBodyPayload & { requestTs: number } => ({
+ type: 'host',
+ query: esQuery,
+ range: {
from: dateRange.from,
to: dateRange.to,
- ignoreLookback: true,
},
- // The user might want to click on the submit button without changing the filters
- // This makes sure all child components will re-render.
+ metrics: HOST_TABLE_METRICS,
+ limit,
+ sourceId,
requestTs: Date.now(),
});
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search.ts
index 950bd9cf3c94e..e242f58054c6c 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search.ts
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search.ts
@@ -23,14 +23,14 @@ import {
} from './use_unified_search_url_state';
const buildQuerySubmittedPayload = (
- hostState: HostsState & { dateRangeTimestamp: StringDateRangeTimestamp }
+ hostState: HostsState & { parsedDateRange: StringDateRangeTimestamp }
) => {
- const { panelFilters, filters, dateRangeTimestamp, query: queryObj } = hostState;
+ const { panelFilters, filters, parsedDateRange, query: queryObj } = hostState;
return {
control_filters: panelFilters.map((filter) => JSON.stringify(filter)),
filters: filters.map((filter) => JSON.stringify(filter)),
- interval: telemetryTimeRangeFormatter(dateRangeTimestamp.to - dateRangeTimestamp.from),
+ interval: telemetryTimeRangeFormatter(parsedDateRange.to - parsedDateRange.from),
query: queryObj.query,
};
};
@@ -41,8 +41,8 @@ const getDefaultTimestamps = () => {
const now = Date.now();
return {
- from: now - DEFAULT_FROM_IN_MILLISECONDS,
- to: now,
+ from: new Date(now - DEFAULT_FROM_IN_MILLISECONDS).toISOString(),
+ to: new Date(now).toISOString(),
};
};
@@ -63,16 +63,25 @@ export const useUnifiedSearch = () => {
const onSubmit = (params?: HostsSearchPayload) => setSearch(params ?? {});
- const getDateRangeAsTimestamp = useCallback(() => {
+ const getParsedDateRange = useCallback(() => {
const defaults = getDefaultTimestamps();
- const from = DateMath.parse(searchCriteria.dateRange.from)?.valueOf() ?? defaults.from;
+ const from = DateMath.parse(searchCriteria.dateRange.from)?.toISOString() ?? defaults.from;
const to =
- DateMath.parse(searchCriteria.dateRange.to, { roundUp: true })?.valueOf() ?? defaults.to;
+ DateMath.parse(searchCriteria.dateRange.to, { roundUp: true })?.toISOString() ?? defaults.to;
return { from, to };
}, [searchCriteria.dateRange]);
+ const getDateRangeAsTimestamp = useCallback(() => {
+ const parsedDate = getParsedDateRange();
+
+ const from = new Date(parsedDate.from).getTime();
+ const to = new Date(parsedDate.to).getTime();
+
+ return { from, to };
+ }, [getParsedDateRange]);
+
const buildQuery = useCallback(() => {
return buildEsQuery(dataView, searchCriteria.query, [
...searchCriteria.filters,
@@ -116,15 +125,16 @@ export const useUnifiedSearch = () => {
// Track telemetry event on query/filter/date changes
useEffect(() => {
- const dateRangeTimestamp = getDateRangeAsTimestamp();
+ const parsedDateRange = getDateRangeAsTimestamp();
telemetry.reportHostsViewQuerySubmitted(
- buildQuerySubmittedPayload({ ...searchCriteria, dateRangeTimestamp })
+ buildQuerySubmittedPayload({ ...searchCriteria, parsedDateRange })
);
}, [getDateRangeAsTimestamp, searchCriteria, telemetry]);
return {
buildQuery,
onSubmit,
+ getParsedDateRange,
getDateRangeAsTimestamp,
searchCriteria,
};
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts
index 861f3c26472e8..bae9f2ed3f713 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts
@@ -13,11 +13,13 @@ import { fold } from 'fp-ts/lib/Either';
import { constant, identity } from 'fp-ts/lib/function';
import { enumeration } from '@kbn/securitysolution-io-ts-types';
import { FilterStateStore } from '@kbn/es-query';
+import useLocalStorage from 'react-use/lib/useLocalStorage';
import { useUrlState } from '../../../../utils/use_url_state';
import {
useKibanaTimefilterTime,
useSyncKibanaTimeFilterTime,
} from '../../../../hooks/use_kibana_timefilter_time';
+import { DEFAULT_HOST_LIMIT, LOCAL_STORAGE_HOST_LIMIT_KEY } from '../constants';
const DEFAULT_QUERY = {
language: 'kuery',
@@ -32,6 +34,7 @@ const INITIAL_HOSTS_STATE: HostsState = {
filters: [],
panelFilters: [],
dateRange: INITIAL_DATE_RANGE,
+ limit: DEFAULT_HOST_LIMIT,
};
const reducer = (prevState: HostsState, params: HostsSearchPayload) => {
@@ -45,9 +48,17 @@ const reducer = (prevState: HostsState, params: HostsSearchPayload) => {
export const useHostsUrlState = (): [HostsState, HostsStateUpdater] => {
const [getTime] = useKibanaTimefilterTime(INITIAL_DATE_RANGE);
+ const [localStorageHostLimit, setLocalStorageHostLimit] = useLocalStorage(
+ LOCAL_STORAGE_HOST_LIMIT_KEY,
+ INITIAL_HOSTS_STATE.limit
+ );
const [urlState, setUrlState] = useUrlState({
- defaultState: { ...INITIAL_HOSTS_STATE, dateRange: getTime() },
+ defaultState: {
+ ...INITIAL_HOSTS_STATE,
+ dateRange: getTime(),
+ limit: localStorageHostLimit ?? INITIAL_HOSTS_STATE.limit,
+ },
decodeUrlState,
encodeUrlState,
urlStateKey: '_a',
@@ -57,6 +68,9 @@ export const useHostsUrlState = (): [HostsState, HostsStateUpdater] => {
const [search, setSearch] = useReducer(reducer, urlState);
if (!deepEqual(search, urlState)) {
setUrlState(search);
+ if (localStorageHostLimit !== search.limit) {
+ setLocalStorageHostLimit(search.limit);
+ }
}
useSyncKibanaTimeFilterTime(INITIAL_DATE_RANGE, urlState.dateRange, (dateRange) =>
@@ -110,6 +124,7 @@ const HostsStateRT = rt.type({
panelFilters: HostsFiltersRT,
query: HostsQueryStateRT,
dateRange: StringDateRangeRT,
+ limit: rt.number,
});
export type HostsState = rt.TypeOf;
@@ -118,6 +133,7 @@ export type HostsSearchPayload = Partial;
export type HostsStateUpdater = (params: HostsSearchPayload) => void;
+export type StringDateRange = rt.TypeOf;
export interface StringDateRangeTimestamp {
from: number;
to: number;
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/types.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/types.ts
index 6b948fb0da6c9..080b47f54d4da 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/types.ts
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/types.ts
@@ -7,7 +7,7 @@
import { Filter } from '@kbn/es-query';
import { ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED } from '@kbn/rule-data-utils';
-import { ALERT_STATUS_ALL } from './constants';
+import { ALERT_STATUS_ALL, HOST_LIMIT_OPTIONS } from './constants';
export type AlertStatus =
| typeof ALERT_STATUS_ACTIVE
@@ -19,3 +19,5 @@ export interface AlertStatusFilter {
query?: Filter['query'];
label: string;
}
+
+export type HostLimitOptions = typeof HOST_LIMIT_OPTIONS[number];
diff --git a/x-pack/plugins/infra/server/routes/infra/lib/constants.ts b/x-pack/plugins/infra/server/routes/infra/lib/constants.ts
index bc9131d1d52fc..f39eaafdd039e 100644
--- a/x-pack/plugins/infra/server/routes/infra/lib/constants.ts
+++ b/x-pack/plugins/infra/server/routes/infra/lib/constants.ts
@@ -24,6 +24,9 @@ export const METADATA_AGGREGATION: Record
Date: Mon, 24 Apr 2023 20:15:18 +0200
Subject: [PATCH 20/36] Fleet: allow Universal Profiling symbolizer permissions
on indices (#155642)
## Summary
For the introduction of the Universal Profiling symbolizer in Cloud,
Fleet needs an update.
The reason for Universal Profiling symbolizer to be different from other
packages running via Fleet is that:
1. it ingests data into indicesm not only data-streams
2. it uses a non-conventional naming scheme for indices
---
x-pack/plugins/fleet/common/constants/epm.ts | 1 +
...kage_policies_to_agent_permissions.test.ts | 92 +++++++++++++++++++
.../package_policies_to_agent_permissions.ts | 33 +++++++
3 files changed, 126 insertions(+)
diff --git a/x-pack/plugins/fleet/common/constants/epm.ts b/x-pack/plugins/fleet/common/constants/epm.ts
index a1d73b452cf72..2635dbc05399f 100644
--- a/x-pack/plugins/fleet/common/constants/epm.ts
+++ b/x-pack/plugins/fleet/common/constants/epm.ts
@@ -16,6 +16,7 @@ export const FLEET_ENDPOINT_PACKAGE = 'endpoint';
export const FLEET_APM_PACKAGE = 'apm';
export const FLEET_SYNTHETICS_PACKAGE = 'synthetics';
export const FLEET_KUBERNETES_PACKAGE = 'kubernetes';
+export const FLEET_UNIVERSAL_PROFILING_SYMBOLIZER_PACKAGE = 'profiler_symbolizer';
export const FLEET_CLOUD_SECURITY_POSTURE_PACKAGE = 'cloud_security_posture';
export const FLEET_CLOUD_SECURITY_POSTURE_KSPM_POLICY_TEMPLATE = 'kspm';
diff --git a/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.test.ts b/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.test.ts
index 1f5ea87c6d6f9..f093b20eaaeb4 100644
--- a/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.test.ts
+++ b/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.test.ts
@@ -13,6 +13,7 @@ import type { DataStreamMeta } from './package_policies_to_agent_permissions';
import {
getDataStreamPrivileges,
storedPackagePoliciesToAgentPermissions,
+ UNIVERSAL_PROFILING_PERMISSIONS,
} from './package_policies_to_agent_permissions';
const packageInfoCache = new Map();
@@ -137,6 +138,56 @@ packageInfoCache.set('osquery_manager-0.3.0', {
},
},
});
+packageInfoCache.set('profiler_symbolizer-8.8.0-preview', {
+ format_version: '2.7.0',
+ name: 'profiler_symbolizer',
+ title: 'Universal Profiling Symbolizer',
+ version: '8.8.0-preview',
+ license: 'basic',
+ description:
+ ' Fleet-wide, whole-system, continuous profiling with zero instrumentation. Symbolize native frames.',
+ type: 'integration',
+ release: 'beta',
+ categories: ['monitoring', 'elastic_stack'],
+ icons: [
+ {
+ src: '/img/logo_profiling_symbolizer.svg',
+ title: 'logo symbolizer',
+ size: '32x32',
+ type: 'image/svg+xml',
+ },
+ ],
+ owner: { github: 'elastic/profiling' },
+ data_streams: [],
+ latestVersion: '8.8.0-preview',
+ notice: undefined,
+ status: 'not_installed',
+ assets: {
+ kibana: {
+ csp_rule_template: [],
+ dashboard: [],
+ visualization: [],
+ search: [],
+ index_pattern: [],
+ map: [],
+ lens: [],
+ security_rule: [],
+ ml_module: [],
+ tag: [],
+ osquery_pack_asset: [],
+ osquery_saved_query: [],
+ },
+ elasticsearch: {
+ component_template: [],
+ ingest_pipeline: [],
+ ilm_policy: [],
+ transform: [],
+ index_template: [],
+ data_stream_ilm_policy: [],
+ ml_model: [],
+ },
+ },
+});
describe('storedPackagePoliciesToAgentPermissions()', () => {
it('Returns `undefined` if there are no package policies', async () => {
@@ -363,6 +414,47 @@ describe('storedPackagePoliciesToAgentPermissions()', () => {
},
});
});
+
+ it('Returns the Universal Profiling permissions for profiler_symbolizer package', async () => {
+ const packagePolicies: PackagePolicy[] = [
+ {
+ id: 'package-policy-uuid-test-123',
+ name: 'test-policy',
+ namespace: '',
+ enabled: true,
+ package: { name: 'profiler_symbolizer', version: '8.8.0-preview', title: 'Test Package' },
+ inputs: [
+ {
+ type: 'pf-elastic-symbolizer',
+ enabled: true,
+ streams: [],
+ },
+ ],
+ created_at: '',
+ updated_at: '',
+ created_by: '',
+ updated_by: '',
+ revision: 1,
+ policy_id: '',
+ },
+ ];
+
+ const permissions = await storedPackagePoliciesToAgentPermissions(
+ packageInfoCache,
+ packagePolicies
+ );
+
+ expect(permissions).toMatchObject({
+ 'package-policy-uuid-test-123': {
+ indices: [
+ {
+ names: ['profiling-*'],
+ privileges: UNIVERSAL_PROFILING_PERMISSIONS,
+ },
+ ],
+ },
+ });
+ });
});
describe('getDataStreamPrivileges()', () => {
diff --git a/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.ts b/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.ts
index 02c44024421ce..f8cd73901e0d7 100644
--- a/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.ts
+++ b/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.ts
@@ -5,6 +5,8 @@
* 2.0.
*/
+import { FLEET_UNIVERSAL_PROFILING_SYMBOLIZER_PACKAGE } from '../../../common/constants';
+
import { getNormalizedDataStreams } from '../../../common/services';
import type {
@@ -19,6 +21,16 @@ import { pkgToPkgKey } from '../epm/registry';
export const DEFAULT_CLUSTER_PERMISSIONS = ['monitor'];
+export const UNIVERSAL_PROFILING_PERMISSIONS = [
+ 'auto_configure',
+ 'read',
+ 'create_doc',
+ 'create',
+ 'write',
+ 'index',
+ 'view_index_metadata',
+];
+
export async function storedPackagePoliciesToAgentPermissions(
packageInfoCache: Map,
packagePolicies?: PackagePolicy[]
@@ -42,6 +54,12 @@ export async function storedPackagePoliciesToAgentPermissions(
const pkg = packageInfoCache.get(pkgToPkgKey(packagePolicy.package))!;
+ // Special handling for Universal Profiling packages, as it does not use data streams _only_,
+ // but also indices that do not adhere to the convention.
+ if (pkg.name === FLEET_UNIVERSAL_PROFILING_SYMBOLIZER_PACKAGE) {
+ return Promise.resolve(universalProfilingPermissions(packagePolicy.id));
+ }
+
const dataStreams = getNormalizedDataStreams(pkg);
if (!dataStreams || dataStreams.length === 0) {
return [packagePolicy.name, undefined];
@@ -175,3 +193,18 @@ export function getDataStreamPrivileges(dataStream: DataStreamMeta, namespace: s
privileges,
};
}
+
+async function universalProfilingPermissions(packagePolicyId: string): Promise<[string, any]> {
+ const profilingIndexPattern = 'profiling-*';
+ return [
+ packagePolicyId,
+ {
+ indices: [
+ {
+ names: [profilingIndexPattern],
+ privileges: UNIVERSAL_PROFILING_PERMISSIONS,
+ },
+ ],
+ },
+ ];
+}
From 953437f05d26ee42ff70eb128e6646617bf7ba62 Mon Sep 17 00:00:00 2001
From: Sander Philipse <94373878+sphilipse@users.noreply.github.com>
Date: Mon, 24 Apr 2023 20:21:39 +0200
Subject: [PATCH 21/36] [Enterprise Search] Fix wording on content settings
page (#155383)
## Summary
This updates the content on the content settings page.
---
packages/kbn-doc-links/src/get_doc_links.ts | 1 +
packages/kbn-doc-links/src/types.ts | 1 +
.../components/settings/settings.tsx | 37 ++++++++++---------
.../components/settings/settings_panel.tsx | 4 +-
.../shared/doc_links/doc_links.ts | 3 ++
.../translations/translations/fr-FR.json | 2 -
.../translations/translations/ja-JP.json | 2 -
.../translations/translations/zh-CN.json | 2 -
8 files changed, 27 insertions(+), 25 deletions(-)
diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts
index d9fbd1dfc8554..dc75ebee9a5eb 100644
--- a/packages/kbn-doc-links/src/get_doc_links.ts
+++ b/packages/kbn-doc-links/src/get_doc_links.ts
@@ -153,6 +153,7 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => {
licenseManagement: `${ENTERPRISE_SEARCH_DOCS}license-management.html`,
machineLearningStart: `${ENTERPRISE_SEARCH_DOCS}machine-learning-start.html`,
mailService: `${ENTERPRISE_SEARCH_DOCS}mailer-configuration.html`,
+ mlDocumentEnrichment: `${ENTERPRISE_SEARCH_DOCS}document-enrichment.html`,
start: `${ENTERPRISE_SEARCH_DOCS}start.html`,
syncRules: `${ENTERPRISE_SEARCH_DOCS}sync-rules.html`,
troubleshootSetup: `${ENTERPRISE_SEARCH_DOCS}troubleshoot-setup.html`,
diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts
index 92dc64e916644..efe5e95f238d0 100644
--- a/packages/kbn-doc-links/src/types.ts
+++ b/packages/kbn-doc-links/src/types.ts
@@ -138,6 +138,7 @@ export interface DocLinks {
readonly licenseManagement: string;
readonly machineLearningStart: string;
readonly mailService: string;
+ readonly mlDocumentEnrichment: string;
readonly start: string;
readonly syncRules: string;
readonly troubleshootSetup: string;
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/settings.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/settings.tsx
index a1bbe6c39956f..840010d3aa219 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/settings.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/settings.tsx
@@ -12,6 +12,8 @@ import { useActions, useValues } from 'kea';
import { EuiButton, EuiLink, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n-react';
+
import { docLinks } from '../../../shared/doc_links';
import { EnterpriseSearchContentPageTemplate } from '../layout/page_template';
@@ -36,6 +38,21 @@ export const Settings: React.FC = () => {
}),
]}
pageHeader={{
+ description: (
+
+ {i18n.translate('xpack.enterpriseSearch.content.settings.ingestLink', {
+ defaultMessage: 'ingest pipelines',
+ })}
+
+ ),
+ }}
+ />
+ ),
pageTitle: i18n.translate('xpack.enterpriseSearch.content.settings.headerTitle', {
defaultMessage: 'Content Settings',
}),
@@ -69,19 +86,12 @@ export const Settings: React.FC = () => {
'xpack.enterpriseSearch.content.settings.contentExtraction.description',
{
defaultMessage:
- 'Allow all ingestion mechanisms on your Enterprise Search deployment to extract searchable content from binary files, like PDFs and Word documents. This setting applies to all new Elasticsearch indices created by an Enterprise Search ingestion mechanism.',
+ 'Extract searchable content from binary files, like PDFs and Word documents.',
}
)}
label={i18n.translate('xpack.enterpriseSearch.content.settings.contactExtraction.label', {
defaultMessage: 'Content extraction',
})}
- link={
-