diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 5fcb619af65704..c91d1a702b7eca 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -315,7 +315,6 @@
/src/plugins/es_ui_shared/ @elastic/kibana-stack-management
/x-pack/plugins/cross_cluster_replication/ @elastic/kibana-stack-management
/x-pack/plugins/index_lifecycle_management/ @elastic/kibana-stack-management
-/x-pack/plugins/console_extensions/ @elastic/kibana-stack-management
/x-pack/plugins/grokdebugger/ @elastic/kibana-stack-management
/x-pack/plugins/index_management/ @elastic/kibana-stack-management
/x-pack/plugins/license_api_guard/ @elastic/kibana-stack-management
@@ -330,7 +329,6 @@
/x-pack/plugins/ingest_pipelines/ @elastic/kibana-stack-management
/packages/kbn-ace/ @elastic/kibana-stack-management
/packages/kbn-monaco/ @elastic/kibana-stack-management
-#CC# /x-pack/plugins/console_extensions/ @elastic/kibana-stack-management
#CC# /x-pack/plugins/cross_cluster_replication/ @elastic/kibana-stack-management
# Security Solution
diff --git a/.github/workflows/project-assigner.yml b/.github/workflows/project-assigner.yml
index 4966a0b5063175..f4e62648a97413 100644
--- a/.github/workflows/project-assigner.yml
+++ b/.github/workflows/project-assigner.yml
@@ -8,8 +8,16 @@ jobs:
name: Assign issue or PR to project based on label
steps:
- name: Assign to project
- uses: elastic/github-actions/project-assigner@v2.0.0
+ uses: elastic/github-actions/project-assigner@v2.1.0
id: project_assigner
with:
- issue-mappings: '[{"label": "Feature:Lens", "projectNumber": 32, "columnName": "Long-term goals"}, {"label": "Feature:Canvas", "projectNumber": 38, "columnName": "Inbox"}, {"label": "Feature:Dashboard", "projectNumber": 68, "columnName": "Inbox"}, {"label": "Feature:Drilldowns", "projectNumber": 68, "columnName": "Inbox"}, {"label": "Feature:Input Controls", "projectNumber": 72, "columnName": "Inbox"}]'
+ issue-mappings: |
+ [
+ {"label": "Feature:Lens", "projectNumber": 32, "columnName": "Long-term goals"},
+ {"label": "Feature:Canvas", "projectNumber": 38, "columnName": "Inbox"},
+ {"label": "Feature:Dashboard", "projectNumber": 68, "columnName": "Inbox"},
+ {"label": "Feature:Drilldowns", "projectNumber": 68, "columnName": "Inbox"},
+ {"label": "Feature:Input Controls", "projectNumber": 72, "columnName": "Inbox"},
+ {"label": "Team:Security", "projectNumber": 320, "columnName": "Awaiting triage", "projectScope": "org"}
+ ]
ghToken: ${{ secrets.PROJECT_ASSIGNER_TOKEN }}
diff --git a/config/kibana.yml b/config/kibana.yml
index eefb6bb8bacdab..dea9849f17b286 100644
--- a/config/kibana.yml
+++ b/config/kibana.yml
@@ -42,6 +42,10 @@
#elasticsearch.username: "kibana_system"
#elasticsearch.password: "pass"
+# Kibana can also authenticate to Elasticsearch via "service account tokens".
+# If may use this token instead of a username/password.
+# elasticsearch.serviceAccountToken: "my_token"
+
# Enables SSL and paths to the PEM-format SSL certificate and SSL key files, respectively.
# These settings enable SSL for outgoing requests from the Kibana server to the browser.
#server.ssl.enabled: false
diff --git a/docs/apm/agent-configuration.asciidoc b/docs/apm/agent-configuration.asciidoc
index 2574d254ac14c1..f2e07412c4a38f 100644
--- a/docs/apm/agent-configuration.asciidoc
+++ b/docs/apm/agent-configuration.asciidoc
@@ -43,6 +43,7 @@ Supported configurations are also tagged with the image:./images/dynamic-config.
[horizontal]
Go Agent:: {apm-go-ref}/configuration.html[Configuration reference]
+iOS agent:: _Not yet supported_
Java Agent:: {apm-java-ref}/configuration.html[Configuration reference]
.NET Agent:: {apm-dotnet-ref}/configuration.html[Configuration reference]
Node.js Agent:: {apm-node-ref}/configuration.html[Configuration reference]
diff --git a/docs/apm/apm-alerts.asciidoc b/docs/apm/apm-alerts.asciidoc
index 3e3e2b178ff10b..42016ac08bfc78 100644
--- a/docs/apm/apm-alerts.asciidoc
+++ b/docs/apm/apm-alerts.asciidoc
@@ -1,69 +1,57 @@
[role="xpack"]
[[apm-alerts]]
-=== Alerts
+=== Alerts and rules
++++
Create an alert
++++
+The APM app allows you to define **rules** to detect complex conditions within your APM data
+and trigger built-in **actions** when those conditions are met.
-The APM app integrates with Kibana's {kibana-ref}/alerting-getting-started.html[alerting and actions] feature.
-It provides a set of built-in **actions** and APM specific threshold **alerts** for you to use
-and enables central management of all alerts from <>.
+The following **rules** are supported:
+
+* Latency anomaly rule:
+Alert when latency of a service is abnormal
+* Transaction error rate threshold rule:
+Alert when the service's transaction error rate is above the defined threshold
+* Error count threshold rule:
+Alert when the number of errors in a service exceeds a defined threshold
[role="screenshot"]
image::apm/images/apm-alert.png[Create an alert in the APM app]
-For a walkthrough of the alert flyout panel, including detailed information on each configurable property,
-see Kibana's <>.
-
-The APM app supports four different types of alerts:
-
-* Transaction duration anomaly:
-alerts when the service's transaction duration reaches a certain anomaly score
-* Transaction duration threshold:
-alerts when the service's transaction duration exceeds a given time limit over a given time frame
-* Transaction error rate threshold:
-alerts when the service's transaction error rate is above the selected rate over a given time frame
-* Error count threshold:
-alerts when service exceeds a selected number of errors over a given time frame
+For a complete walkthrough of the **Create rule** flyout panel, including detailed information on each configurable property,
+see Kibana's <>.
-Below, we'll walk through the creation of two of these alerts.
+Below, we'll walk through the creation of two APM rules.
[float]
[[apm-create-transaction-alert]]
-=== Example: create a transaction duration alert
+=== Example: create a latency anomaly rule
-Transaction duration alerts trigger when the duration of a specific transaction type in a service exceeds a defined threshold.
-This guide will create an alert for the `opbeans-java` service based on the following criteria:
+Latency anomaly rules trigger when the latency of a service is abnormal.
+This guide will create an alert for all services based on the following criteria:
-* Environment: Production
-* Transaction type: `transaction.type:request`
-* Average request is above `1500ms` for the last 5 minutes
-* Check every 10 minutes, and repeat the alert every 30 minutes
-* Send the alert via Slack
+* Environment: production
+* Severity level: critical
+* Run every five minutes
+* Send an alert to a Slack channel only when the rule status changes
-From the APM app, navigate to the `opbeans-java` service and select
-**Alerts** > **Create threshold alert** > **Transaction duration**.
+From any page in the APM app, select **Alerts and rules** > **Latency** > **Create anomaly rule**.
+Change the name of the alert, but do not edit the tags.
-`Transaction duration | opbeans-java` is automatically set as the name of the alert,
-and `apm` and `service.name:opbeans-java` are added as tags.
-It's fine to change the name of the alert, but do not edit the tags.
+Based on the criteria above, define the following rule details:
-Based on the alert criteria, define the following alert details:
+* **Check every** - `5 minutes`
+* **Notify** - "Only on status change"
+* **Environment** - `all`
+* **Has anomaly with severity** - `critical`
-* **Check every** - `10 minutes`
-* **Notify every** - `30 minutes`
-* **TYPE** - `request`
-* **WHEN** - `avg`
-* **IS ABOVE** - `1500ms`
-* **FOR THE LAST** - `5 minutes`
-
-Select an action type.
-Multiple action types can be selected, but in this example, we want to post to a Slack channel.
+Next, add a connector. Multiple connectors can be selected, but in this example we're interested in Slack.
Select **Slack** > **Create a connector**.
Enter a name for the connector,
-and paste the webhook URL.
+and paste your Slack webhook URL.
See Slack's webhook documentation if you need to create one.
A default message is provided as a starting point for your alert.
@@ -72,35 +60,32 @@ to pass additional alert values at the time a condition is detected to an action
A list of available variables can be accessed by selecting the
**add variable** button image:apm/images/add-variable.png[add variable button].
-Select **Save**. The alert has been created and is now active!
+Click **Save**. The rule has been created and is now active!
[float]
[[apm-create-error-alert]]
-=== Example: create an error rate alert
+=== Example: create an error count threshold alert
-Error rate alerts trigger when the number of errors in a service exceeds a defined threshold.
-This guide creates an alert for the `opbeans-python` service based on the following criteria:
+The error count threshold alert triggers when the number of errors in a service exceeds a defined threshold.
+This guide will create an alert for all services based on the following criteria:
-* Environment: Production
+* All environments
* Error rate is above 25 for the last minute
-* Check every 1 minute, and repeat the alert every 10 minutes
-* Send the alert via email to the `opbeans-python` team
-
-From the APM app, navigate to the `opbeans-python` service and select
-**Alerts** > **Create threshold alert** > **Error rate**.
+* Check every 1 minute, and alert every time the rule is active
+* Send the alert via email to the site reliability team
-`Error rate | opbeans-python` is automatically set as the name of the alert,
-and `apm` and `service.name:opbeans-python` are added as tags.
-It's fine to change the name of the alert, but do not edit the tags.
+From any page in the APM app, select **Alerts and rules** > **Error count** > **Create threshold rule**.
+Change the name of the alert, but do not edit the tags.
-Based on the alert criteria, define the following alert details:
+Based on the criteria above, define the following rule details:
* **Check every** - `1 minute`
-* **Notify every** - `10 minutes`
-* **IS ABOVE** - `25 errors`
-* **FOR THE LAST** - `1 minute`
+* **Notify** - "Every time alert is active"
+* **Environment** - `all`
+* **Is above** - `25 errors`
+* **For the last** - `1 minute`
-Select the **Email** action type and click **Create a connector**.
+Select the **Email** connector and click **Create a connector**.
Fill out the required details: sender, host, port, etc., and click **save**.
A default message is provided as a starting point for your alert.
@@ -109,14 +94,14 @@ to pass additional alert values at the time a condition is detected to an action
A list of available variables can be accessed by selecting the
**add variable** button image:apm/images/add-variable.png[add variable button].
-Select **Save**. The alert has been created and is now active!
+Click **Save**. The alert has been created and is now active!
[float]
[[apm-alert-manage]]
-=== Manage alerts and actions
+=== Manage alerts and rules
-From the APM app, select **Alerts** > **View active alerts** to be taken to the Kibana alerts and actions management page.
-From this page, you can create, edit, disable, mute, and delete alerts, and create, edit, and disable connectors.
+From the APM app, select **Alerts and rules** > **Manage rules** to be taken to the Kibana **Rules and Connectors** page.
+From this page, you can disable, mute, and delete APM alerts.
[float]
[[apm-alert-more-info]]
@@ -126,4 +111,4 @@ See {kibana-ref}/alerting-getting-started.html[alerting and actions] for more in
NOTE: If you are using an **on-premise** Elastic Stack deployment with security,
communication between Elasticsearch and Kibana must have TLS configured.
-More information is in the alerting {kibana-ref}/alerting-setup.html#alerting-prerequisites[prerequisites].
\ No newline at end of file
+More information is in the alerting {kibana-ref}/alerting-setup.html#alerting-prerequisites[prerequisites].
diff --git a/docs/apm/filters.asciidoc b/docs/apm/filters.asciidoc
index 56602ab7c05c90..c0ea81c87378bb 100644
--- a/docs/apm/filters.asciidoc
+++ b/docs/apm/filters.asciidoc
@@ -36,6 +36,7 @@ It's vital to be consistent when naming environments in your agents.
To learn how to configure service environments, see the specific agent documentation:
* *Go:* {apm-go-ref}/configuration.html#config-environment[`ELASTIC_APM_ENVIRONMENT`]
+* *iOS agent:* _Not yet supported_
* *Java:* {apm-java-ref}/config-core.html#config-environment[`environment`]
* *.NET:* {apm-dotnet-ref}/config-core.html#config-environment[`Environment`]
* *Node.js:* {apm-node-ref}/configuration.html#environment[`environment`]
diff --git a/docs/apm/images/apm-agent-configuration.png b/docs/apm/images/apm-agent-configuration.png
index 07398f0609187d..22fd9d75c3d730 100644
Binary files a/docs/apm/images/apm-agent-configuration.png and b/docs/apm/images/apm-agent-configuration.png differ
diff --git a/docs/apm/images/apm-alert.png b/docs/apm/images/apm-alert.png
index 2ac91b6b192190..a845d65dd24a53 100644
Binary files a/docs/apm/images/apm-alert.png and b/docs/apm/images/apm-alert.png differ
diff --git a/docs/apm/images/apm-error-group.png b/docs/apm/images/apm-error-group.png
index 359bdc6b704e94..1326e97f757d63 100644
Binary files a/docs/apm/images/apm-error-group.png and b/docs/apm/images/apm-error-group.png differ
diff --git a/docs/apm/images/apm-logs-tab.png b/docs/apm/images/apm-logs-tab.png
index 77aecf744bc7f5..891d2b7a1dd692 100644
Binary files a/docs/apm/images/apm-logs-tab.png and b/docs/apm/images/apm-logs-tab.png differ
diff --git a/docs/apm/images/apm-services-overview.png b/docs/apm/images/apm-services-overview.png
index 1c16ac5b572c3d..7aeb5f1ac379f3 100644
Binary files a/docs/apm/images/apm-services-overview.png and b/docs/apm/images/apm-services-overview.png differ
diff --git a/docs/apm/images/apm-settings.png b/docs/apm/images/apm-settings.png
index c821b7fb76e793..2201ed5fcaa725 100644
Binary files a/docs/apm/images/apm-settings.png and b/docs/apm/images/apm-settings.png differ
diff --git a/docs/apm/images/apm-span-detail.png b/docs/apm/images/apm-span-detail.png
index bacb2d372c1662..c9f55575b2232a 100644
Binary files a/docs/apm/images/apm-span-detail.png and b/docs/apm/images/apm-span-detail.png differ
diff --git a/docs/apm/images/apm-traces.png b/docs/apm/images/apm-traces.png
index 0e9062ee448b43..ee16f9ed16a180 100644
Binary files a/docs/apm/images/apm-traces.png and b/docs/apm/images/apm-traces.png differ
diff --git a/docs/apm/images/apm-transaction-duration-dist.png b/docs/apm/images/apm-transaction-duration-dist.png
index 863f493f20db49..91ae6c3a59ad26 100644
Binary files a/docs/apm/images/apm-transaction-duration-dist.png and b/docs/apm/images/apm-transaction-duration-dist.png differ
diff --git a/docs/apm/images/apm-transaction-response-dist.png b/docs/apm/images/apm-transaction-response-dist.png
index 2f3e69f263a28c..70e5ad7041287f 100644
Binary files a/docs/apm/images/apm-transaction-response-dist.png and b/docs/apm/images/apm-transaction-response-dist.png differ
diff --git a/docs/apm/images/apm-transaction-sample.png b/docs/apm/images/apm-transaction-sample.png
index 0e4bc5f3f878af..54eea902f03111 100644
Binary files a/docs/apm/images/apm-transaction-sample.png and b/docs/apm/images/apm-transaction-sample.png differ
diff --git a/docs/apm/images/apm-transactions-overview.png b/docs/apm/images/apm-transactions-overview.png
index be292c37e24e01..66cf739a861b7f 100644
Binary files a/docs/apm/images/apm-transactions-overview.png and b/docs/apm/images/apm-transactions-overview.png differ
diff --git a/docs/apm/images/service-maps-java.png b/docs/apm/images/service-maps-java.png
index d7c0786e406d94..25600b690a5bd2 100644
Binary files a/docs/apm/images/service-maps-java.png and b/docs/apm/images/service-maps-java.png differ
diff --git a/docs/apm/images/service-maps.png b/docs/apm/images/service-maps.png
index 190b7af3c560e8..511d8401b22f39 100644
Binary files a/docs/apm/images/service-maps.png and b/docs/apm/images/service-maps.png differ
diff --git a/docs/apm/service-maps.asciidoc b/docs/apm/service-maps.asciidoc
index 99a6205ae010e4..f43253d8194290 100644
--- a/docs/apm/service-maps.asciidoc
+++ b/docs/apm/service-maps.asciidoc
@@ -108,6 +108,7 @@ Service maps are supported for the following Agent versions:
[horizontal]
Go agent:: ≥ v1.7.0
+iOS agent:: _Not yet supported_
Java agent:: ≥ v1.13.0
.NET agent:: ≥ v1.3.0
Node.js agent:: ≥ v3.6.0
diff --git a/docs/apm/transactions.asciidoc b/docs/apm/transactions.asciidoc
index c2a3e0bc2502dd..76006d375d0750 100644
--- a/docs/apm/transactions.asciidoc
+++ b/docs/apm/transactions.asciidoc
@@ -100,22 +100,22 @@ the selected transaction group.
image::apm/images/apm-transaction-response-dist.png[Example view of response time distribution]
[[transaction-duration-distribution]]
-==== Transactions duration distribution
+==== Latency distribution
-This chart plots all transaction durations for the given time period.
+A plot of all transaction durations for the given time period.
The screenshot below shows a typical distribution,
and indicates most of our requests were served quickly -- awesome!
-It's the requests on the right, the ones taking longer than average, that we probably want to focus on.
+It's the requests on the right, the ones taking longer than average, that we probably need to focus on.
[role="screenshot"]
-image::apm/images/apm-transaction-duration-dist.png[Example view of transactions duration distribution graph]
+image::apm/images/apm-transaction-duration-dist.png[Example view of latency distribution graph]
-Select a transaction duration _bucket_ to display up to ten trace samples.
+Select a latency duration _bucket_ to display up to ten trace samples.
[[transaction-trace-sample]]
==== Trace sample
-Trace samples are based on the _bucket_ selection in the *Transactions duration distribution* chart;
+Trace samples are based on the _bucket_ selection in the *Latency distribution* chart;
update the samples by selecting a new _bucket_.
The number of requests per bucket is displayed when hovering over the graph,
and the selected bucket is highlighted to stand out.
diff --git a/docs/apm/troubleshooting.asciidoc b/docs/apm/troubleshooting.asciidoc
index 8cab7bb03da75c..4a62f71528676c 100644
--- a/docs/apm/troubleshooting.asciidoc
+++ b/docs/apm/troubleshooting.asciidoc
@@ -15,6 +15,7 @@ don't forget to check our other troubleshooting guides or discussion forum:
* {apm-server-ref}/troubleshooting.html[APM Server troubleshooting]
* {apm-dotnet-ref}/troubleshooting.html[.NET agent troubleshooting]
* {apm-go-ref}/troubleshooting.html[Go agent troubleshooting]
+* {apm-ios-ref}/troubleshooting.html[iOS agent troubleshooting]
* {apm-java-ref}/trouble-shooting.html[Java agent troubleshooting]
* {apm-node-ref}/troubleshooting.html[Node.js agent troubleshooting]
* {apm-php-ref}/troubleshooting.html[PHP agent troubleshooting]
diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc
index eee92ba4337213..5f49360c926bfa 100644
--- a/docs/developer/plugin-list.asciidoc
+++ b/docs/developer/plugin-list.asciidoc
@@ -354,10 +354,6 @@ The plugin exposes the static DefaultEditorController class to consume.
The client-side plugin configures following values:
-|{kib-repo}blob/{branch}/x-pack/plugins/console_extensions/README.md[consoleExtensions]
-|This plugin provides autocomplete definitions of licensed APIs to the OSS Console plugin.
-
-
|{kib-repo}blob/{branch}/x-pack/plugins/cross_cluster_replication/README.md[crossClusterReplication]
|You can run a local cluster and simulate a remote cluster within a single Kibana directory.
@@ -393,7 +389,7 @@ security and spaces filtering as well as performing audit logging.
|{kib-repo}blob/{branch}/x-pack/plugins/enterprise_search/README.md[enterpriseSearch]
-|This plugin's goal is to provide a Kibana user interface to the Enterprise Search solution's products (App Search and Workplace Search). In it's current MVP state, the plugin provides the following with the goal of gathering user feedback and raising product awareness:
+|This plugin provides beta Kibana user interfaces for managing the Enterprise Search solution and its products, App Search and Workplace Search.
|{kib-repo}blob/{branch}/x-pack/plugins/event_log/README.md[eventLog]
diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchclientconfig.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchclientconfig.md
index a854e5ddad19a6..208e0e0175d715 100644
--- a/docs/development/core/server/kibana-plugin-core-server.elasticsearchclientconfig.md
+++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchclientconfig.md
@@ -9,7 +9,7 @@ Configuration options to be used to create a [cluster client](./kibana-plugin-co
Signature:
```typescript
-export declare type ElasticsearchClientConfig = Pick & {
+export declare type ElasticsearchClientConfig = Pick & {
pingTimeout?: ElasticsearchConfig['pingTimeout'] | ClientOptions['pingTimeout'];
requestTimeout?: ElasticsearchConfig['requestTimeout'] | ClientOptions['requestTimeout'];
ssl?: Partial;
diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchconfig.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchconfig.md
index d87ea63d59b8dd..a9ed614ba7552b 100644
--- a/docs/development/core/server/kibana-plugin-core-server.elasticsearchconfig.md
+++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchconfig.md
@@ -31,10 +31,11 @@ export declare class ElasticsearchConfig
| [pingTimeout](./kibana-plugin-core-server.elasticsearchconfig.pingtimeout.md) | | Duration
| Timeout after which PING HTTP request will be aborted and retried. |
| [requestHeadersWhitelist](./kibana-plugin-core-server.elasticsearchconfig.requestheaderswhitelist.md) | | string[]
| List of Kibana client-side headers to send to Elasticsearch when request scoped cluster client is used. If this is an empty array then \*no\* client-side will be sent. |
| [requestTimeout](./kibana-plugin-core-server.elasticsearchconfig.requesttimeout.md) | | Duration
| Timeout after which HTTP request will be aborted and retried. |
+| [serviceAccountToken](./kibana-plugin-core-server.elasticsearchconfig.serviceaccounttoken.md) | | string
| If Elasticsearch security features are enabled, this setting provides the service account token that the Kibana server users to perform its administrative functions.This is an alternative to specifying a username and password. |
| [shardTimeout](./kibana-plugin-core-server.elasticsearchconfig.shardtimeout.md) | | Duration
| Timeout for Elasticsearch to wait for responses from shards. Set to 0 to disable. |
| [sniffInterval](./kibana-plugin-core-server.elasticsearchconfig.sniffinterval.md) | | false | Duration
| Interval to perform a sniff operation and make sure the list of nodes is complete. If false
then sniffing is disabled. |
| [sniffOnConnectionFault](./kibana-plugin-core-server.elasticsearchconfig.sniffonconnectionfault.md) | | boolean
| Specifies whether the client should immediately sniff for a more current list of nodes when a connection dies. |
| [sniffOnStart](./kibana-plugin-core-server.elasticsearchconfig.sniffonstart.md) | | boolean
| Specifies whether the client should attempt to detect the rest of the cluster when it is first instantiated. |
| [ssl](./kibana-plugin-core-server.elasticsearchconfig.ssl.md) | | Pick<SslConfigSchema, Exclude<keyof SslConfigSchema, 'certificateAuthorities' | 'keystore' | 'truststore'>> & {
certificateAuthorities?: string[];
}
| Set of settings configure SSL connection between Kibana and Elasticsearch that are required when xpack.ssl.verification_mode
in Elasticsearch is set to either certificate
or full
. |
-| [username](./kibana-plugin-core-server.elasticsearchconfig.username.md) | | string
| If Elasticsearch is protected with basic authentication, this setting provides the username that the Kibana server uses to perform its administrative functions. |
+| [username](./kibana-plugin-core-server.elasticsearchconfig.username.md) | | string
| If Elasticsearch is protected with basic authentication, this setting provides the username that the Kibana server uses to perform its administrative functions. Cannot be used in conjunction with serviceAccountToken. |
diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchconfig.serviceaccounttoken.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchconfig.serviceaccounttoken.md
new file mode 100644
index 00000000000000..5934e83de17a40
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchconfig.serviceaccounttoken.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ElasticsearchConfig](./kibana-plugin-core-server.elasticsearchconfig.md) > [serviceAccountToken](./kibana-plugin-core-server.elasticsearchconfig.serviceaccounttoken.md)
+
+## ElasticsearchConfig.serviceAccountToken property
+
+If Elasticsearch security features are enabled, this setting provides the service account token that the Kibana server users to perform its administrative functions.
+
+This is an alternative to specifying a username and password.
+
+Signature:
+
+```typescript
+readonly serviceAccountToken?: string;
+```
diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchconfig.username.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchconfig.username.md
index 14db9f2e36ccf2..959870ff43a0f4 100644
--- a/docs/development/core/server/kibana-plugin-core-server.elasticsearchconfig.username.md
+++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchconfig.username.md
@@ -4,7 +4,7 @@
## ElasticsearchConfig.username property
-If Elasticsearch is protected with basic authentication, this setting provides the username that the Kibana server uses to perform its administrative functions.
+If Elasticsearch is protected with basic authentication, this setting provides the username that the Kibana server uses to perform its administrative functions. Cannot be used in conjunction with serviceAccountToken.
Signature:
diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearchclientconfig.md b/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearchclientconfig.md
index b028a09bee4531..a80ebe2fee4937 100644
--- a/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearchclientconfig.md
+++ b/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearchclientconfig.md
@@ -11,7 +11,7 @@
Signature:
```typescript
-export declare type LegacyElasticsearchClientConfig = Pick & Pick & {
+export declare type LegacyElasticsearchClientConfig = Pick & Pick & {
pingTimeout?: ElasticsearchConfig['pingTimeout'] | ConfigOptions['pingTimeout'];
requestTimeout?: ElasticsearchConfig['requestTimeout'] | ConfigOptions['requestTimeout'];
sniffInterval?: ElasticsearchConfig['sniffInterval'] | ConfigOptions['sniffInterval'];
diff --git a/docs/maps/vector-layer.asciidoc b/docs/maps/vector-layer.asciidoc
index 2115c16a889c63..5017ecf91dffd6 100644
--- a/docs/maps/vector-layer.asciidoc
+++ b/docs/maps/vector-layer.asciidoc
@@ -20,10 +20,10 @@ The index must contain at least one field mapped as {ref}/geo-point.html[geo_poi
Results are limited to the `index.max_result_window` index setting, which defaults to 10000.
Select the appropriate *Scaling* option for your use case.
+
-* *Limit results to 10000.* The layer displays features from the first `index.max_result_window` documents.
+* *Limit results to 10,000* The layer displays features from the first `index.max_result_window` documents.
Results exceeding `index.max_result_window` are not displayed.
-* *Show clusters when results exceed 10000.* When results exceed `index.max_result_window`, the layer uses {ref}/search-aggregations-bucket-geotilegrid-aggregation.html[GeoTile grid aggregation] to group your documents into clusters and displays metrics for each cluster. When results are less then `index.max_result_window`, the layer displays features from individual documents.
+* *Show clusters when results exceed 10,000* When results exceed `index.max_result_window`, the layer uses {ref}/search-aggregations-bucket-geotilegrid-aggregation.html[GeoTile grid aggregation] to group your documents into clusters and displays metrics for each cluster. When results are less then `index.max_result_window`, the layer displays features from individual documents.
* *Use vector tiles.* Vector tiles partition your map into 6 to 8 tiles.
Each tile request is limited to the `index.max_result_window` index setting.
diff --git a/docs/settings/apm-settings.asciidoc b/docs/settings/apm-settings.asciidoc
index 79fa9a642428af..dfb239f0e26c06 100644
--- a/docs/settings/apm-settings.asciidoc
+++ b/docs/settings/apm-settings.asciidoc
@@ -18,7 +18,7 @@ It is enabled by default.
// Any changes made in this file will be seen there as well.
// tag::apm-indices-settings[]
-Index defaults can be changed in Kibana. Open the main menu, then click *APM > Settings > Indices*.
+Index defaults can be changed in the APM app. Select **Settings** > **Indices**.
Index settings in the APM app take precedence over those set in `kibana.yml`.
[role="screenshot"]
diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc
index ba333deeb1609c..15abd0fa4ad962 100644
--- a/docs/setup/settings.asciidoc
+++ b/docs/setup/settings.asciidoc
@@ -284,6 +284,11 @@ the username and password that the {kib} server uses to perform maintenance
on the {kib} index at startup. {kib} users still need to authenticate with
{es}, which is proxied through the {kib} server.
+|[[elasticsearch-service-account-token]] `elasticsearch.serviceAccountToken:`
+ | beta[]. If your {es} is protected with basic authentication, this token provides the credentials
+that the {kib} server uses to perform maintenance on the {kib} index at startup. This setting
+is an alternative to `elasticsearch.username` and `elasticsearch.password`.
+
| `enterpriseSearch.host`
| The URL of your Enterprise Search instance
diff --git a/docs/user/dashboard/tsvb.asciidoc b/docs/user/dashboard/tsvb.asciidoc
index 89da3f7285924e..11fe71b7639bb5 100644
--- a/docs/user/dashboard/tsvb.asciidoc
+++ b/docs/user/dashboard/tsvb.asciidoc
@@ -148,6 +148,27 @@ The *Markdown* visualization supports Markdown with Handlebar (mustache) syntax
For answers to frequently asked *TSVB* question, review the following.
+[float]
+===== How do I create dashboard drilldowns for Top N and Table visualizations?
+
+You can create dashboard drilldowns that include the specified time range for *Top N* and *Table* visualizations.
+
+. Open the dashboard that you want to link to, then copy the URL.
+
+. Open the dashboard with the *Top N* and *Table* visualization panel, then click *Edit* in the toolbar.
+
+. Open the *Top N* or *Table* panel menu, then select *Edit visualization*.
+
+. Click *Panel options*.
+
+. In the *Item URL* field, enter the URL.
++
+For example `dashboards#/view/f193ca90-c9f4-11eb-b038-dd3270053a27`.
+
+. Click *Save and return*.
+
+. In the toolbar, cick *Save as*, then make sure *Store time with dashboard* is deselected.
+
[float]
===== Why is my TSVB visualization missing data?
diff --git a/docs/user/management.asciidoc b/docs/user/management.asciidoc
index b86fa82c30381b..2f9f1fe371dc34 100644
--- a/docs/user/management.asciidoc
+++ b/docs/user/management.asciidoc
@@ -82,9 +82,10 @@ connectors>> for triggering actions.
| Monitor the generation of reports—PDF, PNG, and CSV—and download reports that you previously generated.
A report can contain a dashboard, visualization, saved search, or Canvas workpad.
-| {ml-docs}/ml-jobs.html[Machine Learning Jobs]
-| View your {anomaly-jobs} and {dfanalytics-jobs}. Open the Single Metric
-Viewer or Anomaly Explorer to see your {ml} results.
+| Machine Learning Jobs
+| View your <> and
+<> jobs. Open the Single Metric
+Viewer or Anomaly Explorer to see your {anomaly-detect} results.
| <>
| Detect changes in your data by creating, managing, and monitoring alerts.
diff --git a/docs/user/ml/index.asciidoc b/docs/user/ml/index.asciidoc
index b3606b122d750e..a05ff1eeec4a65 100644
--- a/docs/user/ml/index.asciidoc
+++ b/docs/user/ml/index.asciidoc
@@ -48,8 +48,9 @@ pane:
image::user/ml/images/ml-job-management.png[Job Management]
You can use the *Settings* pane to create and edit
-{ml-docs}/ml-calendars.html[calendars] and the filters that are used in
-{ml-docs}/ml-rules.html[custom rules]:
+{ml-docs}/ml-ad-finding-anomalies.html#ml-ad-calendars[calendars] and the
+filters that are used in
+{ml-docs}/ml-ad-finding-anomalies.html#ml-ad-rules[custom rules]:
[role="screenshot"]
image::user/ml/images/ml-settings.png[Calendar Management]
diff --git a/package.json b/package.json
index 22eedde59c5e7d..5cf72e2110982f 100644
--- a/package.json
+++ b/package.json
@@ -99,7 +99,7 @@
"dependencies": {
"@elastic/apm-rum": "^5.8.0",
"@elastic/apm-rum-react": "^1.2.11",
- "@elastic/charts": "31.1.0",
+ "@elastic/charts": "32.0.0",
"@elastic/datemath": "link:bazel-bin/packages/elastic-datemath",
"@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary.13",
"@elastic/ems-client": "7.14.0",
diff --git a/src/plugins/kibana_legacy/public/notify/toasts/index.ts b/packages/kbn-optimizer/src/__fixtures__/mock_repo/packages/kbn-ui-shared-deps/src/public_path_module_creator.ts
similarity index 85%
rename from src/plugins/kibana_legacy/public/notify/toasts/index.ts
rename to packages/kbn-optimizer/src/__fixtures__/mock_repo/packages/kbn-ui-shared-deps/src/public_path_module_creator.ts
index cdd7df04548fbb..b03ee16d2f7463 100644
--- a/src/plugins/kibana_legacy/public/notify/toasts/index.ts
+++ b/packages/kbn-optimizer/src/__fixtures__/mock_repo/packages/kbn-ui-shared-deps/src/public_path_module_creator.ts
@@ -6,4 +6,4 @@
* Side Public License, v 1.
*/
-export { ToastNotifications } from './toast_notifications';
+// stub
diff --git a/packages/kbn-optimizer/src/common/__snapshots__/parse_path.test.ts.snap b/packages/kbn-optimizer/src/common/__snapshots__/parse_path.test.ts.snap
index f537674c3fff7e..2a30694afb8263 100644
--- a/packages/kbn-optimizer/src/common/__snapshots__/parse_path.test.ts.snap
+++ b/packages/kbn-optimizer/src/common/__snapshots__/parse_path.test.ts.snap
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`parseDirPath() parses / 1`] = `
-Object {
+ParsedPath {
"dirs": Array [],
"filename": undefined,
"query": undefined,
@@ -10,7 +10,7 @@ Object {
`;
exports[`parseDirPath() parses /foo 1`] = `
-Object {
+ParsedPath {
"dirs": Array [
"foo",
],
@@ -21,7 +21,7 @@ Object {
`;
exports[`parseDirPath() parses /foo/bar/baz 1`] = `
-Object {
+ParsedPath {
"dirs": Array [
"foo",
"bar",
@@ -34,7 +34,7 @@ Object {
`;
exports[`parseDirPath() parses /foo/bar/baz/ 1`] = `
-Object {
+ParsedPath {
"dirs": Array [
"foo",
"bar",
@@ -47,7 +47,7 @@ Object {
`;
exports[`parseDirPath() parses c:\\ 1`] = `
-Object {
+ParsedPath {
"dirs": Array [],
"filename": undefined,
"query": undefined,
@@ -56,7 +56,7 @@ Object {
`;
exports[`parseDirPath() parses c:\\foo 1`] = `
-Object {
+ParsedPath {
"dirs": Array [
"foo",
],
@@ -67,7 +67,7 @@ Object {
`;
exports[`parseDirPath() parses c:\\foo\\bar\\baz 1`] = `
-Object {
+ParsedPath {
"dirs": Array [
"foo",
"bar",
@@ -80,7 +80,7 @@ Object {
`;
exports[`parseDirPath() parses c:\\foo\\bar\\baz\\ 1`] = `
-Object {
+ParsedPath {
"dirs": Array [
"foo",
"bar",
@@ -93,7 +93,7 @@ Object {
`;
exports[`parseFilePath() parses /foo 1`] = `
-Object {
+ParsedPath {
"dirs": Array [],
"filename": "foo",
"query": undefined,
@@ -102,7 +102,7 @@ Object {
`;
exports[`parseFilePath() parses /foo/bar/baz 1`] = `
-Object {
+ParsedPath {
"dirs": Array [
"foo",
"bar",
@@ -114,7 +114,7 @@ Object {
`;
exports[`parseFilePath() parses /foo/bar/baz.json 1`] = `
-Object {
+ParsedPath {
"dirs": Array [
"foo",
"bar",
@@ -126,7 +126,7 @@ Object {
`;
exports[`parseFilePath() parses /foo/bar/baz.json?light 1`] = `
-Object {
+ParsedPath {
"dirs": Array [
"foo",
"bar",
@@ -140,7 +140,7 @@ Object {
`;
exports[`parseFilePath() parses /foo/bar/baz.json?light=true&dark=false 1`] = `
-Object {
+ParsedPath {
"dirs": Array [
"foo",
"bar",
@@ -155,7 +155,7 @@ Object {
`;
exports[`parseFilePath() parses c:/foo/bar/baz.json 1`] = `
-Object {
+ParsedPath {
"dirs": Array [
"foo",
"bar",
@@ -167,7 +167,7 @@ Object {
`;
exports[`parseFilePath() parses c:\\foo 1`] = `
-Object {
+ParsedPath {
"dirs": Array [],
"filename": "foo",
"query": undefined,
@@ -176,7 +176,7 @@ Object {
`;
exports[`parseFilePath() parses c:\\foo\\bar\\baz 1`] = `
-Object {
+ParsedPath {
"dirs": Array [
"foo",
"bar",
@@ -188,7 +188,7 @@ Object {
`;
exports[`parseFilePath() parses c:\\foo\\bar\\baz.json 1`] = `
-Object {
+ParsedPath {
"dirs": Array [
"foo",
"bar",
@@ -200,7 +200,7 @@ Object {
`;
exports[`parseFilePath() parses c:\\foo\\bar\\baz.json?dark 1`] = `
-Object {
+ParsedPath {
"dirs": Array [
"foo",
"bar",
@@ -214,7 +214,7 @@ Object {
`;
exports[`parseFilePath() parses c:\\foo\\bar\\baz.json?dark=true&light=false 1`] = `
-Object {
+ParsedPath {
"dirs": Array [
"foo",
"bar",
diff --git a/packages/kbn-optimizer/src/common/parse_path.ts b/packages/kbn-optimizer/src/common/parse_path.ts
index 7ea0042db25c97..da3744ba477bdd 100644
--- a/packages/kbn-optimizer/src/common/parse_path.ts
+++ b/packages/kbn-optimizer/src/common/parse_path.ts
@@ -9,17 +9,61 @@
import normalizePath from 'normalize-path';
import Qs from 'querystring';
+class ParsedPath {
+ constructor(
+ public readonly root: string,
+ public readonly dirs: string[],
+ public readonly query?: Record,
+ public readonly filename?: string
+ ) {}
+
+ private indexOfDir(match: string | RegExp, fromIndex: number = 0) {
+ for (let i = fromIndex; i < this.dirs.length; i++) {
+ if (this.matchDir(i, match)) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ private matchDir(i: number, match: string | RegExp) {
+ return typeof match === 'string' ? this.dirs[i] === match : match.test(this.dirs[i]);
+ }
+
+ matchDirs(...segments: Array) {
+ const [first, ...rest] = segments;
+ let fromIndex = 0;
+ while (true) {
+ // do the dirs include the first segment to match?
+ const startIndex = this.indexOfDir(first, fromIndex);
+ if (startIndex === -1) {
+ return;
+ }
+
+ // are all of the ...rest segments also matched at this point?
+ if (!rest.length || rest.every((seg, i) => this.matchDir(startIndex + 1 + i, seg))) {
+ return { startIndex, endIndex: startIndex + rest.length };
+ }
+
+ // no match, search again, this time looking at instances after the matched instance
+ fromIndex = startIndex + 1;
+ }
+ }
+}
+
/**
* Parse an absolute path, supporting normalized paths from webpack,
* into a list of directories and root
*/
export function parseDirPath(path: string) {
const filePath = parseFilePath(path);
- return {
- ...filePath,
- dirs: [...filePath.dirs, ...(filePath.filename ? [filePath.filename] : [])],
- filename: undefined,
- };
+ return new ParsedPath(
+ filePath.root,
+ [...filePath.dirs, ...(filePath.filename ? [filePath.filename] : [])],
+ filePath.query,
+ undefined
+ );
}
export function parseFilePath(path: string) {
@@ -32,10 +76,10 @@ export function parseFilePath(path: string) {
}
const [root, ...others] = normalized.split('/');
- return {
- root: root === '' ? '/' : root,
- dirs: others.slice(0, -1),
+ return new ParsedPath(
+ root === '' ? '/' : root,
+ others.slice(0, -1),
query,
- filename: others[others.length - 1] || undefined,
- };
+ others[others.length - 1] || undefined
+ );
}
diff --git a/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts b/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts
index 97a7f33be673d0..48d36b706b8312 100644
--- a/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts
+++ b/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts
@@ -15,7 +15,7 @@ import cpy from 'cpy';
import del from 'del';
import { tap, filter } from 'rxjs/operators';
import { REPO_ROOT } from '@kbn/utils';
-import { ToolingLog, createReplaceSerializer } from '@kbn/dev-utils';
+import { ToolingLog } from '@kbn/dev-utils';
import { runOptimizer, OptimizerConfig, OptimizerUpdate, logOptimizerState } from '../index';
import { allValuesFrom } from '../common';
@@ -29,8 +29,6 @@ expect.addSnapshotSerializer({
test: (value: any) => typeof value === 'string' && value.includes(REPO_ROOT),
});
-expect.addSnapshotSerializer(createReplaceSerializer(/\w+-fastbuild/, '-fastbuild'));
-
const log = new ToolingLog({
level: 'error',
writeTo: {
@@ -132,7 +130,7 @@ it('builds expected bundles, saves bundle counts to metadata', async () => {
expect(foo.cache.getModuleCount()).toBe(6);
expect(foo.cache.getReferencedFiles()).toMatchInlineSnapshot(`
Array [
- /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/bazel-out/-fastbuild/bin/packages/kbn-ui-shared-deps/target/public_path_module_creator.js,
+ /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/packages/kbn-ui-shared-deps/src/public_path_module_creator.ts,
/packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/kibana.json,
/packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/async_import.ts,
/packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/ext.ts,
@@ -155,7 +153,7 @@ it('builds expected bundles, saves bundle counts to metadata', async () => {
/node_modules/@kbn/optimizer/postcss.config.js,
/node_modules/css-loader/package.json,
/node_modules/style-loader/package.json,
- /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/bazel-out/-fastbuild/bin/packages/kbn-ui-shared-deps/target/public_path_module_creator.js,
+ /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/packages/kbn-ui-shared-deps/src/public_path_module_creator.ts,
/packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/kibana.json,
/packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/public/index.scss,
/packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/public/index.ts,
@@ -175,7 +173,7 @@ it('builds expected bundles, saves bundle counts to metadata', async () => {
expect(baz.cache.getReferencedFiles()).toMatchInlineSnapshot(`
Array [
- /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/bazel-out/-fastbuild/bin/packages/kbn-ui-shared-deps/target/public_path_module_creator.js,
+ /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/packages/kbn-ui-shared-deps/src/public_path_module_creator.ts,
/packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/x-pack/baz/kibana.json,
/packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/x-pack/baz/public/index.ts,
/packages/kbn-optimizer/src/worker/entry_point_creator.ts,
diff --git a/packages/kbn-optimizer/src/worker/populate_bundle_cache_plugin.ts b/packages/kbn-optimizer/src/worker/populate_bundle_cache_plugin.ts
index 8d890b31b639da..a3455d7ddf2b96 100644
--- a/packages/kbn-optimizer/src/worker/populate_bundle_cache_plugin.ts
+++ b/packages/kbn-optimizer/src/worker/populate_bundle_cache_plugin.ts
@@ -6,11 +6,11 @@
* Side Public License, v 1.
*/
-import webpack from 'webpack';
-
import Path from 'path';
import { inspect } from 'util';
+import webpack from 'webpack';
+
import { Bundle, WorkerConfig, ascending, parseFilePath } from '../common';
import { BundleRefModule } from './bundle_ref_module';
import {
@@ -21,6 +21,20 @@ import {
getModulePath,
} from './webpack_helpers';
+function tryToResolveRewrittenPath(from: string, toResolve: string) {
+ try {
+ return require.resolve(toResolve);
+ } catch (error) {
+ if (error.code === 'MODULE_NOT_FOUND') {
+ throw new Error(
+ `attempted to rewrite bazel-out path [${from}] to [${toResolve}] but couldn't find the rewrite target`
+ );
+ }
+
+ throw error;
+ }
+}
+
/**
* sass-loader creates about a 40% overhead on the overall optimizer runtime, and
* so this constant is used to indicate to assignBundlesToWorkers() that there is
@@ -57,17 +71,44 @@ export class PopulateBundleCachePlugin {
let path = getModulePath(module);
let parsedPath = parseFilePath(path);
- if (parsedPath.dirs.includes('bazel-out')) {
- const index = parsedPath.dirs.indexOf('bazel-out');
- path = Path.join(
- workerConfig.repoRoot,
- 'bazel-out',
- ...parsedPath.dirs.slice(index + 1),
- parsedPath.filename ?? ''
+ const bazelOut = parsedPath.matchDirs(
+ 'bazel-out',
+ /-fastbuild$/,
+ 'bin',
+ 'packages',
+ /.*/,
+ 'target'
+ );
+
+ // if the module is referenced from one of our packages and resolved to the `bazel-out` dir
+ // we should rewrite our reference to point to the source file so that we can track the
+ // modified time of that file rather than the built output which is rebuilt all the time
+ // without actually changing
+ if (bazelOut) {
+ const packageDir = parsedPath.dirs[bazelOut.endIndex - 1];
+ const subDirs = parsedPath.dirs.slice(bazelOut.endIndex + 1);
+ path = tryToResolveRewrittenPath(
+ path,
+ Path.join(
+ workerConfig.repoRoot,
+ 'packages',
+ packageDir,
+ 'src',
+ ...subDirs,
+ parsedPath.filename
+ ? Path.basename(parsedPath.filename, Path.extname(parsedPath.filename))
+ : ''
+ )
);
parsedPath = parseFilePath(path);
}
+ if (parsedPath.matchDirs('bazel-out')) {
+ throw new Error(
+ `a bazel-out dir is being referenced by module [${path}] and not getting rewritten to its source location`
+ );
+ }
+
if (!parsedPath.dirs.includes('node_modules')) {
referencedFiles.add(path);
diff --git a/packages/kbn-securitysolution-list-utils/src/autocomplete_operators/index.ts b/packages/kbn-securitysolution-list-utils/src/autocomplete_operators/index.ts
index 967cebc360f61b..051c359dc46129 100644
--- a/packages/kbn-securitysolution-list-utils/src/autocomplete_operators/index.ts
+++ b/packages/kbn-securitysolution-list-utils/src/autocomplete_operators/index.ts
@@ -11,8 +11,7 @@ import {
ListOperatorEnum as OperatorEnum,
ListOperatorTypeEnum as OperatorTypeEnum,
} from '@kbn/securitysolution-io-ts-list-types';
-
-import { OperatorOption } from './types';
+import { OperatorOption } from '../types';
export const isOperator: OperatorOption = {
message: i18n.translate('lists.exceptions.isOperatorLabel', {
diff --git a/packages/kbn-securitysolution-list-utils/src/autocomplete_operators/types.ts b/packages/kbn-securitysolution-list-utils/src/autocomplete_operators/types.ts
deleted file mode 100644
index 1be21bb62a7fee..00000000000000
--- a/packages/kbn-securitysolution-list-utils/src/autocomplete_operators/types.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-import type {
- ListOperatorEnum as OperatorEnum,
- ListOperatorTypeEnum as OperatorTypeEnum,
-} from '@kbn/securitysolution-io-ts-list-types';
-
-export interface OperatorOption {
- message: string;
- value: string;
- operator: OperatorEnum;
- type: OperatorTypeEnum;
-}
diff --git a/packages/kbn-securitysolution-list-utils/src/helpers/index.ts b/packages/kbn-securitysolution-list-utils/src/helpers/index.ts
index d208624b69fc5e..38446b2a08ec0f 100644
--- a/packages/kbn-securitysolution-list-utils/src/helpers/index.ts
+++ b/packages/kbn-securitysolution-list-utils/src/helpers/index.ts
@@ -43,7 +43,6 @@ import {
isOneOfOperator,
isOperator,
} from '../autocomplete_operators';
-import { OperatorOption } from '../autocomplete_operators/types';
import {
BuilderEntry,
@@ -52,6 +51,7 @@ import {
EmptyNestedEntry,
ExceptionsBuilderExceptionItem,
FormattedBuilderEntry,
+ OperatorOption,
} from '../types';
export const isEntryNested = (item: BuilderEntry): item is EntryNested => {
diff --git a/packages/kbn-securitysolution-list-utils/src/types/index.ts b/packages/kbn-securitysolution-list-utils/src/types/index.ts
index faf68ca1579812..537ac06a49f34b 100644
--- a/packages/kbn-securitysolution-list-utils/src/types/index.ts
+++ b/packages/kbn-securitysolution-list-utils/src/types/index.ts
@@ -23,7 +23,12 @@ import {
EXCEPTION_LIST_NAMESPACE_AGNOSTIC,
} from '@kbn/securitysolution-list-constants';
-import type { OperatorOption } from '../autocomplete_operators/types';
+export interface OperatorOption {
+ message: string;
+ value: string;
+ operator: OperatorEnum;
+ type: OperatorTypeEnum;
+}
/**
* @deprecated Use the one from core once it is in its own package which will be from:
diff --git a/packages/kbn-spec-to-console/README.md b/packages/kbn-spec-to-console/README.md
index 0328dec791320c..a0e654713f61bd 100644
--- a/packages/kbn-spec-to-console/README.md
+++ b/packages/kbn-spec-to-console/README.md
@@ -18,15 +18,10 @@ git pull --depth=1 origin master
### Usage
-You need to run the command twice: once for the **OSS** specs and once for the **X-Pack** specs
At the root of the Kibana repository, run the following commands:
```sh
-# OSS
yarn spec_to_console -g "/rest-api-spec/src/main/resources/rest-api-spec/api/*" -d "src/plugins/console/server/lib/spec_definitions/json/generated"
-
-# X-pack
-yarn spec_to_console -g "/x-pack/plugin/src/test/resources/rest-api-spec/api/*" -d "x-pack/plugins/console_extensions/server/lib/spec_definitions/json/generated"
```
### Information used in Console that is not available in the REST spec
diff --git a/packages/kbn-spec-to-console/lib/convert/params.js b/packages/kbn-spec-to-console/lib/convert/params.js
index e5365b4d7311e4..1aa89be11c76d2 100644
--- a/packages/kbn-spec-to-console/lib/convert/params.js
+++ b/packages/kbn-spec-to-console/lib/convert/params.js
@@ -37,6 +37,7 @@ module.exports = (params) => {
case 'string':
case 'number':
case 'number|string':
+ case 'boolean|long':
result[param] = defaultValue || '';
break;
case 'list':
diff --git a/src/cli/serve/serve.js b/src/cli/serve/serve.js
index ad83965efde338..be949350f72297 100644
--- a/src/cli/serve/serve.js
+++ b/src/cli/serve/serve.js
@@ -68,12 +68,14 @@ function applyConfigOverrides(rawConfig, opts, extraCliOptions) {
delete extraCliOptions.env;
if (opts.dev) {
- if (!has('elasticsearch.username')) {
- set('elasticsearch.username', 'kibana_system');
- }
+ if (!has('elasticsearch.serviceAccountToken')) {
+ if (!has('elasticsearch.username')) {
+ set('elasticsearch.username', 'kibana_system');
+ }
- if (!has('elasticsearch.password')) {
- set('elasticsearch.password', 'changeme');
+ if (!has('elasticsearch.password')) {
+ set('elasticsearch.password', 'changeme');
+ }
}
if (opts.ssl) {
diff --git a/src/core/public/chrome/ui/header/header_help_menu.tsx b/src/core/public/chrome/ui/header/header_help_menu.tsx
index c6a09c1177a5e8..cbf89bba2ca443 100644
--- a/src/core/public/chrome/ui/header/header_help_menu.tsx
+++ b/src/core/public/chrome/ui/header/header_help_menu.tsx
@@ -211,7 +211,7 @@ export class HeaderHelpMenu extends Component {
return (
-
+
}>
-
- {ANALYZE_DATA}
-
-
-
-
-
+ {ANALYZE_MESSAGE}}>
+
- {i18n.translate('xpack.apm.addDataButtonLabel', {
- defaultMessage: 'Add data',
- })}
-
-
-
+ {ANALYZE_DATA}
+
+
+
+ {i18n.translate('xpack.apm.addDataButtonLabel', {
+ defaultMessage: 'Add data',
+ })}
+
+
);
}
diff --git a/x-pack/plugins/apm/public/components/app/Settings/agent_configurations/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/agent_configurations/index.tsx
index 1ca7f46a0b26fa..32c93e43175dfb 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/agent_configurations/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/agent_configurations/index.tsx
@@ -18,7 +18,6 @@ import { i18n } from '@kbn/i18n';
import { isEmpty } from 'lodash';
import React from 'react';
import { useLocation } from 'react-router-dom';
-import { useTrackPageview } from '../../../../../../observability/public';
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
import { useFetcher } from '../../../../hooks/use_fetcher';
import { createAgentConfigurationHref } from '../../../shared/Links/apm/agentConfigurationLinks';
@@ -34,9 +33,6 @@ export function AgentConfigurations() {
{ preservePreviousData: false, showToastOnError: false }
);
- useTrackPageview({ app: 'apm', path: 'agent_configuration' });
- useTrackPageview({ app: 'apm', path: 'agent_configuration', delay: 15000 });
-
const hasConfigurations = !isEmpty(data.configurations);
return (
diff --git a/x-pack/plugins/apm/public/components/app/Settings/schema/confirm_switch_modal.tsx b/x-pack/plugins/apm/public/components/app/Settings/schema/confirm_switch_modal.tsx
index 04817aaf84f3ef..5bb93c251777fb 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/schema/confirm_switch_modal.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/schema/confirm_switch_modal.tsx
@@ -15,6 +15,7 @@ import {
htmlIdGenerator,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
+import { useUiTracker } from '../../../../../../observability/public';
import { ElasticDocsLink } from '../../../shared/Links/ElasticDocsLink';
interface Props {
@@ -27,6 +28,7 @@ export function ConfirmSwitchModal({
onCancel,
unsupportedConfigs,
}: Props) {
+ const trackApmEvent = useUiTracker({ app: 'apm' });
const [isConfirmChecked, setIsConfirmChecked] = useState(false);
const hasUnsupportedConfigs = !!unsupportedConfigs.length;
return (
@@ -48,7 +50,12 @@ export function ConfirmSwitchModal({
}
)}
defaultFocusedButton="confirm"
- onConfirm={onConfirm}
+ onConfirm={() => {
+ trackApmEvent({
+ metric: 'confirm_data_stream_switch',
+ });
+ onConfirm();
+ }}
confirmButtonDisabled={!isConfirmChecked}
>
diff --git a/x-pack/plugins/apm/public/components/app/Settings/schema/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/schema/index.tsx
index 5a67ce28e9e1a2..0c95648a1cefc3 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/schema/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/schema/index.tsx
@@ -142,9 +142,7 @@ async function createCloudApmPackagePolicy(
) {
updateLocalStorage(FETCH_STATUS.LOADING);
try {
- const {
- cloud_apm_package_policy: cloudApmPackagePolicy,
- } = await callApmApi({
+ const { cloudApmPackagePolicy } = await callApmApi({
endpoint: 'POST /api/apm/fleet/cloud_apm_package_policy',
signal: null,
});
diff --git a/x-pack/plugins/apm/public/components/app/correlations/index.tsx b/x-pack/plugins/apm/public/components/app/correlations/index.tsx
index 2b32ece14e5cdd..9ad5088bb0bcf4 100644
--- a/x-pack/plugins/apm/public/components/app/correlations/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/correlations/index.tsx
@@ -104,16 +104,7 @@ export function Correlations() {
width: '20%',
});
}
- if (urlParams.transactionName) {
- properties.push({
- label: i18n.translate('xpack.apm.correlations.transactionLabel', {
- defaultMessage: 'Transaction',
- }),
- fieldName: TRANSACTION_NAME,
- val: urlParams.transactionName,
- width: '20%',
- });
- }
+
if (urlParams.environment) {
properties.push({
label: i18n.translate('xpack.apm.correlations.environmentLabel', {
@@ -125,6 +116,17 @@ export function Correlations() {
});
}
+ if (urlParams.transactionName) {
+ properties.push({
+ label: i18n.translate('xpack.apm.correlations.transactionLabel', {
+ defaultMessage: 'Transaction',
+ }),
+ fieldName: TRANSACTION_NAME,
+ val: urlParams.transactionName,
+ width: '20%',
+ });
+ }
+
return properties;
}, [serviceName, urlParams.environment, urlParams.transactionName]);
diff --git a/x-pack/plugins/apm/public/components/app/correlations/ml_latency_correlations.tsx b/x-pack/plugins/apm/public/components/app/correlations/ml_latency_correlations.tsx
index f9536353747ee9..03fab3e788639a 100644
--- a/x-pack/plugins/apm/public/components/app/correlations/ml_latency_correlations.tsx
+++ b/x-pack/plugins/apm/public/components/app/correlations/ml_latency_correlations.tsx
@@ -36,6 +36,7 @@ import { useCorrelations } from './use_correlations';
import { push } from '../../shared/Links/url_helpers';
import { useUiTracker } from '../../../../../observability/public';
import { asPreciseDecimal } from '../../../../common/utils/formatters';
+import { LatencyCorrelationsHelpPopover } from './ml_latency_correlations_help_popover';
const DEFAULT_PERCENTILE_THRESHOLD = 95;
const isErrorMessage = (arg: unknown): arg is Error => {
@@ -60,17 +61,16 @@ export function MlLatencyCorrelations({ onClose }: Props) {
} = useApmPluginContext();
const { serviceName } = useParams<{ serviceName: string }>();
- const { urlParams } = useUrlParams();
-
- const fetchOptions = useMemo(
- () => ({
- ...{
- serviceName,
- ...urlParams,
- },
- }),
- [serviceName, urlParams]
- );
+ const {
+ urlParams: {
+ environment,
+ kuery,
+ transactionName,
+ transactionType,
+ start,
+ end,
+ },
+ } = useUrlParams();
const {
error,
@@ -84,7 +84,15 @@ export function MlLatencyCorrelations({ onClose }: Props) {
} = useCorrelations({
index: 'apm-*',
...{
- ...fetchOptions,
+ ...{
+ environment,
+ kuery,
+ serviceName,
+ transactionName,
+ transactionType,
+ start,
+ end,
+ },
percentileThreshold: DEFAULT_PERCENTILE_THRESHOLD,
},
});
@@ -151,7 +159,7 @@ export function MlLatencyCorrelations({ onClose }: Props) {
'xpack.apm.correlations.latencyCorrelations.correlationsTable.correlationColumnDescription',
{
defaultMessage:
- 'The impact of a field on the latency of the service, ranging from 0 to 1.',
+ 'The correlation score [0-1] of an attribute; the greater the score, the more an attribute increases latency.',
}
)}
>
@@ -263,20 +271,6 @@ export function MlLatencyCorrelations({ onClose }: Props) {
return (
<>
-
-
- {i18n.translate(
- 'xpack.apm.correlations.latencyCorrelations.description',
- {
- defaultMessage:
- 'What is slowing down my service? Correlations will help discover a slower performance in a particular cohort of your data.',
- }
- )}
-
-
-
-
-
{!isRunning && (
@@ -320,6 +314,9 @@ export function MlLatencyCorrelations({ onClose }: Props) {
+
+
+
@@ -332,8 +329,7 @@ export function MlLatencyCorrelations({ onClose }: Props) {
{
defaultMessage: 'Latency distribution for {name}',
values: {
- name:
- fetchOptions.transactionName ?? fetchOptions.serviceName,
+ name: transactionName ?? serviceName,
},
}
)}
diff --git a/x-pack/plugins/apm/public/components/app/correlations/ml_latency_correlations_help_popover.tsx b/x-pack/plugins/apm/public/components/app/correlations/ml_latency_correlations_help_popover.tsx
new file mode 100644
index 00000000000000..1f9a41c1139cdc
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/correlations/ml_latency_correlations_help_popover.tsx
@@ -0,0 +1,64 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { useState } from 'react';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { HelpPopover, HelpPopoverButton } from '../help_popover/help_popover';
+
+export function LatencyCorrelationsHelpPopover() {
+ const [isPopoverOpen, setIsPopoverOpen] = useState(false);
+
+ return (
+ {
+ setIsPopoverOpen(!isPopoverOpen);
+ }}
+ />
+ }
+ closePopover={() => setIsPopoverOpen(false)}
+ isOpen={isPopoverOpen}
+ title={i18n.translate('xpack.apm.correlations.latencyPopoverTitle', {
+ defaultMessage: 'Latency correlations',
+ })}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/x-pack/plugins/apm/public/components/app/correlations/use_correlations.ts b/x-pack/plugins/apm/public/components/app/correlations/use_correlations.ts
index 8c874571d23dba..2baeb63fa4a239 100644
--- a/x-pack/plugins/apm/public/components/app/correlations/use_correlations.ts
+++ b/x-pack/plugins/apm/public/components/app/correlations/use_correlations.ts
@@ -36,6 +36,7 @@ interface RawResponse {
took: number;
values: SearchServiceValue[];
overallHistogram: HistogramItem[];
+ log: string[];
}
export const useCorrelations = (params: CorrelationsOptions) => {
diff --git a/x-pack/plugins/apm/public/components/app/error_group_details/index.tsx b/x-pack/plugins/apm/public/components/app/error_group_details/index.tsx
index 344393d42506fe..225186cab12c82 100644
--- a/x-pack/plugins/apm/public/components/app/error_group_details/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/error_group_details/index.tsx
@@ -17,7 +17,6 @@ import {
import { i18n } from '@kbn/i18n';
import React from 'react';
import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common';
-import { useTrackPageview } from '../../../../../observability/public';
import { NOT_AVAILABLE_LABEL } from '../../../../common/i18n';
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
import { useErrorGroupDistributionFetcher } from '../../../hooks/use_error_group_distribution_fetcher';
@@ -128,9 +127,6 @@ export function ErrorGroupDetails({
groupId,
});
- useTrackPageview({ app: 'apm', path: 'error_group_details' });
- useTrackPageview({ app: 'apm', path: 'error_group_details', delay: 15000 });
-
if (!errorGroupData || !errorDistributionData) {
return ;
}
diff --git a/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx b/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx
index 4c622758e6c8bf..8d8d0cb9c107cb 100644
--- a/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx
@@ -14,7 +14,6 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
-import { useTrackPageview } from '../../../../../observability/public';
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
import { useErrorGroupDistributionFetcher } from '../../../hooks/use_error_group_distribution_fetcher';
import { useFetcher } from '../../../hooks/use_fetcher';
@@ -60,12 +59,6 @@ export function ErrorGroupOverview({ serviceName }: ErrorGroupOverviewProps) {
[environment, kuery, serviceName, start, end, sortField, sortDirection]
);
- useTrackPageview({
- app: 'apm',
- path: 'error_group_overview',
- });
- useTrackPageview({ app: 'apm', path: 'error_group_overview', delay: 15000 });
-
if (!errorDistributionData || !errorGroupListData) {
return null;
}
diff --git a/x-pack/plugins/apm/public/components/app/help_popover/help_popover.tsx b/x-pack/plugins/apm/public/components/app/help_popover/help_popover.tsx
new file mode 100644
index 00000000000000..def310f1d8140d
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/help_popover/help_popover.tsx
@@ -0,0 +1,72 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { ReactNode } from 'react';
+import { i18n } from '@kbn/i18n';
+import {
+ EuiButtonIcon,
+ EuiLinkButtonProps,
+ EuiPopover,
+ EuiPopoverProps,
+ EuiPopoverTitle,
+ EuiText,
+} from '@elastic/eui';
+import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common';
+
+const PopoverContent = euiStyled(EuiText)`
+ max-width: 480px;
+ max-height: 40vh;
+`;
+
+export function HelpPopoverButton({
+ onClick,
+}: {
+ onClick: EuiLinkButtonProps['onClick'];
+}) {
+ return (
+
+ );
+}
+
+export function HelpPopover({
+ anchorPosition,
+ button,
+ children,
+ closePopover,
+ isOpen,
+ title,
+}: {
+ anchorPosition?: EuiPopoverProps['anchorPosition'];
+ button: EuiPopoverProps['button'];
+ children: ReactNode;
+ closePopover: EuiPopoverProps['closePopover'];
+ isOpen: EuiPopoverProps['isOpen'];
+ title?: string;
+}) {
+ return (
+
+ {title && {title} }
+
+ {children}
+
+ );
+}
diff --git a/x-pack/plugins/console_extensions/server/config.ts b/x-pack/plugins/apm/public/components/app/help_popover/index.tsx
similarity index 56%
rename from x-pack/plugins/console_extensions/server/config.ts
rename to x-pack/plugins/apm/public/components/app/help_popover/index.tsx
index 15b06bf93ffbed..b1d53722c7bb5e 100644
--- a/x-pack/plugins/console_extensions/server/config.ts
+++ b/x-pack/plugins/apm/public/components/app/help_popover/index.tsx
@@ -5,10 +5,4 @@
* 2.0.
*/
-import { schema, TypeOf } from '@kbn/config-schema';
-
-export type ConfigType = TypeOf;
-
-export const config = schema.object({
- enabled: schema.boolean({ defaultValue: true }),
-});
+export { HelpPopoverButton, HelpPopover } from './help_popover';
diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx
index cac94885511c10..ef0a5f2df0434a 100644
--- a/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx
@@ -9,7 +9,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useEffect } from 'react';
import { toMountPoint } from '../../../../../../../src/plugins/kibana_react/public';
-import { useTrackPageview } from '../../../../../observability/public';
import { useAnomalyDetectionJobsContext } from '../../../context/anomaly_detection_jobs/use_anomaly_detection_jobs_context';
import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
@@ -92,13 +91,6 @@ export function ServiceInventory() {
const { core } = useApmPluginContext();
const { servicesData, servicesStatus } = useServicesFetcher();
- // The page is called "service inventory" to avoid confusion with the
- // "service overview", but this is tracked in some dashboards because it's the
- // initial landing page for APM, so it stays as "services_overview" (plural.)
- // for backward compatibility.
- useTrackPageview({ app: 'apm', path: 'services_overview' });
- useTrackPageview({ app: 'apm', path: 'services_overview', delay: 15000 });
-
const {
anomalyDetectionJobsData,
anomalyDetectionJobsStatus,
diff --git a/x-pack/plugins/apm/public/components/app/service_map/index.tsx b/x-pack/plugins/apm/public/components/app/service_map/index.tsx
index 582eafe7553af7..22adb10512d7ae 100644
--- a/x-pack/plugins/apm/public/components/app/service_map/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_map/index.tsx
@@ -13,7 +13,6 @@ import {
} from '@elastic/eui';
import React, { PropsWithChildren, ReactNode } from 'react';
import { isActivePlatinumLicense } from '../../../../common/license_check';
-import { useTrackPageview } from '../../../../../observability/public';
import {
invalidLicenseMessage,
SERVICE_MAP_TIMEOUT_ERROR,
@@ -106,9 +105,6 @@ export function ServiceMap({
const PADDING_BOTTOM = 24;
const heightWithPadding = height - PADDING_BOTTOM;
- useTrackPageview({ app: 'apm', path: 'service_map' });
- useTrackPageview({ app: 'apm', path: 'service_map', delay: 15000 });
-
if (!license) {
return null;
}
diff --git a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx
index fce543b05c6c3b..374b2d59ea3472 100644
--- a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx
@@ -7,7 +7,6 @@
import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
import React from 'react';
-import { useTrackPageview } from '../../../../../observability/public';
import { isRumAgentName, isIosAgentName } from '../../../../common/agent_name';
import { AnnotationsContextProvider } from '../../../context/annotations/annotations_context';
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
@@ -35,9 +34,6 @@ interface ServiceOverviewProps {
export function ServiceOverview({ serviceName }: ServiceOverviewProps) {
const { agentName } = useApmServiceContext();
- useTrackPageview({ app: 'apm', path: 'service_overview' });
- useTrackPageview({ app: 'apm', path: 'service_overview', delay: 15000 });
-
// The default EuiFlexGroup breaks at 768, but we want to break at 992, so we
// observe the window width and set the flex directions of rows accordingly
const { isMedium } = useBreakPoints();
diff --git a/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx b/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx
index d280b36a603bab..ccb5fea72432c0 100644
--- a/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx
@@ -6,7 +6,6 @@
*/
import React from 'react';
-import { useTrackPageview } from '../../../../../observability/public';
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher';
import { APIReturnType } from '../../../services/rest/createCallApmApi';
@@ -43,9 +42,6 @@ export function TraceOverview() {
[environment, kuery, start, end]
);
- useTrackPageview({ app: 'apm', path: 'traces_overview' });
- useTrackPageview({ app: 'apm', path: 'traces_overview', delay: 15000 });
-
return (
<>
diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx
index 1e13e224a511a4..40f50e768e76e0 100644
--- a/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx
@@ -9,7 +9,6 @@ import { EuiHorizontalRule, EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui';
import { flatten, isEmpty } from 'lodash';
import React from 'react';
import { useHistory } from 'react-router-dom';
-import { useTrackPageview } from '../../../../../observability/public';
import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context';
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
import { FETCH_STATUS } from '../../../hooks/use_fetcher';
@@ -41,9 +40,6 @@ export function TransactionDetails() {
} = useWaterfallFetcher();
const { transactionName } = urlParams;
- useTrackPageview({ app: 'apm', path: 'transaction_details' });
- useTrackPageview({ app: 'apm', path: 'transaction_details', delay: 15000 });
-
const selectedSample = flatten(
distributionData.buckets.map((bucket) => bucket.samples)
).find(
diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx
index 041c12822357c5..2435e5fc5a1f6e 100644
--- a/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx
@@ -17,7 +17,6 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { Location } from 'history';
import React from 'react';
import { useLocation } from 'react-router-dom';
-import { useTrackPageview } from '../../../../../observability/public';
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
import { IUrlParams } from '../../../context/url_params_context/types';
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
@@ -62,8 +61,6 @@ export function TransactionOverview({ serviceName }: TransactionOverviewProps) {
// redirect to first transaction type
useRedirect(getRedirectLocation({ location, transactionType, urlParams }));
- useTrackPageview({ app: 'apm', path: 'transaction_overview' });
- useTrackPageview({ app: 'apm', path: 'transaction_overview', delay: 15000 });
const {
transactionListData,
transactionListStatus,
diff --git a/x-pack/plugins/apm/public/components/routing/app_root.tsx b/x-pack/plugins/apm/public/components/routing/app_root.tsx
index 8fc59a01eeca04..a924c1f31cbefb 100644
--- a/x-pack/plugins/apm/public/components/routing/app_root.tsx
+++ b/x-pack/plugins/apm/public/components/routing/app_root.tsx
@@ -9,7 +9,7 @@ import { ApmRoute } from '@elastic/apm-rum-react';
import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
import React from 'react';
-import { Route, Router, Switch } from 'react-router-dom';
+import { Route, RouteComponentProps, Router, Switch } from 'react-router-dom';
import { DefaultTheme, ThemeProvider } from 'styled-components';
import { APP_WRAPPER_CLASS } from '../../../../../../src/core/public';
import {
@@ -17,20 +17,21 @@ import {
RedirectAppLinks,
useUiSetting$,
} from '../../../../../../src/plugins/kibana_react/public';
+import { HeaderMenuPortal } from '../../../../observability/public';
import { ScrollToTopOnPathChange } from '../../components/app/Main/ScrollToTopOnPathChange';
+import { AnomalyDetectionJobsContextProvider } from '../../context/anomaly_detection_jobs/anomaly_detection_jobs_context';
import {
ApmPluginContext,
ApmPluginContextValue,
} from '../../context/apm_plugin/apm_plugin_context';
+import { useApmPluginContext } from '../../context/apm_plugin/use_apm_plugin_context';
import { LicenseProvider } from '../../context/license/license_context';
import { UrlParamsProvider } from '../../context/url_params_context/url_params_context';
import { useApmBreadcrumbs } from '../../hooks/use_apm_breadcrumbs';
import { ApmPluginStartDeps } from '../../plugin';
-import { HeaderMenuPortal } from '../../../../observability/public';
import { ApmHeaderActionMenu } from '../shared/apm_header_action_menu';
-import { useApmPluginContext } from '../../context/apm_plugin/use_apm_plugin_context';
-import { AnomalyDetectionJobsContextProvider } from '../../context/anomaly_detection_jobs/anomaly_detection_jobs_context';
import { apmRouteConfig } from './apm_route_config';
+import { TelemetryWrapper } from './telemetry_wrapper';
export function ApmAppRoot({
apmPluginContextValue,
@@ -62,9 +63,18 @@ export function ApmAppRoot({
- {apmRouteConfig.map((route, i) => (
-
- ))}
+ {apmRouteConfig.map((route, i) => {
+ const { component, render, ...rest } = route;
+ return (
+ {
+ return TelemetryWrapper({ route, props });
+ }}
+ />
+ );
+ })}
diff --git a/x-pack/plugins/apm/public/components/routing/telemetry_wrapper.tsx b/x-pack/plugins/apm/public/components/routing/telemetry_wrapper.tsx
new file mode 100644
index 00000000000000..fc3fc6a338d189
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/routing/telemetry_wrapper.tsx
@@ -0,0 +1,33 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import React from 'react';
+import { RouteComponentProps } from 'react-router-dom';
+import { useTrackPageview } from '../../../../observability/public';
+import { APMRouteDefinition } from '../../application/routes';
+import { redirectTo } from './redirect_to';
+
+export function TelemetryWrapper({
+ route,
+ props,
+}: {
+ route: APMRouteDefinition;
+ props: RouteComponentProps;
+}) {
+ const { component, render, path } = route;
+ const pathAsString = path as string;
+
+ useTrackPageview({ app: 'apm', path: pathAsString });
+ useTrackPageview({ app: 'apm', path: pathAsString, delay: 15000 });
+
+ if (component) {
+ return React.createElement(component, props);
+ }
+ if (render) {
+ return <>{render(props)}>;
+ }
+ return <>{redirectTo('/')}>;
+}
diff --git a/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/alerting_popover_flyout.tsx b/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/alerting_popover_flyout.tsx
index ca73f6ddd05b34..4abd36a2773119 100644
--- a/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/alerting_popover_flyout.tsx
+++ b/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/alerting_popover_flyout.tsx
@@ -66,7 +66,6 @@ export function AlertingPopoverAndFlyout({
const button = (
-
+
{i18n.translate('xpack.apm.settingsLinkLabel', {
defaultMessage: 'Settings',
})}
diff --git a/x-pack/plugins/apm/server/lib/alerts/test_utils/index.ts b/x-pack/plugins/apm/server/lib/alerts/test_utils/index.ts
index 1366503ea14284..679f33707b5b53 100644
--- a/x-pack/plugins/apm/server/lib/alerts/test_utils/index.ts
+++ b/x-pack/plugins/apm/server/lib/alerts/test_utils/index.ts
@@ -70,8 +70,14 @@ export const createRuleTypeMocks = () => {
executor: async ({ params }: { params: Record }) => {
return alertExecutor({
services,
- rule: { consumer: APM_SERVER_FEATURE_ID },
params,
+ rule: {
+ consumer: APM_SERVER_FEATURE_ID,
+ name: 'name',
+ producer: 'producer',
+ ruleTypeId: 'ruleTypeId',
+ ruleTypeName: 'ruleTypeName',
+ },
startedAt: new Date(),
});
},
diff --git a/x-pack/plugins/apm/server/lib/fleet/sync_agent_configs_to_apm_package_policies.ts b/x-pack/plugins/apm/server/lib/fleet/sync_agent_configs_to_apm_package_policies.ts
index 4294c5b82cd630..1365ddc28ddb23 100644
--- a/x-pack/plugins/apm/server/lib/fleet/sync_agent_configs_to_apm_package_policies.ts
+++ b/x-pack/plugins/apm/server/lib/fleet/sync_agent_configs_to_apm_package_policies.ts
@@ -10,6 +10,7 @@ import {
CoreStart,
SavedObjectsClientContract,
} from 'kibana/server';
+import { TelemetryUsageCounter } from '../../routes/typings';
import { APMPluginStartDependencies } from '../../types';
import { getInternalSavedObjectsClient } from '../helpers/get_internal_saved_objects_client';
import { Setup } from '../helpers/setup_request';
@@ -21,11 +22,19 @@ export async function syncAgentConfigsToApmPackagePolicies({
core,
fleetPluginStart,
setup,
+ telemetryUsageCounter,
}: {
core: { setup: CoreSetup; start: () => Promise };
fleetPluginStart: NonNullable;
setup: Setup;
+ telemetryUsageCounter?: TelemetryUsageCounter;
}) {
+ if (telemetryUsageCounter) {
+ telemetryUsageCounter.incrementCounter({
+ counterName: 'sync_agent_config_to_apm_package_policies',
+ counterType: 'success',
+ });
+ }
const coreStart = await core.start();
const esClient = coreStart.elasticsearch.client.asInternalUser;
const [
diff --git a/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.test.ts b/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.test.ts
index a5340c1220b443..ef869a0ed6cfa5 100644
--- a/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.test.ts
+++ b/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.test.ts
@@ -13,6 +13,11 @@ import { APMConfig } from '../..';
function getMockSavedObjectsClient() {
return ({
+ get: jest.fn(() => ({
+ attributes: {
+ title: 'apm-*',
+ },
+ })),
create: jest.fn(),
} as unknown) as InternalSavedObjectsClient;
}
@@ -22,14 +27,12 @@ describe('createStaticIndexPattern', () => {
const setup = {} as Setup;
const savedObjectsClient = getMockSavedObjectsClient();
- await createStaticIndexPattern(
+ await createStaticIndexPattern({
setup,
- {
- 'xpack.apm.autocreateApmIndexPattern': false,
- } as APMConfig,
+ config: { 'xpack.apm.autocreateApmIndexPattern': false } as APMConfig,
savedObjectsClient,
- 'default'
- );
+ spaceId: 'default',
+ });
expect(savedObjectsClient.create).not.toHaveBeenCalled();
});
@@ -43,14 +46,12 @@ describe('createStaticIndexPattern', () => {
const savedObjectsClient = getMockSavedObjectsClient();
- await createStaticIndexPattern(
+ await createStaticIndexPattern({
setup,
- {
- 'xpack.apm.autocreateApmIndexPattern': true,
- } as APMConfig,
+ config: { 'xpack.apm.autocreateApmIndexPattern': true } as APMConfig,
savedObjectsClient,
- 'default'
- );
+ spaceId: 'default',
+ });
expect(savedObjectsClient.create).not.toHaveBeenCalled();
});
@@ -64,15 +65,73 @@ describe('createStaticIndexPattern', () => {
const savedObjectsClient = getMockSavedObjectsClient();
- await createStaticIndexPattern(
+ await createStaticIndexPattern({
setup,
- {
+ config: { 'xpack.apm.autocreateApmIndexPattern': true } as APMConfig,
+ savedObjectsClient,
+ spaceId: 'default',
+ });
+
+ expect(savedObjectsClient.create).toHaveBeenCalled();
+ });
+
+ it(`should upgrade an index pattern if 'apm_oss.indexPattern' does not match title`, async () => {
+ const setup = {} as Setup;
+
+ // does have APM data
+ jest
+ .spyOn(HistoricalAgentData, 'hasHistoricalAgentData')
+ .mockResolvedValue(true);
+
+ const savedObjectsClient = getMockSavedObjectsClient();
+ const apmIndexPatternTitle = 'traces-apm*,logs-apm*,metrics-apm*,apm-*';
+
+ await createStaticIndexPattern({
+ setup,
+ config: {
'xpack.apm.autocreateApmIndexPattern': true,
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ 'apm_oss.indexPattern': apmIndexPatternTitle,
} as APMConfig,
savedObjectsClient,
- 'default'
+ spaceId: 'default',
+ });
+
+ expect(savedObjectsClient.get).toHaveBeenCalled();
+ expect(savedObjectsClient.create).toHaveBeenCalled();
+ // @ts-ignore
+ expect(savedObjectsClient.create.mock.calls[0][1].title).toBe(
+ apmIndexPatternTitle
);
+ // @ts-ignore
+ expect(savedObjectsClient.create.mock.calls[0][2].overwrite).toBe(true);
+ });
+
+ it(`should not upgrade an index pattern if 'apm_oss.indexPattern' already match existing title`, async () => {
+ const setup = {} as Setup;
+
+ // does have APM data
+ jest
+ .spyOn(HistoricalAgentData, 'hasHistoricalAgentData')
+ .mockResolvedValue(true);
+
+ const savedObjectsClient = getMockSavedObjectsClient();
+ const apmIndexPatternTitle = 'apm-*';
+
+ await createStaticIndexPattern({
+ setup,
+ config: {
+ 'xpack.apm.autocreateApmIndexPattern': true,
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ 'apm_oss.indexPattern': apmIndexPatternTitle,
+ } as APMConfig,
+ savedObjectsClient,
+ spaceId: 'default',
+ });
+ expect(savedObjectsClient.get).toHaveBeenCalled();
expect(savedObjectsClient.create).toHaveBeenCalled();
+ // @ts-ignore
+ expect(savedObjectsClient.create.mock.calls[0][2].overwrite).toBe(false);
});
});
diff --git a/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts b/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts
index a2944d6241d2d9..5dbee59b4ce866 100644
--- a/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts
+++ b/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts
@@ -15,13 +15,23 @@ import { InternalSavedObjectsClient } from '../helpers/get_internal_saved_object
import { withApmSpan } from '../../utils/with_apm_span';
import { getApmIndexPatternTitle } from './get_apm_index_pattern_title';
-export async function createStaticIndexPattern(
- setup: Setup,
- config: APMRouteHandlerResources['config'],
- savedObjectsClient: InternalSavedObjectsClient,
- spaceId: string | undefined,
- overwrite = false
-): Promise {
+type ApmIndexPatternAttributes = typeof apmIndexPattern.attributes & {
+ title: string;
+};
+
+export async function createStaticIndexPattern({
+ setup,
+ config,
+ savedObjectsClient,
+ spaceId,
+ overwrite = false,
+}: {
+ setup: Setup;
+ config: APMRouteHandlerResources['config'];
+ savedObjectsClient: InternalSavedObjectsClient;
+ spaceId?: string;
+ overwrite?: boolean;
+}): Promise {
return withApmSpan('create_static_index_pattern', async () => {
// don't autocreate APM index pattern if it's been disabled via the config
if (!config['xpack.apm.autocreateApmIndexPattern']) {
@@ -35,8 +45,31 @@ export async function createStaticIndexPattern(
return false;
}
+ const apmIndexPatternTitle = getApmIndexPatternTitle(config);
+
+ if (!overwrite) {
+ try {
+ const {
+ attributes: { title: existingApmIndexPatternTitle },
+ }: {
+ attributes: ApmIndexPatternAttributes;
+ } = await savedObjectsClient.get(
+ 'index-pattern',
+ APM_STATIC_INDEX_PATTERN_ID
+ );
+ // if the existing index pattern does not matches the new one, force an update
+ if (existingApmIndexPatternTitle !== apmIndexPatternTitle) {
+ overwrite = true;
+ }
+ } catch (e) {
+ // if the index pattern (saved object) is not found, then we can continue with creation
+ if (!SavedObjectsErrorHelpers.isNotFoundError(e)) {
+ throw e;
+ }
+ }
+ }
+
try {
- const apmIndexPatternTitle = getApmIndexPatternTitle(config);
await withApmSpan('create_index_pattern_saved_object', () =>
savedObjectsClient.create(
'index-pattern',
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/async_search_service.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/async_search_service.ts
index 7a511fc60fd064..155cb1f4615bdc 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/async_search_service.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/async_search_service.ts
@@ -11,7 +11,7 @@ import { fetchTransactionDurationFieldCandidates } from './query_field_candidate
import { fetchTransactionDurationFieldValuePairs } from './query_field_value_pairs';
import { fetchTransactionDurationPercentiles } from './query_percentiles';
import { fetchTransactionDurationCorrelation } from './query_correlation';
-import { fetchTransactionDurationHistogramRangesteps } from './query_histogram_rangesteps';
+import { fetchTransactionDurationHistogramRangeSteps } from './query_histogram_range_steps';
import { fetchTransactionDurationRanges, HistogramItem } from './query_ranges';
import type {
AsyncSearchProviderProgress,
@@ -24,6 +24,8 @@ import { fetchTransactionDurationFractions } from './query_fractions';
const CORRELATION_THRESHOLD = 0.3;
const KS_TEST_THRESHOLD = 0.1;
+const currentTimeAsString = () => new Date().toISOString();
+
export const asyncSearchServiceProvider = (
esClient: ElasticsearchClient,
params: SearchServiceParams
@@ -31,6 +33,9 @@ export const asyncSearchServiceProvider = (
let isCancelled = false;
let isRunning = true;
let error: Error;
+ const log: string[] = [];
+ const logMessage = (message: string) =>
+ log.push(`${currentTimeAsString()}: ${message}`);
const progress: AsyncSearchProviderProgress = {
started: Date.now(),
@@ -53,13 +58,17 @@ export const asyncSearchServiceProvider = (
let percentileThresholdValue: number;
const cancel = () => {
+ logMessage(`Service cancelled.`);
isCancelled = true;
};
const fetchCorrelations = async () => {
try {
// 95th percentile to be displayed as a marker in the log log chart
- const percentileThreshold = await fetchTransactionDurationPercentiles(
+ const {
+ totalDocs,
+ percentiles: percentileThreshold,
+ } = await fetchTransactionDurationPercentiles(
esClient,
params,
params.percentileThreshold ? [params.percentileThreshold] : undefined
@@ -67,12 +76,32 @@ export const asyncSearchServiceProvider = (
percentileThresholdValue =
percentileThreshold[`${params.percentileThreshold}.0`];
- const histogramRangeSteps = await fetchTransactionDurationHistogramRangesteps(
+ logMessage(
+ `Fetched ${params.percentileThreshold}th percentile value of ${percentileThresholdValue} based on ${totalDocs} documents.`
+ );
+
+ // finish early if we weren't able to identify the percentileThresholdValue.
+ if (percentileThresholdValue === undefined) {
+ logMessage(
+ `Abort service since percentileThresholdValue could not be determined.`
+ );
+ progress.loadedHistogramStepsize = 1;
+ progress.loadedOverallHistogram = 1;
+ progress.loadedFieldCanditates = 1;
+ progress.loadedFieldValuePairs = 1;
+ progress.loadedHistograms = 1;
+ isRunning = false;
+ return;
+ }
+
+ const histogramRangeSteps = await fetchTransactionDurationHistogramRangeSteps(
esClient,
params
);
progress.loadedHistogramStepsize = 1;
+ logMessage(`Loaded histogram range steps.`);
+
if (isCancelled) {
isRunning = false;
return;
@@ -86,6 +115,8 @@ export const asyncSearchServiceProvider = (
progress.loadedOverallHistogram = 1;
overallHistogram = overallLogHistogramChartData;
+ logMessage(`Loaded overall histogram chart data.`);
+
if (isCancelled) {
isRunning = false;
return;
@@ -93,13 +124,13 @@ export const asyncSearchServiceProvider = (
// Create an array of ranges [2, 4, 6, ..., 98]
const percents = Array.from(range(2, 100, 2));
- const percentilesRecords = await fetchTransactionDurationPercentiles(
- esClient,
- params,
- percents
- );
+ const {
+ percentiles: percentilesRecords,
+ } = await fetchTransactionDurationPercentiles(esClient, params, percents);
const percentiles = Object.values(percentilesRecords);
+ logMessage(`Loaded percentiles.`);
+
if (isCancelled) {
isRunning = false;
return;
@@ -110,6 +141,8 @@ export const asyncSearchServiceProvider = (
params
);
+ logMessage(`Identified ${fieldCandidates.length} fieldCandidates.`);
+
progress.loadedFieldCanditates = 1;
const fieldValuePairs = await fetchTransactionDurationFieldValuePairs(
@@ -119,6 +152,8 @@ export const asyncSearchServiceProvider = (
progress
);
+ logMessage(`Identified ${fieldValuePairs.length} fieldValuePairs.`);
+
if (isCancelled) {
isRunning = false;
return;
@@ -133,6 +168,8 @@ export const asyncSearchServiceProvider = (
totalDocCount,
} = await fetchTransactionDurationFractions(esClient, params, ranges);
+ logMessage(`Loaded fractions and totalDocCount of ${totalDocCount}.`);
+
async function* fetchTransactionDurationHistograms() {
for (const item of shuffle(fieldValuePairs)) {
if (item === undefined || isCancelled) {
@@ -185,7 +222,11 @@ export const asyncSearchServiceProvider = (
yield undefined;
}
} catch (e) {
- error = e;
+ // don't fail the whole process for individual correlation queries, just add the error to the internal log.
+ logMessage(
+ `Failed to fetch correlation/kstest for '${item.field}/${item.value}'`
+ );
+ yield undefined;
}
}
}
@@ -199,10 +240,14 @@ export const asyncSearchServiceProvider = (
progress.loadedHistograms = loadedHistograms / fieldValuePairs.length;
}
- isRunning = false;
+ logMessage(
+ `Identified ${values.length} significant correlations out of ${fieldValuePairs.length} field/value pairs.`
+ );
} catch (e) {
error = e;
}
+
+ isRunning = false;
};
fetchCorrelations();
@@ -212,6 +257,7 @@ export const asyncSearchServiceProvider = (
return {
error,
+ log,
isRunning,
loaded: Math.round(progress.getOverallProgress() * 100),
overallHistogram,
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/get_query_with_params.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/get_query_with_params.test.ts
index 12e897ab3eec92..016355b3a64159 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/get_query_with_params.test.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/get_query_with_params.test.ts
@@ -10,10 +10,23 @@ import { getQueryWithParams } from './get_query_with_params';
describe('correlations', () => {
describe('getQueryWithParams', () => {
it('returns the most basic query filtering on processor.event=transaction', () => {
- const query = getQueryWithParams({ params: { index: 'apm-*' } });
+ const query = getQueryWithParams({
+ params: { index: 'apm-*', start: '2020', end: '2021' },
+ });
expect(query).toEqual({
bool: {
- filter: [{ term: { 'processor.event': 'transaction' } }],
+ filter: [
+ { term: { 'processor.event': 'transaction' } },
+ {
+ range: {
+ '@timestamp': {
+ format: 'epoch_millis',
+ gte: 1577836800000,
+ lte: 1609459200000,
+ },
+ },
+ },
+ ],
},
});
});
@@ -24,8 +37,8 @@ describe('correlations', () => {
index: 'apm-*',
serviceName: 'actualServiceName',
transactionName: 'actualTransactionName',
- start: '01-01-2021',
- end: '31-01-2021',
+ start: '2020',
+ end: '2021',
environment: 'dev',
percentileThresholdValue: 75,
},
@@ -33,22 +46,17 @@ describe('correlations', () => {
expect(query).toEqual({
bool: {
filter: [
- { term: { 'processor.event': 'transaction' } },
- {
- term: {
- 'service.name': 'actualServiceName',
- },
- },
{
term: {
- 'transaction.name': 'actualTransactionName',
+ 'processor.event': 'transaction',
},
},
{
range: {
'@timestamp': {
- gte: '01-01-2021',
- lte: '31-01-2021',
+ format: 'epoch_millis',
+ gte: 1577836800000,
+ lte: 1609459200000,
},
},
},
@@ -57,6 +65,16 @@ describe('correlations', () => {
'service.environment': 'dev',
},
},
+ {
+ term: {
+ 'service.name': 'actualServiceName',
+ },
+ },
+ {
+ term: {
+ 'transaction.name': 'actualTransactionName',
+ },
+ },
{
range: {
'transaction.duration.us': {
@@ -71,7 +89,7 @@ describe('correlations', () => {
it('returns a query considering a custom field/value pair', () => {
const query = getQueryWithParams({
- params: { index: 'apm-*' },
+ params: { index: 'apm-*', start: '2020', end: '2021' },
fieldName: 'actualFieldName',
fieldValue: 'actualFieldValue',
});
@@ -79,6 +97,15 @@ describe('correlations', () => {
bool: {
filter: [
{ term: { 'processor.event': 'transaction' } },
+ {
+ range: {
+ '@timestamp': {
+ format: 'epoch_millis',
+ gte: 1577836800000,
+ lte: 1609459200000,
+ },
+ },
+ },
{
term: {
actualFieldName: 'actualFieldValue',
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/get_query_with_params.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/get_query_with_params.ts
index 08ba4b23fec35b..e0ddfc1b053b5f 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/get_query_with_params.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/get_query_with_params.ts
@@ -5,16 +5,19 @@
* 2.0.
*/
+import { pipe } from 'fp-ts/lib/pipeable';
+import { getOrElse } from 'fp-ts/lib/Either';
+import { failure } from 'io-ts/lib/PathReporter';
+import * as t from 'io-ts';
+
import type { estypes } from '@elastic/elasticsearch';
-import {
- PROCESSOR_EVENT,
- SERVICE_NAME,
- TRANSACTION_DURATION,
- TRANSACTION_NAME,
-} from '../../../../common/elasticsearch_fieldnames';
+import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames';
import type { SearchServiceParams } from '../../../../common/search_strategies/correlations/types';
-import { environmentQuery as getEnvironmentQuery } from '../../../utils/queries';
-import { ProcessorEvent } from '../../../../common/processor_event';
+import { rangeRt } from '../../../routes/default_api_types';
+
+import { Setup, SetupTimeRange } from '../../helpers/setup_request';
+
+import { getCorrelationsFilters } from '../../correlations/get_filters';
const getPercentileThresholdValueQuery = (
percentileThresholdValue: number | undefined
@@ -39,26 +42,6 @@ export const getTermsQuery = (
return fieldName && fieldValue ? [{ term: { [fieldName]: fieldValue } }] : [];
};
-const getRangeQuery = (
- start?: string,
- end?: string
-): estypes.QueryDslQueryContainer[] => {
- if (start === undefined && end === undefined) {
- return [];
- }
-
- return [
- {
- range: {
- '@timestamp': {
- ...(start !== undefined ? { gte: start } : {}),
- ...(end !== undefined ? { lte: end } : {}),
- },
- },
- },
- ];
-};
-
interface QueryParams {
params: SearchServiceParams;
fieldName?: string;
@@ -71,21 +54,37 @@ export const getQueryWithParams = ({
}: QueryParams) => {
const {
environment,
+ kuery,
serviceName,
start,
end,
percentileThresholdValue,
+ transactionType,
transactionName,
} = params;
+
+ // converts string based start/end to epochmillis
+ const setup = pipe(
+ rangeRt.decode({ start, end }),
+ getOrElse((errors) => {
+ throw new Error(failure(errors).join('\n'));
+ })
+ ) as Setup & SetupTimeRange;
+
+ const filters = getCorrelationsFilters({
+ setup,
+ environment,
+ kuery,
+ serviceName,
+ transactionType,
+ transactionName,
+ });
+
return {
bool: {
filter: [
- ...getTermsQuery(PROCESSOR_EVENT, ProcessorEvent.transaction),
- ...getTermsQuery(SERVICE_NAME, serviceName),
- ...getTermsQuery(TRANSACTION_NAME, transactionName),
+ ...filters,
...getTermsQuery(fieldName, fieldValue),
- ...getRangeQuery(start, end),
- ...getEnvironmentQuery(environment),
...getPercentileThresholdValueQuery(percentileThresholdValue),
] as estypes.QueryDslQueryContainer[],
},
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_correlation.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_correlation.test.ts
index 24741ebaa2daef..678328dce1a190 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_correlation.test.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_correlation.test.ts
@@ -15,7 +15,7 @@ import {
BucketCorrelation,
} from './query_correlation';
-const params = { index: 'apm-*' };
+const params = { index: 'apm-*', start: '2020', end: '2021' };
const expectations = [1, 3, 5];
const ranges = [{ to: 1 }, { from: 1, to: 3 }, { from: 3, to: 5 }, { from: 5 }];
const fractions = [1, 2, 4, 5];
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_candidates.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_candidates.test.ts
index 89bdd4280d3249..8929b31b3ecb17 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_candidates.test.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_candidates.test.ts
@@ -16,7 +16,7 @@ import {
shouldBeExcluded,
} from './query_field_candidates';
-const params = { index: 'apm-*' };
+const params = { index: 'apm-*', start: '2020', end: '2021' };
describe('query_field_candidates', () => {
describe('shouldBeExcluded', () => {
@@ -61,6 +61,15 @@ describe('query_field_candidates', () => {
'processor.event': 'transaction',
},
},
+ {
+ range: {
+ '@timestamp': {
+ format: 'epoch_millis',
+ gte: 1577836800000,
+ lte: 1609459200000,
+ },
+ },
+ },
],
},
},
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_value_pairs.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_value_pairs.test.ts
index ea5a1f55bc9246..7ffbc5208e41e7 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_value_pairs.test.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_value_pairs.test.ts
@@ -16,7 +16,7 @@ import {
getTermsAggRequest,
} from './query_field_value_pairs';
-const params = { index: 'apm-*' };
+const params = { index: 'apm-*', start: '2020', end: '2021' };
describe('query_field_value_pairs', () => {
describe('getTermsAggRequest', () => {
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_fractions.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_fractions.test.ts
index 6052841d277c3d..3e7d4a52e4de2e 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_fractions.test.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_fractions.test.ts
@@ -14,7 +14,7 @@ import {
getTransactionDurationRangesRequest,
} from './query_fractions';
-const params = { index: 'apm-*' };
+const params = { index: 'apm-*', start: '2020', end: '2021' };
const ranges = [{ to: 1 }, { from: 1, to: 3 }, { from: 3, to: 5 }, { from: 5 }];
describe('query_fractions', () => {
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram.test.ts
index 2be94463522605..ace91779479601 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram.test.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram.test.ts
@@ -14,7 +14,7 @@ import {
getTransactionDurationHistogramRequest,
} from './query_histogram';
-const params = { index: 'apm-*' };
+const params = { index: 'apm-*', start: '2020', end: '2021' };
const interval = 100;
describe('query_histogram', () => {
@@ -40,6 +40,15 @@ describe('query_histogram', () => {
'processor.event': 'transaction',
},
},
+ {
+ range: {
+ '@timestamp': {
+ format: 'epoch_millis',
+ gte: 1577836800000,
+ lte: 1609459200000,
+ },
+ },
+ },
],
},
},
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_interval.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_interval.test.ts
index 9ed529ccabddbe..ebd78f12485102 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_interval.test.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_interval.test.ts
@@ -14,7 +14,7 @@ import {
getHistogramIntervalRequest,
} from './query_histogram_interval';
-const params = { index: 'apm-*' };
+const params = { index: 'apm-*', start: '2020', end: '2021' };
describe('query_histogram_interval', () => {
describe('getHistogramIntervalRequest', () => {
@@ -43,6 +43,15 @@ describe('query_histogram_interval', () => {
'processor.event': 'transaction',
},
},
+ {
+ range: {
+ '@timestamp': {
+ format: 'epoch_millis',
+ gte: 1577836800000,
+ lte: 1609459200000,
+ },
+ },
+ },
],
},
},
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_rangesteps.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_range_steps.test.ts
similarity index 77%
rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_rangesteps.test.ts
rename to x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_range_steps.test.ts
index bb366ea29fed48..76aab1cd979c94 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_rangesteps.test.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_range_steps.test.ts
@@ -10,13 +10,13 @@ import type { estypes } from '@elastic/elasticsearch';
import type { ElasticsearchClient } from 'src/core/server';
import {
- fetchTransactionDurationHistogramRangesteps,
+ fetchTransactionDurationHistogramRangeSteps,
getHistogramIntervalRequest,
-} from './query_histogram_rangesteps';
+} from './query_histogram_range_steps';
-const params = { index: 'apm-*' };
+const params = { index: 'apm-*', start: '2020', end: '2021' };
-describe('query_histogram_rangesteps', () => {
+describe('query_histogram_range_steps', () => {
describe('getHistogramIntervalRequest', () => {
it('returns the request body for the histogram interval request', () => {
const req = getHistogramIntervalRequest(params);
@@ -43,6 +43,15 @@ describe('query_histogram_rangesteps', () => {
'processor.event': 'transaction',
},
},
+ {
+ range: {
+ '@timestamp': {
+ format: 'epoch_millis',
+ gte: 1577836800000,
+ lte: 1609459200000,
+ },
+ },
+ },
],
},
},
@@ -53,13 +62,14 @@ describe('query_histogram_rangesteps', () => {
});
});
- describe('fetchTransactionDurationHistogramRangesteps', () => {
+ describe('fetchTransactionDurationHistogramRangeSteps', () => {
it('fetches the range steps for the log histogram', async () => {
const esClientSearchMock = jest.fn((req: estypes.SearchRequest): {
body: estypes.SearchResponse;
} => {
return {
body: ({
+ hits: { total: { value: 10 } },
aggregations: {
transaction_duration_max: {
value: 10000,
@@ -76,7 +86,7 @@ describe('query_histogram_rangesteps', () => {
search: esClientSearchMock,
} as unknown) as ElasticsearchClient;
- const resp = await fetchTransactionDurationHistogramRangesteps(
+ const resp = await fetchTransactionDurationHistogramRangeSteps(
esClientMock,
params
);
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_rangesteps.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_range_steps.ts
similarity index 83%
rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_rangesteps.ts
rename to x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_range_steps.ts
index e537165ca53f37..6ee5dd6bcdf83b 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_rangesteps.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_range_steps.ts
@@ -23,6 +23,14 @@ import type { SearchServiceParams } from '../../../../common/search_strategies/c
import { getQueryWithParams } from './get_query_with_params';
+const getHistogramRangeSteps = (min: number, max: number, steps: number) => {
+ // A d3 based scale function as a helper to get equally distributed bins on a log scale.
+ const logFn = scaleLog().domain([min, max]).range([1, steps]);
+ return [...Array(steps).keys()]
+ .map(logFn.invert)
+ .map((d) => (isNaN(d) ? 0 : d));
+};
+
export const getHistogramIntervalRequest = (
params: SearchServiceParams
): estypes.SearchRequest => ({
@@ -37,19 +45,24 @@ export const getHistogramIntervalRequest = (
},
});
-export const fetchTransactionDurationHistogramRangesteps = async (
+export const fetchTransactionDurationHistogramRangeSteps = async (
esClient: ElasticsearchClient,
params: SearchServiceParams
): Promise => {
+ const steps = 100;
+
const resp = await esClient.search(getHistogramIntervalRequest(params));
+ if ((resp.body.hits.total as estypes.SearchTotalHits).value === 0) {
+ return getHistogramRangeSteps(0, 1, 100);
+ }
+
if (resp.body.aggregations === undefined) {
throw new Error(
- 'fetchTransactionDurationHistogramInterval failed, did not return aggregations.'
+ 'fetchTransactionDurationHistogramRangeSteps failed, did not return aggregations.'
);
}
- const steps = 100;
const min = (resp.body.aggregations
.transaction_duration_min as estypes.AggregationsValueAggregate).value;
const max =
@@ -57,9 +70,5 @@ export const fetchTransactionDurationHistogramRangesteps = async (
.transaction_duration_max as estypes.AggregationsValueAggregate).value *
2;
- // A d3 based scale function as a helper to get equally distributed bins on a log scale.
- const logFn = scaleLog().domain([min, max]).range([1, steps]);
- return [...Array(steps).keys()]
- .map(logFn.invert)
- .map((d) => (isNaN(d) ? 0 : d));
+ return getHistogramRangeSteps(min, max, steps);
};
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_percentiles.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_percentiles.test.ts
index 0c319aee0fb2b7..f0d01a4849f9fd 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_percentiles.test.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_percentiles.test.ts
@@ -14,7 +14,7 @@ import {
getTransactionDurationPercentilesRequest,
} from './query_percentiles';
-const params = { index: 'apm-*' };
+const params = { index: 'apm-*', start: '2020', end: '2021' };
describe('query_percentiles', () => {
describe('getTransactionDurationPercentilesRequest', () => {
@@ -41,10 +41,20 @@ describe('query_percentiles', () => {
'processor.event': 'transaction',
},
},
+ {
+ range: {
+ '@timestamp': {
+ format: 'epoch_millis',
+ gte: 1577836800000,
+ lte: 1609459200000,
+ },
+ },
+ },
],
},
},
size: 0,
+ track_total_hits: true,
},
index: params.index,
});
@@ -53,6 +63,7 @@ describe('query_percentiles', () => {
describe('fetchTransactionDurationPercentiles', () => {
it('fetches the percentiles', async () => {
+ const totalDocs = 10;
const percentilesValues = {
'1.0': 5.0,
'5.0': 25.0,
@@ -68,6 +79,7 @@ describe('query_percentiles', () => {
} => {
return {
body: ({
+ hits: { total: { value: totalDocs } },
aggregations: {
transaction_duration_percentiles: {
values: percentilesValues,
@@ -86,7 +98,7 @@ describe('query_percentiles', () => {
params
);
- expect(resp).toEqual(percentilesValues);
+ expect(resp).toEqual({ percentiles: percentilesValues, totalDocs });
expect(esClientSearchMock).toHaveBeenCalledTimes(1);
});
});
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_percentiles.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_percentiles.ts
index 18dcefb59a11a5..c80f5d836c0ef1 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_percentiles.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_percentiles.ts
@@ -38,6 +38,7 @@ export const getTransactionDurationPercentilesRequest = (
return {
index: params.index,
body: {
+ track_total_hits: true,
query,
size: 0,
aggs: {
@@ -61,7 +62,7 @@ export const fetchTransactionDurationPercentiles = async (
percents?: number[],
fieldName?: string,
fieldValue?: string
-): Promise> => {
+): Promise<{ totalDocs: number; percentiles: Record }> => {
const resp = await esClient.search(
getTransactionDurationPercentilesRequest(
params,
@@ -71,14 +72,22 @@ export const fetchTransactionDurationPercentiles = async (
)
);
+ // return early with no results if the search didn't return any documents
+ if ((resp.body.hits.total as estypes.SearchTotalHits).value === 0) {
+ return { totalDocs: 0, percentiles: {} };
+ }
+
if (resp.body.aggregations === undefined) {
throw new Error(
'fetchTransactionDurationPercentiles failed, did not return aggregations.'
);
}
- return (
- (resp.body.aggregations
- .transaction_duration_percentiles as estypes.AggregationsTDigestPercentilesAggregate)
- .values ?? {}
- );
+
+ return {
+ totalDocs: (resp.body.hits.total as estypes.SearchTotalHits).value,
+ percentiles:
+ (resp.body.aggregations
+ .transaction_duration_percentiles as estypes.AggregationsTDigestPercentilesAggregate)
+ .values ?? {},
+ };
};
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_ranges.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_ranges.test.ts
index 9451928e47ded3..7d18efc360563f 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_ranges.test.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_ranges.test.ts
@@ -14,7 +14,7 @@ import {
getTransactionDurationRangesRequest,
} from './query_ranges';
-const params = { index: 'apm-*' };
+const params = { index: 'apm-*', start: '2020', end: '2021' };
const rangeSteps = [1, 3, 5];
describe('query_ranges', () => {
@@ -59,6 +59,15 @@ describe('query_ranges', () => {
'processor.event': 'transaction',
},
},
+ {
+ range: {
+ '@timestamp': {
+ format: 'epoch_millis',
+ gte: 1577836800000,
+ lte: 1609459200000,
+ },
+ },
+ },
],
},
},
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/search_strategy.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/search_strategy.test.ts
index 6d4bfcdde99943..09775cb2eb0347 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/search_strategy.test.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/search_strategy.test.ts
@@ -122,6 +122,8 @@ describe('APM Correlations search strategy', () => {
} as unknown) as SearchStrategyDependencies;
params = {
index: 'apm-*',
+ start: '2020',
+ end: '2021',
};
});
@@ -154,10 +156,22 @@ describe('APM Correlations search strategy', () => {
},
query: {
bool: {
- filter: [{ term: { 'processor.event': 'transaction' } }],
+ filter: [
+ { term: { 'processor.event': 'transaction' } },
+ {
+ range: {
+ '@timestamp': {
+ format: 'epoch_millis',
+ gte: 1577836800000,
+ lte: 1609459200000,
+ },
+ },
+ },
+ ],
},
},
size: 0,
+ track_total_hits: true,
})
);
});
@@ -167,11 +181,17 @@ describe('APM Correlations search strategy', () => {
it('retrieves the current request', async () => {
const searchStrategy = await apmCorrelationsSearchStrategyProvider();
const response = await searchStrategy
- .search({ id: 'my-search-id', params }, {}, mockDeps)
+ .search({ params }, {}, mockDeps)
.toPromise();
- expect(response).toEqual(
- expect.objectContaining({ id: 'my-search-id' })
+ const searchStrategyId = response.id;
+
+ const response2 = await searchStrategy
+ .search({ id: searchStrategyId, params }, {}, mockDeps)
+ .toPromise();
+
+ expect(response2).toEqual(
+ expect.objectContaining({ id: searchStrategyId })
);
});
});
@@ -226,7 +246,7 @@ describe('APM Correlations search strategy', () => {
expect(response2.id).toEqual(response1.id);
expect(response2).toEqual(
- expect.objectContaining({ loaded: 10, isRunning: false })
+ expect.objectContaining({ loaded: 100, isRunning: false })
);
});
});
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/search_strategy.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/search_strategy.ts
index d6b4e0e7094b35..8f2e6913c0d062 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/search_strategy.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/search_strategy.ts
@@ -41,14 +41,40 @@ export const apmCorrelationsSearchStrategyProvider = (): ISearchStrategy<
throw new Error('Invalid request parameters.');
}
- const id = request.id ?? uuid();
+ // The function to fetch the current state of the async search service.
+ // This will be either an existing service for a follow up fetch or a new one for new requests.
+ let getAsyncSearchServiceState: ReturnType<
+ typeof asyncSearchServiceProvider
+ >;
+
+ // If the request includes an ID, we require that the async search service already exists
+ // otherwise we throw an error. The client should never poll a service that's been cancelled or finished.
+ // This also avoids instantiating async search services when the service gets called with random IDs.
+ if (typeof request.id === 'string') {
+ const existingGetAsyncSearchServiceState = asyncSearchServiceMap.get(
+ request.id
+ );
- const getAsyncSearchServiceState =
- asyncSearchServiceMap.get(id) ??
- asyncSearchServiceProvider(deps.esClient.asCurrentUser, request.params);
+ if (typeof existingGetAsyncSearchServiceState === 'undefined') {
+ throw new Error(
+ `AsyncSearchService with ID '${request.id}' does not exist.`
+ );
+ }
+
+ getAsyncSearchServiceState = existingGetAsyncSearchServiceState;
+ } else {
+ getAsyncSearchServiceState = asyncSearchServiceProvider(
+ deps.esClient.asCurrentUser,
+ request.params
+ );
+ }
+
+ // Reuse the request's id or create a new one.
+ const id = request.id ?? uuid();
const {
error,
+ log,
isRunning,
loaded,
started,
@@ -76,6 +102,7 @@ export const apmCorrelationsSearchStrategyProvider = (): ISearchStrategy<
isRunning,
isPartial: isRunning,
rawResponse: {
+ log,
took,
values,
percentileThresholdValue,
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/aggregation_utils.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/aggregation_utils.test.ts
index 63de0a59d4894a..4313ad58ecbc09 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/aggregation_utils.test.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/aggregation_utils.test.ts
@@ -14,6 +14,7 @@ describe('aggregation utils', () => {
expect(expectations).toEqual([0, 0.5, 1]);
expect(ranges).toEqual([{ to: 0 }, { from: 0, to: 1 }, { from: 1 }]);
});
+
it('returns expectations and ranges based on given percentiles #2', async () => {
const { expectations, ranges } = computeExpectationsAndRanges([1, 3, 5]);
expect(expectations).toEqual([1, 2, 4, 5]);
@@ -24,6 +25,7 @@ describe('aggregation utils', () => {
{ from: 5 },
]);
});
+
it('returns expectations and ranges with adjusted fractions', async () => {
const { expectations, ranges } = computeExpectationsAndRanges([
1,
@@ -45,5 +47,97 @@ describe('aggregation utils', () => {
{ from: 5 },
]);
});
+
+ // TODO identify these results derived from the array of percentiles are usable with the ES correlation aggregation
+ it('returns expectation and ranges adjusted when percentiles have equal values', async () => {
+ const { expectations, ranges } = computeExpectationsAndRanges([
+ 5000,
+ 5000,
+ 3090428,
+ 3090428,
+ 3090428,
+ 3618812,
+ 3618812,
+ 3618812,
+ 3618812,
+ 3696636,
+ 3696636,
+ 3696636,
+ 3696636,
+ 3696636,
+ 3696636,
+ ]);
+ expect(expectations).toEqual([
+ 5000,
+ 1856256.7999999998,
+ 3392361.714285714,
+ 3665506.4,
+ 3696636,
+ ]);
+ expect(ranges).toEqual([
+ {
+ to: 5000,
+ },
+ {
+ from: 5000,
+ to: 5000,
+ },
+ {
+ from: 5000,
+ to: 3090428,
+ },
+ {
+ from: 3090428,
+ to: 3090428,
+ },
+ {
+ from: 3090428,
+ to: 3090428,
+ },
+ {
+ from: 3090428,
+ to: 3618812,
+ },
+ {
+ from: 3618812,
+ to: 3618812,
+ },
+ {
+ from: 3618812,
+ to: 3618812,
+ },
+ {
+ from: 3618812,
+ to: 3618812,
+ },
+ {
+ from: 3618812,
+ to: 3696636,
+ },
+ {
+ from: 3696636,
+ to: 3696636,
+ },
+ {
+ from: 3696636,
+ to: 3696636,
+ },
+ {
+ from: 3696636,
+ to: 3696636,
+ },
+ {
+ from: 3696636,
+ to: 3696636,
+ },
+ {
+ from: 3696636,
+ to: 3696636,
+ },
+ {
+ from: 3696636,
+ },
+ ]);
+ });
});
});
diff --git a/x-pack/plugins/apm/server/routes/fleet.ts b/x-pack/plugins/apm/server/routes/fleet.ts
index b760014d6af89f..66843f4f0df4d7 100644
--- a/x-pack/plugins/apm/server/routes/fleet.ts
+++ b/x-pack/plugins/apm/server/routes/fleet.ts
@@ -25,8 +25,6 @@ import { createCloudApmPackgePolicy } from '../lib/fleet/create_cloud_apm_packag
import { getUnsupportedApmServerSchema } from '../lib/fleet/get_unsupported_apm_server_schema';
import { isSuperuser } from '../lib/fleet/is_superuser';
import { getInternalSavedObjectsClient } from '../lib/helpers/get_internal_saved_objects_client';
-import { setupRequest } from '../lib/helpers/setup_request';
-import { createStaticIndexPattern } from '../lib/index_pattern/create_static_index_pattern';
const hasFleetDataRoute = createApmServerRoute({
endpoint: 'GET /api/apm/fleet/has_data',
@@ -156,7 +154,7 @@ const createCloudApmPackagePolicyRoute = createApmServerRoute({
endpoint: 'POST /api/apm/fleet/cloud_apm_package_policy',
options: { tags: ['access:apm', 'access:apm_write'] },
handler: async (resources) => {
- const { plugins, context, config, request, logger, core } = resources;
+ const { plugins, context, config, request, logger } = resources;
const cloudApmMigrationEnabled =
config['xpack.apm.agent.migrations.enabled'];
if (!plugins.fleet || !plugins.security) {
@@ -174,7 +172,7 @@ const createCloudApmPackagePolicyRoute = createApmServerRoute({
throw Boom.forbidden(CLOUD_SUPERUSER_REQUIRED_MESSAGE);
}
- const cloudApmAackagePolicy = await createCloudApmPackgePolicy({
+ const cloudApmPackagePolicy = await createCloudApmPackgePolicy({
cloudPluginSetup,
fleetPluginStart,
savedObjectsClient,
@@ -182,25 +180,7 @@ const createCloudApmPackagePolicyRoute = createApmServerRoute({
logger,
});
- const [setup, internalSavedObjectsClient] = await Promise.all([
- setupRequest(resources),
- core
- .start()
- .then(({ savedObjects }) => savedObjects.createInternalRepository()),
- ]);
-
- const spaceId = plugins.spaces?.setup.spacesService.getSpaceId(request);
-
- // force update the index pattern title with data streams
- await createStaticIndexPattern(
- setup,
- config,
- internalSavedObjectsClient,
- spaceId,
- true
- );
-
- return { cloud_apm_package_policy: cloudApmAackagePolicy };
+ return { cloudApmPackagePolicy };
},
});
diff --git a/x-pack/plugins/apm/server/routes/index_pattern.ts b/x-pack/plugins/apm/server/routes/index_pattern.ts
index aa70cde4f96ae2..190baf3bbc2701 100644
--- a/x-pack/plugins/apm/server/routes/index_pattern.ts
+++ b/x-pack/plugins/apm/server/routes/index_pattern.ts
@@ -32,12 +32,12 @@ const staticIndexPatternRoute = createApmServerRoute({
const spaceId = spaces?.setup.spacesService.getSpaceId(request);
- const didCreateIndexPattern = await createStaticIndexPattern(
+ const didCreateIndexPattern = await createStaticIndexPattern({
setup,
config,
savedObjectsClient,
- spaceId
- );
+ spaceId,
+ });
return { created: didCreateIndexPattern };
},
diff --git a/x-pack/plugins/apm/server/routes/register_routes/index.ts b/x-pack/plugins/apm/server/routes/register_routes/index.ts
index 8e6070de722bea..16e77f59f4d02c 100644
--- a/x-pack/plugins/apm/server/routes/register_routes/index.ts
+++ b/x-pack/plugins/apm/server/routes/register_routes/index.ts
@@ -18,9 +18,12 @@ import {
routeValidationObject,
} from '@kbn/server-route-repository';
import { mergeRt, jsonRt } from '@kbn/io-ts-utils';
-import { UsageCollectionSetup } from '../../../../../../src/plugins/usage_collection/server';
import { pickKeys } from '../../../common/utils/pick_keys';
-import { APMRouteHandlerResources, InspectResponse } from '../typings';
+import {
+ APMRouteHandlerResources,
+ InspectResponse,
+ TelemetryUsageCounter,
+} from '../typings';
import type { ApmPluginRequestHandlerContext } from '../typings';
const inspectRt = t.exact(
@@ -56,9 +59,7 @@ export function registerRoutes({
repository: ServerRouteRepository;
config: APMRouteHandlerResources['config'];
ruleDataClient: APMRouteHandlerResources['ruleDataClient'];
- telemetryUsageCounter?: ReturnType<
- UsageCollectionSetup['createUsageCounter']
- >;
+ telemetryUsageCounter?: TelemetryUsageCounter;
}) {
const routes = repository.getRoutes();
@@ -104,6 +105,7 @@ export function registerRoutes({
logger,
core,
plugins,
+ telemetryUsageCounter,
params: merge(
{
query: {
diff --git a/x-pack/plugins/apm/server/routes/settings/agent_configuration.ts b/x-pack/plugins/apm/server/routes/settings/agent_configuration.ts
index 05eec478937935..f50770cb5ded7d 100644
--- a/x-pack/plugins/apm/server/routes/settings/agent_configuration.ts
+++ b/x-pack/plugins/apm/server/routes/settings/agent_configuration.ts
@@ -79,7 +79,7 @@ const deleteAgentConfigurationRoute = createApmServerRoute({
}),
handler: async (resources) => {
const setup = await setupRequest(resources);
- const { params, logger, core } = resources;
+ const { params, logger, core, telemetryUsageCounter } = resources;
const { service } = params.body;
@@ -106,6 +106,7 @@ const deleteAgentConfigurationRoute = createApmServerRoute({
core,
fleetPluginStart: await resources.plugins.fleet.start(),
setup,
+ telemetryUsageCounter,
});
logger.info(
`Updated Fleet integration policy for APM to remove the deleted agent configuration.`
@@ -128,7 +129,7 @@ const createOrUpdateAgentConfigurationRoute = createApmServerRoute({
]),
handler: async (resources) => {
const setup = await setupRequest(resources);
- const { params, logger, core } = resources;
+ const { params, logger, core, telemetryUsageCounter } = resources;
const { body, query } = params;
// if the config already exists, it is fetched and updated
@@ -162,6 +163,7 @@ const createOrUpdateAgentConfigurationRoute = createApmServerRoute({
core,
fleetPluginStart: await resources.plugins.fleet.start(),
setup,
+ telemetryUsageCounter,
});
logger.info(
`Saved latest agent settings to Fleet integration policy for APM.`
diff --git a/x-pack/plugins/apm/server/routes/typings.ts b/x-pack/plugins/apm/server/routes/typings.ts
index 56a5950c273673..4279cfd84328c7 100644
--- a/x-pack/plugins/apm/server/routes/typings.ts
+++ b/x-pack/plugins/apm/server/routes/typings.ts
@@ -18,6 +18,7 @@ import type { RacApiRequestHandlerContext } from '../../../rule_registry/server'
import { LicensingApiRequestHandlerContext } from '../../../licensing/server';
import { APMConfig } from '..';
import { APMPluginDependencies } from '../types';
+import { UsageCollectionSetup } from '../../../../../src/plugins/usage_collection/server';
export interface ApmPluginRequestHandlerContext extends RequestHandlerContext {
licensing: LicensingApiRequestHandlerContext;
@@ -47,6 +48,10 @@ export interface APMRouteCreateOptions {
};
}
+export type TelemetryUsageCounter = ReturnType<
+ UsageCollectionSetup['createUsageCounter']
+>;
+
export interface APMRouteHandlerResources {
request: KibanaRequest;
context: ApmPluginRequestHandlerContext;
@@ -68,4 +73,5 @@ export interface APMRouteHandlerResources {
};
};
ruleDataClient: RuleDataClient;
+ telemetryUsageCounter?: TelemetryUsageCounter;
}
diff --git a/x-pack/plugins/canvas/public/components/function_reference_generator/function_reference_generator.tsx b/x-pack/plugins/canvas/public/components/function_reference_generator/function_reference_generator.tsx
index 81532816d9c830..eb394801f549c4 100644
--- a/x-pack/plugins/canvas/public/components/function_reference_generator/function_reference_generator.tsx
+++ b/x-pack/plugins/canvas/public/components/function_reference_generator/function_reference_generator.tsx
@@ -29,7 +29,7 @@ export const FunctionReferenceGenerator: FC = ({ functionRegistry }) => {
};
return (
-
+
Generate function reference
);
diff --git a/x-pack/plugins/canvas/public/components/help_menu/help_menu.tsx b/x-pack/plugins/canvas/public/components/help_menu/help_menu.tsx
index 2877ccf41056df..af1850beb5290f 100644
--- a/x-pack/plugins/canvas/public/components/help_menu/help_menu.tsx
+++ b/x-pack/plugins/canvas/public/components/help_menu/help_menu.tsx
@@ -46,13 +46,13 @@ export const HelpMenu: FC = ({ functionRegistry }) => {
return (
<>
-
+
{strings.getKeyboardShortcutsLinkLabel()}
{FunctionReferenceGenerator ? (
-
+
) : null}
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/__stories__/__snapshots__/edit_menu.stories.storyshot b/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/__stories__/__snapshots__/edit_menu.stories.storyshot
index cc33ae3526c0c3..f2bc9c57cbcc66 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/__stories__/__snapshots__/edit_menu.stories.storyshot
+++ b/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/__stories__/__snapshots__/edit_menu.stories.storyshot
@@ -9,7 +9,7 @@ exports[`Storyshots components/WorkpadHeader/EditMenu 2 elements selected 1`] =
>
= ({
const editControl = (togglePopover: React.MouseEventHandler) => (
= ({
const shareControl = (togglePopover: React.MouseEventHandler) => (
= ({
};
const viewControl = (togglePopover: React.MouseEventHandler) => (
-
+
{strings.getViewMenuButtonLabel()}
);
diff --git a/x-pack/plugins/cases/docs/README.md b/x-pack/plugins/cases/docs/README.md
index 85482d98dc5096..16e93119587639 100644
--- a/x-pack/plugins/cases/docs/README.md
+++ b/x-pack/plugins/cases/docs/README.md
@@ -19,7 +19,7 @@ yarn global add typedoc typedoc-plugin-markdown
```bash
cd x-pack/plugins/cases/docs
-npx typedoc --options cases_client_typedoc.json
+npx typedoc --gitRemote upstream --options cases_client_typedoc.json
```
After running the above commands the files in the `server` directory will be updated to match the new tsdocs.
diff --git a/x-pack/plugins/cases/docs/cases_client/classes/client.casesclient.md b/x-pack/plugins/cases/docs/cases_client/classes/client.casesclient.md
index bd07a44a2bfdf3..b3bef7b36b5711 100644
--- a/x-pack/plugins/cases/docs/cases_client/classes/client.casesclient.md
+++ b/x-pack/plugins/cases/docs/cases_client/classes/client.casesclient.md
@@ -45,7 +45,7 @@ Client wrapper that contains accessor methods for individual entities within the
**Returns:** [*CasesClient*](client.casesclient.md)
-Defined in: [client.ts:28](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/client.ts#L28)
+Defined in: [client.ts:28](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/client.ts#L28)
## Properties
@@ -53,7 +53,7 @@ Defined in: [client.ts:28](https://github.com/jonathan-buttner/kibana/blob/b65ed
• `Private` `Readonly` **\_attachments**: [*AttachmentsSubClient*](../interfaces/attachments_client.attachmentssubclient.md)
-Defined in: [client.ts:24](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/client.ts#L24)
+Defined in: [client.ts:24](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/client.ts#L24)
___
@@ -61,7 +61,7 @@ ___
• `Private` `Readonly` **\_cases**: [*CasesSubClient*](../interfaces/cases_client.casessubclient.md)
-Defined in: [client.ts:23](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/client.ts#L23)
+Defined in: [client.ts:23](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/client.ts#L23)
___
@@ -69,7 +69,7 @@ ___
• `Private` `Readonly` **\_casesClientInternal**: *CasesClientInternal*
-Defined in: [client.ts:22](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/client.ts#L22)
+Defined in: [client.ts:22](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/client.ts#L22)
___
@@ -77,7 +77,7 @@ ___
• `Private` `Readonly` **\_configure**: [*ConfigureSubClient*](../interfaces/configure_client.configuresubclient.md)
-Defined in: [client.ts:27](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/client.ts#L27)
+Defined in: [client.ts:27](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/client.ts#L27)
___
@@ -85,7 +85,7 @@ ___
• `Private` `Readonly` **\_stats**: [*StatsSubClient*](../interfaces/stats_client.statssubclient.md)
-Defined in: [client.ts:28](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/client.ts#L28)
+Defined in: [client.ts:28](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/client.ts#L28)
___
@@ -93,7 +93,7 @@ ___
• `Private` `Readonly` **\_subCases**: [*SubCasesClient*](../interfaces/sub_cases_client.subcasesclient.md)
-Defined in: [client.ts:26](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/client.ts#L26)
+Defined in: [client.ts:26](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/client.ts#L26)
___
@@ -101,7 +101,7 @@ ___
• `Private` `Readonly` **\_userActions**: [*UserActionsSubClient*](../interfaces/user_actions_client.useractionssubclient.md)
-Defined in: [client.ts:25](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/client.ts#L25)
+Defined in: [client.ts:25](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/client.ts#L25)
## Accessors
@@ -113,7 +113,7 @@ Retrieves an interface for interacting with attachments (comments) entities.
**Returns:** [*AttachmentsSubClient*](../interfaces/attachments_client.attachmentssubclient.md)
-Defined in: [client.ts:50](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/client.ts#L50)
+Defined in: [client.ts:50](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/client.ts#L50)
___
@@ -125,7 +125,7 @@ Retrieves an interface for interacting with cases entities.
**Returns:** [*CasesSubClient*](../interfaces/cases_client.casessubclient.md)
-Defined in: [client.ts:43](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/client.ts#L43)
+Defined in: [client.ts:43](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/client.ts#L43)
___
@@ -137,7 +137,7 @@ Retrieves an interface for interacting with the configuration of external connec
**Returns:** [*ConfigureSubClient*](../interfaces/configure_client.configuresubclient.md)
-Defined in: [client.ts:76](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/client.ts#L76)
+Defined in: [client.ts:76](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/client.ts#L76)
___
@@ -149,7 +149,7 @@ Retrieves an interface for retrieving statistics related to the cases entities.
**Returns:** [*StatsSubClient*](../interfaces/stats_client.statssubclient.md)
-Defined in: [client.ts:83](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/client.ts#L83)
+Defined in: [client.ts:83](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/client.ts#L83)
___
@@ -163,7 +163,7 @@ Currently this functionality is disabled and will throw an error if this functio
**Returns:** [*SubCasesClient*](../interfaces/sub_cases_client.subcasesclient.md)
-Defined in: [client.ts:66](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/client.ts#L66)
+Defined in: [client.ts:66](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/client.ts#L66)
___
@@ -175,4 +175,4 @@ Retrieves an interface for interacting with the user actions associated with the
**Returns:** [*UserActionsSubClient*](../interfaces/user_actions_client.useractionssubclient.md)
-Defined in: [client.ts:57](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/client.ts#L57)
+Defined in: [client.ts:57](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/client.ts#L57)
diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_add.addargs.md b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_add.addargs.md
index f8f7babd15b909..0fafb377bcc41d 100644
--- a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_add.addargs.md
+++ b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_add.addargs.md
@@ -21,14 +21,14 @@ The arguments needed for creating a new attachment to a case.
The case ID that this attachment will be associated with
-Defined in: [attachments/add.ts:305](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/attachments/add.ts#L305)
+Defined in: [attachments/add.ts:305](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/attachments/add.ts#L305)
___
### comment
-• **comment**: { `comment`: *string* ; `owner`: *string* ; `type`: user } \| { `alertId`: *string* \| *string*[] ; `index`: *string* \| *string*[] ; `owner`: *string* ; `rule`: { id: string \| null; name: string \| null; } ; `type`: alert \| generatedAlert }
+• **comment**: { `comment`: *string* ; `owner`: *string* ; `type`: user } \| { `alertId`: *string* \| *string*[] ; `index`: *string* \| *string*[] ; `owner`: *string* ; `rule`: { id: string \| null; name: string \| null; } ; `type`: alert \| generatedAlert } \| { `actions`: { targets: { hostname: string; endpointId: string; }[]; type: string; } ; `comment`: *string* ; `owner`: *string* ; `type`: actions }
The attachment values.
-Defined in: [attachments/add.ts:309](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/attachments/add.ts#L309)
+Defined in: [attachments/add.ts:309](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/attachments/add.ts#L309)
diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_client.attachmentssubclient.md b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_client.attachmentssubclient.md
index 57141796f6f673..ff9744583cfaf7 100644
--- a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_client.attachmentssubclient.md
+++ b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_client.attachmentssubclient.md
@@ -35,7 +35,7 @@ Adds an attachment to a case.
**Returns:** *Promise*<[*ICaseResponse*](typedoc_interfaces.icaseresponse.md)\>
-Defined in: [attachments/client.ts:35](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/attachments/client.ts#L35)
+Defined in: [attachments/client.ts:35](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/attachments/client.ts#L35)
___
@@ -53,7 +53,7 @@ Deletes a single attachment for a specific case.
**Returns:** *Promise*
-Defined in: [attachments/client.ts:43](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/attachments/client.ts#L43)
+Defined in: [attachments/client.ts:43](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/attachments/client.ts#L43)
___
@@ -71,7 +71,7 @@ Deletes all attachments associated with a single case.
**Returns:** *Promise*
-Defined in: [attachments/client.ts:39](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/attachments/client.ts#L39)
+Defined in: [attachments/client.ts:39](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/attachments/client.ts#L39)
___
@@ -89,13 +89,13 @@ Retrieves all comments matching the search criteria.
**Returns:** *Promise*<[*ICommentsResponse*](typedoc_interfaces.icommentsresponse.md)\>
-Defined in: [attachments/client.ts:47](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/attachments/client.ts#L47)
+Defined in: [attachments/client.ts:47](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/attachments/client.ts#L47)
___
### get
-▸ **get**(`getArgs`: [*GetArgs*](attachments_get.getargs.md)): *Promise*<{ `comment`: *string* ; `owner`: *string* ; `type`: user } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* } & { `alertId`: *string* \| *string*[] ; `index`: *string* \| *string*[] ; `owner`: *string* ; `rule`: { id: string \| null; name: string \| null; } ; `type`: alert \| generatedAlert } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* }\>
+▸ **get**(`getArgs`: [*GetArgs*](attachments_get.getargs.md)): *Promise*<{ `comment`: *string* ; `owner`: *string* ; `type`: user } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* } & { `alertId`: *string* \| *string*[] ; `index`: *string* \| *string*[] ; `owner`: *string* ; `rule`: { id: string \| null; name: string \| null; } ; `type`: alert \| generatedAlert } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* } & { `actions`: { targets: { hostname: string; endpointId: string; }[]; type: string; } ; `comment`: *string* ; `owner`: *string* ; `type`: actions } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* }\>
Retrieves a single attachment for a case.
@@ -105,9 +105,9 @@ Retrieves a single attachment for a case.
| :------ | :------ |
| `getArgs` | [*GetArgs*](attachments_get.getargs.md) |
-**Returns:** *Promise*<{ `comment`: *string* ; `owner`: *string* ; `type`: user } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* } & { `alertId`: *string* \| *string*[] ; `index`: *string* \| *string*[] ; `owner`: *string* ; `rule`: { id: string \| null; name: string \| null; } ; `type`: alert \| generatedAlert } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* }\>
+**Returns:** *Promise*<{ `comment`: *string* ; `owner`: *string* ; `type`: user } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* } & { `alertId`: *string* \| *string*[] ; `index`: *string* \| *string*[] ; `owner`: *string* ; `rule`: { id: string \| null; name: string \| null; } ; `type`: alert \| generatedAlert } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* } & { `actions`: { targets: { hostname: string; endpointId: string; }[]; type: string; } ; `comment`: *string* ; `owner`: *string* ; `type`: actions } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* }\>
-Defined in: [attachments/client.ts:59](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/attachments/client.ts#L59)
+Defined in: [attachments/client.ts:59](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/attachments/client.ts#L59)
___
@@ -125,7 +125,7 @@ Gets all attachments for a single case.
**Returns:** *Promise*<[*IAllCommentsResponse*](typedoc_interfaces.iallcommentsresponse.md)\>
-Defined in: [attachments/client.ts:55](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/attachments/client.ts#L55)
+Defined in: [attachments/client.ts:55](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/attachments/client.ts#L55)
___
@@ -143,7 +143,7 @@ Retrieves all alerts attach to a case given a single case ID
**Returns:** *Promise*<{ `attached_at`: *string* ; `id`: *string* ; `index`: *string* }[]\>
-Defined in: [attachments/client.ts:51](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/attachments/client.ts#L51)
+Defined in: [attachments/client.ts:51](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/attachments/client.ts#L51)
___
@@ -163,4 +163,4 @@ The request must include all fields for the attachment. Even the fields that are
**Returns:** *Promise*<[*ICaseResponse*](typedoc_interfaces.icaseresponse.md)\>
-Defined in: [attachments/client.ts:65](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/attachments/client.ts#L65)
+Defined in: [attachments/client.ts:65](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/attachments/client.ts#L65)
diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_delete.deleteallargs.md b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_delete.deleteallargs.md
index d134c92e282a38..39c72e81a9935f 100644
--- a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_delete.deleteallargs.md
+++ b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_delete.deleteallargs.md
@@ -21,7 +21,7 @@ Parameters for deleting all comments of a case or sub case.
The case ID to delete all attachments for
-Defined in: [attachments/delete.ts:31](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/attachments/delete.ts#L31)
+Defined in: [attachments/delete.ts:31](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/attachments/delete.ts#L31)
___
@@ -31,4 +31,4 @@ ___
If specified the caseID will be ignored and this value will be used to find a sub case for deleting all the attachments
-Defined in: [attachments/delete.ts:35](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/attachments/delete.ts#L35)
+Defined in: [attachments/delete.ts:35](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/attachments/delete.ts#L35)
diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_delete.deleteargs.md b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_delete.deleteargs.md
index a1c177bad8a09c..fb7e61fa1521fd 100644
--- a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_delete.deleteargs.md
+++ b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_delete.deleteargs.md
@@ -22,7 +22,7 @@ Parameters for deleting a single attachment of a case or sub case.
The attachment ID to delete
-Defined in: [attachments/delete.ts:49](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/attachments/delete.ts#L49)
+Defined in: [attachments/delete.ts:49](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/attachments/delete.ts#L49)
___
@@ -32,7 +32,7 @@ ___
The case ID to delete an attachment from
-Defined in: [attachments/delete.ts:45](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/attachments/delete.ts#L45)
+Defined in: [attachments/delete.ts:45](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/attachments/delete.ts#L45)
___
@@ -42,4 +42,4 @@ ___
If specified the caseID will be ignored and this value will be used to find a sub case for deleting the attachment
-Defined in: [attachments/delete.ts:53](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/attachments/delete.ts#L53)
+Defined in: [attachments/delete.ts:53](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/attachments/delete.ts#L53)
diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.findargs.md b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.findargs.md
index dcd4deb28b687e..826a05f5865abf 100644
--- a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.findargs.md
+++ b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.findargs.md
@@ -21,7 +21,7 @@ Parameters for finding attachments of a case
The case ID for finding associated attachments
-Defined in: [attachments/get.ts:47](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/attachments/get.ts#L47)
+Defined in: [attachments/get.ts:47](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/attachments/get.ts#L47)
___
@@ -48,4 +48,4 @@ Optional parameters for filtering the returned attachments
| `sortOrder` | *undefined* \| ``"desc"`` \| ``"asc"`` |
| `subCaseId` | *undefined* \| *string* |
-Defined in: [attachments/get.ts:51](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/attachments/get.ts#L51)
+Defined in: [attachments/get.ts:51](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/attachments/get.ts#L51)
diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getallalertsattachtocase.md b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getallalertsattachtocase.md
index d935823054b037..abeeaca19b23ee 100644
--- a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getallalertsattachtocase.md
+++ b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getallalertsattachtocase.md
@@ -18,4 +18,4 @@
The ID of the case to retrieve the alerts from
-Defined in: [attachments/get.ts:87](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/attachments/get.ts#L87)
+Defined in: [attachments/get.ts:87](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/attachments/get.ts#L87)
diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getallargs.md b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getallargs.md
index 9577e89b460741..9ea29437d5c2cd 100644
--- a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getallargs.md
+++ b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getallargs.md
@@ -22,7 +22,7 @@ Parameters for retrieving all attachments of a case
The case ID to retrieve all attachments for
-Defined in: [attachments/get.ts:61](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/attachments/get.ts#L61)
+Defined in: [attachments/get.ts:61](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/attachments/get.ts#L61)
___
@@ -32,7 +32,7 @@ ___
Optionally include the attachments associated with a sub case
-Defined in: [attachments/get.ts:65](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/attachments/get.ts#L65)
+Defined in: [attachments/get.ts:65](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/attachments/get.ts#L65)
___
@@ -42,4 +42,4 @@ ___
If included the case ID will be ignored and the attachments will be retrieved from the specified ID of the sub case
-Defined in: [attachments/get.ts:69](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/attachments/get.ts#L69)
+Defined in: [attachments/get.ts:69](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/attachments/get.ts#L69)
diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getargs.md b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getargs.md
index 5530ad8bd936e6..e46d83d795f485 100644
--- a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getargs.md
+++ b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getargs.md
@@ -19,7 +19,7 @@
The ID of the attachment to retrieve
-Defined in: [attachments/get.ts:80](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/attachments/get.ts#L80)
+Defined in: [attachments/get.ts:80](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/attachments/get.ts#L80)
___
@@ -29,4 +29,4 @@ ___
The ID of the case to retrieve an attachment from
-Defined in: [attachments/get.ts:76](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/attachments/get.ts#L76)
+Defined in: [attachments/get.ts:76](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/attachments/get.ts#L76)
diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_update.updateargs.md b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_update.updateargs.md
index ce586a6bfdfbdf..23d7c88c9c8642 100644
--- a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_update.updateargs.md
+++ b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_update.updateargs.md
@@ -22,7 +22,7 @@ Parameters for updating a single attachment
The ID of the case that is associated with this attachment
-Defined in: [attachments/update.ts:32](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/attachments/update.ts#L32)
+Defined in: [attachments/update.ts:32](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/attachments/update.ts#L32)
___
@@ -32,14 +32,14 @@ ___
The ID of a sub case, if specified a sub case will be searched for to perform the attachment update instead of on a case
-Defined in: [attachments/update.ts:40](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/attachments/update.ts#L40)
+Defined in: [attachments/update.ts:40](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/attachments/update.ts#L40)
___
### updateRequest
-• **updateRequest**: { `comment`: *string* ; `owner`: *string* ; `type`: user } & { `id`: *string* ; `version`: *string* } & { `alertId`: *string* \| *string*[] ; `index`: *string* \| *string*[] ; `owner`: *string* ; `rule`: { id: string \| null; name: string \| null; } ; `type`: alert \| generatedAlert } & { `id`: *string* ; `version`: *string* }
+• **updateRequest**: { `comment`: *string* ; `owner`: *string* ; `type`: user } & { `id`: *string* ; `version`: *string* } & { `alertId`: *string* \| *string*[] ; `index`: *string* \| *string*[] ; `owner`: *string* ; `rule`: { id: string \| null; name: string \| null; } ; `type`: alert \| generatedAlert } & { `id`: *string* ; `version`: *string* } & { `actions`: { targets: { hostname: string; endpointId: string; }[]; type: string; } ; `comment`: *string* ; `owner`: *string* ; `type`: actions } & { `id`: *string* ; `version`: *string* }
The full attachment request with the fields updated with appropriate values
-Defined in: [attachments/update.ts:36](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/attachments/update.ts#L36)
+Defined in: [attachments/update.ts:36](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/attachments/update.ts#L36)
diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/cases_client.casessubclient.md b/x-pack/plugins/cases/docs/cases_client/interfaces/cases_client.casessubclient.md
index 52cf2fbaf1ef12..45285066b46085 100644
--- a/x-pack/plugins/cases/docs/cases_client/interfaces/cases_client.casessubclient.md
+++ b/x-pack/plugins/cases/docs/cases_client/interfaces/cases_client.casessubclient.md
@@ -36,7 +36,7 @@ Creates a case.
**Returns:** *Promise*<[*ICaseResponse*](typedoc_interfaces.icaseresponse.md)\>
-Defined in: [cases/client.ts:49](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/cases/client.ts#L49)
+Defined in: [cases/client.ts:49](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/cases/client.ts#L49)
___
@@ -56,7 +56,7 @@ Delete a case and all its comments.
**Returns:** *Promise*
-Defined in: [cases/client.ts:73](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/cases/client.ts#L73)
+Defined in: [cases/client.ts:73](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/cases/client.ts#L73)
___
@@ -76,7 +76,7 @@ If the `owner` field is left empty then all the cases that the user has access t
**Returns:** *Promise*<[*ICasesFindResponse*](typedoc_interfaces.icasesfindresponse.md)\>
-Defined in: [cases/client.ts:55](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/cases/client.ts#L55)
+Defined in: [cases/client.ts:55](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/cases/client.ts#L55)
___
@@ -94,7 +94,7 @@ Retrieves a single case with the specified ID.
**Returns:** *Promise*<[*ICaseResponse*](typedoc_interfaces.icaseresponse.md)\>
-Defined in: [cases/client.ts:59](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/cases/client.ts#L59)
+Defined in: [cases/client.ts:59](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/cases/client.ts#L59)
___
@@ -112,7 +112,7 @@ Retrieves the cases ID and title that have the requested alert attached to them
**Returns:** *Promise*<{ `id`: *string* ; `title`: *string* }[]\>
-Defined in: [cases/client.ts:85](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/cases/client.ts#L85)
+Defined in: [cases/client.ts:85](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/cases/client.ts#L85)
___
@@ -131,7 +131,7 @@ Retrieves all the reporters across all accessible cases.
**Returns:** *Promise*<{ `email`: *undefined* \| ``null`` \| *string* ; `full_name`: *undefined* \| ``null`` \| *string* ; `username`: *undefined* \| ``null`` \| *string* }[]\>
-Defined in: [cases/client.ts:81](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/cases/client.ts#L81)
+Defined in: [cases/client.ts:81](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/cases/client.ts#L81)
___
@@ -150,7 +150,7 @@ Retrieves all the tags across all cases the user making the request has access t
**Returns:** *Promise*
-Defined in: [cases/client.ts:77](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/cases/client.ts#L77)
+Defined in: [cases/client.ts:77](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/cases/client.ts#L77)
___
@@ -168,7 +168,7 @@ Pushes a specific case to an external system.
**Returns:** *Promise*<[*ICaseResponse*](typedoc_interfaces.icaseresponse.md)\>
-Defined in: [cases/client.ts:63](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/cases/client.ts#L63)
+Defined in: [cases/client.ts:63](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/cases/client.ts#L63)
___
@@ -186,4 +186,4 @@ Update the specified cases with the passed in values.
**Returns:** *Promise*<[*ICasesResponse*](typedoc_interfaces.icasesresponse.md)\>
-Defined in: [cases/client.ts:67](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/cases/client.ts#L67)
+Defined in: [cases/client.ts:67](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/cases/client.ts#L67)
diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/cases_get.casesbyalertidparams.md b/x-pack/plugins/cases/docs/cases_client/interfaces/cases_get.casesbyalertidparams.md
index 4992ed035721be..257269ca645667 100644
--- a/x-pack/plugins/cases/docs/cases_client/interfaces/cases_get.casesbyalertidparams.md
+++ b/x-pack/plugins/cases/docs/cases_client/interfaces/cases_get.casesbyalertidparams.md
@@ -21,7 +21,7 @@ Parameters for finding cases IDs using an alert ID
The alert ID to search for
-Defined in: [cases/get.ts:44](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/cases/get.ts#L44)
+Defined in: [cases/get.ts:44](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/cases/get.ts#L44)
___
@@ -37,4 +37,4 @@ The filtering options when searching for associated cases.
| :------ | :------ |
| `owner` | *undefined* \| *string* \| *string*[] |
-Defined in: [cases/get.ts:48](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/cases/get.ts#L48)
+Defined in: [cases/get.ts:48](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/cases/get.ts#L48)
diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/cases_get.getparams.md b/x-pack/plugins/cases/docs/cases_client/interfaces/cases_get.getparams.md
index a4dfc7301e5434..16cc9527468181 100644
--- a/x-pack/plugins/cases/docs/cases_client/interfaces/cases_get.getparams.md
+++ b/x-pack/plugins/cases/docs/cases_client/interfaces/cases_get.getparams.md
@@ -22,7 +22,7 @@ The parameters for retrieving a case
Case ID
-Defined in: [cases/get.ts:145](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/cases/get.ts#L145)
+Defined in: [cases/get.ts:145](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/cases/get.ts#L145)
___
@@ -32,7 +32,7 @@ ___
Whether to include the attachments for a case in the response
-Defined in: [cases/get.ts:149](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/cases/get.ts#L149)
+Defined in: [cases/get.ts:149](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/cases/get.ts#L149)
___
@@ -42,4 +42,4 @@ ___
Whether to include the attachments for all children of a case in the response
-Defined in: [cases/get.ts:153](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/cases/get.ts#L153)
+Defined in: [cases/get.ts:153](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/cases/get.ts#L153)
diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/cases_push.pushparams.md b/x-pack/plugins/cases/docs/cases_client/interfaces/cases_push.pushparams.md
index 0ed510700af8af..3aa6ee77941a8f 100644
--- a/x-pack/plugins/cases/docs/cases_client/interfaces/cases_push.pushparams.md
+++ b/x-pack/plugins/cases/docs/cases_client/interfaces/cases_push.pushparams.md
@@ -21,7 +21,7 @@ Parameters for pushing a case to an external system
The ID of a case
-Defined in: [cases/push.ts:53](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/cases/push.ts#L53)
+Defined in: [cases/push.ts:53](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/cases/push.ts#L53)
___
@@ -31,4 +31,4 @@ ___
The ID of an external system to push to
-Defined in: [cases/push.ts:57](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/cases/push.ts#L57)
+Defined in: [cases/push.ts:57](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/cases/push.ts#L57)
diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/configure_client.configuresubclient.md b/x-pack/plugins/cases/docs/cases_client/interfaces/configure_client.configuresubclient.md
index 98a6c3a2fcbbf3..f94e7d6c7f49e5 100644
--- a/x-pack/plugins/cases/docs/cases_client/interfaces/configure_client.configuresubclient.md
+++ b/x-pack/plugins/cases/docs/cases_client/interfaces/configure_client.configuresubclient.md
@@ -31,7 +31,7 @@ Creates a configuration if one does not already exist. If one exists it is delet
**Returns:** *Promise*<[*ICasesConfigureResponse*](typedoc_interfaces.icasesconfigureresponse.md)\>
-Defined in: [configure/client.ts:98](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/configure/client.ts#L98)
+Defined in: [configure/client.ts:98](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/configure/client.ts#L98)
___
@@ -50,7 +50,7 @@ Retrieves the external connector configuration for a particular case owner.
**Returns:** *Promise*<{} \| [*ICasesConfigureResponse*](typedoc_interfaces.icasesconfigureresponse.md)\>
-Defined in: [configure/client.ts:80](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/configure/client.ts#L80)
+Defined in: [configure/client.ts:80](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/configure/client.ts#L80)
___
@@ -62,7 +62,7 @@ Retrieves the valid external connectors supported by the cases plugin.
**Returns:** *Promise*
-Defined in: [configure/client.ts:84](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/configure/client.ts#L84)
+Defined in: [configure/client.ts:84](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/configure/client.ts#L84)
___
@@ -81,4 +81,4 @@ Updates a particular configuration with new values.
**Returns:** *Promise*<[*ICasesConfigureResponse*](typedoc_interfaces.icasesconfigureresponse.md)\>
-Defined in: [configure/client.ts:91](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/configure/client.ts#L91)
+Defined in: [configure/client.ts:91](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/configure/client.ts#L91)
diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/stats_client.statssubclient.md b/x-pack/plugins/cases/docs/cases_client/interfaces/stats_client.statssubclient.md
index cc0f30055597d2..9bbf3cdaf015fd 100644
--- a/x-pack/plugins/cases/docs/cases_client/interfaces/stats_client.statssubclient.md
+++ b/x-pack/plugins/cases/docs/cases_client/interfaces/stats_client.statssubclient.md
@@ -29,4 +29,4 @@ Retrieves the total number of open, closed, and in-progress cases.
**Returns:** *Promise*<{ `count_closed_cases`: *number* ; `count_in_progress_cases`: *number* ; `count_open_cases`: *number* }\>
-Defined in: [stats/client.ts:34](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/stats/client.ts#L34)
+Defined in: [stats/client.ts:34](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/stats/client.ts#L34)
diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/sub_cases_client.subcasesclient.md b/x-pack/plugins/cases/docs/cases_client/interfaces/sub_cases_client.subcasesclient.md
index 5c0369709c0f0d..e3ca5a756f8a7d 100644
--- a/x-pack/plugins/cases/docs/cases_client/interfaces/sub_cases_client.subcasesclient.md
+++ b/x-pack/plugins/cases/docs/cases_client/interfaces/sub_cases_client.subcasesclient.md
@@ -31,7 +31,7 @@ Deletes the specified entities and their attachments.
**Returns:** *Promise*
-Defined in: [sub_cases/client.ts:68](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/sub_cases/client.ts#L68)
+Defined in: [sub_cases/client.ts:68](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/sub_cases/client.ts#L68)
___
@@ -49,7 +49,7 @@ Retrieves the sub cases matching the search criteria.
**Returns:** *Promise*<[*ISubCasesFindResponse*](typedoc_interfaces.isubcasesfindresponse.md)\>
-Defined in: [sub_cases/client.ts:72](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/sub_cases/client.ts#L72)
+Defined in: [sub_cases/client.ts:72](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/sub_cases/client.ts#L72)
___
@@ -67,7 +67,7 @@ Retrieves a single sub case.
**Returns:** *Promise*<[*ISubCaseResponse*](typedoc_interfaces.isubcaseresponse.md)\>
-Defined in: [sub_cases/client.ts:76](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/sub_cases/client.ts#L76)
+Defined in: [sub_cases/client.ts:76](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/sub_cases/client.ts#L76)
___
@@ -86,4 +86,4 @@ Updates the specified sub cases to the new values included in the request.
**Returns:** *Promise*<[*ISubCasesResponse*](typedoc_interfaces.isubcasesresponse.md)\>
-Defined in: [sub_cases/client.ts:80](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/sub_cases/client.ts#L80)
+Defined in: [sub_cases/client.ts:80](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/sub_cases/client.ts#L80)
diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icasepostrequest.md b/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icasepostrequest.md
index 70533a15fe6167..5f9189ea41e483 100644
--- a/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icasepostrequest.md
+++ b/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icasepostrequest.md
@@ -29,7 +29,7 @@ the docs are huge.
### connector
-• **connector**: { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { issueType: string \| null; priority: string \| null; parent: string \| null; } ; `type`: jira } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { incidentTypes: string[] \| null; severityCode: string \| null; } ; `type`: resilient } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { impact: string \| null; severity: string \| null; urgency: string \| null; category: string \| null; subcategory: string \| null; } ; `type`: serviceNowITSM } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { category: string \| null; destIp: boolean \| null; malwareHash: boolean \| null; malwareUrl: boolean \| null; priority: string \| null; sourceIp: boolean \| null; subcategory: string \| null; } ; `type`: serviceNowSIR } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` ; `type`: none }
+• **connector**: { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { issueType: string \| null; priority: string \| null; parent: string \| null; } ; `type`: jira } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` ; `type`: none } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { incidentTypes: string[] \| null; severityCode: string \| null; } ; `type`: resilient } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { impact: string \| null; severity: string \| null; urgency: string \| null; category: string \| null; subcategory: string \| null; } ; `type`: serviceNowITSM } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { category: string \| null; destIp: boolean \| null; malwareHash: boolean \| null; malwareUrl: boolean \| null; priority: string \| null; sourceIp: boolean \| null; subcategory: string \| null; } ; `type`: serviceNowSIR } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { caseId: string \| null; } ; `type`: swimlane }
Inherited from: CasePostRequest.connector
diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icaseresponse.md b/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icaseresponse.md
index 5db55e5552473c..dc591a508844bd 100644
--- a/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icaseresponse.md
+++ b/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icaseresponse.md
@@ -57,7 +57,7 @@ ___
### comments
-• **comments**: *undefined* \| { `comment`: *string* ; `owner`: *string* ; `type`: user } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* } & { `alertId`: *string* \| *string*[] ; `index`: *string* \| *string*[] ; `owner`: *string* ; `rule`: { id: string \| null; name: string \| null; } ; `type`: alert \| generatedAlert } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* }[]
+• **comments**: *undefined* \| { `comment`: *string* ; `owner`: *string* ; `type`: user } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* } & { `alertId`: *string* \| *string*[] ; `index`: *string* \| *string*[] ; `owner`: *string* ; `rule`: { id: string \| null; name: string \| null; } ; `type`: alert \| generatedAlert } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* } & { `actions`: { targets: { hostname: string; endpointId: string; }[]; type: string; } ; `comment`: *string* ; `owner`: *string* ; `type`: actions } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* }[]
Inherited from: CaseResponse.comments
@@ -65,7 +65,7 @@ ___
### connector
-• **connector**: { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { issueType: string \| null; priority: string \| null; parent: string \| null; } ; `type`: jira } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { incidentTypes: string[] \| null; severityCode: string \| null; } ; `type`: resilient } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { impact: string \| null; severity: string \| null; urgency: string \| null; category: string \| null; subcategory: string \| null; } ; `type`: serviceNowITSM } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { category: string \| null; destIp: boolean \| null; malwareHash: boolean \| null; malwareUrl: boolean \| null; priority: string \| null; sourceIp: boolean \| null; subcategory: string \| null; } ; `type`: serviceNowSIR } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` ; `type`: none }
+• **connector**: { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { issueType: string \| null; priority: string \| null; parent: string \| null; } ; `type`: jira } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` ; `type`: none } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { incidentTypes: string[] \| null; severityCode: string \| null; } ; `type`: resilient } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { impact: string \| null; severity: string \| null; urgency: string \| null; category: string \| null; subcategory: string \| null; } ; `type`: serviceNowITSM } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { category: string \| null; destIp: boolean \| null; malwareHash: boolean \| null; malwareUrl: boolean \| null; priority: string \| null; sourceIp: boolean \| null; subcategory: string \| null; } ; `type`: serviceNowSIR } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { caseId: string \| null; } ; `type`: swimlane }
Inherited from: CaseResponse.connector
@@ -159,7 +159,7 @@ ___
### subCases
-• **subCases**: *undefined* \| { `status`: CaseStatuses } & { `closed_at`: ``null`` \| *string* ; `closed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `created_at`: *string* ; `created_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `totalAlerts`: *number* ; `totalComment`: *number* ; `version`: *string* } & { `comments`: *undefined* \| { `comment`: *string* ; `owner`: *string* ; `type`: user } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* } & { `alertId`: *string* \| *string*[] ; `index`: *string* \| *string*[] ; `owner`: *string* ; `rule`: { id: string \| null; name: string \| null; } ; `type`: alert \| generatedAlert } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* }[] }[]
+• **subCases**: *undefined* \| { `status`: CaseStatuses } & { `closed_at`: ``null`` \| *string* ; `closed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `created_at`: *string* ; `created_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `totalAlerts`: *number* ; `totalComment`: *number* ; `version`: *string* } & { `comments`: *undefined* \| { `comment`: *string* ; `owner`: *string* ; `type`: user } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* } & { `alertId`: *string* \| *string*[] ; `index`: *string* \| *string*[] ; `owner`: *string* ; `rule`: { id: string \| null; name: string \| null; } ; `type`: alert \| generatedAlert } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* } & { `actions`: { targets: { hostname: string; endpointId: string; }[]; type: string; } ; `comment`: *string* ; `owner`: *string* ; `type`: actions } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* }[] }[]
Inherited from: CaseResponse.subCases
diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icasesconfigurepatch.md b/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icasesconfigurepatch.md
index 3854fda03fb6ac..9ab5341a2dbc6c 100644
--- a/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icasesconfigurepatch.md
+++ b/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icasesconfigurepatch.md
@@ -30,7 +30,7 @@ ___
### connector
-• **connector**: *undefined* \| { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { issueType: string \| null; priority: string \| null; parent: string \| null; } ; `type`: jira } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { incidentTypes: string[] \| null; severityCode: string \| null; } ; `type`: resilient } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { impact: string \| null; severity: string \| null; urgency: string \| null; category: string \| null; subcategory: string \| null; } ; `type`: serviceNowITSM } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { category: string \| null; destIp: boolean \| null; malwareHash: boolean \| null; malwareUrl: boolean \| null; priority: string \| null; sourceIp: boolean \| null; subcategory: string \| null; } ; `type`: serviceNowSIR } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` ; `type`: none }
+• **connector**: *undefined* \| { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { issueType: string \| null; priority: string \| null; parent: string \| null; } ; `type`: jira } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` ; `type`: none } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { incidentTypes: string[] \| null; severityCode: string \| null; } ; `type`: resilient } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { impact: string \| null; severity: string \| null; urgency: string \| null; category: string \| null; subcategory: string \| null; } ; `type`: serviceNowITSM } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { category: string \| null; destIp: boolean \| null; malwareHash: boolean \| null; malwareUrl: boolean \| null; priority: string \| null; sourceIp: boolean \| null; subcategory: string \| null; } ; `type`: serviceNowSIR } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { caseId: string \| null; } ; `type`: swimlane }
Inherited from: CasesConfigurePatch.connector
diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icasesconfigurerequest.md b/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icasesconfigurerequest.md
index 548e1a5c48f587..0b1c11ac548a61 100644
--- a/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icasesconfigurerequest.md
+++ b/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icasesconfigurerequest.md
@@ -30,7 +30,7 @@ ___
### connector
-• **connector**: { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { issueType: string \| null; priority: string \| null; parent: string \| null; } ; `type`: jira } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { incidentTypes: string[] \| null; severityCode: string \| null; } ; `type`: resilient } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { impact: string \| null; severity: string \| null; urgency: string \| null; category: string \| null; subcategory: string \| null; } ; `type`: serviceNowITSM } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { category: string \| null; destIp: boolean \| null; malwareHash: boolean \| null; malwareUrl: boolean \| null; priority: string \| null; sourceIp: boolean \| null; subcategory: string \| null; } ; `type`: serviceNowSIR } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` ; `type`: none }
+• **connector**: { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { issueType: string \| null; priority: string \| null; parent: string \| null; } ; `type`: jira } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` ; `type`: none } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { incidentTypes: string[] \| null; severityCode: string \| null; } ; `type`: resilient } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { impact: string \| null; severity: string \| null; urgency: string \| null; category: string \| null; subcategory: string \| null; } ; `type`: serviceNowITSM } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { category: string \| null; destIp: boolean \| null; malwareHash: boolean \| null; malwareUrl: boolean \| null; priority: string \| null; sourceIp: boolean \| null; subcategory: string \| null; } ; `type`: serviceNowSIR } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { caseId: string \| null; } ; `type`: swimlane }
Inherited from: CasesConfigureRequest.connector
diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icasesconfigureresponse.md b/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icasesconfigureresponse.md
index c493a4c6c0f0c4..42c7378431c1b9 100644
--- a/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icasesconfigureresponse.md
+++ b/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icasesconfigureresponse.md
@@ -38,7 +38,7 @@ ___
### connector
-• **connector**: { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { issueType: string \| null; priority: string \| null; parent: string \| null; } ; `type`: jira } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { incidentTypes: string[] \| null; severityCode: string \| null; } ; `type`: resilient } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { impact: string \| null; severity: string \| null; urgency: string \| null; category: string \| null; subcategory: string \| null; } ; `type`: serviceNowITSM } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { category: string \| null; destIp: boolean \| null; malwareHash: boolean \| null; malwareUrl: boolean \| null; priority: string \| null; sourceIp: boolean \| null; subcategory: string \| null; } ; `type`: serviceNowSIR } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` ; `type`: none }
+• **connector**: { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { issueType: string \| null; priority: string \| null; parent: string \| null; } ; `type`: jira } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` ; `type`: none } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { incidentTypes: string[] \| null; severityCode: string \| null; } ; `type`: resilient } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { impact: string \| null; severity: string \| null; urgency: string \| null; category: string \| null; subcategory: string \| null; } ; `type`: serviceNowITSM } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { category: string \| null; destIp: boolean \| null; malwareHash: boolean \| null; malwareUrl: boolean \| null; priority: string \| null; sourceIp: boolean \| null; subcategory: string \| null; } ; `type`: serviceNowSIR } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { caseId: string \| null; } ; `type`: swimlane }
Inherited from: CasesConfigureResponse.connector
diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icasesfindresponse.md b/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icasesfindresponse.md
index 9be5fd5743a8ee..06e14d219cde0e 100644
--- a/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icasesfindresponse.md
+++ b/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icasesfindresponse.md
@@ -26,7 +26,7 @@
### cases
-• **cases**: { `connector`: { id: string; name: string; } & { type: ConnectorTypes.jira; fields: { issueType: string \| null; priority: string \| null; parent: string \| null; } \| null; } & { id: string; name: string; } & { type: ConnectorTypes.resilient; fields: { incidentTypes: string[] \| null; severityCode: string \| null; } \| null; } & { id: string; name: string; } & { type: ConnectorTypes.serviceNowITSM; fields: { impact: string \| null; severity: string \| null; urgency: string \| null; category: string \| null; subcategory: string \| null; } \| null; } & { id: string; name: string; } & { type: ConnectorTypes.serviceNowSIR; fields: { category: string \| null; destIp: boolean \| null; malwareHash: boolean \| null; malwareUrl: boolean \| null; priority: string \| null; sourceIp: boolean \| null; subcategory: string \| null; } \| null; } & { id: string; name: string; } & { type: ConnectorTypes.none; fields: null; } ; `description`: *string* ; `owner`: *string* ; `settings`: { syncAlerts: boolean; } ; `status`: CaseStatuses ; `tags`: *string*[] ; `title`: *string* ; `type`: CaseType } & { `closed_at`: ``null`` \| *string* ; `closed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `external_service`: ``null`` \| { connector\_id: string; connector\_name: string; external\_id: string; external\_title: string; external\_url: string; } & { pushed\_at: string; pushed\_by: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; }; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `totalAlerts`: *number* ; `totalComment`: *number* ; `version`: *string* } & { `comments`: *undefined* \| { `comment`: *string* ; `owner`: *string* ; `type`: user } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* } & { `alertId`: *string* \| *string*[] ; `index`: *string* \| *string*[] ; `owner`: *string* ; `rule`: { id: string \| null; name: string \| null; } ; `type`: alert \| generatedAlert } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* }[] ; `subCaseIds`: *undefined* \| *string*[] ; `subCases`: *undefined* \| { `status`: CaseStatuses } & { `closed_at`: ``null`` \| *string* ; `closed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `created_at`: *string* ; `created_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `totalAlerts`: *number* ; `totalComment`: *number* ; `version`: *string* } & { comments?: ((({ comment: string; type: CommentType.user; owner: string; } & { associationType: AssociationType; created\_at: string; created\_by: { email: string \| null \| undefined; full\_name: string \| ... 1 more ... \| undefined; username: string \| ... 1 more ... \| undefined; }; ... 4 more ...; updated\_by: { ...; } ...[] }[]
+• **cases**: { `connector`: { id: string; name: string; } & { type: ConnectorTypes.jira; fields: { issueType: string \| null; priority: string \| null; parent: string \| null; } \| null; } & { id: string; name: string; } & { type: ConnectorTypes.none; fields: null; } & { id: string; name: string; } & { type: ConnectorTypes.resilient; fields: { incidentTypes: string[] \| null; severityCode: string \| null; } \| null; } & { id: string; name: string; } & { type: ConnectorTypes.serviceNowITSM; fields: { impact: string \| null; severity: string \| null; urgency: string \| null; category: string \| null; subcategory: string \| null; } \| null; } & { id: string; name: string; } & { type: ConnectorTypes.serviceNowSIR; fields: { category: string \| null; destIp: boolean \| null; malwareHash: boolean \| null; malwareUrl: boolean \| null; priority: string \| null; sourceIp: boolean \| null; subcategory: string \| null; } \| null; } & { id: string; name: string; } & { type: ConnectorTypes.swimlane; fields: { caseId: string \| null; } \| null; } ; `description`: *string* ; `owner`: *string* ; `settings`: { syncAlerts: boolean; } ; `status`: CaseStatuses ; `tags`: *string*[] ; `title`: *string* ; `type`: CaseType } & { `closed_at`: ``null`` \| *string* ; `closed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `external_service`: ``null`` \| { connector\_id: string; connector\_name: string; external\_id: string; external\_title: string; external\_url: string; } & { pushed\_at: string; pushed\_by: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; }; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `totalAlerts`: *number* ; `totalComment`: *number* ; `version`: *string* } & { `comments`: *undefined* \| { `comment`: *string* ; `owner`: *string* ; `type`: user } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* } & { `alertId`: *string* \| *string*[] ; `index`: *string* \| *string*[] ; `owner`: *string* ; `rule`: { id: string \| null; name: string \| null; } ; `type`: alert \| generatedAlert } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* } & { `actions`: { targets: { hostname: string; endpointId: string; }[]; type: string; } ; `comment`: *string* ; `owner`: *string* ; `type`: actions } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* }[] ; `subCaseIds`: *undefined* \| *string*[] ; `subCases`: *undefined* \| { `status`: CaseStatuses } & { `closed_at`: ``null`` \| *string* ; `closed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `created_at`: *string* ; `created_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `totalAlerts`: *number* ; `totalComment`: *number* ; `version`: *string* } & { comments?: ((({ comment: string; type: CommentType.user; owner: string; } & { associationType: AssociationType; created\_at: string; created\_by: { email: string \| null \| undefined; full\_name: string \| ... 1 more ... \| undefined; username: string \| ... 1 more ... \| undefined; }; ... 4 more ...; updated\_by: { ...; } ...[] }[]
Inherited from: CasesFindResponse.cases
diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icasespatchrequest.md b/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icasespatchrequest.md
index bfdb3b7315e554..d4747a1836cc42 100644
--- a/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icasespatchrequest.md
+++ b/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icasespatchrequest.md
@@ -20,6 +20,6 @@
### cases
-• **cases**: { `connector`: *undefined* \| { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { issueType: string \| null; priority: string \| null; parent: string \| null; } ; `type`: jira } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { incidentTypes: string[] \| null; severityCode: string \| null; } ; `type`: resilient } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { impact: string \| null; severity: string \| null; urgency: string \| null; category: string \| null; subcategory: string \| null; } ; `type`: serviceNowITSM } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { category: string \| null; destIp: boolean \| null; malwareHash: boolean \| null; malwareUrl: boolean \| null; priority: string \| null; sourceIp: boolean \| null; subcategory: string \| null; } ; `type`: serviceNowSIR } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` ; `type`: none } ; `description`: *undefined* \| *string* ; `owner`: *undefined* \| *string* ; `settings`: *undefined* \| { `syncAlerts`: *boolean* } ; `status`: *undefined* \| open \| *any*[*any*] \| closed ; `tags`: *undefined* \| *string*[] ; `title`: *undefined* \| *string* ; `type`: *undefined* \| collection \| individual } & { `id`: *string* ; `version`: *string* }[]
+• **cases**: { `connector`: *undefined* \| { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { issueType: string \| null; priority: string \| null; parent: string \| null; } ; `type`: jira } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` ; `type`: none } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { incidentTypes: string[] \| null; severityCode: string \| null; } ; `type`: resilient } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { impact: string \| null; severity: string \| null; urgency: string \| null; category: string \| null; subcategory: string \| null; } ; `type`: serviceNowITSM } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { category: string \| null; destIp: boolean \| null; malwareHash: boolean \| null; malwareUrl: boolean \| null; priority: string \| null; sourceIp: boolean \| null; subcategory: string \| null; } ; `type`: serviceNowSIR } & { `id`: *string* ; `name`: *string* } & { `fields`: ``null`` \| { caseId: string \| null; } ; `type`: swimlane } ; `description`: *undefined* \| *string* ; `owner`: *undefined* \| *string* ; `settings`: *undefined* \| { `syncAlerts`: *boolean* } ; `status`: *undefined* \| open \| *any*[*any*] \| closed ; `tags`: *undefined* \| *string*[] ; `title`: *undefined* \| *string* ; `type`: *undefined* \| collection \| individual } & { `id`: *string* ; `version`: *string* }[]
Inherited from: CasesPatchRequest.cases
diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icommentsresponse.md b/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icommentsresponse.md
index d34480b2c633cf..4a720e2c6d9be7 100644
--- a/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icommentsresponse.md
+++ b/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.icommentsresponse.md
@@ -23,7 +23,7 @@
### comments
-• **comments**: { `comment`: *string* ; `owner`: *string* ; `type`: user } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* } & { `alertId`: *string* \| *string*[] ; `index`: *string* \| *string*[] ; `owner`: *string* ; `rule`: { id: string \| null; name: string \| null; } ; `type`: alert \| generatedAlert } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* }[]
+• **comments**: { `comment`: *string* ; `owner`: *string* ; `type`: user } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* } & { `alertId`: *string* \| *string*[] ; `index`: *string* \| *string*[] ; `owner`: *string* ; `rule`: { id: string \| null; name: string \| null; } ; `type`: alert \| generatedAlert } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* } & { `actions`: { targets: { hostname: string; endpointId: string; }[]; type: string; } ; `comment`: *string* ; `owner`: *string* ; `type`: actions } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* }[]
Inherited from: CommentsResponse.comments
diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.isubcaseresponse.md b/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.isubcaseresponse.md
index b33b280d2e7533..a6bf610f863494 100644
--- a/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.isubcaseresponse.md
+++ b/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.isubcaseresponse.md
@@ -48,7 +48,7 @@ ___
### comments
-• **comments**: *undefined* \| { `comment`: *string* ; `owner`: *string* ; `type`: user } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* } & { `alertId`: *string* \| *string*[] ; `index`: *string* \| *string*[] ; `owner`: *string* ; `rule`: { id: string \| null; name: string \| null; } ; `type`: alert \| generatedAlert } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* }[]
+• **comments**: *undefined* \| { `comment`: *string* ; `owner`: *string* ; `type`: user } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* } & { `alertId`: *string* \| *string*[] ; `index`: *string* \| *string*[] ; `owner`: *string* ; `rule`: { id: string \| null; name: string \| null; } ; `type`: alert \| generatedAlert } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* } & { `actions`: { targets: { hostname: string; endpointId: string; }[]; type: string; } ; `comment`: *string* ; `owner`: *string* ; `type`: actions } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* }[]
Inherited from: SubCaseResponse.comments
diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.isubcasesfindresponse.md b/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.isubcasesfindresponse.md
index 35d63126f608a1..61fb60a54db00a 100644
--- a/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.isubcasesfindresponse.md
+++ b/x-pack/plugins/cases/docs/cases_client/interfaces/typedoc_interfaces.isubcasesfindresponse.md
@@ -66,7 +66,7 @@ ___
### subCases
-• **subCases**: { `status`: CaseStatuses } & { `closed_at`: ``null`` \| *string* ; `closed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `created_at`: *string* ; `created_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `totalAlerts`: *number* ; `totalComment`: *number* ; `version`: *string* } & { `comments`: *undefined* \| { `comment`: *string* ; `owner`: *string* ; `type`: user } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* } & { `alertId`: *string* \| *string*[] ; `index`: *string* \| *string*[] ; `owner`: *string* ; `rule`: { id: string \| null; name: string \| null; } ; `type`: alert \| generatedAlert } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* }[] }[]
+• **subCases**: { `status`: CaseStatuses } & { `closed_at`: ``null`` \| *string* ; `closed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `created_at`: *string* ; `created_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `totalAlerts`: *number* ; `totalComment`: *number* ; `version`: *string* } & { `comments`: *undefined* \| { `comment`: *string* ; `owner`: *string* ; `type`: user } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* } & { `alertId`: *string* \| *string*[] ; `index`: *string* \| *string*[] ; `owner`: *string* ; `rule`: { id: string \| null; name: string \| null; } ; `type`: alert \| generatedAlert } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* } & { `actions`: { targets: { hostname: string; endpointId: string; }[]; type: string; } ; `comment`: *string* ; `owner`: *string* ; `type`: actions } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* }[] }[]
Inherited from: SubCasesFindResponse.subCases
diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/user_actions_client.useractionget.md b/x-pack/plugins/cases/docs/cases_client/interfaces/user_actions_client.useractionget.md
index 5f0cc89239fd8a..1cbebef379dbdd 100644
--- a/x-pack/plugins/cases/docs/cases_client/interfaces/user_actions_client.useractionget.md
+++ b/x-pack/plugins/cases/docs/cases_client/interfaces/user_actions_client.useractionget.md
@@ -21,7 +21,7 @@ Parameters for retrieving user actions for a particular case
The ID of the case
-Defined in: [user_actions/client.ts:19](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/user_actions/client.ts#L19)
+Defined in: [user_actions/client.ts:19](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/user_actions/client.ts#L19)
___
@@ -31,4 +31,4 @@ ___
If specified then a sub case will be used for finding all the user actions
-Defined in: [user_actions/client.ts:23](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/user_actions/client.ts#L23)
+Defined in: [user_actions/client.ts:23](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/user_actions/client.ts#L23)
diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/user_actions_client.useractionssubclient.md b/x-pack/plugins/cases/docs/cases_client/interfaces/user_actions_client.useractionssubclient.md
index df2641adf5a8c4..065f20b4cefcb1 100644
--- a/x-pack/plugins/cases/docs/cases_client/interfaces/user_actions_client.useractionssubclient.md
+++ b/x-pack/plugins/cases/docs/cases_client/interfaces/user_actions_client.useractionssubclient.md
@@ -28,4 +28,4 @@ Retrieves all user actions for a particular case.
**Returns:** *Promise*<[*ICaseUserActionsResponse*](typedoc_interfaces.icaseuseractionsresponse.md)\>
-Defined in: [user_actions/client.ts:33](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/user_actions/client.ts#L33)
+Defined in: [user_actions/client.ts:33](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/user_actions/client.ts#L33)
diff --git a/x-pack/plugins/cases/docs/cases_client/modules/cases_get.md b/x-pack/plugins/cases/docs/cases_client/modules/cases_get.md
index d4ca13501294a1..4c165866cec476 100644
--- a/x-pack/plugins/cases/docs/cases_client/modules/cases_get.md
+++ b/x-pack/plugins/cases/docs/cases_client/modules/cases_get.md
@@ -31,7 +31,7 @@ Retrieves the reporters from all the cases.
**Returns:** *Promise*
-Defined in: [cases/get.ts:290](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/cases/get.ts#L290)
+Defined in: [cases/get.ts:289](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/cases/get.ts#L289)
___
@@ -50,4 +50,4 @@ Retrieves the tags from all the cases.
**Returns:** *Promise*
-Defined in: [cases/get.ts:240](https://github.com/jonathan-buttner/kibana/blob/b65ed845242/x-pack/plugins/cases/server/client/cases/get.ts#L240)
+Defined in: [cases/get.ts:239](https://github.com/elastic/kibana/blob/a80791aa4cc/x-pack/plugins/cases/server/client/cases/get.ts#L239)
diff --git a/x-pack/plugins/cases/public/components/__mock__/form.ts b/x-pack/plugins/cases/public/components/__mock__/form.ts
index 6d3e8353e630ae..aa40ea0421b4c3 100644
--- a/x-pack/plugins/cases/public/components/__mock__/form.ts
+++ b/x-pack/plugins/cases/public/components/__mock__/form.ts
@@ -23,6 +23,7 @@ export const mockFormHook = {
setFieldErrors: jest.fn(),
getFields: jest.fn(),
getFormData: jest.fn(),
+ getFieldDefaultValue: jest.fn(),
/* Returns a list of all errors in the form */
getErrors: jest.fn(),
reset: jest.fn(),
@@ -33,7 +34,6 @@ export const mockFormHook = {
__validateFields: jest.fn(),
__updateFormDataAt: jest.fn(),
__readFieldConfigFromSchema: jest.fn(),
- __getFieldDefaultValue: jest.fn(),
};
export const getFormMock = (sampleData: any) => ({
diff --git a/x-pack/plugins/cases/public/components/user_action_tree/index.test.tsx b/x-pack/plugins/cases/public/components/user_action_tree/index.test.tsx
index 610399c31928b7..be1516843184d1 100644
--- a/x-pack/plugins/cases/public/components/user_action_tree/index.test.tsx
+++ b/x-pack/plugins/cases/public/components/user_action_tree/index.test.tsx
@@ -13,7 +13,14 @@ import routeData from 'react-router';
import { getFormMock, useFormMock, useFormDataMock } from '../__mock__/form';
import { useUpdateComment } from '../../containers/use_update_comment';
-import { basicCase, basicPush, getUserAction } from '../../containers/mock';
+import {
+ basicCase,
+ basicPush,
+ getUserAction,
+ getHostIsolationUserAction,
+ hostIsolationComment,
+ hostReleaseComment,
+} from '../../containers/mock';
import { UserActionTree } from '.';
import { TestProviders } from '../../common/mock';
import { Ecs } from '../../../common';
@@ -368,4 +375,82 @@ describe(`UserActionTree`, () => {
).toEqual(true);
});
});
+ describe('Host isolation action', () => {
+ it('renders in the cases details view', async () => {
+ const isolateAction = [getHostIsolationUserAction()];
+ const props = {
+ ...defaultProps,
+ caseUserActions: isolateAction,
+ data: { ...defaultProps.data, comments: [...basicCase.comments, hostIsolationComment()] },
+ };
+
+ const wrapper = mount(
+
+
+
+ );
+ await waitFor(() => {
+ expect(wrapper.find(`[data-test-subj="endpoint-action"]`).exists()).toBe(true);
+ });
+ });
+
+ it('shows the correct username', async () => {
+ const isolateAction = [getHostIsolationUserAction()];
+ const props = {
+ ...defaultProps,
+ caseUserActions: isolateAction,
+ data: { ...defaultProps.data, comments: [hostIsolationComment()] },
+ };
+
+ const wrapper = mount(
+
+
+
+ );
+ await waitFor(() => {
+ expect(wrapper.find(`[data-test-subj="user-action-avatar"]`).first().prop('name')).toEqual(
+ defaultProps.data.createdBy.fullName
+ );
+ });
+ });
+
+ it('shows a lock icon if the action is isolate', async () => {
+ const isolateAction = [getHostIsolationUserAction()];
+ const props = {
+ ...defaultProps,
+ caseUserActions: isolateAction,
+ data: { ...defaultProps.data, comments: [hostIsolationComment()] },
+ };
+
+ const wrapper = mount(
+
+
+
+ );
+ await waitFor(() => {
+ expect(
+ wrapper.find(`[data-test-subj="endpoint-action"]`).first().prop('timelineIcon')
+ ).toBe('lock');
+ });
+ });
+ it('shows a lockOpen icon if the action is unisolate/release', async () => {
+ const isolateAction = [getHostIsolationUserAction()];
+ const props = {
+ ...defaultProps,
+ caseUserActions: isolateAction,
+ data: { ...defaultProps.data, comments: [hostReleaseComment()] },
+ };
+
+ const wrapper = mount(
+
+
+
+ );
+ await waitFor(() => {
+ expect(
+ wrapper.find(`[data-test-subj="endpoint-action"]`).first().prop('timelineIcon')
+ ).toBe('lockOpen');
+ });
+ });
+ });
});
diff --git a/x-pack/plugins/cases/public/components/user_action_tree/user_action_host_isolation_comment_event.test.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_host_isolation_comment_event.test.tsx
new file mode 100644
index 00000000000000..636cd7e40aac12
--- /dev/null
+++ b/x-pack/plugins/cases/public/components/user_action_tree/user_action_host_isolation_comment_event.test.tsx
@@ -0,0 +1,42 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { mount } from 'enzyme';
+import { HostIsolationCommentEvent } from './user_action_host_isolation_comment_event';
+
+const defaultProps = () => {
+ return {
+ type: 'isolate',
+ endpoints: [{ endpointId: 'e1', hostname: 'host1' }],
+ href: jest.fn(),
+ onClick: jest.fn(),
+ };
+};
+
+describe('UserActionHostIsolationCommentEvent', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('renders with the correct action and hostname', async () => {
+ const wrapper = mount( );
+ expect(wrapper.find(`[data-test-subj="actions-link-e1"]`).first().exists()).toBeTruthy();
+ expect(wrapper.text()).toBe('isolated host host1');
+ });
+
+ it('navigates to app on link click', async () => {
+ const onActionsLinkClick = jest.fn();
+
+ const wrapper = mount(
+
+ );
+
+ wrapper.find(`[data-test-subj="actions-link-e1"]`).first().simulate('click');
+ expect(onActionsLinkClick).toHaveBeenCalled();
+ });
+});
diff --git a/x-pack/plugins/cases/public/components/user_action_tree/user_action_host_isolation_comment_event.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_host_isolation_comment_event.tsx
index d363e874a4e0dd..2381d31b3ada88 100644
--- a/x-pack/plugins/cases/public/components/user_action_tree/user_action_host_isolation_comment_event.tsx
+++ b/x-pack/plugins/cases/public/components/user_action_tree/user_action_host_isolation_comment_event.tsx
@@ -44,7 +44,7 @@ const HostIsolationCommentEventComponent: React.FC = ({
{endpoints[0].hostname}
diff --git a/x-pack/plugins/cases/public/containers/mock.ts b/x-pack/plugins/cases/public/containers/mock.ts
index a900010235c9f1..c955bb34240e28 100644
--- a/x-pack/plugins/cases/public/containers/mock.ts
+++ b/x-pack/plugins/cases/public/containers/mock.ts
@@ -76,6 +76,58 @@ export const alertComment: Comment = {
version: 'WzQ3LDFc',
};
+export const hostIsolationComment: () => Comment = () => {
+ return {
+ type: CommentType.actions,
+ comment: 'I just isolated the host!',
+ id: 'isolate-comment-id',
+ actions: {
+ targets: [
+ {
+ hostname: 'host1',
+ endpointId: '001',
+ },
+ ],
+ type: 'isolate',
+ },
+ associationType: AssociationType.case,
+ createdAt: basicCreatedAt,
+ createdBy: elasticUser,
+ owner: SECURITY_SOLUTION_OWNER,
+ pushedAt: null,
+ pushedBy: null,
+ updatedAt: null,
+ updatedBy: null,
+ version: 'WzQ3LDFc',
+ };
+};
+
+export const hostReleaseComment: () => Comment = () => {
+ return {
+ type: CommentType.actions,
+ comment: 'I just released the host!',
+ id: 'isolate-comment-id',
+ actions: {
+ targets: [
+ {
+ hostname: 'host1',
+ endpointId: '001',
+ },
+ ],
+ type: 'unisolate',
+ },
+ associationType: AssociationType.case,
+ createdAt: basicCreatedAt,
+ createdBy: elasticUser,
+ owner: SECURITY_SOLUTION_OWNER,
+ pushedAt: null,
+ pushedBy: null,
+ updatedAt: null,
+ updatedBy: null,
+ version: 'WzQ3LDFc',
+ };
+};
+
export const basicCase: Case = {
type: CaseType.individual,
owner: SECURITY_SOLUTION_OWNER,
@@ -374,6 +426,15 @@ export const getAlertUserAction = () => ({
newValue: '{"type":"alert","alertId":"alert-id-1","index":"index-id-1"}',
});
+export const getHostIsolationUserAction = () => ({
+ ...basicAction,
+ actionId: 'isolate-action-id',
+ actionField: ['comment'] as UserActionField,
+ action: 'create' as UserAction,
+ commentId: 'isolate-comment-id',
+ newValue: 'some value',
+});
+
export const caseUserActions: CaseUserActions[] = [
getUserAction(['description'], 'create'),
getUserAction(['comment'], 'create'),
diff --git a/x-pack/plugins/cases/server/client/cases/mock.ts b/x-pack/plugins/cases/server/client/cases/mock.ts
index 23db57c6d3097e..313d6cd12a6db8 100644
--- a/x-pack/plugins/cases/server/client/cases/mock.ts
+++ b/x-pack/plugins/cases/server/client/cases/mock.ts
@@ -52,6 +52,106 @@ export const comment: CommentResponse = {
version: 'WzEsMV0=',
};
+export const isolateCommentActions: CommentResponse = {
+ associationType: AssociationType.case,
+ id: 'mock-action-comment-1',
+ comment: 'Isolating this for investigation',
+ type: CommentType.actions as const,
+ created_at: '2019-11-25T21:55:00.177Z',
+ actions: {
+ targets: [
+ {
+ endpointId: '123',
+ hostname: 'windows-host-1',
+ },
+ ],
+ type: 'isolate',
+ },
+ created_by: {
+ full_name: 'elastic',
+ email: 'testemail@elastic.co',
+ username: 'elastic',
+ },
+ owner: SECURITY_SOLUTION_OWNER,
+ pushed_at: null,
+ pushed_by: null,
+ updated_at: '2019-11-25T21:55:00.177Z',
+ updated_by: {
+ full_name: 'elastic',
+ email: 'testemail@elastic.co',
+ username: 'elastic',
+ },
+ version: 'WzEsMV0=',
+};
+
+export const releaseCommentActions: CommentResponse = {
+ associationType: AssociationType.case,
+ id: 'mock-action-comment-1',
+ comment: 'Releasing this for investigation',
+ type: CommentType.actions as const,
+ created_at: '2019-11-25T21:55:00.177Z',
+ actions: {
+ targets: [
+ {
+ endpointId: '123',
+ hostname: 'windows-host-1',
+ },
+ ],
+ type: 'unisolate',
+ },
+ created_by: {
+ full_name: 'elastic',
+ email: 'testemail@elastic.co',
+ username: 'elastic',
+ },
+ owner: SECURITY_SOLUTION_OWNER,
+ pushed_at: null,
+ pushed_by: null,
+ updated_at: '2019-11-25T21:55:00.177Z',
+ updated_by: {
+ full_name: 'elastic',
+ email: 'testemail@elastic.co',
+ username: 'elastic',
+ },
+ version: 'WzEsMV0=',
+};
+
+export const isolateCommentActionsMultipleTargets: CommentResponse = {
+ associationType: AssociationType.case,
+ id: 'mock-action-comment-1',
+ comment: 'Isolating this for investigation',
+ type: CommentType.actions as const,
+ created_at: '2019-11-25T21:55:00.177Z',
+ actions: {
+ targets: [
+ {
+ endpointId: '123',
+ hostname: 'windows-host-1',
+ },
+ {
+ endpointId: '456',
+ hostname: 'windows-host-2',
+ },
+ ],
+ type: 'isolate',
+ },
+ created_by: {
+ full_name: 'elastic',
+ email: 'testemail@elastic.co',
+ username: 'elastic',
+ },
+ owner: SECURITY_SOLUTION_OWNER,
+ pushed_at: null,
+ pushed_by: null,
+ updated_at: '2019-11-25T21:55:00.177Z',
+ updated_by: {
+ full_name: 'elastic',
+ email: 'testemail@elastic.co',
+ username: 'elastic',
+ },
+ version: 'WzEsMV0=',
+};
+
export const commentAlert: CommentResponse = {
associationType: AssociationType.case,
id: 'mock-comment-1',
diff --git a/x-pack/plugins/cases/server/client/cases/utils.test.ts b/x-pack/plugins/cases/server/client/cases/utils.test.ts
index bfd5d1279420be..d7c45d3e1e9ae6 100644
--- a/x-pack/plugins/cases/server/client/cases/utils.test.ts
+++ b/x-pack/plugins/cases/server/client/cases/utils.test.ts
@@ -18,6 +18,9 @@ import {
commentAlert,
commentAlertMultipleIds,
commentGeneratedAlert,
+ isolateCommentActions,
+ releaseCommentActions,
+ isolateCommentActionsMultipleTargets,
} from './mock';
import {
@@ -37,6 +40,52 @@ const formatComment = {
comment: 'Wow, good luck catching that bad meanie!',
};
+const formatIsolateActionComment = {
+ commentId: isolateCommentActions.id,
+ comment: 'Isolating this for investigation',
+ actions: {
+ targets: [
+ {
+ hostname: 'windows-host-1',
+ endpointId: '123',
+ },
+ ],
+ type: 'isolate',
+ },
+};
+
+const formatReleaseActionComment = {
+ commentId: releaseCommentActions.id,
+ comment: 'Releasing this for investigation',
+ actions: {
+ targets: [
+ {
+ hostname: 'windows-host-1',
+ endpointId: '123',
+ },
+ ],
+ type: 'unisolate',
+ },
+};
+
+const formatIsolateCommentActionsMultipleTargets = {
+ commentId: isolateCommentActionsMultipleTargets.id,
+ comment: 'Isolating this for investigation',
+ actions: {
+ targets: [
+ {
+ hostname: 'windows-host-1',
+ endpointId: '123',
+ },
+ {
+ hostname: 'windows-host-2',
+ endpointId: '456',
+ },
+ ],
+ type: 'isolate',
+ },
+};
+
const params = { ...basicParams };
describe('utils', () => {
@@ -289,6 +338,42 @@ describe('utils', () => {
},
]);
});
+
+ test('transform isolate action comment', () => {
+ const comments = [isolateCommentActions];
+ const res = transformComments(comments, ['informationCreated']);
+ const actionText = `Isolated host ${formatIsolateActionComment.actions.targets[0].hostname} with comment: ${formatIsolateActionComment.comment}`;
+ expect(res).toEqual([
+ {
+ commentId: formatIsolateActionComment.commentId,
+ comment: `${actionText} (created at ${comments[0].created_at} by ${comments[0].created_by.full_name})`,
+ },
+ ]);
+ });
+
+ test('transform release action comment', () => {
+ const comments = [releaseCommentActions];
+ const res = transformComments(comments, ['informationCreated']);
+ const actionText = `Released host ${formatReleaseActionComment.actions.targets[0].hostname} with comment: ${formatReleaseActionComment.comment}`;
+ expect(res).toEqual([
+ {
+ commentId: formatReleaseActionComment.commentId,
+ comment: `${actionText} (created at ${comments[0].created_at} by ${comments[0].created_by.full_name})`,
+ },
+ ]);
+ });
+
+ test('transform isolate action comment with multiple hosts', () => {
+ const comments = [isolateCommentActionsMultipleTargets];
+ const res = transformComments(comments, ['informationCreated']);
+ const actionText = `Isolated host ${formatIsolateCommentActionsMultipleTargets.actions.targets[0].hostname} and 1 more with comment: ${formatIsolateCommentActionsMultipleTargets.comment}`;
+ expect(res).toEqual([
+ {
+ commentId: formatIsolateCommentActionsMultipleTargets.commentId,
+ comment: `${actionText} (created at ${comments[0].created_at} by ${comments[0].created_by.full_name})`,
+ },
+ ]);
+ });
});
describe('transformers', () => {
@@ -523,8 +608,7 @@ describe('utils', () => {
},
],
},
- // Remove second push
- userActions: userActions.filter((item, index) => index !== 4),
+ userActions,
connector,
mappings: [
...mappings,
@@ -551,7 +635,7 @@ describe('utils', () => {
]);
});
- it('it removes alerts correctly', async () => {
+ it('it filters out the alerts from the comments correctly', async () => {
const res = await createIncident({
actionsClient: actionsMock,
theCase: {
@@ -582,6 +666,32 @@ describe('utils', () => {
]);
});
+ it('does not add the alerts count comment if all alerts have been pushed', async () => {
+ const res = await createIncident({
+ actionsClient: actionsMock,
+ theCase: {
+ ...theCase,
+ comments: [
+ { ...commentObj, id: 'comment-user-1', pushed_at: '2019-11-25T21:55:00.177Z' },
+ { ...commentGeneratedAlert, pushed_at: '2019-11-25T21:55:00.177Z' },
+ ],
+ },
+ userActions,
+ connector,
+ mappings,
+ alerts: [],
+ casesConnectors,
+ });
+
+ expect(res.comments).toEqual([
+ {
+ comment:
+ 'Wow, good luck catching that bad meanie! (added at 2019-11-25T21:55:00.177Z by elastic)',
+ commentId: 'comment-user-1',
+ },
+ ]);
+ });
+
it('updates an existing incident', async () => {
const existingIncidentData = {
priority: null,
@@ -644,22 +754,6 @@ describe('utils', () => {
});
});
- it('throws error if connector is not supported', async () => {
- expect.assertions(2);
- createIncident({
- actionsClient: actionsMock,
- theCase,
- userActions,
- connector: { ...connector, actionTypeId: 'not-supported' },
- mappings,
- alerts: [],
- casesConnectors,
- }).catch((e) => {
- expect(e).not.toBeNull();
- expect(e).toEqual(new Error('Invalid external service'));
- });
- });
-
describe('getLatestPushInfo', () => {
it('it returns the latest push information correctly', async () => {
const res = getLatestPushInfo('456', userActions);
diff --git a/x-pack/plugins/cases/server/client/cases/utils.ts b/x-pack/plugins/cases/server/client/cases/utils.ts
index f5a10d705e095d..617191462c5566 100644
--- a/x-pack/plugins/cases/server/client/cases/utils.ts
+++ b/x-pack/plugins/cases/server/client/cases/utils.ts
@@ -19,6 +19,7 @@ import {
CommentAttributes,
CommentRequestUserType,
CommentRequestAlertType,
+ CommentRequestActionsType,
} from '../../../common';
import { ActionsClient } from '../../../../actions/server';
import { CasesClientGetAlertsResponse } from '../../client/alerts/types';
@@ -76,18 +77,69 @@ const getCommentContent = (comment: CommentResponse): string => {
} else if (comment.type === CommentType.alert || comment.type === CommentType.generatedAlert) {
const ids = getAlertIds(comment);
return `Alert with ids ${ids.join(', ')} added to case`;
+ } else if (
+ comment.type === CommentType.actions &&
+ (comment.actions.type === 'isolate' || comment.actions.type === 'unisolate')
+ ) {
+ const firstHostname =
+ comment.actions.targets?.length > 0 ? comment.actions.targets[0].hostname : 'unknown';
+ const totalHosts = comment.actions.targets.length;
+ const actionText = comment.actions.type === 'isolate' ? 'Isolated' : 'Released';
+ const additionalHostsText = totalHosts - 1 > 0 ? `and ${totalHosts - 1} more ` : ``;
+
+ return `${actionText} host ${firstHostname} ${additionalHostsText}with comment: ${comment.comment}`;
}
return '';
};
-const countAlerts = (comments: CaseResponse['comments']): number =>
- comments?.reduce((total, comment) => {
- if (comment.type === CommentType.alert || comment.type === CommentType.generatedAlert) {
- return total + (Array.isArray(comment.alertId) ? comment.alertId.length : 1);
- }
- return total;
- }, 0) ?? 0;
+interface CountAlertsInfo {
+ totalComments: number;
+ pushed: number;
+ totalAlerts: number;
+}
+
+const getAlertsInfo = (
+ comments: CaseResponse['comments']
+): { totalAlerts: number; hasUnpushedAlertComments: boolean } => {
+ const countingInfo = { totalComments: 0, pushed: 0, totalAlerts: 0 };
+
+ const res =
+ comments?.reduce(({ totalComments, pushed, totalAlerts }, comment) => {
+ if (comment.type === CommentType.alert || comment.type === CommentType.generatedAlert) {
+ return {
+ totalComments: totalComments + 1,
+ pushed: comment.pushed_at != null ? pushed + 1 : pushed,
+ totalAlerts: totalAlerts + (Array.isArray(comment.alertId) ? comment.alertId.length : 1),
+ };
+ }
+ return { totalComments, pushed, totalAlerts };
+ }, countingInfo) ?? countingInfo;
+
+ return {
+ totalAlerts: res.totalAlerts,
+ hasUnpushedAlertComments: res.totalComments > res.pushed,
+ };
+};
+
+const addAlertMessage = (
+ caseId: string,
+ caseComments: CaseResponse['comments'],
+ comments: ExternalServiceComment[]
+): ExternalServiceComment[] => {
+ const { totalAlerts, hasUnpushedAlertComments } = getAlertsInfo(caseComments);
+
+ const newComments = [...comments];
+
+ if (hasUnpushedAlertComments) {
+ newComments.push({
+ comment: `Elastic Alerts attached to the case: ${totalAlerts}`,
+ commentId: `${caseId}-total-alerts`,
+ });
+ }
+
+ return newComments;
+};
export const createIncident = async ({
actionsClient,
@@ -161,11 +213,10 @@ export const createIncident = async ({
const commentsToBeUpdated = caseComments?.filter(
(comment) =>
// We push only user's comments
- comment.type === CommentType.user && commentsIdsToBeUpdated.has(comment.id)
+ (comment.type === CommentType.user || comment.type === CommentType.actions) &&
+ commentsIdsToBeUpdated.has(comment.id)
);
- const totalAlerts = countAlerts(caseComments);
-
let comments: ExternalServiceComment[] = [];
if (commentsToBeUpdated && Array.isArray(commentsToBeUpdated) && commentsToBeUpdated.length > 0) {
@@ -175,12 +226,7 @@ export const createIncident = async ({
}
}
- if (totalAlerts > 0) {
- comments.push({
- comment: `Elastic Alerts attached to the case: ${totalAlerts}`,
- commentId: `${theCase.id}-total-alerts`,
- });
- }
+ comments = addAlertMessage(theCase.id, caseComments, comments);
return { incident, comments };
};
@@ -322,7 +368,7 @@ export const isCommentAlertType = (
export const getCommentContextFromAttributes = (
attributes: CommentAttributes
-): CommentRequestUserType | CommentRequestAlertType => {
+): CommentRequestUserType | CommentRequestAlertType | CommentRequestActionsType => {
const owner = attributes.owner;
switch (attributes.type) {
case CommentType.user:
@@ -340,6 +386,16 @@ export const getCommentContextFromAttributes = (
rule: attributes.rule,
owner,
};
+ case CommentType.actions:
+ return {
+ type: attributes.type,
+ comment: attributes.comment,
+ actions: {
+ targets: attributes.actions.targets,
+ type: attributes.actions.type,
+ },
+ owner,
+ };
default:
return {
type: CommentType.user,
diff --git a/x-pack/plugins/console_extensions/README.md b/x-pack/plugins/console_extensions/README.md
deleted file mode 100644
index 49d83d2888d6be..00000000000000
--- a/x-pack/plugins/console_extensions/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Console extensions
-
-This plugin provides autocomplete definitions of licensed APIs to the OSS Console plugin.
\ No newline at end of file
diff --git a/x-pack/plugins/console_extensions/kibana.json b/x-pack/plugins/console_extensions/kibana.json
deleted file mode 100644
index 9411523d3f6ddb..00000000000000
--- a/x-pack/plugins/console_extensions/kibana.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "id": "consoleExtensions",
- "version": "1.0.0",
- "kibanaVersion": "kibana",
- "requiredPlugins": ["console"],
- "server": true,
- "ui": false
-}
diff --git a/x-pack/plugins/console_extensions/server/index.ts b/x-pack/plugins/console_extensions/server/index.ts
deleted file mode 100644
index a03111a4870900..00000000000000
--- a/x-pack/plugins/console_extensions/server/index.ts
+++ /dev/null
@@ -1,17 +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 { PluginInitializerContext, PluginConfigDescriptor } from 'kibana/server';
-
-import { config as configSchema, ConfigType } from './config';
-import { ConsoleExtensionsServerPlugin } from './plugin';
-
-export const plugin = (ctx: PluginInitializerContext) => new ConsoleExtensionsServerPlugin(ctx);
-
-export const config: PluginConfigDescriptor = {
- schema: configSchema,
-};
diff --git a/x-pack/plugins/console_extensions/server/lib/spec_definitions/js/ingest.ts b/x-pack/plugins/console_extensions/server/lib/spec_definitions/js/ingest.ts
deleted file mode 100644
index 36ebfa589b8231..00000000000000
--- a/x-pack/plugins/console_extensions/server/lib/spec_definitions/js/ingest.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-// NOTE: This is copy-pasted from es_6_0/ingest.js in OSS Console.
-const commonPipelineParams = {
- on_failure: [],
- ignore_failure: {
- __one_of: [false, true],
- },
- if: '',
- tag: '',
-};
-
-// Based on https://www.elastic.co/guide/en/elasticsearch/reference/master/enrich-processor.html
-const enrichProcessorDefinition = {
- enrich: {
- __template: {
- policy_name: '',
- field: '',
- target_field: '',
- },
- policy_name: '',
- field: '',
- target_field: '',
- ignore_missing: {
- __one_of: [false, true],
- },
- override: {
- __one_of: [true, false],
- },
- max_matches: 1,
- shape_relation: 'INTERSECTS',
- ...commonPipelineParams,
- },
-};
-
-// Based on https://www.elastic.co/guide/en/elasticsearch/reference/master/inference-processor.html
-const inferenceProcessorDefinition = {
- inference: {
- __template: {
- model_id: '',
- inference_config: {},
- field_mappings: {},
- },
- target_field: '',
- model_id: '',
- field_mappings: {
- __template: {},
- },
- inference_config: {
- regression: {
- __template: {},
- results_field: '',
- },
- classification: {
- __template: {},
- results_field: '',
- num_top_classes: 2,
- top_classes_results_field: '',
- },
- },
- ...commonPipelineParams,
- },
-};
-
-export const processors = [enrichProcessorDefinition, inferenceProcessorDefinition];
diff --git a/x-pack/plugins/console_extensions/server/lib/spec_definitions/json/generated/ml.delete_expired_data.json b/x-pack/plugins/console_extensions/server/lib/spec_definitions/json/generated/ml.delete_expired_data.json
deleted file mode 100644
index 4afa9e323b0304..00000000000000
--- a/x-pack/plugins/console_extensions/server/lib/spec_definitions/json/generated/ml.delete_expired_data.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "ml.delete_expired_data": {
- "methods": [
- "DELETE"
- ],
- "patterns": [
- "_ml/_delete_expired_data"
- ]
- }
-}
diff --git a/x-pack/plugins/console_extensions/server/lib/spec_definitions/json/generated/ml.info.json b/x-pack/plugins/console_extensions/server/lib/spec_definitions/json/generated/ml.info.json
deleted file mode 100644
index 51b571776ead9d..00000000000000
--- a/x-pack/plugins/console_extensions/server/lib/spec_definitions/json/generated/ml.info.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "ml.info": {
- "methods": [
- "GET"
- ],
- "patterns": [
- "_ml/info"
- ]
- }
-}
diff --git a/x-pack/plugins/console_extensions/server/lib/spec_definitions/json/generated/ml.put_filter.json b/x-pack/plugins/console_extensions/server/lib/spec_definitions/json/generated/ml.put_filter.json
deleted file mode 100644
index 6d57c433d71f47..00000000000000
--- a/x-pack/plugins/console_extensions/server/lib/spec_definitions/json/generated/ml.put_filter.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "ml.put_filter": {
- "methods": [
- "PUT"
- ],
- "patterns": [
- "_ml/filters/{filter_id}"
- ]
- }
-}
diff --git a/x-pack/plugins/console_extensions/server/lib/spec_definitions/json/generated/ml.put_trained_model.json b/x-pack/plugins/console_extensions/server/lib/spec_definitions/json/generated/ml.put_trained_model.json
deleted file mode 100644
index 27d0393be6086f..00000000000000
--- a/x-pack/plugins/console_extensions/server/lib/spec_definitions/json/generated/ml.put_trained_model.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "ml.put_trained_model": {
- "methods": [
- "PUT"
- ],
- "patterns": [
- "_ml/inference/{model_id}"
- ],
- "documentation": "TODO"
- }
-}
diff --git a/x-pack/plugins/console_extensions/server/lib/spec_definitions/json/generated/rollup.delete_job.json b/x-pack/plugins/console_extensions/server/lib/spec_definitions/json/generated/rollup.delete_job.json
deleted file mode 100644
index 8ecf617751a51c..00000000000000
--- a/x-pack/plugins/console_extensions/server/lib/spec_definitions/json/generated/rollup.delete_job.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "rollup.delete_job": {
- "methods": [
- "DELETE"
- ],
- "patterns": [
- "_rollup/job/{id}"
- ]
- }
-}
diff --git a/x-pack/plugins/console_extensions/server/lib/spec_definitions/json/generated/rollup.put_job.json b/x-pack/plugins/console_extensions/server/lib/spec_definitions/json/generated/rollup.put_job.json
deleted file mode 100644
index 7734fd54a1ab12..00000000000000
--- a/x-pack/plugins/console_extensions/server/lib/spec_definitions/json/generated/rollup.put_job.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "rollup.put_job": {
- "methods": [
- "PUT"
- ],
- "patterns": [
- "_rollup/job/{id}"
- ]
- }
-}
diff --git a/x-pack/plugins/console_extensions/server/plugin.ts b/x-pack/plugins/console_extensions/server/plugin.ts
deleted file mode 100644
index 9ea3f314296ee9..00000000000000
--- a/x-pack/plugins/console_extensions/server/plugin.ts
+++ /dev/null
@@ -1,40 +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 { join } from 'path';
-import { CoreSetup, CoreStart, Logger, Plugin, PluginInitializerContext } from 'kibana/server';
-
-import { ConsoleSetup, ConsoleStart } from '../../../../src/plugins/console/server';
-
-import { processors } from './lib/spec_definitions/js';
-
-interface SetupDependencies {
- console: ConsoleSetup;
-}
-
-interface StartDependencies {
- console: ConsoleStart;
-}
-
-const CONSOLE_XPACK_JSON_SPEC_PATH = join(__dirname, 'lib/spec_definitions/json');
-
-export class ConsoleExtensionsServerPlugin implements Plugin {
- log: Logger;
- constructor(private readonly ctx: PluginInitializerContext) {
- this.log = this.ctx.logger.get();
- }
-
- setup(core: CoreSetup, { console: { addExtensionSpecFilePath } }: SetupDependencies) {
- addExtensionSpecFilePath(CONSOLE_XPACK_JSON_SPEC_PATH);
- this.log.debug(`Added extension path to ${CONSOLE_XPACK_JSON_SPEC_PATH}...`);
- }
-
- start(core: CoreStart, { console: { addProcessorDefinition } }: StartDependencies) {
- processors.forEach((processor) => addProcessorDefinition(processor));
- this.log.debug('Added processor definition extensions.');
- }
-}
diff --git a/x-pack/plugins/console_extensions/tsconfig.json b/x-pack/plugins/console_extensions/tsconfig.json
deleted file mode 100644
index 5ad28f230a0bbf..00000000000000
--- a/x-pack/plugins/console_extensions/tsconfig.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "extends": "../../../tsconfig.base.json",
- "compilerOptions": {
- "composite": true,
- "outDir": "./target/types",
- "emitDeclarationOnly": true,
- "declaration": true,
- "declarationMap": true
- },
- "include": [
- "server/**/*"
- ],
- "references": [
- { "path": "../../../src/core/tsconfig.json" },
- { "path": "../../../src/plugins/console/tsconfig.json" }
- ]
-}
diff --git a/x-pack/plugins/data_enhanced/server/search/session/check_non_persiseted_sessions.ts b/x-pack/plugins/data_enhanced/server/search/session/check_non_persiseted_sessions.ts
index 8c75ce91cac6ab..2115ce85eeb274 100644
--- a/x-pack/plugins/data_enhanced/server/search/session/check_non_persiseted_sessions.ts
+++ b/x-pack/plugins/data_enhanced/server/search/session/check_non_persiseted_sessions.ts
@@ -59,7 +59,11 @@ function checkNonPersistedSessionsPage(
`${SEARCH_SESSIONS_CLEANUP_TASK_TYPE} Found ${nonPersistedSearchSessions.total} sessions, processing ${nonPersistedSearchSessions.saved_objects.length}`
);
- const updatedSessions = await getAllSessionsStatusUpdates(deps, nonPersistedSearchSessions);
+ const updatedSessions = await getAllSessionsStatusUpdates(
+ deps,
+ config,
+ nonPersistedSearchSessions
+ );
const deletedSessionIds: string[] = [];
await Promise.all(
diff --git a/x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.ts b/x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.ts
index 0d51e979522755..3e89383c16d5ec 100644
--- a/x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.ts
+++ b/x-pack/plugins/data_enhanced/server/search/session/check_persisted_sessions.ts
@@ -36,7 +36,11 @@ function checkPersistedSessionsPage(
`${SEARCH_SESSIONS_TASK_TYPE} Found ${persistedSearchSessions.total} sessions, processing ${persistedSearchSessions.saved_objects.length}`
);
- const updatedSessions = await getAllSessionsStatusUpdates(deps, persistedSearchSessions);
+ const updatedSessions = await getAllSessionsStatusUpdates(
+ deps,
+ config,
+ persistedSearchSessions
+ );
await bulkUpdateSessions(deps, updatedSessions);
return persistedSearchSessions;
diff --git a/x-pack/plugins/data_enhanced/server/search/session/expire_persisted_sessions.ts b/x-pack/plugins/data_enhanced/server/search/session/expire_persisted_sessions.ts
index e261c324f440f8..61d1635dabe1b7 100644
--- a/x-pack/plugins/data_enhanced/server/search/session/expire_persisted_sessions.ts
+++ b/x-pack/plugins/data_enhanced/server/search/session/expire_persisted_sessions.ts
@@ -36,7 +36,7 @@ function checkSessionExpirationPage(
`${SEARCH_SESSIONS_EXPIRE_TASK_TYPE} Found ${searchSessions.total} sessions, processing ${searchSessions.saved_objects.length}`
);
- const updatedSessions = await getAllSessionsStatusUpdates(deps, searchSessions);
+ const updatedSessions = await getAllSessionsStatusUpdates(deps, config, searchSessions);
await bulkUpdateSessions(deps, updatedSessions);
return searchSessions;
diff --git a/x-pack/plugins/data_enhanced/server/search/session/get_session_status.test.ts b/x-pack/plugins/data_enhanced/server/search/session/get_session_status.test.ts
index fc86e752973936..c3946e5af16fab 100644
--- a/x-pack/plugins/data_enhanced/server/search/session/get_session_status.test.ts
+++ b/x-pack/plugins/data_enhanced/server/search/session/get_session_status.test.ts
@@ -5,16 +5,21 @@
* 2.0.
*/
-import { SearchStatus } from './types';
+import { SearchSessionsConfig, SearchStatus } from './types';
import { getSessionStatus } from './get_session_status';
import { SearchSessionStatus } from '../../../../../../src/plugins/data/common';
+import moment from 'moment';
describe('getSessionStatus', () => {
+ const mockConfig = ({
+ notTouchedInProgressTimeout: moment.duration(1, 'm'),
+ } as unknown) as SearchSessionsConfig;
test("returns an in_progress status if there's nothing inside the session", () => {
const session: any = {
idMapping: {},
+ touched: moment(),
};
- expect(getSessionStatus(session)).toBe(SearchSessionStatus.IN_PROGRESS);
+ expect(getSessionStatus(session, mockConfig)).toBe(SearchSessionStatus.IN_PROGRESS);
});
test("returns an error status if there's at least one error", () => {
@@ -25,7 +30,25 @@ describe('getSessionStatus', () => {
c: { status: SearchStatus.COMPLETE },
},
};
- expect(getSessionStatus(session)).toBe(SearchSessionStatus.ERROR);
+ expect(getSessionStatus(session, mockConfig)).toBe(SearchSessionStatus.ERROR);
+ });
+
+ test('expires a empty session after a minute', () => {
+ const session: any = {
+ idMapping: {},
+ touched: moment().subtract(2, 'm'),
+ };
+ expect(getSessionStatus(session, mockConfig)).toBe(SearchSessionStatus.EXPIRED);
+ });
+
+ test('doesnt expire a full session after a minute', () => {
+ const session: any = {
+ idMapping: {
+ a: { status: SearchStatus.IN_PROGRESS },
+ },
+ touched: moment().subtract(2, 'm'),
+ };
+ expect(getSessionStatus(session, mockConfig)).toBe(SearchSessionStatus.IN_PROGRESS);
});
test('returns a complete status if all are complete', () => {
@@ -36,7 +59,7 @@ describe('getSessionStatus', () => {
c: { status: SearchStatus.COMPLETE },
},
};
- expect(getSessionStatus(session)).toBe(SearchSessionStatus.COMPLETE);
+ expect(getSessionStatus(session, mockConfig)).toBe(SearchSessionStatus.COMPLETE);
});
test('returns a running status if some are still running', () => {
@@ -47,6 +70,6 @@ describe('getSessionStatus', () => {
c: { status: SearchStatus.IN_PROGRESS },
},
};
- expect(getSessionStatus(session)).toBe(SearchSessionStatus.IN_PROGRESS);
+ expect(getSessionStatus(session, mockConfig)).toBe(SearchSessionStatus.IN_PROGRESS);
});
});
diff --git a/x-pack/plugins/data_enhanced/server/search/session/get_session_status.ts b/x-pack/plugins/data_enhanced/server/search/session/get_session_status.ts
index 23e02eedc00047..e7ae52b6c88aed 100644
--- a/x-pack/plugins/data_enhanced/server/search/session/get_session_status.ts
+++ b/x-pack/plugins/data_enhanced/server/search/session/get_session_status.ts
@@ -5,16 +5,28 @@
* 2.0.
*/
+import moment from 'moment';
import {
SearchSessionSavedObjectAttributes,
SearchSessionStatus,
} from '../../../../../../src/plugins/data/common/';
-import { SearchStatus } from './types';
+import { SearchSessionsConfig, SearchStatus } from './types';
-export function getSessionStatus(session: SearchSessionSavedObjectAttributes): SearchSessionStatus {
+export function getSessionStatus(
+ session: SearchSessionSavedObjectAttributes,
+ config: SearchSessionsConfig
+): SearchSessionStatus {
const searchStatuses = Object.values(session.idMapping);
+ const curTime = moment();
if (searchStatuses.some((item) => item.status === SearchStatus.ERROR)) {
return SearchSessionStatus.ERROR;
+ } else if (
+ searchStatuses.length === 0 &&
+ curTime.diff(moment(session.touched), 'ms') >
+ moment.duration(config.notTouchedInProgressTimeout).asMilliseconds()
+ ) {
+ // Expire empty sessions that weren't touched for a minute
+ return SearchSessionStatus.EXPIRED;
} else if (
searchStatuses.length > 0 &&
searchStatuses.every((item) => item.status === SearchStatus.COMPLETE)
diff --git a/x-pack/plugins/data_enhanced/server/search/session/update_session_status.test.ts b/x-pack/plugins/data_enhanced/server/search/session/update_session_status.test.ts
index 485a30fd549511..d9e3fa6f8cab3d 100644
--- a/x-pack/plugins/data_enhanced/server/search/session/update_session_status.test.ts
+++ b/x-pack/plugins/data_enhanced/server/search/session/update_session_status.test.ts
@@ -21,6 +21,7 @@ import {
describe('bulkUpdateSessions', () => {
let mockClient: any;
+ const mockConfig: any = {};
let savedObjectsClient: jest.Mocked;
const mockLogger: any = {
debug: jest.fn(),
@@ -66,6 +67,7 @@ describe('bulkUpdateSessions', () => {
client: mockClient,
logger: mockLogger,
},
+ mockConfig,
so
);
@@ -105,6 +107,7 @@ describe('bulkUpdateSessions', () => {
client: mockClient,
logger: mockLogger,
},
+ mockConfig,
so
);
@@ -139,6 +142,7 @@ describe('bulkUpdateSessions', () => {
client: mockClient,
logger: mockLogger,
},
+ mockConfig,
so
);
@@ -176,6 +180,7 @@ describe('bulkUpdateSessions', () => {
client: mockClient,
logger: mockLogger,
},
+ mockConfig,
so
);
@@ -219,6 +224,7 @@ describe('bulkUpdateSessions', () => {
client: mockClient,
logger: mockLogger,
},
+ mockConfig,
so
);
diff --git a/x-pack/plugins/data_enhanced/server/search/session/update_session_status.ts b/x-pack/plugins/data_enhanced/server/search/session/update_session_status.ts
index 1c484467bef633..4758e7cb226845 100644
--- a/x-pack/plugins/data_enhanced/server/search/session/update_session_status.ts
+++ b/x-pack/plugins/data_enhanced/server/search/session/update_session_status.ts
@@ -13,11 +13,17 @@ import {
} from '../../../../../../src/plugins/data/common';
import { getSearchStatus } from './get_search_status';
import { getSessionStatus } from './get_session_status';
-import { CheckSearchSessionsDeps, SearchSessionsResponse, SearchStatus } from './types';
+import {
+ CheckSearchSessionsDeps,
+ SearchSessionsConfig,
+ SearchSessionsResponse,
+ SearchStatus,
+} from './types';
import { isSearchSessionExpired } from './utils';
export async function updateSessionStatus(
{ logger, client }: CheckSearchSessionsDeps,
+ config: SearchSessionsConfig,
session: SavedObjectsFindResult
) {
let sessionUpdated = false;
@@ -61,7 +67,7 @@ export async function updateSessionStatus(
// And only then derive the session's status
const sessionStatus = isExpired
? SearchSessionStatus.EXPIRED
- : getSessionStatus(session.attributes);
+ : getSessionStatus(session.attributes, config);
if (sessionStatus !== session.attributes.status) {
const now = new Date().toISOString();
session.attributes.status = sessionStatus;
@@ -79,13 +85,14 @@ export async function updateSessionStatus(
export async function getAllSessionsStatusUpdates(
deps: CheckSearchSessionsDeps,
+ config: SearchSessionsConfig,
searchSessions: SearchSessionsResponse
) {
const updatedSessions = new Array>();
await Promise.all(
searchSessions.saved_objects.map(async (session) => {
- const updated = await updateSessionStatus(deps, session);
+ const updated = await updateSessionStatus(deps, config, session);
if (updated) {
updatedSessions.push(session);
diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_error_callouts.tsx b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_error_callouts.tsx
index 62d860c1513e85..12e92f2936fff8 100644
--- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_error_callouts.tsx
+++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_error_callouts.tsx
@@ -107,7 +107,7 @@ export const FileCouldNotBeRead: FC = ({
defaultMessage="If you know something about this data, such as the file format or timestamp format, adding initial overrides may help us to infer the rest of the structure."
/>
-
+
= (dataVi
earliest,
latest
);
+ // Because load overall stats perform queries in batches
+ // there could be multiple errors
+ if (Array.isArray(allStats.errors) && allStats.errors.length > 0) {
+ allStats.errors.forEach((err: any) => {
+ dataLoader.displayError(extractErrorProperties(err));
+ });
+ }
setOverallStats(allStats);
} catch (err) {
- dataLoader.displayError(err);
+ dataLoader.displayError(err.body ?? err);
}
}
diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/data_loader/data_loader.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/data_loader/data_loader.ts
index 4a3c971cc57cd1..1b92eaddd1343a 100644
--- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/data_loader/data_loader.ts
+++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/data_loader/data_loader.ts
@@ -109,17 +109,17 @@ export class DataLoader {
'The request may have timed out. Try using a smaller sample size or narrowing the time range.',
values: {
index: this._indexPattern.title,
- message: err.message,
+ message: err.error ?? err.message,
},
}),
});
} else {
this._toastNotifications.addError(err, {
- title: i18n.translate('xpack.dataVisualizer.index.errorLoadingDataMessage.', {
- defaultMessage: 'Error loading data in index {index}. {message}',
+ title: i18n.translate('xpack.dataVisualizer.index.errorLoadingDataMessage', {
+ defaultMessage: 'Error loading data in index {index}. {message}.',
values: {
index: this._indexPattern.title,
- message: err.message,
+ message: err.error ?? err.message,
},
}),
});
diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/utils/error_utils.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/utils/error_utils.ts
new file mode 100644
index 00000000000000..9bb36496a149e7
--- /dev/null
+++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/utils/error_utils.ts
@@ -0,0 +1,184 @@
+/*
+ * 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 { HttpFetchError } from 'kibana/public';
+import Boom from '@hapi/boom';
+import { isPopulatedObject } from '../../../../common/utils/object_utils';
+
+export interface WrappedError {
+ body: {
+ attributes: {
+ body: EsErrorBody;
+ };
+ message: Boom.Boom;
+ };
+ statusCode: number;
+}
+
+export interface EsErrorRootCause {
+ type: string;
+ reason: string;
+ caused_by?: EsErrorRootCause;
+ script?: string;
+}
+
+export interface EsErrorBody {
+ error: {
+ root_cause?: EsErrorRootCause[];
+ caused_by?: EsErrorRootCause;
+ type: string;
+ reason: string;
+ };
+ status: number;
+}
+
+export interface DVResponseError {
+ statusCode: number;
+ error: string;
+ message: string;
+ attributes?: {
+ body: EsErrorBody;
+ };
+}
+
+export interface ErrorMessage {
+ message: string;
+}
+
+export interface DVErrorObject {
+ causedBy?: string;
+ message: string;
+ statusCode?: number;
+ fullError?: EsErrorBody;
+}
+
+export interface DVHttpFetchError extends HttpFetchError {
+ body: T;
+}
+
+export type ErrorType =
+ | WrappedError
+ | DVHttpFetchError
+ | EsErrorBody
+ | Boom.Boom
+ | string
+ | undefined;
+
+export function isEsErrorBody(error: any): error is EsErrorBody {
+ return error && error.error?.reason !== undefined;
+}
+
+export function isErrorString(error: any): error is string {
+ return typeof error === 'string';
+}
+
+export function isErrorMessage(error: any): error is ErrorMessage {
+ return error && error.message !== undefined && typeof error.message === 'string';
+}
+
+export function isDVResponseError(error: any): error is DVResponseError {
+ return typeof error.body === 'object' && 'message' in error.body;
+}
+
+export function isBoomError(error: any): error is Boom.Boom {
+ return error.isBoom === true;
+}
+
+export function isWrappedError(error: any): error is WrappedError {
+ return error && isBoomError(error.body?.message) === true;
+}
+
+export const extractErrorProperties = (error: ErrorType): DVErrorObject => {
+ // extract properties of the error object from within the response error
+ // coming from Kibana, Elasticsearch, and our own DV messages
+
+ // some responses contain raw es errors as part of a bulk response
+ // e.g. if some jobs fail the action in a bulk request
+
+ if (isEsErrorBody(error)) {
+ return {
+ message: error.error.reason,
+ statusCode: error.status,
+ fullError: error,
+ };
+ }
+
+ if (isErrorString(error)) {
+ return {
+ message: error,
+ };
+ }
+ if (isWrappedError(error)) {
+ return error.body.message?.output?.payload;
+ }
+
+ if (isBoomError(error)) {
+ return {
+ message: error.output.payload.message,
+ statusCode: error.output.payload.statusCode,
+ };
+ }
+
+ if (error?.body === undefined && !error?.message) {
+ return {
+ message: '',
+ };
+ }
+
+ if (typeof error.body === 'string') {
+ return {
+ message: error.body,
+ };
+ }
+
+ if (isDVResponseError(error)) {
+ if (
+ typeof error.body.attributes === 'object' &&
+ typeof error.body.attributes.body?.error?.reason === 'string'
+ ) {
+ const errObj: DVErrorObject = {
+ message: error.body.attributes.body.error.reason,
+ statusCode: error.body.statusCode,
+ fullError: error.body.attributes.body,
+ };
+ if (
+ typeof error.body.attributes.body.error.caused_by === 'object' &&
+ (typeof error.body.attributes.body.error.caused_by?.reason === 'string' ||
+ typeof error.body.attributes.body.error.caused_by?.caused_by?.reason === 'string')
+ ) {
+ errObj.causedBy =
+ error.body.attributes.body.error.caused_by?.caused_by?.reason ||
+ error.body.attributes.body.error.caused_by?.reason;
+ }
+ if (
+ Array.isArray(error.body.attributes.body.error.root_cause) &&
+ typeof error.body.attributes.body.error.root_cause[0] === 'object' &&
+ isPopulatedObject(error.body.attributes.body.error.root_cause[0], ['script'])
+ ) {
+ errObj.causedBy = error.body.attributes.body.error.root_cause[0].script;
+ errObj.message += `: '${error.body.attributes.body.error.root_cause[0].script}'`;
+ }
+ return errObj;
+ } else {
+ return {
+ message: error.body.message,
+ statusCode: error.body.statusCode,
+ };
+ }
+ }
+
+ if (isErrorMessage(error)) {
+ return {
+ message: error.message,
+ };
+ }
+
+ // If all else fail return an empty message instead of JSON.stringify
+ return {
+ message: '',
+ };
+};
diff --git a/x-pack/plugins/data_visualizer/server/models/data_visualizer/data_visualizer.ts b/x-pack/plugins/data_visualizer/server/models/data_visualizer/data_visualizer.ts
index 27c09c889deb77..155cf09ebb8dbc 100644
--- a/x-pack/plugins/data_visualizer/server/models/data_visualizer/data_visualizer.ts
+++ b/x-pack/plugins/data_visualizer/server/models/data_visualizer/data_visualizer.ts
@@ -31,6 +31,7 @@ import {
getNumericFieldsStats,
getStringFieldsStats,
} from './get_fields_stats';
+import { wrapError } from '../../utils/error_wrapper';
export class DataVisualizer {
private _client: IScopedClusterClient;
@@ -60,6 +61,7 @@ export class DataVisualizer {
aggregatableNotExistsFields: [] as FieldData[],
nonAggregatableExistsFields: [] as FieldData[],
nonAggregatableNotExistsFields: [] as FieldData[],
+ errors: [] as any[],
};
// To avoid checking for the existence of too many aggregatable fields in one request,
@@ -76,49 +78,61 @@ export class DataVisualizer {
await Promise.all(
batches.map(async (fields) => {
- const batchStats = await this.checkAggregatableFieldsExist(
- indexPatternTitle,
- query,
- fields,
- samplerShardSize,
- timeFieldName,
- earliestMs,
- latestMs,
- undefined,
- runtimeMappings
- );
+ try {
+ const batchStats = await this.checkAggregatableFieldsExist(
+ indexPatternTitle,
+ query,
+ fields,
+ samplerShardSize,
+ timeFieldName,
+ earliestMs,
+ latestMs,
+ undefined,
+ runtimeMappings
+ );
- // Total count will be returned with each batch of fields. Just overwrite.
- stats.totalCount = batchStats.totalCount;
+ // Total count will be returned with each batch of fields. Just overwrite.
+ stats.totalCount = batchStats.totalCount;
- // Add to the lists of fields which do and do not exist.
- stats.aggregatableExistsFields.push(...batchStats.aggregatableExistsFields);
- stats.aggregatableNotExistsFields.push(...batchStats.aggregatableNotExistsFields);
+ // Add to the lists of fields which do and do not exist.
+ stats.aggregatableExistsFields.push(...batchStats.aggregatableExistsFields);
+ stats.aggregatableNotExistsFields.push(...batchStats.aggregatableNotExistsFields);
+ } catch (e) {
+ // If index not found, no need to proceed with other batches
+ if (e.statusCode === 404) {
+ throw e;
+ }
+ stats.errors.push(wrapError(e));
+ }
})
);
await Promise.all(
nonAggregatableFields.map(async (field) => {
- const existsInDocs = await this.checkNonAggregatableFieldExists(
- indexPatternTitle,
- query,
- field,
- timeFieldName,
- earliestMs,
- latestMs,
- runtimeMappings
- );
+ try {
+ const existsInDocs = await this.checkNonAggregatableFieldExists(
+ indexPatternTitle,
+ query,
+ field,
+ timeFieldName,
+ earliestMs,
+ latestMs,
+ runtimeMappings
+ );
- const fieldData: FieldData = {
- fieldName: field,
- existsInDocs,
- stats: {},
- };
+ const fieldData: FieldData = {
+ fieldName: field,
+ existsInDocs,
+ stats: {},
+ };
- if (existsInDocs === true) {
- stats.nonAggregatableExistsFields.push(fieldData);
- } else {
- stats.nonAggregatableNotExistsFields.push(fieldData);
+ if (existsInDocs === true) {
+ stats.nonAggregatableExistsFields.push(fieldData);
+ } else {
+ stats.nonAggregatableNotExistsFields.push(fieldData);
+ }
+ } catch (e) {
+ stats.errors.push(wrapError(e));
}
})
);
diff --git a/x-pack/plugins/data_visualizer/server/plugin.ts b/x-pack/plugins/data_visualizer/server/plugin.ts
index 4ae695b05b81f4..9db580959b1162 100644
--- a/x-pack/plugins/data_visualizer/server/plugin.ts
+++ b/x-pack/plugins/data_visualizer/server/plugin.ts
@@ -12,7 +12,7 @@ import { dataVisualizerRoutes } from './routes';
export class DataVisualizerPlugin implements Plugin {
constructor() {}
- async setup(coreSetup: CoreSetup, plugins: SetupDeps) {
+ setup(coreSetup: CoreSetup, plugins: SetupDeps) {
dataVisualizerRoutes(coreSetup);
}
diff --git a/x-pack/plugins/enterprise_search/README.md b/x-pack/plugins/enterprise_search/README.md
index 0b067e25e32e84..96b0391bbc8da1 100644
--- a/x-pack/plugins/enterprise_search/README.md
+++ b/x-pack/plugins/enterprise_search/README.md
@@ -2,16 +2,30 @@
## Overview
-This plugin's goal is to provide a Kibana user interface to the Enterprise Search solution's products (App Search and Workplace Search). In it's current MVP state, the plugin provides the following with the goal of gathering user feedback and raising product awareness:
+This plugin provides beta Kibana user interfaces for managing the Enterprise Search solution and its products, App Search and Workplace Search.
-- **App Search:** A basic engines overview with links into the product.
-- **Workplace Search:** A simple app overview with basic statistics, links to the sources, users (if standard auth), and product settings.
+> :warning: The Kibana interface for Enterprise Search is a beta feature. It is subject to change and is not covered by the same level of support as generally available features. This interface will become the sole management panel for Enterprise Search with the 8.0 release. Until then, the standalone Enterprise Search UI remains available and supported.
+
+### App Search
+
+
+
+Add rich, relevant search to your apps and websites. https://www.elastic.co/app-search/
+
+### Workplace Search
+
+
+
+Unify all your team's content into a personalized search experience. https://www.elastic.co/workplace-search/
## Development
1. When developing locally, Enterprise Search should be running locally alongside Kibana on `localhost:3002`.
2. Update `config/kibana.dev.yml` with `enterpriseSearch.host: 'http://localhost:3002'`
-3. For faster QA/development, run Enterprise Search on [elasticsearch-native auth](https://www.elastic.co/guide/en/app-search/current/security-and-users.html#app-search-self-managed-security-and-user-management-elasticsearch-native-realm) and log in as the `elastic` superuser on Kibana.
+
+Problems? If you're an Elastic Enterprise Search engineer, please reach out to @elastic/enterprise-search-frontend for questions or our in-depth Getting Started developer guide.
+
+Don't forget to read Kibana's [contributing documentation](https://github.com/elastic/kibana/#building-and-running-kibana-andor-contributing-code) and developer guides for more general info on the Kibana ecosystem.
### Kea
diff --git a/x-pack/plugins/enterprise_search/kibana.json b/x-pack/plugins/enterprise_search/kibana.json
index f8b4261114a22d..723b24f951434c 100644
--- a/x-pack/plugins/enterprise_search/kibana.json
+++ b/x-pack/plugins/enterprise_search/kibana.json
@@ -12,5 +12,5 @@
"name": "Enterprise Search",
"githubTeam": "enterprise-search-frontend"
},
- "description": "Adds dashboards for discovering and managing Enterprise Search products"
+ "description": "Adds dashboards for discovering and managing Enterprise Search products."
}
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_filters.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_filters.tsx
index 0c8455e986ae1d..dd99d368a0105d 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_filters.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_filters.tsx
@@ -80,6 +80,7 @@ export const AnalyticsFilters: React.FC = () => {
'xpack.enterpriseSearch.appSearch.engine.analytics.filters.startDateAriaLabel',
{ defaultMessage: 'Filter by start date' }
)}
+ locale={i18n.getLocale()}
/>
}
endDateControl={
@@ -93,6 +94,7 @@ export const AnalyticsFilters: React.FC = () => {
'xpack.enterpriseSearch.appSearch.engine.analytics.filters.endDateAriaLabel',
{ defaultMessage: 'Filter by end date' }
)}
+ locale={i18n.getLocale()}
/>
}
fullWidth
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts
index 748dc6a7cbcf88..c599a13cc31195 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts
@@ -303,6 +303,7 @@ export const exampleResult = {
titleField: 'otherTitle',
subtitleField: 'otherSubtitle',
urlField: 'myLink',
+ urlFieldIsLinkable: true,
color: '#e3e3e3',
descriptionField: 'about',
typeField: 'otherType',
@@ -314,14 +315,18 @@ export const exampleResult = {
{ fieldName: 'dogs', label: 'Canines' },
],
},
- titleFieldHover: false,
- urlFieldHover: false,
exampleDocuments: [
{
myLink: 'http://foo',
otherTitle: 'foo',
+ content_source_id: '60e85e7ea2564c265a88a4f0',
+ external_id: 'doc-60e85eb7a2564c937a88a4f3',
+ last_updated: '2021-07-09T14:35:35+00:00',
+ updated_at: '2021-07-09T14:35:35+00:00',
+ source: 'custom',
},
],
+ schemaFields: {},
};
export const mostRecentIndexJob = {
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts
index edc772b369558b..e50b12f7819470 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts
@@ -96,7 +96,7 @@ export interface ContentSource {
export interface SourceContentItem {
id: string;
last_updated: string;
- [key: string]: string;
+ [key: string]: string | CustomAPIFieldValue;
}
export interface ContentSourceDetails extends ContentSource {
@@ -186,8 +186,25 @@ export interface CustomSource {
id: string;
}
+// https://www.elastic.co/guide/en/workplace-search/current/workplace-search-custom-sources-api.html#_schema_data_types
+type CustomAPIString = string | string[];
+type CustomAPINumber = number | number[];
+type CustomAPIDate = string | string[];
+type CustomAPIGeolocation = string | string[] | number[] | number[][];
+
+export type CustomAPIFieldValue =
+ | CustomAPIString
+ | CustomAPINumber
+ | CustomAPIDate
+ | CustomAPIGeolocation;
+
export interface Result {
- [key: string]: string | string[];
+ content_source_id: string;
+ last_updated: string;
+ external_id: string;
+ updated_at: string;
+ source: string;
+ [key: string]: CustomAPIFieldValue;
}
export interface OptionValue {
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/get_as_local_datetime_string.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/get_as_local_datetime_string.test.ts
index 6475df7f4c3996..36df182b99b857 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/get_as_local_datetime_string.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/get_as_local_datetime_string.test.ts
@@ -14,6 +14,12 @@ describe('getAsLocalDateTimeString', () => {
expect(getAsLocalDateTimeString(date)).toEqual(new Date(Date.parse(date)).toLocaleString());
});
+ it('returns null if passed value is not a string', () => {
+ const date = ['1', '2'];
+
+ expect(getAsLocalDateTimeString(date)).toEqual(null);
+ });
+
it('returns null if string cannot be parsed as date', () => {
const date = 'foo';
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/get_as_local_datetime_string.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/get_as_local_datetime_string.ts
index d5ceb50d4c9af7..6350c4e4a40992 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/get_as_local_datetime_string.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/get_as_local_datetime_string.ts
@@ -5,7 +5,11 @@
* 2.0.
*/
-export const getAsLocalDateTimeString = (str: string) => {
- const dateValue = Date.parse(str);
+import { CustomAPIFieldValue } from '../types';
+
+export const getAsLocalDateTimeString = (maybeDate: CustomAPIFieldValue) => {
+ if (typeof maybeDate !== 'string') return null;
+
+ const dateValue = Date.parse(maybeDate);
return dateValue ? new Date(dateValue).toLocaleString() : null;
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/mime_types.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/mime_types.ts
index f7664c90d461c8..7a5020be5986ef 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/mime_types.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/mime_types.ts
@@ -5,6 +5,8 @@
* 2.0.
*/
+import { CustomAPIFieldValue } from '../types';
+
const mimeTypes = {
'application/iwork-keynote-sffkey': 'Keynote',
'application/x-iwork-keynote-sffkey': 'Keynote',
@@ -51,4 +53,5 @@ const mimeTypes = {
'video/quicktime': 'MOV',
} as { [key: string]: string };
-export const mimeType = (type: string) => mimeTypes[type.toLowerCase()] || type;
+export const mimeType = (type: CustomAPIFieldValue) =>
+ mimeTypes[type.toString().toLowerCase()] || type;
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.test.tsx
index b30511f0a6d80b..af94707aa36126 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.test.tsx
@@ -58,6 +58,13 @@ describe('AddSourceList', () => {
expect(wrapper.find(AvailableSourcesList)).toHaveLength(1);
});
+ it('does not render header when loading', () => {
+ setMockValues({ ...mockValues, dataLoading: true });
+ const wrapper = shallow( );
+
+ expect(wrapper.prop('pageHeader')).toBe(undefined);
+ });
+
describe('layout', () => {
it('renders the default workplace search layout when on an organization view', () => {
setMockValues({ ...mockValues, isOrganization: true });
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.tsx
index a7a64194cb42f2..165586dcc3903a 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.tsx
@@ -104,7 +104,9 @@ export const AddSourceList: React.FC = () => {
{!isOrganization && (
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts
index fcaa847c47f3eb..09ba41f81d76ac 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts
@@ -20,8 +20,14 @@ jest.mock('../../../../app_logic', () => ({
}));
import { AppLogic } from '../../../../app_logic';
-import { ADD_GITHUB_PATH, SOURCES_PATH, getSourcesPath } from '../../../../routes';
+import {
+ ADD_GITHUB_PATH,
+ SOURCES_PATH,
+ PERSONAL_SOURCES_PATH,
+ getSourcesPath,
+} from '../../../../routes';
import { CustomSource } from '../../../../types';
+import { PERSONAL_DASHBOARD_SOURCE_ERROR } from '../../constants';
import { SourcesLogic } from '../../sources_logic';
import {
@@ -36,7 +42,7 @@ describe('AddSourceLogic', () => {
const { mount } = new LogicMounter(AddSourceLogic);
const { http } = mockHttpValues;
const { navigateToUrl } = mockKibanaValues;
- const { clearFlashMessages, flashAPIErrors } = mockFlashMessageHelpers;
+ const { clearFlashMessages, flashAPIErrors, setErrorMessage } = mockFlashMessageHelpers;
const defaultValues = {
addSourceCurrentStep: AddSourceSteps.ConfigIntroStep,
@@ -353,6 +359,33 @@ describe('AddSourceLogic', () => {
expect(navigateToUrl).toHaveBeenCalledWith(`${ADD_GITHUB_PATH}/configure${queryString}`);
});
+ describe('Github error edge case', () => {
+ const getGithubQueryString = (context: 'organization' | 'account') =>
+ `?error=redirect_uri_mismatch&error_description=The+redirect_uri+MUST+match+the+registered+callback+URL+for+this+application.&error_uri=https%3A%2F%2Fdocs.github.com%2Fapps%2Fmanaging-oauth-apps%2Ftroubleshooting-authorization-request-errors%2F%23redirect-uri-mismatch&state=%7B%22action%22%3A%22create%22%2C%22context%22%3A%22${context}%22%2C%22service_type%22%3A%22github%22%2C%22csrf_token%22%3A%22TOKEN%3D%3D%22%2C%22index_permissions%22%3Afalse%7D`;
+
+ it('handles "organization" redirect and displays error', () => {
+ const githubQueryString = getGithubQueryString('organization');
+ AddSourceLogic.actions.saveSourceParams(githubQueryString);
+
+ expect(navigateToUrl).toHaveBeenCalledWith('/');
+ expect(setErrorMessage).toHaveBeenCalledWith(
+ 'The redirect_uri MUST match the registered callback URL for this application.'
+ );
+ });
+
+ it('handles "account" redirect and displays error', () => {
+ const githubQueryString = getGithubQueryString('account');
+ AddSourceLogic.actions.saveSourceParams(githubQueryString);
+
+ expect(navigateToUrl).toHaveBeenCalledWith(PERSONAL_SOURCES_PATH);
+ expect(setErrorMessage).toHaveBeenCalledWith(
+ PERSONAL_DASHBOARD_SOURCE_ERROR(
+ 'The redirect_uri MUST match the registered callback URL for this application.'
+ )
+ );
+ });
+ });
+
it('handles error', async () => {
http.get.mockReturnValue(Promise.reject('this is an error'));
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts
index 0bd37aed81c32a..81e27f07293dc2 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts
@@ -16,14 +16,21 @@ import {
flashAPIErrors,
setSuccessMessage,
clearFlashMessages,
+ setErrorMessage,
} from '../../../../../shared/flash_messages';
import { HttpLogic } from '../../../../../shared/http';
import { KibanaLogic } from '../../../../../shared/kibana';
import { parseQueryParams } from '../../../../../shared/query_params';
import { AppLogic } from '../../../../app_logic';
import { CUSTOM_SERVICE_TYPE, WORKPLACE_SEARCH_URL_PREFIX } from '../../../../constants';
-import { SOURCES_PATH, ADD_GITHUB_PATH, getSourcesPath } from '../../../../routes';
+import {
+ SOURCES_PATH,
+ ADD_GITHUB_PATH,
+ PERSONAL_SOURCES_PATH,
+ getSourcesPath,
+} from '../../../../routes';
import { CustomSource } from '../../../../types';
+import { PERSONAL_DASHBOARD_SOURCE_ERROR } from '../../constants';
import { staticSourceData } from '../../source_data';
import { SourcesLogic } from '../../sources_logic';
@@ -50,6 +57,8 @@ export interface OauthParams {
state: string;
session_state: string;
oauth_verifier?: string;
+ error?: string;
+ error_description?: string;
}
export interface AddSourceActions {
@@ -501,6 +510,22 @@ export const AddSourceLogic = kea {
{detailFields.length > 0 ? (
detailFields.map(({ fieldName, label }, index) => {
- const value = result[fieldName] as string;
+ const value = result[fieldName];
const dateValue = getAsLocalDateTimeString(value);
return (
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_search_result_group.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_search_result_group.tsx
index 549faf1676a540..3ca5b619c03664 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_search_result_group.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_search_result_group.tsx
@@ -117,7 +117,7 @@ export const ExampleSearchResultGroup: React.FC = () => {
data-test-subj="MediaTypeField"
>
- {mimeType(result[mediaTypeField] as string)}
+ {mimeType(result[mediaTypeField])}
)}
@@ -135,8 +135,7 @@ export const ExampleSearchResultGroup: React.FC = () => {
by {result[updatedByField]}
)}
- {getAsLocalDateTimeString(result.last_updated as string) ||
- result.last_updated}
+ {getAsLocalDateTimeString(result.last_updated) || result.last_updated}
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_standout_result.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_standout_result.tsx
index 46b8de67894674..b3ba4c6d509730 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_standout_result.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_standout_result.tsx
@@ -109,7 +109,7 @@ export const ExampleStandoutResult: React.FC = () => {
data-test-subj="MediaTypeField"
>
- {mimeType(result[mediaTypeField] as string)}
+ {mimeType(result[mediaTypeField])}
)}
@@ -127,7 +127,7 @@ export const ExampleStandoutResult: React.FC = () => {
by {result[updatedByField]}
)}
- {getAsLocalDateTimeString(result.last_updated as string) || result.last_updated}
+ {getAsLocalDateTimeString(result.last_updated) || result.last_updated}
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/subtitle_field.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/subtitle_field.test.tsx
index 76c28ae3d4060b..7506c499dff317 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/subtitle_field.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/subtitle_field.test.tsx
@@ -5,6 +5,8 @@
* 2.0.
*/
+import { exampleResult } from '../../../../__mocks__/content_sources.mock';
+
import React from 'react';
import { shallow } from 'enzyme';
@@ -12,7 +14,11 @@ import { shallow } from 'enzyme';
import { SubtitleField } from './subtitle_field';
describe('SubtitleField', () => {
- const result = { foo: 'bar' };
+ const result = {
+ ...exampleResult.exampleDocuments[0],
+ foo: 'bar',
+ };
+
it('renders', () => {
const props = {
result,
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/title_field.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/title_field.test.tsx
index 2ed4aa0b0fad18..e5681bc7e8619d 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/title_field.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/title_field.test.tsx
@@ -5,6 +5,8 @@
* 2.0.
*/
+import { exampleResult } from '../../../../__mocks__/content_sources.mock';
+
import React from 'react';
import { shallow } from 'enzyme';
@@ -12,7 +14,10 @@ import { shallow } from 'enzyme';
import { TitleField } from './title_field';
describe('TitleField', () => {
- const result = { foo: 'bar' };
+ const result = {
+ ...exampleResult.exampleDocuments[0],
+ foo: 'bar',
+ };
it('renders', () => {
const props = {
result,
@@ -26,7 +31,10 @@ describe('TitleField', () => {
it('handles title when array', () => {
const props = {
- result: { foo: ['baz', 'bar'] },
+ result: {
+ ...exampleResult.exampleDocuments[0],
+ foo: ['baz', 'bar'],
+ },
titleField: 'foo',
titleFieldHover: false,
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx
index a0e3c28f20eb0b..a97cc85cb822ad 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx
@@ -137,7 +137,7 @@ export const SourceContent: React.FC = () => {
)}
{urlFieldIsLinkable && (
-
+
)}
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/constants.ts
index 8e5123dbc7cceb..d57afc6699c1af 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/constants.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/constants.ts
@@ -470,3 +470,10 @@ export const PRIVATE_DASHBOARD_READ_ONLY_MODE_WARNING = i18n.translate(
'Workplace Search is currently available for search only, due to regular maintenance. Contact your system administrator for more information.',
}
);
+
+export const PERSONAL_DASHBOARD_SOURCE_ERROR = (error: string) =>
+ i18n.translate('xpack.enterpriseSearch.workplaceSearch.personalDashboardSourceError', {
+ defaultMessage:
+ 'Could not connect the source, reach out to your admin for help. Error message: {error}',
+ values: { error },
+ });
diff --git a/x-pack/plugins/file_upload/server/plugin.ts b/x-pack/plugins/file_upload/server/plugin.ts
index c729afec92f940..bd5eebe372a752 100644
--- a/x-pack/plugins/file_upload/server/plugin.ts
+++ b/x-pack/plugins/file_upload/server/plugin.ts
@@ -21,7 +21,7 @@ export class FileUploadPlugin implements Plugin {
this._logger = initializerContext.logger.get();
}
- async setup(coreSetup: CoreSetup, plugins: SetupDeps) {
+ setup(coreSetup: CoreSetup, plugins: SetupDeps) {
fileUploadRoutes(coreSetup, this._logger);
setupCapabilities(coreSetup);
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx
index b07d76dc6bd8e8..ee529b6865e565 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx
@@ -430,7 +430,9 @@ export const EditPackagePolicyForm = memo<{
/>
)}
{configurePackage}
-
+ {/* Extra space to accomodate the EuiBottomBar height */}
+
+
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_dataset.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_dataset.tsx
index 47174561230baf..18f6a8b565ab97 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_dataset.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_dataset.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import React, { memo, useState, useEffect } from 'react';
+import React, { memo, useState, useEffect, useCallback } from 'react';
import { EuiPopover, EuiFilterButton, EuiFilterSelectItem } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
@@ -22,6 +22,9 @@ export const DatasetFilter: React.FunctionComponent<{
const [isLoading, setIsLoading] = useState(false);
const [datasetValues, setDatasetValues] = useState([AGENT_DATASET]);
+ const togglePopover = useCallback(() => setIsOpen((prevIsOpen) => !prevIsOpen), [setIsOpen]);
+ const closePopover = useCallback(() => setIsOpen(false), [setIsOpen]);
+
useEffect(() => {
const fetchValues = async () => {
setIsLoading(true);
@@ -48,7 +51,7 @@ export const DatasetFilter: React.FunctionComponent<{
button={
setIsOpen(true)}
+ onClick={togglePopover}
isSelected={isOpen}
isLoading={isLoading}
numFilters={datasetValues.length}
@@ -61,7 +64,7 @@ export const DatasetFilter: React.FunctionComponent<{
}
isOpen={isOpen}
- closePopover={() => setIsOpen(false)}
+ closePopover={closePopover}
panelPaddingSize="none"
>
{datasetValues.map((dataset) => (
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_log_level.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_log_level.tsx
index 120f21fe68207a..b423f3a8a57b37 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_log_level.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_log_level.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import React, { memo, useState, useEffect } from 'react';
+import React, { memo, useState, useEffect, useCallback } from 'react';
import { EuiPopover, EuiFilterButton, EuiFilterSelectItem } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
@@ -33,6 +33,9 @@ export const LogLevelFilter: React.FunctionComponent<{
const [isLoading, setIsLoading] = useState(false);
const [levelValues, setLevelValues] = useState([]);
+ const togglePopover = useCallback(() => setIsOpen((prevIsOpen) => !prevIsOpen), []);
+ const closePopover = useCallback(() => setIsOpen(false), []);
+
useEffect(() => {
const fetchValues = async () => {
setIsLoading(true);
@@ -59,7 +62,7 @@ export const LogLevelFilter: React.FunctionComponent<{
button={
setIsOpen(true)}
+ onClick={togglePopover}
isSelected={isOpen}
isLoading={isLoading}
numFilters={levelValues.length}
@@ -72,7 +75,7 @@ export const LogLevelFilter: React.FunctionComponent<{
}
isOpen={isOpen}
- closePopover={() => setIsOpen(false)}
+ closePopover={closePopover}
panelPaddingSize="none"
>
{levelValues.map((level) => (
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx
index 0b13fcc9c72be3..fae88fcda4bc83 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx
@@ -137,7 +137,7 @@ export const AgentUnenrollAgentModal: React.FunctionComponent = ({
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/markdown_renderers.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/markdown_renderers.tsx
index cbc2f7b5f78881..2cbdfe3671c4e6 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/markdown_renderers.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/markdown_renderers.tsx
@@ -24,6 +24,12 @@ const REL_NOFOLLOW = 'nofollow';
/** prevents the browser from sending the current address as referrer via the Referer HTTP header */
const REL_NOREFERRER = 'noreferrer';
+// Maps deprecated code block languages to supported ones in prism.js
+const CODE_LANGUAGE_OVERRIDES: Record = {
+ $json: 'json',
+ $yml: 'yml',
+};
+
export const markdownRenderers = {
root: ({ children }: { children: React.ReactNode[] }) => (
{children}
@@ -60,8 +66,17 @@ export const markdownRenderers = {
),
code: ({ language, value }: { language: string; value: string }) => {
- // Old packages are using `$json`, which is not valid any more with the move to prism.js
- const parsedLang = language === '$json' ? 'json' : language;
+ let parsedLang = language;
+
+ // Some integrations export code block content that includes language tags that have since
+ // been removed or deprecated in `prism.js`, the upstream depedency that handles syntax highlighting
+ // in EuiCodeBlock components
+ const languageOverride = CODE_LANGUAGE_OVERRIDES[language];
+
+ if (languageOverride) {
+ parsedLang = languageOverride;
+ }
+
return (
{value}
diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.ts b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.ts
index d2e7c4089e88bd..5c292187982dc0 100644
--- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.ts
+++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.ts
@@ -11,6 +11,7 @@ jest.mock('../../hooks/use_request', () => {
...module,
useGetSettings: jest.fn(),
sendGetFleetStatus: jest.fn(),
+ sendGetOneAgentPolicy: jest.fn(),
};
});
diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx
index f68b1b878c51c0..18296134ee1a71 100644
--- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx
+++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx
@@ -16,7 +16,7 @@ import { coreMock } from 'src/core/public/mocks';
import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public';
import type { AgentPolicy } from '../../../common';
-import { useGetSettings, sendGetFleetStatus } from '../../hooks/use_request';
+import { useGetSettings, sendGetFleetStatus, sendGetOneAgentPolicy } from '../../hooks/use_request';
import { FleetStatusProvider, ConfigContext } from '../../hooks';
import { useFleetServerInstructions } from '../../applications/fleet/sections/agents/agent_requirements_page/components';
@@ -79,6 +79,10 @@ describe(' ', () => {
data: { isReady: true },
});
+ (sendGetOneAgentPolicy as jest.Mock).mockResolvedValue({
+ data: { item: { package_policies: [] } },
+ });
+
(useFleetServerInstructions as jest.Mock).mockReturnValue({
serviceToken: 'test',
getServiceToken: jest.fn(),
diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx
index 9b82b2a80b5e14..87911e5d6c2c78 100644
--- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx
+++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx
@@ -22,7 +22,9 @@ import {
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
-import { useGetSettings, useUrlModal } from '../../hooks';
+import { useGetSettings, useUrlModal, sendGetOneAgentPolicy, useFleetStatus } from '../../hooks';
+import { FLEET_SERVER_PACKAGE } from '../../constants';
+import type { PackagePolicy } from '../../types';
import { ManagedInstructions } from './managed_instructions';
import { StandaloneInstructions } from './standalone_instructions';
@@ -63,6 +65,30 @@ export const AgentEnrollmentFlyout: React.FunctionComponent = ({
}
}, [modal, lastModal, settings]);
+ const fleetStatus = useFleetStatus();
+ const [policyId, setSelectedPolicyId] = useState(agentPolicy?.id);
+ const [isFleetServerPolicySelected, setIsFleetServerPolicySelected] = useState(false);
+
+ useEffect(() => {
+ async function checkPolicyIsFleetServer() {
+ if (policyId && setIsFleetServerPolicySelected) {
+ const agentPolicyRequest = await sendGetOneAgentPolicy(policyId);
+ if (
+ agentPolicyRequest.data?.item &&
+ (agentPolicyRequest.data.item.package_policies as PackagePolicy[]).some(
+ (packagePolicy) => packagePolicy.package?.name === FLEET_SERVER_PACKAGE
+ )
+ ) {
+ setIsFleetServerPolicySelected(true);
+ } else {
+ setIsFleetServerPolicySelected(false);
+ }
+ }
+ }
+
+ checkPolicyIsFleetServer();
+ }, [policyId]);
+
const isLoadingInitialRequest = settings.isLoading && settings.isInitialRequest;
return (
@@ -110,16 +136,23 @@ export const AgentEnrollmentFlyout: React.FunctionComponent = ({
) : undefined
}
>
- {fleetServerHosts.length === 0 && mode === 'managed' ? null : mode === 'managed' ? (
+ {mode === 'managed' ? (
) : (
diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/managed_instructions.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/managed_instructions.tsx
index 61f86335cd7f93..8054c48fbbaa80 100644
--- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/managed_instructions.tsx
+++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/managed_instructions.tsx
@@ -11,7 +11,7 @@ import type { EuiContainedStepProps } from '@elastic/eui/src/components/steps/st
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
-import { useGetOneEnrollmentAPIKey, useGetSettings, useLink, useFleetStatus } from '../../hooks';
+import { useGetOneEnrollmentAPIKey, useLink, useFleetStatus } from '../../hooks';
import { ManualInstructions } from '../../components/enrollment_instructions';
import {
@@ -56,14 +56,19 @@ const FleetServerMissingRequirements = () => {
};
export const ManagedInstructions = React.memo(
- ({ agentPolicy, agentPolicies, viewDataStep }) => {
+ ({
+ agentPolicy,
+ agentPolicies,
+ viewDataStep,
+ setSelectedPolicyId,
+ isFleetServerPolicySelected,
+ settings,
+ }) => {
const fleetStatus = useFleetStatus();
const [selectedApiKeyId, setSelectedAPIKeyId] = useState();
- const [isFleetServerPolicySelected, setIsFleetServerPolicySelected] = useState(false);
const apiKey = useGetOneEnrollmentAPIKey(selectedApiKeyId);
- const settings = useGetSettings();
const fleetServerInstructions = useFleetServerInstructions(apiKey?.data?.item?.policy_id);
const fleetServerSteps = useMemo(() => {
@@ -88,7 +93,7 @@ export const ManagedInstructions = React.memo(
}, [fleetServerInstructions]);
const steps = useMemo(() => {
- const fleetServerHosts = settings.data?.item?.fleet_server_hosts || [];
+ const fleetServerHosts = settings?.fleet_server_hosts || [];
const baseSteps: EuiContainedStepProps[] = [
DownloadStep(),
!agentPolicy
@@ -96,7 +101,7 @@ export const ManagedInstructions = React.memo(
agentPolicies,
selectedApiKeyId,
setSelectedAPIKeyId,
- setIsFleetServerPolicySelected,
+ setSelectedPolicyId,
})
: AgentEnrollmentKeySelectionStep({ agentPolicy, selectedApiKeyId, setSelectedAPIKeyId }),
];
@@ -121,30 +126,39 @@ export const ManagedInstructions = React.memo(
}, [
agentPolicy,
selectedApiKeyId,
+ setSelectedPolicyId,
setSelectedAPIKeyId,
agentPolicies,
apiKey.data,
fleetServerSteps,
isFleetServerPolicySelected,
- settings.data?.item?.fleet_server_hosts,
+ settings?.fleet_server_hosts,
viewDataStep,
]);
+ if (fleetStatus.isReady && settings?.fleet_server_hosts.length === 0) {
+ return null;
+ }
+
+ if (fleetStatus.isReady) {
+ return (
+ <>
+
+
+
+
+
+ >
+ );
+ }
+
return (
<>
- {fleetStatus.isReady ? (
- <>
-
-
-
-
-
- >
- ) : fleetStatus.missingRequirements?.length === 1 &&
- fleetStatus.missingRequirements[0] === 'fleet_server' ? (
+ {fleetStatus.missingRequirements?.length === 1 &&
+ fleetStatus.missingRequirements[0] === 'fleet_server' ? (
) : (
diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps.tsx
index 6cffa39628d923..1cfdc45fb7dba0 100644
--- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps.tsx
+++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps.tsx
@@ -11,9 +11,8 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import semver from 'semver';
-import type { AgentPolicy, PackagePolicy } from '../../types';
-import { sendGetOneAgentPolicy, useKibanaVersion } from '../../hooks';
-import { FLEET_SERVER_PACKAGE } from '../../constants';
+import type { AgentPolicy } from '../../types';
+import { useKibanaVersion } from '../../hooks';
import { EnrollmentStepAgentPolicy } from './agent_policy_selection';
import { AdvancedAgentAuthenticationSettings } from './advanced_agent_authentication_settings';
@@ -69,13 +68,11 @@ export const AgentPolicySelectionStep = ({
selectedApiKeyId,
setSelectedAPIKeyId,
excludeFleetServer,
- setIsFleetServerPolicySelected,
}: {
agentPolicies?: AgentPolicy[];
setSelectedPolicyId?: (policyId?: string) => void;
selectedApiKeyId?: string;
setSelectedAPIKeyId?: (key?: string) => void;
- setIsFleetServerPolicySelected?: (selected: boolean) => void;
excludeFleetServer?: boolean;
}) => {
const regularAgentPolicies = useMemo(() => {
@@ -92,21 +89,8 @@ export const AgentPolicySelectionStep = ({
if (setSelectedPolicyId) {
setSelectedPolicyId(policyId);
}
- if (policyId && setIsFleetServerPolicySelected) {
- const agentPolicyRequest = await sendGetOneAgentPolicy(policyId);
- if (
- agentPolicyRequest.data?.item &&
- (agentPolicyRequest.data.item.package_policies as PackagePolicy[]).some(
- (packagePolicy) => packagePolicy.package?.name === FLEET_SERVER_PACKAGE
- )
- ) {
- setIsFleetServerPolicySelected(true);
- } else {
- setIsFleetServerPolicySelected(false);
- }
- }
},
- [setIsFleetServerPolicySelected, setSelectedPolicyId]
+ [setSelectedPolicyId]
);
return {
diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts
index 9ee514c634655f..282a5b243caed2 100644
--- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts
+++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts
@@ -7,7 +7,7 @@
import type { EuiStepProps } from '@elastic/eui';
-import type { AgentPolicy } from '../../types';
+import type { AgentPolicy, Settings } from '../../types';
export interface BaseProps {
/**
@@ -27,4 +27,10 @@ export interface BaseProps {
* in some way. This is an area for consumers to render a button and text explaining how data can be viewed.
*/
viewDataStep?: EuiStepProps;
+
+ settings?: Settings;
+
+ setSelectedPolicyId?: (policyId?: string) => void;
+
+ isFleetServerPolicySelected?: boolean;
}
diff --git a/x-pack/plugins/fleet/public/types/index.ts b/x-pack/plugins/fleet/public/types/index.ts
index f21552d68e77b6..c91ec42d3e5277 100644
--- a/x-pack/plugins/fleet/public/types/index.ts
+++ b/x-pack/plugins/fleet/public/types/index.ts
@@ -27,6 +27,7 @@ export {
PackagePolicyPackage,
Output,
DataStream,
+ Settings,
// API schema - misc setup, status
GetFleetStatusResponse,
// API schemas - Agent policy
diff --git a/x-pack/plugins/graph/public/plugin.ts b/x-pack/plugins/graph/public/plugin.ts
index 4525b42b3feb45..ec19e639b91c96 100644
--- a/x-pack/plugins/graph/public/plugin.ts
+++ b/x-pack/plugins/graph/public/plugin.ts
@@ -19,10 +19,7 @@ import {
} from '../../../../src/core/public';
import { Storage } from '../../../../src/plugins/kibana_utils/public';
-import {
- initAngularBootstrap,
- KibanaLegacyStart,
-} from '../../../../src/plugins/kibana_legacy/public';
+import { KibanaLegacyStart } from '../../../../src/plugins/kibana_legacy/public';
import { NavigationPublicPluginStart as NavigationStart } from '../../../../src/plugins/navigation/public';
import { DataPublicPluginStart } from '../../../../src/plugins/data/public';
@@ -77,7 +74,6 @@ export class GraphPlugin
const config = this.initializerContext.config.get();
- initAngularBootstrap();
core.application.register({
id: 'graph',
title: 'Graph',
@@ -88,6 +84,7 @@ export class GraphPlugin
updater$: this.appUpdater$,
mount: async (params: AppMountParameters) => {
const [coreStart, pluginsStart] = await core.getStartServices();
+ await pluginsStart.kibanaLegacy.loadAngularBootstrap();
coreStart.chrome.docTitle.change(
i18n.translate('xpack.graph.pageTitle', { defaultMessage: 'Graph' })
);
diff --git a/x-pack/plugins/console_extensions/server/lib/spec_definitions/js/index.ts b/x-pack/plugins/infra/common/alerting/logs/log_threshold/index.ts
similarity index 82%
rename from x-pack/plugins/console_extensions/server/lib/spec_definitions/js/index.ts
rename to x-pack/plugins/infra/common/alerting/logs/log_threshold/index.ts
index ef5a474df32d5a..3f4cbc82c405cd 100644
--- a/x-pack/plugins/console_extensions/server/lib/spec_definitions/js/index.ts
+++ b/x-pack/plugins/infra/common/alerting/logs/log_threshold/index.ts
@@ -5,4 +5,5 @@
* 2.0.
*/
-export { processors } from './ingest';
+export * from './rule_data';
+export * from './types';
diff --git a/x-pack/plugins/infra/common/alerting/logs/log_threshold/rule_data.ts b/x-pack/plugins/infra/common/alerting/logs/log_threshold/rule_data.ts
new file mode 100644
index 00000000000000..dd607392897562
--- /dev/null
+++ b/x-pack/plugins/infra/common/alerting/logs/log_threshold/rule_data.ts
@@ -0,0 +1,19 @@
+/*
+ * 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 { jsonRt } from '@kbn/io-ts-utils/target/json_rt';
+import * as rt from 'io-ts';
+import { alertParamsRT as logThresholdAlertParamsRT } from './types';
+
+export const serializedParamsKey = 'serialized_params';
+
+export const logThresholdRuleDataNamespace = 'log_threshold_rule';
+export const logThresholdRuleDataSerializedParamsKey = `${logThresholdRuleDataNamespace}.${serializedParamsKey}` as const;
+
+export const logThresholdRuleDataRT = rt.type({
+ [logThresholdRuleDataSerializedParamsKey]: rt.array(jsonRt.pipe(logThresholdAlertParamsRT)),
+});
diff --git a/x-pack/plugins/infra/common/constants.ts b/x-pack/plugins/infra/common/constants.ts
index 9362293fce82f5..1c3aa550f2f629 100644
--- a/x-pack/plugins/infra/common/constants.ts
+++ b/x-pack/plugins/infra/common/constants.ts
@@ -11,3 +11,8 @@ export const LOGS_INDEX_PATTERN = 'logs-*,filebeat-*,kibana_sample_data_logs*';
export const TIMESTAMP_FIELD = '@timestamp';
export const METRICS_APP = 'metrics';
export const LOGS_APP = 'logs';
+
+export const METRICS_FEATURE_ID = 'infrastructure';
+export const LOGS_FEATURE_ID = 'logs';
+
+export type InfraFeatureId = typeof METRICS_FEATURE_ID | typeof LOGS_FEATURE_ID;
diff --git a/x-pack/plugins/infra/kibana.json b/x-pack/plugins/infra/kibana.json
index ec1b11c90f7a31..981036114282ed 100644
--- a/x-pack/plugins/infra/kibana.json
+++ b/x-pack/plugins/infra/kibana.json
@@ -12,7 +12,8 @@
"visTypeTimeseries",
"alerting",
"triggersActionsUi",
- "observability"
+ "observability",
+ "ruleRegistry"
],
"optionalPlugins": ["ml", "home", "embeddable"],
"server": true,
diff --git a/x-pack/plugins/infra/public/alerting/common/components/metrics_alert_dropdown.tsx b/x-pack/plugins/infra/public/alerting/common/components/metrics_alert_dropdown.tsx
index cf84ea40d64ccf..1f2998db4b43fe 100644
--- a/x-pack/plugins/infra/public/alerting/common/components/metrics_alert_dropdown.tsx
+++ b/x-pack/plugins/infra/public/alerting/common/components/metrics_alert_dropdown.tsx
@@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n';
import React, { useState, useCallback, useMemo } from 'react';
import {
EuiPopover,
- EuiButtonEmpty,
+ EuiHeaderLink,
EuiContextMenu,
EuiContextMenuPanelDescriptor,
} from '@elastic/eui';
@@ -134,8 +134,7 @@ export const MetricsAlertDropdown = () => {
panelPaddingSize="none"
anchorPosition="downLeft"
button={
- {
id="xpack.infra.alerting.alertsButton"
defaultMessage="Alerts and rules"
/>
-
+
}
isOpen={popoverOpen}
closePopover={closePopover}
diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_dropdown.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_dropdown.tsx
index f3481cab733603..302de15db9f5a0 100644
--- a/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_dropdown.tsx
+++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_dropdown.tsx
@@ -7,7 +7,7 @@
import React, { useState, useCallback, useMemo } from 'react';
import { i18n } from '@kbn/i18n';
-import { EuiPopover, EuiButtonEmpty, EuiContextMenuItem, EuiContextMenuPanel } from '@elastic/eui';
+import { EuiPopover, EuiContextMenuItem, EuiContextMenuPanel, EuiHeaderLink } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { AlertFlyout } from './alert_flyout';
import { useLinkProps } from '../../../hooks/use_link_props';
@@ -83,8 +83,7 @@ export const AlertDropdown = () => {
{
id="xpack.infra.alerting.logs.alertsButton"
defaultMessage="Alerts and rules"
/>
-
+
}
isOpen={popoverOpen}
closePopover={closePopover}
diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/index.ts b/x-pack/plugins/infra/public/alerting/log_threshold/index.ts
index 5bd64de2f3ac26..0f2746b4469278 100644
--- a/x-pack/plugins/infra/public/alerting/log_threshold/index.ts
+++ b/x-pack/plugins/infra/public/alerting/log_threshold/index.ts
@@ -5,5 +5,5 @@
* 2.0.
*/
-export { getAlertType } from './log_threshold_alert_type';
+export * from './log_threshold_alert_type';
export { AlertDropdown } from './components/alert_dropdown';
diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_alert_type.ts b/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_alert_type.ts
index 2c8a6a7ea286a9..44097fd005cc7b 100644
--- a/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_alert_type.ts
+++ b/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_alert_type.ts
@@ -7,14 +7,15 @@
import { i18n } from '@kbn/i18n';
import React from 'react';
-import { AlertTypeModel } from '../../../../triggers_actions_ui/public';
+import { ObservabilityRuleTypeModel } from '../../../../observability/public';
import {
LOG_DOCUMENT_COUNT_ALERT_TYPE_ID,
PartialAlertParams,
} from '../../../common/alerting/logs/log_threshold/types';
+import { formatReason } from './rule_data_formatters';
import { validateExpression } from './validation';
-export function getAlertType(): AlertTypeModel {
+export function createLogThresholdAlertType(): ObservabilityRuleTypeModel {
return {
id: LOG_DOCUMENT_COUNT_ALERT_TYPE_ID,
description: i18n.translate('xpack.infra.logs.alertFlyout.alertDescription', {
@@ -33,5 +34,6 @@ export function getAlertType(): AlertTypeModel {
}
),
requiresAppContext: false,
+ format: formatReason,
};
}
diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/rule_data_formatters.ts b/x-pack/plugins/infra/public/alerting/log_threshold/rule_data_formatters.ts
new file mode 100644
index 00000000000000..6ca081ffbc5ef6
--- /dev/null
+++ b/x-pack/plugins/infra/public/alerting/log_threshold/rule_data_formatters.ts
@@ -0,0 +1,87 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { i18n } from '@kbn/i18n';
+import {
+ ALERT_EVALUATION_THRESHOLD,
+ ALERT_EVALUATION_VALUE,
+ ALERT_ID,
+ ALERT_START,
+} from '@kbn/rule-data-utils';
+import { modifyUrl } from '@kbn/std';
+import { fold } from 'fp-ts/lib/Either';
+import { pipe } from 'fp-ts/lib/function';
+import { ObservabilityRuleTypeFormatter } from '../../../../observability/public';
+import {
+ ComparatorToi18nMap,
+ logThresholdRuleDataRT,
+ logThresholdRuleDataSerializedParamsKey,
+ ratioAlertParamsRT,
+} from '../../../common/alerting/logs/log_threshold';
+
+export const formatReason: ObservabilityRuleTypeFormatter = ({ fields }) => {
+ const reason = pipe(
+ logThresholdRuleDataRT.decode(fields),
+ fold(
+ () =>
+ i18n.translate('xpack.infra.logs.alerting.threshold.unknownReasonDescription', {
+ defaultMessage: 'unknown reason',
+ }),
+ (logThresholdRuleData) => {
+ const params = logThresholdRuleData[logThresholdRuleDataSerializedParamsKey][0];
+
+ const actualCount = fields[ALERT_EVALUATION_VALUE];
+ const groupName = fields[ALERT_ID];
+ const isGrouped = (params.groupBy?.length ?? 0) > 0;
+ const isRatio = ratioAlertParamsRT.is(params);
+ const thresholdCount = fields[ALERT_EVALUATION_THRESHOLD];
+ const translatedComparator = ComparatorToi18nMap[params.count.comparator];
+
+ if (isRatio) {
+ return i18n.translate('xpack.infra.logs.alerting.threshold.ratioAlertReasonDescription', {
+ defaultMessage:
+ '{isGrouped, select, true{{groupName}: } false{}}The log entries ratio is {actualCount} ({translatedComparator} {thresholdCount}).',
+ values: {
+ actualCount,
+ translatedComparator,
+ groupName,
+ isGrouped,
+ thresholdCount,
+ },
+ });
+ } else {
+ return i18n.translate('xpack.infra.logs.alerting.threshold.countAlertReasonDescription', {
+ defaultMessage:
+ '{isGrouped, select, true{{groupName}: } false{}}{actualCount, plural, one {{actualCount} log entry} other {{actualCount} log entries} } ({translatedComparator} {thresholdCount}) match the configured conditions.',
+ values: {
+ actualCount,
+ translatedComparator,
+ groupName,
+ isGrouped,
+ thresholdCount,
+ },
+ });
+ }
+ }
+ )
+ );
+
+ const alertStartDate = fields[ALERT_START];
+ const timestamp = alertStartDate != null ? new Date(alertStartDate).valueOf() : null;
+ const link = modifyUrl('/app/logs/link-to/default/logs', ({ query, ...otherUrlParts }) => ({
+ ...otherUrlParts,
+ query: {
+ ...query,
+ ...(timestamp != null ? { time: `${timestamp}` } : {}),
+ },
+ }));
+
+ return {
+ reason,
+ link, // TODO: refactor to URL generators
+ };
+};
diff --git a/x-pack/plugins/infra/public/pages/logs/page_content.tsx b/x-pack/plugins/infra/public/pages/logs/page_content.tsx
index 8175a95f6a0649..d8b5667e60d04c 100644
--- a/x-pack/plugins/infra/public/pages/logs/page_content.tsx
+++ b/x-pack/plugins/infra/public/pages/logs/page_content.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui';
+import { EuiHeaderLinks, EuiHeaderLink } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useContext } from 'react';
import { Route, Switch } from 'react-router-dom';
@@ -78,28 +78,19 @@ export const LogsPageContent: React.FunctionComponent = () => {
{setHeaderActionMenu && (
-
-
-
- {settingsTabTitle}
-
-
-
-
-
-
-
- {ADD_DATA_LABEL}
-
-
-
+
+
+ {settingsTabTitle}
+
+
+
+ {ADD_DATA_LABEL}
+
+
)}
diff --git a/x-pack/plugins/infra/public/pages/metrics/index.tsx b/x-pack/plugins/infra/public/pages/metrics/index.tsx
index 045fcb57ae9436..d4845a4dd9e449 100644
--- a/x-pack/plugins/infra/public/pages/metrics/index.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/index.tsx
@@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n';
import React, { useContext } from 'react';
import { Route, RouteComponentProps, Switch } from 'react-router-dom';
-import { EuiErrorBoundary, EuiFlexItem, EuiFlexGroup, EuiButtonEmpty } from '@elastic/eui';
+import { EuiErrorBoundary, EuiHeaderLinks, EuiHeaderLink } from '@elastic/eui';
import { IIndexPattern } from 'src/plugins/data/common';
import { MetricsSourceConfigurationProperties } from '../../../common/metrics_sources';
import { DocumentTitle } from '../../components/document_title';
@@ -86,31 +86,22 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => {
{setHeaderActionMenu && (
-
-
-
- {settingsTabTitle}
-
-
-
-
-
-
-
-
-
-
- {ADD_DATA_LABEL}
-
-
-
+
+
+ {settingsTabTitle}
+
+
+
+
+ {ADD_DATA_LABEL}
+
+
)}
diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomaly_detection_flyout.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomaly_detection_flyout.tsx
index d2cd4f87a53422..4e28fb4202bdc2 100644
--- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomaly_detection_flyout.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomaly_detection_flyout.tsx
@@ -6,7 +6,7 @@
*/
import React, { useState, useCallback } from 'react';
-import { EuiButtonEmpty, EuiFlyout } from '@elastic/eui';
+import { EuiHeaderLink, EuiFlyout } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { FlyoutHome } from './flyout_home';
import { JobSetupScreen } from './job_setup_screen';
@@ -50,8 +50,7 @@ export const AnomalyDetectionFlyout = () => {
return (
<>
- {
id="xpack.infra.ml.anomalyDetectionButton"
defaultMessage="Anomaly detection"
/>
-
+
{showFlyout && (
;
-type LogThresholdAlertServices = AlertServices<
- AlertInstanceState,
- AlertInstanceContext,
- LogThresholdActionGroups
->;
-type LogThresholdAlertExecutorOptions = AlertExecutorOptions<
- AlertTypeParams,
- AlertTypeState,
- AlertInstanceState,
- AlertInstanceContext,
+export type LogThresholdActionGroups = ActionGroupIdsOf;
+export type LogThresholdAlertTypeParams = AlertParams;
+export type LogThresholdAlertTypeState = AlertTypeState; // no specific state used
+export type LogThresholdAlertInstanceState = AlertInstanceState; // no specific state used
+export type LogThresholdAlertInstanceContext = AlertInstanceContext; // no specific instance context used
+
+type LogThresholdAlertInstance = AlertInstance<
+ LogThresholdAlertInstanceState,
+ LogThresholdAlertInstanceContext,
LogThresholdActionGroups
>;
+type LogThresholdAlertInstanceFactory = (
+ id: string,
+ threshold: number,
+ value: number
+) => LogThresholdAlertInstance;
const COMPOSITE_GROUP_SIZE = 2000;
@@ -75,9 +80,26 @@ const checkValueAgainstComparatorMap: {
// With forks for group_by vs ungrouped, and ratio vs non-ratio.
export const createLogThresholdExecutor = (libs: InfraBackendLibs) =>
- async function ({ services, params }: LogThresholdAlertExecutorOptions) {
- const { alertInstanceFactory, savedObjectsClient, scopedClusterClient } = services;
+ libs.logsRules.createLifecycleRuleExecutor<
+ LogThresholdAlertTypeParams,
+ LogThresholdAlertTypeState,
+ LogThresholdAlertInstanceState,
+ LogThresholdAlertInstanceContext,
+ LogThresholdActionGroups
+ >(async ({ services, params }) => {
+ const { alertWithLifecycle, savedObjectsClient, scopedClusterClient } = services;
const { sources } = libs;
+ const alertInstanceFactory: LogThresholdAlertInstanceFactory = (id, threshold, value) =>
+ alertWithLifecycle({
+ id,
+ fields: {
+ [ALERT_EVALUATION_THRESHOLD]: threshold,
+ [ALERT_EVALUATION_VALUE]: value,
+ ...logThresholdRuleDataRT.encode({
+ [logThresholdRuleDataSerializedParamsKey]: [params],
+ }),
+ },
+ });
const sourceConfiguration = await sources.getSourceConfiguration(savedObjectsClient, 'default');
const { indices, timestampField, runtimeMappings } = await resolveLogSourceConfiguration(
@@ -113,7 +135,7 @@ export const createLogThresholdExecutor = (libs: InfraBackendLibs) =>
} catch (e) {
throw new Error(e);
}
- };
+ });
async function executeAlert(
alertParams: CountAlertParams,
@@ -121,7 +143,7 @@ async function executeAlert(
indexPattern: string,
runtimeMappings: estypes.MappingRuntimeFields,
esClient: ElasticsearchClient,
- alertInstanceFactory: LogThresholdAlertServices['alertInstanceFactory']
+ alertInstanceFactory: LogThresholdAlertInstanceFactory
) {
const query = getESQuery(alertParams, timestampField, indexPattern, runtimeMappings);
@@ -152,7 +174,7 @@ async function executeRatioAlert(
indexPattern: string,
runtimeMappings: estypes.MappingRuntimeFields,
esClient: ElasticsearchClient,
- alertInstanceFactory: LogThresholdAlertServices['alertInstanceFactory']
+ alertInstanceFactory: LogThresholdAlertInstanceFactory
) {
// Ratio alert params are separated out into two standard sets of alert params
const numeratorParams: AlertParams = {
@@ -214,14 +236,14 @@ const getESQuery = (
export const processUngroupedResults = (
results: UngroupedSearchQueryResponse,
params: CountAlertParams,
- alertInstanceFactory: LogThresholdAlertExecutorOptions['services']['alertInstanceFactory'],
+ alertInstanceFactory: LogThresholdAlertInstanceFactory,
alertInstaceUpdater: AlertInstanceUpdater
) => {
const { count, criteria } = params;
const documentCount = results.hits.total.value;
if (checkValueAgainstComparatorMap[count.comparator](documentCount, count.value)) {
- const alertInstance = alertInstanceFactory(UNGROUPED_FACTORY_KEY);
+ const alertInstance = alertInstanceFactory(UNGROUPED_FACTORY_KEY, count.value, documentCount);
alertInstaceUpdater(alertInstance, AlertStates.ALERT, [
{
actionGroup: FIRED_ACTIONS.id,
@@ -240,7 +262,7 @@ export const processUngroupedRatioResults = (
numeratorResults: UngroupedSearchQueryResponse,
denominatorResults: UngroupedSearchQueryResponse,
params: RatioAlertParams,
- alertInstanceFactory: LogThresholdAlertExecutorOptions['services']['alertInstanceFactory'],
+ alertInstanceFactory: LogThresholdAlertInstanceFactory,
alertInstaceUpdater: AlertInstanceUpdater
) => {
const { count, criteria } = params;
@@ -250,7 +272,7 @@ export const processUngroupedRatioResults = (
const ratio = getRatio(numeratorCount, denominatorCount);
if (ratio !== undefined && checkValueAgainstComparatorMap[count.comparator](ratio, count.value)) {
- const alertInstance = alertInstanceFactory(UNGROUPED_FACTORY_KEY);
+ const alertInstance = alertInstanceFactory(UNGROUPED_FACTORY_KEY, count.value, ratio);
alertInstaceUpdater(alertInstance, AlertStates.ALERT, [
{
actionGroup: FIRED_ACTIONS.id,
@@ -308,7 +330,7 @@ const getReducedGroupByResults = (
export const processGroupByResults = (
results: GroupedSearchQueryResponse['aggregations']['groups']['buckets'],
params: CountAlertParams,
- alertInstanceFactory: LogThresholdAlertExecutorOptions['services']['alertInstanceFactory'],
+ alertInstanceFactory: LogThresholdAlertInstanceFactory,
alertInstaceUpdater: AlertInstanceUpdater
) => {
const { count, criteria } = params;
@@ -319,7 +341,7 @@ export const processGroupByResults = (
const documentCount = group.documentCount;
if (checkValueAgainstComparatorMap[count.comparator](documentCount, count.value)) {
- const alertInstance = alertInstanceFactory(group.name);
+ const alertInstance = alertInstanceFactory(group.name, count.value, documentCount);
alertInstaceUpdater(alertInstance, AlertStates.ALERT, [
{
actionGroup: FIRED_ACTIONS.id,
@@ -339,7 +361,7 @@ export const processGroupByRatioResults = (
numeratorResults: GroupedSearchQueryResponse['aggregations']['groups']['buckets'],
denominatorResults: GroupedSearchQueryResponse['aggregations']['groups']['buckets'],
params: RatioAlertParams,
- alertInstanceFactory: LogThresholdAlertExecutorOptions['services']['alertInstanceFactory'],
+ alertInstanceFactory: LogThresholdAlertInstanceFactory,
alertInstaceUpdater: AlertInstanceUpdater
) => {
const { count, criteria } = params;
@@ -360,7 +382,7 @@ export const processGroupByRatioResults = (
ratio !== undefined &&
checkValueAgainstComparatorMap[count.comparator](ratio, count.value)
) {
- const alertInstance = alertInstanceFactory(numeratorGroup.name);
+ const alertInstance = alertInstanceFactory(numeratorGroup.name, count.value, ratio);
alertInstaceUpdater(alertInstance, AlertStates.ALERT, [
{
actionGroup: FIRED_ACTIONS.id,
diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_alert_type.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_alert_type.ts
index 62d92d0487ff74..3d0bac3dd2bf5e 100644
--- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_alert_type.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_alert_type.ts
@@ -6,14 +6,7 @@
*/
import { i18n } from '@kbn/i18n';
-import {
- PluginSetupContract,
- AlertTypeParams,
- AlertTypeState,
- AlertInstanceContext,
- AlertInstanceState,
- ActionGroupIdsOf,
-} from '../../../../../alerting/server';
+import { PluginSetupContract } from '../../../../../alerting/server';
import { createLogThresholdExecutor, FIRED_ACTIONS } from './log_threshold_executor';
import {
LOG_DOCUMENT_COUNT_ALERT_TYPE_ID,
@@ -88,13 +81,7 @@ export async function registerLogThresholdAlertType(
);
}
- alertingPlugin.registerType<
- AlertTypeParams,
- AlertTypeState,
- AlertInstanceState,
- AlertInstanceContext,
- ActionGroupIdsOf
- >({
+ alertingPlugin.registerType({
id: LOG_DOCUMENT_COUNT_ALERT_TYPE_ID,
name: i18n.translate('xpack.infra.logs.alertName', {
defaultMessage: 'Log threshold',
diff --git a/x-pack/plugins/infra/server/lib/infra_types.ts b/x-pack/plugins/infra/server/lib/infra_types.ts
index 0c57ff2e05847e..332a2e499977bb 100644
--- a/x-pack/plugins/infra/server/lib/infra_types.ts
+++ b/x-pack/plugins/infra/server/lib/infra_types.ts
@@ -5,15 +5,16 @@
* 2.0.
*/
+import { handleEsError } from '../../../../../src/plugins/es_ui_shared/server';
+import { InfraConfig } from '../plugin';
+import { GetLogQueryFields } from '../services/log_queries/get_log_query_fields';
+import { RulesServiceSetup } from '../services/rules';
+import { KibanaFramework } from './adapters/framework/kibana_framework_adapter';
import { InfraFieldsDomain } from './domains/fields_domain';
import { InfraLogEntriesDomain } from './domains/log_entries_domain';
import { InfraMetricsDomain } from './domains/metrics_domain';
import { InfraSources } from './sources';
import { InfraSourceStatus } from './source_status';
-import { InfraConfig } from '../plugin';
-import { KibanaFramework } from './adapters/framework/kibana_framework_adapter';
-import { GetLogQueryFields } from '../services/log_queries/get_log_query_fields';
-import { handleEsError } from '../../../../../src/plugins/es_ui_shared/server';
export interface InfraDomainLibs {
fields: InfraFieldsDomain;
@@ -28,4 +29,6 @@ export interface InfraBackendLibs extends InfraDomainLibs {
sourceStatus: InfraSourceStatus;
getLogQueryFields: GetLogQueryFields;
handleEsError: typeof handleEsError;
+ logsRules: RulesServiceSetup;
+ metricsRules: RulesServiceSetup;
}
diff --git a/x-pack/plugins/infra/server/plugin.ts b/x-pack/plugins/infra/server/plugin.ts
index 7c5666049bd60c..de445affc178e0 100644
--- a/x-pack/plugins/infra/server/plugin.ts
+++ b/x-pack/plugins/infra/server/plugin.ts
@@ -8,7 +8,9 @@
import { Server } from '@hapi/hapi';
import { schema, TypeOf } from '@kbn/config-schema';
import { i18n } from '@kbn/i18n';
+import { Logger } from '@kbn/logging';
import { CoreSetup, PluginInitializerContext, Plugin } from 'src/core/server';
+import { LOGS_FEATURE_ID, METRICS_FEATURE_ID } from '../common/constants';
import { InfraStaticSourceConfiguration } from '../common/source_configuration/source_configuration';
import { inventoryViewSavedObjectType } from '../common/saved_objects/inventory_view';
import { metricsExplorerViewSavedObjectType } from '../common/saved_objects/metrics_explorer_view';
@@ -32,6 +34,7 @@ import { InfraPluginRequestHandlerContext } from './types';
import { UsageCollector } from './usage/usage_collector';
import { createGetLogQueryFields } from './services/log_queries/get_log_query_fields';
import { handleEsError } from '../../../../src/plugins/es_ui_shared/server';
+import { RulesService } from './services/rules';
export const config = {
schema: schema.object({
@@ -82,9 +85,25 @@ export interface InfraPluginSetup {
export class InfraServerPlugin implements Plugin {
public config: InfraConfig;
public libs: InfraBackendLibs | undefined;
+ public logger: Logger;
+
+ private logsRules: RulesService;
+ private metricsRules: RulesService;
constructor(context: PluginInitializerContext) {
this.config = context.config.get();
+ this.logger = context.logger.get();
+
+ this.logsRules = new RulesService(
+ LOGS_FEATURE_ID,
+ 'observability.logs',
+ this.logger.get('logsRules')
+ );
+ this.metricsRules = new RulesService(
+ METRICS_FEATURE_ID,
+ 'observability.metrics',
+ this.logger.get('metricsRules')
+ );
}
setup(core: CoreSetup, plugins: InfraServerPluginSetupDeps) {
@@ -126,6 +145,8 @@ export class InfraServerPlugin implements Plugin {
...domainLibs,
getLogQueryFields: createGetLogQueryFields(sources, framework),
handleEsError,
+ logsRules: this.logsRules.setup(core, plugins),
+ metricsRules: this.metricsRules.setup(core, plugins),
};
plugins.features.registerKibanaFeature(METRICS_FEATURE);
diff --git a/x-pack/plugins/infra/server/services/rules/index.ts b/x-pack/plugins/infra/server/services/rules/index.ts
new file mode 100644
index 00000000000000..eaa3d0da493e5c
--- /dev/null
+++ b/x-pack/plugins/infra/server/services/rules/index.ts
@@ -0,0 +1,9 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export * from './rules_service';
+export * from './types';
diff --git a/x-pack/plugins/infra/server/services/rules/rule_data_client.ts b/x-pack/plugins/infra/server/services/rules/rule_data_client.ts
new file mode 100644
index 00000000000000..d693be40f10d0d
--- /dev/null
+++ b/x-pack/plugins/infra/server/services/rules/rule_data_client.ts
@@ -0,0 +1,87 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { once } from 'lodash';
+import { CoreSetup, Logger } from 'src/core/server';
+import { TECHNICAL_COMPONENT_TEMPLATE_NAME } from '../../../../rule_registry/common/assets';
+import { RuleRegistryPluginSetupContract } from '../../../../rule_registry/server';
+import { logThresholdRuleDataNamespace } from '../../../common/alerting/logs/log_threshold';
+import type { InfraFeatureId } from '../../../common/constants';
+import { RuleRegistrationContext, RulesServiceStartDeps } from './types';
+
+export const createRuleDataClient = ({
+ ownerFeatureId,
+ registrationContext,
+ getStartServices,
+ logger,
+ ruleDataService,
+}: {
+ ownerFeatureId: InfraFeatureId;
+ registrationContext: RuleRegistrationContext;
+ getStartServices: CoreSetup['getStartServices'];
+ logger: Logger;
+ ruleDataService: RuleRegistryPluginSetupContract['ruleDataService'];
+}) => {
+ const initializeRuleDataTemplates = once(async () => {
+ const componentTemplateName = ruleDataService.getFullAssetName(
+ `${registrationContext}-mappings`
+ );
+
+ const indexNamePattern = ruleDataService.getFullAssetName(`${registrationContext}*`);
+
+ if (!ruleDataService.isWriteEnabled()) {
+ return;
+ }
+
+ await ruleDataService.createOrUpdateComponentTemplate({
+ name: componentTemplateName,
+ body: {
+ template: {
+ settings: {
+ number_of_shards: 1,
+ },
+ mappings: {
+ properties: {
+ [logThresholdRuleDataNamespace]: {
+ properties: {
+ serialized_params: {
+ type: 'keyword',
+ index: false,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ });
+
+ await ruleDataService.createOrUpdateIndexTemplate({
+ name: ruleDataService.getFullAssetName(registrationContext),
+ body: {
+ index_patterns: [indexNamePattern],
+ composed_of: [
+ ruleDataService.getFullAssetName(TECHNICAL_COMPONENT_TEMPLATE_NAME),
+ componentTemplateName,
+ ],
+ },
+ });
+
+ await ruleDataService.updateIndexMappingsMatchingPattern(indexNamePattern);
+ });
+
+ // initialize eagerly
+ const initializeRuleDataTemplatesPromise = initializeRuleDataTemplates().catch((err) => {
+ logger.error(err);
+ });
+
+ return ruleDataService.getRuleDataClient(
+ ownerFeatureId,
+ ruleDataService.getFullAssetName(registrationContext),
+ () => initializeRuleDataTemplatesPromise
+ );
+};
diff --git a/x-pack/plugins/infra/server/services/rules/rules_service.ts b/x-pack/plugins/infra/server/services/rules/rules_service.ts
new file mode 100644
index 00000000000000..9341fc59d75b83
--- /dev/null
+++ b/x-pack/plugins/infra/server/services/rules/rules_service.ts
@@ -0,0 +1,50 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { CoreSetup, Logger } from 'src/core/server';
+import { createLifecycleExecutor } from '../../../../rule_registry/server';
+import { InfraFeatureId } from '../../../common/constants';
+import { createRuleDataClient } from './rule_data_client';
+import {
+ RuleRegistrationContext,
+ RulesServiceSetup,
+ RulesServiceSetupDeps,
+ RulesServiceStart,
+ RulesServiceStartDeps,
+} from './types';
+
+export class RulesService {
+ constructor(
+ public readonly ownerFeatureId: InfraFeatureId,
+ public readonly registrationContext: RuleRegistrationContext,
+ private readonly logger: Logger
+ ) {}
+
+ public setup(
+ core: CoreSetup,
+ setupDeps: RulesServiceSetupDeps
+ ): RulesServiceSetup {
+ const ruleDataClient = createRuleDataClient({
+ getStartServices: core.getStartServices,
+ logger: this.logger,
+ ownerFeatureId: this.ownerFeatureId,
+ registrationContext: this.registrationContext,
+ ruleDataService: setupDeps.ruleRegistry.ruleDataService,
+ });
+
+ const createLifecycleRuleExecutor = createLifecycleExecutor(this.logger, ruleDataClient);
+
+ return {
+ createLifecycleRuleExecutor,
+ ruleDataClient,
+ };
+ }
+
+ public start(_startDeps: RulesServiceStartDeps): RulesServiceStart {
+ return {};
+ }
+}
diff --git a/x-pack/plugins/infra/server/services/rules/types.ts b/x-pack/plugins/infra/server/services/rules/types.ts
new file mode 100644
index 00000000000000..b67b79ee5d3c24
--- /dev/null
+++ b/x-pack/plugins/infra/server/services/rules/types.ts
@@ -0,0 +1,33 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { PluginSetupContract as AlertingPluginSetup } from '../../../../alerting/server';
+import {
+ createLifecycleExecutor,
+ RuleDataClient,
+ RuleRegistryPluginSetupContract,
+} from '../../../../rule_registry/server';
+
+type LifecycleRuleExecutorCreator = ReturnType;
+
+export interface RulesServiceSetupDeps {
+ alerting: AlertingPluginSetup;
+ ruleRegistry: RuleRegistryPluginSetupContract;
+}
+
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
+export interface RulesServiceStartDeps {}
+
+export interface RulesServiceSetup {
+ createLifecycleRuleExecutor: LifecycleRuleExecutorCreator;
+ ruleDataClient: RuleDataClient;
+}
+
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
+export interface RulesServiceStart {}
+
+export type RuleRegistrationContext = 'observability.logs' | 'observability.metrics';
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/network_direction.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/network_direction.test.tsx
new file mode 100644
index 00000000000000..7a4c55d6f5e027
--- /dev/null
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/network_direction.test.tsx
@@ -0,0 +1,180 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { act } from 'react-dom/test-utils';
+import { setup, SetupResult, getProcessorValue } from './processor.helpers';
+
+// Default parameter values automatically added to the network direction processor when saved
+const defaultNetworkDirectionParameters = {
+ if: undefined,
+ tag: undefined,
+ source_ip: undefined,
+ description: undefined,
+ target_field: undefined,
+ ignore_missing: undefined,
+ ignore_failure: undefined,
+ destination_ip: undefined,
+ internal_networks: undefined,
+ internal_networks_field: undefined,
+};
+
+const NETWORK_DIRECTION_TYPE = 'network_direction';
+
+describe('Processor: Network Direction', () => {
+ let onUpdate: jest.Mock;
+ let testBed: SetupResult;
+
+ beforeAll(() => {
+ jest.useFakeTimers();
+ });
+
+ afterAll(() => {
+ jest.useRealTimers();
+ });
+
+ beforeEach(async () => {
+ onUpdate = jest.fn();
+
+ await act(async () => {
+ testBed = await setup({
+ value: {
+ processors: [],
+ },
+ onFlyoutOpen: jest.fn(),
+ onUpdate,
+ });
+ });
+
+ testBed.component.update();
+
+ // Open flyout to add new processor
+ testBed.actions.addProcessor();
+ // Add type (the other fields are not visible until a type is selected)
+ await testBed.actions.addProcessorType(NETWORK_DIRECTION_TYPE);
+ });
+
+ test('prevents form submission if internal_network field is not provided', async () => {
+ const {
+ actions: { saveNewProcessor },
+ form,
+ } = testBed;
+
+ // Click submit button with only the type defined
+ await saveNewProcessor();
+
+ // Expect form error as "field" is required parameter
+ expect(form.getErrorsMessages()).toEqual(['A field value is required.']);
+ });
+
+ test('saves with default parameter values', async () => {
+ const {
+ actions: { saveNewProcessor },
+ find,
+ component,
+ } = testBed;
+
+ // Add "networkDirectionField" value (required)
+ await act(async () => {
+ find('networkDirectionField.input').simulate('change', [{ label: 'loopback' }]);
+ });
+ component.update();
+
+ // Save the field
+ await saveNewProcessor();
+
+ const processors = getProcessorValue(onUpdate, NETWORK_DIRECTION_TYPE);
+ expect(processors[0][NETWORK_DIRECTION_TYPE]).toEqual({
+ ...defaultNetworkDirectionParameters,
+ internal_networks: ['loopback'],
+ });
+ });
+
+ test('allows to set internal_networks_field', async () => {
+ const {
+ actions: { saveNewProcessor },
+ form,
+ find,
+ } = testBed;
+
+ find('toggleCustomField').simulate('click');
+
+ form.setInputValue('networkDirectionField.input', 'internal_networks_field');
+
+ // Save the field with new changes
+ await saveNewProcessor();
+
+ const processors = getProcessorValue(onUpdate, NETWORK_DIRECTION_TYPE);
+ expect(processors[0][NETWORK_DIRECTION_TYPE]).toEqual({
+ ...defaultNetworkDirectionParameters,
+ internal_networks_field: 'internal_networks_field',
+ });
+ });
+
+ test('allows to set just internal_networks_field or internal_networks', async () => {
+ const {
+ actions: { saveNewProcessor },
+ form,
+ find,
+ component,
+ } = testBed;
+
+ // Set internal_networks field
+ await act(async () => {
+ find('networkDirectionField.input').simulate('change', [{ label: 'loopback' }]);
+ });
+ component.update();
+
+ // Toggle to internal_networks_field and set a random value
+ find('toggleCustomField').simulate('click');
+ form.setInputValue('networkDirectionField.input', 'internal_networks_field');
+
+ // Save the field with new changes
+ await saveNewProcessor();
+
+ const processors = getProcessorValue(onUpdate, NETWORK_DIRECTION_TYPE);
+ expect(processors[0][NETWORK_DIRECTION_TYPE]).toEqual({
+ ...defaultNetworkDirectionParameters,
+ internal_networks_field: 'internal_networks_field',
+ });
+ });
+
+ test('allows optional parameters to be set', async () => {
+ const {
+ actions: { saveNewProcessor },
+ form,
+ find,
+ component,
+ } = testBed;
+
+ // Add "networkDirectionField" value (required)
+ await act(async () => {
+ find('networkDirectionField.input').simulate('change', [{ label: 'loopback' }]);
+ });
+ component.update();
+
+ // Set optional parameteres
+ form.toggleEuiSwitch('ignoreMissingSwitch.input');
+ form.toggleEuiSwitch('ignoreFailureSwitch.input');
+ form.setInputValue('sourceIpField.input', 'source.ip');
+ form.setInputValue('targetField.input', 'target_field');
+ form.setInputValue('destinationIpField.input', 'destination.ip');
+
+ // Save the field with new changes
+ await saveNewProcessor();
+
+ const processors = getProcessorValue(onUpdate, NETWORK_DIRECTION_TYPE);
+ expect(processors[0][NETWORK_DIRECTION_TYPE]).toEqual({
+ ...defaultNetworkDirectionParameters,
+ ignore_failure: true,
+ ignore_missing: false,
+ source_ip: 'source.ip',
+ target_field: 'target_field',
+ destination_ip: 'destination.ip',
+ internal_networks: ['loopback'],
+ });
+ });
+});
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx
index 24e1ddce008ea6..e4024e4ec67f46 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx
@@ -171,6 +171,10 @@ type TestSubject =
| 'regexFileField.input'
| 'valueFieldInput'
| 'mediaTypeSelectorField'
+ | 'networkDirectionField.input'
+ | 'sourceIpField.input'
+ | 'destinationIpField.input'
+ | 'toggleCustomField'
| 'ignoreEmptyField.input'
| 'overrideField.input'
| 'fieldsValueField.input'
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/index.ts
index 5e3e5f82478bd6..f5eb1ab3ec59be 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/index.ts
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/index.ts
@@ -28,6 +28,7 @@ export { Join } from './join';
export { Json } from './json';
export { Kv } from './kv';
export { Lowercase } from './lowercase';
+export { NetworkDirection } from './network_direction';
export { Pipeline } from './pipeline';
export { RegisteredDomain } from './registered_domain';
export { Remove } from './remove';
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/network_direction.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/network_direction.tsx
new file mode 100644
index 00000000000000..2026a77bc6566e
--- /dev/null
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/network_direction.tsx
@@ -0,0 +1,242 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { FunctionComponent, useState, useCallback, useMemo } from 'react';
+import { i18n } from '@kbn/i18n';
+import { isEmpty } from 'lodash';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiButtonEmpty, EuiCode } from '@elastic/eui';
+
+import {
+ FIELD_TYPES,
+ UseField,
+ useFormContext,
+ Field,
+ FieldHook,
+ FieldConfig,
+ SerializerFunc,
+} from '../../../../../../shared_imports';
+import { FieldsConfig, from, to } from './shared';
+import { TargetField } from './common_fields/target_field';
+import { IgnoreMissingField } from './common_fields/ignore_missing_field';
+
+interface InternalNetworkTypes {
+ internal_networks: string[];
+ internal_networks_field: string;
+}
+
+type InternalNetworkFields = {
+ [K in keyof InternalNetworkTypes]: FieldHook;
+};
+
+const internalNetworkValues: string[] = [
+ 'loopback',
+ 'unicast',
+ 'global_unicast',
+ 'multicast',
+ 'interface_local_multicast',
+ 'link_local_unicast',
+ 'link_local_multicast',
+ 'link_local_multicast',
+ 'private',
+ 'public',
+ 'unspecified',
+];
+
+const fieldsConfig: FieldsConfig = {
+ /* Optional fields config */
+ source_ip: {
+ type: FIELD_TYPES.TEXT,
+ serializer: from.emptyStringToUndefined,
+ label: i18n.translate('xpack.ingestPipelines.pipelineEditor.networkDirection.sourceIpLabel', {
+ defaultMessage: 'Source IP (optional)',
+ }),
+ helpText: (
+ {'source.ip'},
+ }}
+ />
+ ),
+ },
+ destination_ip: {
+ type: FIELD_TYPES.TEXT,
+ serializer: from.emptyStringToUndefined,
+ label: i18n.translate(
+ 'xpack.ingestPipelines.pipelineEditor.networkDirection.destinationIpLabel',
+ {
+ defaultMessage: 'Destination IP (optional)',
+ }
+ ),
+ helpText: (
+ {'destination.ip'},
+ }}
+ />
+ ),
+ },
+};
+
+const getInternalNetworkConfig: (
+ toggleCustom: () => void
+) => Record<
+ keyof InternalNetworkFields,
+ {
+ path: string;
+ config?: FieldConfig;
+ euiFieldProps?: Record;
+ labelAppend: JSX.Element;
+ }
+> = (toggleCustom: () => void) => ({
+ internal_networks: {
+ path: 'fields.internal_networks',
+ euiFieldProps: {
+ noSuggestions: false,
+ options: internalNetworkValues.map((label) => ({ label })),
+ },
+ config: {
+ type: FIELD_TYPES.COMBO_BOX,
+ deserializer: to.arrayOfStrings,
+ serializer: from.optionalArrayOfStrings,
+ fieldsToValidateOnChange: ['fields.internal_networks', 'fields.internal_networks_field'],
+ validations: [
+ {
+ validator: ({ value, path, formData }) => {
+ if (isEmpty(value) && isEmpty(formData['fields.internal_networks_field'])) {
+ return { path, message: 'A field value is required.' };
+ }
+ },
+ },
+ ],
+ label: i18n.translate(
+ 'xpack.ingestPipelines.pipelineEditor.networkDirection.internalNetworksLabel',
+ {
+ defaultMessage: 'Internal networks',
+ }
+ ),
+ helpText: (
+
+ ),
+ },
+ labelAppend: (
+
+ {i18n.translate('xpack.ingestPipelines.pipelineEditor.internalNetworkCustomLabel', {
+ defaultMessage: 'Use custom field',
+ })}
+
+ ),
+ key: 'preset',
+ },
+ internal_networks_field: {
+ path: 'fields.internal_networks_field',
+ config: {
+ type: FIELD_TYPES.TEXT,
+ serializer: from.emptyStringToUndefined,
+ fieldsToValidateOnChange: ['fields.internal_networks', 'fields.internal_networks_field'],
+ validations: [
+ {
+ validator: ({ value, path, formData }) => {
+ if (isEmpty(value) && isEmpty(formData['fields.internal_networks'])) {
+ return { path, message: 'A field value is required.' };
+ }
+ },
+ },
+ ],
+ label: i18n.translate(
+ 'xpack.ingestPipelines.pipelineEditor.networkDirection.internalNetworksFieldLabel',
+ {
+ defaultMessage: 'Internal networks field',
+ }
+ ),
+ helpText: (
+ {'internal_networks'},
+ }}
+ />
+ ),
+ },
+ labelAppend: (
+
+ {i18n.translate('xpack.ingestPipelines.pipelineEditor.internalNetworkPredefinedLabel', {
+ defaultMessage: 'Use preset field',
+ })}
+
+ ),
+ key: 'custom',
+ },
+});
+
+export const NetworkDirection: FunctionComponent = () => {
+ const { getFieldDefaultValue } = useFormContext();
+ const isInternalNetowrksFieldDefined =
+ getFieldDefaultValue('fields.internal_networks_field') !== undefined;
+ const [isCustom, setIsCustom] = useState(isInternalNetowrksFieldDefined);
+
+ const toggleCustom = useCallback(() => {
+ setIsCustom((prev) => !prev);
+ }, []);
+
+ const internalNetworkFieldProps = useMemo(
+ () =>
+ isCustom
+ ? getInternalNetworkConfig(toggleCustom).internal_networks_field
+ : getInternalNetworkConfig(toggleCustom).internal_networks,
+ [isCustom, toggleCustom]
+ );
+
+ return (
+ <>
+
+
+
+
+ {'network.direction'},
+ }}
+ />
+ }
+ />
+
+
+
+ }
+ />
+ >
+ );
+};
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx
index 983fb0ea67bb0f..e6ca465bf1a022 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx
@@ -34,6 +34,7 @@ import {
Json,
Kv,
Lowercase,
+ NetworkDirection,
Pipeline,
RegisteredDomain,
Remove,
@@ -517,6 +518,23 @@ export const mapProcessorTypeToDescriptor: MapProcessorTypeToDescriptor = {
},
}),
},
+ network_direction: {
+ FieldsComponent: NetworkDirection,
+ docLinkPath: '/network-direction-processor.html',
+ label: i18n.translate('xpack.ingestPipelines.processors.label.networkDirection', {
+ defaultMessage: 'Network Direction',
+ }),
+ typeDescription: i18n.translate(
+ 'xpack.ingestPipelines.processors.description.networkDirection',
+ {
+ defaultMessage: 'Calculates the network direction given a source IP address.',
+ }
+ ),
+ getDefaultDescription: () =>
+ i18n.translate('xpack.ingestPipelines.processors.defaultDescription.networkDirection', {
+ defaultMessage: 'Calculates the network direction given a source IP address.',
+ }),
+ },
pipeline: {
FieldsComponent: Pipeline,
docLinkPath: '/pipeline-processor.html',
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/context/processors_context.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/context/processors_context.tsx
index 0c43297e811d3c..ddf996de7805c4 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/context/processors_context.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/context/processors_context.tsx
@@ -151,7 +151,13 @@ export const PipelineProcessorsContextProvider: FunctionComponent = ({
break;
case 'managingProcessor':
// These are the option names we get back from our UI
- const knownOptionNames = Object.keys(processorTypeAndOptions.options);
+ const knownOptionNames = [
+ ...Object.keys(processorTypeAndOptions.options),
+ // We manually add fields that we **don't** want to be treated as "unknownOptions"
+ 'internal_networks',
+ 'internal_networks_field',
+ ];
+
// The processor that we are updating may have options configured the UI does not know about
const unknownOptions = omit(mode.arg.processor.options, knownOptionNames);
// In order to keep the options we don't get back from our UI, we merge the known and unknown options
diff --git a/x-pack/plugins/ingest_pipelines/public/shared_imports.ts b/x-pack/plugins/ingest_pipelines/public/shared_imports.ts
index 8ed57221a13956..29be11430bf646 100644
--- a/x-pack/plugins/ingest_pipelines/public/shared_imports.ts
+++ b/x-pack/plugins/ingest_pipelines/public/shared_imports.ts
@@ -43,6 +43,7 @@ export {
ArrayItem,
FormHook,
useFormContext,
+ UseMultiFields,
FormDataProvider,
OnFormUpdateArg,
FieldConfig,
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.test.tsx
index 19d91c1006cf05..936f1e477057d0 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.test.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.test.tsx
@@ -35,7 +35,7 @@ const operationDefinitionMap: Record = {
}),
} as unknown) as GenericOperationDefinition,
terms: { input: 'field' } as GenericOperationDefinition,
- sum: { input: 'field' } as GenericOperationDefinition,
+ sum: { input: 'field', filterable: true } as GenericOperationDefinition,
last_value: { input: 'field' } as GenericOperationDefinition,
max: { input: 'field' } as GenericOperationDefinition,
count: ({
@@ -928,6 +928,63 @@ invalid: "
).toEqual(['The operation average does not accept any parameter']);
});
+ it('returns an error if first argument type is passed multiple times', () => {
+ const formulas = [
+ 'average(bytes, bytes)',
+ "sum(bytes, kql='category.keyword: *', bytes)",
+ 'moving_average(average(bytes), average(bytes))',
+ "moving_average(average(bytes), kql='category.keyword: *', average(bytes))",
+ 'moving_average(average(bytes, bytes), count())',
+ 'moving_average(moving_average(average(bytes, bytes), count(), count()))',
+ ];
+ for (const formula of formulas) {
+ expect(
+ formulaOperation.getErrorMessage!(
+ getNewLayerWithFormula(formula),
+ 'col1',
+ indexPattern,
+ operationDefinitionMap
+ )
+ ).toEqual(
+ expect.arrayContaining([
+ expect.stringMatching(
+ /The operation (moving_average|average|sum) in the Formula requires a single (field|metric), found:/
+ ),
+ ])
+ );
+ }
+ });
+
+ it('returns an error if a function received an argument of the wrong argument type in any position', () => {
+ const formulas = [
+ 'average(bytes, count())',
+ "sum(bytes, kql='category.keyword: *', count(), count())",
+ 'average(bytes, bytes + 1)',
+ 'average(count(), bytes)',
+ 'moving_average(average(bytes), bytes)',
+ 'moving_average(bytes, bytes)',
+ 'moving_average(average(bytes), window=7, bytes)',
+ 'moving_average(window=7, bytes)',
+ "moving_average(kql='category.keyword: *', bytes)",
+ ];
+ for (const formula of formulas) {
+ expect(
+ formulaOperation.getErrorMessage!(
+ getNewLayerWithFormula(formula),
+ 'col1',
+ indexPattern,
+ operationDefinitionMap
+ )
+ ).toEqual(
+ expect.arrayContaining([
+ expect.stringMatching(
+ /The operation (moving_average|average|sum) in the Formula does not support (metric|field) parameters, found:/
+ ),
+ ])
+ );
+ }
+ });
+
it('returns an error if the parameter passed to an operation is of the wrong type', () => {
expect(
formulaOperation.getErrorMessage!(
@@ -1087,6 +1144,14 @@ invalid: "
)
).toEqual([`The first argument for ${fn} should be a field name. Found no field`]);
}
+ expect(
+ formulaOperation.getErrorMessage!(
+ getNewLayerWithFormula(`sum(kql='category.keyword: *')`),
+ 'col1',
+ indexPattern,
+ operationDefinitionMap
+ )
+ ).toEqual([`The first argument for sum should be a field name. Found category.keyword: *`]);
});
it("returns a clear error when there's a missing function for a fullReference operation", () => {
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/util.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/util.ts
index 445df21a6067eb..6d0a585db048f9 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/util.ts
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/util.ts
@@ -455,7 +455,7 @@ Example: Calculate area based on side length
},
};
-export function isMathNode(node: TinymathAST) {
+export function isMathNode(node: TinymathAST | string) {
return isObject(node) && node.type === 'function' && tinymathFunctions[node.name];
}
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/validation.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/validation.ts
index 5b7a9beaa4e324..d65ef5ada8b379 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/validation.ts
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/validation.ts
@@ -7,7 +7,7 @@
import { isObject, partition } from 'lodash';
import { i18n } from '@kbn/i18n';
-import { parse, TinymathLocation } from '@kbn/tinymath';
+import { parse, TinymathLocation, TinymathVariable } from '@kbn/tinymath';
import type { TinymathAST, TinymathFunction, TinymathNamedArgument } from '@kbn/tinymath';
import { esKuery, esQuery } from '../../../../../../../../src/plugins/data/public';
import {
@@ -63,6 +63,19 @@ interface ValidationErrors {
message: string;
type: {};
};
+ tooManyFirstArguments: {
+ message: string;
+ type: {
+ operation: string;
+ type: string;
+ text: string;
+ supported?: number;
+ };
+ };
+ wrongArgument: {
+ message: string;
+ type: { operation: string; text: string; type: string };
+ };
}
type ErrorTypes = keyof ValidationErrors;
@@ -276,6 +289,25 @@ function getMessageFromId({
defaultMessage: 'Use only one of kql= or lucene=, not both',
});
break;
+ case 'tooManyFirstArguments':
+ message = i18n.translate('xpack.lens.indexPattern.formulaOperationTooManyFirstArguments', {
+ defaultMessage:
+ 'The operation {operation} in the Formula requires a {supported, plural, one {single} other {supported}} {type}, found: {text}',
+ values: {
+ operation: out.operation,
+ text: out.text,
+ type: out.type,
+ supported: out.supported || 1,
+ },
+ });
+ break;
+ case 'wrongArgument':
+ message = i18n.translate('xpack.lens.indexPattern.formulaOperationwrongArgument', {
+ defaultMessage:
+ 'The operation {operation} in the Formula does not support {type} parameters, found: {text}',
+ values: { operation: out.operation, text: out.text, type: out.type },
+ });
+ break;
// case 'mathRequiresFunction':
// message = i18n.translate('xpack.lens.indexPattern.formulaMathRequiresFunctionLabel', {
// defaultMessage; 'The function {name} requires an Elasticsearch function',
@@ -531,14 +563,16 @@ function runFullASTValidation(
} else {
if (nodeOperation.input === 'field') {
if (shouldHaveFieldArgument(node)) {
- if (!isFirstArgumentValidType(firstArg, 'variable')) {
+ if (!isArgumentValidType(firstArg, 'variable')) {
if (isMathNode(firstArg)) {
errors.push(
getMessageFromId({
messageId: 'wrongFirstArgument',
values: {
operation: node.name,
- type: 'field',
+ type: i18n.translate('xpack.lens.indexPattern.formulaFieldValue', {
+ defaultMessage: 'field',
+ }),
argument: `math operation`,
},
locations: node.location ? [node.location] : [],
@@ -550,7 +584,9 @@ function runFullASTValidation(
messageId: 'wrongFirstArgument',
values: {
operation: node.name,
- type: 'field',
+ type: i18n.translate('xpack.lens.indexPattern.formulaFieldValue', {
+ defaultMessage: 'field',
+ }),
argument:
getValueOrName(firstArg) ||
i18n.translate('xpack.lens.indexPattern.formulaNoFieldForOperation', {
@@ -561,6 +597,25 @@ function runFullASTValidation(
})
);
}
+ } else {
+ // If the first argument is valid proceed with the other arguments validation
+ const fieldErrors = validateFieldArguments(node, variables, {
+ isFieldOperation: true,
+ firstArg,
+ });
+ if (fieldErrors.length) {
+ errors.push(...fieldErrors);
+ }
+ }
+ const functionErrors = validateFunctionArguments(node, functions, 0, {
+ isFieldOperation: true,
+ type: i18n.translate('xpack.lens.indexPattern.formulaFieldValue', {
+ defaultMessage: 'field',
+ }),
+ firstArgValidation: false,
+ });
+ if (functionErrors.length) {
+ errors.push(...functionErrors);
}
} else {
// Named arguments only
@@ -602,16 +657,20 @@ function runFullASTValidation(
if (nodeOperation.input === 'fullReference') {
// What about fn(7 + 1)? We may want to allow that
// In general this should be handled down the Esaggs route rather than here
- if (
- !isFirstArgumentValidType(firstArg, 'function') ||
- (isMathNode(firstArg) && validateMathNodes(firstArg, missingVariablesSet).length)
- ) {
+ const isFirstArgumentNotValid = Boolean(
+ !isArgumentValidType(firstArg, 'function') ||
+ (isMathNode(firstArg) && validateMathNodes(firstArg, missingVariablesSet).length)
+ );
+ // First field has a special handling
+ if (isFirstArgumentNotValid) {
errors.push(
getMessageFromId({
messageId: 'wrongFirstArgument',
values: {
operation: node.name,
- type: 'operation',
+ type: i18n.translate('xpack.lens.indexPattern.formulaOperationValue', {
+ defaultMessage: 'operation',
+ }),
argument:
getValueOrName(firstArg) ||
i18n.translate('xpack.lens.indexPattern.formulaNoOperation', {
@@ -622,6 +681,21 @@ function runFullASTValidation(
})
);
}
+ // Check for multiple function passed
+ const requiredFunctions = nodeOperation.requiredReferences
+ ? nodeOperation.requiredReferences.length
+ : 1;
+ const functionErrors = validateFunctionArguments(node, functions, requiredFunctions, {
+ isFieldOperation: false,
+ firstArgValidation: isFirstArgumentNotValid,
+ type: i18n.translate('xpack.lens.indexPattern.formulaMetricValue', {
+ defaultMessage: 'metric',
+ }),
+ });
+ if (functionErrors.length) {
+ errors.push(...functionErrors);
+ }
+
if (!canHaveParams(nodeOperation) && namedArguments.length) {
errors.push(
getMessageFromId({
@@ -633,6 +707,14 @@ function runFullASTValidation(
})
);
} else {
+ // check for fields passed at any position
+ const fieldErrors = validateFieldArguments(node, variables, {
+ isFieldOperation: false,
+ firstArg,
+ });
+ if (fieldErrors.length) {
+ errors.push(...fieldErrors);
+ }
const argumentsErrors = validateNameArguments(
node,
nodeOperation,
@@ -736,7 +818,7 @@ export function hasFunctionFieldArgument(type: string) {
return !['count'].includes(type);
}
-export function isFirstArgumentValidType(arg: TinymathAST, type: TinymathNodeTypes['type']) {
+export function isArgumentValidType(arg: TinymathAST | string, type: TinymathNodeTypes['type']) {
return isObject(arg) && arg.type === type;
}
@@ -812,3 +894,109 @@ export function validateMathNodes(root: TinymathAST, missingVariableSet: Set,
+ { isFieldOperation, firstArg }: { isFieldOperation: boolean; firstArg: TinymathAST }
+) {
+ const fields = variables.filter(
+ (arg) => isArgumentValidType(arg, 'variable') && !isMathNode(arg)
+ );
+ const errors = [];
+ if (isFieldOperation && (fields.length > 1 || (fields.length === 1 && fields[0] !== firstArg))) {
+ errors.push(
+ getMessageFromId({
+ messageId: 'tooManyFirstArguments',
+ values: {
+ operation: node.name,
+ type: i18n.translate('xpack.lens.indexPattern.formulaFieldValue', {
+ defaultMessage: 'field',
+ }),
+ supported: 1,
+ text: (fields as TinymathVariable[]).map(({ text }) => text).join(', '),
+ },
+ locations: node.location ? [node.location] : [],
+ })
+ );
+ }
+ if (!isFieldOperation && fields.length) {
+ errors.push(
+ getMessageFromId({
+ messageId: 'wrongArgument',
+ values: {
+ operation: node.name,
+ text: (fields as TinymathVariable[]).map(({ text }) => text).join(', '),
+ type: i18n.translate('xpack.lens.indexPattern.formulaFieldValue', {
+ defaultMessage: 'field',
+ }),
+ },
+ locations: node.location ? [node.location] : [],
+ })
+ );
+ }
+ return errors;
+}
+
+function validateFunctionArguments(
+ node: TinymathFunction,
+ functions: TinymathFunction[],
+ requiredFunctions: number = 0,
+ {
+ isFieldOperation,
+ firstArgValidation,
+ type,
+ }: { isFieldOperation: boolean; firstArgValidation: boolean; type: string }
+) {
+ const errors = [];
+ // For math operation let the native operation run its own validation
+ const [esOperations, mathOperations] = partition(functions, (arg) => !isMathNode(arg));
+ if (esOperations.length > requiredFunctions) {
+ if (isFieldOperation) {
+ errors.push(
+ getMessageFromId({
+ messageId: 'wrongArgument',
+ values: {
+ operation: node.name,
+ text: (esOperations as TinymathFunction[]).map(({ text }) => text).join(', '),
+ type: i18n.translate('xpack.lens.indexPattern.formulaMetricValue', {
+ defaultMessage: 'metric',
+ }),
+ },
+ locations: node.location ? [node.location] : [],
+ })
+ );
+ } else {
+ errors.push(
+ getMessageFromId({
+ messageId: 'tooManyFirstArguments',
+ values: {
+ operation: node.name,
+ type,
+ supported: requiredFunctions,
+ text: (esOperations as TinymathFunction[]).map(({ text }) => text).join(', '),
+ },
+ locations: node.location ? [node.location] : [],
+ })
+ );
+ }
+ }
+ // full reference operation have another way to handle math operations
+ if (
+ isFieldOperation &&
+ ((!firstArgValidation && mathOperations.length) || mathOperations.length > 1)
+ ) {
+ errors.push(
+ getMessageFromId({
+ messageId: 'wrongArgument',
+ values: {
+ operation: node.name,
+ type,
+ text: (mathOperations as TinymathFunction[]).map(({ text }) => text).join(', '),
+ },
+ locations: node.location ? [node.location] : [],
+ })
+ );
+ }
+ return errors;
+}
diff --git a/x-pack/plugins/lens/server/routes/field_stats.ts b/x-pack/plugins/lens/server/routes/field_stats.ts
index 12d3ef3f4a95e7..7103e395eabdc3 100644
--- a/x-pack/plugins/lens/server/routes/field_stats.ts
+++ b/x-pack/plugins/lens/server/routes/field_stats.ts
@@ -8,7 +8,7 @@ import { errors, estypes } from '@elastic/elasticsearch';
import DateMath from '@elastic/datemath';
import { schema } from '@kbn/config-schema';
import { CoreSetup } from 'src/core/server';
-import { IFieldType } from 'src/plugins/data/common';
+import type { IndexPatternField } from 'src/plugins/data/common';
import { SavedObjectNotFound } from '../../../../../src/plugins/kibana_utils/common';
import { ESSearchResponse } from '../../../../../src/core/types/elasticsearch';
import { FieldStatsResponse, BASE_API_URL } from '../../common';
@@ -79,6 +79,14 @@ export async function initFieldsRoute(setup: CoreSetup) {
},
};
+ const runtimeMappings = indexPattern.fields
+ .filter((f) => f.runtimeField)
+ .reduce((acc, f) => {
+ if (!f.runtimeField) return acc;
+ acc[f.name] = f.runtimeField;
+ return acc;
+ }, {} as Record);
+
const search = async (aggs: Record) => {
const { body: result } = await requestClient.search({
index: indexPattern.title,
@@ -86,7 +94,7 @@ export async function initFieldsRoute(setup: CoreSetup) {
body: {
query,
aggs,
- runtime_mappings: field.runtimeField ? { [fieldName]: field.runtimeField } : {},
+ runtime_mappings: runtimeMappings,
},
size: 0,
});
@@ -138,7 +146,7 @@ export async function getNumberHistogram(
aggSearchWithBody: (
aggs: Record
) => Promise,
- field: IFieldType,
+ field: IndexPatternField,
useTopHits = true
): Promise {
const fieldRef = getFieldRef(field);
@@ -247,7 +255,7 @@ export async function getNumberHistogram(
export async function getStringSamples(
aggSearchWithBody: (aggs: Record) => unknown,
- field: IFieldType,
+ field: IndexPatternField,
size = 10
): Promise {
const fieldRef = getFieldRef(field);
@@ -287,7 +295,7 @@ export async function getStringSamples(
// This one is not sampled so that it returns the full date range
export async function getDateHistogram(
aggSearchWithBody: (aggs: Record) => unknown,
- field: IFieldType,
+ field: IndexPatternField,
range: { fromDate: string; toDate: string }
): Promise {
const fromDate = DateMath.parse(range.fromDate);
@@ -329,7 +337,7 @@ export async function getDateHistogram(
};
}
-function getFieldRef(field: IFieldType) {
+function getFieldRef(field: IndexPatternField) {
return field.scripted
? {
script: {
diff --git a/x-pack/plugins/lists/public/exceptions/components/autocomplete/field.tsx b/x-pack/plugins/lists/public/exceptions/components/autocomplete/field.tsx
index b3a5e36f12e404..47527914e71ff0 100644
--- a/x-pack/plugins/lists/public/exceptions/components/autocomplete/field.tsx
+++ b/x-pack/plugins/lists/public/exceptions/components/autocomplete/field.tsx
@@ -28,6 +28,13 @@ interface OperatorProps {
selectedField: IFieldType | undefined;
}
+/**
+ * There is a copy within:
+ * x-pack/plugins/security_solution/public/common/components/autocomplete/field.tsx
+ *
+ * TODO: This should be in its own packaged and not copied, https://github.com/elastic/kibana/issues/105378
+ * NOTE: This has deviated from the copy and will have to be reconciled.
+ */
export const FieldComponent: React.FC = ({
fieldInputWidth,
fieldTypeFilter = [],
diff --git a/x-pack/plugins/lists/public/exceptions/components/autocomplete/field_value_match.tsx b/x-pack/plugins/lists/public/exceptions/components/autocomplete/field_value_match.tsx
index c1776280842c69..8dbe8f223ae5bd 100644
--- a/x-pack/plugins/lists/public/exceptions/components/autocomplete/field_value_match.tsx
+++ b/x-pack/plugins/lists/public/exceptions/components/autocomplete/field_value_match.tsx
@@ -47,6 +47,11 @@ interface AutocompleteFieldMatchProps {
onError?: (arg: boolean) => void;
}
+/**
+ * There is a copy of this within:
+ * x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match.tsx
+ * TODO: This should be in its own packaged and not copied, https://github.com/elastic/kibana/issues/105378
+ */
export const AutocompleteFieldMatchComponent: React.FC = ({
placeholder,
rowLabel,
diff --git a/x-pack/plugins/lists/public/exceptions/components/autocomplete/helpers.ts b/x-pack/plugins/lists/public/exceptions/components/autocomplete/helpers.ts
index 965214815eedf6..975416e272227c 100644
--- a/x-pack/plugins/lists/public/exceptions/components/autocomplete/helpers.ts
+++ b/x-pack/plugins/lists/public/exceptions/components/autocomplete/helpers.ts
@@ -10,6 +10,7 @@ import { EuiComboBoxOptionOption } from '@elastic/eui';
import type { ListSchema, Type } from '@kbn/securitysolution-io-ts-list-types';
import {
EXCEPTION_OPERATORS,
+ OperatorOption,
doesNotExistOperator,
existsOperator,
isNotOperator,
@@ -18,7 +19,7 @@ import {
import { IFieldType } from '../../../../../../../src/plugins/data/common';
-import { GetGenericComboBoxPropsReturn, OperatorOption } from './types';
+import { GetGenericComboBoxPropsReturn } from './types';
import * as i18n from './translations';
/**
@@ -72,6 +73,10 @@ export const checkEmptyValue = (
/**
* Very basic validation for values
+ * There is a copy within:
+ * x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.ts
+ *
+ * TODO: This should be in its own packaged and not copied, https://github.com/elastic/kibana/issues/105378
*
* @param param the value being checked
* @param field the selected field
@@ -109,7 +114,10 @@ export const paramIsValid = (
/**
* Determines the options, selected values and option labels for EUI combo box
+ * There is a copy within:
+ * x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.ts
*
+ * TODO: This should be in its own packaged and not copied, https://github.com/elastic/kibana/issues/105378
* @param options options user can select from
* @param selectedOptions user selection if any
* @param getLabel helper function to know which property to use for labels
diff --git a/x-pack/plugins/lists/public/exceptions/components/autocomplete/hooks/use_field_value_autocomplete.ts b/x-pack/plugins/lists/public/exceptions/components/autocomplete/hooks/use_field_value_autocomplete.ts
index 674bb5e5537d92..63d3925d6d64d3 100644
--- a/x-pack/plugins/lists/public/exceptions/components/autocomplete/hooks/use_field_value_autocomplete.ts
+++ b/x-pack/plugins/lists/public/exceptions/components/autocomplete/hooks/use_field_value_autocomplete.ts
@@ -33,7 +33,10 @@ export interface UseFieldValueAutocompleteProps {
}
/**
* Hook for using the field value autocomplete service
+ * There is a copy within:
+ * x-pack/plugins/security_solution/public/common/components/autocomplete/hooks/use_field_value_autocomplete.ts
*
+ * TODO: This should be in its own packaged and not copied, https://github.com/elastic/kibana/issues/105378
*/
export const useFieldValueAutocomplete = ({
selectedField,
diff --git a/x-pack/plugins/lists/public/exceptions/components/autocomplete/operator.tsx b/x-pack/plugins/lists/public/exceptions/components/autocomplete/operator.tsx
index 7fc221c5a097c3..0d2fe5bd664be1 100644
--- a/x-pack/plugins/lists/public/exceptions/components/autocomplete/operator.tsx
+++ b/x-pack/plugins/lists/public/exceptions/components/autocomplete/operator.tsx
@@ -7,11 +7,12 @@
import React, { useCallback, useMemo } from 'react';
import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
+import { OperatorOption } from '@kbn/securitysolution-list-utils';
import { IFieldType } from '../../../../../../../src/plugins/data/common';
import { getGenericComboBoxProps, getOperators } from './helpers';
-import { GetGenericComboBoxPropsReturn, OperatorOption } from './types';
+import { GetGenericComboBoxPropsReturn } from './types';
const AS_PLAIN_TEXT = { asPlainText: true };
diff --git a/x-pack/plugins/lists/public/exceptions/components/autocomplete/types.ts b/x-pack/plugins/lists/public/exceptions/components/autocomplete/types.ts
index 76d5b7758007b4..07f1903fb70e1c 100644
--- a/x-pack/plugins/lists/public/exceptions/components/autocomplete/types.ts
+++ b/x-pack/plugins/lists/public/exceptions/components/autocomplete/types.ts
@@ -6,20 +6,9 @@
*/
import { EuiComboBoxOptionOption } from '@elastic/eui';
-import type {
- ListOperatorEnum as OperatorEnum,
- ListOperatorTypeEnum as OperatorTypeEnum,
-} from '@kbn/securitysolution-io-ts-list-types';
export interface GetGenericComboBoxPropsReturn {
comboOptions: EuiComboBoxOptionOption[];
labels: string[];
selectedComboOptions: EuiComboBoxOptionOption[];
}
-
-export interface OperatorOption {
- message: string;
- value: string;
- operator: OperatorEnum;
- type: OperatorTypeEnum;
-}
diff --git a/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.tsx b/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.tsx
index 7daef8467dd1a9..c54da89766d76e 100644
--- a/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.tsx
+++ b/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.tsx
@@ -18,6 +18,7 @@ import {
BuilderEntry,
EXCEPTION_OPERATORS_ONLY_LISTS,
FormattedBuilderEntry,
+ OperatorOption,
getEntryOnFieldChange,
getEntryOnListChange,
getEntryOnMatchAnyChange,
@@ -32,7 +33,6 @@ import { IFieldType, IIndexPattern } from '../../../../../../../src/plugins/data
import { HttpStart } from '../../../../../../../src/core/public';
import { FieldComponent } from '../autocomplete/field';
import { OperatorComponent } from '../autocomplete/operator';
-import { OperatorOption } from '../autocomplete/types';
import { AutocompleteFieldExistsComponent } from '../autocomplete/field_value_exists';
import { AutocompleteFieldMatchComponent } from '../autocomplete/field_value_match';
import { AutocompleteFieldMatchAnyComponent } from '../autocomplete/field_value_match_any';
diff --git a/x-pack/plugins/lists/public/exceptions/components/builder/helpers.test.ts b/x-pack/plugins/lists/public/exceptions/components/builder/helpers.test.ts
index 212db40f3168cc..afeac2d1bf4de3 100644
--- a/x-pack/plugins/lists/public/exceptions/components/builder/helpers.test.ts
+++ b/x-pack/plugins/lists/public/exceptions/components/builder/helpers.test.ts
@@ -24,6 +24,7 @@ import {
EmptyEntry,
ExceptionsBuilderExceptionItem,
FormattedBuilderEntry,
+ OperatorOption,
doesNotExistOperator,
existsOperator,
filterExceptionItems,
@@ -64,7 +65,6 @@ import { getEntryNestedMock } from '../../../../common/schemas/types/entry_neste
import { getEntryMatchMock } from '../../../../common/schemas/types/entry_match.mock';
import { getEntryMatchAnyMock } from '../../../../common/schemas/types/entry_match_any.mock';
import { getListResponseMock } from '../../../../common/schemas/response/list_schema.mock';
-import { OperatorOption } from '../autocomplete/types';
import { getEntryListMock } from '../../../../common/schemas/types/entry_list.mock';
// TODO: ALL THESE TESTS SHOULD BE MOVED TO @kbn/securitysolution-list-utils for its helper. The only reason why they're here is due to missing other packages we hae to create or missing things from kbn packages such as mocks from kibana core
diff --git a/x-pack/plugins/maps/public/classes/layers/file_upload_wizard/wizard.tsx b/x-pack/plugins/maps/public/classes/layers/file_upload_wizard/wizard.tsx
index 024c2308df6c67..87747d915af4a7 100644
--- a/x-pack/plugins/maps/public/classes/layers/file_upload_wizard/wizard.tsx
+++ b/x-pack/plugins/maps/public/classes/layers/file_upload_wizard/wizard.tsx
@@ -106,7 +106,7 @@ export class ClientFileCreateSourceEditor extends Component {
applyGlobalTime: true,
id: '12345',
indexPatternId: 'apm_static_index_pattern_id',
- indexPatternTitle: 'apm-*',
+ indexPatternTitle: 'traces-apm*,logs-apm*,metrics-apm*,apm-*',
metrics: [
{
field: 'transaction.duration.us',
diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts
index adf6f1d7f270d2..0b57afb38d585e 100644
--- a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts
+++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts
@@ -39,7 +39,7 @@ import { getDefaultDynamicProperties } from '../../../styles/vector/vector_style
// redefining APM constant to avoid making maps app depend on APM plugin
export const APM_INDEX_PATTERN_ID = 'apm_static_index_pattern_id';
-export const APM_INDEX_PATTERN_TITLE = 'apm-*';
+export const APM_INDEX_PATTERN_TITLE = 'traces-apm*,logs-apm*,metrics-apm*,apm-*';
const defaultDynamicProperties = getDefaultDynamicProperties();
diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.test.ts b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.test.ts
index 9c6e72fc11d3ad..a3a3e8b20f678b 100644
--- a/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.test.ts
+++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.test.ts
@@ -706,4 +706,343 @@ describe('createLayerDescriptor', () => {
},
]);
});
+
+ test('apm data stream', () => {
+ expect(createSecurityLayerDescriptors('id', 'traces-apm-opbean-node')).toEqual([
+ {
+ __dataRequests: [],
+ alpha: 0.75,
+ id: '12345',
+ includeInFitToBounds: true,
+ joins: [],
+ label: 'traces-apm-opbean-node | Source Point',
+ maxZoom: 24,
+ minZoom: 0,
+ sourceDescriptor: {
+ applyGlobalQuery: true,
+ applyGlobalTime: true,
+ filterByMapBounds: true,
+ geoField: 'client.geo.location',
+ id: '12345',
+ indexPatternId: 'id',
+ scalingType: 'TOP_HITS',
+ sortField: '',
+ sortOrder: 'desc',
+ tooltipProperties: [
+ 'host.name',
+ 'client.ip',
+ 'client.domain',
+ 'client.geo.country_iso_code',
+ 'client.as.organization.name',
+ ],
+ topHitsSize: 1,
+ topHitsSplitField: 'client.ip',
+ type: 'ES_SEARCH',
+ },
+ style: {
+ isTimeAware: true,
+ properties: {
+ fillColor: {
+ options: {
+ color: '#6092C0',
+ },
+ type: 'STATIC',
+ },
+ icon: {
+ options: {
+ value: 'home',
+ },
+ type: 'STATIC',
+ },
+ iconOrientation: {
+ options: {
+ orientation: 0,
+ },
+ type: 'STATIC',
+ },
+ iconSize: {
+ options: {
+ size: 8,
+ },
+ type: 'STATIC',
+ },
+ labelBorderColor: {
+ options: {
+ color: '#FFFFFF',
+ },
+ type: 'STATIC',
+ },
+ labelBorderSize: {
+ options: {
+ size: 'SMALL',
+ },
+ },
+ labelColor: {
+ options: {
+ color: '#000000',
+ },
+ type: 'STATIC',
+ },
+ labelSize: {
+ options: {
+ size: 14,
+ },
+ type: 'STATIC',
+ },
+ labelText: {
+ options: {
+ value: '',
+ },
+ type: 'STATIC',
+ },
+ lineColor: {
+ options: {
+ color: '#FFFFFF',
+ },
+ type: 'STATIC',
+ },
+ lineWidth: {
+ options: {
+ size: 2,
+ },
+ type: 'STATIC',
+ },
+ symbolizeAs: {
+ options: {
+ value: 'icon',
+ },
+ },
+ },
+ type: 'VECTOR',
+ },
+ type: 'VECTOR',
+ visible: true,
+ },
+ {
+ __dataRequests: [],
+ alpha: 0.75,
+ id: '12345',
+ includeInFitToBounds: true,
+ joins: [],
+ label: 'traces-apm-opbean-node | Destination point',
+ maxZoom: 24,
+ minZoom: 0,
+ sourceDescriptor: {
+ applyGlobalQuery: true,
+ applyGlobalTime: true,
+ filterByMapBounds: true,
+ geoField: 'server.geo.location',
+ id: '12345',
+ indexPatternId: 'id',
+ scalingType: 'TOP_HITS',
+ sortField: '',
+ sortOrder: 'desc',
+ tooltipProperties: [
+ 'host.name',
+ 'server.ip',
+ 'server.domain',
+ 'server.geo.country_iso_code',
+ 'server.as.organization.name',
+ ],
+ topHitsSize: 1,
+ topHitsSplitField: 'server.ip',
+ type: 'ES_SEARCH',
+ },
+ style: {
+ isTimeAware: true,
+ properties: {
+ fillColor: {
+ options: {
+ color: '#D36086',
+ },
+ type: 'STATIC',
+ },
+ icon: {
+ options: {
+ value: 'marker',
+ },
+ type: 'STATIC',
+ },
+ iconOrientation: {
+ options: {
+ orientation: 0,
+ },
+ type: 'STATIC',
+ },
+ iconSize: {
+ options: {
+ size: 8,
+ },
+ type: 'STATIC',
+ },
+ labelBorderColor: {
+ options: {
+ color: '#FFFFFF',
+ },
+ type: 'STATIC',
+ },
+ labelBorderSize: {
+ options: {
+ size: 'SMALL',
+ },
+ },
+ labelColor: {
+ options: {
+ color: '#000000',
+ },
+ type: 'STATIC',
+ },
+ labelSize: {
+ options: {
+ size: 14,
+ },
+ type: 'STATIC',
+ },
+ labelText: {
+ options: {
+ value: '',
+ },
+ type: 'STATIC',
+ },
+ lineColor: {
+ options: {
+ color: '#FFFFFF',
+ },
+ type: 'STATIC',
+ },
+ lineWidth: {
+ options: {
+ size: 2,
+ },
+ type: 'STATIC',
+ },
+ symbolizeAs: {
+ options: {
+ value: 'icon',
+ },
+ },
+ },
+ type: 'VECTOR',
+ },
+ type: 'VECTOR',
+ visible: true,
+ },
+ {
+ __dataRequests: [],
+ alpha: 0.75,
+ id: '12345',
+ includeInFitToBounds: true,
+ joins: [],
+ label: 'traces-apm-opbean-node | Line',
+ maxZoom: 24,
+ minZoom: 0,
+ sourceDescriptor: {
+ applyGlobalQuery: true,
+ applyGlobalTime: true,
+ destGeoField: 'server.geo.location',
+ id: '12345',
+ indexPatternId: 'id',
+ metrics: [
+ {
+ field: 'client.bytes',
+ type: 'sum',
+ },
+ {
+ field: 'server.bytes',
+ type: 'sum',
+ },
+ ],
+ sourceGeoField: 'client.geo.location',
+ type: 'ES_PEW_PEW',
+ },
+ style: {
+ isTimeAware: true,
+ properties: {
+ fillColor: {
+ options: {
+ color: '#54B399',
+ },
+ type: 'STATIC',
+ },
+ icon: {
+ options: {
+ value: 'marker',
+ },
+ type: 'STATIC',
+ },
+ iconOrientation: {
+ options: {
+ orientation: 0,
+ },
+ type: 'STATIC',
+ },
+ iconSize: {
+ options: {
+ size: 6,
+ },
+ type: 'STATIC',
+ },
+ labelBorderColor: {
+ options: {
+ color: '#FFFFFF',
+ },
+ type: 'STATIC',
+ },
+ labelBorderSize: {
+ options: {
+ size: 'SMALL',
+ },
+ },
+ labelColor: {
+ options: {
+ color: '#000000',
+ },
+ type: 'STATIC',
+ },
+ labelSize: {
+ options: {
+ size: 14,
+ },
+ type: 'STATIC',
+ },
+ labelText: {
+ options: {
+ value: '',
+ },
+ type: 'STATIC',
+ },
+ lineColor: {
+ options: {
+ color: '#6092C0',
+ },
+ type: 'STATIC',
+ },
+ lineWidth: {
+ options: {
+ field: {
+ name: 'doc_count',
+ origin: 'source',
+ },
+ fieldMetaOptions: {
+ isEnabled: true,
+ sigma: 3,
+ },
+ maxSize: 8,
+ minSize: 1,
+ },
+ type: 'DYNAMIC',
+ },
+ symbolizeAs: {
+ options: {
+ value: 'circle',
+ },
+ },
+ },
+ type: 'VECTOR',
+ },
+ type: 'VECTOR',
+ visible: true,
+ },
+ ]);
+ });
});
diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.ts b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.ts
index b2283196a41dd2..8a40ba63bed0dc 100644
--- a/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.ts
+++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.ts
@@ -35,7 +35,11 @@ const defaultDynamicProperties = getDefaultDynamicProperties();
const euiVisColorPalette = euiPaletteColorBlind();
function isApmIndex(indexPatternTitle: string) {
- return minimatch(indexPatternTitle, APM_INDEX_PATTERN_TITLE);
+ return APM_INDEX_PATTERN_TITLE.split(',')
+ .map((pattern) => {
+ return minimatch(indexPatternTitle, pattern);
+ })
+ .some(Boolean);
}
function getSourceField(indexPatternTitle: string) {
diff --git a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx
index 880a47d0981cb0..6277411fa053a4 100644
--- a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx
+++ b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx
@@ -95,13 +95,13 @@ export class TiledVectorLayer extends VectorLayer {
? i18n.translate('xpack.maps.tiles.resultsTrimmedMsg', {
defaultMessage: `Results limited to {count} documents.`,
values: {
- count: totalFeaturesCount,
+ count: totalFeaturesCount.toLocaleString(),
},
})
: i18n.translate('xpack.maps.tiles.resultsCompleteMsg', {
defaultMessage: `Found {count} documents.`,
values: {
- count: totalFeaturesCount,
+ count: totalFeaturesCount.toLocaleString(),
},
}),
areResultsTrimmed: isIncomplete,
diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.test.ts b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.test.ts
index 6d3171312eaec7..0c15afff6b0517 100644
--- a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.test.ts
+++ b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.test.ts
@@ -51,7 +51,7 @@ describe('getSourceTooltipContent', () => {
sourceDataRequest
);
expect(areResultsTrimmed).toBe(true);
- expect(tooltipContent).toBe('Results limited to first 1000 tracks of ~5000.');
+ expect(tooltipContent).toBe('Results limited to first 1,000 tracks of ~5,000.');
});
it('Should show results trimmed icon and message when tracks are trimmed', () => {
@@ -90,7 +90,7 @@ describe('getSourceTooltipContent', () => {
);
expect(areResultsTrimmed).toBe(true);
expect(tooltipContent).toBe(
- 'Results limited to first 1000 tracks of ~5000. 10 of 1000 tracks are incomplete.'
+ 'Results limited to first 1,000 tracks of ~5,000. 10 of 1,000 tracks are incomplete.'
);
});
});
diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx
index 460c1228e50a83..82be83dad43f7b 100644
--- a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx
+++ b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx
@@ -326,21 +326,21 @@ export class ESGeoLineSource extends AbstractESAggSource {
? i18n.translate('xpack.maps.esGeoLine.areEntitiesTrimmedMsg', {
defaultMessage: `Results limited to first {entityCount} tracks of ~{totalEntities}.`,
values: {
- entityCount: meta.entityCount,
- totalEntities: meta.totalEntities,
+ entityCount: meta.entityCount.toLocaleString(),
+ totalEntities: meta.totalEntities.toLocaleString(),
},
})
: i18n.translate('xpack.maps.esGeoLine.tracksCountMsg', {
defaultMessage: `Found {entityCount} tracks.`,
- values: { entityCount: meta.entityCount },
+ values: { entityCount: meta.entityCount.toLocaleString() },
});
const tracksTrimmedMsg =
meta.numTrimmedTracks > 0
? i18n.translate('xpack.maps.esGeoLine.tracksTrimmedMsg', {
defaultMessage: `{numTrimmedTracks} of {entityCount} tracks are incomplete.`,
values: {
- entityCount: meta.entityCount,
- numTrimmedTracks: meta.numTrimmedTracks,
+ entityCount: meta.entityCount.toLocaleString(),
+ numTrimmedTracks: meta.numTrimmedTracks.toLocaleString(),
},
})
: undefined;
diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx
index 343c366b548f6e..55eed588b8840c 100644
--- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx
+++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx
@@ -620,17 +620,17 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye
? i18n.translate('xpack.maps.esSearch.topHitsResultsTrimmedMsg', {
defaultMessage: `Results limited to first {entityCount} entities of ~{totalEntities}.`,
values: {
- entityCount: meta.entityCount,
- totalEntities: meta.totalEntities,
+ entityCount: meta.entityCount?.toLocaleString(),
+ totalEntities: meta.totalEntities?.toLocaleString(),
},
})
: i18n.translate('xpack.maps.esSearch.topHitsEntitiesCountMsg', {
defaultMessage: `Found {entityCount} entities.`,
- values: { entityCount: meta.entityCount },
+ values: { entityCount: meta.entityCount?.toLocaleString() },
});
const docsPerEntityMsg = i18n.translate('xpack.maps.esSearch.topHitsSizeMsg', {
defaultMessage: `Showing top {topHitsSize} documents per entity.`,
- values: { topHitsSize: this._descriptor.topHitsSize },
+ values: { topHitsSize: this._descriptor.topHitsSize?.toLocaleString() },
});
return {
@@ -645,7 +645,7 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye
return {
tooltipContent: i18n.translate('xpack.maps.esSearch.resultsTrimmedMsg', {
defaultMessage: `Results limited to first {count} documents.`,
- values: { count: meta.resultsCount },
+ values: { count: meta.resultsCount?.toLocaleString() },
}),
areResultsTrimmed: true,
};
@@ -654,7 +654,7 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye
return {
tooltipContent: i18n.translate('xpack.maps.esSearch.featureCountMsg', {
defaultMessage: `Found {count} documents.`,
- values: { count: meta.resultsCount },
+ values: { count: meta.resultsCount?.toLocaleString() },
}),
areResultsTrimmed: false,
};
diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/util/__snapshots__/scaling_form.test.tsx.snap b/x-pack/plugins/maps/public/classes/sources/es_search_source/util/__snapshots__/scaling_form.test.tsx.snap
index 03f2594f287eaf..99ce13ce326d6f 100644
--- a/x-pack/plugins/maps/public/classes/sources/es_search_source/util/__snapshots__/scaling_form.test.tsx.snap
+++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/util/__snapshots__/scaling_form.test.tsx.snap
@@ -28,7 +28,7 @@ exports[`scaling form should disable clusters option when clustering is not supp
@@ -114,14 +114,14 @@ exports[`scaling form should render 1`] = `
{
state = {
- maxResultWindow: DEFAULT_MAX_RESULT_WINDOW,
+ maxResultWindow: DEFAULT_MAX_RESULT_WINDOW.toLocaleString(),
};
_isMounted = false;
@@ -61,7 +61,7 @@ export class ScalingForm extends Component {
const indexPattern = await getIndexPatternService().get(this.props.indexPatternId);
const { maxResultWindow } = await loadIndexSettings(indexPattern!.title);
if (this._isMounted) {
- this.setState({ maxResultWindow });
+ this.setState({ maxResultWindow: maxResultWindow.toLocaleString() });
}
} catch (err) {
return;
@@ -90,7 +90,7 @@ export class ScalingForm extends Component {
{
,
+ "name": "Edit layer settings",
+ "onClick": [Function],
+ "toolTipContent": null,
+ },
],
"title": "Layer actions",
},
diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx
index 83b4d2c2a756b2..2a3186f00d7cef 100644
--- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx
+++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx
@@ -158,6 +158,17 @@ export class TOCEntryActionsPopover extends Component {
},
},
];
+ actionItems.push({
+ disabled: this.props.isEditButtonDisabled,
+ name: EDIT_LAYER_SETTINGS_LABEL,
+ icon: ,
+ 'data-test-subj': 'layerSettingsButton',
+ toolTipContent: null,
+ onClick: () => {
+ this._closePopover();
+ this.props.openLayerSettings();
+ },
+ });
if (!this.props.isReadOnly) {
if (this.state.supportsFeatureEditing) {
@@ -186,17 +197,6 @@ export class TOCEntryActionsPopover extends Component {
},
});
}
- actionItems.push({
- disabled: this.props.isEditButtonDisabled,
- name: EDIT_LAYER_SETTINGS_LABEL,
- icon: ,
- 'data-test-subj': 'layerSettingsButton',
- toolTipContent: null,
- onClick: () => {
- this._closePopover();
- this.props.openLayerSettings();
- },
- });
actionItems.push({
name: i18n.translate('xpack.maps.layerTocActions.cloneLayerTitle', {
defaultMessage: 'Clone layer',
diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx
index 41c2992c77d88d..ffad34454bb61c 100644
--- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx
+++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx
@@ -116,7 +116,7 @@ export class TOCEntryButton extends Component {
footnotes.push({
icon: ,
message: i18n.translate('xpack.maps.layer.isUsingSearchMsg', {
- defaultMessage: 'Results narrowed by search bar',
+ defaultMessage: 'Results narrowed by query and filters',
}),
});
}
diff --git a/x-pack/plugins/ml/common/constants/messages.test.ts b/x-pack/plugins/ml/common/constants/messages.test.ts
index 1141eea2c176d8..59fc50757b674c 100644
--- a/x-pack/plugins/ml/common/constants/messages.test.ts
+++ b/x-pack/plugins/ml/common/constants/messages.test.ts
@@ -35,7 +35,7 @@ describe('Constants: Messages parseMessages()', () => {
status: 'success',
text: 'Presence of detector functions validated in all detectors.',
url:
- 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/create-jobs.html#detectors',
+ 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/ml-ad-finding-anomalies.html#ml-ad-detectors',
},
{
bucketSpan: '15m',
@@ -44,7 +44,7 @@ describe('Constants: Messages parseMessages()', () => {
status: 'success',
text: 'Format of "15m" is valid and passed validation checks.',
url:
- 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/create-jobs.html#bucket-span',
+ 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/ml-ad-finding-anomalies.html#ml-ad-bucket-span',
},
{
heading: 'Time range',
@@ -58,7 +58,7 @@ describe('Constants: Messages parseMessages()', () => {
status: 'success',
text: 'Valid and within the estimated model memory limit.',
url:
- 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/create-jobs.html#model-memory-limits',
+ 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/ml-ad-finding-anomalies.html#ml-ad-model-memory-limits',
},
]);
});
@@ -79,7 +79,7 @@ describe('Constants: Messages parseMessages()', () => {
status: 'success',
text: 'Presence of detector functions validated in all detectors.',
url:
- 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/create-jobs.html#detectors',
+ 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/ml-ad-finding-anomalies.html#ml-ad-detectors',
},
{
bucketSpan: '15m',
@@ -116,7 +116,7 @@ describe('Constants: Messages parseMessages()', () => {
status: 'success',
text: 'Presence of detector functions validated in all detectors.',
url:
- 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/create-jobs.html#detectors',
+ 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/ml-ad-finding-anomalies.html#ml-ad-detectors',
},
{
id: 'cardinality_model_plot_high',
@@ -131,7 +131,7 @@ describe('Constants: Messages parseMessages()', () => {
text:
'Cardinality of partition_field "order_id" is above 1000 and might result in high memory usage.',
url:
- 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/create-jobs.html#cardinality',
+ 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/ml-ad-finding-anomalies.html#ml-ad-cardinality',
},
{
heading: 'Bucket span',
@@ -140,7 +140,7 @@ describe('Constants: Messages parseMessages()', () => {
text:
'Bucket span is 1 day or more. Be aware that days are considered as UTC days, not local days.',
url:
- 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/create-jobs.html#bucket-span',
+ 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/ml-ad-finding-anomalies.html#ml-ad-bucket-span',
},
{
bucketSpanCompareFactor: 25,
@@ -156,7 +156,7 @@ describe('Constants: Messages parseMessages()', () => {
status: 'success',
text: 'Influencer configuration passed the validation checks.',
url:
- 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/ml-influencers.html',
+ 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/ml-ad-finding-anomalies.html#ml-ad-influencers',
},
{
id: 'half_estimated_mml_greater_than_mml',
@@ -165,7 +165,7 @@ describe('Constants: Messages parseMessages()', () => {
text:
'The specified model memory limit is less than half of the estimated model memory limit and will likely hit the hard limit.',
url:
- 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/create-jobs.html#model-memory-limits',
+ 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/ml-ad-finding-anomalies.html#ml-ad-model-memory-limits',
},
{
id: 'missing_summary_count_field_name',
diff --git a/x-pack/plugins/ml/common/types/anomalies.ts b/x-pack/plugins/ml/common/types/anomalies.ts
index e84035aa50c8f8..2bf717067f7129 100644
--- a/x-pack/plugins/ml/common/types/anomalies.ts
+++ b/x-pack/plugins/ml/common/types/anomalies.ts
@@ -12,6 +12,8 @@ export interface Influencer {
influencer_field_values: string[];
}
+export type MLAnomalyDoc = AnomalyRecordDoc;
+
export interface AnomalyRecordDoc {
[key: string]: any;
job_id: string;
diff --git a/x-pack/plugins/ml/public/application/components/help_popover/help_popover.tsx b/x-pack/plugins/ml/public/application/components/help_popover/help_popover.tsx
index 8cd6a3fbd11386..95c66d58dbb751 100644
--- a/x-pack/plugins/ml/public/application/components/help_popover/help_popover.tsx
+++ b/x-pack/plugins/ml/public/application/components/help_popover/help_popover.tsx
@@ -6,6 +6,7 @@
*/
import React, { ReactNode } from 'react';
+import { i18n } from '@kbn/i18n';
import {
EuiButtonIcon,
EuiLinkButtonProps,
@@ -22,6 +23,9 @@ export const HelpPopoverButton = ({ onClick }: { onClick: EuiLinkButtonProps['on
className="mlHelpPopover__buttonIcon"
size="s"
iconType="help"
+ aria-label={i18n.translate('xpack.ml.helpPopover.ariaLabel', {
+ defaultMessage: 'Help',
+ })}
onClick={onClick}
/>
);
diff --git a/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/revert_model_snapshot_flyout.tsx b/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/revert_model_snapshot_flyout.tsx
index 6dd4e6c14589b2..f282b2fde2b3ac 100644
--- a/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/revert_model_snapshot_flyout.tsx
+++ b/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/revert_model_snapshot_flyout.tsx
@@ -42,6 +42,7 @@ import { Anomaly } from '../../../jobs/new_job/common/results_loader/results_loa
import { parseInterval } from '../../../../../common/util/parse_interval';
import { CreateCalendar, CalendarEvent } from './create_calendar';
import { timeFormatter } from '../../../../../common/util/date_utils';
+import { toastNotificationServiceProvider } from '../../../services/toast_notification_service';
interface Props {
snapshot: ModelSnapshot;
@@ -139,6 +140,10 @@ export const RevertModelSnapshotFlyout: FC = ({
})
);
refresh();
+ })
+ .catch((error) => {
+ const { displayErrorToast } = toastNotificationServiceProvider(toasts);
+ displayErrorToast(error);
});
hideRevertModal();
closeFlyout();
diff --git a/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts b/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts
index 621ce442047309..45afab0cce4fd9 100644
--- a/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts
+++ b/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts
@@ -223,7 +223,9 @@ const loadExplorerDataProvider = (
swimlaneLimit,
viewByPerPage,
viewByFromPage,
- swimlaneContainerWidth
+ swimlaneContainerWidth,
+ selectionInfluencers,
+ influencersFilterQuery
)
: Promise.resolve([]),
}).pipe(
diff --git a/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx b/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx
index 6f5ae5e17590ad..57e051e1b8417e 100644
--- a/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx
+++ b/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx
@@ -95,7 +95,6 @@ function getInitSearchInputState({
interface ExplorerQueryBarProps {
filterActive: boolean;
- filterIconTriggeredQuery: string;
filterPlaceHolder: string;
indexPattern: IIndexPattern;
queryString?: string;
@@ -104,7 +103,6 @@ interface ExplorerQueryBarProps {
export const ExplorerQueryBar: FC = ({
filterActive,
- filterIconTriggeredQuery,
filterPlaceHolder,
indexPattern,
queryString,
@@ -116,14 +114,12 @@ export const ExplorerQueryBar: FC = ({
);
const [errorMessage, setErrorMessage] = useState(undefined);
- useEffect(() => {
- if (filterIconTriggeredQuery !== undefined) {
- setSearchInput({
- language: searchInput.language,
- query: filterIconTriggeredQuery,
- });
- }
- }, [filterIconTriggeredQuery]);
+ useEffect(
+ function updateSearchInputFromFilter() {
+ setSearchInput(getInitSearchInputState({ filterActive, queryString }));
+ },
+ [filterActive, queryString]
+ );
const searchChangeHandler = (query: Query) => {
if (searchInput.language !== query.language) {
@@ -131,6 +127,7 @@ export const ExplorerQueryBar: FC = ({
}
setSearchInput(query);
};
+
const applyInfluencersFilterQuery = (query: Query) => {
try {
const { clearSettings, settings } = getKqlQueryValues({
diff --git a/x-pack/plugins/ml/public/application/explorer/explorer.js b/x-pack/plugins/ml/public/application/explorer/explorer.js
index 31058b62af7fe5..81474c212d265e 100644
--- a/x-pack/plugins/ml/public/application/explorer/explorer.js
+++ b/x-pack/plugins/ml/public/application/explorer/explorer.js
@@ -86,7 +86,6 @@ const ExplorerPage = ({
filterPlaceHolder,
indexPattern,
queryString,
- filterIconTriggeredQuery,
updateLanguage,
}) => (
@@ -121,7 +120,6 @@ const ExplorerPage = ({
filterPlaceHolder={filterPlaceHolder}
indexPattern={indexPattern}
queryString={queryString}
- filterIconTriggeredQuery={filterIconTriggeredQuery}
updateLanguage={updateLanguage}
/>
@@ -151,7 +149,7 @@ export class ExplorerUI extends React.Component {
selectedJobsRunning: PropTypes.bool.isRequired,
};
- state = { filterIconTriggeredQuery: undefined, language: DEFAULT_QUERY_LANG };
+ state = { language: DEFAULT_QUERY_LANG };
htmlIdGen = htmlIdGenerator();
componentDidMount() {
@@ -200,8 +198,6 @@ export class ExplorerUI extends React.Component {
}
}
- this.setState({ filterIconTriggeredQuery: `${newQueryString}` });
-
try {
const { clearSettings, settings } = getKqlQueryValues({
inputString: `${newQueryString}`,
@@ -327,7 +323,6 @@ export class ExplorerUI extends React.Component {
influencers={influencers}
filterActive={filterActive}
filterPlaceHolder={filterPlaceHolder}
- filterIconTriggeredQuery={this.state.filterIconTriggeredQuery}
indexPattern={indexPattern}
queryString={queryString}
updateLanguage={this.updateLanguage}
diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js
index 7efd36bbe57c61..27a934fa841fe0 100644
--- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js
+++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js
@@ -270,12 +270,6 @@ export class ExplorerChartDistribution extends React.Component {
const tickValuesStart = Math.max(config.selectedEarliest, config.plotEarliest);
// +1 ms to account for the ms that was subtracted for query aggregations.
const interval = config.selectedLatest - config.selectedEarliest + 1;
- const tickValues = getTickValues(
- tickValuesStart,
- interval,
- config.plotEarliest,
- config.plotLatest
- );
const xAxis = d3.svg
.axis()
@@ -286,10 +280,18 @@ export class ExplorerChartDistribution extends React.Component {
.tickPadding(10)
.tickFormat((d) => moment(d).format(xAxisTickFormat));
- // With tooManyBuckets the chart would end up with no x-axis labels
- // because the ticks are based on the span of the emphasis section,
- // and the highlighted area spans the whole chart.
- if (tooManyBuckets === false) {
+ // With tooManyBuckets, or when the chart is used as an embeddable,
+ // the chart would end up with no x-axis labels because the ticks are based on the span of the
+ // emphasis section, and the selected area spans the whole chart.
+ const useAutoTicks =
+ tooManyBuckets === true || interval >= config.plotLatest - config.plotEarliest;
+ if (useAutoTicks === false) {
+ const tickValues = getTickValues(
+ tickValuesStart,
+ interval,
+ config.plotEarliest,
+ config.plotLatest
+ );
xAxis.tickValues(tickValues);
} else {
xAxis.ticks(numTicksForDateFormat(vizWidth, xAxisTickFormat));
@@ -327,7 +329,7 @@ export class ExplorerChartDistribution extends React.Component {
});
}
- if (tooManyBuckets === false) {
+ if (useAutoTicks === false) {
removeLabelOverlap(gAxis, tickValuesStart, interval, vizWidth);
}
}
diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js
index 11a15b192fc520..8d2f66a870c75c 100644
--- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js
+++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js
@@ -139,7 +139,7 @@ describe('ExplorerChart', () => {
expect(+selectedInterval.getAttribute('height')).toBe(166);
const xAxisTicks = wrapper.getDOMNode().querySelector('.x').querySelectorAll('.tick');
- expect([...xAxisTicks]).toHaveLength(0);
+ expect([...xAxisTicks]).toHaveLength(1);
const yAxisTicks = wrapper.getDOMNode().querySelector('.y').querySelectorAll('.tick');
expect([...yAxisTicks]).toHaveLength(5);
const emphasizedAxisLabel = wrapper
diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js
index dd07a7d6cbdee0..19390017244a8c 100644
--- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js
+++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js
@@ -196,12 +196,6 @@ export class ExplorerChartSingleMetric extends React.Component {
const tickValuesStart = Math.max(config.selectedEarliest, config.plotEarliest);
// +1 ms to account for the ms that was subtracted for query aggregations.
const interval = config.selectedLatest - config.selectedEarliest + 1;
- const tickValues = getTickValues(
- tickValuesStart,
- interval,
- config.plotEarliest,
- config.plotLatest
- );
const xAxis = d3.svg
.axis()
@@ -212,10 +206,18 @@ export class ExplorerChartSingleMetric extends React.Component {
.tickPadding(10)
.tickFormat((d) => moment(d).format(xAxisTickFormat));
- // With tooManyBuckets the chart would end up with no x-axis labels
- // because the ticks are based on the span of the emphasis section,
- // and the highlighted area spans the whole chart.
- if (tooManyBuckets === false) {
+ // With tooManyBuckets, or when the chart is used as an embeddable,
+ // the chart would end up with no x-axis labels because the ticks are based on the span of the
+ // emphasis section, and the selected area spans the whole chart.
+ const useAutoTicks =
+ tooManyBuckets === true || interval >= config.plotLatest - config.plotEarliest;
+ if (useAutoTicks === false) {
+ const tickValues = getTickValues(
+ tickValuesStart,
+ interval,
+ config.plotEarliest,
+ config.plotLatest
+ );
xAxis.tickValues(tickValues);
} else {
xAxis.ticks(numTicksForDateFormat(vizWidth, xAxisTickFormat));
@@ -243,7 +245,7 @@ export class ExplorerChartSingleMetric extends React.Component {
axes.append('g').attr('class', 'y axis').call(yAxis);
- if (tooManyBuckets === false) {
+ if (useAutoTicks === false) {
removeLabelOverlap(gAxis, tickValuesStart, interval, vizWidth);
}
}
diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js
index 981f7515d3d706..00172965bc2162 100644
--- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js
+++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js
@@ -144,7 +144,7 @@ describe('ExplorerChart', () => {
expect(+selectedInterval.getAttribute('height')).toBe(166);
const xAxisTicks = wrapper.getDOMNode().querySelector('.x').querySelectorAll('.tick');
- expect([...xAxisTicks]).toHaveLength(0);
+ expect([...xAxisTicks]).toHaveLength(1);
const yAxisTicks = wrapper.getDOMNode().querySelector('.y').querySelectorAll('.tick');
expect([...yAxisTicks]).toHaveLength(10);
diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_utils.d.ts b/x-pack/plugins/ml/public/application/explorer/explorer_utils.d.ts
index ebab308b86027d..e8abe9e45c09ab 100644
--- a/x-pack/plugins/ml/public/application/explorer/explorer_utils.d.ts
+++ b/x-pack/plugins/ml/public/application/explorer/explorer_utils.d.ts
@@ -181,15 +181,6 @@ declare interface LoadOverallDataResponse {
overallSwimlaneData: OverallSwimlaneData;
}
-export declare const loadViewByTopFieldValuesForSelectedTime: (
- earliestMs: number,
- latestMs: number,
- selectedJobs: ExplorerJob[],
- viewBySwimlaneFieldName: string,
- swimlaneLimit: number,
- noInfluencersConfigured: boolean
-) => Promise;
-
export declare interface FilterData {
influencersFilterQuery: InfluencersFilterQuery;
filterActive: boolean;
diff --git a/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx b/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx
index 82f8a90fafb7df..86ec2014c83398 100644
--- a/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx
+++ b/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx
@@ -411,7 +411,7 @@ export const SwimlaneContainer: FC = ({
>
<>
-
+
{showSwimlane && !isLoading && (
= ({ basePath }) => (
}
@@ -38,13 +38,13 @@ export const InsufficientLicensePage: FC = ({ basePath }) => (
),
diff --git a/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx b/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx
index a0a81f77b7b087..f645a0753f8c2a 100644
--- a/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx
+++ b/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx
@@ -127,7 +127,7 @@ const ExplorerUrlStateManager: FC = ({ jobsWithTim
to: globalState.time.to,
});
}
- }, [globalState?.time?.from, globalState?.time?.to]);
+ }, [lastRefresh, globalState?.time?.from, globalState?.time?.to]);
const getJobsWithStoppedPartitions = useCallback(async (selectedJobIds: string[]) => {
try {
diff --git a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx
index ef669a7703c1f0..6eb43862767530 100644
--- a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx
+++ b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx
@@ -142,10 +142,10 @@ export const TimeSeriesExplorerUrlStateManager: FC selectedEarliestMs || chartRange.max < selectedLatestBucketStart) &&
- chartRange.max - chartRange.min < selectedLatestBucketStart - selectedEarliestMs
+ (chartRange.min > selectedEarliestBucketCeil || chartRange.max < selectedLatestBucketStart) &&
+ chartRange.max - chartRange.min < selectedLatestBucketStart - selectedEarliestBucketCeil
) {
tooManyBuckets = true;
}
diff --git a/x-pack/plugins/ml/public/application/services/anomaly_timeline_service.ts b/x-pack/plugins/ml/public/application/services/anomaly_timeline_service.ts
index e11eb4048c374b..6f2b5417eff5fb 100644
--- a/x-pack/plugins/ml/public/application/services/anomaly_timeline_service.ts
+++ b/x-pack/plugins/ml/public/application/services/anomaly_timeline_service.ts
@@ -25,6 +25,8 @@ import {
} from '../explorer/explorer_utils';
import { OVERALL_LABEL, VIEW_BY_JOB_LABEL } from '../explorer/explorer_constants';
import { MlResultsService } from './results_service';
+import { EntityField } from '../../../common/util/anomaly_utils';
+import { InfluencersFilterQuery } from '../../../common/types/es_client';
/**
* Service for retrieving anomaly swim lanes data.
@@ -241,7 +243,9 @@ export class AnomalyTimelineService {
swimlaneLimit: number,
perPage: number,
fromPage: number,
- swimlaneContainerWidth: number
+ swimlaneContainerWidth: number,
+ selectionInfluencers: EntityField[],
+ influencersFilterQuery: InfluencersFilterQuery
) {
const selectedJobIds = selectedJobs.map((d) => d.id);
@@ -254,7 +258,9 @@ export class AnomalyTimelineService {
latestMs,
swimlaneLimit,
perPage,
- fromPage
+ fromPage,
+ selectionInfluencers,
+ influencersFilterQuery
);
if (resp.influencers[viewBySwimlaneFieldName] === undefined) {
return [];
@@ -276,6 +282,8 @@ export class AnomalyTimelineService {
earliestMs,
latestMs,
this.getSwimlaneBucketInterval(selectedJobs, swimlaneContainerWidth).asMilliseconds(),
+ perPage,
+ fromPage,
swimlaneLimit
);
return Object.keys(resp.results);
diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/results.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/results.ts
index 25ef36782207f1..a9f6dbb45f6e34 100644
--- a/x-pack/plugins/ml/public/application/services/ml_api_service/results.ts
+++ b/x-pack/plugins/ml/public/application/services/ml_api_service/results.ts
@@ -16,6 +16,11 @@ import { JobId } from '../../../../common/types/anomaly_detection_jobs';
import { JOB_ID, PARTITION_FIELD_VALUE } from '../../../../common/constants/anomalies';
import { PartitionFieldsDefinition } from '../results_service/result_service_rx';
import { PartitionFieldsConfig } from '../../../../common/types/storage';
+import {
+ ESSearchRequest,
+ ESSearchResponse,
+} from '../../../../../../../src/core/types/elasticsearch';
+import { MLAnomalyDoc } from '../../../../common/types/anomalies';
export const resultsApiProvider = (httpService: HttpService) => ({
getAnomaliesTableData(
@@ -112,18 +117,18 @@ export const resultsApiProvider = (httpService: HttpService) => ({
});
},
- anomalySearch(query: any, jobIds: string[]) {
+ anomalySearch(query: ESSearchRequest, jobIds: string[]) {
const body = JSON.stringify({ query, jobIds });
- return httpService.http({
+ return httpService.http>({
path: `${basePath()}/results/anomaly_search`,
method: 'POST',
body,
});
},
- anomalySearch$(query: any, jobIds: string[]) {
+ anomalySearch$(query: ESSearchRequest, jobIds: string[]) {
const body = JSON.stringify({ query, jobIds });
- return httpService.http$({
+ return httpService.http$>({
path: `${basePath()}/results/anomaly_search`,
method: 'POST',
body,
diff --git a/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts b/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts
index ea07d32bfff1d0..1848b13cb5a1f9 100644
--- a/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts
+++ b/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts
@@ -23,7 +23,8 @@ export function resultsServiceProvider(
intervalMs: number,
perPage?: number,
fromPage?: number,
- swimLaneSeverity?: number
+ swimLaneSeverity?: number,
+ influencersFilterQuery?: InfluencersFilterQuery
): Promise;
getTopInfluencers(
selectedJobIds: string[],
@@ -32,7 +33,7 @@ export function resultsServiceProvider(
maxFieldValues: number,
perPage?: number,
fromPage?: number,
- influencers?: any[],
+ influencers?: EntityField[],
influencersFilterQuery?: InfluencersFilterQuery
): Promise;
getTopInfluencerValues(): Promise;
diff --git a/x-pack/plugins/ml/server/models/job_service/model_snapshots.ts b/x-pack/plugins/ml/server/models/job_service/model_snapshots.ts
index 6cb5f67149fb63..56221f9a72c89a 100644
--- a/x-pack/plugins/ml/server/models/job_service/model_snapshots.ts
+++ b/x-pack/plugins/ml/server/models/job_service/model_snapshots.ts
@@ -85,7 +85,6 @@ export function modelSnapshotProvider(client: IScopedClusterClient, mlClient: Ml
),
events: calendarEvents.map((s) => ({
calendar_id: calendarId,
- event_id: '',
description: s.description,
start_time: `${s.start}`,
end_time: `${s.end}`,
diff --git a/x-pack/plugins/monitoring/public/plugin.ts b/x-pack/plugins/monitoring/public/plugin.ts
index a5b7d4906b5869..9f84165a27ba9f 100644
--- a/x-pack/plugins/monitoring/public/plugin.ts
+++ b/x-pack/plugins/monitoring/public/plugin.ts
@@ -93,7 +93,10 @@ export class MonitoringPlugin
category: DEFAULT_APP_CATEGORIES.management,
mount: async (params: AppMountParameters) => {
const [coreStart, pluginsStart] = await core.getStartServices();
- const { AngularApp } = await import('./angular');
+ const [, { AngularApp }] = await Promise.all([
+ pluginsStart.kibanaLegacy.loadAngularBootstrap(),
+ import('./angular'),
+ ]);
const deps: MonitoringStartPluginDependencies = {
navigation: pluginsStart.navigation,
kibanaLegacy: pluginsStart.kibanaLegacy,
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_storage.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_storage.test.tsx
new file mode 100644
index 00000000000000..c32acc47abd1b0
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_storage.test.tsx
@@ -0,0 +1,131 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { useEffect } from 'react';
+
+import { UrlStorageContextProvider, useSeriesStorage } from './use_series_storage';
+import { render } from '@testing-library/react';
+
+const mockSingleSeries = {
+ 'performance-distribution': {
+ reportType: 'data-distribution',
+ dataType: 'ux',
+ breakdown: 'user_agent.name',
+ time: { from: 'now-15m', to: 'now' },
+ },
+};
+
+const mockMultipleSeries = {
+ 'performance-distribution': {
+ reportType: 'data-distribution',
+ dataType: 'ux',
+ breakdown: 'user_agent.name',
+ time: { from: 'now-15m', to: 'now' },
+ },
+ 'kpi-over-time': {
+ reportType: 'kpi-over-time',
+ dataType: 'synthetics',
+ breakdown: 'user_agent.name',
+ time: { from: 'now-15m', to: 'now' },
+ },
+};
+
+describe('userSeries', function () {
+ function setupTestComponent(seriesData: any) {
+ const setData = jest.fn();
+ function TestComponent() {
+ const data = useSeriesStorage();
+
+ useEffect(() => {
+ setData(data);
+ }, [data]);
+
+ return Test ;
+ }
+
+ render(
+
+
+
+ );
+
+ return setData;
+ }
+ it('should return expected result when there is one series', function () {
+ const setData = setupTestComponent(mockSingleSeries);
+
+ expect(setData).toHaveBeenCalledTimes(2);
+ expect(setData).toHaveBeenLastCalledWith(
+ expect.objectContaining({
+ allSeries: {
+ 'performance-distribution': {
+ breakdown: 'user_agent.name',
+ dataType: 'ux',
+ reportType: 'data-distribution',
+ time: { from: 'now-15m', to: 'now' },
+ },
+ },
+ allSeriesIds: ['performance-distribution'],
+ firstSeries: {
+ breakdown: 'user_agent.name',
+ dataType: 'ux',
+ reportType: 'data-distribution',
+ time: { from: 'now-15m', to: 'now' },
+ },
+ firstSeriesId: 'performance-distribution',
+ })
+ );
+ });
+
+ it('should return expected result when there are multiple series series', function () {
+ const setData = setupTestComponent(mockMultipleSeries);
+
+ expect(setData).toHaveBeenCalledTimes(2);
+ expect(setData).toHaveBeenLastCalledWith(
+ expect.objectContaining({
+ allSeries: {
+ 'performance-distribution': {
+ breakdown: 'user_agent.name',
+ dataType: 'ux',
+ reportType: 'data-distribution',
+ time: { from: 'now-15m', to: 'now' },
+ },
+ 'kpi-over-time': {
+ reportType: 'kpi-over-time',
+ dataType: 'synthetics',
+ breakdown: 'user_agent.name',
+ time: { from: 'now-15m', to: 'now' },
+ },
+ },
+ allSeriesIds: ['performance-distribution', 'kpi-over-time'],
+ firstSeries: {
+ breakdown: 'user_agent.name',
+ dataType: 'ux',
+ reportType: 'data-distribution',
+ time: { from: 'now-15m', to: 'now' },
+ },
+ firstSeriesId: 'performance-distribution',
+ })
+ );
+ });
+
+ it('should return expected result when there are no series', function () {
+ const setData = setupTestComponent({});
+
+ expect(setData).toHaveBeenCalledTimes(2);
+ expect(setData).toHaveBeenLastCalledWith(
+ expect.objectContaining({
+ allSeries: {},
+ allSeriesIds: [],
+ firstSeries: undefined,
+ firstSeriesId: undefined,
+ })
+ );
+ });
+});
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_storage.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_storage.tsx
index 0add5a19a95cc9..a47a124d14b4d9 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_storage.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_series_storage.tsx
@@ -67,7 +67,7 @@ export function UrlStorageContextProvider({
setAllSeries(allSeriesN);
setFirstSeriesId(allSeriesIds?.[0]);
- setFirstSeries(allSeriesN?.[0]);
+ setFirstSeries(allSeriesN?.[allSeriesIds?.[0]]);
(storage as IKbnUrlStateStorage).set(allSeriesKey, allShortSeries);
}, [allShortSeries, storage]);
diff --git a/x-pack/plugins/observability/public/index.ts b/x-pack/plugins/observability/public/index.ts
index 0561eab08fb452..6bafe465fd024d 100644
--- a/x-pack/plugins/observability/public/index.ts
+++ b/x-pack/plugins/observability/public/index.ts
@@ -68,5 +68,9 @@ export { createExploratoryViewUrl } from './components/shared/exploratory_view/c
export { FilterValueLabel } from './components/shared/filter_value_label/filter_value_label';
export type { SeriesUrl } from './components/shared/exploratory_view/types';
-export type { ObservabilityRuleTypeRegistry } from './rules/create_observability_rule_type_registry';
+export type {
+ ObservabilityRuleTypeFormatter,
+ ObservabilityRuleTypeModel,
+ ObservabilityRuleTypeRegistry,
+} from './rules/create_observability_rule_type_registry';
export { createObservabilityRuleTypeRegistryMock } from './rules/observability_rule_type_registry_mock';
diff --git a/x-pack/plugins/observability/public/rules/create_observability_rule_type_registry.ts b/x-pack/plugins/observability/public/rules/create_observability_rule_type_registry.ts
index 35f2dc18c2f226..d6f8c083598888 100644
--- a/x-pack/plugins/observability/public/rules/create_observability_rule_type_registry.ts
+++ b/x-pack/plugins/observability/public/rules/create_observability_rule_type_registry.ts
@@ -5,19 +5,29 @@
* 2.0.
*/
-import { AlertTypeModel, AlertTypeRegistryContract } from '../../../triggers_actions_ui/public';
+import {
+ AlertTypeModel,
+ AlertTypeParams,
+ AlertTypeRegistryContract,
+} from '../../../triggers_actions_ui/public';
import { ParsedTechnicalFields } from '../../../rule_registry/common/parse_technical_fields';
import { AsDuration, AsPercent } from '../../common/utils/formatters';
-export type Formatter = (options: {
+export type ObservabilityRuleTypeFormatter = (options: {
fields: ParsedTechnicalFields & Record;
formatters: { asDuration: AsDuration; asPercent: AsPercent };
}) => { reason: string; link: string };
+export interface ObservabilityRuleTypeModel
+ extends AlertTypeModel {
+ format: ObservabilityRuleTypeFormatter;
+}
+
export function createObservabilityRuleTypeRegistry(alertTypeRegistry: AlertTypeRegistryContract) {
- const formatters: Array<{ typeId: string; fn: Formatter }> = [];
+ const formatters: Array<{ typeId: string; fn: ObservabilityRuleTypeFormatter }> = [];
+
return {
- register: (type: AlertTypeModel & { format: Formatter }) => {
+ register: (type: ObservabilityRuleTypeModel) => {
const { format, ...rest } = type;
formatters.push({ typeId: type.id, fn: format });
alertTypeRegistry.register(rest);
diff --git a/x-pack/plugins/observability/server/plugin.ts b/x-pack/plugins/observability/server/plugin.ts
index 3e8f511eb11534..868e234fcb2a15 100644
--- a/x-pack/plugins/observability/server/plugin.ts
+++ b/x-pack/plugins/observability/server/plugin.ts
@@ -38,47 +38,49 @@ export class ObservabilityPlugin implements Plugin {
}
public setup(core: CoreSetup, plugins: PluginSetup) {
- plugins.features.registerKibanaFeature({
- id: casesFeatureId,
- name: i18n.translate('xpack.observability.featureRegistry.linkObservabilityTitle', {
- defaultMessage: 'Cases',
- }),
- order: 1100,
- category: DEFAULT_APP_CATEGORIES.observability,
- app: [casesFeatureId, 'kibana'],
- catalogue: [observabilityFeatureId],
- cases: [observabilityFeatureId],
- privileges: {
- all: {
- app: [casesFeatureId, 'kibana'],
- catalogue: [observabilityFeatureId],
- cases: {
- all: [observabilityFeatureId],
- },
- api: [],
- savedObject: {
- all: [],
- read: [],
- },
- ui: ['crud_cases', 'read_cases'], // uiCapabilities[casesFeatureId].crud_cases or read_cases
- },
- read: {
- app: [casesFeatureId, 'kibana'],
- catalogue: [observabilityFeatureId],
- cases: {
- read: [observabilityFeatureId],
+ const config = this.initContext.config.get();
+
+ if (config.unsafe.cases.enabled) {
+ plugins.features.registerKibanaFeature({
+ id: casesFeatureId,
+ name: i18n.translate('xpack.observability.featureRegistry.linkObservabilityTitle', {
+ defaultMessage: 'Cases',
+ }),
+ order: 1100,
+ category: DEFAULT_APP_CATEGORIES.observability,
+ app: [casesFeatureId, 'kibana'],
+ catalogue: [observabilityFeatureId],
+ cases: [observabilityFeatureId],
+ privileges: {
+ all: {
+ app: [casesFeatureId, 'kibana'],
+ catalogue: [observabilityFeatureId],
+ cases: {
+ all: [observabilityFeatureId],
+ },
+ api: [],
+ savedObject: {
+ all: [],
+ read: [],
+ },
+ ui: ['crud_cases', 'read_cases'], // uiCapabilities[casesFeatureId].crud_cases or read_cases
},
- api: [],
- savedObject: {
- all: [],
- read: [],
+ read: {
+ app: [casesFeatureId, 'kibana'],
+ catalogue: [observabilityFeatureId],
+ cases: {
+ read: [observabilityFeatureId],
+ },
+ api: [],
+ savedObject: {
+ all: [],
+ read: [],
+ },
+ ui: ['read_cases'], // uiCapabilities[uiCapabilities[casesFeatureId]].read_cases
},
- ui: ['read_cases'], // uiCapabilities[uiCapabilities[casesFeatureId]].read_cases
},
- },
- });
-
- const config = this.initContext.config.get();
+ });
+ }
let annotationsApiPromise: Promise | undefined;
diff --git a/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx b/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx
index d3b0e38a5e033b..bf4c97d63d74ca 100644
--- a/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx
+++ b/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx
@@ -8,15 +8,13 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { i18n } from '@kbn/i18n';
-import { EuiLink, EuiInMemoryTable, EuiCodeBlock } from '@elastic/eui';
+import { EuiInMemoryTable, EuiCodeBlock } from '@elastic/eui';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
-import { PLUGIN_ID } from '../../../fleet/common';
-import { pagePathGetters } from '../../../fleet/public';
+import { AgentIdToName } from '../agents/agent_id_to_name';
import { useActionResults } from './use_action_results';
import { useAllResults } from '../results/use_all_results';
import { Direction } from '../../common/search_strategy';
-import { useKibana } from '../common/lib/kibana';
interface ActionResultsSummaryProps {
actionId: string;
@@ -35,7 +33,6 @@ const ActionResultsSummaryComponent: React.FC = ({
expirationDate,
agentIds,
}) => {
- const getUrlForApp = useKibana().services.application.getUrlForApp;
// @ts-expect-error update types
const [pageIndex, setPageIndex] = useState(0);
// @ts-expect-error update types
@@ -70,20 +67,7 @@ const ActionResultsSummaryComponent: React.FC = ({
isLive,
});
- const renderAgentIdColumn = useCallback(
- (agentId) => (
-
- {agentId}
-
- ),
- [getUrlForApp]
- );
+ const renderAgentIdColumn = useCallback((agentId) => , []);
const renderRowsColumn = useCallback(
(_, item) => {
diff --git a/x-pack/plugins/osquery/public/actions/actions_table.tsx b/x-pack/plugins/osquery/public/actions/actions_table.tsx
index 0ee928ad8aa147..045c1f67b070da 100644
--- a/x-pack/plugins/osquery/public/actions/actions_table.tsx
+++ b/x-pack/plugins/osquery/public/actions/actions_table.tsx
@@ -9,6 +9,7 @@ import { isArray } from 'lodash';
import { i18n } from '@kbn/i18n';
import { EuiBasicTable, EuiButtonIcon, EuiCodeBlock, formatDate } from '@elastic/eui';
import React, { useState, useCallback, useMemo } from 'react';
+import { useHistory } from 'react-router-dom';
import { useAllActions } from './use_all_actions';
import { Direction } from '../../common/search_strategy';
@@ -27,6 +28,7 @@ const ActionTableResultsButton = React.memo(({ ac
ActionTableResultsButton.displayName = 'ActionTableResultsButton';
const ActionsTableComponent = () => {
+ const { push } = useHistory();
const [pageIndex, setPageIndex] = useState(0);
const [pageSize, setPageSize] = useState(20);
@@ -67,6 +69,16 @@ const ActionsTableComponent = () => {
[]
);
+ const handlePlayClick = useCallback(
+ (item) =>
+ push('/live_queries/new', {
+ form: {
+ query: item._source?.data?.query,
+ },
+ }),
+ [push]
+ );
+
const columns = useMemo(
() => [
{
@@ -106,6 +118,11 @@ const ActionsTableComponent = () => {
defaultMessage: 'View details',
}),
actions: [
+ {
+ type: 'icon',
+ icon: 'play',
+ onClick: handlePlayClick,
+ },
{
render: renderActionsColumn,
},
@@ -113,6 +130,7 @@ const ActionsTableComponent = () => {
},
],
[
+ handlePlayClick,
renderActionsColumn,
renderAgentsColumn,
renderCreatedByColumn,
@@ -135,6 +153,7 @@ const ActionsTableComponent = () => {
= ({ agentId }) => {
+ const getUrlForApp = useKibana().services.application.getUrlForApp;
+ const { data } = useAgentDetails({ agentId });
+
+ return (
+
+ {data?.item.local_metadata.host.name ?? agentId}
+
+ );
+};
+
+export const AgentIdToName = React.memo(AgentIdToNameComponent);
diff --git a/x-pack/plugins/osquery/public/agents/agents_table.tsx b/x-pack/plugins/osquery/public/agents/agents_table.tsx
index 7e8f49c051614d..53e2ce1d534204 100644
--- a/x-pack/plugins/osquery/public/agents/agents_table.tsx
+++ b/x-pack/plugins/osquery/public/agents/agents_table.tsx
@@ -21,7 +21,12 @@ import {
generateAgentSelection,
} from './helpers';
-import { SELECT_AGENT_LABEL, generateSelectedAgentsMessage } from './translations';
+import {
+ SELECT_AGENT_LABEL,
+ generateSelectedAgentsMessage,
+ ALL_AGENTS_LABEL,
+ AGENT_POLICY_LABEL,
+} from './translations';
import {
AGENT_GROUP_KEY,
@@ -72,8 +77,17 @@ const AgentsTableComponent: React.FC = ({ agentSelection, onCh
useEffect(() => {
if (agentSelection && !defaultValueInitialized.current && options.length) {
- if (agentSelection.policiesSelected) {
- const policyOptions = find(['label', 'Policy'], options);
+ if (agentSelection.allAgentsSelected) {
+ const allAgentsOptions = find(['label', ALL_AGENTS_LABEL], options);
+
+ if (allAgentsOptions?.options) {
+ setSelectedOptions(allAgentsOptions.options);
+ defaultValueInitialized.current = true;
+ }
+ }
+
+ if (agentSelection.policiesSelected.length) {
+ const policyOptions = find(['label', AGENT_POLICY_LABEL], options);
if (policyOptions) {
const defaultOptions = policyOptions.options?.filter((option) =>
@@ -82,12 +96,12 @@ const AgentsTableComponent: React.FC = ({ agentSelection, onCh
if (defaultOptions?.length) {
setSelectedOptions(defaultOptions);
+ defaultValueInitialized.current = true;
}
- defaultValueInitialized.current = true;
}
}
}
- }, [agentSelection, options]);
+ }, [agentSelection, options, selectedOptions]);
useEffect(() => {
// update the groups when groups or agents have changed
diff --git a/x-pack/plugins/osquery/public/agents/use_agent_details.ts b/x-pack/plugins/osquery/public/agents/use_agent_details.ts
new file mode 100644
index 00000000000000..1a0663812dec36
--- /dev/null
+++ b/x-pack/plugins/osquery/public/agents/use_agent_details.ts
@@ -0,0 +1,36 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { i18n } from '@kbn/i18n';
+import { useQuery } from 'react-query';
+
+import { GetOneAgentResponse, agentRouteService } from '../../../fleet/common';
+import { useErrorToast } from '../common/hooks/use_error_toast';
+import { useKibana } from '../common/lib/kibana';
+
+interface UseAgentDetails {
+ agentId: string;
+}
+
+export const useAgentDetails = ({ agentId }: UseAgentDetails) => {
+ const { http } = useKibana().services;
+ const setErrorToast = useErrorToast();
+ return useQuery(
+ ['agentDetails', agentId],
+ () => http.get(agentRouteService.getInfoPath(agentId)),
+ {
+ enabled: agentId.length > 0,
+ onSuccess: () => setErrorToast(),
+ onError: (error) =>
+ setErrorToast(error as Error, {
+ title: i18n.translate('xpack.osquery.agentDetails.fetchError', {
+ defaultMessage: 'Error while fetching agent details',
+ }),
+ }),
+ }
+ );
+};
diff --git a/x-pack/plugins/osquery/public/agents/use_all_agents.ts b/x-pack/plugins/osquery/public/agents/use_all_agents.ts
index 30ba4d2f579079..cda15cc8054371 100644
--- a/x-pack/plugins/osquery/public/agents/use_all_agents.ts
+++ b/x-pack/plugins/osquery/public/agents/use_all_agents.ts
@@ -38,7 +38,7 @@ export const useAllAgents = (
let kuery = `last_checkin_status: online and (${policyFragment})`;
if (searchValue) {
- kuery += `and (local_metadata.host.hostname:*${searchValue}* or local_metadata.elastic.agent.id:*${searchValue}*)`;
+ kuery += ` and (local_metadata.host.hostname:*${searchValue}* or local_metadata.elastic.agent.id:*${searchValue}*)`;
}
return http.get(agentRouteService.getListPath(), {
diff --git a/x-pack/plugins/osquery/public/live_queries/form/live_query_query_field.tsx b/x-pack/plugins/osquery/public/live_queries/form/live_query_query_field.tsx
index 9f0b5acd8994a5..070339bb58af22 100644
--- a/x-pack/plugins/osquery/public/live_queries/form/live_query_query_field.tsx
+++ b/x-pack/plugins/osquery/public/live_queries/form/live_query_query_field.tsx
@@ -6,12 +6,15 @@
*/
import { EuiFormRow, EuiSpacer } from '@elastic/eui';
-import React, { useCallback } from 'react';
+import React, { useCallback, useRef } from 'react';
import { OsquerySchemaLink } from '../../components/osquery_schema_link';
import { FieldHook } from '../../shared_imports';
import { OsqueryEditor } from '../../editor';
-import { SavedQueriesDropdown } from '../../saved_queries/saved_queries_dropdown';
+import {
+ SavedQueriesDropdown,
+ SavedQueriesDropdownRef,
+} from '../../saved_queries/saved_queries_dropdown';
interface LiveQueryQueryFieldProps {
disabled?: boolean;
@@ -21,16 +24,18 @@ interface LiveQueryQueryFieldProps {
const LiveQueryQueryFieldComponent: React.FC = ({ disabled, field }) => {
const { value, setValue, errors } = field;
const error = errors[0]?.message;
+ const savedQueriesDropdownRef = useRef(null);
const handleSavedQueryChange = useCallback(
(savedQuery) => {
- setValue(savedQuery.query);
+ setValue(savedQuery?.query ?? '');
},
[setValue]
);
const handleEditorChange = useCallback(
(newValue) => {
+ savedQueriesDropdownRef.current?.clearSelection();
setValue(newValue);
},
[setValue]
@@ -39,7 +44,11 @@ const LiveQueryQueryFieldComponent: React.FC = ({ disa
return (
<>
-
+
}>
diff --git a/x-pack/plugins/osquery/public/routes/live_queries/new/index.tsx b/x-pack/plugins/osquery/public/routes/live_queries/new/index.tsx
index 9967eb97cddf22..40614c1f3e1b8e 100644
--- a/x-pack/plugins/osquery/public/routes/live_queries/new/index.tsx
+++ b/x-pack/plugins/osquery/public/routes/live_queries/new/index.tsx
@@ -8,7 +8,7 @@
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { useMemo } from 'react';
-import { useLocation } from 'react-router-dom';
+import { useHistory, useLocation } from 'react-router-dom';
import qs from 'query-string';
import { WithHeaderLayout } from '../../../components/layouts';
@@ -19,12 +19,18 @@ import { BetaBadge, BetaBadgeRowWrapper } from '../../../components/beta_badge';
const NewLiveQueryPageComponent = () => {
useBreadcrumbs('live_query_new');
+ const { replace } = useHistory();
const location = useLocation();
const liveQueryListProps = useRouterNavigate('live_queries');
const formDefaultValue = useMemo(() => {
const queryParams = qs.parse(location.search);
+ if (location.state?.form.query) {
+ replace({ state: null });
+ return { query: location.state?.form.query };
+ }
+
if (queryParams?.agentPolicyId) {
return {
agentSelection: {
@@ -37,7 +43,7 @@ const NewLiveQueryPageComponent = () => {
}
return undefined;
- }, [location.search]);
+ }, [location.search, location.state, replace]);
const LeftColumn = useMemo(
() => (
diff --git a/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx b/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx
index 7e8e8e543dfabf..8738c06d065979 100644
--- a/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx
+++ b/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx
@@ -16,6 +16,7 @@ import {
import React, { useCallback, useMemo, useState } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
+import { useHistory } from 'react-router-dom';
import { SavedObject } from 'kibana/public';
import { WithHeaderLayout } from '../../../components/layouts';
@@ -51,6 +52,7 @@ const EditButton = React.memo(EditButtonComponent);
const SavedQueriesPageComponent = () => {
useBreadcrumbs('saved_queries');
+ const { push } = useHistory();
const newQueryLinkProps = useRouterNavigate('saved_queries/new');
const [pageIndex, setPageIndex] = useState(0);
const [pageSize, setPageSize] = useState(10);
@@ -59,21 +61,15 @@ const SavedQueriesPageComponent = () => {
const { data } = useSavedQueries({ isLive: true });
- // const handlePlayClick = useCallback(
- // (item) =>
- // push({
- // search: qs.stringify({
- // tab: 'live_query',
- // }),
- // state: {
- // query: {
- // id: item.id,
- // query: item.attributes.query,
- // },
- // },
- // }),
- // [push]
- // );
+ const handlePlayClick = useCallback(
+ (item) =>
+ push('/live_queries/new', {
+ form: {
+ savedQueryId: item.id,
+ },
+ }),
+ [push]
+ );
const renderEditAction = useCallback(
(item: SavedObject<{ name: string }>) => (
@@ -96,45 +92,53 @@ const SavedQueriesPageComponent = () => {
() => [
{
field: 'attributes.id',
- name: 'Query ID',
+ name: i18n.translate('xpack.osquery.savedQueries.table.queryIdColumnTitle', {
+ defaultMessage: 'Query ID',
+ }),
sortable: true,
truncateText: true,
},
{
field: 'attributes.description',
- name: 'Description',
+ name: i18n.translate('xpack.osquery.savedQueries.table.descriptionColumnTitle', {
+ defaultMessage: 'Description',
+ }),
sortable: true,
truncateText: true,
},
{
field: 'attributes.created_by',
- name: 'Created by',
+ name: i18n.translate('xpack.osquery.savedQueries.table.createdByColumnTitle', {
+ defaultMessage: 'Created by',
+ }),
sortable: true,
truncateText: true,
},
{
field: 'attributes.updated_at',
- name: 'Last updated at',
+ name: i18n.translate('xpack.osquery.savedQueries.table.updatedAtColumnTitle', {
+ defaultMessage: 'Last updated at',
+ }),
sortable: (item: SavedObject<{ updated_at: string }>) =>
item.attributes.updated_at ? Date.parse(item.attributes.updated_at) : 0,
truncateText: true,
render: renderUpdatedAt,
},
{
- name: 'Actions',
+ name: i18n.translate('xpack.osquery.savedQueries.table.actionsColumnTitle', {
+ defaultMessage: 'Actions',
+ }),
actions: [
- // {
- // name: 'Live query',
- // description: 'Run live query',
- // type: 'icon',
- // icon: 'play',
- // onClick: handlePlayClick,
- // },
+ {
+ type: 'icon',
+ icon: 'play',
+ onClick: handlePlayClick,
+ },
{ render: renderEditAction },
],
},
],
- [renderEditAction, renderUpdatedAt]
+ [handlePlayClick, renderEditAction, renderUpdatedAt]
);
const onTableChange = useCallback(({ page = {}, sort = {} }) => {
diff --git a/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx b/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx
index e30954a695b2d9..fc7cee2fc804cd 100644
--- a/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx
+++ b/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx
@@ -6,45 +6,83 @@
*/
import { find } from 'lodash/fp';
-import { EuiCodeBlock, EuiFormRow, EuiComboBox, EuiText } from '@elastic/eui';
-import React, { useCallback, useState } from 'react';
+import { EuiCodeBlock, EuiFormRow, EuiComboBox, EuiTextColor } from '@elastic/eui';
+import React, {
+ forwardRef,
+ useCallback,
+ useEffect,
+ useImperativeHandle,
+ useMemo,
+ useState,
+} from 'react';
import { SimpleSavedObject } from 'kibana/public';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
+import { useHistory, useLocation } from 'react-router-dom';
+import styled from 'styled-components';
import { useSavedQueries } from './use_saved_queries';
+export interface SavedQueriesDropdownRef {
+ clearSelection: () => void;
+}
+
+const TextTruncate = styled.div`
+ overflow: hidden;
+ text-overflow: ellipsis;
+`;
+
+const StyledEuiCodeBlock = styled(EuiCodeBlock)`
+ .euiCodeBlock__line {
+ white-space: nowrap;
+ }
+`;
+
interface SavedQueriesDropdownProps {
disabled?: boolean;
onChange: (
- value: SimpleSavedObject<{
- id: string;
- description?: string | undefined;
- query: string;
- }>['attributes']
+ value:
+ | SimpleSavedObject<{
+ id: string;
+ description?: string | undefined;
+ query: string;
+ }>['attributes']
+ | null
) => void;
}
-const SavedQueriesDropdownComponent: React.FC = ({
- disabled,
- onChange,
-}) => {
+const SavedQueriesDropdownComponent = forwardRef<
+ SavedQueriesDropdownRef,
+ SavedQueriesDropdownProps
+>(({ disabled, onChange }, ref) => {
+ const { replace } = useHistory();
+ const location = useLocation();
const [selectedOptions, setSelectedOptions] = useState([]);
const { data } = useSavedQueries({});
- const queryOptions =
- data?.savedObjects?.map((savedQuery) => ({
- label: savedQuery.attributes.id ?? '',
- value: {
- id: savedQuery.attributes.id,
- description: savedQuery.attributes.description,
- query: savedQuery.attributes.query,
- },
- })) ?? [];
+ const queryOptions = useMemo(
+ () =>
+ data?.savedObjects?.map((savedQuery) => ({
+ label: savedQuery.attributes.id ?? '',
+ value: {
+ savedObjectId: savedQuery.id,
+ id: savedQuery.attributes.id,
+ description: savedQuery.attributes.description,
+ query: savedQuery.attributes.query,
+ },
+ })) ?? [],
+ [data?.savedObjects]
+ );
const handleSavedQueryChange = useCallback(
(newSelectedOptions) => {
+ if (!newSelectedOptions.length) {
+ onChange(null);
+ setSelectedOptions(newSelectedOptions);
+ return;
+ }
+
const selectedSavedQuery = find(
['attributes.id', newSelectedOptions[0].value.id],
data?.savedObjects
@@ -62,17 +100,41 @@ const SavedQueriesDropdownComponent: React.FC = ({
({ value }) => (
<>
{value.id}
-
- {value.description}
-
-
- {value.query}
-
+
+ {value.description}
+
+
+ {value.query.split('\n').join(' ')}
+
>
),
[]
);
+ const clearSelection = useCallback(() => setSelectedOptions([]), []);
+
+ useEffect(() => {
+ const savedQueryId = location.state?.form?.savedQueryId;
+
+ if (savedQueryId) {
+ const savedQueryOption = find(['value.savedObjectId', savedQueryId], queryOptions);
+
+ if (savedQueryOption) {
+ handleSavedQueryChange([savedQueryOption]);
+ }
+
+ replace({ state: null });
+ }
+ }, [handleSavedQueryChange, replace, location.state, queryOptions]);
+
+ useImperativeHandle(
+ ref,
+ () => ({
+ clearSelection,
+ }),
+ [clearSelection]
+ );
+
return (
= ({
selectedOptions={selectedOptions}
onChange={handleSavedQueryChange}
renderOption={renderOption}
- rowHeight={90}
+ rowHeight={110}
/>
);
-};
+});
export const SavedQueriesDropdown = React.memo(SavedQueriesDropdownComponent);
diff --git a/x-pack/plugins/osquery/public/saved_queries/use_update_saved_query.ts b/x-pack/plugins/osquery/public/saved_queries/use_update_saved_query.ts
index 1260413676a4e1..6f4aa517108112 100644
--- a/x-pack/plugins/osquery/public/saved_queries/use_update_saved_query.ts
+++ b/x-pack/plugins/osquery/public/saved_queries/use_update_saved_query.ts
@@ -56,7 +56,7 @@ export const useUpdateSavedQuery = ({ savedQueryId }: UseUpdateSavedQueryProps)
i18n.translate('xpack.osquery.editSavedQuery.successToastMessageText', {
defaultMessage: 'Successfully updated "{savedQueryName}" query',
values: {
- savedQueryName: payload.attributes?.name ?? '',
+ savedQueryName: payload.attributes?.id ?? '',
},
})
);
diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/query_flyout.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/query_flyout.tsx
index 32547bc5dd2d08..95a31efeaf135b 100644
--- a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/query_flyout.tsx
+++ b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/query_flyout.tsx
@@ -71,10 +71,14 @@ const QueryFlyoutComponent: React.FC = ({
[integrationPackageVersion]
);
- const { submit, setFieldValue } = form;
+ const { submit, setFieldValue, reset } = form;
const handleSetQueryValue = useCallback(
(savedQuery) => {
+ if (!savedQuery) {
+ return reset();
+ }
+
setFieldValue('id', savedQuery.id);
setFieldValue('query', savedQuery.query);
@@ -94,7 +98,7 @@ const QueryFlyoutComponent: React.FC = ({
setFieldValue('version', [savedQuery.version]);
}
},
- [isFieldSupported, setFieldValue]
+ [isFieldSupported, setFieldValue, reset]
);
return (
diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_group_queries_table.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_group_queries_table.tsx
index 36d15587086f2d..01acf2dc0d8263 100644
--- a/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_group_queries_table.tsx
+++ b/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_group_queries_table.tsx
@@ -258,7 +258,7 @@ const ViewResultsInDiscoverActionComponent: React.FC{ILM_POLICY_NAME},
}}
@@ -33,7 +33,10 @@ const i18nTexts = {
buttonLabel: i18n.translate(
'xpack.reporting.listing.ilmPolicyCallout.migrateIndicesButtonLabel',
{
- defaultMessage: 'Migrate indices',
+ defaultMessage: 'Apply {ilmPolicyName} policy',
+ values: {
+ ilmPolicyName: ILM_POLICY_NAME,
+ },
}
),
migrateErrorTitle: i18n.translate(
@@ -45,7 +48,7 @@ const i18nTexts = {
migrateSuccessTitle: i18n.translate(
'xpack.reporting.listing.ilmPolicyCallout.migrateIndicesSuccessTitle',
{
- defaultMessage: 'Successfully migrated reporting indices',
+ defaultMessage: 'Reporting policy active for all reporting indices',
}
),
};
diff --git a/x-pack/plugins/rule_registry/server/index.ts b/x-pack/plugins/rule_registry/server/index.ts
index b6fd6b9a605c0a..19ea85b056bedf 100644
--- a/x-pack/plugins/rule_registry/server/index.ts
+++ b/x-pack/plugins/rule_registry/server/index.ts
@@ -15,6 +15,11 @@ export { RuleDataClient } from './rule_data_client';
export { IRuleDataClient } from './rule_data_client/types';
export { getRuleExecutorData, RuleExecutorData } from './utils/get_rule_executor_data';
export { createLifecycleRuleTypeFactory } from './utils/create_lifecycle_rule_type_factory';
+export {
+ LifecycleRuleExecutor,
+ LifecycleAlertServices,
+ createLifecycleExecutor,
+} from './utils/create_lifecycle_executor';
export { createPersistenceRuleTypeFactory } from './utils/create_persistence_rule_type_factory';
export const plugin = (initContext: PluginInitializerContext) =>
diff --git a/x-pack/plugins/rule_registry/server/types.ts b/x-pack/plugins/rule_registry/server/types.ts
index f8bd1940b10a8e..051789b1896bbe 100644
--- a/x-pack/plugins/rule_registry/server/types.ts
+++ b/x-pack/plugins/rule_registry/server/types.ts
@@ -12,7 +12,7 @@ import {
AlertTypeParams,
AlertTypeState,
} from '../../alerting/common';
-import { AlertType } from '../../alerting/server';
+import { AlertExecutorOptions, AlertServices, AlertType } from '../../alerting/server';
import { AlertsClient } from './alert_data_client/alerts_client';
type SimpleAlertType<
@@ -41,6 +41,20 @@ export type AlertTypeWithExecutor<
executor: AlertTypeExecutor;
};
+export type AlertExecutorOptionsWithExtraServices<
+ Params extends AlertTypeParams = never,
+ State extends AlertTypeState = never,
+ InstanceState extends AlertInstanceState = never,
+ InstanceContext extends AlertInstanceContext = never,
+ ActionGroupIds extends string = never,
+ TExtraServices extends {} = never
+> = Omit<
+ AlertExecutorOptions,
+ 'services'
+> & {
+ services: AlertServices & TExtraServices;
+};
+
/**
* @public
*/
diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts
new file mode 100644
index 00000000000000..06c2cc8ff005db
--- /dev/null
+++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts
@@ -0,0 +1,330 @@
+/*
+ * 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 { Logger } from '@kbn/logging';
+import { getOrElse } from 'fp-ts/lib/Either';
+import * as rt from 'io-ts';
+import { Mutable } from 'utility-types';
+import { v4 } from 'uuid';
+import {
+ AlertExecutorOptions,
+ AlertInstance,
+ AlertInstanceContext,
+ AlertInstanceState,
+ AlertTypeParams,
+ AlertTypeState,
+} from '../../../alerting/server';
+import { ParsedTechnicalFields, parseTechnicalFields } from '../../common/parse_technical_fields';
+import {
+ ALERT_DURATION,
+ ALERT_END,
+ ALERT_ID,
+ ALERT_START,
+ ALERT_STATUS,
+ ALERT_UUID,
+ EVENT_ACTION,
+ EVENT_KIND,
+ OWNER,
+ RULE_UUID,
+ TIMESTAMP,
+} from '../../common/technical_rule_data_field_names';
+import { RuleDataClient } from '../rule_data_client';
+import { AlertExecutorOptionsWithExtraServices } from '../types';
+import { getRuleData } from './get_rule_executor_data';
+
+type LifecycleAlertService<
+ InstanceState extends AlertInstanceState = never,
+ InstanceContext extends AlertInstanceContext = never,
+ ActionGroupIds extends string = never
+> = (alert: {
+ id: string;
+ fields: Record;
+}) => AlertInstance;
+
+export interface LifecycleAlertServices<
+ InstanceState extends AlertInstanceState = never,
+ InstanceContext extends AlertInstanceContext = never,
+ ActionGroupIds extends string = never
+> {
+ alertWithLifecycle: LifecycleAlertService;
+}
+
+export type LifecycleRuleExecutor<
+ Params extends AlertTypeParams = never,
+ State extends AlertTypeState = never,
+ InstanceState extends AlertInstanceState = never,
+ InstanceContext extends AlertInstanceContext = never,
+ ActionGroupIds extends string = never
+> = (
+ options: AlertExecutorOptionsWithExtraServices<
+ Params,
+ State,
+ InstanceState,
+ InstanceContext,
+ ActionGroupIds,
+ LifecycleAlertServices
+ >
+) => Promise;
+
+const trackedAlertStateRt = rt.type({
+ alertId: rt.string,
+ alertUuid: rt.string,
+ started: rt.string,
+});
+
+export type TrackedLifecycleAlertState = rt.TypeOf;
+
+const alertTypeStateRt = () =>
+ rt.record(rt.string, rt.unknown) as rt.Type;
+
+const wrappedStateRt = () =>
+ rt.type({
+ wrapped: alertTypeStateRt(),
+ trackedAlerts: rt.record(rt.string, trackedAlertStateRt),
+ });
+
+/**
+ * This is redefined instead of derived from above `wrappedStateRt` because
+ * there's no easy way to instantiate generic values such as the runtime type
+ * factory function.
+ */
+export type WrappedLifecycleRuleState = AlertTypeState & {
+ wrapped: State | void;
+ trackedAlerts: Record;
+};
+
+export const createLifecycleExecutor = (logger: Logger, ruleDataClient: RuleDataClient) => <
+ Params extends AlertTypeParams = never,
+ State extends AlertTypeState = never,
+ InstanceState extends AlertInstanceState = never,
+ InstanceContext extends AlertInstanceContext = never,
+ ActionGroupIds extends string = never
+>(
+ wrappedExecutor: LifecycleRuleExecutor<
+ Params,
+ State,
+ InstanceState,
+ InstanceContext,
+ ActionGroupIds
+ >
+) => async (
+ options: AlertExecutorOptions<
+ Params,
+ WrappedLifecycleRuleState,
+ InstanceState,
+ InstanceContext,
+ ActionGroupIds
+ >
+): Promise> => {
+ const {
+ rule,
+ services: { alertInstanceFactory },
+ state: previousState,
+ } = options;
+
+ const ruleExecutorData = getRuleData(options);
+
+ const state = getOrElse(
+ (): WrappedLifecycleRuleState => ({
+ wrapped: previousState as State,
+ trackedAlerts: {},
+ })
+ )(wrappedStateRt().decode(previousState));
+
+ const currentAlerts: Record = {};
+
+ const timestamp = options.startedAt.toISOString();
+
+ const lifecycleAlertServices: LifecycleAlertServices<
+ InstanceState,
+ InstanceContext,
+ ActionGroupIds
+ > = {
+ alertWithLifecycle: ({ id, fields }) => {
+ currentAlerts[id] = {
+ ...fields,
+ [ALERT_ID]: id,
+ };
+ return alertInstanceFactory(id);
+ },
+ };
+
+ const nextWrappedState = await wrappedExecutor({
+ ...options,
+ state: state.wrapped != null ? state.wrapped : ({} as State),
+ services: {
+ ...options.services,
+ ...lifecycleAlertServices,
+ },
+ });
+
+ const currentAlertIds = Object.keys(currentAlerts);
+ const trackedAlertIds = Object.keys(state.trackedAlerts);
+ const newAlertIds = currentAlertIds.filter((alertId) => !trackedAlertIds.includes(alertId));
+
+ const allAlertIds = [...new Set(currentAlertIds.concat(trackedAlertIds))];
+
+ const trackedAlertStatesOfRecovered = Object.values(state.trackedAlerts).filter(
+ (trackedAlertState) => !currentAlerts[trackedAlertState.alertId]
+ );
+
+ logger.debug(
+ `Tracking ${allAlertIds.length} alerts (${newAlertIds.length} new, ${trackedAlertStatesOfRecovered.length} recovered)`
+ );
+
+ const alertsDataMap: Record<
+ string,
+ {
+ [ALERT_ID]: string;
+ }
+ > = {
+ ...currentAlerts,
+ };
+
+ if (trackedAlertStatesOfRecovered.length) {
+ const { hits } = await ruleDataClient.getReader().search({
+ body: {
+ query: {
+ bool: {
+ filter: [
+ {
+ term: {
+ [RULE_UUID]: ruleExecutorData[RULE_UUID],
+ },
+ },
+ {
+ terms: {
+ [ALERT_UUID]: trackedAlertStatesOfRecovered.map(
+ (trackedAlertState) => trackedAlertState.alertUuid
+ ),
+ },
+ },
+ ],
+ },
+ },
+ size: trackedAlertStatesOfRecovered.length,
+ collapse: {
+ field: ALERT_UUID,
+ },
+ _source: false,
+ fields: [{ field: '*', include_unmapped: true }],
+ sort: {
+ [TIMESTAMP]: 'desc' as const,
+ },
+ },
+ allow_no_indices: true,
+ });
+
+ hits.hits.forEach((hit) => {
+ const fields = parseTechnicalFields(hit.fields);
+ const alertId = fields[ALERT_ID]!;
+ alertsDataMap[alertId] = {
+ ...fields,
+ [ALERT_ID]: alertId,
+ };
+ });
+ }
+
+ const eventsToIndex = allAlertIds.map((alertId) => {
+ const alertData = alertsDataMap[alertId];
+
+ if (!alertData) {
+ logger.warn(`Could not find alert data for ${alertId}`);
+ }
+
+ const event: Mutable = {
+ ...alertData,
+ ...ruleExecutorData,
+ [TIMESTAMP]: timestamp,
+ [EVENT_KIND]: 'event',
+ [OWNER]: rule.consumer,
+ [ALERT_ID]: alertId,
+ };
+
+ const isNew = !state.trackedAlerts[alertId];
+ const isRecovered = !currentAlerts[alertId];
+ const isActiveButNotNew = !isNew && !isRecovered;
+ const isActive = !isRecovered;
+
+ const { alertUuid, started } = state.trackedAlerts[alertId] ?? {
+ alertUuid: v4(),
+ started: timestamp,
+ };
+
+ event[ALERT_START] = started;
+ event[ALERT_UUID] = alertUuid;
+
+ if (isNew) {
+ event[EVENT_ACTION] = 'open';
+ }
+
+ if (isRecovered) {
+ event[ALERT_END] = timestamp;
+ event[EVENT_ACTION] = 'close';
+ event[ALERT_STATUS] = 'closed';
+ }
+
+ if (isActiveButNotNew) {
+ event[EVENT_ACTION] = 'active';
+ }
+
+ if (isActive) {
+ event[ALERT_STATUS] = 'open';
+ }
+
+ event[ALERT_DURATION] =
+ (options.startedAt.getTime() - new Date(event[ALERT_START]!).getTime()) * 1000;
+
+ return event;
+ });
+
+ if (eventsToIndex.length) {
+ const alertEvents: Map = new Map();
+
+ for (const event of eventsToIndex) {
+ const uuid = event[ALERT_UUID]!;
+ let storedEvent = alertEvents.get(uuid);
+ if (!storedEvent) {
+ storedEvent = event;
+ }
+ alertEvents.set(uuid, {
+ ...storedEvent,
+ [EVENT_KIND]: 'signal',
+ });
+ }
+ logger.debug(`Preparing to index ${eventsToIndex.length} alerts.`);
+
+ if (ruleDataClient.isWriteEnabled()) {
+ await ruleDataClient.getWriter().bulk({
+ body: eventsToIndex
+ .flatMap((event) => [{ index: {} }, event])
+ .concat(
+ Array.from(alertEvents.values()).flatMap((event) => [
+ { index: { _id: event[ALERT_UUID]! } },
+ event,
+ ])
+ ),
+ });
+ }
+ }
+
+ const nextTrackedAlerts = Object.fromEntries(
+ eventsToIndex
+ .filter((event) => event[ALERT_STATUS] !== 'closed')
+ .map((event) => {
+ const alertId = event[ALERT_ID]!;
+ const alertUuid = event[ALERT_UUID]!;
+ const started = new Date(event[ALERT_START]!).toISOString();
+ return [alertId, { alertId, alertUuid, started }];
+ })
+ );
+
+ return {
+ wrapped: nextWrappedState ?? ({} as State),
+ trackedAlerts: ruleDataClient.isWriteEnabled() ? nextTrackedAlerts : {},
+ };
+};
diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts
index a37ba9ef566366..3e7fbbe5cbc59f 100644
--- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts
+++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts
@@ -38,11 +38,11 @@ function createRule() {
});
nextAlerts = [];
},
- id: 'test_type',
+ id: 'ruleTypeId',
minimumLicenseRequired: 'basic',
isExportable: true,
- name: 'Test type',
- producer: 'test',
+ name: 'ruleTypeName',
+ producer: 'producer',
actionVariables: {
context: [],
params: [],
@@ -195,11 +195,11 @@ describe('createLifecycleRuleTypeFactory', () => {
"kibana.rac.alert.duration.us": 0,
"kibana.rac.alert.id": "opbeans-java",
"kibana.rac.alert.owner": "consumer",
- "kibana.rac.alert.producer": "test",
+ "kibana.rac.alert.producer": "producer",
"kibana.rac.alert.start": "2021-06-16T09:01:00.000Z",
"kibana.rac.alert.status": "open",
- "rule.category": "Test type",
- "rule.id": "test_type",
+ "rule.category": "ruleTypeName",
+ "rule.id": "ruleTypeId",
"rule.name": "name",
"rule.uuid": "alertId",
"service.name": "opbeans-java",
@@ -214,11 +214,11 @@ describe('createLifecycleRuleTypeFactory', () => {
"kibana.rac.alert.duration.us": 0,
"kibana.rac.alert.id": "opbeans-node",
"kibana.rac.alert.owner": "consumer",
- "kibana.rac.alert.producer": "test",
+ "kibana.rac.alert.producer": "producer",
"kibana.rac.alert.start": "2021-06-16T09:01:00.000Z",
"kibana.rac.alert.status": "open",
- "rule.category": "Test type",
- "rule.id": "test_type",
+ "rule.category": "ruleTypeName",
+ "rule.id": "ruleTypeId",
"rule.name": "name",
"rule.uuid": "alertId",
"service.name": "opbeans-node",
@@ -233,11 +233,11 @@ describe('createLifecycleRuleTypeFactory', () => {
"kibana.rac.alert.duration.us": 0,
"kibana.rac.alert.id": "opbeans-java",
"kibana.rac.alert.owner": "consumer",
- "kibana.rac.alert.producer": "test",
+ "kibana.rac.alert.producer": "producer",
"kibana.rac.alert.start": "2021-06-16T09:01:00.000Z",
"kibana.rac.alert.status": "open",
- "rule.category": "Test type",
- "rule.id": "test_type",
+ "rule.category": "ruleTypeName",
+ "rule.id": "ruleTypeId",
"rule.name": "name",
"rule.uuid": "alertId",
"service.name": "opbeans-java",
@@ -252,11 +252,11 @@ describe('createLifecycleRuleTypeFactory', () => {
"kibana.rac.alert.duration.us": 0,
"kibana.rac.alert.id": "opbeans-node",
"kibana.rac.alert.owner": "consumer",
- "kibana.rac.alert.producer": "test",
+ "kibana.rac.alert.producer": "producer",
"kibana.rac.alert.start": "2021-06-16T09:01:00.000Z",
"kibana.rac.alert.status": "open",
- "rule.category": "Test type",
- "rule.id": "test_type",
+ "rule.category": "ruleTypeName",
+ "rule.id": "ruleTypeId",
"rule.name": "name",
"rule.uuid": "alertId",
"service.name": "opbeans-node",
diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type_factory.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type_factory.ts
index 34045a2a905f8f..cf1be1bd32013b 100644
--- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type_factory.ts
+++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type_factory.ts
@@ -5,51 +5,26 @@
* 2.0.
*/
import { Logger } from '@kbn/logging';
-import { isLeft } from 'fp-ts/lib/Either';
-import * as t from 'io-ts';
-import { Mutable } from 'utility-types';
-import v4 from 'uuid/v4';
-import { AlertInstance } from '../../../alerting/server';
import { RuleDataClient } from '..';
import {
AlertInstanceContext,
AlertInstanceState,
AlertTypeParams,
+ AlertTypeState,
} from '../../../alerting/common';
-import {
- ALERT_DURATION,
- ALERT_END,
- ALERT_ID,
- ALERT_START,
- ALERT_STATUS,
- ALERT_UUID,
- EVENT_ACTION,
- EVENT_KIND,
- OWNER,
- RULE_UUID,
- TIMESTAMP,
-} from '../../common/technical_rule_data_field_names';
+import { AlertInstance } from '../../../alerting/server';
import { AlertTypeWithExecutor } from '../types';
-import { ParsedTechnicalFields, parseTechnicalFields } from '../../common/parse_technical_fields';
-import { getRuleExecutorData } from './get_rule_executor_data';
+import { createLifecycleExecutor } from './create_lifecycle_executor';
export type LifecycleAlertService> = (alert: {
id: string;
fields: Record;
}) => AlertInstance;
-const trackedAlertStateRt = t.type({
- alertId: t.string,
- alertUuid: t.string,
- started: t.string,
-});
-
-const wrappedStateRt = t.type({
- wrapped: t.record(t.string, t.unknown),
- trackedAlerts: t.record(t.string, trackedAlertStateRt),
-});
-
-type CreateLifecycleRuleTypeFactory = (options: {
+export const createLifecycleRuleTypeFactory = ({
+ logger,
+ ruleDataClient,
+}: {
ruleDataClient: RuleDataClient;
logger: Logger;
}) => <
@@ -58,216 +33,17 @@ type CreateLifecycleRuleTypeFactory = (options: {
TServices extends { alertWithLifecycle: LifecycleAlertService }
>(
type: AlertTypeWithExecutor
-) => AlertTypeWithExecutor;
-
-export const createLifecycleRuleTypeFactory: CreateLifecycleRuleTypeFactory = ({
- logger,
- ruleDataClient,
-}) => (type) => {
+): AlertTypeWithExecutor => {
+ const createBoundLifecycleExecutor = createLifecycleExecutor(logger, ruleDataClient);
+ const executor = createBoundLifecycleExecutor<
+ TParams,
+ AlertTypeState,
+ AlertInstanceState,
+ TAlertInstanceContext,
+ string
+ >(type.executor as any);
return {
...type,
- executor: async (options) => {
- const {
- services: { alertInstanceFactory },
- state: previousState,
- rule,
- } = options;
-
- const ruleExecutorData = getRuleExecutorData(type, options);
-
- const decodedState = wrappedStateRt.decode(previousState);
-
- const state = isLeft(decodedState)
- ? {
- wrapped: previousState,
- trackedAlerts: {},
- }
- : decodedState.right;
-
- const currentAlerts: Record = {};
-
- const timestamp = options.startedAt.toISOString();
-
- const nextWrappedState = await type.executor({
- ...options,
- state: state.wrapped,
- services: {
- ...options.services,
- alertWithLifecycle: ({ id, fields }) => {
- currentAlerts[id] = {
- ...fields,
- [ALERT_ID]: id,
- };
- return alertInstanceFactory(id);
- },
- },
- });
-
- const currentAlertIds = Object.keys(currentAlerts);
- const trackedAlertIds = Object.keys(state.trackedAlerts);
- const newAlertIds = currentAlertIds.filter((alertId) => !trackedAlertIds.includes(alertId));
-
- const allAlertIds = [...new Set(currentAlertIds.concat(trackedAlertIds))];
-
- const trackedAlertStatesOfRecovered = Object.values(state.trackedAlerts).filter(
- (trackedAlertState) => !currentAlerts[trackedAlertState.alertId]
- );
-
- logger.debug(
- `Tracking ${allAlertIds.length} alerts (${newAlertIds.length} new, ${trackedAlertStatesOfRecovered.length} recovered)`
- );
-
- const alertsDataMap: Record<
- string,
- {
- [ALERT_ID]: string;
- }
- > = {
- ...currentAlerts,
- };
-
- if (trackedAlertStatesOfRecovered.length) {
- const { hits } = await ruleDataClient.getReader().search({
- body: {
- query: {
- bool: {
- filter: [
- {
- term: {
- [RULE_UUID]: ruleExecutorData[RULE_UUID],
- },
- },
- {
- terms: {
- [ALERT_UUID]: trackedAlertStatesOfRecovered.map(
- (trackedAlertState) => trackedAlertState.alertUuid
- ),
- },
- },
- ],
- },
- },
- size: trackedAlertStatesOfRecovered.length,
- collapse: {
- field: ALERT_UUID,
- },
- _source: false,
- fields: [{ field: '*', include_unmapped: true }],
- sort: {
- [TIMESTAMP]: 'desc' as const,
- },
- },
- allow_no_indices: true,
- });
-
- hits.hits.forEach((hit) => {
- const fields = parseTechnicalFields(hit.fields);
- const alertId = fields[ALERT_ID]!;
- alertsDataMap[alertId] = {
- ...fields,
- [ALERT_ID]: alertId,
- };
- });
- }
-
- const eventsToIndex = allAlertIds.map((alertId) => {
- const alertData = alertsDataMap[alertId];
-
- if (!alertData) {
- logger.warn(`Could not find alert data for ${alertId}`);
- }
-
- const event: Mutable = {
- ...alertData,
- ...ruleExecutorData,
- [TIMESTAMP]: timestamp,
- [EVENT_KIND]: 'event',
- [OWNER]: rule.consumer,
- [ALERT_ID]: alertId,
- };
-
- const isNew = !state.trackedAlerts[alertId];
- const isRecovered = !currentAlerts[alertId];
- const isActiveButNotNew = !isNew && !isRecovered;
- const isActive = !isRecovered;
-
- const { alertUuid, started } = state.trackedAlerts[alertId] ?? {
- alertUuid: v4(),
- started: timestamp,
- };
-
- event[ALERT_START] = started;
- event[ALERT_UUID] = alertUuid;
-
- if (isNew) {
- event[EVENT_ACTION] = 'open';
- }
-
- if (isRecovered) {
- event[ALERT_END] = timestamp;
- event[EVENT_ACTION] = 'close';
- event[ALERT_STATUS] = 'closed';
- }
-
- if (isActiveButNotNew) {
- event[EVENT_ACTION] = 'active';
- }
-
- if (isActive) {
- event[ALERT_STATUS] = 'open';
- }
-
- event[ALERT_DURATION] =
- (options.startedAt.getTime() - new Date(event[ALERT_START]!).getTime()) * 1000;
-
- return event;
- });
-
- if (eventsToIndex.length) {
- const alertEvents: Map = new Map();
-
- for (const event of eventsToIndex) {
- const uuid = event[ALERT_UUID]!;
- let storedEvent = alertEvents.get(uuid);
- if (!storedEvent) {
- storedEvent = event;
- }
- alertEvents.set(uuid, {
- ...storedEvent,
- [EVENT_KIND]: 'signal',
- });
- }
- logger.debug(`Preparing to index ${eventsToIndex.length} alerts.`);
-
- if (ruleDataClient.isWriteEnabled()) {
- await ruleDataClient.getWriter().bulk({
- body: eventsToIndex
- .flatMap((event) => [{ index: {} }, event])
- .concat(
- Array.from(alertEvents.values()).flatMap((event) => [
- { index: { _id: event[ALERT_UUID]! } },
- event,
- ])
- ),
- });
- }
- }
-
- const nextTrackedAlerts = Object.fromEntries(
- eventsToIndex
- .filter((event) => event[ALERT_STATUS] !== 'closed')
- .map((event) => {
- const alertId = event[ALERT_ID]!;
- const alertUuid = event[ALERT_UUID]!;
- const started = new Date(event[ALERT_START]!).toISOString();
- return [alertId, { alertId, alertUuid, started }];
- })
- );
-
- return {
- wrapped: nextWrappedState ?? {},
- trackedAlerts: ruleDataClient.isWriteEnabled() ? nextTrackedAlerts : {},
- };
- },
+ executor: executor as any,
};
};
diff --git a/x-pack/plugins/rule_registry/server/utils/get_rule_executor_data.ts b/x-pack/plugins/rule_registry/server/utils/get_rule_executor_data.ts
index 1ea640add7b48f..7cb02428322a65 100644
--- a/x-pack/plugins/rule_registry/server/utils/get_rule_executor_data.ts
+++ b/x-pack/plugins/rule_registry/server/utils/get_rule_executor_data.ts
@@ -5,6 +5,7 @@
* 2.0.
*/
+import { AlertExecutorOptions } from '../../../alerting/server';
import {
PRODUCER,
RULE_CATEGORY,
@@ -37,3 +38,14 @@ export function getRuleExecutorData(
[PRODUCER]: type.producer,
};
}
+
+export function getRuleData(options: AlertExecutorOptions) {
+ return {
+ [RULE_ID]: options.rule.ruleTypeId,
+ [RULE_UUID]: options.alertId,
+ [RULE_CATEGORY]: options.rule.ruleTypeName,
+ [RULE_NAME]: options.rule.name,
+ [TAGS]: options.tags,
+ [PRODUCER]: options.rule.producer,
+ };
+}
diff --git a/x-pack/plugins/rule_registry/server/utils/rbac.ts b/x-pack/plugins/rule_registry/server/utils/rbac.ts
index 812dbb84088123..e07c4394be2f10 100644
--- a/x-pack/plugins/rule_registry/server/utils/rbac.ts
+++ b/x-pack/plugins/rule_registry/server/utils/rbac.ts
@@ -9,9 +9,14 @@
* registering a new instance of the rule data client
* in a new plugin will require updating the below data structure
* to include the index name where the alerts as data will be written to.
+ *
+ * This doesn't work in combination with the `xpack.ruleRegistry.index`
+ * setting, with which the user can change the index prefix.
*/
export const mapConsumerToIndexName = {
apm: '.alerts-observability-apm',
+ logs: '.alerts-observability.logs',
+ infrastructure: '.alerts-observability.metrics',
observability: '.alerts-observability',
siem: ['.alerts-security.alerts', '.siem-signals'],
};
diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts
index abfcb4014a79fd..27d4a5c9fd3994 100644
--- a/x-pack/plugins/security_solution/common/constants.ts
+++ b/x-pack/plugins/security_solution/common/constants.ts
@@ -110,6 +110,7 @@ export const APP_EVENT_FILTERS_PATH = `${APP_PATH}${EVENT_FILTERS_PATH}`;
/** The comma-delimited list of Elasticsearch indices from which the SIEM app collects events */
export const DEFAULT_INDEX_PATTERN = [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
diff --git a/x-pack/plugins/security_solution/common/cti/constants.ts b/x-pack/plugins/security_solution/common/cti/constants.ts
index 631a13df1ecb1f..7b22e9036f5667 100644
--- a/x-pack/plugins/security_solution/common/cti/constants.ts
+++ b/x-pack/plugins/security_solution/common/cti/constants.ts
@@ -65,9 +65,9 @@ export const CTI_DEFAULT_SOURCES = [
'Abuse Malware',
'AlienVault OTX',
'Anomali',
- 'Anomali ThreatStream',
'Malware Bazaar',
'MISP',
+ 'Recorded Future',
];
export const DEFAULT_CTI_SOURCE_INDEX = ['filebeat-*'];
diff --git a/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.test.ts b/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.test.ts
index 7de082e778a07b..b38886296e74d4 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.test.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.test.ts
@@ -1150,7 +1150,7 @@ describe('get_filter', () => {
},
{
field: '@timestamp',
- format: 'epoch_millis',
+ format: 'strict_date_optional_time',
},
],
},
@@ -1195,9 +1195,13 @@ describe('get_filter', () => {
field: '*',
include_unmapped: true,
},
+ {
+ field: 'event.ingested',
+ format: 'strict_date_optional_time',
+ },
{
field: '@timestamp',
- format: 'epoch_millis',
+ format: 'strict_date_optional_time',
},
],
},
@@ -1289,7 +1293,7 @@ describe('get_filter', () => {
},
{
field: '@timestamp',
- format: 'epoch_millis',
+ format: 'strict_date_optional_time',
},
],
},
diff --git a/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.ts b/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.ts
index 86e66577abd456..1e7bcb0002dad0 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.ts
@@ -79,6 +79,15 @@ export const buildEqlSearchRequest = (
eventCategoryOverride: string | undefined
): EqlSearchRequest => {
const timestamp = timestampOverride ?? '@timestamp';
+
+ const defaultTimeFields = ['@timestamp'];
+ const timestamps =
+ timestampOverride != null ? [timestampOverride, ...defaultTimeFields] : defaultTimeFields;
+ const docFields = timestamps.map((tstamp) => ({
+ field: tstamp,
+ format: 'strict_date_optional_time',
+ }));
+
// Assume that `indices.query.bool.max_clause_count` is at least 1024 (the default value),
// allowing us to make 1024-item chunks of exception list items.
// Discussion at https://issues.apache.org/jira/browse/LUCENE-4835 indicates that 1024 is a
@@ -126,14 +135,7 @@ export const buildEqlSearchRequest = (
field: '*',
include_unmapped: true,
},
- {
- field: '@timestamp',
- // BUG: We have to format @timestamp until this bug is fixed with epoch_millis
- // https://github.com/elastic/elasticsearch/issues/74582
- // TODO: Remove epoch and use the same techniques from x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.ts
- // where we format both the timestamp and any overrides as ISO8601
- format: 'epoch_millis',
- },
+ ...docFields,
],
},
};
diff --git a/x-pack/plugins/security_solution/common/ecs/file/index.ts b/x-pack/plugins/security_solution/common/ecs/file/index.ts
index 5e409b1095cf59..0c9dde20011f49 100644
--- a/x-pack/plugins/security_solution/common/ecs/file/index.ts
+++ b/x-pack/plugins/security_solution/common/ecs/file/index.ts
@@ -14,9 +14,42 @@ export interface CodeSignature {
subject_name: string[];
trusted: string[];
}
+
+export interface Token {
+ integrity_level_name: string;
+}
+
+export interface MemoryPe {
+ imphash?: string;
+}
+
+export interface StartAddressDetails {
+ allocation_base?: number;
+ allocation_protection?: string;
+ allocation_size?: number;
+ allocation_type?: string;
+ bytes_address?: number;
+ bytes_allocation_offset?: number;
+ bytes_compressed?: string;
+ bytes_compressed_present?: string;
+ mapped_path?: string;
+ mapped_pe_detected?: boolean;
+ memory_pe_detected?: boolean;
+ region_base?: number;
+ region_protection?: string;
+ region_size?: number;
+ region_state?: string;
+ strings?: string;
+ memory_pe?: MemoryPe;
+}
+
export interface Ext {
code_signature?: CodeSignature[] | CodeSignature;
original?: Original;
+ token?: Token;
+ start_address_allocation_offset?: number;
+ start_address_bytes_disasm_hash?: string;
+ start_address_details?: StartAddressDetails;
}
export interface Hash {
md5?: string[];
diff --git a/x-pack/plugins/security_solution/common/ecs/index.ts b/x-pack/plugins/security_solution/common/ecs/index.ts
index 8054b3c8521db5..610a2fd1f6e9ed 100644
--- a/x-pack/plugins/security_solution/common/ecs/index.ts
+++ b/x-pack/plugins/security_solution/common/ecs/index.ts
@@ -30,6 +30,8 @@ import { ProcessEcs } from './process';
import { SystemEcs } from './system';
import { ThreatEcs } from './threat';
import { Ransomware } from './ransomware';
+import { MemoryProtection } from './memory_protection';
+import { Target } from './target_type';
export interface Ecs {
_id: string;
@@ -63,4 +65,7 @@ export interface Ecs {
// This should be temporary
eql?: { parentId: string; sequenceNumber: string };
Ransomware?: Ransomware;
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ Memory_protection?: MemoryProtection;
+ Target?: Target;
}
diff --git a/x-pack/plugins/security_solution/common/ecs/memory_protection/index.ts b/x-pack/plugins/security_solution/common/ecs/memory_protection/index.ts
new file mode 100644
index 00000000000000..8115fc0dcd26e8
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/ecs/memory_protection/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+export interface MemoryProtection {
+ cross_session?: boolean;
+ feature?: string;
+ parent_to_child?: boolean;
+ self_injection?: boolean;
+ unique_key_v1?: string;
+}
diff --git a/x-pack/plugins/security_solution/common/ecs/process/index.ts b/x-pack/plugins/security_solution/common/ecs/process/index.ts
index 820ecc5560e6c5..0eb2400466e640 100644
--- a/x-pack/plugins/security_solution/common/ecs/process/index.ts
+++ b/x-pack/plugins/security_solution/common/ecs/process/index.ts
@@ -37,4 +37,5 @@ export interface ProcessParentData {
export interface Thread {
id?: number[];
start?: string[];
+ Ext?: Ext;
}
diff --git a/x-pack/plugins/security_solution/common/ecs/target_type/index.ts b/x-pack/plugins/security_solution/common/ecs/target_type/index.ts
new file mode 100644
index 00000000000000..3c19b51173a043
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/ecs/target_type/index.ts
@@ -0,0 +1,12 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { ProcessEcs } from '../process';
+
+export interface Target {
+ process: ProcessEcs;
+}
diff --git a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
index 876cb3866c6147..255ab8f0a598c9 100644
--- a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
@@ -7,6 +7,7 @@
import uuid from 'uuid';
import seedrandom from 'seedrandom';
+import { assertNever } from '@kbn/std';
import {
AlertEvent,
DataStream,
@@ -387,6 +388,12 @@ const eventsDefaultDataStream = {
namespace: 'default',
};
+enum AlertTypes {
+ MALWARE = 'MALWARE',
+ MEMORY_SIGNATURE = 'MEMORY_SIGNATURE',
+ MEMORY_SHELLCODE = 'MEMORY_SHELLCODE',
+}
+
const alertsDefaultDataStream = {
type: 'logs',
dataset: 'endpoint.alerts',
@@ -509,16 +516,15 @@ export class EndpointDocGenerator extends BaseDataGenerator {
data_stream: metadataDataStream,
};
}
-
/**
- * Creates an alert from the simulated host represented by this EndpointDocGenerator
+ * Creates a malware alert from the simulated host represented by this EndpointDocGenerator
* @param ts - Timestamp to put in the event
* @param entityID - entityID of the originating process
* @param parentEntityID - optional entityID of the parent process, if it exists
* @param ancestry - an array of ancestors for the generated alert
* @param alertsDataStream the values to populate the data_stream fields when generating alert documents
*/
- public generateAlert({
+ public generateMalwareAlert({
ts = new Date().getTime(),
entityID = this.randomString(10),
parentEntityID,
@@ -619,37 +625,198 @@ export class EndpointDocGenerator extends BaseDataGenerator {
},
},
},
- dll: [
- {
- pe: {
- architecture: 'x64',
+ dll: this.getAlertsDefaultDll(),
+ };
+ }
+
+ /**
+ * Creates a memory alert from the simulated host represented by this EndpointDocGenerator
+ * @param ts - Timestamp to put in the event
+ * @param entityID - entityID of the originating process
+ * @param parentEntityID - optional entityID of the parent process, if it exists
+ * @param ancestry - an array of ancestors for the generated alert
+ * @param alertsDataStream the values to populate the data_stream fields when generating alert documents
+ */
+ public generateMemoryAlert({
+ ts = new Date().getTime(),
+ entityID = this.randomString(10),
+ parentEntityID,
+ ancestry = [],
+ alertsDataStream = alertsDefaultDataStream,
+ alertType,
+ }: {
+ ts?: number;
+ entityID?: string;
+ parentEntityID?: string;
+ ancestry?: string[];
+ alertsDataStream?: DataStream;
+ alertType?: AlertTypes;
+ } = {}): AlertEvent {
+ const processName = this.randomProcessName();
+ const isShellcode = alertType === AlertTypes.MEMORY_SHELLCODE;
+ const newAlert: AlertEvent = {
+ ...this.commonInfo,
+ data_stream: alertsDataStream,
+ '@timestamp': ts,
+ ecs: {
+ version: '1.6.0',
+ },
+ // disabling naming-convention to accommodate external field
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ Memory_protection: {
+ feature: isShellcode ? 'shellcode_thread' : 'signature',
+ self_injection: true,
+ },
+ event: {
+ action: 'start',
+ kind: 'alert',
+ category: 'malware',
+ code: isShellcode ? 'malicious_thread' : 'memory_signature',
+ id: this.seededUUIDv4(),
+ dataset: 'endpoint',
+ module: 'endpoint',
+ type: 'info',
+ sequence: this.sequence++,
+ },
+ file: {},
+ process: {
+ pid: 2,
+ name: processName,
+ start: ts,
+ uptime: 0,
+ entity_id: entityID,
+ executable: `C:/fake/${processName}`,
+ parent: parentEntityID ? { entity_id: parentEntityID, pid: 1 } : undefined,
+ hash: {
+ md5: 'fake md5',
+ sha1: 'fake sha1',
+ sha256: 'fake sha256',
+ },
+ Ext: {
+ ancestry,
+ code_signature: [
+ {
+ trusted: false,
+ subject_name: 'bad signer',
+ },
+ ],
+ user: 'SYSTEM',
+ token: {
+ integrity_level_name: 'high',
},
- code_signature: {
- subject_name: 'Cybereason Inc',
- trusted: true,
+ malware_signature: {
+ all_names: 'Windows.Trojan.FakeAgent',
+ identifier: 'diagnostic-malware-signature-v1-fake',
},
+ },
+ },
+ dll: this.getAlertsDefaultDll(),
+ };
- hash: {
- md5: '1f2d082566b0fc5f2c238a5180db7451',
- sha1: 'ca85243c0af6a6471bdaa560685c51eefd6dbc0d',
- sha256: '8ad40c90a611d36eb8f9eb24fa04f7dbca713db383ff55a03aa0f382e92061a2',
+ // shellcode_thread memory alert have an additional process field
+ if (isShellcode) {
+ newAlert.Target = {
+ process: {
+ thread: {
+ Ext: {
+ start_address_allocation_offset: 0,
+ start_address_bytes_disasm_hash: 'a disam hash',
+ start_address_details: {
+ allocation_type: 'PRIVATE',
+ allocation_size: 4000,
+ region_size: 4000,
+ region_protection: 'RWX',
+ memory_pe: {
+ imphash: 'a hash',
+ },
+ },
+ },
},
+ },
+ };
+ }
+ return newAlert;
+ }
+ /**
+ * Creates an alert from the simulated host represented by this EndpointDocGenerator
+ * @param ts - Timestamp to put in the event
+ * @param entityID - entityID of the originating process
+ * @param parentEntityID - optional entityID of the parent process, if it exists
+ * @param ancestry - an array of ancestors for the generated alert
+ * @param alertsDataStream the values to populate the data_stream fields when generating alert documents
+ */
+ public generateAlert({
+ ts = new Date().getTime(),
+ entityID = this.randomString(10),
+ parentEntityID,
+ ancestry = [],
+ alertsDataStream = alertsDefaultDataStream,
+ }: {
+ ts?: number;
+ entityID?: string;
+ parentEntityID?: string;
+ ancestry?: string[];
+ alertsDataStream?: DataStream;
+ } = {}): AlertEvent {
+ const alertType = this.randomChoice(Object.values(AlertTypes));
+ switch (alertType) {
+ case AlertTypes.MALWARE:
+ return this.generateMalwareAlert({
+ ts,
+ entityID,
+ parentEntityID,
+ ancestry,
+ alertsDataStream,
+ });
+ case AlertTypes.MEMORY_SIGNATURE:
+ case AlertTypes.MEMORY_SHELLCODE:
+ return this.generateMemoryAlert({
+ ts,
+ entityID,
+ parentEntityID,
+ ancestry,
+ alertsDataStream,
+ alertType,
+ });
+ default:
+ return assertNever(alertType);
+ }
+ }
- path: 'C:\\Program Files\\Cybereason ActiveProbe\\AmSvc.exe',
- Ext: {
- compile_time: 1534424710,
- mapped_address: 5362483200,
- mapped_size: 0,
- malware_classification: {
- identifier: 'Whitelisted',
- score: 0,
- threshold: 0,
- version: '3.0.0',
- },
+ /**
+ * Returns the default DLLs used in alerts
+ */
+ private getAlertsDefaultDll() {
+ return [
+ {
+ pe: {
+ architecture: 'x64',
+ },
+ code_signature: {
+ subject_name: 'Cybereason Inc',
+ trusted: true,
+ },
+
+ hash: {
+ md5: '1f2d082566b0fc5f2c238a5180db7451',
+ sha1: 'ca85243c0af6a6471bdaa560685c51eefd6dbc0d',
+ sha256: '8ad40c90a611d36eb8f9eb24fa04f7dbca713db383ff55a03aa0f382e92061a2',
+ },
+
+ path: 'C:\\Program Files\\Cybereason ActiveProbe\\AmSvc.exe',
+ Ext: {
+ compile_time: 1534424710,
+ mapped_address: 5362483200,
+ mapped_size: 0,
+ malware_classification: {
+ identifier: 'Whitelisted',
+ score: 0,
+ threshold: 0,
+ version: '3.0.0',
},
},
- ],
- };
+ },
+ ];
}
/**
diff --git a/x-pack/plugins/security_solution/common/endpoint/types/index.ts b/x-pack/plugins/security_solution/common/endpoint/types/index.ts
index 0e799497691d60..fd119ba2e4a98c 100644
--- a/x-pack/plugins/security_solution/common/endpoint/types/index.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/types/index.ts
@@ -295,6 +295,31 @@ export type AlertEvent = Partial<{
}>;
}>;
}>;
+ // disabling naming-convention to accommodate external field
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ Memory_protection: Partial<{
+ feature: ECSField;
+ self_injection: ECSField;
+ }>;
+ Target: Partial<{
+ process: Partial<{
+ thread: Partial<{
+ Ext: Partial<{
+ start_address_allocation_offset: ECSField;
+ start_address_bytes_disasm_hash: ECSField;
+ start_address_details: Partial<{
+ allocation_type: ECSField;
+ allocation_size: ECSField;
+ region_size: ECSField;
+ region_protection: ECSField;
+ memory_pe: Partial<{
+ imphash: ECSField;
+ }>;
+ }>;
+ }>;
+ }>;
+ }>;
+ }>;
process: Partial<{
command_line: ECSField;
ppid: ECSField;
@@ -328,6 +353,10 @@ export type AlertEvent = Partial<{
>;
}>;
user: ECSField;
+ malware_signature: Partial<{
+ all_names: ECSField;
+ identifier: ECSField;
+ }>;
}>;
}>;
file: Partial<{
@@ -400,6 +429,11 @@ export enum HostStatus {
* Host is inactive as indicated by its checkin status during the last checkin window
*/
INACTIVE = 'inactive',
+
+ /**
+ * Host is unenrolled
+ */
+ UNENROLLED = 'unenrolled',
}
export type PolicyInfo = Immutable<{
diff --git a/x-pack/plugins/security_solution/cypress/ccs_integration/detection_alerts/alerts_details.spec.ts b/x-pack/plugins/security_solution/cypress/ccs_integration/detection_alerts/alerts_details.spec.ts
index f87399a6669048..229bbcce87696d 100644
--- a/x-pack/plugins/security_solution/cypress/ccs_integration/detection_alerts/alerts_details.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/ccs_integration/detection_alerts/alerts_details.spec.ts
@@ -18,7 +18,7 @@ import { cleanKibana } from '../../tasks/common';
import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
import { esArchiverCCSLoad, esArchiverCCSUnload } from '../../tasks/es_archiver';
-import { unmappedCCSRule } from '../../objects/rule';
+import { getUnmappedCCSRule } from '../../objects/rule';
import { ALERTS_URL } from '../../urls/navigation';
@@ -29,7 +29,7 @@ describe('Alert details with unmapped fields', () => {
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
waitForAlertsPanelToBeLoaded();
waitForAlertsIndexToBeCreated();
- createCustomRuleActivated(unmappedCCSRule);
+ createCustomRuleActivated(getUnmappedCCSRule());
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
waitForAlertsPanelToBeLoaded();
expandFirstAlert();
diff --git a/x-pack/plugins/security_solution/cypress/integration/cases/attach_timeline.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases/attach_timeline.spec.ts
index 29105ce1582cf0..e94f7d00f0b378 100644
--- a/x-pack/plugins/security_solution/cypress/integration/cases/attach_timeline.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/cases/attach_timeline.spec.ts
@@ -13,8 +13,8 @@ import {
selectCase,
} from '../../tasks/timeline';
import { DESCRIPTION_INPUT, ADD_COMMENT_INPUT } from '../../screens/create_new_case';
-import { case1 } from '../../objects/case';
-import { timeline } from '../../objects/timeline';
+import { getCase1 } from '../../objects/case';
+import { getTimeline } from '../../objects/timeline';
import { createTimeline } from '../../tasks/api_calls/timelines';
import { cleanKibana } from '../../tasks/common';
import { createCase } from '../../tasks/api_calls/cases';
@@ -23,7 +23,7 @@ describe('attach timeline to case', () => {
context('without cases created', () => {
beforeEach(() => {
cleanKibana();
- createTimeline(timeline).then((response) => {
+ createTimeline(getTimeline()).then((response) => {
cy.wrap(response.body.data.persistTimeline.timeline).as('myTimeline');
});
});
@@ -57,10 +57,10 @@ describe('attach timeline to case', () => {
context('with cases created', () => {
before(() => {
cleanKibana();
- createTimeline(timeline).then((response) =>
+ createTimeline(getTimeline()).then((response) =>
cy.wrap(response.body.data.persistTimeline.timeline.savedObjectId).as('timelineId')
);
- createCase(case1).then((response) => cy.wrap(response.body.id).as('caseId'));
+ createCase(getCase1()).then((response) => cy.wrap(response.body.id).as('caseId'));
});
it('attach timeline to an existing case', function () {
@@ -71,7 +71,9 @@ describe('attach timeline to case', () => {
cy.location('origin').then((origin) => {
cy.get(ADD_COMMENT_INPUT).should(
'have.text',
- `[${timeline.title}](${origin}/app/security/timelines?timeline=(id:%27${this.timelineId}%27,isOpen:!t))`
+ `[${getTimeline().title}](${origin}/app/security/timelines?timeline=(id:%27${
+ this.timelineId
+ }%27,isOpen:!t))`
);
});
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/cases/connector_options.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases/connector_options.spec.ts
index 95b555c2acae6d..0959f999a4b53c 100644
--- a/x-pack/plugins/security_solution/cypress/integration/cases/connector_options.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/cases/connector_options.spec.ts
@@ -7,13 +7,13 @@
import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
import {
- case1,
- connectorIds,
- mockConnectorsResponse,
- executeResponses,
- ibmResilientConnectorOptions,
- jiraConnectorOptions,
- serviceNowConnectorOpions,
+ getCase1,
+ getConnectorIds,
+ getMockConnectorsResponse,
+ getExecuteResponses,
+ getIbmResilientConnectorOptions,
+ getJiraConnectorOptions,
+ getServiceNowConnectorOptions,
} from '../../objects/case';
import {
createCase,
@@ -30,26 +30,26 @@ import { cleanKibana } from '../../tasks/common';
describe('Cases connector incident fields', () => {
beforeEach(() => {
cleanKibana();
- cy.intercept('GET', '/api/cases/configure/connectors/_find', mockConnectorsResponse);
- cy.intercept('POST', `/api/actions/action/${connectorIds.sn}/_execute`, (req) => {
+ cy.intercept('GET', '/api/cases/configure/connectors/_find', getMockConnectorsResponse());
+ cy.intercept('POST', `/api/actions/action/${getConnectorIds().sn}/_execute`, (req) => {
const response =
req.body.params.subAction === 'getChoices'
- ? executeResponses.servicenow.choices
+ ? getExecuteResponses().servicenow.choices
: { status: 'ok', data: [] };
req.reply(response);
});
- cy.intercept('POST', `/api/actions/action/${connectorIds.jira}/_execute`, (req) => {
+ cy.intercept('POST', `/api/actions/action/${getConnectorIds().jira}/_execute`, (req) => {
const response =
req.body.params.subAction === 'issueTypes'
- ? executeResponses.jira.issueTypes
- : executeResponses.jira.fieldsByIssueType;
+ ? getExecuteResponses().jira.issueTypes
+ : getExecuteResponses().jira.fieldsByIssueType;
req.reply(response);
});
- cy.intercept('POST', `/api/actions/action/${connectorIds.resilient}/_execute`, (req) => {
+ cy.intercept('POST', `/api/actions/action/${getConnectorIds().resilient}/_execute`, (req) => {
const response =
req.body.params.subAction === 'incidentTypes'
- ? executeResponses.resilient.incidentTypes
- : executeResponses.resilient.severity;
+ ? getExecuteResponses().resilient.incidentTypes
+ : getExecuteResponses().resilient.severity;
req.reply(response);
});
});
@@ -57,19 +57,19 @@ describe('Cases connector incident fields', () => {
it('Correct incident fields show when connector is changed', () => {
loginAndWaitForPageWithoutDateRange(CASES_URL);
goToCreateNewCase();
- fillCasesMandatoryfields(case1);
- fillJiraConnectorOptions(jiraConnectorOptions);
- fillServiceNowConnectorOptions(serviceNowConnectorOpions);
- fillIbmResilientConnectorOptions(ibmResilientConnectorOptions);
+ fillCasesMandatoryfields(getCase1());
+ fillJiraConnectorOptions(getJiraConnectorOptions());
+ fillServiceNowConnectorOptions(getServiceNowConnectorOptions());
+ fillIbmResilientConnectorOptions(getIbmResilientConnectorOptions());
createCase();
- cy.get(CONNECTOR_TITLE).should('have.text', ibmResilientConnectorOptions.title);
+ cy.get(CONNECTOR_TITLE).should('have.text', getIbmResilientConnectorOptions().title);
cy.get(CONNECTOR_CARD_DETAILS).should(
'have.text',
`${
- ibmResilientConnectorOptions.title
- }Incident Types: ${ibmResilientConnectorOptions.incidentTypes.join(', ')}Severity: ${
- ibmResilientConnectorOptions.severity
+ getIbmResilientConnectorOptions().title
+ }Incident Types: ${getIbmResilientConnectorOptions().incidentTypes.join(', ')}Severity: ${
+ getIbmResilientConnectorOptions().severity
}`
);
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/cases/connectors.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases/connectors.spec.ts
index 9e55067ce4ed4d..aa1bd7a5db5cc0 100644
--- a/x-pack/plugins/security_solution/cypress/integration/cases/connectors.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/cases/connectors.spec.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { serviceNowConnector } from '../../objects/case';
+import { getServiceNowConnector } from '../../objects/case';
import { SERVICE_NOW_MAPPING, TOASTER } from '../../screens/configure_cases';
@@ -77,7 +77,7 @@ describe('Cases connectors', () => {
loginAndWaitForPageWithoutDateRange(CASES_URL);
goToEditExternalConnection();
openAddNewConnectorOption();
- addServiceNowConnector(serviceNowConnector);
+ addServiceNowConnector(getServiceNowConnector());
cy.wait('@createConnector').then(({ response }) => {
cy.wrap(response!.statusCode).should('eql', 200);
diff --git a/x-pack/plugins/security_solution/cypress/integration/cases/creation.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases/creation.spec.ts
index c568aaae664a09..9e3b775156cabd 100644
--- a/x-pack/plugins/security_solution/cypress/integration/cases/creation.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/cases/creation.spec.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { case1 } from '../../objects/case';
+import { getCase1, TestCase } from '../../objects/case';
import {
ALL_CASES_CLOSED_CASES_STATS,
@@ -55,12 +55,12 @@ import { CASES_URL } from '../../urls/navigation';
describe('Cases', () => {
beforeEach(() => {
cleanKibana();
- createTimeline(case1.timeline).then((response) =>
+ createTimeline(getCase1().timeline).then((response) =>
cy
.wrap({
- ...case1,
+ ...getCase1(),
timeline: {
- ...case1.timeline,
+ ...getCase1().timeline,
id: response.body.data.persistTimeline.timeline.savedObjectId,
},
})
@@ -86,7 +86,7 @@ describe('Cases', () => {
cy.get(ALL_CASES_TAGS_COUNT).should('have.text', 'Tags2');
cy.get(ALL_CASES_NAME).should('have.text', this.mycase.name);
cy.get(ALL_CASES_REPORTER).should('have.text', this.mycase.reporter);
- (this.mycase as typeof case1).tags.forEach((tag, index) => {
+ (this.mycase as TestCase).tags.forEach((tag, index) => {
cy.get(ALL_CASES_TAGS(index)).should('have.text', tag);
});
cy.get(ALL_CASES_COMMENTS_COUNT).should('have.text', '0');
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts
index d9ca43339d4126..825cc7f8081e5f 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts
@@ -18,7 +18,7 @@ import { cleanKibana } from '../../tasks/common';
import { esArchiverLoad } from '../../tasks/es_archiver';
import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
-import { unmappedRule } from '../../objects/rule';
+import { getUnmappedRule } from '../../objects/rule';
import { ALERTS_URL } from '../../urls/navigation';
@@ -29,7 +29,7 @@ describe('Alert details with unmapped fields', () => {
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
waitForAlertsPanelToBeLoaded();
waitForAlertsIndexToBeCreated();
- createCustomRuleActivated(unmappedRule);
+ createCustomRuleActivated(getUnmappedRule());
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
waitForAlertsPanelToBeLoaded();
expandFirstAlert();
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_detection_callouts_index_outdated.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_detection_callouts_index_outdated.spec.ts
index fb0b96c977e32c..eaed80c484f603 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_detection_callouts_index_outdated.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_detection_callouts_index_outdated.spec.ts
@@ -7,7 +7,7 @@
import { ROLES } from '../../../common/test';
import { DETECTIONS_RULE_MANAGEMENT_URL, ALERTS_URL } from '../../urls/navigation';
-import { newRule } from '../../objects/rule';
+import { getNewRule } from '../../objects/rule';
import { PAGE_TITLE } from '../../screens/common/page';
import {
@@ -77,7 +77,7 @@ describe('Detections > Need Admin Callouts indicating an admin is needed to migr
context('On Rule Details page', () => {
beforeEach(() => {
- createCustomRule(newRule);
+ createCustomRule(getNewRule());
loadPageAsPlatformEngineerUser(DETECTIONS_RULE_MANAGEMENT_URL);
waitForPageTitleToBeShown();
goToRuleDetails();
@@ -127,7 +127,7 @@ describe('Detections > Need Admin Callouts indicating an admin is needed to migr
context('On Rule Details page', () => {
beforeEach(() => {
- createCustomRule(newRule);
+ createCustomRule(getNewRule());
loadPageAsPlatformEngineerUser(DETECTIONS_RULE_MANAGEMENT_URL);
waitForPageTitleToBeShown();
goToRuleDetails();
@@ -177,7 +177,7 @@ describe('Detections > Need Admin Callouts indicating an admin is needed to migr
context('On Rule Details page', () => {
beforeEach(() => {
- createCustomRule(newRule);
+ createCustomRule(getNewRule());
loadPageAsPlatformEngineerUser(DETECTIONS_RULE_MANAGEMENT_URL);
waitForPageTitleToBeShown();
goToRuleDetails();
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/attach_to_case.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/attach_to_case.spec.ts
index 6cc5d2443e7840..e052d1a3272acc 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/attach_to_case.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/attach_to_case.spec.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { newRule } from '../../objects/rule';
+import { getNewRule } from '../../objects/rule';
import { ROLES } from '../../../common/test';
import { waitForAlertsIndexToBeCreated, waitForAlertsPanelToBeLoaded } from '../../tasks/alerts';
@@ -30,7 +30,7 @@ describe('Alerts timeline', () => {
loginAndWaitForPage(ALERTS_URL, ROLES.platform_engineer);
waitForAlertsPanelToBeLoaded();
waitForAlertsIndexToBeCreated();
- createCustomRuleActivated(newRule);
+ createCustomRuleActivated(getNewRule());
refreshPage();
waitForAlertsToPopulate(500);
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/closing.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/closing.spec.ts
index 6ae23733d6434b..038bc30c90c1e0 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/closing.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/closing.spec.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { newRule } from '../../objects/rule';
+import { getNewRule } from '../../objects/rule';
import {
ALERTS,
ALERTS_COUNT,
@@ -39,7 +39,7 @@ describe('Closing alerts', () => {
loginAndWaitForPage(ALERTS_URL);
waitForAlertsPanelToBeLoaded();
waitForAlertsIndexToBeCreated();
- createCustomRuleActivated(newRule, '1', '100m', 100);
+ createCustomRuleActivated(getNewRule(), '1', '100m', 100);
refreshPage();
waitForAlertsToPopulate(100);
deleteCustomRule();
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts
index b03daf74ce247a..522e25590994fe 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { newThreatIndicatorRule } from '../../objects/rule';
+import { getNewThreatIndicatorRule } from '../../objects/rule';
import { cleanKibana, reload } from '../../tasks/common';
import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver';
import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
@@ -39,7 +39,7 @@ describe('CTI Enrichment', () => {
esArchiverLoad('suspicious_source_event');
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
goToManageAlertsDetectionRules();
- createCustomIndicatorRule(newThreatIndicatorRule);
+ createCustomIndicatorRule(getNewThreatIndicatorRule());
reload();
});
@@ -56,9 +56,9 @@ describe('CTI Enrichment', () => {
it('Displays enrichment matched.* fields on the timeline', () => {
const expectedFields = {
- 'threat.indicator.matched.atomic': newThreatIndicatorRule.atomic,
+ 'threat.indicator.matched.atomic': getNewThreatIndicatorRule().atomic,
'threat.indicator.matched.type': 'indicator_match_rule',
- 'threat.indicator.matched.field': newThreatIndicatorRule.indicatorMappingField,
+ 'threat.indicator.matched.field': getNewThreatIndicatorRule().indicatorMappingField,
};
const fields = Object.keys(expectedFields) as Array;
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/in_progress.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/in_progress.spec.ts
index cb8694d5c35af2..890f8a064aa9e4 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/in_progress.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/in_progress.spec.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { newRule } from '../../objects/rule';
+import { getNewRule } from '../../objects/rule';
import {
ALERTS,
ALERTS_COUNT,
@@ -36,7 +36,7 @@ describe('Marking alerts as in-progress', () => {
loginAndWaitForPage(ALERTS_URL);
waitForAlertsPanelToBeLoaded();
waitForAlertsIndexToBeCreated();
- createCustomRuleActivated(newRule);
+ createCustomRuleActivated(getNewRule());
refreshPage();
waitForAlertsToPopulate(500);
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/investigate_in_timeline.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/investigate_in_timeline.spec.ts
index 115118b6762d9d..01a06b3d592666 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/investigate_in_timeline.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/investigate_in_timeline.spec.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { newRule } from '../../objects/rule';
+import { getNewRule } from '../../objects/rule';
import { PROVIDER_BADGE } from '../../screens/timeline';
import {
@@ -27,7 +27,7 @@ describe('Alerts timeline', () => {
loginAndWaitForPage(ALERTS_URL);
waitForAlertsPanelToBeLoaded();
waitForAlertsIndexToBeCreated();
- createCustomRuleActivated(newRule);
+ createCustomRuleActivated(getNewRule());
refreshPage();
waitForAlertsToPopulate(500);
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/missing_privileges_callout.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/missing_privileges_callout.spec.ts
index 20a863e742efd6..0db30179284e08 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/missing_privileges_callout.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/missing_privileges_callout.spec.ts
@@ -7,7 +7,7 @@
import { ROLES } from '../../../common/test';
import { DETECTIONS_RULE_MANAGEMENT_URL, ALERTS_URL } from '../../urls/navigation';
-import { newRule } from '../../objects/rule';
+import { getNewRule } from '../../objects/rule';
import { PAGE_TITLE } from '../../screens/common/page';
import {
@@ -95,7 +95,7 @@ describe('Detections > Callouts', () => {
context('On Rule Details page', () => {
beforeEach(() => {
- createCustomRule(newRule);
+ createCustomRule(getNewRule());
loadPageAsReadOnlyUser(DETECTIONS_RULE_MANAGEMENT_URL);
waitForPageTitleToBeShown();
goToRuleDetails();
@@ -145,7 +145,7 @@ describe('Detections > Callouts', () => {
context('On Rule Details page', () => {
beforeEach(() => {
- createCustomRule(newRule);
+ createCustomRule(getNewRule());
loadPageAsPlatformEngineer(DETECTIONS_RULE_MANAGEMENT_URL);
waitForPageTitleToBeShown();
goToRuleDetails();
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/opening.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/opening.spec.ts
index 6cbc82b93f446f..4f78bdac847892 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/opening.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/opening.spec.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { newRule } from '../../objects/rule';
+import { getNewRule } from '../../objects/rule';
import {
ALERTS_COUNT,
SELECTED_ALERTS,
@@ -37,7 +37,7 @@ describe('Opening alerts', () => {
loginAndWaitForPage(ALERTS_URL);
waitForAlertsPanelToBeLoaded();
waitForAlertsIndexToBeCreated();
- createCustomRuleActivated(newRule);
+ createCustomRuleActivated(getNewRule());
refreshPage();
waitForAlertsToPopulate(500);
selectNumberOfAlerts(5);
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts
index 218b1f7745d947..e30cde49892845 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts
@@ -7,11 +7,11 @@
import { formatMitreAttackDescription } from '../../helpers/rules';
import {
- newRule,
- existingRule,
- indexPatterns,
- editedRule,
- newOverrideRule,
+ getNewRule,
+ getExistingRule,
+ getIndexPatterns,
+ getEditedRule,
+ getNewOverrideRule,
} from '../../objects/rule';
import {
ALERT_RULE_METHOD,
@@ -120,19 +120,19 @@ import { activatesRule } from '../../tasks/rule_details';
import { ALERTS_URL } from '../../urls/navigation';
describe('Custom detection rules creation', () => {
- const expectedUrls = newRule.referenceUrls.join('');
- const expectedFalsePositives = newRule.falsePositivesExamples.join('');
- const expectedTags = newRule.tags.join('');
- const expectedMitre = formatMitreAttackDescription(newRule.mitre);
+ const expectedUrls = getNewRule().referenceUrls.join('');
+ const expectedFalsePositives = getNewRule().falsePositivesExamples.join('');
+ const expectedTags = getNewRule().tags.join('');
+ const expectedMitre = formatMitreAttackDescription(getNewRule().mitre);
const expectedNumberOfRules = 1;
beforeEach(() => {
cleanKibana();
- createTimeline(newRule.timeline).then((response) => {
+ createTimeline(getNewRule().timeline).then((response) => {
cy.wrap({
- ...newRule,
+ ...getNewRule(),
timeline: {
- ...newRule.timeline,
+ ...getNewRule().timeline,
id: response.body.data.persistTimeline.timeline.savedObjectId,
},
}).as('rule');
@@ -201,7 +201,7 @@ describe('Custom detection rules creation', () => {
cy.get(INVESTIGATION_NOTES_TOGGLE).click({ force: true });
cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN);
cy.get(DEFINITION_DETAILS).within(() => {
- getDetails(INDEX_PATTERNS_DETAILS).should('have.text', indexPatterns.join(''));
+ getDetails(INDEX_PATTERNS_DETAILS).should('have.text', getIndexPatterns().join(''));
getDetails(CUSTOM_QUERY_DETAILS).should('have.text', this.rule.customQuery);
getDetails(RULE_TYPE_DETAILS).should('have.text', 'Query');
getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None');
@@ -209,11 +209,11 @@ describe('Custom detection rules creation', () => {
cy.get(SCHEDULE_DETAILS).within(() => {
getDetails(RUNS_EVERY_DETAILS).should(
'have.text',
- `${newRule.runsEvery.interval}${newRule.runsEvery.type}`
+ `${getNewRule().runsEvery.interval}${getNewRule().runsEvery.type}`
);
getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should(
'have.text',
- `${newRule.lookBack.interval}${newRule.lookBack.type}`
+ `${getNewRule().lookBack.interval}${getNewRule().lookBack.type}`
);
});
@@ -236,9 +236,9 @@ describe('Custom detection rules deletion and edition', () => {
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
goToManageAlertsDetectionRules();
waitForAlertsIndexToBeCreated();
- createCustomRuleActivated(newRule, 'rule1');
- createCustomRuleActivated(newOverrideRule, 'rule2');
- createCustomRuleActivated(existingRule, 'rule3');
+ createCustomRuleActivated(getNewRule(), 'rule1');
+ createCustomRuleActivated(getNewOverrideRule(), 'rule2');
+ createCustomRuleActivated(getExistingRule(), 'rule3');
reload();
});
@@ -303,16 +303,18 @@ describe('Custom detection rules deletion and edition', () => {
});
context('Edition', () => {
- const expectedEditedtags = editedRule.tags.join('');
+ const expectedEditedtags = getEditedRule().tags.join('');
const expectedEditedIndexPatterns =
- editedRule.index && editedRule.index.length ? editedRule.index : indexPatterns;
+ getEditedRule().index && getEditedRule().index.length
+ ? getEditedRule().index
+ : getIndexPatterns();
beforeEach(() => {
cleanKibana();
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
goToManageAlertsDetectionRules();
waitForAlertsIndexToBeCreated();
- createCustomRuleActivated(existingRule, 'rule1');
+ createCustomRuleActivated(getExistingRule(), 'rule1');
reload();
});
@@ -326,7 +328,7 @@ describe('Custom detection rules deletion and edition', () => {
cy.wait('@fetchRuleDetails').then(({ response }) => {
cy.wrap(response!.statusCode).should('eql', 200);
- cy.wrap(response!.body.max_signals).should('eql', existingRule.maxSignals);
+ cy.wrap(response!.body.max_signals).should('eql', getExistingRule().maxSignals);
cy.wrap(response!.body.enabled).should('eql', false);
});
});
@@ -336,25 +338,25 @@ describe('Custom detection rules deletion and edition', () => {
waitForKibana();
// expect define step to populate
- cy.get(CUSTOM_QUERY_INPUT).should('have.value', existingRule.customQuery);
- if (existingRule.index && existingRule.index.length > 0) {
- cy.get(DEFINE_INDEX_INPUT).should('have.text', existingRule.index.join(''));
+ cy.get(CUSTOM_QUERY_INPUT).should('have.value', getExistingRule().customQuery);
+ if (getExistingRule().index && getExistingRule().index.length > 0) {
+ cy.get(DEFINE_INDEX_INPUT).should('have.text', getExistingRule().index.join(''));
}
goToAboutStepTab();
// expect about step to populate
- cy.get(RULE_NAME_INPUT).invoke('val').should('eql', existingRule.name);
- cy.get(RULE_DESCRIPTION_INPUT).should('have.text', existingRule.description);
- cy.get(TAGS_FIELD).should('have.text', existingRule.tags.join(''));
- cy.get(SEVERITY_DROPDOWN).should('have.text', existingRule.severity);
- cy.get(DEFAULT_RISK_SCORE_INPUT).invoke('val').should('eql', existingRule.riskScore);
+ cy.get(RULE_NAME_INPUT).invoke('val').should('eql', getExistingRule().name);
+ cy.get(RULE_DESCRIPTION_INPUT).should('have.text', getExistingRule().description);
+ cy.get(TAGS_FIELD).should('have.text', getExistingRule().tags.join(''));
+ cy.get(SEVERITY_DROPDOWN).should('have.text', getExistingRule().severity);
+ cy.get(DEFAULT_RISK_SCORE_INPUT).invoke('val').should('eql', getExistingRule().riskScore);
goToScheduleStepTab();
// expect schedule step to populate
- const intervalParts =
- existingRule.interval && existingRule.interval.match(/[0-9]+|[a-zA-Z]+/g);
+ const interval = getExistingRule().interval;
+ const intervalParts = interval != null && interval.match(/[0-9]+|[a-zA-Z]+/g);
if (intervalParts) {
const [amount, unit] = intervalParts;
cy.get(SCHEDULE_INTERVAL_AMOUNT_INPUT).invoke('val').should('eql', amount);
@@ -380,7 +382,7 @@ describe('Custom detection rules deletion and edition', () => {
goToAboutStepTab();
cy.get(TAGS_CLEAR_BUTTON).click({ force: true });
- fillAboutRule(editedRule);
+ fillAboutRule(getEditedRule());
cy.intercept('GET', '/api/detection_engine/rules?id').as('getRule');
@@ -389,30 +391,30 @@ describe('Custom detection rules deletion and edition', () => {
cy.wait('@getRule').then(({ response }) => {
cy.wrap(response!.statusCode).should('eql', 200);
// ensure that editing rule does not modify max_signals
- cy.wrap(response!.body.max_signals).should('eql', existingRule.maxSignals);
+ cy.wrap(response!.body.max_signals).should('eql', getExistingRule().maxSignals);
});
- cy.get(RULE_NAME_HEADER).should('contain', `${editedRule.name}`);
- cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', editedRule.description);
+ cy.get(RULE_NAME_HEADER).should('contain', `${getEditedRule().name}`);
+ cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', getEditedRule().description);
cy.get(ABOUT_DETAILS).within(() => {
- getDetails(SEVERITY_DETAILS).should('have.text', editedRule.severity);
- getDetails(RISK_SCORE_DETAILS).should('have.text', editedRule.riskScore);
+ getDetails(SEVERITY_DETAILS).should('have.text', getEditedRule().severity);
+ getDetails(RISK_SCORE_DETAILS).should('have.text', getEditedRule().riskScore);
getDetails(TAGS_DETAILS).should('have.text', expectedEditedtags);
});
cy.get(INVESTIGATION_NOTES_TOGGLE).click({ force: true });
- cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', editedRule.note);
+ cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', getEditedRule().note);
cy.get(DEFINITION_DETAILS).within(() => {
getDetails(INDEX_PATTERNS_DETAILS).should(
'have.text',
expectedEditedIndexPatterns.join('')
);
- getDetails(CUSTOM_QUERY_DETAILS).should('have.text', editedRule.customQuery);
+ getDetails(CUSTOM_QUERY_DETAILS).should('have.text', getEditedRule().customQuery);
getDetails(RULE_TYPE_DETAILS).should('have.text', 'Query');
getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None');
});
- if (editedRule.interval) {
+ if (getEditedRule().interval) {
cy.get(SCHEDULE_DETAILS).within(() => {
- getDetails(RUNS_EVERY_DETAILS).should('have.text', editedRule.interval);
+ getDetails(RUNS_EVERY_DETAILS).should('have.text', getEditedRule().interval);
});
}
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/event_correlation_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/event_correlation_rule.spec.ts
index 337e2a8ec5033e..677a9b55464948 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/event_correlation_rule.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/event_correlation_rule.spec.ts
@@ -6,7 +6,7 @@
*/
import { formatMitreAttackDescription } from '../../helpers/rules';
-import { eqlRule, eqlSequenceRule, indexPatterns } from '../../objects/rule';
+import { getEqlRule, getEqlSequenceRule, getIndexPatterns } from '../../objects/rule';
import {
ALERT_RULE_METHOD,
@@ -78,20 +78,20 @@ import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
import { ALERTS_URL } from '../../urls/navigation';
describe('Detection rules, EQL', () => {
- const expectedUrls = eqlRule.referenceUrls.join('');
- const expectedFalsePositives = eqlRule.falsePositivesExamples.join('');
- const expectedTags = eqlRule.tags.join('');
- const expectedMitre = formatMitreAttackDescription(eqlRule.mitre);
+ const expectedUrls = getEqlRule().referenceUrls.join('');
+ const expectedFalsePositives = getEqlRule().falsePositivesExamples.join('');
+ const expectedTags = getEqlRule().tags.join('');
+ const expectedMitre = formatMitreAttackDescription(getEqlRule().mitre);
const expectedNumberOfRules = 1;
const expectedNumberOfAlerts = 7;
beforeEach(() => {
cleanKibana();
- createTimeline(eqlRule.timeline).then((response) => {
+ createTimeline(getEqlRule().timeline).then((response) => {
cy.wrap({
- ...eqlRule,
+ ...getEqlRule(),
timeline: {
- ...eqlRule.timeline,
+ ...getEqlRule().timeline,
id: response.body.data.persistTimeline.timeline.savedObjectId,
},
}).as('rule');
@@ -148,7 +148,7 @@ describe('Detection rules, EQL', () => {
cy.get(INVESTIGATION_NOTES_TOGGLE).click({ force: true });
cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN);
cy.get(DEFINITION_DETAILS).within(() => {
- getDetails(INDEX_PATTERNS_DETAILS).should('have.text', indexPatterns.join(''));
+ getDetails(INDEX_PATTERNS_DETAILS).should('have.text', getIndexPatterns().join(''));
getDetails(CUSTOM_QUERY_DETAILS).should('have.text', this.rule.customQuery);
getDetails(RULE_TYPE_DETAILS).should('have.text', 'Event Correlation');
getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None');
@@ -182,11 +182,11 @@ describe('Detection rules, sequence EQL', () => {
beforeEach(() => {
cleanKibana();
- createTimeline(eqlSequenceRule.timeline).then((response) => {
+ createTimeline(getEqlSequenceRule().timeline).then((response) => {
cy.wrap({
- ...eqlSequenceRule,
+ ...getEqlSequenceRule(),
timeline: {
- ...eqlSequenceRule.timeline,
+ ...getEqlSequenceRule().timeline,
id: response.body.data.persistTimeline.timeline.savedObjectId,
},
}).as('rule');
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/export_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/export_rule.spec.ts
index 1de636010f9670..03086810a84358 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/export_rule.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/export_rule.spec.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { expectedExportedRule, newRule } from '../../objects/rule';
+import { expectedExportedRule, getNewRule } from '../../objects/rule';
import {
goToManageAlertsDetectionRules,
waitForAlertsIndexToBeCreated,
@@ -28,7 +28,7 @@ describe('Export rules', () => {
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
waitForAlertsPanelToBeLoaded();
waitForAlertsIndexToBeCreated();
- createCustomRule(newRule).as('ruleResponse');
+ createCustomRule(getNewRule()).as('ruleResponse');
});
it('Exports a custom rule', function () {
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts
index e1268c52f75d4d..07b40df53e2d5b 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts
@@ -6,7 +6,7 @@
*/
import { formatMitreAttackDescription } from '../../helpers/rules';
-import { indexPatterns, newThreatIndicatorRule } from '../../objects/rule';
+import { getIndexPatterns, getNewThreatIndicatorRule } from '../../objects/rule';
import {
ALERT_RULE_METHOD,
@@ -109,10 +109,10 @@ import { ALERTS_URL, RULE_CREATION } from '../../urls/navigation';
describe('indicator match', () => {
describe('Detection rules, Indicator Match', () => {
- const expectedUrls = newThreatIndicatorRule.referenceUrls.join('');
- const expectedFalsePositives = newThreatIndicatorRule.falsePositivesExamples.join('');
- const expectedTags = newThreatIndicatorRule.tags.join('');
- const expectedMitre = formatMitreAttackDescription(newThreatIndicatorRule.mitre);
+ const expectedUrls = getNewThreatIndicatorRule().referenceUrls.join('');
+ const expectedFalsePositives = getNewThreatIndicatorRule().falsePositivesExamples.join('');
+ const expectedTags = getNewThreatIndicatorRule().tags.join('');
+ const expectedMitre = formatMitreAttackDescription(getNewThreatIndicatorRule().mitre);
const expectedNumberOfRules = 1;
const expectedNumberOfAlerts = 1;
@@ -134,12 +134,12 @@ describe('indicator match', () => {
describe('Index patterns', () => {
it('Contains a predefined index pattern', () => {
- getIndicatorIndex().should('have.text', indexPatterns.join(''));
+ getIndicatorIndex().should('have.text', getIndexPatterns().join(''));
});
it('Does NOT show invalidation text on initial page load if indicator index pattern is filled out', () => {
getIndicatorIndicatorIndex().type(
- `${newThreatIndicatorRule.indicatorIndexPattern}{enter}`
+ `${getNewThreatIndicatorRule().indicatorIndexPattern}{enter}`
);
getDefineContinueButton().click();
getIndexPatternInvalidationText().should('not.exist');
@@ -148,7 +148,7 @@ describe('indicator match', () => {
it('Shows invalidation text when you try to continue without filling it out', () => {
getIndexPatternClearButton().click();
getIndicatorIndicatorIndex().type(
- `${newThreatIndicatorRule.indicatorIndexPattern}{enter}`
+ `${getNewThreatIndicatorRule().indicatorIndexPattern}{enter}`
);
getDefineContinueButton().click();
getIndexPatternInvalidationText().should('exist');
@@ -195,8 +195,8 @@ describe('indicator match', () => {
describe('Indicator mapping', () => {
beforeEach(() => {
fillIndexAndIndicatorIndexPattern(
- newThreatIndicatorRule.index,
- newThreatIndicatorRule.indicatorIndexPattern
+ getNewThreatIndicatorRule().index,
+ getNewThreatIndicatorRule().indicatorIndexPattern
);
});
@@ -221,8 +221,8 @@ describe('indicator match', () => {
it('Does NOT show invalidation text when there is a valid "index field" and a valid "indicator index field"', () => {
fillIndicatorMatchRow({
- indexField: newThreatIndicatorRule.indicatorMappingField,
- indicatorIndexField: newThreatIndicatorRule.indicatorIndexField,
+ indexField: getNewThreatIndicatorRule().indicatorMappingField,
+ indicatorIndexField: getNewThreatIndicatorRule().indicatorIndexField,
});
getDefineContinueButton().click();
getIndicatorInvalidationText().should('not.exist');
@@ -231,7 +231,7 @@ describe('indicator match', () => {
it('Shows invalidation text when there is an invalid "index field" and a valid "indicator index field"', () => {
fillIndicatorMatchRow({
indexField: 'non-existent-value',
- indicatorIndexField: newThreatIndicatorRule.indicatorIndexField,
+ indicatorIndexField: getNewThreatIndicatorRule().indicatorIndexField,
validColumns: 'indicatorField',
});
getDefineContinueButton().click();
@@ -240,7 +240,7 @@ describe('indicator match', () => {
it('Shows invalidation text when there is a valid "index field" and an invalid "indicator index field"', () => {
fillIndicatorMatchRow({
- indexField: newThreatIndicatorRule.indicatorMappingField,
+ indexField: getNewThreatIndicatorRule().indicatorMappingField,
indicatorIndexField: 'non-existent-value',
validColumns: 'indexField',
});
@@ -250,21 +250,21 @@ describe('indicator match', () => {
it('Deletes the first row when you have two rows. Both rows valid rows of "index fields" and valid "indicator index fields". The second row should become the first row', () => {
fillIndicatorMatchRow({
- indexField: newThreatIndicatorRule.indicatorMappingField,
- indicatorIndexField: newThreatIndicatorRule.indicatorIndexField,
+ indexField: getNewThreatIndicatorRule().indicatorMappingField,
+ indicatorIndexField: getNewThreatIndicatorRule().indicatorIndexField,
});
getIndicatorAndButton().click();
fillIndicatorMatchRow({
rowNumber: 2,
indexField: 'agent.name',
- indicatorIndexField: newThreatIndicatorRule.indicatorIndexField,
+ indicatorIndexField: getNewThreatIndicatorRule().indicatorIndexField,
validColumns: 'indicatorField',
});
getIndicatorDeleteButton().click();
getIndicatorIndexComboField().should('have.text', 'agent.name');
getIndicatorMappingComboField().should(
'have.text',
- newThreatIndicatorRule.indicatorIndexField
+ getNewThreatIndicatorRule().indicatorIndexField
);
getIndicatorIndexComboField(2).should('not.exist');
getIndicatorMappingComboField(2).should('not.exist');
@@ -272,14 +272,14 @@ describe('indicator match', () => {
it('Deletes the first row when you have two rows. Both rows have valid "index fields" and invalid "indicator index fields". The second row should become the first row', () => {
fillIndicatorMatchRow({
- indexField: newThreatIndicatorRule.indicatorMappingField,
+ indexField: getNewThreatIndicatorRule().indicatorMappingField,
indicatorIndexField: 'non-existent-value',
validColumns: 'indexField',
});
getIndicatorAndButton().click();
fillIndicatorMatchRow({
rowNumber: 2,
- indexField: newThreatIndicatorRule.indicatorMappingField,
+ indexField: getNewThreatIndicatorRule().indicatorMappingField,
indicatorIndexField: 'second-non-existent-value',
validColumns: 'indexField',
});
@@ -292,14 +292,14 @@ describe('indicator match', () => {
it('Deletes the first row when you have two rows. Both rows have valid "indicator index fields" and invalid "index fields". The second row should become the first row', () => {
fillIndicatorMatchRow({
indexField: 'non-existent-value',
- indicatorIndexField: newThreatIndicatorRule.indicatorIndexField,
+ indicatorIndexField: getNewThreatIndicatorRule().indicatorIndexField,
validColumns: 'indicatorField',
});
getIndicatorAndButton().click();
fillIndicatorMatchRow({
rowNumber: 2,
indexField: 'second-non-existent-value',
- indicatorIndexField: newThreatIndicatorRule.indicatorIndexField,
+ indicatorIndexField: getNewThreatIndicatorRule().indicatorIndexField,
validColumns: 'indicatorField',
});
getIndicatorDeleteButton().click();
@@ -310,8 +310,8 @@ describe('indicator match', () => {
it('Deletes the first row of data but not the UI elements and the text defaults back to the placeholder of Search', () => {
fillIndicatorMatchRow({
- indexField: newThreatIndicatorRule.indicatorMappingField,
- indicatorIndexField: newThreatIndicatorRule.indicatorIndexField,
+ indexField: getNewThreatIndicatorRule().indicatorMappingField,
+ indicatorIndexField: getNewThreatIndicatorRule().indicatorIndexField,
});
getIndicatorDeleteButton().click();
getIndicatorIndexComboField().should('text', 'Search');
@@ -322,8 +322,8 @@ describe('indicator match', () => {
it('Deletes the second row when you have three rows. The first row is valid data, the second row is invalid data, and the third row is valid data. Third row should shift up correctly', () => {
fillIndicatorMatchRow({
- indexField: newThreatIndicatorRule.indicatorMappingField,
- indicatorIndexField: newThreatIndicatorRule.indicatorIndexField,
+ indexField: getNewThreatIndicatorRule().indicatorMappingField,
+ indicatorIndexField: getNewThreatIndicatorRule().indicatorIndexField,
});
getIndicatorAndButton().click();
fillIndicatorMatchRow({
@@ -335,25 +335,25 @@ describe('indicator match', () => {
getIndicatorAndButton().click();
fillIndicatorMatchRow({
rowNumber: 3,
- indexField: newThreatIndicatorRule.indicatorMappingField,
- indicatorIndexField: newThreatIndicatorRule.indicatorIndexField,
+ indexField: getNewThreatIndicatorRule().indicatorMappingField,
+ indicatorIndexField: getNewThreatIndicatorRule().indicatorIndexField,
});
getIndicatorDeleteButton(2).click();
getIndicatorIndexComboField(1).should(
'text',
- newThreatIndicatorRule.indicatorMappingField
+ getNewThreatIndicatorRule().indicatorMappingField
);
getIndicatorMappingComboField(1).should(
'text',
- newThreatIndicatorRule.indicatorIndexField
+ getNewThreatIndicatorRule().indicatorIndexField
);
getIndicatorIndexComboField(2).should(
'text',
- newThreatIndicatorRule.indicatorMappingField
+ getNewThreatIndicatorRule().indicatorMappingField
);
getIndicatorMappingComboField(2).should(
'text',
- newThreatIndicatorRule.indicatorIndexField
+ getNewThreatIndicatorRule().indicatorIndexField
);
getIndicatorIndexComboField(3).should('not.exist');
getIndicatorMappingComboField(3).should('not.exist');
@@ -368,17 +368,17 @@ describe('indicator match', () => {
getIndicatorOrButton().click();
fillIndicatorMatchRow({
rowNumber: 2,
- indexField: newThreatIndicatorRule.indicatorMappingField,
- indicatorIndexField: newThreatIndicatorRule.indicatorIndexField,
+ indexField: getNewThreatIndicatorRule().indicatorMappingField,
+ indicatorIndexField: getNewThreatIndicatorRule().indicatorIndexField,
});
getIndicatorDeleteButton().click();
getIndicatorIndexComboField().should(
'text',
- newThreatIndicatorRule.indicatorMappingField
+ getNewThreatIndicatorRule().indicatorMappingField
);
getIndicatorMappingComboField().should(
'text',
- newThreatIndicatorRule.indicatorIndexField
+ getNewThreatIndicatorRule().indicatorIndexField
);
getIndicatorIndexComboField(2).should('not.exist');
getIndicatorMappingComboField(2).should('not.exist');
@@ -399,9 +399,9 @@ describe('indicator match', () => {
waitForRulesTableToBeLoaded();
goToCreateNewRule();
selectIndicatorMatchType();
- fillDefineIndicatorMatchRuleAndContinue(newThreatIndicatorRule);
- fillAboutRuleAndContinue(newThreatIndicatorRule);
- fillScheduleRuleAndContinue(newThreatIndicatorRule);
+ fillDefineIndicatorMatchRuleAndContinue(getNewThreatIndicatorRule());
+ fillAboutRuleAndContinue(getNewThreatIndicatorRule());
+ fillScheduleRuleAndContinue(getNewThreatIndicatorRule());
createAndActivateRule();
cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)');
@@ -417,18 +417,18 @@ describe('indicator match', () => {
cy.get(RULES_TABLE).then(($table) => {
cy.wrap($table.find(RULES_ROW).length).should('eql', 1);
});
- cy.get(RULE_NAME).should('have.text', newThreatIndicatorRule.name);
- cy.get(RISK_SCORE).should('have.text', newThreatIndicatorRule.riskScore);
- cy.get(SEVERITY).should('have.text', newThreatIndicatorRule.severity);
+ cy.get(RULE_NAME).should('have.text', getNewThreatIndicatorRule().name);
+ cy.get(RISK_SCORE).should('have.text', getNewThreatIndicatorRule().riskScore);
+ cy.get(SEVERITY).should('have.text', getNewThreatIndicatorRule().severity);
cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true');
goToRuleDetails();
- cy.get(RULE_NAME_HEADER).should('contain', `${newThreatIndicatorRule.name}`);
- cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', newThreatIndicatorRule.description);
+ cy.get(RULE_NAME_HEADER).should('contain', `${getNewThreatIndicatorRule().name}`);
+ cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', getNewThreatIndicatorRule().description);
cy.get(ABOUT_DETAILS).within(() => {
- getDetails(SEVERITY_DETAILS).should('have.text', newThreatIndicatorRule.severity);
- getDetails(RISK_SCORE_DETAILS).should('have.text', newThreatIndicatorRule.riskScore);
+ getDetails(SEVERITY_DETAILS).should('have.text', getNewThreatIndicatorRule().severity);
+ getDetails(RISK_SCORE_DETAILS).should('have.text', getNewThreatIndicatorRule().riskScore);
getDetails(REFERENCE_URLS_DETAILS).should((details) => {
expect(removeExternalLinkText(details.text())).equal(expectedUrls);
});
@@ -444,18 +444,20 @@ describe('indicator match', () => {
cy.get(DEFINITION_DETAILS).within(() => {
getDetails(INDEX_PATTERNS_DETAILS).should(
'have.text',
- newThreatIndicatorRule.index.join('')
+ getNewThreatIndicatorRule().index.join('')
);
getDetails(CUSTOM_QUERY_DETAILS).should('have.text', '*:*');
getDetails(RULE_TYPE_DETAILS).should('have.text', 'Indicator Match');
getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None');
getDetails(INDICATOR_INDEX_PATTERNS).should(
'have.text',
- newThreatIndicatorRule.indicatorIndexPattern.join('')
+ getNewThreatIndicatorRule().indicatorIndexPattern.join('')
);
getDetails(INDICATOR_MAPPING).should(
'have.text',
- `${newThreatIndicatorRule.indicatorMappingField} MATCHES ${newThreatIndicatorRule.indicatorIndexField}`
+ `${getNewThreatIndicatorRule().indicatorMappingField} MATCHES ${
+ getNewThreatIndicatorRule().indicatorIndexField
+ }`
);
getDetails(INDICATOR_INDEX_QUERY).should('have.text', '*:*');
});
@@ -463,11 +465,15 @@ describe('indicator match', () => {
cy.get(SCHEDULE_DETAILS).within(() => {
getDetails(RUNS_EVERY_DETAILS).should(
'have.text',
- `${newThreatIndicatorRule.runsEvery.interval}${newThreatIndicatorRule.runsEvery.type}`
+ `${getNewThreatIndicatorRule().runsEvery.interval}${
+ getNewThreatIndicatorRule().runsEvery.type
+ }`
);
getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should(
'have.text',
- `${newThreatIndicatorRule.lookBack.interval}${newThreatIndicatorRule.lookBack.type}`
+ `${getNewThreatIndicatorRule().lookBack.interval}${
+ getNewThreatIndicatorRule().lookBack.type
+ }`
);
});
@@ -475,13 +481,15 @@ describe('indicator match', () => {
waitForAlertsToPopulate();
cy.get(NUMBER_OF_ALERTS).should('have.text', expectedNumberOfAlerts);
- cy.get(ALERT_RULE_NAME).first().should('have.text', newThreatIndicatorRule.name);
+ cy.get(ALERT_RULE_NAME).first().should('have.text', getNewThreatIndicatorRule().name);
cy.get(ALERT_RULE_VERSION).first().should('have.text', '1');
cy.get(ALERT_RULE_METHOD).first().should('have.text', 'threat_match');
cy.get(ALERT_RULE_SEVERITY)
.first()
- .should('have.text', newThreatIndicatorRule.severity.toLowerCase());
- cy.get(ALERT_RULE_RISK_SCORE).first().should('have.text', newThreatIndicatorRule.riskScore);
+ .should('have.text', getNewThreatIndicatorRule().severity.toLowerCase());
+ cy.get(ALERT_RULE_RISK_SCORE)
+ .first()
+ .should('have.text', getNewThreatIndicatorRule().riskScore);
});
it('Investigate alert in timeline', () => {
@@ -492,7 +500,7 @@ describe('indicator match', () => {
loadPrepackagedTimelineTemplates();
goToManageAlertsDetectionRules();
- createCustomIndicatorRule(newThreatIndicatorRule);
+ createCustomIndicatorRule(getNewThreatIndicatorRule());
reload();
goToRuleDetails();
@@ -502,13 +510,25 @@ describe('indicator match', () => {
cy.get(PROVIDER_BADGE).should('have.length', 3);
cy.get(PROVIDER_BADGE).should(
'have.text',
- `threat.indicator.matched.atomic: "${newThreatIndicatorRule.atomic}"threat.indicator.matched.type: "indicator_match_rule"threat.indicator.matched.field: "${newThreatIndicatorRule.indicatorMappingField}"`
+ `threat.indicator.matched.atomic: "${
+ getNewThreatIndicatorRule().atomic
+ }"threat.indicator.matched.type: "indicator_match_rule"threat.indicator.matched.field: "${
+ getNewThreatIndicatorRule().indicatorMappingField
+ }"`
);
cy.readFile(threatIndicatorPath).then((threatIndicator) => {
cy.get(INDICATOR_MATCH_ROW_RENDER).should(
'have.text',
- `threat.indicator.matched.field${newThreatIndicatorRule.indicatorMappingField}${accessibilityText}matched${newThreatIndicatorRule.indicatorMappingField}${newThreatIndicatorRule.atomic}${accessibilityText}threat.indicator.matched.typeindicator_match_rule${accessibilityText}fromthreat.indicator.event.dataset${threatIndicator.value.source.event.dataset}${accessibilityText}:threat.indicator.event.reference${threatIndicator.value.source.event.reference}(opens in a new tab or window)${accessibilityText}`
+ `threat.indicator.matched.field${
+ getNewThreatIndicatorRule().indicatorMappingField
+ }${accessibilityText}matched${getNewThreatIndicatorRule().indicatorMappingField}${
+ getNewThreatIndicatorRule().atomic
+ }${accessibilityText}threat.indicator.matched.typeindicator_match_rule${accessibilityText}fromthreat.indicator.event.dataset${
+ threatIndicator.value.source.event.dataset
+ }${accessibilityText}:threat.indicator.event.reference${
+ threatIndicator.value.source.event.reference
+ }(opens in a new tab or window)${accessibilityText}`
);
});
});
@@ -519,7 +539,7 @@ describe('indicator match', () => {
cleanKibana();
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
goToManageAlertsDetectionRules();
- createCustomIndicatorRule(newThreatIndicatorRule);
+ createCustomIndicatorRule(getNewThreatIndicatorRule());
reload();
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/links.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/links.spec.ts
index fdc4bce677f745..85eb68a6cdfa95 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/links.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/links.spec.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { newRule } from '../../objects/rule';
+import { getNewRule } from '../../objects/rule';
import { RULES_MONIROTING_TABLE, RULE_NAME } from '../../screens/alerts_detection_rules';
import { goToManageAlertsDetectionRules, waitForAlertsIndexToBeCreated } from '../../tasks/alerts';
import { createCustomRuleActivated } from '../../tasks/api_calls/rules';
@@ -19,7 +19,7 @@ describe('Rules talbes links', () => {
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
goToManageAlertsDetectionRules();
waitForAlertsIndexToBeCreated();
- createCustomRuleActivated(newRule, 'rule1');
+ createCustomRuleActivated(getNewRule(), 'rule1');
reload();
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/machine_learning_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/machine_learning_rule.spec.ts
index 2d869b314b67ca..e66f8f55be9869 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/machine_learning_rule.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/machine_learning_rule.spec.ts
@@ -6,7 +6,7 @@
*/
import { formatMitreAttackDescription } from '../../helpers/rules';
-import { machineLearningRule } from '../../objects/rule';
+import { getMachineLearningRule } from '../../objects/rule';
import {
CUSTOM_RULES_BTN,
@@ -65,10 +65,10 @@ import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
import { ALERTS_URL } from '../../urls/navigation';
describe('Detection rules, machine learning', () => {
- const expectedUrls = machineLearningRule.referenceUrls.join('');
- const expectedFalsePositives = machineLearningRule.falsePositivesExamples.join('');
- const expectedTags = machineLearningRule.tags.join('');
- const expectedMitre = formatMitreAttackDescription(machineLearningRule.mitre);
+ const expectedUrls = getMachineLearningRule().referenceUrls.join('');
+ const expectedFalsePositives = getMachineLearningRule().falsePositivesExamples.join('');
+ const expectedTags = getMachineLearningRule().tags.join('');
+ const expectedMitre = formatMitreAttackDescription(getMachineLearningRule().mitre);
const expectedNumberOfRules = 1;
beforeEach(() => {
@@ -83,9 +83,9 @@ describe('Detection rules, machine learning', () => {
waitForRulesTableToBeLoaded();
goToCreateNewRule();
selectMachineLearningRuleType();
- fillDefineMachineLearningRuleAndContinue(machineLearningRule);
- fillAboutRuleAndContinue(machineLearningRule);
- fillScheduleRuleAndContinue(machineLearningRule);
+ fillDefineMachineLearningRuleAndContinue(getMachineLearningRule());
+ fillAboutRuleAndContinue(getMachineLearningRule());
+ fillScheduleRuleAndContinue(getMachineLearningRule());
createAndActivateRule();
cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)');
@@ -101,18 +101,18 @@ describe('Detection rules, machine learning', () => {
cy.get(RULES_TABLE).then(($table) => {
cy.wrap($table.find(RULES_ROW).length).should('eql', 1);
});
- cy.get(RULE_NAME).should('have.text', machineLearningRule.name);
- cy.get(RISK_SCORE).should('have.text', machineLearningRule.riskScore);
- cy.get(SEVERITY).should('have.text', machineLearningRule.severity);
+ cy.get(RULE_NAME).should('have.text', getMachineLearningRule().name);
+ cy.get(RISK_SCORE).should('have.text', getMachineLearningRule().riskScore);
+ cy.get(SEVERITY).should('have.text', getMachineLearningRule().severity);
cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true');
goToRuleDetails();
- cy.get(RULE_NAME_HEADER).should('contain', `${machineLearningRule.name}`);
- cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', machineLearningRule.description);
+ cy.get(RULE_NAME_HEADER).should('contain', `${getMachineLearningRule().name}`);
+ cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', getMachineLearningRule().description);
cy.get(ABOUT_DETAILS).within(() => {
- getDetails(SEVERITY_DETAILS).should('have.text', machineLearningRule.severity);
- getDetails(RISK_SCORE_DETAILS).should('have.text', machineLearningRule.riskScore);
+ getDetails(SEVERITY_DETAILS).should('have.text', getMachineLearningRule().severity);
+ getDetails(RISK_SCORE_DETAILS).should('have.text', getMachineLearningRule().riskScore);
getDetails(REFERENCE_URLS_DETAILS).should((details) => {
expect(removeExternalLinkText(details.text())).equal(expectedUrls);
});
@@ -125,11 +125,11 @@ describe('Detection rules, machine learning', () => {
cy.get(DEFINITION_DETAILS).within(() => {
getDetails(ANOMALY_SCORE_DETAILS).should(
'have.text',
- machineLearningRule.anomalyScoreThreshold
+ getMachineLearningRule().anomalyScoreThreshold
);
getDetails(RULE_TYPE_DETAILS).should('have.text', 'Machine Learning');
getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None');
- machineLearningRule.machineLearningJobs.forEach((machineLearningJob, jobIndex) => {
+ getMachineLearningRule().machineLearningJobs.forEach((machineLearningJob, jobIndex) => {
cy.get(MACHINE_LEARNING_JOB_STATUS).eq(jobIndex).should('have.text', 'Stopped');
cy.get(MACHINE_LEARNING_JOB_ID).eq(jobIndex).should('have.text', machineLearningJob);
});
@@ -137,11 +137,11 @@ describe('Detection rules, machine learning', () => {
cy.get(SCHEDULE_DETAILS).within(() => {
getDetails(RUNS_EVERY_DETAILS).should(
'have.text',
- `${machineLearningRule.runsEvery.interval}${machineLearningRule.runsEvery.type}`
+ `${getMachineLearningRule().runsEvery.interval}${getMachineLearningRule().runsEvery.type}`
);
getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should(
'have.text',
- `${machineLearningRule.lookBack.interval}${machineLearningRule.lookBack.type}`
+ `${getMachineLearningRule().lookBack.interval}${getMachineLearningRule().lookBack.type}`
);
});
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/override.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/override.spec.ts
index a791cc293c1f06..24a56dd563e174 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/override.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/override.spec.ts
@@ -7,9 +7,9 @@
import { formatMitreAttackDescription } from '../../helpers/rules';
import {
- indexPatterns,
- newOverrideRule,
- severitiesOverride,
+ getIndexPatterns,
+ getNewOverrideRule,
+ getSeveritiesOverride,
OverrideRule,
} from '../../objects/rule';
@@ -89,18 +89,18 @@ import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
import { ALERTS_URL } from '../../urls/navigation';
describe('Detection rules, override', () => {
- const expectedUrls = newOverrideRule.referenceUrls.join('');
- const expectedFalsePositives = newOverrideRule.falsePositivesExamples.join('');
- const expectedTags = newOverrideRule.tags.join('');
- const expectedMitre = formatMitreAttackDescription(newOverrideRule.mitre);
+ const expectedUrls = getNewOverrideRule().referenceUrls.join('');
+ const expectedFalsePositives = getNewOverrideRule().falsePositivesExamples.join('');
+ const expectedTags = getNewOverrideRule().tags.join('');
+ const expectedMitre = formatMitreAttackDescription(getNewOverrideRule().mitre);
beforeEach(() => {
cleanKibana();
- createTimeline(newOverrideRule.timeline).then((response) => {
+ createTimeline(getNewOverrideRule().timeline).then((response) => {
cy.wrap({
- ...newOverrideRule,
+ ...getNewOverrideRule(),
timeline: {
- ...newOverrideRule.timeline,
+ ...getNewOverrideRule().timeline,
id: response.body.data.persistTimeline.timeline.savedObjectId,
},
}).as('rule');
@@ -167,7 +167,7 @@ describe('Detection rules, override', () => {
.eq(severityOverrideIndex + i)
.should(
'have.text',
- `${severity.sourceField}:${severity.sourceValue}${severitiesOverride[i]}`
+ `${severity.sourceField}:${severity.sourceValue}${getSeveritiesOverride()[i]}`
);
});
});
@@ -175,7 +175,7 @@ describe('Detection rules, override', () => {
cy.get(INVESTIGATION_NOTES_TOGGLE).click({ force: true });
cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN);
cy.get(DEFINITION_DETAILS).within(() => {
- getDetails(INDEX_PATTERNS_DETAILS).should('have.text', indexPatterns.join(''));
+ getDetails(INDEX_PATTERNS_DETAILS).should('have.text', getIndexPatterns().join(''));
getDetails(CUSTOM_QUERY_DETAILS).should('have.text', this.rule.customQuery);
getDetails(RULE_TYPE_DETAILS).should('have.text', 'Query');
getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None');
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/sorting.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/sorting.spec.ts
index 7d42ea533a9ae5..ef3d3a82d40bd7 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/sorting.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/sorting.spec.ts
@@ -39,7 +39,12 @@ import { DEFAULT_RULE_REFRESH_INTERVAL_VALUE } from '../../../common/constants';
import { ALERTS_URL } from '../../urls/navigation';
import { createCustomRule } from '../../tasks/api_calls/rules';
import { cleanKibana } from '../../tasks/common';
-import { existingRule, newOverrideRule, newRule, newThresholdRule } from '../../objects/rule';
+import {
+ getExistingRule,
+ getNewOverrideRule,
+ getNewRule,
+ getNewThresholdRule,
+} from '../../objects/rule';
describe('Alerts detection rules', () => {
beforeEach(() => {
@@ -47,10 +52,10 @@ describe('Alerts detection rules', () => {
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
waitForAlertsPanelToBeLoaded();
waitForAlertsIndexToBeCreated();
- createCustomRule(newRule, '1');
- createCustomRule(existingRule, '2');
- createCustomRule(newOverrideRule, '3');
- createCustomRule(newThresholdRule, '4');
+ createCustomRule(getNewRule(), '1');
+ createCustomRule(getExistingRule(), '2');
+ createCustomRule(getNewOverrideRule(), '3');
+ createCustomRule(getNewThresholdRule(), '4');
});
it('Sorts by activated rules', () => {
@@ -90,8 +95,8 @@ describe('Alerts detection rules', () => {
});
it('Pagination updates page number and results', () => {
- createCustomRule({ ...newRule, name: 'Test a rule' }, '5');
- createCustomRule({ ...newRule, name: 'Not same as first rule' }, '6');
+ createCustomRule({ ...getNewRule(), name: 'Test a rule' }, '5');
+ createCustomRule({ ...getNewRule(), name: 'Not same as first rule' }, '6');
goToManageAlertsDetectionRules();
waitForRulesTableToBeLoaded();
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts
index ce00c9b40aead9..dba12fb4ab95c2 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts
@@ -6,7 +6,12 @@
*/
import { formatMitreAttackDescription } from '../../helpers/rules';
-import { indexPatterns, newRule, newThresholdRule, ThresholdRule } from '../../objects/rule';
+import {
+ getIndexPatterns,
+ getNewRule,
+ getNewThresholdRule,
+ ThresholdRule,
+} from '../../objects/rule';
import {
ALERT_RULE_METHOD,
@@ -84,16 +89,16 @@ import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
import { ALERTS_URL } from '../../urls/navigation';
describe('Detection rules, threshold', () => {
- const expectedUrls = newThresholdRule.referenceUrls.join('');
- const expectedFalsePositives = newThresholdRule.falsePositivesExamples.join('');
- const expectedTags = newThresholdRule.tags.join('');
- const expectedMitre = formatMitreAttackDescription(newThresholdRule.mitre);
-
- const rule = { ...newThresholdRule };
+ let rule = getNewThresholdRule();
+ const expectedUrls = getNewThresholdRule().referenceUrls.join('');
+ const expectedFalsePositives = getNewThresholdRule().falsePositivesExamples.join('');
+ const expectedTags = getNewThresholdRule().tags.join('');
+ const expectedMitre = formatMitreAttackDescription(getNewThresholdRule().mitre);
beforeEach(() => {
+ rule = getNewThresholdRule();
cleanKibana();
- createTimeline(newThresholdRule.timeline).then((response) => {
+ createTimeline(getNewThresholdRule().timeline).then((response) => {
rule.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId;
});
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
@@ -149,7 +154,7 @@ describe('Detection rules, threshold', () => {
cy.get(INVESTIGATION_NOTES_TOGGLE).click({ force: true });
cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN);
cy.get(DEFINITION_DETAILS).within(() => {
- getDetails(INDEX_PATTERNS_DETAILS).should('have.text', indexPatterns.join(''));
+ getDetails(INDEX_PATTERNS_DETAILS).should('have.text', getIndexPatterns().join(''));
getDetails(CUSTOM_QUERY_DETAILS).should('have.text', rule.customQuery);
getDetails(RULE_TYPE_DETAILS).should('have.text', 'Threshold');
getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None');
@@ -181,15 +186,14 @@ describe('Detection rules, threshold', () => {
});
it('Preview results of keyword using "host.name"', () => {
- const previewRule: ThresholdRule = { ...newThresholdRule };
- previewRule.index = [...previewRule.index, '.siem-signals*'];
+ rule.index = [...rule.index, '.siem-signals*'];
- createCustomRuleActivated(newRule);
+ createCustomRuleActivated(getNewRule());
goToManageAlertsDetectionRules();
waitForRulesTableToBeLoaded();
goToCreateNewRule();
selectThresholdRuleType();
- fillDefineThresholdRule(previewRule);
+ fillDefineThresholdRule(rule);
previewResults();
cy.get(PREVIEW_HEADER_SUBTITLE).should('have.text', '3 unique hits');
@@ -197,13 +201,13 @@ describe('Detection rules, threshold', () => {
it('Preview results of "ip" using "source.ip"', () => {
const previewRule: ThresholdRule = {
- ...newThresholdRule,
+ ...rule,
thresholdField: 'source.ip',
threshold: '1',
};
previewRule.index = [...previewRule.index, '.siem-signals*'];
- createCustomRuleActivated(newRule);
+ createCustomRuleActivated(getNewRule());
goToManageAlertsDetectionRules();
waitForRulesTableToBeLoaded();
goToCreateNewRule();
diff --git a/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_modal.spec.ts b/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_modal.spec.ts
index a4b929f7d8e1d1..7eedc99652f80b 100644
--- a/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_modal.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_modal.spec.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { newRule } from '../../objects/rule';
+import { getNewRule } from '../../objects/rule';
import { RULE_STATUS } from '../../screens/create_new_rule';
@@ -44,7 +44,7 @@ describe('Exceptions modal', () => {
cleanKibana();
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
waitForAlertsIndexToBeCreated();
- createCustomRule(newRule);
+ createCustomRule(getNewRule());
goToManageAlertsDetectionRules();
goToRuleDetails();
diff --git a/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_table.spec.ts b/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_table.spec.ts
index 83277075b35cc9..051ebbb9643f69 100644
--- a/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_table.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_table.spec.ts
@@ -5,8 +5,12 @@
* 2.0.
*/
-import { exception, exceptionList, expectedExportedExceptionList } from '../../objects/exception';
-import { newRule } from '../../objects/rule';
+import {
+ getException,
+ getExceptionList,
+ expectedExportedExceptionList,
+} from '../../objects/exception';
+import { getNewRule } from '../../objects/rule';
import { RULE_STATUS } from '../../screens/create_new_rule';
@@ -46,7 +50,7 @@ describe('Exceptions Table', () => {
cleanKibana();
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
waitForAlertsIndexToBeCreated();
- createCustomRule(newRule);
+ createCustomRule(getNewRule());
goToManageAlertsDetectionRules();
goToRuleDetails();
@@ -56,11 +60,11 @@ describe('Exceptions Table', () => {
// Add a detections exception list
goToExceptionsTab();
- addsExceptionFromRuleSettings(exception);
+ addsExceptionFromRuleSettings(getException());
waitForTheRuleToBeExecuted();
// Create exception list not used by any rules
- createExceptionList(exceptionList).as('exceptionListResponse');
+ createExceptionList(getExceptionList()).as('exceptionListResponse');
goBackToAllRulesTable();
waitForRulesTableToBeLoaded();
diff --git a/x-pack/plugins/security_solution/cypress/integration/exceptions/from_alert.spec.ts b/x-pack/plugins/security_solution/cypress/integration/exceptions/from_alert.spec.ts
index 4918de7488dddf..8a683aacd5f666 100644
--- a/x-pack/plugins/security_solution/cypress/integration/exceptions/from_alert.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/exceptions/from_alert.spec.ts
@@ -5,8 +5,8 @@
* 2.0.
*/
-import { exception } from '../../objects/exception';
-import { newRule } from '../../objects/rule';
+import { getException } from '../../objects/exception';
+import { getNewRule } from '../../objects/rule';
import { ALERTS_COUNT, NUMBER_OF_ALERTS } from '../../screens/alerts';
import { RULE_STATUS } from '../../screens/create_new_rule';
@@ -43,7 +43,7 @@ describe('From alert', () => {
cleanKibana();
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
waitForAlertsIndexToBeCreated();
- createCustomRule(newRule, 'rule_testing', '10s');
+ createCustomRule(getNewRule(), 'rule_testing', '10s');
goToManageAlertsDetectionRules();
goToRuleDetails();
@@ -66,7 +66,7 @@ describe('From alert', () => {
it('Creates an exception and deletes it', () => {
addExceptionFromFirstAlert();
- addsException(exception);
+ addsException(getException());
esArchiverLoad('auditbeat_for_exceptions2');
cy.get(ALERTS_COUNT).should('exist');
diff --git a/x-pack/plugins/security_solution/cypress/integration/exceptions/from_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/exceptions/from_rule.spec.ts
index ea8988456d8b30..8fa0050a36521c 100644
--- a/x-pack/plugins/security_solution/cypress/integration/exceptions/from_rule.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/exceptions/from_rule.spec.ts
@@ -5,8 +5,8 @@
* 2.0.
*/
-import { exception } from '../../objects/exception';
-import { newRule } from '../../objects/rule';
+import { getException } from '../../objects/exception';
+import { getNewRule } from '../../objects/rule';
import { ALERTS_COUNT, NUMBER_OF_ALERTS } from '../../screens/alerts';
import { RULE_STATUS } from '../../screens/create_new_rule';
@@ -41,7 +41,7 @@ describe('From rule', () => {
cleanKibana();
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
waitForAlertsIndexToBeCreated();
- createCustomRule(newRule, 'rule_testing', '10s');
+ createCustomRule(getNewRule(), 'rule_testing', '10s');
goToManageAlertsDetectionRules();
goToRuleDetails();
@@ -64,7 +64,7 @@ describe('From rule', () => {
it('Creates an exception and deletes it', () => {
goToExceptionsTab();
- addsExceptionFromRuleSettings(exception);
+ addsExceptionFromRuleSettings(getException());
esArchiverLoad('auditbeat_for_exceptions2');
waitForTheRuleToBeExecuted();
goToAlertsTab();
diff --git a/x-pack/plugins/security_solution/cypress/integration/header/search_bar.spec.ts b/x-pack/plugins/security_solution/cypress/integration/header/search_bar.spec.ts
index d7bef9d67df2fa..c02c2bd9ec1391 100644
--- a/x-pack/plugins/security_solution/cypress/integration/header/search_bar.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/header/search_bar.spec.ts
@@ -8,7 +8,7 @@
import { loginAndWaitForPage } from '../../tasks/login';
import { openAddFilterPopover, fillAddFilterForm } from '../../tasks/search_bar';
import { GLOBAL_SEARCH_BAR_FILTER_ITEM } from '../../screens/search_bar';
-import { hostIpFilter } from '../../objects/filter';
+import { getHostIpFilter } from '../../objects/filter';
import { HOSTS_URL } from '../../urls/navigation';
import { waitForAllHostsToBeLoaded } from '../../tasks/hosts/all_hosts';
@@ -23,11 +23,11 @@ describe('SearchBar', () => {
it('adds correctly a filter to the global search bar', () => {
openAddFilterPopover();
- fillAddFilterForm(hostIpFilter);
+ fillAddFilterForm(getHostIpFilter());
cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM).should(
'have.text',
- `${hostIpFilter.key}: ${hostIpFilter.value}`
+ `${getHostIpFilter().key}: ${getHostIpFilter().value}`
);
});
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/overview/overview.spec.ts b/x-pack/plugins/security_solution/cypress/integration/overview/overview.spec.ts
index 3ff036fa0107fe..ca9f83183ab10e 100644
--- a/x-pack/plugins/security_solution/cypress/integration/overview/overview.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/overview/overview.spec.ts
@@ -16,7 +16,7 @@ import overviewFixture from '../../fixtures/overview_search_strategy.json';
import emptyInstance from '../../fixtures/empty_instance.json';
import { cleanKibana } from '../../tasks/common';
import { createTimeline, favoriteTimeline } from '../../tasks/api_calls/timelines';
-import { timeline } from '../../objects/timeline';
+import { getTimeline } from '../../objects/timeline';
describe('Overview Page', () => {
before(() => {
@@ -53,7 +53,7 @@ describe('Overview Page', () => {
describe('Favorite Timelines', () => {
it('should appear on overview page', () => {
- createTimeline(timeline)
+ createTimeline(getTimeline())
.then((response) => response.body.data.persistTimeline.timeline.savedObjectId)
.then((timelineId: string) => {
favoriteTimeline({ timelineId, timelineType: 'default' }).then(() => {
@@ -61,7 +61,7 @@ describe('Overview Page', () => {
loginAndWaitForPage(OVERVIEW_URL);
cy.get('[data-test-subj="overview-recent-timelines"]').should(
'contain',
- timeline.title
+ getTimeline().title
);
});
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_templates/creation.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_templates/creation.spec.ts
index e2c1d7eef38c38..3930088f8bfddc 100644
--- a/x-pack/plugins/security_solution/cypress/integration/timeline_templates/creation.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/timeline_templates/creation.spec.ts
@@ -5,14 +5,13 @@
* 2.0.
*/
-import { timeline } from '../../objects/timeline';
+import { getTimeline } from '../../objects/timeline';
import {
FAVORITE_TIMELINE,
LOCKED_ICON,
NOTES,
NOTES_TAB_BUTTON,
- // NOTES_COUNT,
NOTES_TEXT_AREA,
PIN_EVENT,
TIMELINE_DESCRIPTION,
@@ -61,7 +60,7 @@ describe('Timeline Templates', () => {
openTimelineUsingToggle();
createNewTimelineTemplate();
populateTimeline();
- addFilter(timeline.filter);
+ addFilter(getTimeline().filter);
cy.get(PIN_EVENT).should(
'have.attr',
'aria-label',
@@ -69,21 +68,21 @@ describe('Timeline Templates', () => {
);
cy.get(LOCKED_ICON).should('be.visible');
- addNameToTimeline(timeline.title);
+ addNameToTimeline(getTimeline().title);
cy.wait('@timeline').then(({ response }) => {
const timelineId = response!.body.data.persistTimeline.timeline.savedObjectId;
- addDescriptionToTimeline(timeline.description);
- addNotesToTimeline(timeline.notes);
+ addDescriptionToTimeline(getTimeline().description);
+ addNotesToTimeline(getTimeline().notes);
markAsFavorite();
waitForTimelineChanges();
createNewTimelineTemplate();
closeTimeline();
openTimelineTemplateFromSettings(timelineId);
- cy.contains(timeline.title).should('exist');
- cy.get(TIMELINES_DESCRIPTION).first().should('have.text', timeline.description);
+ cy.contains(getTimeline().title).should('exist');
+ cy.get(TIMELINES_DESCRIPTION).first().should('have.text', getTimeline().description);
cy.get(TIMELINES_PINNED_EVENT_COUNT).first().should('have.text', '1');
cy.get(TIMELINES_NOTES_COUNT).first().should('have.text', '1');
cy.get(TIMELINES_FAVORITE).first().should('exist');
@@ -91,30 +90,30 @@ describe('Timeline Templates', () => {
openTimeline(timelineId);
cy.get(FAVORITE_TIMELINE).should('exist');
- cy.get(TIMELINE_TITLE).should('have.text', timeline.title);
- cy.get(TIMELINE_DESCRIPTION).should('have.text', timeline.description);
- cy.get(TIMELINE_QUERY).should('have.text', timeline.query);
+ cy.get(TIMELINE_TITLE).should('have.text', getTimeline().title);
+ cy.get(TIMELINE_DESCRIPTION).should('have.text', getTimeline().description);
+ cy.get(TIMELINE_QUERY).should('have.text', getTimeline().query);
// Comments this assertion until we agreed what to do with the filters.
// cy.get(TIMELINE_FILTER(timeline.filter)).should('exist');
// cy.get(NOTES_COUNT).should('have.text', '1');
cy.get(NOTES_TAB_BUTTON).click();
cy.get(NOTES_TEXT_AREA).should('exist');
- cy.get(NOTES).should('have.text', timeline.notes);
+ cy.get(NOTES).should('have.text', getTimeline().notes);
});
});
it('Create template from timeline', () => {
waitForTimelinesPanelToBeLoaded();
- createTimeline(timeline).then(() => {
+ createTimeline(getTimeline()).then(() => {
expandEventAction();
clickingOnCreateTemplateFromTimelineBtn();
cy.wait('@timeline', { timeout: 100000 }).then(({ request }) => {
expect(request.body.timeline).to.haveOwnProperty('templateTimelineId');
- expect(request.body.timeline).to.haveOwnProperty('description', timeline.description);
+ expect(request.body.timeline).to.haveOwnProperty('description', getTimeline().description);
expect(request.body.timeline.kqlQuery.filterQuery.kuery).to.haveOwnProperty(
'expression',
- timeline.query
+ getTimeline().query
);
cy.get(TIMELINE_FLYOUT_WRAPPER).should('have.css', 'visibility', 'visible');
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_templates/export.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_templates/export.spec.ts
index aa0a6c9308a521..5c2d87c9b727ff 100644
--- a/x-pack/plugins/security_solution/cypress/integration/timeline_templates/export.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/timeline_templates/export.spec.ts
@@ -9,7 +9,7 @@ import { exportTimeline } from '../../tasks/timelines';
import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
import {
expectedExportedTimelineTemplate,
- timeline as timelineTemplate,
+ getTimeline as getTimelineTemplate,
} from '../../objects/timeline';
import { TIMELINE_TEMPLATES_URL } from '../../urls/navigation';
@@ -20,7 +20,7 @@ describe('Export timelines', () => {
beforeEach(() => {
cleanKibana();
cy.intercept('POST', 'api/timeline/_export?file_name=timelines_export.ndjson').as('export');
- createTimelineTemplate(timelineTemplate).then((response) => {
+ createTimelineTemplate(getTimelineTemplate()).then((response) => {
cy.wrap(response).as('templateResponse');
cy.wrap(response.body.data.persistTimeline.timeline.savedObjectId).as('templateId');
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/creation.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines/creation.spec.ts
index 8a90b67682cb2d..4203b9125d1552 100644
--- a/x-pack/plugins/security_solution/cypress/integration/timelines/creation.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/timelines/creation.spec.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { timeline } from '../../objects/timeline';
+import { getTimeline } from '../../objects/timeline';
import {
LOCKED_ICON,
@@ -64,7 +64,7 @@ describe('Timelines', (): void => {
before(() => {
openTimelineUsingToggle();
- addNameAndDescriptionToTimeline(timeline);
+ addNameAndDescriptionToTimeline(getTimeline());
populateTimeline();
});
@@ -73,8 +73,8 @@ describe('Timelines', (): void => {
});
it('can be added filter', () => {
- addFilter(timeline.filter);
- cy.get(TIMELINE_FILTER(timeline.filter)).should('exist');
+ addFilter(getTimeline().filter);
+ cy.get(TIMELINE_FILTER(getTimeline().filter)).should('exist');
});
it('pins an event', () => {
@@ -89,8 +89,8 @@ describe('Timelines', (): void => {
});
it('can be added notes', () => {
- addNotesToTimeline(timeline.notes);
- cy.get(NOTES_TEXT).should('have.text', timeline.notes);
+ addNotesToTimeline(getTimeline().notes);
+ cy.get(NOTES_TEXT).should('have.text', getTimeline().notes);
});
it('should update timeline after adding eql', () => {
@@ -116,17 +116,20 @@ describe('Create a timeline from a template', () => {
});
it('Should have the same query and open the timeline modal', () => {
- createTimelineTemplate(timeline).then(() => {
+ createTimelineTemplate(getTimeline()).then(() => {
expandEventAction();
cy.intercept('/api/timeline').as('timeline');
clickingOnCreateTimelineFormTemplateBtn();
cy.wait('@timeline', { timeout: 100000 }).then(({ request }) => {
if (request.body && request.body.timeline) {
- expect(request.body.timeline).to.haveOwnProperty('description', timeline.description);
+ expect(request.body.timeline).to.haveOwnProperty(
+ 'description',
+ getTimeline().description
+ );
expect(request.body.timeline.kqlQuery.filterQuery.kuery).to.haveOwnProperty(
'expression',
- timeline.query
+ getTimeline().query
);
cy.get(TIMELINE_FLYOUT_WRAPPER).should('have.css', 'visibility', 'visible');
}
diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/export.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines/export.spec.ts
index c2bd31c635a703..918a554db5606b 100644
--- a/x-pack/plugins/security_solution/cypress/integration/timelines/export.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/timelines/export.spec.ts
@@ -10,14 +10,14 @@ import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
import { TIMELINES_URL } from '../../urls/navigation';
import { createTimeline } from '../../tasks/api_calls/timelines';
-import { expectedExportedTimeline, timeline } from '../../objects/timeline';
+import { expectedExportedTimeline, getTimeline } from '../../objects/timeline';
import { cleanKibana } from '../../tasks/common';
describe('Export timelines', () => {
beforeEach(() => {
cleanKibana();
cy.intercept('POST', '/api/timeline/_export?file_name=timelines_export.ndjson').as('export');
- createTimeline(timeline).then((response) => {
+ createTimeline(getTimeline()).then((response) => {
cy.wrap(response).as('timelineResponse');
cy.wrap(response.body.data.persistTimeline.timeline.savedObjectId).as('timelineId');
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/notes_tab.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines/notes_tab.spec.ts
index 24309b8fda0849..0a784cf952ca66 100644
--- a/x-pack/plugins/security_solution/cypress/integration/timelines/notes_tab.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/timelines/notes_tab.spec.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { timelineNonValidQuery } from '../../objects/timeline';
+import { getTimelineNonValidQuery } from '../../objects/timeline';
import {
NOTES_AUTHOR,
@@ -39,7 +39,7 @@ describe('Timeline notes tab', () => {
loginAndWaitForPageWithoutDateRange(TIMELINES_URL);
waitForTimelinesPanelToBeLoaded();
- createTimeline(timelineNonValidQuery)
+ createTimeline(getTimelineNonValidQuery())
.then((response) => response.body.data.persistTimeline.timeline.savedObjectId)
.then((timelineId: string) =>
refreshTimelinesUntilTimeLinePresent(timelineId)
@@ -56,16 +56,16 @@ describe('Timeline notes tab', () => {
});
it('should render mockdown', () => {
cy.intercept('/api/note').as(`updateNote`);
- addNotesToTimeline(timelineNonValidQuery.notes);
+ addNotesToTimeline(getTimelineNonValidQuery().notes);
cy.wait('@updateNote').its('response.statusCode').should('eq', 200);
cy.get(NOTES_TEXT_AREA).should('exist');
});
it('should contain notes', () => {
cy.intercept('/api/note').as(`updateNote`);
- addNotesToTimeline(timelineNonValidQuery.notes);
+ addNotesToTimeline(getTimelineNonValidQuery().notes);
cy.wait('@updateNote').its('response.statusCode').should('eq', 200);
- cy.get(NOTES_TEXT).first().should('have.text', timelineNonValidQuery.notes);
+ cy.get(NOTES_TEXT).first().should('have.text', getTimelineNonValidQuery().notes);
});
it('should be able to render font in bold', () => {
@@ -91,7 +91,7 @@ describe('Timeline notes tab', () => {
it('should render the right author', () => {
cy.intercept('/api/note').as(`updateNote`);
- addNotesToTimeline(timelineNonValidQuery.notes);
+ addNotesToTimeline(getTimelineNonValidQuery().notes);
cy.wait('@updateNote').its('response.statusCode').should('eq', 200);
cy.get(NOTES_AUTHOR).first().should('have.text', text);
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/open_timeline.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines/open_timeline.spec.ts
index 814631b2af636a..5c620a983b2b32 100644
--- a/x-pack/plugins/security_solution/cypress/integration/timelines/open_timeline.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/timelines/open_timeline.spec.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { timeline } from '../../objects/timeline';
+import { getTimeline } from '../../objects/timeline';
import { TIMELINE_DESCRIPTION, TIMELINE_TITLE, OPEN_TIMELINE_MODAL } from '../../screens/timeline';
import {
@@ -39,7 +39,7 @@ describe('Open timeline', () => {
loginAndWaitForPageWithoutDateRange(TIMELINES_URL);
waitForTimelinesPanelToBeLoaded();
- createTimeline(timeline)
+ createTimeline(getTimeline())
.then((response) => response.body.data.persistTimeline.timeline.savedObjectId)
.then((timelineId: string) => {
refreshTimelinesUntilTimeLinePresent(timelineId)
@@ -47,7 +47,7 @@ describe('Open timeline', () => {
// request responses and indeterminism since on clicks to activates URL's.
.then(() => cy.wait(1000))
.then(() =>
- addNoteToTimeline(timeline.notes, timelineId).should((response) =>
+ addNoteToTimeline(getTimeline().notes, timelineId).should((response) =>
expect(response.status).to.equal(200)
)
)
@@ -71,11 +71,11 @@ describe('Open timeline', () => {
});
it('should display timeline info - title', () => {
- cy.contains(timeline.title).should('exist');
+ cy.contains(getTimeline().title).should('exist');
});
it('should display timeline info - description', () => {
- cy.get(TIMELINES_DESCRIPTION).first().should('have.text', timeline.description);
+ cy.get(TIMELINES_DESCRIPTION).first().should('have.text', getTimeline().description);
});
it('should display timeline info - pinned event count', () => {
@@ -91,11 +91,11 @@ describe('Open timeline', () => {
});
it('should display timeline content - title', () => {
- cy.get(TIMELINE_TITLE).should('have.text', timeline.title);
+ cy.get(TIMELINE_TITLE).should('have.text', getTimeline().title);
});
it('should display timeline content - description', () => {
- cy.get(TIMELINE_DESCRIPTION).should('have.text', timeline.description);
+ cy.get(TIMELINE_DESCRIPTION).should('have.text', getTimeline().description);
});
});
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/query_tab.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines/query_tab.spec.ts
index f37a66ac048fb1..06891121d63543 100644
--- a/x-pack/plugins/security_solution/cypress/integration/timelines/query_tab.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/timelines/query_tab.spec.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { timeline } from '../../objects/timeline';
+import { getTimeline } from '../../objects/timeline';
import {
UNLOCKED_ICON,
@@ -38,7 +38,7 @@ describe('Timeline query tab', () => {
loginAndWaitForPageWithoutDateRange(TIMELINES_URL);
waitForTimelinesPanelToBeLoaded();
- createTimeline(timeline)
+ createTimeline(getTimeline())
.then((response) => response.body.data.persistTimeline.timeline.savedObjectId)
.then((timelineId: string) => {
refreshTimelinesUntilTimeLinePresent(timelineId)
@@ -46,14 +46,14 @@ describe('Timeline query tab', () => {
// request responses and indeterminism since on clicks to activates URL's.
.then(() => cy.wait(1000))
.then(() =>
- addNoteToTimeline(timeline.notes, timelineId).should((response) =>
+ addNoteToTimeline(getTimeline().notes, timelineId).should((response) =>
expect(response.status).to.equal(200)
)
)
.then(() => openTimelineById(timelineId))
.then(() => pinFirstEvent())
.then(() => persistNoteToFirstEvent('event note'))
- .then(() => addFilter(timeline.filter));
+ .then(() => addFilter(getTimeline().filter));
});
});
@@ -63,7 +63,7 @@ describe('Timeline query tab', () => {
});
it('should contain the right query', () => {
- cy.get(TIMELINE_QUERY).should('have.text', `${timeline.query}`);
+ cy.get(TIMELINE_QUERY).should('have.text', `${getTimeline().query}`);
});
it('should be able to add event note', () => {
@@ -71,7 +71,7 @@ describe('Timeline query tab', () => {
});
it('should display timeline filter', () => {
- cy.get(TIMELINE_FILTER(timeline.filter)).should('exist');
+ cy.get(TIMELINE_FILTER(getTimeline().filter)).should('exist');
});
it('should display pinned events', () => {
diff --git a/x-pack/plugins/security_solution/cypress/integration/urls/state.spec.ts b/x-pack/plugins/security_solution/cypress/integration/urls/state.spec.ts
index 842dd85b42ef8b..a72657d78b70d5 100644
--- a/x-pack/plugins/security_solution/cypress/integration/urls/state.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/urls/state.spec.ts
@@ -37,7 +37,7 @@ import { addNameToTimeline, closeTimeline, populateTimeline } from '../../tasks/
import { HOSTS_URL } from '../../urls/navigation';
import { ABSOLUTE_DATE_RANGE } from '../../urls/state';
-import { timeline } from '../../objects/timeline';
+import { getTimeline } from '../../objects/timeline';
import { TIMELINE } from '../../screens/create_new_case';
import { cleanKibana } from '../../tasks/common';
@@ -244,7 +244,7 @@ describe('url state', () => {
cy.intercept('PATCH', '/api/timeline').as('timeline');
- addNameToTimeline(timeline.title);
+ addNameToTimeline(getTimeline().title);
cy.wait('@timeline').then(({ response }) => {
closeTimeline();
@@ -256,7 +256,7 @@ describe('url state', () => {
cy.get(DATE_PICKER_APPLY_BUTTON_TIMELINE).should('not.have.text', 'Updating');
cy.get(TIMELINE).should('be.visible');
cy.get(TIMELINE_TITLE).should('be.visible');
- cy.get(TIMELINE_TITLE).should('have.text', timeline.title);
+ cy.get(TIMELINE_TITLE).should('have.text', getTimeline().title);
});
});
});
diff --git a/x-pack/plugins/security_solution/cypress/objects/case.ts b/x-pack/plugins/security_solution/cypress/objects/case.ts
index 847236688dee74..8bc90c5fa2a3be 100644
--- a/x-pack/plugins/security_solution/cypress/objects/case.ts
+++ b/x-pack/plugins/security_solution/cypress/objects/case.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { CompleteTimeline, timeline } from './timeline';
+import { CompleteTimeline, getTimeline } from './timeline';
export interface TestCase extends TestCaseWithoutTimeline {
timeline: CompleteTimeline;
@@ -43,49 +43,50 @@ export interface IbmResilientConnectorOptions {
incidentTypes: string[];
}
-export const case1: TestCase = {
+export const getCase1 = (): TestCase => ({
name: 'This is the title of the case',
tags: ['Tag1', 'Tag2'],
description: 'This is the case description',
- timeline,
+ timeline: getTimeline(),
reporter: 'elastic',
owner: 'securitySolution',
-};
+});
-export const serviceNowConnector: Connector = {
+export const getServiceNowConnector = (): Connector => ({
connectorName: 'New connector',
URL: 'https://www.test.service-now.com',
username: 'Username Name',
password: 'password',
-};
+});
-export const jiraConnectorOptions: JiraConnectorOptions = {
+export const getJiraConnectorOptions = (): JiraConnectorOptions => ({
issueType: '10006',
priority: 'High',
-};
+});
-export const serviceNowConnectorOpions: ServiceNowconnectorOptions = {
+export const getServiceNowConnectorOptions = (): ServiceNowconnectorOptions => ({
urgency: '2',
severity: '1',
impact: '3',
-};
+});
-export const ibmResilientConnectorOptions: IbmResilientConnectorOptions = {
+export const getIbmResilientConnectorOptions = (): IbmResilientConnectorOptions => ({
title: 'Resilient',
severity: 'Medium',
incidentTypes: ['Communication error (fax; email)', 'Denial of Service'],
-};
+});
export const TIMELINE_CASE_ID = '68248e00-f689-11ea-9ab2-59238b522856';
-export const connectorIds = {
+
+export const getConnectorIds = () => ({
jira: '000e5f86-08b0-4882-adfd-6df981d45c1b',
sn: '93a69ba3-3c31-4b4c-bf86-cc79a090f437',
resilient: 'a6a8dd7f-7e88-48fe-9b9f-70b668da8cbc',
-};
+});
-export const mockConnectorsResponse = [
+export const getMockConnectorsResponse = () => [
{
- id: connectorIds.jira,
+ id: getConnectorIds().jira,
actionTypeId: '.jira',
name: 'Jira',
config: {
@@ -96,7 +97,7 @@ export const mockConnectorsResponse = [
referencedByCount: 0,
},
{
- id: connectorIds.resilient,
+ id: getConnectorIds().resilient,
actionTypeId: '.resilient',
name: 'Resilient',
config: {
@@ -107,7 +108,7 @@ export const mockConnectorsResponse = [
referencedByCount: 0,
},
{
- id: connectorIds.sn,
+ id: getConnectorIds().sn,
actionTypeId: '.servicenow',
name: 'ServiceNow',
config: {
@@ -117,7 +118,8 @@ export const mockConnectorsResponse = [
referencedByCount: 0,
},
];
-export const executeResponses = {
+
+export const getExecuteResponses = () => ({
servicenow: {
choices: {
status: 'ok',
@@ -208,7 +210,7 @@ export const executeResponses = {
{ id: '10006', name: 'Task' },
{ id: '10007', name: 'Sub-task' },
],
- actionId: connectorIds.jira,
+ actionId: getConnectorIds().jira,
},
fieldsByIssueType: {
status: 'ok',
@@ -299,7 +301,7 @@ export const executeResponses = {
timetracking: { allowedValues: [], defaultValue: {} },
labels: { allowedValues: [], defaultValue: {} },
},
- actionId: connectorIds.jira,
+ actionId: getConnectorIds().jira,
},
},
resilient: {
@@ -309,7 +311,7 @@ export const executeResponses = {
{ id: 17, name: 'Communication error (fax; email)' },
{ id: 21, name: 'Denial of Service' },
],
- actionId: connectorIds.resilient,
+ actionId: getConnectorIds().resilient,
},
severity: {
status: 'ok',
@@ -318,7 +320,7 @@ export const executeResponses = {
{ id: 5, name: 'Medium' },
{ id: 6, name: 'High' },
],
- actionId: connectorIds.resilient,
+ actionId: getConnectorIds().resilient,
},
},
-};
+});
diff --git a/x-pack/plugins/security_solution/cypress/objects/connector.ts b/x-pack/plugins/security_solution/cypress/objects/connector.ts
index 2a0f1cc43eff0e..a5244583bf4943 100644
--- a/x-pack/plugins/security_solution/cypress/objects/connector.ts
+++ b/x-pack/plugins/security_solution/cypress/objects/connector.ts
@@ -14,11 +14,11 @@ export interface EmailConnector {
password: string;
}
-export const emailConnector: EmailConnector = {
+export const getEmailConnector = (): EmailConnector => ({
name: 'Test connector',
from: 'test@example.com',
host: 'example.com',
port: '80',
user: 'username',
password: 'password',
-};
+});
diff --git a/x-pack/plugins/security_solution/cypress/objects/exception.ts b/x-pack/plugins/security_solution/cypress/objects/exception.ts
index 73457f10ccec6f..6a934e1ec46517 100644
--- a/x-pack/plugins/security_solution/cypress/objects/exception.ts
+++ b/x-pack/plugins/security_solution/cypress/objects/exception.ts
@@ -20,22 +20,22 @@ export interface ExceptionList {
type: 'detection' | 'endpoint';
}
-export const exceptionList: ExceptionList = {
+export const getExceptionList = (): ExceptionList => ({
description: 'Test exception list description',
list_id: 'test_exception_list',
name: 'Test exception list',
namespace_type: 'single',
tags: ['test tag'],
type: 'detection',
-};
+});
-export const exception: Exception = {
+export const getException = (): Exception => ({
field: 'host.name',
operator: 'is',
values: ['suricata-iowa'],
-};
+});
-export const expectedExportedExceptionList = (exceptionListResponse: Cypress.Response) => {
+export const expectedExportedExceptionList = (exceptionListResponse: Cypress.Response): string => {
const jsonrule = exceptionListResponse.body;
return `{"_version":"${jsonrule._version}","created_at":"${jsonrule.created_at}","created_by":"elastic","description":"${jsonrule.description}","id":"${jsonrule.id}","immutable":false,"list_id":"test_exception_list","name":"Test exception list","namespace_type":"single","os_types":[],"tags":[],"tie_breaker_id":"${jsonrule.tie_breaker_id}","type":"detection","updated_at":"${jsonrule.updated_at}","updated_by":"elastic","version":1}\n"\n""\n{"exception_list_items_details":"{"exported_count":0}\n"}`;
diff --git a/x-pack/plugins/security_solution/cypress/objects/filter.ts b/x-pack/plugins/security_solution/cypress/objects/filter.ts
index b00954de174222..5a69100a4b38a1 100644
--- a/x-pack/plugins/security_solution/cypress/objects/filter.ts
+++ b/x-pack/plugins/security_solution/cypress/objects/filter.ts
@@ -10,7 +10,7 @@ export interface SearchBarFilter {
value: string;
}
-export const hostIpFilter: SearchBarFilter = {
+export const getHostIpFilter = (): SearchBarFilter => ({
key: 'host.ip',
value: '1.1.1.1',
-};
+});
diff --git a/x-pack/plugins/security_solution/cypress/objects/rule.ts b/x-pack/plugins/security_solution/cypress/objects/rule.ts
index 3383ef4996eadb..7589c8fab3dae5 100644
--- a/x-pack/plugins/security_solution/cypress/objects/rule.ts
+++ b/x-pack/plugins/security_solution/cypress/objects/rule.ts
@@ -7,8 +7,8 @@
/* eslint-disable @kbn/eslint/no-restricted-paths */
import { rawRules } from '../../server/lib/detection_engine/rules/prepackaged_rules/index';
-import { mockThreatData } from '../../public/detections/mitre/mitre_tactics_techniques';
-import { timeline, CompleteTimeline, indicatorMatchTimelineTemplate } from './timeline';
+import { getMockThreatData } from '../../public/detections/mitre/mitre_tactics_techniques';
+import { getTimeline, CompleteTimeline, getIndicatorMatchTimelineTemplate } from './timeline';
export const totalNumberOfPrebuiltRules = rawRules.length;
@@ -96,8 +96,9 @@ export interface MachineLearningRule {
lookBack: Interval;
}
-export const indexPatterns = [
+export const getIndexPatterns = (): string[] => [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -106,67 +107,69 @@ export const indexPatterns = [
'winlogbeat-*',
];
-const { tactic, technique, subtechnique } = mockThreatData;
-
-const mitre1: Mitre = {
- tactic: `${tactic.name} (${tactic.id})`,
+const getMitre1 = (): Mitre => ({
+ tactic: `${getMockThreatData().tactic.name} (${getMockThreatData().tactic.id})`,
techniques: [
{
- name: `${technique.name} (${technique.id})`,
- subtechniques: [`${subtechnique.name} (${subtechnique.id})`],
+ name: `${getMockThreatData().technique.name} (${getMockThreatData().technique.id})`,
+ subtechniques: [
+ `${getMockThreatData().subtechnique.name} (${getMockThreatData().subtechnique.id})`,
+ ],
},
{
- name: `${technique.name} (${technique.id})`,
+ name: `${getMockThreatData().technique.name} (${getMockThreatData().technique.id})`,
subtechniques: [],
},
],
-};
+});
-const mitre2: Mitre = {
- tactic: `${tactic.name} (${tactic.id})`,
+const getMitre2 = (): Mitre => ({
+ tactic: `${getMockThreatData().tactic.name} (${getMockThreatData().tactic.id})`,
techniques: [
{
- name: `${technique.name} (${technique.id})`,
- subtechniques: [`${subtechnique.name} (${subtechnique.id})`],
+ name: `${getMockThreatData().technique.name} (${getMockThreatData().technique.id})`,
+ subtechniques: [
+ `${getMockThreatData().subtechnique.name} (${getMockThreatData().subtechnique.id})`,
+ ],
},
],
-};
+});
-const severityOverride1: SeverityOverride = {
+const getSeverityOverride1 = (): SeverityOverride => ({
sourceField: 'host.name',
sourceValue: 'host',
-};
+});
-const severityOverride2: SeverityOverride = {
+const getSeverityOverride2 = (): SeverityOverride => ({
sourceField: '@timestamp',
sourceValue: '10/02/2020',
-};
+});
-const severityOverride3: SeverityOverride = {
+const getSeverityOverride3 = (): SeverityOverride => ({
sourceField: 'host.geo.name',
sourceValue: 'atack',
-};
+});
-const severityOverride4: SeverityOverride = {
+const getSeverityOverride4 = (): SeverityOverride => ({
sourceField: 'agent.type',
sourceValue: 'auditbeat',
-};
+});
-const runsEvery: Interval = {
+const getRunsEvery = (): Interval => ({
interval: '1',
timeType: 'Seconds',
type: 's',
-};
+});
-const lookBack: Interval = {
+const getLookBack = (): Interval => ({
interval: '17520',
timeType: 'Hours',
type: 'h',
-};
+});
-export const newRule: CustomRule = {
+export const getNewRule = (): CustomRule => ({
customQuery: 'host.name: *',
- index: indexPatterns,
+ index: getIndexPatterns(),
name: 'New Rule Test',
description: 'The new rule description.',
severity: 'High',
@@ -174,15 +177,15 @@ export const newRule: CustomRule = {
tags: ['test', 'newRule'],
referenceUrls: ['http://example.com/', 'https://example.com/'],
falsePositivesExamples: ['False1', 'False2'],
- mitre: [mitre1, mitre2],
+ mitre: [getMitre1(), getMitre2()],
note: '# test markdown',
- runsEvery,
- lookBack,
- timeline,
+ runsEvery: getRunsEvery(),
+ lookBack: getLookBack(),
+ timeline: getTimeline(),
maxSignals: 100,
-};
+});
-export const unmappedRule: CustomRule = {
+export const getUnmappedRule = (): CustomRule => ({
customQuery: '*:*',
index: ['unmapped*'],
name: 'Rule with unmapped fields',
@@ -192,15 +195,15 @@ export const unmappedRule: CustomRule = {
tags: ['test', 'newRule'],
referenceUrls: ['http://example.com/', 'https://example.com/'],
falsePositivesExamples: ['False1', 'False2'],
- mitre: [mitre1, mitre2],
+ mitre: [getMitre1(), getMitre2()],
note: '# test markdown',
- runsEvery,
- lookBack,
- timeline,
+ runsEvery: getRunsEvery(),
+ lookBack: getLookBack(),
+ timeline: getTimeline(),
maxSignals: 100,
-};
+});
-export const unmappedCCSRule: CustomRule = {
+export const getUnmappedCCSRule = (): CustomRule => ({
customQuery: '*:*',
index: [`${ccsRemoteName}:unmapped*`],
name: 'Rule with unmapped fields',
@@ -210,15 +213,15 @@ export const unmappedCCSRule: CustomRule = {
tags: ['test', 'newRule'],
referenceUrls: ['http://example.com/', 'https://example.com/'],
falsePositivesExamples: ['False1', 'False2'],
- mitre: [mitre1, mitre2],
+ mitre: [getMitre1(), getMitre2()],
note: '# test markdown',
- runsEvery,
- lookBack,
- timeline,
+ runsEvery: getRunsEvery(),
+ lookBack: getLookBack(),
+ timeline: getTimeline(),
maxSignals: 100,
-};
+});
-export const existingRule: CustomRule = {
+export const getExistingRule = (): CustomRule => ({
customQuery: 'host.name: *',
name: 'Rule 1',
description: 'Description for Rule 1',
@@ -231,17 +234,17 @@ export const existingRule: CustomRule = {
falsePositivesExamples: [],
mitre: [],
note: 'This is my note',
- runsEvery,
- lookBack,
- timeline,
+ runsEvery: getRunsEvery(),
+ lookBack: getLookBack(),
+ timeline: getTimeline(),
// Please do not change, or if you do, needs
// to be any number other than default value
maxSignals: 500,
-};
+});
-export const newOverrideRule: OverrideRule = {
+export const getNewOverrideRule = (): OverrideRule => ({
customQuery: 'host.name: *',
- index: indexPatterns,
+ index: getIndexPatterns(),
name: 'Override Rule',
description: 'The new rule description.',
severity: 'High',
@@ -249,21 +252,26 @@ export const newOverrideRule: OverrideRule = {
tags: ['test', 'newRule'],
referenceUrls: ['http://example.com/', 'https://example.com/'],
falsePositivesExamples: ['False1', 'False2'],
- mitre: [mitre1, mitre2],
+ mitre: [getMitre1(), getMitre2()],
note: '# test markdown',
- severityOverride: [severityOverride1, severityOverride2, severityOverride3, severityOverride4],
+ severityOverride: [
+ getSeverityOverride1(),
+ getSeverityOverride2(),
+ getSeverityOverride3(),
+ getSeverityOverride4(),
+ ],
riskOverride: 'destination.port',
nameOverride: 'agent.type',
timestampOverride: '@timestamp',
- runsEvery,
- lookBack,
- timeline,
+ runsEvery: getRunsEvery(),
+ lookBack: getLookBack(),
+ timeline: getTimeline(),
maxSignals: 100,
-};
+});
-export const newThresholdRule: ThresholdRule = {
+export const getNewThresholdRule = (): ThresholdRule => ({
customQuery: 'host.name: *',
- index: indexPatterns,
+ index: getIndexPatterns(),
name: 'Threshold Rule',
description: 'The new rule description.',
severity: 'High',
@@ -271,17 +279,17 @@ export const newThresholdRule: ThresholdRule = {
tags: ['test', 'newRule'],
referenceUrls: ['http://example.com/', 'https://example.com/'],
falsePositivesExamples: ['False1', 'False2'],
- mitre: [mitre1, mitre2],
+ mitre: [getMitre1(), getMitre2()],
note: '# test markdown',
thresholdField: 'host.name',
threshold: '10',
- runsEvery,
- lookBack,
- timeline,
+ runsEvery: getRunsEvery(),
+ lookBack: getLookBack(),
+ timeline: getTimeline(),
maxSignals: 100,
-};
+});
-export const machineLearningRule: MachineLearningRule = {
+export const getMachineLearningRule = (): MachineLearningRule => ({
machineLearningJobs: ['linux_anomalous_network_service', 'linux_anomalous_network_activity_ecs'],
anomalyScoreThreshold: '20',
name: 'New ML Rule Test',
@@ -291,52 +299,52 @@ export const machineLearningRule: MachineLearningRule = {
tags: ['ML'],
referenceUrls: ['https://elastic.co/'],
falsePositivesExamples: ['False1'],
- mitre: [mitre1],
+ mitre: [getMitre1()],
note: '# test markdown',
- runsEvery,
- lookBack,
-};
+ runsEvery: getRunsEvery(),
+ lookBack: getLookBack(),
+});
-export const eqlRule: CustomRule = {
+export const getEqlRule = (): CustomRule => ({
customQuery: 'any where process.name == "which"',
name: 'New EQL Rule',
- index: indexPatterns,
+ index: getIndexPatterns(),
description: 'New EQL rule description.',
severity: 'High',
riskScore: '17',
tags: ['test', 'newRule'],
referenceUrls: ['http://example.com/', 'https://example.com/'],
falsePositivesExamples: ['False1', 'False2'],
- mitre: [mitre1, mitre2],
+ mitre: [getMitre1(), getMitre2()],
note: '# test markdown',
- runsEvery,
- lookBack,
- timeline,
+ runsEvery: getRunsEvery(),
+ lookBack: getLookBack(),
+ timeline: getTimeline(),
maxSignals: 100,
-};
+});
-export const eqlSequenceRule: CustomRule = {
+export const getEqlSequenceRule = (): CustomRule => ({
customQuery:
'sequence with maxspan=30s\
[any where process.name == "which"]\
[any where process.name == "xargs"]',
name: 'New EQL Sequence Rule',
- index: indexPatterns,
+ index: getIndexPatterns(),
description: 'New EQL rule description.',
severity: 'High',
riskScore: '17',
tags: ['test', 'newRule'],
referenceUrls: ['http://example.com/', 'https://example.com/'],
falsePositivesExamples: ['False1', 'False2'],
- mitre: [mitre1, mitre2],
+ mitre: [getMitre1(), getMitre2()],
note: '# test markdown',
- runsEvery,
- lookBack,
- timeline,
+ runsEvery: getRunsEvery(),
+ lookBack: getLookBack(),
+ timeline: getTimeline(),
maxSignals: 100,
-};
+});
-export const newThreatIndicatorRule: ThreatIndicatorRule = {
+export const getNewThreatIndicatorRule = (): ThreatIndicatorRule => ({
name: 'Threat Indicator Rule Test',
description: 'The threat indicator rule description.',
index: ['suspicious-*'],
@@ -345,31 +353,31 @@ export const newThreatIndicatorRule: ThreatIndicatorRule = {
tags: ['test', 'threat'],
referenceUrls: ['http://example.com/', 'https://example.com/'],
falsePositivesExamples: ['False1', 'False2'],
- mitre: [mitre1, mitre2],
+ mitre: [getMitre1(), getMitre2()],
note: '# test markdown',
- runsEvery,
- lookBack,
+ runsEvery: getRunsEvery(),
+ lookBack: getLookBack(),
indicatorIndexPattern: ['filebeat-*'],
indicatorMappingField: 'myhash.mysha256',
indicatorIndexField: 'threatintel.indicator.file.hash.sha256',
type: 'file',
atomic: 'a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3',
- timeline: indicatorMatchTimelineTemplate,
+ timeline: getIndicatorMatchTimelineTemplate(),
maxSignals: 100,
-};
+});
-export const duplicatedRuleName = `${newThreatIndicatorRule.name} [Duplicate]`;
+export const duplicatedRuleName = `${getNewThreatIndicatorRule().name} [Duplicate]`;
-export const severitiesOverride = ['Low', 'Medium', 'High', 'Critical'];
+export const getSeveritiesOverride = (): string[] => ['Low', 'Medium', 'High', 'Critical'];
-export const editedRule = {
- ...existingRule,
+export const getEditedRule = (): CustomRule => ({
+ ...getExistingRule(),
severity: 'Medium',
description: 'Edited Rule description',
- tags: [...existingRule.tags, 'edited'],
-};
+ tags: [...getExistingRule().tags, 'edited'],
+});
-export const expectedExportedRule = (ruleResponse: Cypress.Response) => {
+export const expectedExportedRule = (ruleResponse: Cypress.Response): string => {
const jsonrule = ruleResponse.body;
return `{"id":"${jsonrule.id}","updated_at":"${jsonrule.updated_at}","updated_by":"elastic","created_at":"${jsonrule.created_at}","created_by":"elastic","name":"${jsonrule.name}","tags":[],"interval":"100m","enabled":false,"description":"${jsonrule.description}","risk_score":${jsonrule.risk_score},"severity":"${jsonrule.severity}","output_index":".siem-signals-default","author":[],"false_positives":[],"from":"now-17520h","rule_id":"rule_testing","max_signals":100,"risk_score_mapping":[],"severity_mapping":[],"threat":[],"to":"now","references":[],"version":1,"exceptions_list":[],"immutable":false,"type":"query","language":"kuery","index":["exceptions-*"],"query":"${jsonrule.query}","throttle":"no_actions","actions":[]}\n{"exported_count":1,"missing_rules":[],"missing_rules_count":0}\n`;
diff --git a/x-pack/plugins/security_solution/cypress/objects/timeline.ts b/x-pack/plugins/security_solution/cypress/objects/timeline.ts
index 1b66b50605508a..c13c1b01ef0ed6 100644
--- a/x-pack/plugins/security_solution/cypress/objects/timeline.ts
+++ b/x-pack/plugins/security_solution/cypress/objects/timeline.ts
@@ -24,51 +24,51 @@ export interface TimelineFilter {
value?: string;
}
-export const filter: TimelineFilter = {
+export const getFilter = (): TimelineFilter => ({
field: 'host.name',
operator: 'exists',
value: 'exists',
-};
+});
-export const timeline: CompleteTimeline = {
+export const getTimeline = (): CompleteTimeline => ({
title: 'Security Timeline',
description: 'This is the best timeline',
query: 'host.name: *',
notes: 'Yes, the best timeline',
- filter,
-};
+ filter: getFilter(),
+});
-export const indicatorMatchTimelineTemplate: CompleteTimeline = {
- ...timeline,
+export const getIndicatorMatchTimelineTemplate = (): CompleteTimeline => ({
+ ...getTimeline(),
title: 'Generic Threat Match Timeline',
templateTimelineId: '495ad7a7-316e-4544-8a0f-9c098daee76e',
-};
+});
/**
* Timeline query that finds no valid data to cut down on test failures
* or other issues for when we want to test one specific thing and not also
* test the queries happening
*/
-export const timelineNonValidQuery: CompleteTimeline = {
- ...timeline,
+export const getTimelineNonValidQuery = (): CompleteTimeline => ({
+ ...getTimeline(),
query: 'query_to_intentionally_find_nothing: *',
-};
+});
-export const caseTimeline: Timeline = {
+export const caseTimeline = (): Timeline => ({
title: 'SIEM test',
description: 'description',
query: 'host.name: *',
id: '0162c130-78be-11ea-9718-118a926974a4',
-};
+});
-export const expectedExportedTimelineTemplate = (templateResponse: Cypress.Response) => {
+export const expectedExportedTimelineTemplate = (templateResponse: Cypress.Response): string => {
const timelineTemplateBody = templateResponse.body.data.persistTimeline.timeline;
return `{"savedObjectId":"${timelineTemplateBody.savedObjectId}","version":"${timelineTemplateBody.version}","columns":[{"id":"@timestamp"},{"id":"user.name"},{"id":"event.category"},{"id":"event.action"},{"id":"host.name"}],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"expression":"${timelineTemplateBody.kqlQuery.filterQuery.kuery.expression}","kind":"kuery"}}},"dateRange":{"start":"${timelineTemplateBody.dateRange.start}","end":"${timelineTemplateBody.dateRange.end}"},"description":"${timelineTemplateBody.description}","title":"${timelineTemplateBody.title}","templateTimelineVersion":1,"timelineType":"template","created":${timelineTemplateBody.created},"createdBy":"elastic","updated":${timelineTemplateBody.updated},"updatedBy":"elastic","sort":[],"eventNotes":[],"globalNotes":[],"pinnedEventIds":[]}
`;
};
-export const expectedExportedTimeline = (timelineResponse: Cypress.Response) => {
+export const expectedExportedTimeline = (timelineResponse: Cypress.Response): string => {
const timelineBody = timelineResponse.body.data.persistTimeline.timeline;
return `{"savedObjectId":"${timelineBody.savedObjectId}","version":"${timelineBody.version}","columns":[{"id":"@timestamp"},{"id":"user.name"},{"id":"event.category"},{"id":"event.action"},{"id":"host.name"}],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"expression":"${timelineBody.kqlQuery.filterQuery.kuery.expression}","kind":"kuery"}}},"dateRange":{"start":"${timelineBody.dateRange.start}","end":"${timelineBody.dateRange.end}"},"description":"${timelineBody.description}","title":"${timelineBody.title}","created":${timelineBody.created},"createdBy":"elastic","updated":${timelineBody.updated},"updatedBy":"elastic","timelineType":"default","sort":[],"eventNotes":[],"globalNotes":[],"pinnedEventIds":[]}\n`;
diff --git a/x-pack/plugins/security_solution/cypress/screens/edit_connector.ts b/x-pack/plugins/security_solution/cypress/screens/edit_connector.ts
index 5b353983e5a920..598485b167c9fc 100644
--- a/x-pack/plugins/security_solution/cypress/screens/edit_connector.ts
+++ b/x-pack/plugins/security_solution/cypress/screens/edit_connector.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { connectorIds } from '../objects/case';
+import { getConnectorIds } from '../objects/case';
export const CONNECTOR_RESILIENT = `[data-test-subj="connector-fields-resilient"]`;
@@ -17,14 +17,16 @@ export const SELECT_INCIDENT_TYPE = `[data-test-subj="incidentTypeComboBox"] inp
export const SELECT_ISSUE_TYPE = `[data-test-subj="issueTypeSelect"]`;
-export const SELECT_JIRA = `[data-test-subj="dropdown-connector-${connectorIds.jira}"]`;
+export const SELECT_JIRA = `[data-test-subj="dropdown-connector-${getConnectorIds().jira}"]`;
export const SELECT_PRIORITY = `[data-test-subj="prioritySelect"]`;
-export const SELECT_RESILIENT = `[data-test-subj="dropdown-connector-${connectorIds.resilient}"]`;
+export const SELECT_RESILIENT = `[data-test-subj="dropdown-connector-${
+ getConnectorIds().resilient
+}"]`;
export const SELECT_SEVERITY = `[data-test-subj="severitySelect"]`;
-export const SELECT_SN = `[data-test-subj="dropdown-connector-${connectorIds.sn}"]`;
+export const SELECT_SN = `[data-test-subj="dropdown-connector-${getConnectorIds().sn}"]`;
export const SELECT_URGENCY = `[data-test-subj="urgencySelect"]`;
diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts
index 1b420cd6d1520d..d8d91dc9ca6243 100644
--- a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts
+++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts
@@ -5,11 +5,11 @@
* 2.0.
*/
-import { emailConnector, EmailConnector } from '../objects/connector';
+import { getEmailConnector, EmailConnector } from '../objects/connector';
import {
CustomRule,
MachineLearningRule,
- machineLearningRule,
+ getMachineLearningRule,
OverrideRule,
ThreatIndicatorRule,
ThresholdRule,
@@ -397,7 +397,7 @@ export const fillIndexAndIndicatorIndexPattern = (
getIndicatorIndicatorIndex().type(`${indicatorIndex}{enter}`);
};
-export const fillEmailConnectorForm = (connector: EmailConnector = emailConnector) => {
+export const fillEmailConnectorForm = (connector: EmailConnector = getEmailConnector()) => {
cy.get(CONNECTOR_NAME_INPUT).type(connector.name);
cy.get(EMAIL_CONNECTOR_FROM_INPUT).type(connector.from);
cy.get(EMAIL_CONNECTOR_HOST_INPUT).type(connector.host);
@@ -478,9 +478,12 @@ export const fillDefineMachineLearningRuleAndContinue = (rule: MachineLearningRu
cy.get(MACHINE_LEARNING_DROPDOWN_INPUT).type(`${machineLearningJob}{enter}`);
cy.get(MACHINE_LEARNING_DROPDOWN_INPUT).type('{esc}');
});
- cy.get(ANOMALY_THRESHOLD_INPUT).type(`{selectall}${machineLearningRule.anomalyScoreThreshold}`, {
- force: true,
- });
+ cy.get(ANOMALY_THRESHOLD_INPUT).type(
+ `{selectall}${getMachineLearningRule().anomalyScoreThreshold}`,
+ {
+ force: true,
+ }
+ );
getDefineContinueButton().should('exist').click({ force: true });
cy.get(MACHINE_LEARNING_DROPDOWN_INPUT).should('not.exist');
diff --git a/x-pack/plugins/security_solution/public/cases/components/__mock__/form.ts b/x-pack/plugins/security_solution/public/cases/components/__mock__/form.ts
index 9ec356f70f9a41..3ba7aa616f1c11 100644
--- a/x-pack/plugins/security_solution/public/cases/components/__mock__/form.ts
+++ b/x-pack/plugins/security_solution/public/cases/components/__mock__/form.ts
@@ -25,6 +25,7 @@ export const mockFormHook = {
setFieldErrors: jest.fn(),
getFields: jest.fn(),
getFormData: jest.fn(),
+ getFieldDefaultValue: jest.fn(),
/* Returns a list of all errors in the form */
getErrors: jest.fn(),
reset: jest.fn(),
@@ -35,7 +36,6 @@ export const mockFormHook = {
__validateFields: jest.fn(),
__updateFormDataAt: jest.fn(),
__readFieldConfigFromSchema: jest.fn(),
- __getFieldDefaultValue: jest.fn(),
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const getFormMock = (sampleData: any) => ({
diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/field.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete/field.tsx
index d4185fe639695e..a175a9b847c715 100644
--- a/x-pack/plugins/security_solution/public/common/components/autocomplete/field.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/autocomplete/field.tsx
@@ -25,6 +25,13 @@ interface OperatorProps {
onChange: (a: IFieldType[]) => void;
}
+/**
+ * There is a copy within:
+ * x-pack/plugins/lists/public/exceptions/components/autocomplete/field.tsx
+ *
+ * TODO: This should be in its own packaged and not copied, https://github.com/elastic/kibana/issues/105378
+ * NOTE: This has deviated from the copy and will have to be reconciled.
+ */
export const FieldComponent: React.FC = ({
placeholder,
selectedField,
diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_exists.test.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_exists.test.tsx
deleted file mode 100644
index b6300581f12dd8..00000000000000
--- a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_exists.test.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { mount } from 'enzyme';
-
-import { AutocompleteFieldExistsComponent } from './field_value_exists';
-
-describe('AutocompleteFieldExistsComponent', () => {
- test('it renders field disabled', () => {
- const wrapper = mount( );
-
- expect(
- wrapper
- .find(`[data-test-subj="valuesAutocompleteComboBox existsComboxBox"] input`)
- .prop('disabled')
- ).toBeTruthy();
- });
-});
diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_exists.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_exists.tsx
deleted file mode 100644
index 715ba52701177b..00000000000000
--- a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_exists.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { EuiFormRow, EuiComboBox } from '@elastic/eui';
-
-interface AutocompleteFieldExistsProps {
- placeholder: string;
- rowLabel?: string;
-}
-
-export const AutocompleteFieldExistsComponent: React.FC = ({
- placeholder,
- rowLabel,
-}): JSX.Element => (
-
-
-
-);
-
-AutocompleteFieldExistsComponent.displayName = 'AutocompleteFieldExists';
diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.test.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.test.tsx
deleted file mode 100644
index 164b8e8d2a6d68..00000000000000
--- a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.test.tsx
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { mount } from 'enzyme';
-import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
-import { waitFor } from '@testing-library/react';
-
-import { getField } from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks';
-import type { ListSchema } from '@kbn/securitysolution-io-ts-list-types';
-import { getFoundListSchemaMock } from '../../../../../lists/common/schemas/response/found_list_schema.mock';
-import { getListResponseMock } from '../../../../../lists/common/schemas/response/list_schema.mock';
-import { DATE_NOW, VERSION, IMMUTABLE } from '../../../../../lists/common/constants.mock';
-
-import { AutocompleteFieldListsComponent } from './field_value_lists';
-
-jest.mock('../../../common/lib/kibana');
-const mockStart = jest.fn();
-const mockKeywordList: ListSchema = {
- ...getListResponseMock(),
- id: 'keyword_list',
- type: 'keyword',
- name: 'keyword list',
-};
-const mockResult = { ...getFoundListSchemaMock() };
-mockResult.data = [...mockResult.data, mockKeywordList];
-jest.mock('@kbn/securitysolution-list-hooks', () => {
- const originalModule = jest.requireActual('@kbn/securitysolution-list-hooks');
-
- return {
- ...originalModule,
- useFindLists: () => ({
- loading: false,
- start: mockStart.mockReturnValue(mockResult),
- result: mockResult,
- error: undefined,
- }),
- };
-});
-
-describe('AutocompleteFieldListsComponent', () => {
- test('it renders disabled if "isDisabled" is true', async () => {
- const wrapper = mount(
-
- );
-
- expect(
- wrapper
- .find(`[data-test-subj="valuesAutocompleteComboBox listsComboxBox"] input`)
- .prop('disabled')
- ).toBeTruthy();
- });
-
- test('it renders loading if "isLoading" is true', async () => {
- const wrapper = mount(
-
- );
-
- wrapper
- .find(`[data-test-subj="valuesAutocompleteComboBox listsComboxBox"] button`)
- .at(0)
- .simulate('click');
- expect(
- wrapper
- .find(
- `EuiComboBoxOptionsList[data-test-subj="valuesAutocompleteComboBox listsComboxBox-optionsList"]`
- )
- .prop('isLoading')
- ).toBeTruthy();
- });
-
- test('it allows user to clear values if "isClearable" is true', async () => {
- const wrapper = mount(
-
- );
- expect(
- wrapper
- .find('EuiComboBox[data-test-subj="valuesAutocompleteComboBox listsComboxBox"]')
- .prop('options')
- ).toEqual([{ label: 'some name' }]);
- });
-
- test('it correctly displays lists that match the selected "keyword" field esType', () => {
- const wrapper = mount(
-
- );
-
- wrapper.find('[data-test-subj="comboBoxToggleListButton"] button').simulate('click');
-
- expect(
- wrapper
- .find('EuiComboBox[data-test-subj="valuesAutocompleteComboBox listsComboxBox"]')
- .prop('options')
- ).toEqual([{ label: 'keyword list' }]);
- });
-
- test('it correctly displays lists that match the selected "ip" field esType', () => {
- const wrapper = mount(
-
- );
-
- wrapper.find('[data-test-subj="comboBoxToggleListButton"] button').simulate('click');
-
- expect(
- wrapper
- .find('EuiComboBox[data-test-subj="valuesAutocompleteComboBox listsComboxBox"]')
- .prop('options')
- ).toEqual([{ label: 'some name' }]);
- });
-
- test('it correctly displays selected list', async () => {
- const wrapper = mount(
-
- );
-
- expect(
- wrapper
- .find(`[data-test-subj="valuesAutocompleteComboBox listsComboxBox"] EuiComboBoxPill`)
- .at(0)
- .text()
- ).toEqual('some name');
- });
-
- test('it invokes "onChange" when option selected', async () => {
- const mockOnChange = jest.fn();
- const wrapper = mount(
-
- );
-
- ((wrapper.find(EuiComboBox).props() as unknown) as {
- onChange: (a: EuiComboBoxOptionOption[]) => void;
- }).onChange([{ label: 'some name' }]);
-
- await waitFor(() => {
- expect(mockOnChange).toHaveBeenCalledWith({
- created_at: DATE_NOW,
- created_by: 'some user',
- description: 'some description',
- id: 'some-list-id',
- meta: {},
- name: 'some name',
- tie_breaker_id: '6a76b69d-80df-4ab2-8c3e-85f466b06a0e',
- type: 'ip',
- updated_at: DATE_NOW,
- updated_by: 'some user',
- _version: undefined,
- version: VERSION,
- deserializer: undefined,
- serializer: undefined,
- immutable: IMMUTABLE,
- });
- });
- });
-});
diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.tsx
deleted file mode 100644
index e8a3c2e70c75b2..00000000000000
--- a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.tsx
+++ /dev/null
@@ -1,121 +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, { useState, useEffect, useCallback, useMemo } from 'react';
-import { EuiFormRow, EuiComboBoxOptionOption, EuiComboBox } from '@elastic/eui';
-
-import type { ListSchema } from '@kbn/securitysolution-io-ts-list-types';
-import { useFindLists } from '@kbn/securitysolution-list-hooks';
-import { IFieldType } from '../../../../../../../src/plugins/data/common';
-import { useKibana } from '../../../common/lib/kibana';
-import { filterFieldToList, getGenericComboBoxProps } from './helpers';
-import * as i18n from './translations';
-
-interface AutocompleteFieldListsProps {
- placeholder: string;
- selectedField: IFieldType | undefined;
- selectedValue: string | undefined;
- isLoading: boolean;
- isDisabled: boolean;
- isClearable: boolean;
- isRequired?: boolean;
- rowLabel?: string;
- onChange: (arg: ListSchema) => void;
-}
-
-export const AutocompleteFieldListsComponent: React.FC = ({
- placeholder,
- rowLabel,
- selectedField,
- selectedValue,
- isLoading = false,
- isDisabled = false,
- isClearable = false,
- isRequired = false,
- onChange,
-}): JSX.Element => {
- const [error, setError] = useState(undefined);
- const { http } = useKibana().services;
- const [lists, setLists] = useState([]);
- const { loading, result, start } = useFindLists();
- const getLabel = useCallback(({ name }) => name, []);
-
- const optionsMemo = useMemo(() => filterFieldToList(lists, selectedField), [
- lists,
- selectedField,
- ]);
- const selectedOptionsMemo = useMemo(() => {
- if (selectedValue != null) {
- const list = lists.filter(({ id }) => id === selectedValue);
- return list ?? [];
- } else {
- return [];
- }
- }, [selectedValue, lists]);
- const { comboOptions, labels, selectedComboOptions } = useMemo(
- () =>
- getGenericComboBoxProps({
- options: optionsMemo,
- selectedOptions: selectedOptionsMemo,
- getLabel,
- }),
- [optionsMemo, selectedOptionsMemo, getLabel]
- );
-
- const handleValuesChange = useCallback(
- (newOptions: EuiComboBoxOptionOption[]) => {
- const [newValue] = newOptions.map(({ label }) => optionsMemo[labels.indexOf(label)]);
- onChange(newValue ?? '');
- },
- [labels, optionsMemo, onChange]
- );
-
- const setIsTouchedValue = useCallback((): void => {
- setError(selectedValue == null ? i18n.FIELD_REQUIRED_ERR : undefined);
- }, [selectedValue]);
-
- useEffect(() => {
- if (result != null) {
- setLists(result.data);
- }
- }, [result]);
-
- useEffect(() => {
- if (selectedField != null) {
- start({
- http,
- pageIndex: 1,
- pageSize: 500,
- });
- }
- }, [selectedField, start, http]);
-
- const isLoadingState = useMemo((): boolean => isLoading || loading, [isLoading, loading]);
-
- return (
-
-
-
- );
-};
-
-AutocompleteFieldListsComponent.displayName = 'AutocompleteFieldList';
diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match.tsx
index 9cb219e7a8d456..21d1d9b4b31aa1 100644
--- a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match.tsx
@@ -38,6 +38,11 @@ interface AutocompleteFieldMatchProps {
onError?: (arg: boolean) => void;
}
+/**
+ * There is a copy of this within:
+ * x-pack/plugins/lists/public/exceptions/components/autocomplete/field_value_match.tsx
+ * TODO: This should be in its own packaged and not copied, https://github.com/elastic/kibana/issues/105378
+ */
export const AutocompleteFieldMatchComponent: React.FC = ({
placeholder,
rowLabel,
diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match_any.test.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match_any.test.tsx
deleted file mode 100644
index 6b479c5ab8c4c9..00000000000000
--- a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match_any.test.tsx
+++ /dev/null
@@ -1,238 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { mount, ReactWrapper } from 'enzyme';
-import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
-import { act } from '@testing-library/react';
-
-import {
- fields,
- getField,
-} from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks';
-import { AutocompleteFieldMatchAnyComponent } from './field_value_match_any';
-import { useFieldValueAutocomplete } from './hooks/use_field_value_autocomplete';
-
-jest.mock('./hooks/use_field_value_autocomplete');
-
-describe('AutocompleteFieldMatchAnyComponent', () => {
- let wrapper: ReactWrapper;
- const getValueSuggestionsMock = jest
- .fn()
- .mockResolvedValue([false, true, ['value 3', 'value 4'], jest.fn()]);
-
- beforeEach(() => {
- (useFieldValueAutocomplete as jest.Mock).mockReturnValue([
- false,
- true,
- ['value 1', 'value 2'],
- getValueSuggestionsMock,
- ]);
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- wrapper.unmount();
- });
-
- test('it renders disabled if "isDisabled" is true', () => {
- wrapper = mount(
-
- );
-
- expect(
- wrapper.find(`[data-test-subj="valuesAutocompleteMatchAny"] input`).prop('disabled')
- ).toBeTruthy();
- });
-
- test('it renders loading if "isLoading" is true', () => {
- wrapper = mount(
-
- );
- wrapper.find(`[data-test-subj="valuesAutocompleteMatchAny"] button`).at(0).simulate('click');
- expect(
- wrapper
- .find(`EuiComboBoxOptionsList[data-test-subj="valuesAutocompleteMatchAny-optionsList"]`)
- .prop('isLoading')
- ).toBeTruthy();
- });
-
- test('it allows user to clear values if "isClearable" is true', () => {
- wrapper = mount(
-
- );
-
- expect(
- wrapper
- .find(`[data-test-subj="comboBoxInput"]`)
- .hasClass('euiComboBox__inputWrap-isClearable')
- ).toBeTruthy();
- });
-
- test('it correctly displays selected value', () => {
- wrapper = mount(
-
- );
-
- expect(
- wrapper.find(`[data-test-subj="valuesAutocompleteMatchAny"] EuiComboBoxPill`).at(0).text()
- ).toEqual('126.45.211.34');
- });
-
- test('it invokes "onChange" when new value created', async () => {
- const mockOnChange = jest.fn();
- wrapper = mount(
-
- );
-
- ((wrapper.find(EuiComboBox).props() as unknown) as {
- onCreateOption: (a: string) => void;
- }).onCreateOption('126.45.211.34');
-
- expect(mockOnChange).toHaveBeenCalledWith(['126.45.211.34']);
- });
-
- test('it invokes "onChange" when new value selected', async () => {
- const mockOnChange = jest.fn();
- wrapper = mount(
-
- );
-
- ((wrapper.find(EuiComboBox).props() as unknown) as {
- onChange: (a: EuiComboBoxOptionOption[]) => void;
- }).onChange([{ label: 'value 1' }]);
-
- expect(mockOnChange).toHaveBeenCalledWith(['value 1']);
- });
-
- test('it refreshes autocomplete with search query when new value searched', () => {
- wrapper = mount(
-
- );
- act(() => {
- ((wrapper.find(EuiComboBox).props() as unknown) as {
- onSearchChange: (a: string) => void;
- }).onSearchChange('value 1');
- });
-
- expect(useFieldValueAutocomplete).toHaveBeenCalledWith({
- selectedField: getField('machine.os.raw'),
- operatorType: 'match_any',
- query: 'value 1',
- fieldValue: [],
- indexPattern: {
- id: '1234',
- title: 'logstash-*',
- fields,
- },
- });
- });
-});
diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match_any.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match_any.tsx
deleted file mode 100644
index dbfdaf9749b6de..00000000000000
--- a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match_any.tsx
+++ /dev/null
@@ -1,221 +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, { useState, useCallback, useMemo } from 'react';
-import { EuiFormRow, EuiComboBoxOptionOption, EuiComboBox } from '@elastic/eui';
-import { uniq } from 'lodash';
-
-import { ListOperatorTypeEnum as OperatorTypeEnum } from '@kbn/securitysolution-io-ts-list-types';
-import { IFieldType, IIndexPattern } from '../../../../../../../src/plugins/data/common';
-import { useFieldValueAutocomplete } from './hooks/use_field_value_autocomplete';
-import { getGenericComboBoxProps, paramIsValid } from './helpers';
-import { GetGenericComboBoxPropsReturn } from './types';
-
-import * as i18n from './translations';
-
-interface AutocompleteFieldMatchAnyProps {
- placeholder: string;
- selectedField: IFieldType | undefined;
- selectedValue: string[];
- indexPattern: IIndexPattern | undefined;
- isLoading: boolean;
- isDisabled: boolean;
- isClearable: boolean;
- isRequired?: boolean;
- rowLabel?: string;
- onChange: (arg: string[]) => void;
- onError?: (arg: boolean) => void;
-}
-
-export const AutocompleteFieldMatchAnyComponent: React.FC = ({
- placeholder,
- rowLabel,
- selectedField,
- selectedValue,
- indexPattern,
- isLoading,
- isDisabled = false,
- isClearable = false,
- isRequired = false,
- onChange,
- onError,
-}): JSX.Element => {
- const [searchQuery, setSearchQuery] = useState('');
- const [touched, setIsTouched] = useState(false);
- const [error, setError] = useState(undefined);
- const [isLoadingSuggestions, isSuggestingValues, suggestions] = useFieldValueAutocomplete({
- selectedField,
- operatorType: OperatorTypeEnum.MATCH_ANY,
- fieldValue: selectedValue,
- query: searchQuery,
- indexPattern,
- });
- const getLabel = useCallback((option: string): string => option, []);
- const optionsMemo = useMemo(
- (): string[] => (selectedValue ? uniq([...selectedValue, ...suggestions]) : suggestions),
- [suggestions, selectedValue]
- );
- const { comboOptions, labels, selectedComboOptions } = useMemo(
- (): GetGenericComboBoxPropsReturn =>
- getGenericComboBoxProps({
- options: optionsMemo,
- selectedOptions: selectedValue,
- getLabel,
- }),
- [optionsMemo, selectedValue, getLabel]
- );
-
- const handleError = useCallback(
- (err: string | undefined): void => {
- setError((existingErr): string | undefined => {
- const oldErr = existingErr != null;
- const newErr = err != null;
- if (oldErr !== newErr && onError != null) {
- onError(newErr);
- }
-
- return err;
- });
- },
- [setError, onError]
- );
-
- const handleValuesChange = useCallback(
- (newOptions: EuiComboBoxOptionOption[]): void => {
- const newValues: string[] = newOptions.map(({ label }) => optionsMemo[labels.indexOf(label)]);
- handleError(undefined);
- onChange(newValues);
- },
- [handleError, labels, onChange, optionsMemo]
- );
-
- const handleSearchChange = useCallback(
- (searchVal: string) => {
- if (searchVal === '') {
- handleError(undefined);
- }
-
- if (searchVal !== '' && selectedField != null) {
- const err = paramIsValid(searchVal, selectedField, isRequired, touched);
- handleError(err);
-
- setSearchQuery(searchVal);
- }
- },
- [handleError, isRequired, selectedField, touched]
- );
-
- const handleCreateOption = useCallback(
- (option: string): boolean | void => {
- const err = paramIsValid(option, selectedField, isRequired, touched);
- handleError(err);
-
- if (err != null) {
- // Explicitly reject the user's input
- return false;
- } else {
- onChange([...(selectedValue || []), option]);
- }
- },
- [handleError, isRequired, onChange, selectedField, selectedValue, touched]
- );
-
- const setIsTouchedValue = useCallback((): void => {
- handleError(selectedComboOptions.length === 0 ? i18n.FIELD_REQUIRED_ERR : undefined);
- setIsTouched(true);
- }, [setIsTouched, handleError, selectedComboOptions]);
-
- const inputPlaceholder = useMemo(
- (): string => (isLoading || isLoadingSuggestions ? i18n.LOADING : placeholder),
- [isLoading, isLoadingSuggestions, placeholder]
- );
-
- const isLoadingState = useMemo((): boolean => isLoading || isLoadingSuggestions, [
- isLoading,
- isLoadingSuggestions,
- ]);
-
- const defaultInput = useMemo((): JSX.Element => {
- return (
-
-
-
- );
- }, [
- comboOptions,
- error,
- handleCreateOption,
- handleSearchChange,
- handleValuesChange,
- inputPlaceholder,
- isClearable,
- isDisabled,
- isLoadingState,
- rowLabel,
- selectedComboOptions,
- selectedField,
- setIsTouchedValue,
- ]);
-
- if (!isSuggestingValues && selectedField != null) {
- switch (selectedField.type) {
- case 'number':
- return (
-
-
-
- );
- default:
- return defaultInput;
- }
- }
-
- return defaultInput;
-};
-
-AutocompleteFieldMatchAnyComponent.displayName = 'AutocompleteFieldMatchAny';
diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.test.ts b/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.test.ts
index ae695bf7be9782..1618de245365dc 100644
--- a/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.test.ts
+++ b/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.test.ts
@@ -8,65 +8,13 @@
import moment from 'moment';
import '../../../common/mock/match_media';
import { getField } from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks';
-import { IFieldType } from '../../../../../../../src/plugins/data/common';
import * as i18n from './translations';
-import {
- EXCEPTION_OPERATORS,
- isOperator,
- isNotOperator,
- existsOperator,
- doesNotExistOperator,
-} from '@kbn/securitysolution-list-utils';
-import {
- getOperators,
- checkEmptyValue,
- paramIsValid,
- getGenericComboBoxProps,
- typeMatch,
- filterFieldToList,
-} from './helpers';
-import { getListResponseMock } from '../../../../../lists/common/schemas/response/list_schema.mock';
-import type { ListSchema } from '@kbn/securitysolution-io-ts-list-types';
+import { checkEmptyValue, paramIsValid, getGenericComboBoxProps } from './helpers';
describe('helpers', () => {
// @ts-ignore
moment.suppressDeprecationWarnings = true;
- describe('#getOperators', () => {
- test('it returns "isOperator" if passed in field is "undefined"', () => {
- const operator = getOperators(undefined);
-
- expect(operator).toEqual([isOperator]);
- });
-
- test('it returns expected operators when field type is "boolean"', () => {
- const operator = getOperators(getField('ssl'));
-
- expect(operator).toEqual([isOperator, isNotOperator, existsOperator, doesNotExistOperator]);
- });
-
- test('it returns "isOperator" when field type is "nested"', () => {
- const operator = getOperators({
- name: 'nestedField',
- type: 'nested',
- esTypes: ['text'],
- count: 0,
- scripted: false,
- searchable: true,
- aggregatable: false,
- readFromDocValues: false,
- subType: { nested: { path: 'nestedField' } },
- });
-
- expect(operator).toEqual([isOperator]);
- });
-
- test('it returns all operator types when field type is not null, boolean, or nested', () => {
- const operator = getOperators(getField('machine.os.raw'));
-
- expect(operator).toEqual(EXCEPTION_OPERATORS);
- });
- });
describe('#checkEmptyValue', () => {
test('returns no errors if no field has been selected', () => {
@@ -272,117 +220,4 @@ describe('helpers', () => {
});
});
});
-
- describe('#typeMatch', () => {
- test('ip -> ip is true', () => {
- expect(typeMatch('ip', 'ip')).toEqual(true);
- });
-
- test('keyword -> keyword is true', () => {
- expect(typeMatch('keyword', 'keyword')).toEqual(true);
- });
-
- test('text -> text is true', () => {
- expect(typeMatch('text', 'text')).toEqual(true);
- });
-
- test('ip_range -> ip is true', () => {
- expect(typeMatch('ip_range', 'ip')).toEqual(true);
- });
-
- test('date_range -> date is true', () => {
- expect(typeMatch('date_range', 'date')).toEqual(true);
- });
-
- test('double_range -> double is true', () => {
- expect(typeMatch('double_range', 'double')).toEqual(true);
- });
-
- test('float_range -> float is true', () => {
- expect(typeMatch('float_range', 'float')).toEqual(true);
- });
-
- test('integer_range -> integer is true', () => {
- expect(typeMatch('integer_range', 'integer')).toEqual(true);
- });
-
- test('long_range -> long is true', () => {
- expect(typeMatch('long_range', 'long')).toEqual(true);
- });
-
- test('ip -> date is false', () => {
- expect(typeMatch('ip', 'date')).toEqual(false);
- });
-
- test('long -> float is false', () => {
- expect(typeMatch('long', 'float')).toEqual(false);
- });
-
- test('integer -> long is false', () => {
- expect(typeMatch('integer', 'long')).toEqual(false);
- });
- });
-
- describe('#filterFieldToList', () => {
- test('it returns empty array if given a undefined for field', () => {
- const filter = filterFieldToList([], undefined);
- expect(filter).toEqual([]);
- });
-
- test('it returns empty array if filed does not contain esTypes', () => {
- const field: IFieldType = { name: 'some-name', type: 'some-type' };
- const filter = filterFieldToList([], field);
- expect(filter).toEqual([]);
- });
-
- test('it returns single filtered list of ip_range -> ip', () => {
- const field: IFieldType = { name: 'some-name', type: 'ip', esTypes: ['ip'] };
- const listItem: ListSchema = { ...getListResponseMock(), type: 'ip_range' };
- const filter = filterFieldToList([listItem], field);
- const expected: ListSchema[] = [listItem];
- expect(filter).toEqual(expected);
- });
-
- test('it returns single filtered list of ip -> ip', () => {
- const field: IFieldType = { name: 'some-name', type: 'ip', esTypes: ['ip'] };
- const listItem: ListSchema = { ...getListResponseMock(), type: 'ip' };
- const filter = filterFieldToList([listItem], field);
- const expected: ListSchema[] = [listItem];
- expect(filter).toEqual(expected);
- });
-
- test('it returns single filtered list of keyword -> keyword', () => {
- const field: IFieldType = { name: 'some-name', type: 'keyword', esTypes: ['keyword'] };
- const listItem: ListSchema = { ...getListResponseMock(), type: 'keyword' };
- const filter = filterFieldToList([listItem], field);
- const expected: ListSchema[] = [listItem];
- expect(filter).toEqual(expected);
- });
-
- test('it returns single filtered list of text -> text', () => {
- const field: IFieldType = { name: 'some-name', type: 'text', esTypes: ['text'] };
- const listItem: ListSchema = { ...getListResponseMock(), type: 'text' };
- const filter = filterFieldToList([listItem], field);
- const expected: ListSchema[] = [listItem];
- expect(filter).toEqual(expected);
- });
-
- test('it returns 2 filtered lists of ip_range -> ip', () => {
- const field: IFieldType = { name: 'some-name', type: 'ip', esTypes: ['ip'] };
- const listItem1: ListSchema = { ...getListResponseMock(), type: 'ip_range' };
- const listItem2: ListSchema = { ...getListResponseMock(), type: 'ip_range' };
- const filter = filterFieldToList([listItem1, listItem2], field);
- const expected: ListSchema[] = [listItem1, listItem2];
- expect(filter).toEqual(expected);
- });
-
- test('it returns 1 filtered lists of ip_range -> ip if the 2nd is not compatible type', () => {
- const field: IFieldType = { name: 'some-name', type: 'ip', esTypes: ['ip'] };
- const listItem1: ListSchema = { ...getListResponseMock(), type: 'ip_range' };
- const listItem2: ListSchema = { ...getListResponseMock(), type: 'text' };
- const filter = filterFieldToList([listItem1, listItem2], field);
- const expected: ListSchema[] = [listItem1];
- expect(filter).toEqual(expected);
- });
- });
});
diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.ts b/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.ts
index 81f5a66238567d..890f1e67558349 100644
--- a/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.ts
+++ b/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.ts
@@ -8,46 +8,17 @@
import dateMath from '@elastic/datemath';
import { EuiComboBoxOptionOption } from '@elastic/eui';
-import type { Type, ListSchema } from '@kbn/securitysolution-io-ts-list-types';
-import {
- EXCEPTION_OPERATORS,
- isOperator,
- isNotOperator,
- existsOperator,
- doesNotExistOperator,
-} from '@kbn/securitysolution-list-utils';
import { IFieldType } from '../../../../../../../src/plugins/data/common';
-import { GetGenericComboBoxPropsReturn, OperatorOption } from './types';
+import { GetGenericComboBoxPropsReturn } from './types';
import * as i18n from './translations';
-/**
- * Returns the appropriate operators given a field type
- *
- * @param field IFieldType selected field
- *
- */
-export const getOperators = (field: IFieldType | undefined): OperatorOption[] => {
- if (field == null) {
- return [isOperator];
- } else if (field.type === 'boolean') {
- return [isOperator, isNotOperator, existsOperator, doesNotExistOperator];
- } else if (field.type === 'nested') {
- return [isOperator];
- } else {
- return EXCEPTION_OPERATORS;
- }
-};
-
/**
* Determines if empty value is ok
+ * There is a copy within:
+ * x-pack/plugins/lists/public/exceptions/components/autocomplete/helpers.ts
*
- * @param param the value being checked
- * @param field the selected field
- * @param isRequired whether or not an empty value is allowed
- * @param touched has field been touched by user
- * @returns undefined if valid, string with error message if invalid,
- * null if no checks matched
+ * TODO: This should be in its own packaged and not copied, https://github.com/elastic/kibana/issues/105378
*/
export const checkEmptyValue = (
param: string | undefined,
@@ -72,7 +43,10 @@ export const checkEmptyValue = (
/**
* Very basic validation for values
+ * There is a copy within:
+ * x-pack/plugins/lists/public/exceptions/components/autocomplete/helpers.ts
*
+ * TODO: This should be in its own packaged and not copied, https://github.com/elastic/kibana/issues/105378
* @param param the value being checked
* @param field the selected field
* @param isRequired whether or not an empty value is allowed
@@ -109,7 +83,10 @@ export const paramIsValid = (
/**
* Determines the options, selected values and option labels for EUI combo box
+ * There is a copy within:
+ * x-pack/plugins/lists/public/exceptions/components/autocomplete/helpers.ts
*
+ * TODO: This should be in its own packaged and not copied, https://github.com/elastic/kibana/issues/105378
* @param options options user can select from
* @param selectedOptions user selection if any
* @param getLabel helper function to know which property to use for labels
@@ -140,36 +117,3 @@ export function getGenericComboBoxProps({
selectedComboOptions: newSelectedComboOptions,
};
}
-
-/**
- * Given an array of lists and optionally a field this will return all
- * the lists that match against the field based on the types from the field
- * @param lists The lists to match against the field
- * @param field The field to check against the list to see if they are compatible
- */
-export const filterFieldToList = (lists: ListSchema[], field?: IFieldType): ListSchema[] => {
- if (field != null) {
- const { esTypes = [] } = field;
- return lists.filter(({ type }) => esTypes.some((esType) => typeMatch(type, esType)));
- } else {
- return [];
- }
-};
-
-/**
- * Given an input list type and a string based ES type this will match
- * if they're exact or if they are compatible with a range
- * @param type The type to match against the esType
- * @param esType The ES type to match with
- */
-export const typeMatch = (type: Type, esType: string): boolean => {
- return (
- type === esType ||
- (type === 'ip_range' && esType === 'ip') ||
- (type === 'date_range' && esType === 'date') ||
- (type === 'double_range' && esType === 'double') ||
- (type === 'float_range' && esType === 'float') ||
- (type === 'integer_range' && esType === 'integer') ||
- (type === 'long_range' && esType === 'long')
- );
-};
diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/hooks/use_field_value_autocomplete.ts b/x-pack/plugins/security_solution/public/common/components/autocomplete/hooks/use_field_value_autocomplete.ts
index 0f369fa01d01ed..0fc4a663b7e11b 100644
--- a/x-pack/plugins/security_solution/public/common/components/autocomplete/hooks/use_field_value_autocomplete.ts
+++ b/x-pack/plugins/security_solution/public/common/components/autocomplete/hooks/use_field_value_autocomplete.ts
@@ -30,9 +30,13 @@ export interface UseFieldValueAutocompleteProps {
query: string;
indexPattern: IIndexPattern | undefined;
}
+
/**
* Hook for using the field value autocomplete service
+ * There is a copy within:
+ * x-pack/plugins/lists/public/exceptions/components/autocomplete/hooks.ts
*
+ * TODO: This should be in its own packaged and not copied, https://github.com/elastic/kibana/issues/105378
*/
export const useFieldValueAutocomplete = ({
selectedField,
diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/operator.test.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete/operator.test.tsx
deleted file mode 100644
index 5e00d2beb571c4..00000000000000
--- a/x-pack/plugins/security_solution/public/common/components/autocomplete/operator.test.tsx
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { mount } from 'enzyme';
-import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
-
-import { getField } from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks';
-import { OperatorComponent } from './operator';
-import { isOperator, isNotOperator } from '@kbn/securitysolution-list-utils';
-
-describe('OperatorComponent', () => {
- test('it renders disabled if "isDisabled" is true', () => {
- const wrapper = mount(
-
- );
-
- expect(
- wrapper.find(`[data-test-subj="operatorAutocompleteComboBox"] input`).prop('disabled')
- ).toBeTruthy();
- });
-
- test('it renders loading if "isLoading" is true', () => {
- const wrapper = mount(
-
- );
- wrapper.find(`[data-test-subj="operatorAutocompleteComboBox"] button`).at(0).simulate('click');
- expect(
- wrapper
- .find(`EuiComboBoxOptionsList[data-test-subj="operatorAutocompleteComboBox-optionsList"]`)
- .prop('isLoading')
- ).toBeTruthy();
- });
-
- test('it allows user to clear values if "isClearable" is true', () => {
- const wrapper = mount(
-
- );
-
- expect(wrapper.find(`button[data-test-subj="comboBoxClearButton"]`).exists()).toBeTruthy();
- });
-
- test('it displays "operatorOptions" if param is passed in with items', () => {
- const wrapper = mount(
-
- );
-
- expect(
- wrapper.find(`[data-test-subj="operatorAutocompleteComboBox"]`).at(0).prop('options')
- ).toEqual([{ label: 'is not' }]);
- });
-
- test('it does not display "operatorOptions" if param is passed in with no items', () => {
- const wrapper = mount(
-
- );
-
- expect(
- wrapper.find(`[data-test-subj="operatorAutocompleteComboBox"]`).at(0).prop('options')
- ).toEqual([
- {
- label: 'is',
- },
- {
- label: 'is not',
- },
- {
- label: 'is one of',
- },
- {
- label: 'is not one of',
- },
- {
- label: 'exists',
- },
- {
- label: 'does not exist',
- },
- {
- label: 'is in list',
- },
- {
- label: 'is not in list',
- },
- ]);
- });
-
- test('it correctly displays selected operator', () => {
- const wrapper = mount(
-
- );
-
- expect(
- wrapper.find(`[data-test-subj="operatorAutocompleteComboBox"] EuiComboBoxPill`).at(0).text()
- ).toEqual('is');
- });
-
- test('it only displays subset of operators if field type is nested', () => {
- const wrapper = mount(
-
- );
-
- expect(
- wrapper.find(`[data-test-subj="operatorAutocompleteComboBox"]`).at(0).prop('options')
- ).toEqual([{ label: 'is' }]);
- });
-
- test('it only displays subset of operators if field type is boolean', () => {
- const wrapper = mount(
-
- );
-
- expect(
- wrapper.find(`[data-test-subj="operatorAutocompleteComboBox"]`).at(0).prop('options')
- ).toEqual([
- { label: 'is' },
- { label: 'is not' },
- { label: 'exists' },
- { label: 'does not exist' },
- ]);
- });
-
- test('it invokes "onChange" when option selected', () => {
- const mockOnChange = jest.fn();
- const wrapper = mount(
-
- );
-
- ((wrapper.find(EuiComboBox).props() as unknown) as {
- onChange: (a: EuiComboBoxOptionOption[]) => void;
- }).onChange([{ label: 'is not' }]);
-
- expect(mockOnChange).toHaveBeenCalledWith([
- { message: 'is not', operator: 'excluded', type: 'match', value: 'is_not' },
- ]);
- });
-});
diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/operator.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete/operator.tsx
deleted file mode 100644
index d8f49acd04b99b..00000000000000
--- a/x-pack/plugins/security_solution/public/common/components/autocomplete/operator.tsx
+++ /dev/null
@@ -1,82 +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, { useCallback, useMemo } from 'react';
-import { EuiComboBoxOptionOption, EuiComboBox } from '@elastic/eui';
-
-import { IFieldType } from '../../../../../../../src/plugins/data/common';
-import { getOperators, getGenericComboBoxProps } from './helpers';
-import { GetGenericComboBoxPropsReturn, OperatorOption } from './types';
-
-interface OperatorState {
- placeholder: string;
- selectedField: IFieldType | undefined;
- operator: OperatorOption;
- isLoading: boolean;
- isDisabled: boolean;
- isClearable: boolean;
- operatorInputWidth?: number;
- operatorOptions?: OperatorOption[];
- onChange: (arg: OperatorOption[]) => void;
-}
-
-export const OperatorComponent: React.FC = ({
- placeholder,
- selectedField,
- operator,
- isLoading = false,
- isDisabled = false,
- isClearable = false,
- operatorOptions,
- operatorInputWidth = 150,
- onChange,
-}): JSX.Element => {
- const getLabel = useCallback(({ message }): string => message, []);
- const optionsMemo = useMemo(
- (): OperatorOption[] =>
- operatorOptions != null && operatorOptions.length > 0
- ? operatorOptions
- : getOperators(selectedField),
- [operatorOptions, selectedField]
- );
- const selectedOptionsMemo = useMemo((): OperatorOption[] => (operator ? [operator] : []), [
- operator,
- ]);
- const { comboOptions, labels, selectedComboOptions } = useMemo(
- (): GetGenericComboBoxPropsReturn =>
- getGenericComboBoxProps({
- options: optionsMemo,
- selectedOptions: selectedOptionsMemo,
- getLabel,
- }),
- [optionsMemo, selectedOptionsMemo, getLabel]
- );
-
- const handleValuesChange = (newOptions: EuiComboBoxOptionOption[]): void => {
- const newValues: OperatorOption[] = newOptions.map(
- ({ label }) => optionsMemo[labels.indexOf(label)]
- );
- onChange(newValues);
- };
-
- return (
-
- );
-};
-
-OperatorComponent.displayName = 'Operator';
diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/types.ts b/x-pack/plugins/security_solution/public/common/components/autocomplete/types.ts
index 1d8e3e9aee28eb..07f1903fb70e1c 100644
--- a/x-pack/plugins/security_solution/public/common/components/autocomplete/types.ts
+++ b/x-pack/plugins/security_solution/public/common/components/autocomplete/types.ts
@@ -7,20 +7,8 @@
import { EuiComboBoxOptionOption } from '@elastic/eui';
-import type {
- ListOperatorEnum as OperatorEnum,
- ListOperatorTypeEnum as OperatorTypeEnum,
-} from '@kbn/securitysolution-io-ts-list-types';
-
export interface GetGenericComboBoxPropsReturn {
comboOptions: EuiComboBoxOptionOption[];
labels: string[];
selectedComboOptions: EuiComboBoxOptionOption[];
}
-
-export interface OperatorOption {
- message: string;
- value: string;
- operator: OperatorEnum;
- type: OperatorTypeEnum;
-}
diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap
index 2dc3f7fd336a2d..6ef797580be9b3 100644
--- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap
@@ -366,6 +366,7 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] =
"format": "",
"indexes": Array [
"apm-*-transaction*",
+ "traces-apm*",
"auditbeat-*",
"endgame-*",
"filebeat-*",
diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/agent_status.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/agent_status.tsx
index f93721349fdac0..d7091059012151 100644
--- a/x-pack/plugins/security_solution/public/common/components/endpoint/agent_status.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/endpoint/agent_status.tsx
@@ -20,7 +20,7 @@ export const AgentStatus = React.memo(({ hostStatus }: { hostStatus: HostStatus
>
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/__mocks__/index.ts b/x-pack/plugins/security_solution/public/common/components/event_details/__mocks__/index.ts
index 3edd6e6fda14b3..620c3991b0ad98 100644
--- a/x-pack/plugins/security_solution/public/common/components/event_details/__mocks__/index.ts
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/__mocks__/index.ts
@@ -406,6 +406,7 @@ export const mockAlertDetailsData = [
field: 'signal.rule.index',
values: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -415,6 +416,7 @@ export const mockAlertDetailsData = [
],
originalValue: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_fields.json b/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_fields.json
index b5480aac27f678..c37be60545ab2e 100644
--- a/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_fields.json
+++ b/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_fields.json
@@ -2,10 +2,12 @@
"Endpoint.policy.applied.id",
"Target.process.Ext.services",
"Target.process.Ext.user",
+ "Target.process.executable",
"Target.process.hash.md5",
"Target.process.hash.sha1",
"Target.process.hash.sha256",
"Target.process.hash.sha512",
+ "Target.process.name",
"Target.process.parent.hash.md5",
"Target.process.parent.hash.sha1",
"Target.process.parent.hash.sha256",
@@ -17,6 +19,14 @@
"Target.process.pe.original_file_name",
"Target.process.pe.product",
"Target.process.pgid",
+ "Target.process.thread.Ext.start_address_details.allocation_type",
+ "Target.process.thread.Ext.start_address_bytes_disasm_hash",
+ "Target.process.thread.Ext.start_address_allocation_offset",
+ "Target.process.thread.Ext.start_address_details.allocation_size",
+ "Target.process.thread.Ext.start_address_details.region_size",
+ "Target.process.thread.Ext.start_address_details.region_protection",
+ "Target.process.thread.Ext.start_address_details.memory_pe.imphash",
+ "Target.process.thread.Ext.start_address_bytes",
"agent.id",
"agent.type",
"agent.version",
@@ -68,10 +78,13 @@
"host.type",
"process.Ext.services",
"process.Ext.user",
+ "process.Ext.code_signature",
+ "process.executable",
"process.hash.md5",
"process.hash.sha1",
"process.hash.sha256",
"process.hash.sha512",
+ "process.name",
"process.parent.hash.md5",
"process.parent.hash.sha1",
"process.parent.hash.sha256",
@@ -88,5 +101,7 @@
"user.email",
"user.hash",
"user.id",
- "Ransomware.feature"
+ "Ransomware.feature",
+ "Memory_protection.feature",
+ "Memory_protection.self_injection"
]
diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx
index 0af83e2cff3b5b..32eb4baad50598 100644
--- a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx
@@ -928,5 +928,172 @@ describe('Exception helpers', () => {
},
]);
});
+
+ test('it should return pre-populated memory signature items for event code `memory_signature`', () => {
+ const defaultItems = defaultEndpointExceptionItems('list_id', 'my_rule', {
+ _id: '123',
+ process: {
+ name: 'some name',
+ executable: 'some file path',
+ hash: {
+ sha256: 'some hash',
+ },
+ },
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ Memory_protection: {
+ feature: 'signature',
+ },
+ event: {
+ code: 'memory_signature',
+ },
+ });
+
+ expect(defaultItems[0].entries).toEqual([
+ {
+ field: 'Memory_protection.feature',
+ operator: 'included',
+ type: 'match',
+ value: 'signature',
+ id: '123',
+ },
+ {
+ field: 'process.executable.caseless',
+ operator: 'included',
+ type: 'match',
+ value: 'some file path',
+ id: '123',
+ },
+ {
+ field: 'process.name.caseless',
+ operator: 'included',
+ type: 'match',
+ value: 'some name',
+ id: '123',
+ },
+ {
+ field: 'process.hash.sha256',
+ operator: 'included',
+ type: 'match',
+ value: 'some hash',
+ id: '123',
+ },
+ ]);
+ });
+
+ test('it should return pre-populated memory shellcode items for event code `malicious_thread`', () => {
+ const defaultItems = defaultEndpointExceptionItems('list_id', 'my_rule', {
+ _id: '123',
+ process: {
+ name: 'some name',
+ executable: 'some file path',
+ Ext: {
+ token: {
+ integrity_level_name: 'high',
+ },
+ },
+ },
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ Memory_protection: {
+ feature: 'shellcode_thread',
+ self_injection: true,
+ },
+ event: {
+ code: 'malicious_thread',
+ },
+ Target: {
+ process: {
+ thread: {
+ Ext: {
+ start_address_allocation_offset: 0,
+ start_address_bytes_disasm_hash: 'a disam hash',
+ start_address_details: {
+ allocation_type: 'PRIVATE',
+ allocation_size: 4000,
+ region_size: 4000,
+ region_protection: 'RWX',
+ memory_pe: {
+ imphash: 'a hash',
+ },
+ },
+ },
+ },
+ },
+ },
+ });
+
+ expect(defaultItems[0].entries).toEqual([
+ {
+ field: 'Memory_protection.feature',
+ operator: 'included',
+ type: 'match',
+ value: 'shellcode_thread',
+ id: '123',
+ },
+ {
+ field: 'Memory_protection.self_injection',
+ operator: 'included',
+ type: 'match',
+ value: 'true',
+ id: '123',
+ },
+ {
+ field: 'process.executable.caseless',
+ operator: 'included',
+ type: 'match',
+ value: 'some file path',
+ id: '123',
+ },
+ {
+ field: 'process.name.caseless',
+ operator: 'included',
+ type: 'match',
+ value: 'some name',
+ id: '123',
+ },
+ {
+ field: 'process.Ext.token.integrity_level_name',
+ operator: 'included',
+ type: 'match',
+ value: 'high',
+ id: '123',
+ },
+ {
+ field: 'Target.process.thread.Ext.start_address_details',
+ type: 'nested',
+ entries: [
+ {
+ field: 'allocation_type',
+ operator: 'included',
+ type: 'match',
+ value: 'PRIVATE',
+ id: '123',
+ },
+ {
+ field: 'allocation_size',
+ operator: 'included',
+ type: 'match',
+ value: '4000',
+ id: '123',
+ },
+ { field: 'region_size', operator: 'included', type: 'match', value: '4000', id: '123' },
+ {
+ field: 'region_protection',
+ operator: 'included',
+ type: 'match',
+ value: 'RWX',
+ id: '123',
+ },
+ {
+ field: 'memory_pe.imphash',
+ operator: 'included',
+ type: 'match',
+ value: 'a hash',
+ id: '123',
+ },
+ ],
+ id: '123',
+ },
+ ]);
+ });
});
});
diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx
index bfb5c7298f3301..3c8652637a997f 100644
--- a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx
@@ -496,6 +496,139 @@ export const getPrepopulatedRansomwareException = ({
};
};
+export const getPrepopulatedMemorySignatureException = ({
+ listId,
+ ruleName,
+ eventCode,
+ listNamespace = 'agnostic',
+ alertEcsData,
+}: {
+ listId: string;
+ listNamespace?: NamespaceType;
+ ruleName: string;
+ eventCode: string;
+ alertEcsData: Flattened;
+}): ExceptionsBuilderExceptionItem => {
+ const { process } = alertEcsData;
+ return {
+ ...getNewExceptionItem({ listId, namespaceType: listNamespace, ruleName }),
+ entries: addIdToEntries([
+ {
+ field: 'Memory_protection.feature',
+ operator: 'included',
+ type: 'match',
+ value: alertEcsData.Memory_protection?.feature ?? '',
+ },
+ {
+ field: 'process.executable.caseless',
+ operator: 'included',
+ type: 'match',
+ value: process?.executable ?? '',
+ },
+ {
+ field: 'process.name.caseless',
+ operator: 'included',
+ type: 'match',
+ value: process?.name ?? '',
+ },
+ {
+ field: 'process.hash.sha256',
+ operator: 'included',
+ type: 'match',
+ value: process?.hash?.sha256 ?? '',
+ },
+ ]),
+ };
+};
+export const getPrepopulatedMemoryShellcodeException = ({
+ listId,
+ ruleName,
+ eventCode,
+ listNamespace = 'agnostic',
+ alertEcsData,
+}: {
+ listId: string;
+ listNamespace?: NamespaceType;
+ ruleName: string;
+ eventCode: string;
+ alertEcsData: Flattened;
+}): ExceptionsBuilderExceptionItem => {
+ const { process, Target } = alertEcsData;
+ return {
+ ...getNewExceptionItem({ listId, namespaceType: listNamespace, ruleName }),
+ entries: addIdToEntries([
+ {
+ field: 'Memory_protection.feature',
+ operator: 'included',
+ type: 'match',
+ value: alertEcsData.Memory_protection?.feature ?? '',
+ },
+ {
+ field: 'Memory_protection.self_injection',
+ operator: 'included',
+ type: 'match',
+ value: String(alertEcsData.Memory_protection?.self_injection) ?? '',
+ },
+ {
+ field: 'process.executable.caseless',
+ operator: 'included',
+ type: 'match',
+ value: process?.executable ?? '',
+ },
+ {
+ field: 'process.name.caseless',
+ operator: 'included',
+ type: 'match',
+ value: process?.name ?? '',
+ },
+ {
+ field: 'process.Ext.token.integrity_level_name',
+ operator: 'included',
+ type: 'match',
+ value: process?.Ext?.token?.integrity_level_name ?? '',
+ },
+ {
+ field: 'Target.process.thread.Ext.start_address_details',
+ type: 'nested',
+ entries: [
+ {
+ field: 'allocation_type',
+ operator: 'included',
+ type: 'match',
+ value: Target?.process?.thread?.Ext?.start_address_details?.allocation_type ?? '',
+ },
+ {
+ field: 'allocation_size',
+ operator: 'included',
+ type: 'match',
+ value:
+ String(Target?.process?.thread?.Ext?.start_address_details?.allocation_size) ?? '',
+ },
+ {
+ field: 'region_size',
+ operator: 'included',
+ type: 'match',
+ value: String(Target?.process?.thread?.Ext?.start_address_details?.region_size) ?? '',
+ },
+ {
+ field: 'region_protection',
+ operator: 'included',
+ type: 'match',
+ value:
+ String(Target?.process?.thread?.Ext?.start_address_details?.region_protection) ?? '',
+ },
+ {
+ field: 'memory_pe.imphash',
+ operator: 'included',
+ type: 'match',
+ value:
+ String(Target?.process?.thread?.Ext?.start_address_details?.memory_pe?.imphash) ?? '',
+ },
+ ],
+ },
+ ]),
+ };
+};
/**
* Determines whether or not any entries within the given exceptionItems contain values not in the specified ECS mapping
*/
@@ -537,26 +670,45 @@ export const defaultEndpointExceptionItems = (
const { event: alertEvent } = alertEcsData;
const eventCode = alertEvent?.code ?? '';
- if (eventCode === 'ransomware') {
- return getProcessCodeSignature(alertEcsData).map((codeSignature) =>
- getPrepopulatedRansomwareException({
- listId,
- ruleName,
- eventCode,
- codeSignature,
- alertEcsData,
- })
- );
+ switch (eventCode) {
+ case 'memory_signature':
+ return [
+ getPrepopulatedMemorySignatureException({
+ listId,
+ ruleName,
+ eventCode,
+ alertEcsData,
+ }),
+ ];
+ case 'malicious_thread':
+ return [
+ getPrepopulatedMemoryShellcodeException({
+ listId,
+ ruleName,
+ eventCode,
+ alertEcsData,
+ }),
+ ];
+ case 'ransomware':
+ return getProcessCodeSignature(alertEcsData).map((codeSignature) =>
+ getPrepopulatedRansomwareException({
+ listId,
+ ruleName,
+ eventCode,
+ codeSignature,
+ alertEcsData,
+ })
+ );
+ default:
+ // By default return the standard prepopulated Endpoint Exception fields
+ return getFileCodeSignature(alertEcsData).map((codeSignature) =>
+ getPrepopulatedEndpointException({
+ listId,
+ ruleName,
+ eventCode,
+ codeSignature,
+ alertEcsData,
+ })
+ );
}
-
- // By default return the standard prepopulated Endpoint Exception fields
- return getFileCodeSignature(alertEcsData).map((codeSignature) =>
- getPrepopulatedEndpointException({
- listId,
- ruleName,
- eventCode,
- codeSignature,
- alertEcsData,
- })
- );
};
diff --git a/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx
index e8f382a5050d82..87a7ce805940f7 100644
--- a/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx
@@ -39,6 +39,7 @@ const mockOptions = [
{ label: 'filebeat-*', value: 'filebeat-*' },
{ label: 'logs-*', value: 'logs-*' },
{ label: 'packetbeat-*', value: 'packetbeat-*' },
+ { label: 'traces-apm*', value: 'traces-apm*' },
{ label: 'winlogbeat-*', value: 'winlogbeat-*' },
];
diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.test.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.test.ts
index 730857b6494d9b..dd608138ef9f03 100644
--- a/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.test.ts
+++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.test.ts
@@ -23,6 +23,7 @@ describe('Sourcerer selectors', () => {
'filebeat-*',
'logs-*',
'packetbeat-*',
+ 'traces-apm*',
'winlogbeat-*',
'-*elastic-cloud-logs-*',
]);
@@ -42,6 +43,7 @@ describe('Sourcerer selectors', () => {
'endgame-*',
'filebeat-*',
'packetbeat-*',
+ 'traces-apm*',
'winlogbeat-*',
]);
});
@@ -64,6 +66,7 @@ describe('Sourcerer selectors', () => {
'filebeat-*',
'logs-endpoint.event-*',
'packetbeat-*',
+ 'traces-apm*',
'winlogbeat-*',
]);
});
diff --git a/x-pack/plugins/security_solution/public/common/utils/shorten_count_into_string.test.ts b/x-pack/plugins/security_solution/public/common/utils/shorten_count_into_string.test.ts
new file mode 100644
index 00000000000000..13699f5dc30607
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/utils/shorten_count_into_string.test.ts
@@ -0,0 +1,38 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { shortenCountIntoString } from './shorten_count_into_string';
+
+describe('utils', () => {
+ describe('shortenCountIntoString', () => {
+ it('should not change small numbers', () => {
+ expect(shortenCountIntoString(0)).toBe('0');
+ expect(shortenCountIntoString(9999)).toBe('9999');
+ });
+
+ it('should add K when appropriate', () => {
+ expect(shortenCountIntoString(10000)).toBe('10K');
+ expect(shortenCountIntoString(109000)).toBe('109K');
+ expect(shortenCountIntoString(109800)).toBe('109.8K');
+ expect(shortenCountIntoString(109897)).toBe('109.8K');
+ });
+
+ it('should add M when appropriate', () => {
+ expect(shortenCountIntoString(10000000)).toBe('10M');
+ expect(shortenCountIntoString(109000000)).toBe('109M');
+ expect(shortenCountIntoString(109800000)).toBe('109.8M');
+ expect(shortenCountIntoString(109890000)).toBe('109.8M');
+ });
+
+ it('should add B when appropriate', () => {
+ expect(shortenCountIntoString(10000000000)).toBe('10B');
+ expect(shortenCountIntoString(109000000000)).toBe('109B');
+ expect(shortenCountIntoString(109800000000)).toBe('109.8B');
+ expect(shortenCountIntoString(109890000000)).toBe('109.8B');
+ });
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/common/utils/shorten_count_into_string.ts b/x-pack/plugins/security_solution/public/common/utils/shorten_count_into_string.ts
new file mode 100644
index 00000000000000..63d2c8f597911b
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/utils/shorten_count_into_string.ts
@@ -0,0 +1,35 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export const shortenCountIntoString = (count: number): string => {
+ if (count < 10000) {
+ return count.toString();
+ }
+ const abbreviations = [
+ { magnitude: 1e18, unit: 'E' },
+ { magnitude: 1e15, unit: 'P' },
+ { magnitude: 1e12, unit: 'T' },
+ { magnitude: 1e9, unit: 'B' },
+ { magnitude: 1e6, unit: 'M' },
+ { magnitude: 1e3, unit: 'K' },
+ ];
+ const { magnitude, unit } = abbreviations.find(
+ (abbreviation) => count >= abbreviation.magnitude
+ ) ?? {
+ magnitude: 1,
+ unit: '',
+ };
+
+ return (
+ toFixedWithoutRounding(count / magnitude, 1).replace(/\.0+$|(\.[0-9]*[1-9])0+$/, '$1') + unit
+ );
+};
+
+const toFixedWithoutRounding = (n: number, p: number) => {
+ const result = n.toFixed(p);
+ return +result <= n ? result : (+result - Math.pow(0.1, p)).toFixed(p);
+};
diff --git a/x-pack/plugins/security_solution/public/detections/components/host_isolation/take_action_dropdown.tsx b/x-pack/plugins/security_solution/public/detections/components/host_isolation/take_action_dropdown.tsx
index a10ad901441ea5..1404f7927d6ec8 100644
--- a/x-pack/plugins/security_solution/public/detections/components/host_isolation/take_action_dropdown.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/host_isolation/take_action_dropdown.tsx
@@ -10,6 +10,7 @@ import { EuiContextMenuItem, EuiContextMenuPanel, EuiButton, EuiPopover } from '
import { ISOLATE_HOST, UNISOLATE_HOST } from './translations';
import { TAKE_ACTION } from '../alerts_table/alerts_utility_bar/translations';
import { useHostIsolationStatus } from '../../containers/detection_engine/alerts/use_host_isolation_status';
+import { HostStatus } from '../../../../common/endpoint/types';
export const TakeActionDropdown = React.memo(
({
@@ -19,7 +20,9 @@ export const TakeActionDropdown = React.memo(
onChange: (action: 'isolateHost' | 'unisolateHost') => void;
agentId: string;
}) => {
- const { loading, isIsolated: isolationStatus } = useHostIsolationStatus({ agentId });
+ const { loading, isIsolated: isolationStatus, agentStatus } = useHostIsolationStatus({
+ agentId,
+ });
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const closePopoverHandler = useCallback(() => {
@@ -41,7 +44,7 @@ export const TakeActionDropdown = React.memo(
iconSide="right"
fill
iconType="arrowDown"
- disabled={loading}
+ disabled={loading || agentStatus === HostStatus.UNENROLLED}
onClick={() => {
setIsPopoverOpen(!isPopoverOpen);
}}
@@ -49,7 +52,7 @@ export const TakeActionDropdown = React.memo(
{TAKE_ACTION}
);
- }, [isPopoverOpen, loading]);
+ }, [isPopoverOpen, loading, agentStatus]);
return (
= {
}
),
labelAppend: OptionalFieldLabel,
+ validations: [
+ {
+ validator: emptyField(
+ i18n.translate(
+ 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.authorFieldEmptyError',
+ {
+ defaultMessage: 'An author must not be empty',
+ }
+ )
+ ),
+ type: VALIDATION_TYPES.ARRAY_ITEM,
+ isBlocking: false,
+ },
+ ],
},
name: {
type: FIELD_TYPES.TEXT,
@@ -243,6 +258,20 @@ export const schema: FormSchema = {
}
),
labelAppend: OptionalFieldLabel,
+ validations: [
+ {
+ validator: emptyField(
+ i18n.translate(
+ 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.tagFieldEmptyError',
+ {
+ defaultMessage: 'A tag must not be empty',
+ }
+ )
+ ),
+ type: VALIDATION_TYPES.ARRAY_ITEM,
+ isBlocking: false,
+ },
+ ],
},
note: {
type: FIELD_TYPES.TEXTAREA,
diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/mock.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/mock.ts
index e4bddfba8278bb..7aba8fa4ac10f1 100644
--- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/mock.ts
+++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/mock.ts
@@ -175,6 +175,7 @@ export const alertsMock: AlertSearchResponse = {
immutable: false,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -414,6 +415,7 @@ export const alertsMock: AlertSearchResponse = {
immutable: false,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -619,6 +621,7 @@ export const alertsMock: AlertSearchResponse = {
immutable: false,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -822,6 +825,7 @@ export const alertsMock: AlertSearchResponse = {
immutable: false,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/translations.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/translations.ts
index d5234e719b869c..ed6a22375a7769 100644
--- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/translations.ts
+++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/translations.ts
@@ -37,13 +37,3 @@ export const CASES_FROM_ALERTS_FAILURE = i18n.translate(
'xpack.securitySolution.endpoint.hostIsolation.casesFromAlerts.title',
{ defaultMessage: 'Failed to find associated cases' }
);
-
-export const ISOLATION_STATUS_FAILURE = i18n.translate(
- 'xpack.securitySolution.endpoint.hostIsolation.isolationStatus.title',
- { defaultMessage: 'Failed to retrieve current isolation status' }
-);
-
-export const ISOLATION_PENDING_FAILURE = i18n.translate(
- 'xpack.securitySolution.endpoint.hostIsolation.isolationPending.title',
- { defaultMessage: 'Failed to retrieve isolation pending statuses' }
-);
diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_isolation_status.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_isolation_status.tsx
index 259a377b10b796..6a40898d0a109f 100644
--- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_isolation_status.tsx
+++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_isolation_status.tsx
@@ -7,9 +7,7 @@
import { isEmpty } from 'lodash';
import { useEffect, useState } from 'react';
-import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
import { getHostMetadata } from './api';
-import { ISOLATION_STATUS_FAILURE, ISOLATION_PENDING_FAILURE } from './translations';
import { fetchPendingActionsByAgentId } from '../../../../common/lib/endpoint_pending_actions';
import { isEndpointHostIsolated } from '../../../../common/utils/validators';
import { HostStatus } from '../../../../../common/endpoint/types';
@@ -17,7 +15,7 @@ import { HostStatus } from '../../../../../common/endpoint/types';
interface HostIsolationStatusResponse {
loading: boolean;
isIsolated: boolean;
- agentStatus: HostStatus;
+ agentStatus: HostStatus | undefined;
pendingIsolation: number;
pendingUnisolation: number;
}
@@ -30,13 +28,11 @@ export const useHostIsolationStatus = ({
agentId: string;
}): HostIsolationStatusResponse => {
const [isIsolated, setIsIsolated] = useState(false);
- const [agentStatus, setAgentStatus] = useState(HostStatus.UNHEALTHY);
+ const [agentStatus, setAgentStatus] = useState();
const [pendingIsolation, setPendingIsolation] = useState(0);
const [pendingUnisolation, setPendingUnisolation] = useState(0);
const [loading, setLoading] = useState(false);
- const { addError } = useAppToasts();
-
useEffect(() => {
const abortCtrl = new AbortController();
// isMounted tracks if a component is mounted before changing state
@@ -55,7 +51,10 @@ export const useHostIsolationStatus = ({
if (error.name === 'AbortError') {
return;
}
- addError(error.message, { title: ISOLATION_STATUS_FAILURE });
+
+ if (isMounted && error.body.statusCode === 404) {
+ setAgentStatus(HostStatus.UNENROLLED);
+ }
}
try {
@@ -65,7 +64,8 @@ export const useHostIsolationStatus = ({
setPendingUnisolation(data[0].pending_actions?.unisolate ?? 0);
}
} catch (error) {
- addError(error.message, { title: ISOLATION_PENDING_FAILURE });
+ // silently catch non-user initiated error
+ return;
}
if (isMounted) {
@@ -87,6 +87,6 @@ export const useHostIsolationStatus = ({
isMounted = false;
abortCtrl.abort();
};
- }, [addError, agentId]);
+ }, [agentId]);
return { loading, isIsolated, agentStatus, pendingIsolation, pendingUnisolation };
};
diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/mock.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/mock.ts
index 1104cb86064b07..533ab6138cb09f 100644
--- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/mock.ts
+++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/mock.ts
@@ -20,6 +20,7 @@ export const savedRuleMock: Rule = {
id: '12345678987654321',
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule.test.tsx
index ca6cd5b11f7057..096463872fc011 100644
--- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule.test.tsx
@@ -54,6 +54,7 @@ describe('useRule', () => {
immutable: false,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.test.tsx
index 3394f1fc553ae5..4d01e2ff00ec18 100644
--- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.test.tsx
@@ -43,6 +43,7 @@ const testRule: Rule = {
immutable: false,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.test.tsx
index abd5a2781c8a77..1f08a356602152 100644
--- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.test.tsx
@@ -62,6 +62,7 @@ describe('useRuleWithFallback', () => {
"immutable": false,
"index": Array [
"apm-*-transaction*",
+ "traces-apm*",
"auditbeat-*",
"endgame-*",
"filebeat-*",
@@ -125,6 +126,7 @@ describe('useRuleWithFallback', () => {
"immutable": false,
"index": Array [
"apm-*-transaction*",
+ "traces-apm*",
"auditbeat-*",
"endgame-*",
"filebeat-*",
diff --git a/x-pack/plugins/security_solution/public/detections/mitre/mitre_tactics_techniques.ts b/x-pack/plugins/security_solution/public/detections/mitre/mitre_tactics_techniques.ts
index a5da747787ba6e..f28311d9c96e76 100644
--- a/x-pack/plugins/security_solution/public/detections/mitre/mitre_tactics_techniques.ts
+++ b/x-pack/plugins/security_solution/public/detections/mitre/mitre_tactics_techniques.ts
@@ -10143,7 +10143,7 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [
*
* Is built alongside and sampled from the data in the file so to always be valid with the most up to date MITRE ATT&CK data
*/
-export const mockThreatData = {
+export const getMockThreatData = () => ({
tactic: {
name: 'Privilege Escalation',
id: 'TA0004',
@@ -10162,4 +10162,4 @@ export const mockThreatData = {
tactics: ['privilege-escalation', 'persistence'],
techniqueId: 'T1546',
},
-};
+});
diff --git a/x-pack/plugins/security_solution/public/detections/mitre/valid_threat_mock.ts b/x-pack/plugins/security_solution/public/detections/mitre/valid_threat_mock.ts
index a7de7494e11167..743b143213c220 100644
--- a/x-pack/plugins/security_solution/public/detections/mitre/valid_threat_mock.ts
+++ b/x-pack/plugins/security_solution/public/detections/mitre/valid_threat_mock.ts
@@ -6,9 +6,9 @@
*/
import { Threats } from '@kbn/securitysolution-io-ts-alerting-types';
-import { mockThreatData } from './mitre_tactics_techniques';
+import { getMockThreatData } from './mitre_tactics_techniques';
-const { tactic, technique, subtechnique } = mockThreatData;
+const { tactic, technique, subtechnique } = getMockThreatData();
const { tactics, ...mockTechnique } = technique;
const { tactics: subtechniqueTactics, ...mockSubtechnique } = subtechnique;
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts
index fb9661b509a33c..45cf7d725443d0 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts
@@ -18,6 +18,7 @@ export const HOST_STATUS_TO_BADGE_COLOR = Object.freeze<
[HostStatus.UPDATING]: 'primary',
[HostStatus.OFFLINE]: 'default',
[HostStatus.INACTIVE]: 'default',
+ [HostStatus.UNENROLLED]: 'default',
});
export const POLICY_STATUS_TO_HEALTH_COLOR = Object.freeze<
diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/__mocks__/mock.ts b/x-pack/plugins/security_solution/public/network/components/embeddables/__mocks__/mock.ts
index ba9b6518c6accc..834447b21929fe 100644
--- a/x-pack/plugins/security_solution/public/network/components/embeddables/__mocks__/mock.ts
+++ b/x-pack/plugins/security_solution/public/network/components/embeddables/__mocks__/mock.ts
@@ -14,6 +14,7 @@ export const mockIndexPatternIds: IndexPatternMapping[] = [
export const mockAPMIndexPatternIds: IndexPatternMapping[] = [
{ title: 'apm-*', id: '8c7323ac-97ad-4b53-ac0a-40f8f691a918' },
+ { title: 'traces-apm*,logs-apm*,metrics-apm*,apm-*', id: '8c7323ac-97ad-4b53-ac0a-40f8f691a918' },
];
export const mockSourceLayer = {
@@ -183,6 +184,11 @@ export const mockClientLayer = {
joins: [],
};
+const mockApmDataStreamClientLayer = {
+ ...mockClientLayer,
+ label: 'traces-apm*,logs-apm*,metrics-apm*,apm-* | Client Point',
+};
+
export const mockServerLayer = {
sourceDescriptor: {
id: 'uuid.v4()',
@@ -238,6 +244,11 @@ export const mockServerLayer = {
query: { query: '', language: 'kuery' },
};
+const mockApmDataStreamServerLayer = {
+ ...mockServerLayer,
+ label: 'traces-apm*,logs-apm*,metrics-apm*,apm-* | Server Point',
+};
+
export const mockLineLayer = {
sourceDescriptor: {
type: 'ES_PEW_PEW',
@@ -365,6 +376,10 @@ export const mockClientServerLineLayer = {
type: 'VECTOR',
query: { query: '', language: 'kuery' },
};
+const mockApmDataStreamClientServerLineLayer = {
+ ...mockClientServerLineLayer,
+ label: 'traces-apm*,logs-apm*,metrics-apm*,apm-* | Line',
+};
export const mockLayerList = [
{
@@ -421,6 +436,9 @@ export const mockLayerListMixed = [
mockClientServerLineLayer,
mockServerLayer,
mockClientLayer,
+ mockApmDataStreamClientServerLineLayer,
+ mockApmDataStreamServerLayer,
+ mockApmDataStreamClientLayer,
];
export const mockAPMIndexPattern: IndexPatternSavedObject = {
@@ -468,6 +486,15 @@ export const mockAPMTransactionIndexPattern: IndexPatternSavedObject = {
},
};
+export const mockAPMTracesDataStreamIndexPattern: IndexPatternSavedObject = {
+ id: 'traces-apm*',
+ type: 'index-pattern',
+ _version: 'abc',
+ attributes: {
+ title: 'traces-apm*',
+ },
+};
+
export const mockGlobIndexPattern: IndexPatternSavedObject = {
id: '*',
type: 'index-pattern',
diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map_helpers.test.tsx b/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map_helpers.test.tsx
index 6136f5da51d51a..613a6ce4c00daa 100644
--- a/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map_helpers.test.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map_helpers.test.tsx
@@ -12,6 +12,7 @@ import {
mockAPMIndexPattern,
mockAPMRegexIndexPattern,
mockAPMTransactionIndexPattern,
+ mockAPMTracesDataStreamIndexPattern,
mockAuditbeatIndexPattern,
mockCCSGlobIndexPattern,
mockCommaFilebeatAuditbeatCCSGlobIndexPattern,
@@ -69,6 +70,7 @@ describe('embedded_map_helpers', () => {
describe('findMatchingIndexPatterns', () => {
const siemDefaultIndices = [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -102,11 +104,16 @@ describe('embedded_map_helpers', () => {
test('finds exact glob-matched index patterns ', () => {
const matchingIndexPatterns = findMatchingIndexPatterns({
- kibanaIndexPatterns: [mockAPMTransactionIndexPattern, mockFilebeatIndexPattern],
+ kibanaIndexPatterns: [
+ mockAPMTransactionIndexPattern,
+ mockAPMTracesDataStreamIndexPattern,
+ mockFilebeatIndexPattern,
+ ],
siemDefaultIndices,
});
expect(matchingIndexPatterns).toEqual([
mockAPMTransactionIndexPattern,
+ mockAPMTracesDataStreamIndexPattern,
mockFilebeatIndexPattern,
]);
});
diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/map_config.ts b/x-pack/plugins/security_solution/public/network/components/embeddables/map_config.ts
index f4af4dd3b25f2b..ecbb80123e07ec 100644
--- a/x-pack/plugins/security_solution/public/network/components/embeddables/map_config.ts
+++ b/x-pack/plugins/security_solution/public/network/components/embeddables/map_config.ts
@@ -61,6 +61,21 @@ export const SUM_OF_DESTINATION_BYTES = 'sum_of_destination.bytes';
export const SUM_OF_CLIENT_BYTES = 'sum_of_client.bytes';
export const SUM_OF_SERVER_BYTES = 'sum_of_server.bytes';
+const APM_LAYER_FIELD_MAPPING = {
+ source: {
+ metricField: 'client.bytes',
+ geoField: 'client.geo.location',
+ tooltipProperties: Object.keys(clientFieldMappings),
+ label: i18n.CLIENT_LAYER,
+ },
+ destination: {
+ metricField: 'server.bytes',
+ geoField: 'server.geo.location',
+ tooltipProperties: Object.keys(serverFieldMappings),
+ label: i18n.SERVER_LAYER,
+ },
+};
+
// Mapping to fields for creating specific layers for a given index pattern
// e.g. The apm-* index pattern needs layers for client/server instead of source/destination
export const lmc: LayerMappingCollection = {
@@ -78,20 +93,8 @@ export const lmc: LayerMappingCollection = {
label: i18n.DESTINATION_LAYER,
},
},
- 'apm-*': {
- source: {
- metricField: 'client.bytes',
- geoField: 'client.geo.location',
- tooltipProperties: Object.keys(clientFieldMappings),
- label: i18n.CLIENT_LAYER,
- },
- destination: {
- metricField: 'server.bytes',
- geoField: 'server.geo.location',
- tooltipProperties: Object.keys(serverFieldMappings),
- label: i18n.SERVER_LAYER,
- },
- },
+ 'apm-*': APM_LAYER_FIELD_MAPPING,
+ 'traces-apm*,logs-apm*,metrics-apm*,apm-*': APM_LAYER_FIELD_MAPPING,
};
/**
diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.tsx
index 21a4beca72f3b2..1600356882c36b 100644
--- a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.tsx
@@ -21,11 +21,11 @@ export const CtiDisabledModuleComponent = () => {
const danger = useMemo(
() => (
+
{i18n.DANGER_BUTTON}
}
diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_inner_panel.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_inner_panel.tsx
index 08bf0a432f9bb8..ddff78608dfb02 100644
--- a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_inner_panel.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_inner_panel.tsx
@@ -17,13 +17,9 @@ const ButtonContainer = styled(EuiFlexGroup)`
padding: ${({ theme }) => theme.eui.paddingSizes.s};
`;
-const Title = styled(EuiText)<{ textcolor: 'primary' | 'warning' | 'danger' }>`
+const Title = styled(EuiText)<{ textcolor: 'primary' | 'warning' }>`
color: ${({ theme, textcolor }) =>
- textcolor === 'primary'
- ? theme.eui.euiColorPrimary
- : textcolor === 'warning'
- ? theme.eui.euiColorWarningText
- : theme.eui.euiColorDangerText};
+ textcolor === 'primary' ? theme.eui.euiColorPrimary : theme.eui.euiColorWarningText};
margin-bottom: ${({ theme }) => theme.eui.paddingSizes.m};
`;
@@ -40,12 +36,12 @@ export const CtiInnerPanel = ({
body,
button,
}: {
- color: 'primary' | 'warning' | 'danger';
+ color: 'primary' | 'warning';
title: string;
body: string;
button?: JSX.Element;
}) => {
- const iconType = color === 'primary' ? 'iInCircle' : color === 'warning' ? 'help' : 'alert';
+ const iconType = color === 'primary' ? 'iInCircle' : 'help';
return (
diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_no_events.test.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_no_events.test.tsx
index 5e1697279dd4c7..f00e1053e80822 100644
--- a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_no_events.test.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_no_events.test.tsx
@@ -69,7 +69,7 @@ describe('CtiNoEvents', () => {
);
expect(wrapper.find('[data-test-subj="cti-total-event-count"]').text()).toEqual(
- 'Showing: 0 events'
+ 'Showing: 0 indicators'
);
});
});
diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_with_events.test.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_with_events.test.tsx
index 3b03b9c418a1cd..fac05bb72df38f 100644
--- a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_with_events.test.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_with_events.test.tsx
@@ -51,7 +51,7 @@ describe('CtiWithEvents', () => {
);
expect(wrapper.find('[data-test-subj="cti-total-event-count"]').text()).toEqual(
- `Showing: ${mockCtiWithEventsProps.totalCount} events`
+ `Showing: ${mockCtiWithEventsProps.totalCount} indicators`
);
});
});
diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.test.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.test.tsx
index 59ee1e5447ba30..f0e3bcaaec6e02 100644
--- a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.test.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.test.tsx
@@ -141,7 +141,7 @@ describe('ThreatIntelPanelView', () => {
);
expect(wrapper.find('[data-test-subj="cti-total-event-count"]').text()).toEqual(
- `Showing: ${mockThreatIntelPanelViewProps.totalEventCount} events`
+ `Showing: ${mockThreatIntelPanelViewProps.totalEventCount} indicators`
);
});
});
diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.tsx
index b34f6e657d39a9..babbd5d13224f1 100644
--- a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.tsx
@@ -25,6 +25,7 @@ import { CtiListItem } from '../../containers/overview_cti_links/helpers';
import { useKibana } from '../../../common/lib/kibana';
import { CtiInnerPanel } from './cti_inner_panel';
import * as i18n from './translations';
+import { shortenCountIntoString } from '../../../common/utils/shorten_count_into_string';
const DashboardLink = styled.li`
margin: 0 ${({ theme }) => theme.eui.paddingSizes.s} 0 ${({ theme }) => theme.eui.paddingSizes.m};
@@ -84,7 +85,7 @@ export const ThreatIntelPanelView: React.FC = ({
() => (
@@ -159,10 +160,10 @@ export const ThreatIntelPanelView: React.FC = ({
alignItems="center"
justifyContent="flexEnd"
>
-
- {count}
+
+ {shortenCountIntoString(count)}
-
+
{path ? (
{linkCopy}
diff --git a/x-pack/plugins/security_solution/public/overview/containers/overview_cti_links/index.tsx b/x-pack/plugins/security_solution/public/overview/containers/overview_cti_links/index.tsx
index b7f919dc97013d..8839aff7dc33d2 100644
--- a/x-pack/plugins/security_solution/public/overview/containers/overview_cti_links/index.tsx
+++ b/x-pack/plugins/security_solution/public/overview/containers/overview_cti_links/index.tsx
@@ -74,8 +74,8 @@ export const useCtiDashboardLinks = (
})
)
);
- const items = DashboardsSO.savedObjects?.reduce(
- (acc: CtiListItem[], dashboardSO, i) => {
+ const items = DashboardsSO.savedObjects
+ ?.reduce((acc: CtiListItem[], dashboardSO, i) => {
const item = createLinkFromDashboardSO(
dashboardSO,
eventCountsByDataset,
@@ -87,9 +87,8 @@ export const useCtiDashboardLinks = (
acc.push(item);
}
return acc;
- },
- []
- );
+ }, [])
+ .sort((a, b) => (a.title > b.title ? 1 : -1));
setListItems(items);
} else {
handleDisabledPlugin();
diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.tsx
index 3c9d9161a7a483..2afb2af01406d4 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.tsx
@@ -26,7 +26,7 @@ interface FlyoutPaneComponentProps {
const StyledEuiFlyout = styled(EuiFlyout)`
animation: none;
min-width: 150px;
- z-index: ${({ theme }) => theme.eui.euiZLevel6};
+ z-index: ${({ theme }) => theme.eui.euiZLevel4};
`;
const FlyoutPaneComponent: React.FC = ({
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap
index d484a76940d5f4..b008f95285f236 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap
@@ -367,6 +367,7 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = `
"format": "",
"indexes": Array [
"apm-*-transaction*",
+ "traces-apm*",
"auditbeat-*",
"endgame-*",
"filebeat-*",
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/agent_statuses.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/agent_statuses.tsx
index 2c88b305c7d05f..dac10f46487841 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/agent_statuses.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/agent_statuses.tsx
@@ -6,11 +6,12 @@
*/
import React from 'react';
-import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
import { DefaultDraggable } from '../../../../../common/components/draggables';
import { EndpointHostIsolationStatus } from '../../../../../common/components/endpoint/host_isolation';
import { useHostIsolationStatus } from '../../../../../detections/containers/detection_engine/alerts/use_host_isolation_status';
import { AgentStatus } from '../../../../../common/components/endpoint/agent_status';
+import { EMPTY_STATUS } from './translations';
export const AgentStatuses = React.memo(
({
@@ -33,16 +34,22 @@ export const AgentStatuses = React.memo(
const isolationFieldName = 'host.isolation';
return (
-
-
-
-
-
+ {agentStatus !== undefined ? (
+
+
+
+
+
+ ) : (
+
+ {EMPTY_STATUS}
+
+ )}
{
wrapper.find('[data-test-subj="timelineSizeRowPopover"] button').first().simulate('click');
expect(wrapper.find('[data-test-subj="timelinePickSizeRow"]').exists()).toBeTruthy();
});
+
+ test('it renders last updated when updated at is > 0', () => {
+ const wrapper = mount(
+
+
+
+ );
+
+ expect(wrapper.find('[data-test-subj="fixed-width-last-updated"]').exists()).toBeTruthy();
+ });
+
+ test('it does NOT render last updated when updated at is 0', () => {
+ const wrapper = mount(
+
+
+
+ );
+
+ expect(wrapper.find('[data-test-subj="fixed-width-last-updated"]').exists()).toBeFalsy();
+ });
});
describe('Events', () => {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/index.tsx
index b71cbb4c082eff..2a253087567a74 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/index.tsx
@@ -45,11 +45,11 @@ const FixedWidthLastUpdatedContainer = React.memo isCompactFooter(width), [width]);
- return (
+ return updatedAt > 0 ? (
{timelines.getLastUpdated({ updatedAt, compact })}
- );
+ ) : null;
}
);
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_role.json
index 6c9b4e2cba49c6..82ef8cc6687b44 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_role.json
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_role.json
@@ -8,6 +8,7 @@
".lists*",
".items*",
"apm-*-transaction*",
+ "traces-apm*",
"auditbeat-*",
"endgame-*",
"filebeat-*",
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_role.json
index 119fe5421c86c5..ba9adfda82beaa 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_role.json
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_role.json
@@ -5,6 +5,7 @@
{
"names": [
"apm-*-transaction*",
+ "traces-apm*",
"auditbeat-*",
"endgame-*",
"filebeat-*",
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_role.json
index 17dbd90d179253..73a9559389b4e5 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_role.json
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_role.json
@@ -9,6 +9,7 @@
{
"names": [
"apm-*-transaction*",
+ "traces-apm*",
"auditbeat-*",
"endgame-*",
"filebeat-*",
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_role.json
index 0db8359c577640..bb606616d1bd53 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_role.json
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_role.json
@@ -5,6 +5,7 @@
{
"names": [
"apm-*-transaction*",
+ "traces-apm*",
"auditbeat-*",
"endgame-*",
"filebeat-*",
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_role.json
index 6962701ae5be35..92a62034afcefc 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_role.json
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_role.json
@@ -5,6 +5,7 @@
{
"names": [
"apm-*-transaction*",
+ "traces-apm*",
"auditbeat-*",
"endgame-*",
"filebeat-*",
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_role.json
index 07827069dbc739..be082e380211a0 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_role.json
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_role.json
@@ -6,6 +6,7 @@
{
"names": [
"apm-*-transaction*",
+ "traces-apm*",
"auditbeat-*",
"endgame-*",
"filebeat-*",
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_role.json
index f554c916c6684d..f9e069f174a91c 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_role.json
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_role.json
@@ -8,6 +8,7 @@
".lists*",
".items*",
"apm-*-transaction*",
+ "traces-apm*",
"auditbeat-*",
"endgame-*",
"filebeat-*",
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/rules/patches/simplest_updated_name.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/rules/patches/simplest_updated_name.json
index bec88bcb0e30e7..3e5a5c274f1baa 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/rules/patches/simplest_updated_name.json
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/rules/patches/simplest_updated_name.json
@@ -8,6 +8,7 @@
"from": "now-360s",
"index": [
"apm-*-transaction*",
+ "traces-apm*",
"auditbeat-*",
"endgame-*",
"filebeat-*",
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/rules/queries/query_with_mappings.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/rules/queries/query_with_mappings.json
index f0d7cb4ec914b4..2508c9a6a3e374 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/rules/queries/query_with_mappings.json
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/rules/queries/query_with_mappings.json
@@ -3,6 +3,7 @@
"enabled": false,
"index": [
"apm-*-transaction*",
+ "traces-apm*",
"auditbeat-*",
"endgame-*",
"filebeat-*",
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/rules/test_cases/queries/action_without_meta.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/rules/test_cases/queries/action_without_meta.json
index 6569a641de3a2c..f0ddced46d52e0 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/rules/test_cases/queries/action_without_meta.json
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/rules/test_cases/queries/action_without_meta.json
@@ -2,6 +2,7 @@
"type": "query",
"index": [
"apm-*-transaction*",
+ "traces-apm*",
"auditbeat-*",
"endgame-*",
"filebeat-*",
diff --git a/x-pack/plugins/security_solution/server/lib/source_status/elasticsearch_adapter.ts b/x-pack/plugins/security_solution/server/lib/source_status/elasticsearch_adapter.ts
index b2058a91c0413b..3da0c1675e81eb 100644
--- a/x-pack/plugins/security_solution/server/lib/source_status/elasticsearch_adapter.ts
+++ b/x-pack/plugins/security_solution/server/lib/source_status/elasticsearch_adapter.ts
@@ -12,6 +12,7 @@ import { ApmServiceNameAgg } from './types';
import { ENDPOINT_METADATA_INDEX } from '../../../common/constants';
const APM_INDEX_NAME = 'apm-*-transaction*';
+const APM_DATA_STREAM = 'traces-apm*';
export class ElasticsearchSourceStatusAdapter implements SourceStatusAdapter {
constructor(private readonly framework: FrameworkAdapter) {}
@@ -23,7 +24,9 @@ export class ElasticsearchSourceStatusAdapter implements SourceStatusAdapter {
// Add endpoint metadata index to indices to check
indexNames.push(ENDPOINT_METADATA_INDEX);
// Remove APM index if exists, and only query if length > 0 in case it's the only index provided
- const nonApmIndexNames = indexNames.filter((name) => name !== APM_INDEX_NAME);
+ const nonApmIndexNames = indexNames.filter(
+ (name) => name !== APM_INDEX_NAME && name !== APM_DATA_STREAM
+ );
const indexCheckResponse = await (nonApmIndexNames.length > 0
? this.framework.callWithRequest(request, 'search', {
index: nonApmIndexNames,
@@ -39,7 +42,8 @@ export class ElasticsearchSourceStatusAdapter implements SourceStatusAdapter {
// Note: Additional check necessary for APM-specific index. For details see: https://github.com/elastic/kibana/issues/56363
// Only verify if APM data exists if indexNames includes `apm-*-transaction*` (default included apm index)
- const includesApmIndex = indexNames.includes(APM_INDEX_NAME);
+ const includesApmIndex =
+ indexNames.includes(APM_INDEX_NAME) || indexNames.includes(APM_DATA_STREAM);
const hasApmDataResponse = await (includesApmIndex
? this.framework.callWithRequest<{}, ApmServiceNameAgg>(
request,
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/__mocks__/index.ts
index b6a5435a0e0461..0369f182a4c753 100644
--- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/__mocks__/index.ts
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/__mocks__/index.ts
@@ -18,6 +18,7 @@ import {
export const mockOptions: HostsRequestOptions = {
defaultIndex: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -613,6 +614,7 @@ export const formattedSearchStrategyResponse = {
allowNoIndices: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -822,6 +824,7 @@ export const expectedDsl = {
ignoreUnavailable: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/__mocks__/index.ts
index f29bb58da2f790..1dd3dc8ee4cff5 100644
--- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/__mocks__/index.ts
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/__mocks__/index.ts
@@ -17,6 +17,7 @@ import {
export const mockOptions: HostAuthenticationsRequestOptions = {
defaultIndex: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -2151,6 +2152,7 @@ export const formattedSearchStrategyResponse = {
allowNoIndices: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -2372,6 +2374,7 @@ export const expectedDsl = {
allowNoIndices: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/__mocks__/index.ts
index 9dfff5e11715d0..cc97a5f0cacefa 100644
--- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/__mocks__/index.ts
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/__mocks__/index.ts
@@ -17,6 +17,7 @@ import {
export const mockOptions: HostDetailsRequestOptions = {
defaultIndex: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -1303,6 +1304,7 @@ export const formattedSearchStrategyResponse = {
allowNoIndices: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -1416,6 +1418,7 @@ export const expectedDsl = {
allowNoIndices: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/__mocks__/index.ts
index b492bf57f94a66..443e7e96a3c7f8 100644
--- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/__mocks__/index.ts
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/__mocks__/index.ts
@@ -14,6 +14,7 @@ import {
export const mockOptions: HostFirstLastSeenRequestOptions = {
defaultIndex: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -126,6 +127,7 @@ export const formattedSearchStrategyFirstResponse = {
allowNoIndices: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -191,6 +193,7 @@ export const formattedSearchStrategyLastResponse = {
allowNoIndices: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -225,6 +228,7 @@ export const expectedDsl = {
allowNoIndices: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/__mocks__/index.ts
index 987754420430d6..2b4e4b8291401a 100644
--- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/__mocks__/index.ts
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/__mocks__/index.ts
@@ -15,6 +15,7 @@ import {
export const mockOptions: HostOverviewRequestOptions = {
defaultIndex: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -119,6 +120,7 @@ export const formattedSearchStrategyResponse = {
allowNoIndices: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -331,6 +333,7 @@ export const expectedDsl = {
allowNoIndices: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/__mocks__/index.ts
index 258e72a5e9b8eb..0ad976a0f498c2 100644
--- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/__mocks__/index.ts
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/__mocks__/index.ts
@@ -10,6 +10,7 @@ import { HostsQueries, SortField } from '../../../../../../../common/search_stra
export const mockOptions = {
defaultIndex: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -4302,6 +4303,7 @@ export const formattedSearchStrategyResponse = {
allowNoIndices: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -4436,6 +4438,7 @@ export const expectedDsl = {
allowNoIndices: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/__mocks__/index.ts
index c33ca75aa26e12..7f36e3551e5bee 100644
--- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/__mocks__/index.ts
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/__mocks__/index.ts
@@ -33,6 +33,7 @@ export const formattedAlertsSearchStrategyResponse: MatrixHistogramStrategyRespo
{
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -166,6 +167,7 @@ export const expectedDsl = {
ignoreUnavailable: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -199,6 +201,7 @@ export const formattedAnomaliesSearchStrategyResponse: MatrixHistogramStrategyRe
{
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -381,6 +384,7 @@ export const formattedAuthenticationsSearchStrategyResponse: MatrixHistogramStra
{
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -947,6 +951,7 @@ export const formattedEventsSearchStrategyResponse: MatrixHistogramStrategyRespo
{
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -1925,6 +1930,7 @@ export const formattedDnsSearchStrategyResponse: MatrixHistogramStrategyResponse
allowNoIndices: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/alerts/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/alerts/__mocks__/index.ts
index 86006c31554477..82531f35b09abc 100644
--- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/alerts/__mocks__/index.ts
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/alerts/__mocks__/index.ts
@@ -10,6 +10,7 @@ import { MatrixHistogramType } from '../../../../../../../common/search_strategy
export const mockOptions = {
defaultIndex: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -27,6 +28,7 @@ export const mockOptions = {
export const expectedDsl = {
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/anomalies/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/anomalies/__mocks__/index.ts
index 81da78a132084a..ab76d54dee11ff 100644
--- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/anomalies/__mocks__/index.ts
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/anomalies/__mocks__/index.ts
@@ -10,6 +10,7 @@ import { MatrixHistogramType } from '../../../../../../../common/search_strategy
export const mockOptions = {
defaultIndex: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -27,6 +28,7 @@ export const mockOptions = {
export const expectedDsl = {
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/authentications/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/authentications/__mocks__/index.ts
index 5cf667a0085fa7..1fd7b85242df64 100644
--- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/authentications/__mocks__/index.ts
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/authentications/__mocks__/index.ts
@@ -10,6 +10,7 @@ import { MatrixHistogramType } from '../../../../../../../common/search_strategy
export const mockOptions = {
defaultIndex: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -26,6 +27,7 @@ export const mockOptions = {
export const expectedDsl = {
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/dns/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/dns/__mocks__/index.ts
index 9b8dfb139d9f40..4d97fba3cb80cf 100644
--- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/dns/__mocks__/index.ts
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/dns/__mocks__/index.ts
@@ -10,6 +10,7 @@ import { MatrixHistogramType } from '../../../../../../../common/search_strategy
export const mockOptions = {
defaultIndex: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -28,6 +29,7 @@ export const expectedDsl = {
allowNoIndices: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/events/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/events/__mocks__/index.ts
index c361db38a6caac..5dab2bcd5cf9de 100644
--- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/events/__mocks__/index.ts
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/events/__mocks__/index.ts
@@ -14,6 +14,7 @@ import {
export const mockOptions: MatrixHistogramRequestOptions = {
defaultIndex: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -31,6 +32,7 @@ export const mockOptions: MatrixHistogramRequestOptions = {
export const expectedDsl = {
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -85,6 +87,7 @@ export const expectedDsl = {
export const expectedThresholdDsl = {
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -141,6 +144,7 @@ export const expectedThresholdDsl = {
export const expectedThresholdMissingFieldDsl = {
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -243,6 +247,7 @@ export const expectedThresholdWithCardinalityDsl = {
ignoreUnavailable: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -256,6 +261,7 @@ export const expectedThresholdWithCardinalityDsl = {
export const expectedThresholdWithGroupFieldsAndCardinalityDsl = {
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -362,6 +368,7 @@ export const expectedThresholdGroupWithCardinalityDsl = {
ignoreUnavailable: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -375,6 +382,7 @@ export const expectedThresholdGroupWithCardinalityDsl = {
export const expectedIpIncludingMissingDataDsl = {
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -437,6 +445,7 @@ export const expectedIpIncludingMissingDataDsl = {
export const expectedIpNotIncludingMissingDataDsl = {
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/__mocks__/index.ts
index fb11069f9c8346..7f71906bcaa97f 100644
--- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/__mocks__/index.ts
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/__mocks__/index.ts
@@ -15,6 +15,7 @@ import {
export const mockOptions: NetworkDetailsRequestOptions = {
defaultIndex: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -306,6 +307,7 @@ export const formattedSearchStrategyResponse = {
allowNoIndices: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -447,6 +449,7 @@ export const expectedDsl = {
allowNoIndices: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/__mocks__/index.ts
index 3252a7c249d722..cc01450e5bec56 100644
--- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/__mocks__/index.ts
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/__mocks__/index.ts
@@ -17,6 +17,7 @@ import {
export const mockOptions: NetworkDnsRequestOptions = {
defaultIndex: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -133,6 +134,7 @@ export const formattedSearchStrategyResponse = {
allowNoIndices: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -204,6 +206,7 @@ export const expectedDsl = {
allowNoIndices: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/__mocks__/index.ts
index aaf29f07537b54..b34027338e2ba9 100644
--- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/__mocks__/index.ts
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/__mocks__/index.ts
@@ -18,6 +18,7 @@ import {
export const mockOptions: NetworkHttpRequestOptions = {
defaultIndex: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -621,6 +622,7 @@ export const formattedSearchStrategyResponse = {
allowNoIndices: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -678,6 +680,7 @@ export const expectedDsl = {
allowNoIndices: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/__mocks__/index.ts
index fcb30be7a403d6..74b201e9a2294b 100644
--- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/__mocks__/index.ts
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/overview/__mocks__/index.ts
@@ -15,6 +15,7 @@ import {
export const mockOptions: NetworkOverviewRequestOptions = {
defaultIndex: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -103,6 +104,7 @@ export const formattedSearchStrategyResponse = {
allowNoIndices: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -208,6 +210,7 @@ export const expectedDsl = {
ignoreUnavailable: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/__mocks__/index.ts
index 16750acc5adeed..8616a2ef14856f 100644
--- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/__mocks__/index.ts
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/__mocks__/index.ts
@@ -18,6 +18,7 @@ import {
export const mockOptions: NetworkTlsRequestOptions = {
defaultIndex: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -61,6 +62,7 @@ export const formattedSearchStrategyResponse = {
allowNoIndices: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -115,6 +117,7 @@ export const expectedDsl = {
allowNoIndices: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_countries/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_countries/__mocks__/index.ts
index 9f95dbe9c1c4f2..ba5db90df82452 100644
--- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_countries/__mocks__/index.ts
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_countries/__mocks__/index.ts
@@ -18,6 +18,7 @@ import {
export const mockOptions: NetworkTopCountriesRequestOptions = {
defaultIndex: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -60,6 +61,7 @@ export const formattedSearchStrategyResponse = {
allowNoIndices: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -119,6 +121,7 @@ export const expectedDsl = {
allowNoIndices: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_n_flow/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_n_flow/__mocks__/index.ts
index c815ed22f2b548..e881a9ef93949c 100644
--- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_n_flow/__mocks__/index.ts
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/top_n_flow/__mocks__/index.ts
@@ -19,6 +19,7 @@ import {
export const mockOptions: NetworkTopNFlowRequestOptions = {
defaultIndex: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -812,6 +813,7 @@ export const formattedSearchStrategyResponse: NetworkTopNFlowStrategyResponse =
allowNoIndices: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -879,6 +881,7 @@ export const expectedDsl = {
allowNoIndices: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/users/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/users/__mocks__/index.ts
index 3837afabe57993..686730dbe79275 100644
--- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/users/__mocks__/index.ts
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/users/__mocks__/index.ts
@@ -18,6 +18,7 @@ import {
export const mockOptions: NetworkUsersRequestOptions = {
defaultIndex: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -121,6 +122,7 @@ export const formattedSearchStrategyResponse = {
allowNoIndices: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -210,6 +212,7 @@ export const expectedDsl = {
ignoreUnavailable: true,
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
diff --git a/x-pack/plugins/spaces/public/nav_control/nav_control_popover.tsx b/x-pack/plugins/spaces/public/nav_control/nav_control_popover.tsx
index 6f743b3a308e25..1653506c550f6f 100644
--- a/x-pack/plugins/spaces/public/nav_control/nav_control_popover.tsx
+++ b/x-pack/plugins/spaces/public/nav_control/nav_control_popover.tsx
@@ -140,7 +140,7 @@ export class NavControlPopover extends Component {
}
return this.getButton(
- }>
+ }>
,
(activeSpace as Space).name
diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/__snapshots__/index.test.tsx.snap b/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/__snapshots__/index.test.tsx.snap
index 9ee08bcd966f35..e6e56818bcc846 100644
--- a/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/__snapshots__/index.test.tsx.snap
+++ b/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/__snapshots__/index.test.tsx.snap
@@ -367,6 +367,7 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = `
"format": "",
"indexes": Array [
"apm-*-transaction*",
+ "traces-apm*",
"auditbeat-*",
"endgame-*",
"filebeat-*",
diff --git a/x-pack/plugins/timelines/public/mock/browser_fields.ts b/x-pack/plugins/timelines/public/mock/browser_fields.ts
index 1581175e329043..6ab06e1be018a6 100644
--- a/x-pack/plugins/timelines/public/mock/browser_fields.ts
+++ b/x-pack/plugins/timelines/public/mock/browser_fields.ts
@@ -10,6 +10,7 @@ import type { BrowserFields } from '../../common/search_strategy/index_fields';
const DEFAULT_INDEX_PATTERN = [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
diff --git a/x-pack/plugins/timelines/public/mock/global_state.ts b/x-pack/plugins/timelines/public/mock/global_state.ts
index bb7bee3d1552ad..f7d3297738373d 100644
--- a/x-pack/plugins/timelines/public/mock/global_state.ts
+++ b/x-pack/plugins/timelines/public/mock/global_state.ts
@@ -24,6 +24,7 @@ export const mockGlobalState: TimelineState = {
id: 'test',
indexNames: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
diff --git a/x-pack/plugins/timelines/server/search_strategy/index_fields/index.test.ts b/x-pack/plugins/timelines/server/search_strategy/index_fields/index.test.ts
index f6d78f2f1259fd..cb2b097d787018 100644
--- a/x-pack/plugins/timelines/server/search_strategy/index_fields/index.test.ts
+++ b/x-pack/plugins/timelines/server/search_strategy/index_fields/index.test.ts
@@ -845,7 +845,7 @@ describe('Fields Provider', () => {
});
it('should search apm index fields', async () => {
- const indices = ['apm-*-transaction*'];
+ const indices = ['apm-*-transaction*', 'traces-apm*'];
const request = {
indices,
onlyCheckIfIndicesExist: false,
@@ -861,13 +861,13 @@ describe('Fields Provider', () => {
});
it('should check apm index exists with data', async () => {
- const indices = ['apm-*-transaction*'];
+ const indices = ['apm-*-transaction*', 'traces-apm*'];
const request = {
indices,
onlyCheckIfIndicesExist: true,
};
- esClientSearchMock.mockResolvedValueOnce({
+ esClientSearchMock.mockResolvedValue({
body: { hits: { total: { value: 1 } } },
});
const response = await requestIndexFieldSearch(request, deps, beatFields);
@@ -876,6 +876,10 @@ describe('Fields Provider', () => {
index: indices[0],
body: { query: { match_all: {} }, size: 0 },
});
+ expect(esClientSearchMock).toHaveBeenCalledWith({
+ index: indices[1],
+ body: { query: { match_all: {} }, size: 0 },
+ });
expect(getFieldsForWildcardMock).not.toHaveBeenCalled();
expect(response.indexFields).toHaveLength(0);
@@ -883,13 +887,13 @@ describe('Fields Provider', () => {
});
it('should check apm index exists with no data', async () => {
- const indices = ['apm-*-transaction*'];
+ const indices = ['apm-*-transaction*', 'traces-apm*'];
const request = {
indices,
onlyCheckIfIndicesExist: true,
};
- esClientSearchMock.mockResolvedValueOnce({
+ esClientSearchMock.mockResolvedValue({
body: { hits: { total: { value: 0 } } },
});
@@ -899,6 +903,10 @@ describe('Fields Provider', () => {
index: indices[0],
body: { query: { match_all: {} }, size: 0 },
});
+ expect(esClientSearchMock).toHaveBeenCalledWith({
+ index: indices[1],
+ body: { query: { match_all: {} }, size: 0 },
+ });
expect(getFieldsForWildcardMock).not.toHaveBeenCalled();
expect(response.indexFields).toHaveLength(0);
diff --git a/x-pack/plugins/timelines/server/search_strategy/index_fields/index.ts b/x-pack/plugins/timelines/server/search_strategy/index_fields/index.ts
index d100e8db21493f..b6cf4af1561c3f 100644
--- a/x-pack/plugins/timelines/server/search_strategy/index_fields/index.ts
+++ b/x-pack/plugins/timelines/server/search_strategy/index_fields/index.ts
@@ -25,6 +25,7 @@ import {
} from '../../../common/search_strategy/index_fields';
const apmIndexPattern = 'apm-*-transaction*';
+const apmDataStreamsPattern = 'traces-apm*';
export const indexFieldsProvider = (): ISearchStrategy<
IndexFieldsStrategyRequest,
@@ -51,7 +52,10 @@ export const requestIndexFieldSearch = async (
const responsesIndexFields = await Promise.all(
dedupeIndices
.map(async (index) => {
- if (request.onlyCheckIfIndicesExist && index.includes(apmIndexPattern)) {
+ if (
+ request.onlyCheckIfIndicesExist &&
+ (index.includes(apmIndexPattern) || index.includes(apmDataStreamsPattern))
+ ) {
// for apm index pattern check also if there's data https://github.com/elastic/kibana/issues/90661
const searchResponse = await esClient.asCurrentUser.search({
index,
diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/helpers.test.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/helpers.test.ts
index 9197917ad764f8..c9be6582015f1c 100644
--- a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/helpers.test.ts
+++ b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/helpers.test.ts
@@ -141,7 +141,7 @@ describe('#formatTimelineData', () => {
parent: {
depth: 0,
index:
- 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*',
+ 'apm-*-transaction*,traces-apm*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*',
id: '0268af90-d8da-576a-9747-2a191519416a',
type: 'event',
},
@@ -180,6 +180,7 @@ describe('#formatTimelineData', () => {
query: '_id :*',
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -246,7 +247,7 @@ describe('#formatTimelineData', () => {
{
depth: 0,
index:
- 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*',
+ 'apm-*-transaction*,traces-apm*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*',
id: '0268af90-d8da-576a-9747-2a191519416a',
type: 'event',
},
@@ -255,7 +256,7 @@ describe('#formatTimelineData', () => {
{
depth: 0,
index:
- 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*',
+ 'apm-*-transaction*,traces-apm*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*',
id: '0268af90-d8da-576a-9747-2a191519416a',
type: 'event',
},
@@ -279,6 +280,7 @@ describe('#formatTimelineData', () => {
'signal.rule.version': ['1'],
'signal.rule.index': [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
@@ -332,6 +334,7 @@ describe('#formatTimelineData', () => {
id: ['696c24e0-526d-11eb-836c-e1620268b945'],
index: [
'apm-*-transaction*',
+ 'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
diff --git a/x-pack/plugins/uptime/public/components/common/header/action_menu_content.tsx b/x-pack/plugins/uptime/public/components/common/header/action_menu_content.tsx
index 479a512b7238a1..9f00dd2e8f0619 100644
--- a/x-pack/plugins/uptime/public/components/common/header/action_menu_content.tsx
+++ b/x-pack/plugins/uptime/public/components/common/header/action_menu_content.tsx
@@ -6,7 +6,7 @@
*/
import React from 'react';
-import { EuiButtonEmpty, EuiHeaderLinks, EuiHeaderSectionItem, EuiToolTip } from '@elastic/eui';
+import { EuiHeaderLinks, EuiToolTip, EuiHeaderLink } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { useHistory } from 'react-router-dom';
@@ -51,53 +51,45 @@ export function ActionMenuContent(): React.ReactElement {
return (
-
-
+
+
+
+
+
+ {ANALYZE_MESSAGE}
}>
+
-
-
-
-
-
-
-
- {ANALYZE_MESSAGE}}>
-
- {ANALYZE_DATA}
-
-
-
-
-
- {ADD_DATA_LABEL}
-
-
+ {ANALYZE_DATA}
+
+
+
+
+ {ADD_DATA_LABEL}
+
);
}
diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_title.test.tsx b/x-pack/plugins/uptime/public/components/monitor/monitor_title.test.tsx
index 4fd6335c3d3ca9..726ad235f7f49e 100644
--- a/x-pack/plugins/uptime/public/components/monitor/monitor_title.test.tsx
+++ b/x-pack/plugins/uptime/public/components/monitor/monitor_title.test.tsx
@@ -11,7 +11,7 @@ import { screen } from '@testing-library/react';
import { render } from '../../lib/helper/rtl_helpers';
import * as reactRouterDom from 'react-router-dom';
import { Ping } from '../../../common/runtime_types';
-import { MonitorPageTitle } from './monitor_title';
+import { MonitorPageTitle, MonitorPageTitleContent } from './monitor_title';
jest.mock('react-router-dom', () => {
const originalModule = jest.requireActual('react-router-dom');
@@ -77,11 +77,17 @@ describe('MonitorTitle component', () => {
});
it('renders the monitor heading and EnableMonitorAlert toggle', () => {
- render( , {
- state: { monitorStatus: { status: monitorStatusWithName, loading: false } },
- });
- expect(screen.getByRole('heading', { level: 1, name: monitorName })).toBeInTheDocument();
- expect(screen.getByTestId('uptimeDisplayDefineConnector')).toBeInTheDocument();
+ render(
+ <>
+
+
+ >,
+ {
+ state: { monitorStatus: { status: monitorStatusWithName, loading: false } },
+ }
+ );
+ expect(screen.getByText(monitorName));
+ expect(screen.getByRole('switch')).toBeInTheDocument();
});
it('renders the user provided monitorId when the name is not present', () => {
@@ -89,21 +95,24 @@ describe('MonitorTitle component', () => {
render( , {
state: { monitorStatus: { status: defaultMonitorStatus, loading: false } },
});
- expect(screen.getByRole('heading', { level: 1, name: defaultMonitorId })).toBeInTheDocument();
+ expect(screen.getByText(defaultMonitorId));
});
it('renders the url when the monitorId is auto generated and the monitor name is not present', () => {
mockReactRouterDomHooks({ useParamsResponse: { monitorId: autoGeneratedMonitorIdEncoded } });
- render( , {
- state: { monitorStatus: { status: defaultMonitorStatus, loading: false } },
- });
- expect(
- screen.getByRole('heading', { level: 1, name: defaultMonitorStatus.url?.full })
- ).toBeInTheDocument();
+ render(
+
+
+
,
+ {
+ state: { monitorStatus: { status: defaultMonitorStatus, loading: false } },
+ }
+ );
+ expect(screen.getByText(defaultMonitorStatus!.url!.full!));
});
it('renders beta disclaimer for synthetics monitors', () => {
- render( , {
+ render( , {
state: { monitorStatus: { status: defaultBrowserMonitorStatus, loading: false } },
});
const betaLink = screen.getByRole('link', {
diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx b/x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx
index 2112af06536695..aa68e2aa7fc4bc 100644
--- a/x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx
+++ b/x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx
@@ -5,15 +5,7 @@
* 2.0.
*/
-import {
- EuiBadge,
- EuiFlexGroup,
- EuiFlexItem,
- EuiSpacer,
- EuiTitle,
- EuiLink,
- EuiText,
-} from '@elastic/eui';
+import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiLink, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
import { useSelector } from 'react-redux';
@@ -40,18 +32,11 @@ const getPageTitle = (monitorId: string, selectedMonitor: Ping | null) => {
return monitorId;
};
-export const MonitorPageTitle: React.FC = () => {
+export const MonitorPageTitleContent: React.FC = () => {
const monitorId = useMonitorId();
-
const selectedMonitor = useSelector(monitorStatusSelector);
-
- const nameOrId = selectedMonitor?.monitor?.name || getPageTitle(monitorId, selectedMonitor);
-
const type = selectedMonitor?.monitor?.type;
const isBrowser = type === 'browser';
-
- useBreadcrumbs([{ text: nameOrId }]);
-
const renderMonitorType = (monitorType: string) => {
switch (monitorType) {
case 'http':
@@ -86,12 +71,13 @@ export const MonitorPageTitle: React.FC = () => {
return '';
}
};
-
return (
<>
-
- {nameOrId}
-
+
+
+
+
+
@@ -118,7 +104,18 @@ export const MonitorPageTitle: React.FC = () => {
)}
-
>
);
};
+
+export const MonitorPageTitle: React.FC = () => {
+ const monitorId = useMonitorId();
+
+ const selectedMonitor = useSelector(monitorStatusSelector);
+
+ const nameOrId = selectedMonitor?.monitor?.name || getPageTitle(monitorId, selectedMonitor);
+
+ useBreadcrumbs([{ text: nameOrId }]);
+
+ return {nameOrId} ;
+};
diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx
index 610107f406306a..c24ecd91838659 100644
--- a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx
+++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx
@@ -16,7 +16,7 @@ import { useUiSetting$ } from '../../../../../../../../src/plugins/kibana_react/
import { useMonitorBreadcrumb } from './use_monitor_breadcrumb';
import { ClientPluginsStart } from '../../../../apps/plugin';
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
-import { StepPageTitle } from './step_page_title';
+import { StepPageTitleContent } from './step_page_title';
import { StepPageNavigation } from './step_page_nav';
import { WaterfallChartContainer } from './waterfall/waterfall_chart_container';
@@ -78,10 +78,11 @@ export const StepDetailContainer: React.FC = ({ checkGroup, stepIndex })
return (
void;
handleNextStep: () => void;
}
-export const StepPageTitle = ({
- stepName,
+
+export const StepPageTitleContent = ({
stepIndex,
totalSteps,
handleNextStep,
@@ -29,11 +29,6 @@ export const StepPageTitle = ({
}: Props) => {
return (
-
-
- {stepName}
-
-
diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx
index 278958bd1987bb..22193fe4623d68 100644
--- a/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx
+++ b/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx
@@ -6,7 +6,7 @@
*/
import {
- EuiButtonEmpty,
+ EuiHeaderLink,
EuiContextMenu,
EuiContextMenuPanelDescriptor,
EuiContextMenuPanelItemDescriptor,
@@ -123,8 +123,7 @@ export const ToggleAlertFlyoutButtonComponent: React.FC = ({
return (
= ({
id="xpack.uptime.alerts.toggleAlertFlyoutButtonText"
defaultMessage="Alerts and rules"
/>
-
+
}
closePopover={() => setIsOpen(false)}
isOpen={isOpen}
diff --git a/x-pack/plugins/uptime/public/components/overview/synthetics_callout.test.tsx b/x-pack/plugins/uptime/public/components/overview/synthetics_callout.test.tsx
index 5f6f9d7a7207ed..ec9e5f958ec3a5 100644
--- a/x-pack/plugins/uptime/public/components/overview/synthetics_callout.test.tsx
+++ b/x-pack/plugins/uptime/public/components/overview/synthetics_callout.test.tsx
@@ -67,9 +67,6 @@ describe('SyntheticsCallout', () => {
-
`);
});
@@ -128,9 +125,6 @@ describe('SyntheticsCallout', () => {
-
`);
wrapper.find('EuiButton').simulate('click');
diff --git a/x-pack/plugins/uptime/public/components/overview/synthetics_callout.tsx b/x-pack/plugins/uptime/public/components/overview/synthetics_callout.tsx
index fa28e42d7d0c14..4e9c3256f15781 100644
--- a/x-pack/plugins/uptime/public/components/overview/synthetics_callout.tsx
+++ b/x-pack/plugins/uptime/public/components/overview/synthetics_callout.tsx
@@ -5,14 +5,7 @@
* 2.0.
*/
-import {
- EuiButton,
- EuiButtonEmpty,
- EuiCallOut,
- EuiFlexGroup,
- EuiFlexItem,
- EuiSpacer,
-} from '@elastic/eui';
+import { EuiButton, EuiButtonEmpty, EuiCallOut, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import React, { useState } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
@@ -74,7 +67,6 @@ export const SyntheticsCallout = () => {
-
>
);
};
diff --git a/x-pack/plugins/uptime/public/routes.tsx b/x-pack/plugins/uptime/public/routes.tsx
index e3c558cee2c320..2b0cc4dc5e5c2b 100644
--- a/x-pack/plugins/uptime/public/routes.tsx
+++ b/x-pack/plugins/uptime/public/routes.tsx
@@ -23,7 +23,7 @@ import { UptimePage, useUptimeTelemetry } from './hooks';
import { OverviewPageComponent } from './pages/overview';
import { SyntheticsCheckSteps } from './pages/synthetics/synthetics_checks';
import { ClientPluginsStart } from './apps/plugin';
-import { MonitorPageTitle } from './components/monitor/monitor_title';
+import { MonitorPageTitle, MonitorPageTitleContent } from './components/monitor/monitor_title';
import { UptimeDatePicker } from './components/common/uptime_date_picker';
import { useKibana } from '../../../../src/plugins/kibana_react/public';
import { CertRefreshBtn } from './components/certificates/cert_refresh_btn';
@@ -36,10 +36,16 @@ interface RouteProps {
dataTestSubj: string;
title: string;
telemetryId: UptimePage;
- pageHeader?: { pageTitle: string | JSX.Element; rightSideItems?: JSX.Element[] };
+ pageHeader?: {
+ children?: JSX.Element;
+ pageTitle: string | JSX.Element;
+ rightSideItems?: JSX.Element[];
+ };
}
-const baseTitle = 'Uptime - Kibana';
+const baseTitle = i18n.translate('xpack.uptime.routes.baseTitle', {
+ defaultMessage: 'Uptime - Kibana',
+});
export const MONITORING_OVERVIEW_LABEL = i18n.translate('xpack.uptime.overview.heading', {
defaultMessage: 'Monitors',
@@ -47,18 +53,25 @@ export const MONITORING_OVERVIEW_LABEL = i18n.translate('xpack.uptime.overview.h
const Routes: RouteProps[] = [
{
- title: `Monitor | ${baseTitle}`,
+ title: i18n.translate('xpack.uptime.monitorRoute.title', {
+ defaultMessage: 'Monitor | {baseTitle}',
+ values: { baseTitle },
+ }),
path: MONITOR_ROUTE,
component: MonitorPage,
dataTestSubj: 'uptimeMonitorPage',
telemetryId: UptimePage.Monitor,
pageHeader: {
+ children: ,
pageTitle: ,
rightSideItems: [ ],
},
},
{
- title: `Settings | ${baseTitle}`,
+ title: i18n.translate('xpack.uptime.settingsRoute.title', {
+ defaultMessage: `Settings | {baseTitle}`,
+ values: { baseTitle },
+ }),
path: SETTINGS_ROUTE,
component: SettingsPage,
dataTestSubj: 'uptimeSettingsPage',
@@ -70,7 +83,10 @@ const Routes: RouteProps[] = [
},
},
{
- title: `Certificates | ${baseTitle}`,
+ title: i18n.translate('xpack.uptime.certificatesRoute.title', {
+ defaultMessage: `Certificates | {baseTitle}`,
+ values: { baseTitle },
+ }),
path: CERTIFICATES_ROUTE,
component: CertificatesPage,
dataTestSubj: 'uptimeCertificatesPage',
@@ -81,7 +97,10 @@ const Routes: RouteProps[] = [
},
},
{
- title: baseTitle,
+ title: i18n.translate('xpack.uptime.stepDetailRoute.title', {
+ defaultMessage: 'Synthetics detail | {baseTitle}',
+ values: { baseTitle },
+ }),
path: STEP_DETAIL_ROUTE,
component: StepDetailPage,
dataTestSubj: 'uptimeStepDetailPage',
diff --git a/x-pack/plugins/uptime/server/lib/alerts/status_check.test.ts b/x-pack/plugins/uptime/server/lib/alerts/status_check.test.ts
index 29f2f0cca82bcd..743e9f6bc75ac9 100644
--- a/x-pack/plugins/uptime/server/lib/alerts/status_check.test.ts
+++ b/x-pack/plugins/uptime/server/lib/alerts/status_check.test.ts
@@ -4,7 +4,6 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-
import {
generateFilterDSL,
hasFilters,
@@ -62,7 +61,12 @@ const mockOptions = (
shouldCheckStatus: true,
},
services = alertsMock.createAlertServices(),
- state = {}
+ state = {},
+ rule = {
+ schedule: {
+ interval: '5m',
+ },
+ }
): any => {
services.scopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
services.scopedClusterClient.asCurrentUser = (jest.fn() as unknown) as any;
@@ -77,13 +81,16 @@ const mockOptions = (
params,
services,
state,
+ rule,
};
};
describe('status check alert', () => {
let toISOStringSpy: jest.SpyInstance;
+ const mockDate = new Date('2021-05-13T12:33:37.000Z');
beforeEach(() => {
toISOStringSpy = jest.spyOn(Date.prototype, 'toISOString');
+ Date.now = jest.fn().mockReturnValue(mockDate);
});
afterEach(() => {
@@ -108,10 +115,14 @@ describe('status check alert', () => {
"filters": undefined,
"locations": Array [],
"numTimes": 5,
- "timerange": Object {
+ "timespanRange": Object {
"from": "now-15m",
"to": "now",
},
+ "timestampRange": Object {
+ "from": 1620821917000,
+ "to": "now",
+ },
"uptimeEsClient": Object {
"baseESClient": [MockFunction],
"count": [Function],
@@ -163,10 +174,14 @@ describe('status check alert', () => {
"filters": undefined,
"locations": Array [],
"numTimes": 5,
- "timerange": Object {
+ "timespanRange": Object {
"from": "now-15m",
"to": "now",
},
+ "timestampRange": Object {
+ "from": 1620821917000,
+ "to": "now",
+ },
"uptimeEsClient": Object {
"baseESClient": [MockFunction],
"count": [Function],
@@ -476,10 +491,14 @@ describe('status check alert', () => {
},
"locations": Array [],
"numTimes": 3,
- "timerange": Object {
+ "timespanRange": Object {
"from": "now-15m",
"to": "now",
},
+ "timestampRange": Object {
+ "from": 1620821917000,
+ "to": "now",
+ },
"uptimeEsClient": Object {
"baseESClient": [MockFunction],
"count": [Function],
@@ -583,10 +602,14 @@ describe('status check alert', () => {
},
"locations": Array [],
"numTimes": 20,
- "timerange": Object {
+ "timespanRange": Object {
"from": "now-30h",
"to": "now",
},
+ "timestampRange": Object {
+ "from": 1620714817000,
+ "to": "now",
+ },
"uptimeEsClient": Object {
"baseESClient": [MockFunction],
"count": [Function],
@@ -900,6 +923,85 @@ describe('status check alert', () => {
});
});
+ it('generates timespan and @timestamp ranges appropriately', async () => {
+ const mockGetter = jest.fn();
+ mockGetter.mockReturnValue([]);
+ const { server, libs, plugins } = bootstrapDependencies({
+ getIndexPattern: jest.fn(),
+ getMonitorStatus: mockGetter,
+ });
+ const alert = statusCheckAlertFactory(server, libs, plugins);
+ const options = mockOptions({
+ numTimes: 20,
+ timerangeCount: 30,
+ timerangeUnit: 'h',
+ filters: {
+ 'monitor.type': ['http'],
+ 'observer.geo.name': [],
+ tags: [],
+ 'url.port': [],
+ },
+ search: 'url.full: *',
+ });
+ await alert.executor(options);
+
+ expect(mockGetter).toHaveBeenCalledTimes(1);
+ expect(mockGetter.mock.calls[0][0]).toEqual(
+ expect.objectContaining({
+ timespanRange: {
+ from: 'now-30h',
+ to: 'now',
+ },
+ timestampRange: {
+ from: mockDate.setHours(mockDate.getHours() - 54).valueOf(), // now minus the timerange (30h), plus an additional 24 hour buffer
+ to: 'now',
+ },
+ })
+ );
+ });
+
+ it('uses the larger of alert interval and timerange when defining timestampRange', async () => {
+ const mockGetter = jest.fn();
+ mockGetter.mockReturnValue([]);
+ const { server, libs, plugins } = bootstrapDependencies({
+ getIndexPattern: jest.fn(),
+ getMonitorStatus: mockGetter,
+ });
+ const alert = statusCheckAlertFactory(server, libs, plugins);
+ const options = mockOptions(
+ {
+ numTimes: 20,
+ timerangeCount: 30,
+ timerangeUnit: 'h',
+ filters: {
+ 'monitor.type': ['http'],
+ 'observer.geo.name': [],
+ tags: [],
+ 'url.port': [],
+ },
+ search: 'url.full: *',
+ },
+ undefined,
+ undefined,
+ { schedule: { interval: '60h' } }
+ );
+ await alert.executor(options);
+
+ expect(mockGetter).toHaveBeenCalledTimes(1);
+ expect(mockGetter.mock.calls[0][0]).toEqual(
+ expect.objectContaining({
+ timespanRange: {
+ from: 'now-30h',
+ to: 'now',
+ },
+ timestampRange: {
+ from: mockDate.setHours(mockDate.getHours() - 60).valueOf(), // 60h rule schedule interval is larger than 30h timerange, so use now - 60h to define timestamp range
+ to: 'now',
+ },
+ })
+ );
+ });
+
describe('hasFilters', () => {
it('returns false for undefined filters', () => {
expect(hasFilters()).toBe(false);
diff --git a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts
index 6f3e3303f6bdcf..364518bba720ad 100644
--- a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts
+++ b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts
@@ -4,7 +4,8 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-
+import datemath from '@elastic/datemath';
+import { min } from 'lodash';
import { schema } from '@kbn/config-schema';
import { i18n } from '@kbn/i18n';
import Mustache from 'mustache';
@@ -31,6 +32,34 @@ import { UMServerLibs, UptimeESClient } from '../lib';
export type ActionGroupIds = ActionGroupIdsOf;
+/**
+ * Returns the appropriate range for filtering the documents by `@timestamp`.
+ *
+ * We check monitor status by `monitor.timespan`, but need to first cut down on the number of documents
+ * searched by filtering by `@timestamp`. To ensure that we catch as many documents as possible which could
+ * likely contain a down monitor with a `monitor.timespan` in the given timerange, we create a filter
+ * range for `@timestamp` that is the greater of either: from now to now - timerange interval - 24 hours
+ * OR from now to now - rule interval
+ * @param ruleScheduleLookback - string representing now minus the interval at which the rule is ran
+ * @param timerangeLookback - string representing now minus the timerange configured by the user for checking down monitors
+ */
+export function getTimestampRange({
+ ruleScheduleLookback,
+ timerangeLookback,
+}: Record<'ruleScheduleLookback' | 'timerangeLookback', string>) {
+ const scheduleIntervalAbsoluteTime = datemath.parse(ruleScheduleLookback)?.valueOf();
+ const defaultIntervalAbsoluteTime = datemath
+ .parse(timerangeLookback)
+ ?.subtract('24', 'hours')
+ .valueOf();
+ const from = min([scheduleIntervalAbsoluteTime, defaultIntervalAbsoluteTime]) ?? 'now-24h';
+
+ return {
+ to: 'now',
+ from,
+ };
+}
+
const getMonIdByLoc = (monitorId: string, location: string) => {
return monitorId + '-' + location;
};
@@ -264,6 +293,9 @@ export const statusCheckAlertFactory: UptimeAlertTypeFactory = (
params: rawParams,
state,
services: { alertInstanceFactory },
+ rule: {
+ schedule: { interval },
+ },
},
uptimeEsClient,
}) {
@@ -279,14 +311,22 @@ export const statusCheckAlertFactory: UptimeAlertTypeFactory = (
isAutoGenerated,
timerange: oldVersionTimeRange,
} = rawParams;
-
const filterString = await formatFilterString(uptimeEsClient, filters, search, libs);
- const timerange = oldVersionTimeRange || {
- from: `now-${String(timerangeCount) + timerangeUnit}`,
+ const timespanInterval = `${String(timerangeCount)}${timerangeUnit}`;
+
+ // Range filter for `monitor.timespan`, the range of time the ping is valid
+ const timespanRange = oldVersionTimeRange || {
+ from: `now-${timespanInterval}`,
to: 'now',
};
+ // Range filter for `@timestamp`, the time the document was indexed
+ const timestampRange = getTimestampRange({
+ ruleScheduleLookback: `now-${interval}`,
+ timerangeLookback: timespanRange.from,
+ });
+
let downMonitorsByLocation: GetMonitorStatusResult[] = [];
// if oldVersionTimeRange present means it's 7.7 format and
@@ -294,7 +334,8 @@ export const statusCheckAlertFactory: UptimeAlertTypeFactory = (
if (!(!oldVersionTimeRange && shouldCheckStatus === false)) {
downMonitorsByLocation = await libs.requests.getMonitorStatus({
uptimeEsClient,
- timerange,
+ timespanRange,
+ timestampRange,
numTimes,
locations: [],
filters: filterString,
diff --git a/x-pack/plugins/uptime/server/lib/requests/get_certs.test.ts b/x-pack/plugins/uptime/server/lib/requests/get_certs.test.ts
index 333824df174a65..6e06dea0436dbf 100644
--- a/x-pack/plugins/uptime/server/lib/requests/get_certs.test.ts
+++ b/x-pack/plugins/uptime/server/lib/requests/get_certs.test.ts
@@ -173,7 +173,7 @@ describe('getCerts', () => {
"filter": Array [
Object {
"exists": Object {
- "field": "tls.server",
+ "field": "tls.server.hash.sha256",
},
},
Object {
diff --git a/x-pack/plugins/uptime/server/lib/requests/get_certs.ts b/x-pack/plugins/uptime/server/lib/requests/get_certs.ts
index 1b20ed9085fefc..86a9825f8a4856 100644
--- a/x-pack/plugins/uptime/server/lib/requests/get_certs.ts
+++ b/x-pack/plugins/uptime/server/lib/requests/get_certs.ts
@@ -64,7 +64,7 @@ export const getCerts: UMElasticsearchQueryFn = asyn
filter: [
{
exists: {
- field: 'tls.server',
+ field: 'tls.server.hash.sha256',
},
},
{
@@ -138,7 +138,6 @@ export const getCerts: UMElasticsearchQueryFn = asyn
searchBody.query.bool.filter.push(validityFilters);
}
- // console.log(JSON.stringify(params, null, 2));
const { body: result } = await uptimeEsClient.search({
body: searchBody,
});
diff --git a/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.test.ts b/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.test.ts
index 6d88ccb9a9efff..08b675576a5d20 100644
--- a/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.test.ts
+++ b/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.test.ts
@@ -85,10 +85,14 @@ describe('getMonitorStatus', () => {
filters: exampleFilter,
locations: [],
numTimes: 5,
- timerange: {
+ timespanRange: {
from: 'now-10m',
to: 'now-1m',
},
+ timestampRange: {
+ from: 'now-24h',
+ to: 'now',
+ },
});
expect(esMock.search).toHaveBeenCalledTimes(1);
const [params] = esMock.search.mock.calls[0];
@@ -144,6 +148,14 @@ describe('getMonitorStatus', () => {
Object {
"range": Object {
"@timestamp": Object {
+ "gte": "now-24h",
+ "lte": "now",
+ },
+ },
+ },
+ Object {
+ "range": Object {
+ "monitor.timespan": Object {
"gte": "now-10m",
"lte": "now-1m",
},
@@ -202,10 +214,14 @@ describe('getMonitorStatus', () => {
uptimeEsClient,
locations: ['fairbanks', 'harrisburg'],
numTimes: 1,
- timerange: {
+ timespanRange: {
from: 'now-2m',
to: 'now',
},
+ timestampRange: {
+ from: 'now-24h',
+ to: 'now',
+ },
});
expect(esMock.search).toHaveBeenCalledTimes(1);
const [params] = esMock.search.mock.calls[0];
@@ -261,6 +277,14 @@ describe('getMonitorStatus', () => {
Object {
"range": Object {
"@timestamp": Object {
+ "gte": "now-24h",
+ "lte": "now",
+ },
+ },
+ },
+ Object {
+ "range": Object {
+ "monitor.timespan": Object {
"gte": "now-2m",
"lte": "now",
},
@@ -298,10 +322,14 @@ describe('getMonitorStatus', () => {
genBucketItem
);
const clientParameters = {
- timerange: {
+ timespanRange: {
from: 'now-15m',
to: 'now',
},
+ timestampRange: {
+ from: 'now-24h',
+ to: 'now',
+ },
numTimes: 5,
locations: [],
filters: {
@@ -415,6 +443,14 @@ describe('getMonitorStatus', () => {
Object {
"range": Object {
"@timestamp": Object {
+ "gte": "now-24h",
+ "lte": "now",
+ },
+ },
+ },
+ Object {
+ "range": Object {
+ "monitor.timespan": Object {
"gte": "now-15m",
"lte": "now",
},
@@ -485,10 +521,14 @@ describe('getMonitorStatus', () => {
genBucketItem
);
const clientParameters = {
- timerange: {
+ timespanRange: {
from: 'now-15m',
to: 'now',
},
+ timestampRange: {
+ from: 'now-24h',
+ to: 'now',
+ },
numTimes: 5,
locations: [],
filters: {
@@ -562,6 +602,14 @@ describe('getMonitorStatus', () => {
Object {
"range": Object {
"@timestamp": Object {
+ "gte": "now-24h",
+ "lte": "now",
+ },
+ },
+ },
+ Object {
+ "range": Object {
+ "monitor.timespan": Object {
"gte": "now-15m",
"lte": "now",
},
@@ -618,10 +666,14 @@ describe('getMonitorStatus', () => {
filters: undefined,
locations: [],
numTimes: 5,
- timerange: {
+ timespanRange: {
from: 'now-12m',
to: 'now-2m',
},
+ timestampRange: {
+ from: 'now-24h',
+ to: 'now',
+ },
};
const { uptimeEsClient } = getUptimeESMockClient(esMock);
@@ -684,6 +736,14 @@ describe('getMonitorStatus', () => {
Object {
"range": Object {
"@timestamp": Object {
+ "gte": "now-24h",
+ "lte": "now",
+ },
+ },
+ },
+ Object {
+ "range": Object {
+ "monitor.timespan": Object {
"gte": "now-12m",
"lte": "now-2m",
},
@@ -810,10 +870,14 @@ describe('getMonitorStatus', () => {
uptimeEsClient,
locations: [],
numTimes: 5,
- timerange: {
+ timespanRange: {
from: 'now-10m',
to: 'now-1m',
},
+ timestampRange: {
+ from: 'now-24h',
+ to: 'now',
+ },
});
expect(result.length).toBe(8);
expect(result).toMatchInlineSnapshot(`
diff --git a/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts b/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts
index 07047bd0be7bc5..15e6fe30db1867 100644
--- a/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts
+++ b/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts
@@ -15,7 +15,8 @@ export interface GetMonitorStatusParams {
filters?: JsonObject;
locations: string[];
numTimes: number;
- timerange: { from: string; to: string };
+ timespanRange: { from: string; to: string };
+ timestampRange: { from: string | number; to: string };
}
export interface GetMonitorStatusResult {
@@ -43,7 +44,7 @@ export type AfterKey = Record | undefined;
export const getMonitorStatus: UMElasticsearchQueryFn<
GetMonitorStatusParams,
GetMonitorStatusResult[]
-> = async ({ uptimeEsClient, filters, locations, numTimes, timerange: { from, to } }) => {
+> = async ({ uptimeEsClient, filters, locations, numTimes, timespanRange, timestampRange }) => {
let afterKey: AfterKey;
const STATUS = 'down';
@@ -63,8 +64,16 @@ export const getMonitorStatus: UMElasticsearchQueryFn<
{
range: {
'@timestamp': {
- gte: from,
- lte: to,
+ gte: timestampRange.from,
+ lte: timestampRange.to,
+ },
+ },
+ },
+ {
+ range: {
+ 'monitor.timespan': {
+ gte: timespanRange.from,
+ lte: timespanRange.to,
},
},
},
diff --git a/x-pack/test/api_integration/apis/lens/field_stats.ts b/x-pack/test/api_integration/apis/lens/field_stats.ts
index 5dcb749f54b317..5090fe14576d58 100644
--- a/x-pack/test/api_integration/apis/lens/field_stats.ts
+++ b/x-pack/test/api_integration/apis/lens/field_stats.ts
@@ -427,6 +427,38 @@ export default ({ getService }: FtrProviderContext) => {
expect(body.totalDocuments).to.eql(425);
});
+
+ it('should allow filtering on a runtime field other than the field in use', async () => {
+ const { body } = await supertest
+ .post('/api/lens/index_stats/logstash-2015.09.22/field')
+ .set(COMMON_HEADERS)
+ .send({
+ dslQuery: {
+ bool: {
+ filter: [{ exists: { field: 'runtime_string_field' } }],
+ },
+ },
+ fromDate: TEST_START_TIME,
+ toDate: TEST_END_TIME,
+ fieldName: 'runtime_number_field',
+ })
+ .expect(200);
+
+ expect(body).to.eql({
+ totalDocuments: 4634,
+ sampledDocuments: 4634,
+ sampledValues: 4634,
+ topValues: {
+ buckets: [
+ {
+ count: 4634,
+ key: 5,
+ },
+ ],
+ },
+ histogram: { buckets: [] },
+ });
+ });
});
describe('histogram', () => {
diff --git a/x-pack/test/api_integration/apis/ml/data_visualizer/get_overall_stats.ts b/x-pack/test/api_integration/apis/ml/data_visualizer/get_overall_stats.ts
index 4ce9d4871246c2..7987875a75519a 100644
--- a/x-pack/test/api_integration/apis/ml/data_visualizer/get_overall_stats.ts
+++ b/x-pack/test/api_integration/apis/ml/data_visualizer/get_overall_stats.ts
@@ -52,6 +52,7 @@ export default ({ getService }: FtrProviderContext) => {
aggregatableNotExistsFields: [{ fieldName: 'sourcetype', existsInDocs: false }],
nonAggregatableExistsFields: [{ fieldName: 'type', existsInDocs: true, stats: {} }],
nonAggregatableNotExistsFields: [],
+ errors: [],
},
},
},
@@ -98,6 +99,7 @@ export default ({ getService }: FtrProviderContext) => {
aggregatableNotExistsFields: [{ fieldName: 'sourcetype', existsInDocs: false }],
nonAggregatableExistsFields: [{ fieldName: 'type', existsInDocs: true, stats: {} }],
nonAggregatableNotExistsFields: [],
+ errors: [],
},
},
},
diff --git a/x-pack/test/apm_api_integration/tests/correlations/latency_ml.ts b/x-pack/test/apm_api_integration/tests/correlations/latency_ml.ts
new file mode 100644
index 00000000000000..cc8f48fb589445
--- /dev/null
+++ b/x-pack/test/apm_api_integration/tests/correlations/latency_ml.ts
@@ -0,0 +1,266 @@
+/*
+ * 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 request from 'superagent';
+import { FtrProviderContext } from '../../common/ftr_provider_context';
+import { registry } from '../../common/registry';
+
+import { PartialSearchRequest } from '../../../../plugins/apm/server/lib/search_strategies/correlations/search_strategy';
+
+function parseBfetchResponse(resp: request.Response): Array> {
+ return resp.text
+ .trim()
+ .split('\n')
+ .map((item) => JSON.parse(item));
+}
+
+export default function ApiTest({ getService }: FtrProviderContext) {
+ const retry = getService('retry');
+ const supertest = getService('supertest');
+
+ const getRequestBody = () => {
+ const partialSearchRequest: PartialSearchRequest = {
+ params: {
+ index: 'apm-*',
+ environment: 'ENVIRONMENT_ALL',
+ start: '2020',
+ end: '2021',
+ percentileThreshold: 95,
+ },
+ };
+
+ return {
+ batch: [
+ {
+ request: partialSearchRequest,
+ options: { strategy: 'apmCorrelationsSearchStrategy' },
+ },
+ ],
+ };
+ };
+
+ registry.when(
+ 'correlations latency_ml overall without data',
+ { config: 'trial', archives: [] },
+ () => {
+ it('handles the empty state', async () => {
+ const intialResponse = await supertest
+ .post(`/internal/bsearch`)
+ .set('kbn-xsrf', 'foo')
+ .send(getRequestBody());
+
+ expect(intialResponse.status).to.eql(
+ 200,
+ `Expected status to be '200', got '${intialResponse.status}'`
+ );
+ expect(intialResponse.body).to.eql(
+ {},
+ `Expected response body to be an empty object, actual response is in the text attribute. Got: '${JSON.stringify(
+ intialResponse.body
+ )}'`
+ );
+
+ const body = parseBfetchResponse(intialResponse)[0];
+
+ expect(typeof body.result).to.be('object');
+ const { result } = body;
+
+ expect(typeof result?.id).to.be('string');
+
+ // pass on id for follow up queries
+ const searchStrategyId = result.id;
+
+ // follow up request body including search strategy ID
+ const reqBody = getRequestBody();
+ reqBody.batch[0].request.id = searchStrategyId;
+
+ let followUpResponse: Record = {};
+
+ // continues querying until the search strategy finishes
+ await retry.waitForWithTimeout(
+ 'search strategy eventually completes and returns full results',
+ 5000,
+ async () => {
+ const response = await supertest
+ .post(`/internal/bsearch`)
+ .set('kbn-xsrf', 'foo')
+ .send(reqBody);
+
+ followUpResponse = parseBfetchResponse(response)[0];
+
+ return (
+ followUpResponse?.result?.isRunning === false || followUpResponse?.error !== undefined
+ );
+ }
+ );
+
+ expect(followUpResponse?.error).to.eql(
+ undefined,
+ `search strategy should not return an error, got: ${JSON.stringify(
+ followUpResponse?.error
+ )}`
+ );
+
+ const followUpResult = followUpResponse.result;
+ expect(followUpResult?.isRunning).to.eql(false, 'search strategy should not be running');
+ expect(followUpResult?.isPartial).to.eql(
+ false,
+ 'search strategy result should not be partial'
+ );
+ expect(followUpResult?.id).to.eql(
+ searchStrategyId,
+ 'search strategy id should match original id'
+ );
+ expect(followUpResult?.isRestored).to.eql(
+ true,
+ 'search strategy response should be restored'
+ );
+ expect(followUpResult?.loaded).to.eql(100, 'loaded state should be 100');
+ expect(followUpResult?.total).to.eql(100, 'total state should be 100');
+
+ expect(typeof followUpResult?.rawResponse).to.be('object');
+
+ const { rawResponse: finalRawResponse } = followUpResult;
+
+ expect(typeof finalRawResponse?.took).to.be('number');
+ expect(finalRawResponse?.percentileThresholdValue).to.be(undefined);
+ expect(finalRawResponse?.overallHistogram).to.be(undefined);
+ expect(finalRawResponse?.values.length).to.be(0);
+ expect(finalRawResponse?.log.map((d: string) => d.split(': ')[1])).to.eql([
+ 'Fetched 95th percentile value of undefined based on 0 documents.',
+ 'Abort service since percentileThresholdValue could not be determined.',
+ ]);
+ });
+ }
+ );
+
+ registry.when(
+ 'Correlations latency_ml with data and opbeans-node args',
+ { config: 'trial', archives: ['ml_8.0.0'] },
+ () => {
+ // putting this into a single `it` because the responses depend on each other
+ it('queries the search strategy and returns results', async () => {
+ const intialResponse = await supertest
+ .post(`/internal/bsearch`)
+ .set('kbn-xsrf', 'foo')
+ .send(getRequestBody());
+
+ expect(intialResponse.status).to.eql(
+ 200,
+ `Expected status to be '200', got '${intialResponse.status}'`
+ );
+ expect(intialResponse.body).to.eql(
+ {},
+ `Expected response body to be an empty object, actual response is in the text attribute. Got: '${JSON.stringify(
+ intialResponse.body
+ )}'`
+ );
+
+ const body = parseBfetchResponse(intialResponse)[0];
+
+ expect(typeof body?.result).to.be('object');
+ const { result } = body;
+
+ expect(typeof result?.id).to.be('string');
+
+ // pass on id for follow up queries
+ const searchStrategyId = result.id;
+
+ expect(result?.loaded).to.be(0);
+ expect(result?.total).to.be(100);
+ expect(result?.isRunning).to.be(true);
+ expect(result?.isPartial).to.be(true);
+ expect(result?.isRestored).to.eql(
+ false,
+ `Expected response result to be not restored. Got: '${result?.isRestored}'`
+ );
+ expect(typeof result?.rawResponse).to.be('object');
+
+ const { rawResponse } = result;
+
+ expect(typeof rawResponse?.took).to.be('number');
+ expect(rawResponse?.values).to.eql([]);
+
+ // follow up request body including search strategy ID
+ const reqBody = getRequestBody();
+ reqBody.batch[0].request.id = searchStrategyId;
+
+ let followUpResponse: Record = {};
+
+ // continues querying until the search strategy finishes
+ await retry.waitForWithTimeout(
+ 'search strategy eventually completes and returns full results',
+ 5000,
+ async () => {
+ const response = await supertest
+ .post(`/internal/bsearch`)
+ .set('kbn-xsrf', 'foo')
+ .send(reqBody);
+ followUpResponse = parseBfetchResponse(response)[0];
+
+ return (
+ followUpResponse?.result?.isRunning === false || followUpResponse?.error !== undefined
+ );
+ }
+ );
+
+ expect(followUpResponse?.error).to.eql(
+ undefined,
+ `Finished search strategy should not return an error, got: ${JSON.stringify(
+ followUpResponse?.error
+ )}`
+ );
+
+ const followUpResult = followUpResponse.result;
+ expect(followUpResult?.isRunning).to.eql(
+ false,
+ `Expected finished result not to be running. Got: ${followUpResult?.isRunning}`
+ );
+ expect(followUpResult?.isPartial).to.eql(
+ false,
+ `Expected finished result not to be partial. Got: ${followUpResult?.isPartial}`
+ );
+ expect(followUpResult?.id).to.be(searchStrategyId);
+ expect(followUpResult?.isRestored).to.be(true);
+ expect(followUpResult?.loaded).to.be(100);
+ expect(followUpResult?.total).to.be(100);
+
+ expect(typeof followUpResult?.rawResponse).to.be('object');
+
+ const { rawResponse: finalRawResponse } = followUpResult;
+
+ expect(typeof finalRawResponse?.took).to.be('number');
+ expect(finalRawResponse?.percentileThresholdValue).to.be(1404927.875);
+ expect(finalRawResponse?.overallHistogram.length).to.be(101);
+
+ expect(finalRawResponse?.values.length).to.eql(
+ 1,
+ `Expected 1 identified correlations, got ${finalRawResponse?.values.length}.`
+ );
+ expect(finalRawResponse?.log.map((d: string) => d.split(': ')[1])).to.eql([
+ 'Fetched 95th percentile value of 1404927.875 based on 989 documents.',
+ 'Loaded histogram range steps.',
+ 'Loaded overall histogram chart data.',
+ 'Loaded percentiles.',
+ 'Identified 67 fieldCandidates.',
+ 'Identified 339 fieldValuePairs.',
+ 'Loaded fractions and totalDocCount of 989.',
+ 'Identified 1 significant correlations out of 339 field/value pairs.',
+ ]);
+
+ const correlation = finalRawResponse?.values[0];
+ expect(typeof correlation).to.be('object');
+ expect(correlation?.field).to.be('transaction.result');
+ expect(correlation?.value).to.be('success');
+ expect(correlation?.correlation).to.be(0.37418510688551887);
+ expect(correlation?.ksTest).to.be(1.1238496968312214e-10);
+ expect(correlation?.histogram.length).to.be(101);
+ });
+ }
+ );
+}
diff --git a/x-pack/test/apm_api_integration/tests/index.ts b/x-pack/test/apm_api_integration/tests/index.ts
index 813e0e4f3cdb89..a00fa1723fa3ec 100644
--- a/x-pack/test/apm_api_integration/tests/index.ts
+++ b/x-pack/test/apm_api_integration/tests/index.ts
@@ -32,6 +32,10 @@ export default function apmApiIntegrationTests(providerContext: FtrProviderConte
loadTestFile(require.resolve('./correlations/latency_slow_transactions'));
});
+ describe('correlations/latency_ml', function () {
+ loadTestFile(require.resolve('./correlations/latency_ml'));
+ });
+
describe('correlations/latency_overall', function () {
loadTestFile(require.resolve('./correlations/latency_overall'));
});
diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_exceptions.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_exceptions.ts
index 05b097cc87b616..e2251b0b8af084 100644
--- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_exceptions.ts
+++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_exceptions.ts
@@ -47,9 +47,10 @@ import {
getSignalsByIds,
findImmutableRuleById,
getPrePackagedRulesStatus,
- getRuleForSignalTesting,
getOpenSignals,
createRuleWithExceptionEntries,
+ getEqlRuleForSignalTesting,
+ getThresholdRuleForSignalTesting,
} from '../../utils';
import { ROLES } from '../../../../plugins/security_solution/common/test';
import { createUserAndRole, deleteUserAndRole } from '../roles_users_utils';
@@ -615,10 +616,7 @@ export default ({ getService }: FtrProviderContext) => {
it('generates no signals when an exception is added for an EQL rule', async () => {
const rule: EqlCreateSchema = {
- ...getRuleForSignalTesting(['auditbeat-*']),
- rule_id: 'eql-rule',
- type: 'eql',
- language: 'eql',
+ ...getEqlRuleForSignalTesting(['auditbeat-*']),
query: 'configuration where agent.id=="a1d7b39c-f898-4dbe-a761-efb61939302d"',
};
const createdRule = await createRuleWithExceptionEntries(supertest, rule, [
@@ -637,11 +635,7 @@ export default ({ getService }: FtrProviderContext) => {
it('generates no signals when an exception is added for a threshold rule', async () => {
const rule: ThresholdCreateSchema = {
- ...getRuleForSignalTesting(['auditbeat-*']),
- rule_id: 'threshold-rule',
- type: 'threshold',
- language: 'kuery',
- query: '*:*',
+ ...getThresholdRuleForSignalTesting(['auditbeat-*']),
threshold: {
field: 'host.id',
value: 700,
diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts
index a1a97ac8bfd354..4972b485be06c7 100644
--- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts
+++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts
@@ -21,11 +21,13 @@ import {
createSignalsIndex,
deleteAllAlerts,
deleteSignalsIndex,
+ getEqlRuleForSignalTesting,
getOpenSignals,
getRuleForSignalTesting,
getSignalsByIds,
getSignalsByRuleIds,
getSimpleRule,
+ getThresholdRuleForSignalTesting,
waitForRuleSuccessOrStatus,
waitForSignalsToBePresent,
} from '../../utils';
@@ -273,16 +275,13 @@ export default ({ getService }: FtrProviderContext) => {
describe('EQL Rules', () => {
it('generates a correctly formatted signal from EQL non-sequence queries', async () => {
const rule: EqlCreateSchema = {
- ...getRuleForSignalTesting(['auditbeat-*']),
- rule_id: 'eql-rule',
- type: 'eql',
- language: 'eql',
+ ...getEqlRuleForSignalTesting(['auditbeat-*']),
query: 'configuration where agent.id=="a1d7b39c-f898-4dbe-a761-efb61939302d"',
};
const { id } = await createRule(supertest, rule);
await waitForRuleSuccessOrStatus(supertest, id);
await waitForSignalsToBePresent(supertest, 1, [id]);
- const signals = await getSignalsByRuleIds(supertest, ['eql-rule']);
+ const signals = await getSignalsByIds(supertest, [id]);
expect(signals.hits.hits.length).eql(1);
const fullSignal = signals.hits.hits[0]._source;
@@ -393,13 +392,7 @@ export default ({ getService }: FtrProviderContext) => {
});
it('generates up to max_signals for non-sequence EQL queries', async () => {
- const rule: EqlCreateSchema = {
- ...getRuleForSignalTesting(['auditbeat-*']),
- rule_id: 'eql-rule',
- type: 'eql',
- language: 'eql',
- query: 'any where true',
- };
+ const rule: EqlCreateSchema = getEqlRuleForSignalTesting(['auditbeat-*']);
const { id } = await createRule(supertest, rule);
await waitForRuleSuccessOrStatus(supertest, id);
await waitForSignalsToBePresent(supertest, 100, [id]);
@@ -412,17 +405,14 @@ export default ({ getService }: FtrProviderContext) => {
it('uses the provided event_category_override', async () => {
const rule: EqlCreateSchema = {
- ...getRuleForSignalTesting(['auditbeat-*']),
- rule_id: 'eql-rule',
- type: 'eql',
- language: 'eql',
+ ...getEqlRuleForSignalTesting(['auditbeat-*']),
query: 'config_change where agent.id=="a1d7b39c-f898-4dbe-a761-efb61939302d"',
event_category_override: 'auditd.message_type',
};
const { id } = await createRule(supertest, rule);
await waitForRuleSuccessOrStatus(supertest, id);
await waitForSignalsToBePresent(supertest, 1, [id]);
- const signals = await getSignalsByRuleIds(supertest, ['eql-rule']);
+ const signals = await getSignalsByIds(supertest, [id]);
expect(signals.hits.hits.length).eql(1);
const fullSignal = signals.hits.hits[0]._source;
@@ -534,16 +524,13 @@ export default ({ getService }: FtrProviderContext) => {
it('generates building block signals from EQL sequences in the expected form', async () => {
const rule: EqlCreateSchema = {
- ...getRuleForSignalTesting(['auditbeat-*']),
- rule_id: 'eql-rule',
- type: 'eql',
- language: 'eql',
+ ...getEqlRuleForSignalTesting(['auditbeat-*']),
query: 'sequence by host.name [anomoly where true] [any where true]',
};
const { id } = await createRule(supertest, rule);
await waitForRuleSuccessOrStatus(supertest, id);
await waitForSignalsToBePresent(supertest, 3, [id]);
- const signals = await getSignalsByRuleIds(supertest, ['eql-rule']);
+ const signals = await getSignalsByIds(supertest, [id]);
const buildingBlock = signals.hits.hits.find(
(signal) =>
signal._source.signal.depth === 1 &&
@@ -699,16 +686,13 @@ export default ({ getService }: FtrProviderContext) => {
it('generates shell signals from EQL sequences in the expected form', async () => {
const rule: EqlCreateSchema = {
- ...getRuleForSignalTesting(['auditbeat-*']),
- rule_id: 'eql-rule',
- type: 'eql',
- language: 'eql',
+ ...getEqlRuleForSignalTesting(['auditbeat-*']),
query: 'sequence by host.name [anomoly where true] [any where true]',
};
const { id } = await createRule(supertest, rule);
await waitForRuleSuccessOrStatus(supertest, id);
await waitForSignalsToBePresent(supertest, 3, [id]);
- const signalsOpen = await getSignalsByRuleIds(supertest, ['eql-rule']);
+ const signalsOpen = await getSignalsByIds(supertest, [id]);
const sequenceSignal = signalsOpen.hits.hits.find(
(signal) => signal._source.signal.depth === 2
);
@@ -802,10 +786,7 @@ export default ({ getService }: FtrProviderContext) => {
it('generates up to max_signals with an EQL rule', async () => {
const rule: EqlCreateSchema = {
- ...getRuleForSignalTesting(['auditbeat-*']),
- rule_id: 'eql-rule',
- type: 'eql',
- language: 'eql',
+ ...getEqlRuleForSignalTesting(['auditbeat-*']),
query: 'sequence by host.name [any where true] [any where true]',
};
const { id } = await createRule(supertest, rule);
@@ -829,13 +810,8 @@ export default ({ getService }: FtrProviderContext) => {
describe('Threshold Rules', () => {
it('generates 1 signal from Threshold rules when threshold is met', async () => {
- const ruleId = 'threshold-rule';
const rule: ThresholdCreateSchema = {
- ...getRuleForSignalTesting(['auditbeat-*']),
- rule_id: ruleId,
- type: 'threshold',
- language: 'kuery',
- query: '*:*',
+ ...getThresholdRuleForSignalTesting(['auditbeat-*']),
threshold: {
field: 'host.id',
value: 700,
@@ -844,7 +820,7 @@ export default ({ getService }: FtrProviderContext) => {
const { id } = await createRule(supertest, rule);
await waitForRuleSuccessOrStatus(supertest, id);
await waitForSignalsToBePresent(supertest, 1, [id]);
- const signalsOpen = await getSignalsByRuleIds(supertest, [ruleId]);
+ const signalsOpen = await getSignalsByIds(supertest, [id]);
expect(signalsOpen.hits.hits.length).eql(1);
const fullSignal = signalsOpen.hits.hits[0]._source;
const eventIds = fullSignal.signal.parents.map((event) => event.id);
@@ -895,13 +871,8 @@ export default ({ getService }: FtrProviderContext) => {
});
it('generates 2 signals from Threshold rules when threshold is met', async () => {
- const ruleId = 'threshold-rule';
const rule: ThresholdCreateSchema = {
- ...getRuleForSignalTesting(['auditbeat-*']),
- rule_id: ruleId,
- type: 'threshold',
- language: 'kuery',
- query: '*:*',
+ ...getThresholdRuleForSignalTesting(['auditbeat-*']),
threshold: {
field: 'host.id',
value: 100,
@@ -910,17 +881,13 @@ export default ({ getService }: FtrProviderContext) => {
const { id } = await createRule(supertest, rule);
await waitForRuleSuccessOrStatus(supertest, id);
await waitForSignalsToBePresent(supertest, 2, [id]);
- const signalsOpen = await getSignalsByRuleIds(supertest, [ruleId]);
+ const signalsOpen = await getSignalsByIds(supertest, [id]);
expect(signalsOpen.hits.hits.length).eql(2);
});
it('applies the provided query before bucketing ', async () => {
- const ruleId = 'threshold-rule';
const rule: ThresholdCreateSchema = {
- ...getRuleForSignalTesting(['auditbeat-*']),
- rule_id: ruleId,
- type: 'threshold',
- language: 'kuery',
+ ...getThresholdRuleForSignalTesting(['auditbeat-*']),
query: 'host.id:"2ab45fc1c41e4c84bbd02202a7e5761f"',
threshold: {
field: 'process.name',
@@ -930,18 +897,13 @@ export default ({ getService }: FtrProviderContext) => {
const { id } = await createRule(supertest, rule);
await waitForRuleSuccessOrStatus(supertest, id);
await waitForSignalsToBePresent(supertest, 1, [id]);
- const signalsOpen = await getSignalsByRuleIds(supertest, [ruleId]);
+ const signalsOpen = await getSignalsByIds(supertest, [id]);
expect(signalsOpen.hits.hits.length).eql(1);
});
it('generates no signals from Threshold rules when threshold is met and cardinality is not met', async () => {
- const ruleId = 'threshold-rule';
const rule: ThresholdCreateSchema = {
- ...getRuleForSignalTesting(['auditbeat-*']),
- rule_id: ruleId,
- type: 'threshold',
- language: 'kuery',
- query: '*:*',
+ ...getThresholdRuleForSignalTesting(['auditbeat-*']),
threshold: {
field: 'host.id',
value: 100,
@@ -959,13 +921,8 @@ export default ({ getService }: FtrProviderContext) => {
});
it('generates no signals from Threshold rules when cardinality is met and threshold is not met', async () => {
- const ruleId = 'threshold-rule';
const rule: ThresholdCreateSchema = {
- ...getRuleForSignalTesting(['auditbeat-*']),
- rule_id: ruleId,
- type: 'threshold',
- language: 'kuery',
- query: '*:*',
+ ...getThresholdRuleForSignalTesting(['auditbeat-*']),
threshold: {
field: 'host.id',
value: 1000,
@@ -983,13 +940,8 @@ export default ({ getService }: FtrProviderContext) => {
});
it('generates signals from Threshold rules when threshold and cardinality are both met', async () => {
- const ruleId = 'threshold-rule';
const rule: ThresholdCreateSchema = {
- ...getRuleForSignalTesting(['auditbeat-*']),
- rule_id: ruleId,
- type: 'threshold',
- language: 'kuery',
- query: '*:*',
+ ...getThresholdRuleForSignalTesting(['auditbeat-*']),
threshold: {
field: 'host.id',
value: 100,
@@ -1059,13 +1011,8 @@ export default ({ getService }: FtrProviderContext) => {
});
it('should not generate signals if only one field meets the threshold requirement', async () => {
- const ruleId = 'threshold-rule';
const rule: ThresholdCreateSchema = {
- ...getRuleForSignalTesting(['auditbeat-*']),
- rule_id: ruleId,
- type: 'threshold',
- language: 'kuery',
- query: '*:*',
+ ...getThresholdRuleForSignalTesting(['auditbeat-*']),
threshold: {
field: ['host.id', 'process.name'],
value: 22,
@@ -1077,13 +1024,8 @@ export default ({ getService }: FtrProviderContext) => {
});
it('generates signals from Threshold rules when bucketing by multiple fields', async () => {
- const ruleId = 'threshold-rule';
const rule: ThresholdCreateSchema = {
- ...getRuleForSignalTesting(['auditbeat-*']),
- rule_id: ruleId,
- type: 'threshold',
- language: 'kuery',
- query: '*:*',
+ ...getThresholdRuleForSignalTesting(['auditbeat-*']),
threshold: {
field: ['host.id', 'process.name', 'event.module'],
value: 21,
diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/const_keyword.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/const_keyword.ts
index b793fc635843e4..7d1a4d01fe27c4 100644
--- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/const_keyword.ts
+++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/const_keyword.ts
@@ -17,8 +17,10 @@ import {
createSignalsIndex,
deleteAllAlerts,
deleteSignalsIndex,
+ getEqlRuleForSignalTesting,
getRuleForSignalTesting,
getSignalsById,
+ getThresholdRuleForSignalTesting,
waitForRuleSuccessOrStatus,
waitForSignalsToBePresent,
} from '../../../utils';
@@ -84,10 +86,7 @@ export default ({ getService }: FtrProviderContext) => {
describe('"eql" rule type', () => {
it('should detect the "dataset_name_1" from "event.dataset" and have 4 signals', async () => {
const rule: EqlCreateSchema = {
- ...getRuleForSignalTesting(['const_keyword']),
- rule_id: 'eql-rule',
- type: 'eql',
- language: 'eql',
+ ...getEqlRuleForSignalTesting(['const_keyword']),
query: 'any where event.dataset=="dataset_name_1"',
};
@@ -100,10 +99,7 @@ export default ({ getService }: FtrProviderContext) => {
it('should copy the "dataset_name_1" from "event.dataset"', async () => {
const rule: EqlCreateSchema = {
- ...getRuleForSignalTesting(['const_keyword']),
- rule_id: 'eql-rule',
- type: 'eql',
- language: 'eql',
+ ...getEqlRuleForSignalTesting(['const_keyword']),
query: 'any where event.dataset=="dataset_name_1"',
};
@@ -126,11 +122,7 @@ export default ({ getService }: FtrProviderContext) => {
describe('"threshold" rule type', async () => {
it('should detect the "dataset_name_1" from "event.dataset"', async () => {
const rule: ThresholdCreateSchema = {
- ...getRuleForSignalTesting(['const_keyword']),
- rule_id: 'threshold-rule',
- type: 'threshold',
- language: 'kuery',
- query: '*:*',
+ ...getThresholdRuleForSignalTesting(['const_keyword']),
threshold: {
field: 'event.dataset',
value: 1,
diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/keyword.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/keyword.ts
index d2d2898587ee2a..fba13c95c66acc 100644
--- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/keyword.ts
+++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/keyword.ts
@@ -13,13 +13,16 @@ import {
createSignalsIndex,
deleteAllAlerts,
deleteSignalsIndex,
+ getEqlRuleForSignalTesting,
getRuleForSignalTesting,
getSignalsById,
+ getThresholdRuleForSignalTesting,
waitForRuleSuccessOrStatus,
waitForSignalsToBePresent,
} from '../../../utils';
import {
EqlCreateSchema,
+ QueryCreateSchema,
ThresholdCreateSchema,
} from '../../../../../plugins/security_solution/common/detection_engine/schemas/request';
@@ -47,7 +50,7 @@ export default ({ getService }: FtrProviderContext) => {
describe('"kql" rule type', () => {
it('should detect the "dataset_name_1" from "event.dataset"', async () => {
- const rule = {
+ const rule: QueryCreateSchema = {
...getRuleForSignalTesting(['keyword']),
query: 'event.dataset: "dataset_name_1"',
};
@@ -70,10 +73,7 @@ export default ({ getService }: FtrProviderContext) => {
describe('"eql" rule type', () => {
it('should detect the "dataset_name_1" from "event.dataset"', async () => {
const rule: EqlCreateSchema = {
- ...getRuleForSignalTesting(['keyword']),
- rule_id: 'eql-rule',
- type: 'eql',
- language: 'eql',
+ ...getEqlRuleForSignalTesting(['keyword']),
query: 'any where event.dataset=="dataset_name_1"',
};
@@ -96,11 +96,7 @@ export default ({ getService }: FtrProviderContext) => {
describe('"threshold" rule type', async () => {
it('should detect the "dataset_name_1" from "event.dataset"', async () => {
const rule: ThresholdCreateSchema = {
- ...getRuleForSignalTesting(['keyword']),
- rule_id: 'threshold-rule',
- type: 'threshold',
- language: 'kuery',
- query: '*:*',
+ ...getThresholdRuleForSignalTesting(['keyword']),
threshold: {
field: 'event.dataset',
value: 1,
diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/keyword_mixed_with_const.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/keyword_mixed_with_const.ts
index 2ce88da13afab2..2a354a83a10aee 100644
--- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/keyword_mixed_with_const.ts
+++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/keyword_mixed_with_const.ts
@@ -17,6 +17,7 @@ import {
createSignalsIndex,
deleteAllAlerts,
deleteSignalsIndex,
+ getEqlRuleForSignalTesting,
getRuleForSignalTesting,
getSignalsById,
waitForRuleSuccessOrStatus,
@@ -90,10 +91,7 @@ export default ({ getService }: FtrProviderContext) => {
describe('"eql" rule type', () => {
it('should detect the "dataset_name_1" from "event.dataset" and have 8 signals, 4 from each index', async () => {
const rule: EqlCreateSchema = {
- ...getRuleForSignalTesting(['keyword', 'const_keyword']),
- rule_id: 'eql-rule',
- type: 'eql',
- language: 'eql',
+ ...getEqlRuleForSignalTesting(['keyword', 'const_keyword']),
query: 'any where event.dataset=="dataset_name_1"',
};
@@ -106,10 +104,7 @@ export default ({ getService }: FtrProviderContext) => {
it('should copy the "dataset_name_1" from "event.dataset"', async () => {
const rule: EqlCreateSchema = {
- ...getRuleForSignalTesting(['keyword', 'const_keyword']),
- rule_id: 'eql-rule',
- type: 'eql',
- language: 'eql',
+ ...getEqlRuleForSignalTesting(['keyword', 'const_keyword']),
query: 'any where event.dataset=="dataset_name_1"',
};
diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/timestamps.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/timestamps.ts
index 8645fec287b074..2c304803ded897 100644
--- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/timestamps.ts
+++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/timestamps.ts
@@ -7,7 +7,10 @@
import expect from '@kbn/expect';
import { orderBy } from 'lodash';
-import { QueryCreateSchema } from '../../../../plugins/security_solution/common/detection_engine/schemas/request';
+import {
+ EqlCreateSchema,
+ QueryCreateSchema,
+} from '../../../../plugins/security_solution/common/detection_engine/schemas/request';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import {
@@ -19,6 +22,7 @@ import {
waitForSignalsToBePresent,
getRuleForSignalTesting,
getSignalsByIds,
+ getEqlRuleForSignalTesting,
} from '../../utils';
// eslint-disable-next-line import/no-default-export
@@ -54,27 +58,54 @@ export default ({ getService }: FtrProviderContext) => {
);
});
- it('should convert the @timestamp which is epoch_seconds into the correct ISO format', async () => {
- const rule = getRuleForSignalTesting(['timestamp_in_seconds']);
- const { id } = await createRule(supertest, rule);
- await waitForRuleSuccessOrStatus(supertest, id);
- await waitForSignalsToBePresent(supertest, 1, [id]);
- const signalsOpen = await getSignalsByIds(supertest, [id]);
- const hits = signalsOpen.hits.hits.map((hit) => hit._source.signal.original_time).sort();
- expect(hits).to.eql(['2021-06-02T23:33:15.000Z']);
+ describe('KQL query', () => {
+ it('should convert the @timestamp which is epoch_seconds into the correct ISO format', async () => {
+ const rule = getRuleForSignalTesting(['timestamp_in_seconds']);
+ const { id } = await createRule(supertest, rule);
+ await waitForRuleSuccessOrStatus(supertest, id);
+ await waitForSignalsToBePresent(supertest, 1, [id]);
+ const signalsOpen = await getSignalsByIds(supertest, [id]);
+ const hits = signalsOpen.hits.hits.map((hit) => hit._source.signal.original_time).sort();
+ expect(hits).to.eql(['2021-06-02T23:33:15.000Z']);
+ });
+
+ it('should still use the @timestamp field even with an override field. It should never use the override field', async () => {
+ const rule: QueryCreateSchema = {
+ ...getRuleForSignalTesting(['myfakeindex-5']),
+ timestamp_override: 'event.ingested',
+ };
+ const { id } = await createRule(supertest, rule);
+ await waitForRuleSuccessOrStatus(supertest, id);
+ await waitForSignalsToBePresent(supertest, 1, [id]);
+ const signalsOpen = await getSignalsByIds(supertest, [id]);
+ const hits = signalsOpen.hits.hits.map((hit) => hit._source.signal.original_time).sort();
+ expect(hits).to.eql(['2020-12-16T15:16:18.000Z']);
+ });
});
- it('should still use the @timestamp field even with an override field. It should never use the override field', async () => {
- const rule: QueryCreateSchema = {
- ...getRuleForSignalTesting(['myfakeindex-5']),
- timestamp_override: 'event.ingested',
- };
- const { id } = await createRule(supertest, rule);
- await waitForRuleSuccessOrStatus(supertest, id);
- await waitForSignalsToBePresent(supertest, 1, [id]);
- const signalsOpen = await getSignalsByIds(supertest, [id]);
- const hits = signalsOpen.hits.hits.map((hit) => hit._source.signal.original_time).sort();
- expect(hits).to.eql(['2020-12-16T15:16:18.000Z']);
+ describe('EQL query', () => {
+ it('should convert the @timestamp which is epoch_seconds into the correct ISO format for EQL', async () => {
+ const rule = getEqlRuleForSignalTesting(['timestamp_in_seconds']);
+ const { id } = await createRule(supertest, rule);
+ await waitForRuleSuccessOrStatus(supertest, id);
+ await waitForSignalsToBePresent(supertest, 1, [id]);
+ const signalsOpen = await getSignalsByIds(supertest, [id]);
+ const hits = signalsOpen.hits.hits.map((hit) => hit._source.signal.original_time).sort();
+ expect(hits).to.eql(['2021-06-02T23:33:15.000Z']);
+ });
+
+ it('should still use the @timestamp field even with an override field. It should never use the override field', async () => {
+ const rule: EqlCreateSchema = {
+ ...getEqlRuleForSignalTesting(['myfakeindex-5']),
+ timestamp_override: 'event.ingested',
+ };
+ const { id } = await createRule(supertest, rule);
+ await waitForRuleSuccessOrStatus(supertest, id);
+ await waitForSignalsToBePresent(supertest, 1, [id]);
+ const signalsOpen = await getSignalsByIds(supertest, [id]);
+ const hits = signalsOpen.hits.hits.map((hit) => hit._source.signal.original_time).sort();
+ expect(hits).to.eql(['2020-12-16T15:16:18.000Z']);
+ });
});
});
@@ -119,73 +150,91 @@ export default ({ getService }: FtrProviderContext) => {
);
});
- it('should generate signals with event.ingested, @timestamp and (event.ingested + timestamp)', async () => {
- const rule: QueryCreateSchema = {
- ...getRuleForSignalTesting(['myfa*']),
- timestamp_override: 'event.ingested',
- };
+ describe('KQL', () => {
+ it('should generate signals with event.ingested, @timestamp and (event.ingested + timestamp)', async () => {
+ const rule: QueryCreateSchema = {
+ ...getRuleForSignalTesting(['myfa*']),
+ timestamp_override: 'event.ingested',
+ };
- const { id } = await createRule(supertest, rule);
+ const { id } = await createRule(supertest, rule);
- await waitForRuleSuccessOrStatus(supertest, id, 'partial failure');
- await waitForSignalsToBePresent(supertest, 3, [id]);
- const signalsResponse = await getSignalsByIds(supertest, [id], 3);
- const signals = signalsResponse.hits.hits.map((hit) => hit._source);
- const signalsOrderedByEventId = orderBy(signals, 'signal.parent.id', 'asc');
+ await waitForRuleSuccessOrStatus(supertest, id, 'partial failure');
+ await waitForSignalsToBePresent(supertest, 3, [id]);
+ const signalsResponse = await getSignalsByIds(supertest, [id], 3);
+ const signals = signalsResponse.hits.hits.map((hit) => hit._source);
+ const signalsOrderedByEventId = orderBy(signals, 'signal.parent.id', 'asc');
- expect(signalsOrderedByEventId.length).equal(3);
- });
+ expect(signalsOrderedByEventId.length).equal(3);
+ });
- it('should generate 2 signals with @timestamp', async () => {
- const rule: QueryCreateSchema = getRuleForSignalTesting(['myfa*']);
+ it('should generate 2 signals with @timestamp', async () => {
+ const rule: QueryCreateSchema = getRuleForSignalTesting(['myfa*']);
- const { id } = await createRule(supertest, rule);
+ const { id } = await createRule(supertest, rule);
- await waitForRuleSuccessOrStatus(supertest, id, 'partial failure');
- await waitForSignalsToBePresent(supertest, 2, [id]);
- const signalsResponse = await getSignalsByIds(supertest, [id]);
- const signals = signalsResponse.hits.hits.map((hit) => hit._source);
- const signalsOrderedByEventId = orderBy(signals, 'signal.parent.id', 'asc');
+ await waitForRuleSuccessOrStatus(supertest, id, 'partial failure');
+ await waitForSignalsToBePresent(supertest, 2, [id]);
+ const signalsResponse = await getSignalsByIds(supertest, [id]);
+ const signals = signalsResponse.hits.hits.map((hit) => hit._source);
+ const signalsOrderedByEventId = orderBy(signals, 'signal.parent.id', 'asc');
- expect(signalsOrderedByEventId.length).equal(2);
- });
+ expect(signalsOrderedByEventId.length).equal(2);
+ });
+
+ it('should generate 2 signals when timestamp override does not exist', async () => {
+ const rule: QueryCreateSchema = {
+ ...getRuleForSignalTesting(['myfa*']),
+ timestamp_override: 'event.fakeingestfield',
+ };
+ const { id } = await createRule(supertest, rule);
- it('should generate 2 signals when timestamp override does not exist', async () => {
- const rule: QueryCreateSchema = {
- ...getRuleForSignalTesting(['myfa*']),
- timestamp_override: 'event.fakeingestfield',
- };
- const { id } = await createRule(supertest, rule);
+ await waitForRuleSuccessOrStatus(supertest, id, 'partial failure');
+ await waitForSignalsToBePresent(supertest, 2, [id]);
+ const signalsResponse = await getSignalsByIds(supertest, [id, id]);
+ const signals = signalsResponse.hits.hits.map((hit) => hit._source);
+ const signalsOrderedByEventId = orderBy(signals, 'signal.parent.id', 'asc');
- await waitForRuleSuccessOrStatus(supertest, id, 'partial failure');
- await waitForSignalsToBePresent(supertest, 2, [id]);
- const signalsResponse = await getSignalsByIds(supertest, [id, id]);
- const signals = signalsResponse.hits.hits.map((hit) => hit._source);
- const signalsOrderedByEventId = orderBy(signals, 'signal.parent.id', 'asc');
+ expect(signalsOrderedByEventId.length).equal(2);
+ });
- expect(signalsOrderedByEventId.length).equal(2);
+ /**
+ * We should not use the timestamp override as the "original_time" as that can cause
+ * confusion if you have both a timestamp and an override in the source event. Instead the "original_time"
+ * field should only be overridden by the "timestamp" since when we generate a signal
+ * and we add a new timestamp to the signal.
+ */
+ it('should NOT use the timestamp override as the "original_time"', async () => {
+ const rule: QueryCreateSchema = {
+ ...getRuleForSignalTesting(['myfakeindex-2']),
+ timestamp_override: 'event.ingested',
+ };
+ const { id } = await createRule(supertest, rule);
+
+ await waitForRuleSuccessOrStatus(supertest, id);
+ await waitForSignalsToBePresent(supertest, 1, [id]);
+ const signalsResponse = await getSignalsByIds(supertest, [id, id]);
+ const hits = signalsResponse.hits.hits
+ .map((hit) => hit._source.signal.original_time)
+ .sort();
+ expect(hits).to.eql([undefined]);
+ });
});
- /**
- * We should not use the timestamp override as the "original_time" as that can cause
- * confusion if you have both a timestamp and an override in the source event. Instead the "original_time"
- * field should only be overridden by the "timestamp" since when we generate a signal
- * and we add a new timestamp to the signal.
- */
- it('should NOT use the timestamp override as the "original_time"', async () => {
- const rule: QueryCreateSchema = {
- ...getRuleForSignalTesting(['myfakeindex-2']),
- timestamp_override: 'event.ingested',
- };
- const { id } = await createRule(supertest, rule);
-
- await waitForRuleSuccessOrStatus(supertest, id);
- await waitForSignalsToBePresent(supertest, 1, [id]);
- const signalsResponse = await getSignalsByIds(supertest, [id, id]);
- const hits = signalsResponse.hits.hits
- .map((hit) => hit._source.signal.original_time)
- .sort();
- expect(hits).to.eql([undefined]);
+ describe('EQL', () => {
+ it('should generate 2 signals with @timestamp', async () => {
+ const rule: EqlCreateSchema = getEqlRuleForSignalTesting(['myfa*']);
+
+ const { id } = await createRule(supertest, rule);
+
+ await waitForRuleSuccessOrStatus(supertest, id, 'partial failure');
+ await waitForSignalsToBePresent(supertest, 2, [id]);
+ const signalsResponse = await getSignalsByIds(supertest, [id]);
+ const signals = signalsResponse.hits.hits.map((hit) => hit._source);
+ const signalsOrderedByEventId = orderBy(signals, 'signal.parent.id', 'asc');
+
+ expect(signalsOrderedByEventId.length).equal(2);
+ });
});
});
@@ -201,31 +250,33 @@ export default ({ getService }: FtrProviderContext) => {
await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts');
});
- /**
- * This represents our worst case scenario where this field is not mapped on any index
- * We want to check that our logic continues to function within the constraints of search after
- * Elasticsearch returns java's long.MAX_VALUE for unmapped date fields
- * Javascript does not support numbers this large, but without passing in a number of this size
- * The search_after will continue to return the same results and not iterate to the next set
- * So to circumvent this limitation of javascript we return the stringified version of Java's
- * Long.MAX_VALUE so that search_after does not enter into an infinite loop.
- *
- * ref: https://github.com/elastic/elasticsearch/issues/28806#issuecomment-369303620
- */
- it('should generate 200 signals when timestamp override does not exist', async () => {
- const rule: QueryCreateSchema = {
- ...getRuleForSignalTesting(['auditbeat-*']),
- timestamp_override: 'event.fakeingested',
- max_signals: 200,
- };
-
- const { id } = await createRule(supertest, rule);
- await waitForRuleSuccessOrStatus(supertest, id, 'partial failure');
- await waitForSignalsToBePresent(supertest, 200, [id]);
- const signalsResponse = await getSignalsByIds(supertest, [id], 200);
- const signals = signalsResponse.hits.hits.map((hit) => hit._source);
-
- expect(signals.length).equal(200);
+ describe('KQL', () => {
+ /**
+ * This represents our worst case scenario where this field is not mapped on any index
+ * We want to check that our logic continues to function within the constraints of search after
+ * Elasticsearch returns java's long.MAX_VALUE for unmapped date fields
+ * Javascript does not support numbers this large, but without passing in a number of this size
+ * The search_after will continue to return the same results and not iterate to the next set
+ * So to circumvent this limitation of javascript we return the stringified version of Java's
+ * Long.MAX_VALUE so that search_after does not enter into an infinite loop.
+ *
+ * ref: https://github.com/elastic/elasticsearch/issues/28806#issuecomment-369303620
+ */
+ it('should generate 200 signals when timestamp override does not exist', async () => {
+ const rule: QueryCreateSchema = {
+ ...getRuleForSignalTesting(['auditbeat-*']),
+ timestamp_override: 'event.fakeingested',
+ max_signals: 200,
+ };
+
+ const { id } = await createRule(supertest, rule);
+ await waitForRuleSuccessOrStatus(supertest, id, 'partial failure');
+ await waitForSignalsToBePresent(supertest, 200, [id]);
+ const signalsResponse = await getSignalsByIds(supertest, [id], 200);
+ const signals = signalsResponse.hits.hits.map((hit) => hit._source);
+
+ expect(signals.length).equal(200);
+ });
});
});
});
diff --git a/x-pack/test/detection_engine_api_integration/utils.ts b/x-pack/test/detection_engine_api_integration/utils.ts
index 54252b19fc940f..ac11dd31c15e80 100644
--- a/x-pack/test/detection_engine_api_integration/utils.ts
+++ b/x-pack/test/detection_engine_api_integration/utils.ts
@@ -27,6 +27,8 @@ import {
UpdateRulesSchema,
FullResponseSchema,
QueryCreateSchema,
+ EqlCreateSchema,
+ ThresholdCreateSchema,
} from '../../plugins/security_solution/common/detection_engine/schemas/request';
import { Signal } from '../../plugins/security_solution/server/lib/detection_engine/signals/types';
import { signalsMigrationType } from '../../plugins/security_solution/server/lib/detection_engine/migrations/saved_objects';
@@ -123,6 +125,46 @@ export const getRuleForSignalTesting = (
from: '1900-01-01T00:00:00.000Z',
});
+/**
+ * This is a typical signal testing rule that is easy for most basic testing of output of EQL signals.
+ * It starts out in an enabled true state. The from is set very far back to test the basics of signal
+ * creation for EQL and testing by getting all the signals at once.
+ * @param ruleId The optional ruleId which is eql-rule by default.
+ * @param enabled Enables the rule on creation or not. Defaulted to true.
+ */
+export const getEqlRuleForSignalTesting = (
+ index: string[],
+ ruleId = 'eql-rule',
+ enabled = true
+): EqlCreateSchema => ({
+ ...getRuleForSignalTesting(index, ruleId, enabled),
+ type: 'eql',
+ language: 'eql',
+ query: 'any where true',
+});
+
+/**
+ * This is a typical signal testing rule that is easy for most basic testing of output of Threshold signals.
+ * It starts out in an enabled true state. The from is set very far back to test the basics of signal
+ * creation for Threshold and testing by getting all the signals at once.
+ * @param ruleId The optional ruleId which is threshold-rule by default.
+ * @param enabled Enables the rule on creation or not. Defaulted to true.
+ */
+export const getThresholdRuleForSignalTesting = (
+ index: string[],
+ ruleId = 'threshold-rule',
+ enabled = true
+): ThresholdCreateSchema => ({
+ ...getRuleForSignalTesting(index, ruleId, enabled),
+ type: 'threshold',
+ language: 'kuery',
+ query: '*:*',
+ threshold: {
+ field: 'process.name',
+ value: 21,
+ },
+});
+
export const getRuleForSignalTestingWithTimestampOverride = (
index: string[],
ruleId = 'rule-1',
diff --git a/x-pack/test/functional/apps/lens/formula.ts b/x-pack/test/functional/apps/lens/formula.ts
index 6148215d8b6d25..7662b32b8aee62 100644
--- a/x-pack/test/functional/apps/lens/formula.ts
+++ b/x-pack/test/functional/apps/lens/formula.ts
@@ -15,9 +15,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const browser = getService('browser');
const testSubjects = getService('testSubjects');
const fieldEditor = getService('fieldEditor');
+ const retry = getService('retry');
- // FLAKY: https://github.com/elastic/kibana/issues/105016
- describe.skip('lens formula', () => {
+ describe('lens formula', () => {
it('should transition from count to formula', async () => {
await PageObjects.visualize.gotoVisualizationLandingPage();
await listingTable.searchForItemWithName('lnsXYvis');
@@ -55,8 +55,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const input = await find.activeElement();
await input.type('*');
- await PageObjects.header.waitUntilLoadingHasFinished();
- expect(await PageObjects.lens.getDatatableCellText(0, 0)).to.eql('14,005');
+ await retry.try(async () => {
+ expect(await PageObjects.lens.getDatatableCellText(0, 0)).to.eql('14,005');
+ });
});
it('should insert single quotes and escape when needed to create valid KQL', async () => {
@@ -79,15 +80,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.common.sleep(100);
- let element = await find.byCssSelector('.monaco-editor');
- expect(await element.getVisibleText()).to.equal(`count(kql='Men\\'s Clothing ')`);
+ PageObjects.lens.expectFormulaText(`count(kql='Men\\'s Clothing ')`);
await PageObjects.lens.typeFormula('count(kql=');
+
input = await find.activeElement();
await input.type(`Men\'s Clothing`);
- element = await find.byCssSelector('.monaco-editor');
- expect(await element.getVisibleText()).to.equal(`count(kql='Men\\'s Clothing')`);
+ PageObjects.lens.expectFormulaText(`count(kql='Men\\'s Clothing')`);
});
it('should insert single quotes and escape when needed to create valid field name', async () => {
@@ -109,20 +109,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
await PageObjects.lens.switchToFormula();
- let element = await find.byCssSelector('.monaco-editor');
- expect(await element.getVisibleText()).to.equal(`unique_count('*\\' "\\'')`);
+ PageObjects.lens.expectFormulaText(`unique_count('*\\' "\\'')`);
+ await PageObjects.lens.typeFormula('unique_count(');
const input = await find.activeElement();
- await input.clearValueWithKeyboard({ charByChar: true });
- await input.type('unique_count(');
- await PageObjects.common.sleep(100);
await input.type('*');
await input.pressKeys(browser.keys.ENTER);
await PageObjects.common.sleep(100);
- element = await find.byCssSelector('.monaco-editor');
- expect(await element.getVisibleText()).to.equal(`unique_count('*\\' "\\'')`);
+ PageObjects.lens.expectFormulaText(`unique_count('*\\' "\\'')`);
});
it('should persist a broken formula on close', async () => {
diff --git a/x-pack/test/functional/es_archives/visualize/default/data.json b/x-pack/test/functional/es_archives/visualize/default/data.json
index 7d0ad0c25f96db..a16e1676611ce5 100644
--- a/x-pack/test/functional/es_archives/visualize/default/data.json
+++ b/x-pack/test/functional/es_archives/visualize/default/data.json
@@ -157,7 +157,7 @@
"timeFieldName": "@timestamp",
"title": "logstash-2015.09.22",
"fields":"[{\"name\":\"scripted_date\",\"type\":\"date\",\"count\":0,\"scripted\":true,\"script\":\"1234\",\"lang\":\"painless\",\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"scripted_string\",\"type\":\"string\",\"count\":0,\"scripted\":true,\"script\":\"return 'hello'\",\"lang\":\"painless\",\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false}]",
- "runtimeFieldMap":"{\"runtime_string_field\":{\"type\":\"keyword\",\"script\":{\"source\":\"emit('hello world!')\"}}}"
+ "runtimeFieldMap":"{\"runtime_string_field\":{\"type\":\"keyword\",\"script\":{\"source\":\"emit('hello world!')\"}},\"runtime_number_field\":{\"type\":\"double\",\"script\":{\"source\":\"emit(5)\"}}}"
},
"migrationVersion": {
"index-pattern": "7.11.0"
diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts
index e256d5cd651cc1..1acddd4641ff41 100644
--- a/x-pack/test/functional/page_objects/lens_page.ts
+++ b/x-pack/test/functional/page_objects/lens_page.ts
@@ -139,6 +139,8 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
}
if (opts.formula) {
+ // Formula takes time to open
+ await PageObjects.common.sleep(500);
await this.typeFormula(opts.formula);
}
@@ -1067,13 +1069,18 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
},
async typeFormula(formula: string) {
- // Formula takes time to open
- await PageObjects.common.sleep(500);
await find.byCssSelector('.monaco-editor');
await find.clickByCssSelectorWhenNotDisabled('.monaco-editor');
const input = await find.activeElement();
await input.clearValueWithKeyboard({ charByChar: true });
await input.type(formula);
+ // Debounce time for formula
+ await PageObjects.common.sleep(300);
+ },
+
+ async expectFormulaText(formula: string) {
+ const element = await find.byCssSelector('.monaco-editor');
+ expect(await element.getVisibleText()).to.equal(formula);
},
async filterLegend(value: string) {
diff --git a/x-pack/test/load/runner.ts b/x-pack/test/load/runner.ts
index 2d379391b20897..0bea5992f55394 100644
--- a/x-pack/test/load/runner.ts
+++ b/x-pack/test/load/runner.ts
@@ -18,6 +18,7 @@ const simulationPackage = 'org.kibanaLoadTest.simulation';
const simulationFIleExtension = '.scala';
const gatlingProjectRootPath: string =
process.env.GATLING_PROJECT_PATH || resolve(REPO_ROOT, '../kibana-load-testing');
+const puppeteerProjectRootPath: string = resolve(gatlingProjectRootPath, 'puppeteer');
const simulationEntry: string = process.env.GATLING_SIMULATIONS || 'branch.DemoJourney';
if (!Fs.existsSync(gatlingProjectRootPath)) {
@@ -52,6 +53,15 @@ export async function GatlingTestRunner({ getService }: FtrProviderContext) {
const log = getService('log');
await withProcRunner(log, async (procs) => {
+ await procs.run('node build/index.js', {
+ cmd: 'node',
+ args: ['build/index.js'],
+ cwd: puppeteerProjectRootPath,
+ env: {
+ ...process.env,
+ },
+ wait: true,
+ });
for (let i = 0; i < simulationClasses.length; i++) {
await procs.run('gatling: test', {
cmd: 'mvn',
diff --git a/x-pack/test/stack_functional_integration/apps/ccs/ccs_discover.js b/x-pack/test/stack_functional_integration/apps/ccs/ccs_discover.js
index a22e4438c7dbdd..588ff9a6e9f928 100644
--- a/x-pack/test/stack_functional_integration/apps/ccs/ccs_discover.js
+++ b/x-pack/test/stack_functional_integration/apps/ccs/ccs_discover.js
@@ -5,16 +5,7 @@
* 2.0.
*/
-import fs from 'fs';
-import { resolve } from 'path';
import expect from '@kbn/expect';
-import { Client as EsClient } from '@elastic/elasticsearch';
-import { KbnClient } from '@kbn/test';
-import { EsArchiver } from '@kbn/es-archiver';
-import { CA_CERT_PATH, REPO_ROOT } from '@kbn/dev-utils';
-
-const INTEGRATION_TEST_ROOT = process.env.WORKSPACE || resolve(REPO_ROOT, '../integration-test');
-const ARCHIVE = resolve(INTEGRATION_TEST_ROOT, 'test/es_archives/metricbeat');
export default ({ getService, getPageObjects }) => {
describe('Cross cluster search test in discover', async () => {
@@ -212,151 +203,5 @@ export default ({ getService, getPageObjects }) => {
expect(hitCount).to.be.lessThan(originalHitCount);
});
});
-
- describe('Detection engine', async function () {
- const supertest = getService('supertest');
- const esSupertest = getService('esSupertest');
- const config = getService('config');
-
- const esClient = new EsClient({
- ssl: {
- ca: fs.readFileSync(CA_CERT_PATH, 'utf-8'),
- },
- nodes: [process.env.TEST_ES_URLDATA],
- requestTimeout: config.get('timeouts.esRequestTimeout'),
- });
-
- const kbnClient = new KbnClient({
- log,
- url: process.env.TEST_KIBANA_URLDATA,
- certificateAuthorities: config.get('servers.kibana.certificateAuthorities'),
- uiSettingDefaults: kibanaServer.uiSettings,
- });
-
- const esArchiver = new EsArchiver({
- log,
- client: esClient,
- kbnClient,
- });
-
- let signalsId;
- let dataId;
- let ruleId;
-
- before('Prepare .siem-signal-*', async function () {
- log.info('Create index');
- // visit app/security so to create .siem-signals-* as side effect
- await PageObjects.common.navigateToApp('security', { insertTimestamp: false });
-
- log.info('Create index pattern');
- signalsId = await supertest
- .post('/api/index_patterns/index_pattern')
- .set('kbn-xsrf', 'true')
- .send({
- index_pattern: {
- title: '.siem-signals-*',
- },
- override: true,
- })
- .expect(200)
- .then((res) => JSON.parse(res.text).index_pattern.id);
- log.debug('id: ' + signalsId);
- });
-
- before('Prepare data:metricbeat-*', async function () {
- log.info('Create index');
- await esArchiver.load(ARCHIVE);
-
- log.info('Create index pattern');
- dataId = await supertest
- .post('/api/index_patterns/index_pattern')
- .set('kbn-xsrf', 'true')
- .send({
- index_pattern: {
- title: 'data:metricbeat-*',
- },
- override: true,
- })
- .expect(200)
- .then((res) => JSON.parse(res.text).index_pattern.id);
- log.debug('id: ' + dataId);
- });
-
- before('Add detection rule', async function () {
- ruleId = await supertest
- .post('/api/detection_engine/rules')
- .set('kbn-xsrf', 'true')
- .send({
- description: 'This is the description of the rule',
- risk_score: 17,
- severity: 'low',
- interval: '10s',
- name: 'CCS_Detection_test',
- type: 'query',
- from: 'now-1y',
- index: ['data:metricbeat-*'],
- query: '*:*',
- language: 'kuery',
- enabled: true,
- })
- .expect(200)
- .then((res) => JSON.parse(res.text).id);
- log.debug('id: ' + ruleId);
- });
-
- after('Clean up detection rule', async function () {
- if (ruleId !== undefined) {
- log.debug('id: ' + ruleId);
- await supertest
- .delete('/api/detection_engine/rules?id=' + ruleId)
- .set('kbn-xsrf', 'true')
- .expect(200);
- }
- });
-
- after('Clean up data:metricbeat-*', async function () {
- if (dataId !== undefined) {
- log.info('Delete index pattern');
- log.debug('id: ' + dataId);
- await supertest
- .delete('/api/index_patterns/index_pattern/' + dataId)
- .set('kbn-xsrf', 'true')
- .expect(200);
- }
-
- log.info('Delete index');
- await esArchiver.unload(ARCHIVE);
- });
-
- after('Clean up .siem-signal-*', async function () {
- if (signalsId !== undefined) {
- log.info('Delete index pattern: .siem-signals-*');
- log.debug('id: ' + signalsId);
- await supertest
- .delete('/api/index_patterns/index_pattern/' + signalsId)
- .set('kbn-xsrf', 'true')
- .expect(200);
- }
-
- log.info('Delete index alias: .siem-signals-default');
- await esSupertest
- .delete('/.siem-signals-default-000001/_alias/.siem-signals-default')
- .expect(200);
-
- log.info('Delete index: .siem-signals-default-000001');
- await esSupertest.delete('/.siem-signals-default-000001').expect(200);
- });
-
- it('Should generate alerts based on remote events', async function () {
- log.info('Check if any alert got to .siem-signals-*');
- await PageObjects.common.navigateToApp('discover', { insertTimestamp: false });
- await PageObjects.discover.selectIndexPattern('.siem-signals-*');
- await retry.tryForTime(30000, async () => {
- const hitCount = await PageObjects.discover.getHitCount();
- log.debug('### hit count = ' + hitCount);
- expect(hitCount).to.be('100');
- });
- });
- });
});
};
diff --git a/x-pack/test/stack_functional_integration/apps/monitoring/_monitoring_metricbeat.js b/x-pack/test/stack_functional_integration/apps/monitoring/_monitoring_metricbeat.js
index d8341c56aa25cc..9dcc18b3c3f203 100644
--- a/x-pack/test/stack_functional_integration/apps/monitoring/_monitoring_metricbeat.js
+++ b/x-pack/test/stack_functional_integration/apps/monitoring/_monitoring_metricbeat.js
@@ -12,6 +12,7 @@ export default ({ getService, getPageObjects }) => {
const log = getService('log');
const testSubjects = getService('testSubjects');
const isSaml = !!process.env.VM.includes('saml') || !!process.env.VM.includes('oidc');
+ const clusterOverview = getService('monitoringClusterOverview');
before(async () => {
await browser.setWindowSize(1200, 800);
@@ -25,6 +26,7 @@ export default ({ getService, getPageObjects }) => {
}
// navigateToApp without a username and password will default to the superuser
await PageObjects.common.navigateToApp('monitoring', { insertTimestamp: false });
+ await clusterOverview.closeAlertsModal();
});
it('should have Monitoring already enabled', async () => {
diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json
index 0424891064cd33..cd43e7108b06df 100644
--- a/x-pack/test/tsconfig.json
+++ b/x-pack/test/tsconfig.json
@@ -55,7 +55,6 @@
{ "path": "../plugins/banners/tsconfig.json" },
{ "path": "../plugins/cases/tsconfig.json" },
{ "path": "../plugins/cloud/tsconfig.json" },
- { "path": "../plugins/console_extensions/tsconfig.json" },
{ "path": "../plugins/dashboard_mode/tsconfig.json" },
{ "path": "../plugins/enterprise_search/tsconfig.json" },
{ "path": "../plugins/fleet/tsconfig.json" },
diff --git a/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts b/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts
index 73819b5bac695c..0bc3cd7c2610eb 100644
--- a/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts
+++ b/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts
@@ -16,6 +16,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
const renderable = getService('renderable');
const dashboardExpect = getService('dashboardExpect');
const PageObjects = getPageObjects(['common', 'header', 'home', 'dashboard', 'timePicker']);
+ const kibanaServer = getService('kibanaServer');
+ const browser = getService('browser');
describe('dashboard smoke tests', function describeIndexTests() {
const spaces = [
@@ -36,6 +38,14 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
basePath,
});
await PageObjects.header.waitUntilLoadingHasFinished();
+ await kibanaServer.uiSettings.update(
+ {
+ 'visualization:visualize:legacyChartsLibrary': true,
+ 'visualization:visualize:legacyPieChartsLibrary': true,
+ },
+ { space }
+ );
+ await browser.refresh();
});
dashboardTests.forEach(({ name, numPanels }) => {
it('should launch sample ' + name + ' data set dashboard', async () => {
@@ -56,9 +66,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await renderable.waitForRender();
log.debug('Checking pie charts rendered');
await pieChart.expectPieSliceCount(4);
- // https://github.com/elastic/kibana/issues/92887
- // log.debug('Checking area, bar and heatmap charts rendered');
- // await dashboardExpect.seriesElementCount(15);
+ log.debug('Checking area, bar and heatmap charts rendered');
+ await dashboardExpect.seriesElementCount(15);
log.debug('Checking saved searches rendered');
await dashboardExpect.savedSearchRowCount(49);
log.debug('Checking input controls rendered');
diff --git a/x-pack/test/upgrade/apps/reporting/reporting_smoke_tests.ts b/x-pack/test/upgrade/apps/reporting/reporting_smoke_tests.ts
index c00e761d54226a..20fc34f77dbf80 100644
--- a/x-pack/test/upgrade/apps/reporting/reporting_smoke_tests.ts
+++ b/x-pack/test/upgrade/apps/reporting/reporting_smoke_tests.ts
@@ -69,6 +69,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
if (type === 'pdf_optimize') {
await testSubjects.click('usePrintLayout');
}
+ const advOpt = await find.byXPath(`//button[descendant::*[text()='Advanced options']]`);
+ await advOpt.click();
const postUrl = await find.byXPath(`//button[descendant::*[text()='Copy POST URL']]`);
await postUrl.click();
const url = await browser.getClipboardValue();
diff --git a/yarn.lock b/yarn.lock
index dccbd8f91a4299..7a70953379f54e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1389,10 +1389,10 @@
dependencies:
object-hash "^1.3.0"
-"@elastic/charts@31.1.0":
- version "31.1.0"
- resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-31.1.0.tgz#cebfd45e672ab19d7d6c5a7f7e3115a6eaa41e8f"
- integrity sha512-D2zPT7CRweRdbfhO9gd1+YBm0ETdJsEkh+Su0I6tleINqKKuSB+kPOG6t+fm0+HsR72pX4dKvT60ikZJZ3fRhg==
+"@elastic/charts@32.0.0":
+ version "32.0.0"
+ resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-32.0.0.tgz#f747b8fa931027ba7476a6284be03383cd6a6dab"
+ integrity sha512-3cvX0Clezocd6/T2R5h3+nilPdIgWrO+it043giyW5U0pAtFC5P+5VyNEjn22LlD3zzbndxAbXHSj0QDvHXOBw==
dependencies:
"@popperjs/core" "^2.4.0"
chroma-js "^2.1.0"