From 60176bcffdcbdb75b48823f4783923528797efe0 Mon Sep 17 00:00:00 2001
From: Vitalii Dmyterko <92328789+vitaliidm@users.noreply.github.com>
Date: Thu, 19 Sep 2024 14:45:41 +0100
Subject: [PATCH] [Security Solution][Detection Engine] log ES requests when
running rule preview (#191107)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Summary
**Status:** works only for **ES|QL and EQL** rule types
When clicking on "Show Elasticsearch requests, ran during rule
executions" preview would return logged Elasticsearch queries that can
be used to debug/explore rule execution.
Each rule execution accordion has time rule execution started and its
duration.
Upon opening accordion: it will display ES requests with their
description and duration.
**NOTE**: Only search requests are returned, not the requests that
create actual alerts
Feature flag: **loggingRequestsEnabled**
On week Demo([internal
link](https://drive.google.com/drive/folders/1l-cDhbiMxykNH6BzIxFAnLeibmV9a4Cz))
### Video demo (older UI)
https://github.com/user-attachments/assets/26f963da-c528-447c-9efd-350b4d42b52c
### Up to date UI
#### UI control
#### List of executions and code blocks
### Checklist
Delete any items that are not applicable to this PR.
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
🎉 All tests passed! -
[kibana-flaky-test-suite-runner#6909](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/6909)
[✅] [Serverless] Security Solution Detection Engine - Cypress: 100/100
tests passed.
[✅] Security Solution Detection Engine - Cypress: 100/100 tests passed.
FTR tests -
https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/6918
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
---
.../rule_preview/rule_preview.gen.ts | 18 ++
.../rule_preview/rule_preview.schema.yaml | 23 ++
.../common/api/quickstart_client.gen.ts | 3 +
.../common/experimental_features.ts | 5 +
...ections_api_2023_10_31.bundled.schema.yaml | 24 ++
...ections_api_2023_10_31.bundled.schema.yaml | 24 ++
.../rule_preview/__mocks__/preview_logs.ts | 93 ++++++
.../components/rule_preview/index.test.tsx | 68 ++++-
.../components/rule_preview/index.tsx | 48 ++-
.../rule_preview/logged_requests.test.tsx | 68 +++++
.../rule_preview/logged_requests.tsx | 58 ++++
.../rule_preview/logged_requests_item.tsx | 79 +++++
.../rule_preview/optimized_accordion.test.tsx | 59 ++++
.../rule_preview/optimized_accordion.tsx | 39 +++
.../components/rule_preview/preview_logs.tsx | 20 +-
.../components/rule_preview/translations.ts | 21 ++
.../rule_preview/use_accordion_styling.ts | 16 +
.../rule_preview/use_preview_route.tsx | 3 +
.../rule_preview/use_preview_rule.ts | 5 +-
.../rule_management/api/api.test.ts | 15 +
.../rule_management/api/api.ts | 2 +
.../rule_management/logic/types.ts | 6 +-
.../rule_preview/api/preview_rules/route.ts | 23 +-
.../create_security_rule_type_wrapper.ts | 6 +-
.../rule_types/eql/create_eql_alert_type.ts | 5 +-
.../rule_types/eql/eql.test.ts | 8 +-
.../detection_engine/rule_types/eql/eql.ts | 33 ++-
.../detection_engine/rule_types/esql/esql.ts | 279 ++++++++++--------
.../rule_types/esql/fetch_source_documents.ts | 31 +-
.../rule_types/translations.ts | 29 ++
.../lib/detection_engine/rule_types/types.ts | 9 +-
.../rule_types/utils/logged_requests/index.ts | 10 +
.../utils/logged_requests/log_eql.ts | 19 ++
.../utils/logged_requests/log_esql.ts | 15 +
.../utils/logged_requests/log_query.ts | 35 +++
.../services/security_solution_api.gen.ts | 9 +-
.../config/ess/config.base.ts | 1 +
.../configs/serverless.config.ts | 5 +-
.../execution_logic/eql.ts | 27 ++
.../execution_logic/esql.ts | 58 ++++
.../utils/rules/preview_rule.ts | 3 +
.../test/security_solution_cypress/config.ts | 5 +-
.../detection_engine/rule_edit/preview.cy.ts | 85 ++++++
.../cypress/screens/create_new_rule.ts | 16 +
.../cypress/tasks/create_new_rule.ts | 20 ++
.../serverless_config.ts | 5 +-
46 files changed, 1268 insertions(+), 165 deletions(-)
create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/__mocks__/preview_logs.ts
create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/logged_requests.test.tsx
create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/logged_requests.tsx
create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/logged_requests_item.tsx
create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/optimized_accordion.test.tsx
create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/optimized_accordion.tsx
create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/use_accordion_styling.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/translations.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/logged_requests/index.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/logged_requests/log_eql.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/logged_requests/log_esql.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/logged_requests/log_query.ts
create mode 100644 x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/preview.cy.ts
diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_preview/rule_preview.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_preview/rule_preview.gen.ts
index 0e7fb75c2c4c2..ad9b6d9ea12c2 100644
--- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_preview/rule_preview.gen.ts
+++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_preview/rule_preview.gen.ts
@@ -15,6 +15,7 @@
*/
import { z } from '@kbn/zod';
+import { BooleanFromString } from '@kbn/zod-helpers';
import {
EqlRuleCreateProps,
@@ -34,6 +35,13 @@ export const RulePreviewParams = z.object({
timeframeEnd: z.string().datetime(),
});
+export type RulePreviewLoggedRequest = z.infer;
+export const RulePreviewLoggedRequest = z.object({
+ request: NonEmptyString,
+ description: NonEmptyString.optional(),
+ duration: z.number().int().optional(),
+});
+
export type RulePreviewLogs = z.infer;
export const RulePreviewLogs = z.object({
errors: z.array(NonEmptyString),
@@ -43,7 +51,17 @@ export const RulePreviewLogs = z.object({
*/
duration: z.number().int(),
startedAt: NonEmptyString.optional(),
+ requests: z.array(RulePreviewLoggedRequest).optional(),
+});
+
+export type RulePreviewRequestQuery = z.infer;
+export const RulePreviewRequestQuery = z.object({
+ /**
+ * Enables logging and returning in response ES queries, performed during rule execution
+ */
+ enable_logged_requests: BooleanFromString.optional(),
});
+export type RulePreviewRequestQueryInput = z.input;
export type RulePreviewRequestBody = z.infer;
export const RulePreviewRequestBody = z.discriminatedUnion('type', [
diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_preview/rule_preview.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/rule_preview/rule_preview.schema.yaml
index 1d65d6b4e037e..400b84e533a02 100644
--- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_preview/rule_preview.schema.yaml
+++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_preview/rule_preview.schema.yaml
@@ -11,6 +11,13 @@ paths:
summary: Preview rule alerts generated on specified time range
tags:
- Rule preview API
+ parameters:
+ - name: enable_logged_requests
+ in: query
+ description: Enables logging and returning in response ES queries, performed during rule execution
+ required: false
+ schema:
+ type: boolean
requestBody:
description: An object containing tags to add or remove and alert ids the changes will be applied
required: true
@@ -94,6 +101,18 @@ components:
format: date-time
required: [invocationCount, timeframeEnd]
+ RulePreviewLoggedRequest:
+ type: object
+ properties:
+ request:
+ $ref: '../../model/primitives.schema.yaml#/components/schemas/NonEmptyString'
+ description:
+ $ref: '../../model/primitives.schema.yaml#/components/schemas/NonEmptyString'
+ duration:
+ type: integer
+ required:
+ - request
+
RulePreviewLogs:
type: object
properties:
@@ -110,6 +129,10 @@ components:
description: Execution duration in milliseconds
startedAt:
$ref: '../../model/primitives.schema.yaml#/components/schemas/NonEmptyString'
+ requests:
+ type: array
+ items:
+ $ref: '#/components/schemas/RulePreviewLoggedRequest'
required:
- errors
- warnings
diff --git a/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts b/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts
index 5c9d33858d0f7..bb564dbe69b34 100644
--- a/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts
+++ b/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts
@@ -99,6 +99,7 @@ import type {
GetRuleExecutionResultsResponse,
} from './detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_results/get_rule_execution_results_route.gen';
import type {
+ RulePreviewRequestQueryInput,
RulePreviewRequestBodyInput,
RulePreviewResponse,
} from './detection_engine/rule_preview/rule_preview.gen';
@@ -1763,6 +1764,7 @@ detection engine rules.
},
method: 'POST',
body: props.body,
+ query: props.query,
})
.catch(catchAxiosErrorFormatAndThrow);
}
@@ -2160,6 +2162,7 @@ export interface ResolveTimelineProps {
query: ResolveTimelineRequestQueryInput;
}
export interface RulePreviewProps {
+ query: RulePreviewRequestQueryInput;
body: RulePreviewRequestBodyInput;
}
export interface SearchAlertsProps {
diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts
index 4147404e940c1..030e00768349d 100644
--- a/x-pack/plugins/security_solution/common/experimental_features.ts
+++ b/x-pack/plugins/security_solution/common/experimental_features.ts
@@ -138,6 +138,11 @@ export const allowedExperimentalValues = Object.freeze({
*/
esqlRulesDisabled: false,
+ /**
+ * enables logging requests during rule preview
+ */
+ loggingRequestsEnabled: false,
+
/**
* Enables Protection Updates tab in the Endpoint Policy Details page
*/
diff --git a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_detections_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_detections_api_2023_10_31.bundled.schema.yaml
index 0df64f51f37ce..b9c1ac658fd90 100644
--- a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_detections_api_2023_10_31.bundled.schema.yaml
+++ b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_detections_api_2023_10_31.bundled.schema.yaml
@@ -891,6 +891,15 @@ paths:
/api/detection_engine/rules/preview:
post:
operationId: RulePreview
+ parameters:
+ - description: >-
+ Enables logging and returning in response ES queries, performed
+ during rule execution
+ in: query
+ name: enable_logged_requests
+ required: false
+ schema:
+ type: boolean
requestBody:
content:
application/json:
@@ -5178,6 +5187,17 @@ components:
- $ref: '#/components/schemas/MachineLearningRulePatchProps'
- $ref: '#/components/schemas/NewTermsRulePatchProps'
- $ref: '#/components/schemas/EsqlRulePatchProps'
+ RulePreviewLoggedRequest:
+ type: object
+ properties:
+ description:
+ $ref: '#/components/schemas/NonEmptyString'
+ duration:
+ type: integer
+ request:
+ $ref: '#/components/schemas/NonEmptyString'
+ required:
+ - request
RulePreviewLogs:
type: object
properties:
@@ -5188,6 +5208,10 @@ components:
items:
$ref: '#/components/schemas/NonEmptyString'
type: array
+ requests:
+ items:
+ $ref: '#/components/schemas/RulePreviewLoggedRequest'
+ type: array
startedAt:
$ref: '#/components/schemas/NonEmptyString'
warnings:
diff --git a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_detections_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_detections_api_2023_10_31.bundled.schema.yaml
index 4699de9a25228..d1de42913c4e0 100644
--- a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_detections_api_2023_10_31.bundled.schema.yaml
+++ b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_detections_api_2023_10_31.bundled.schema.yaml
@@ -476,6 +476,15 @@ paths:
/api/detection_engine/rules/preview:
post:
operationId: RulePreview
+ parameters:
+ - description: >-
+ Enables logging and returning in response ES queries, performed
+ during rule execution
+ in: query
+ name: enable_logged_requests
+ required: false
+ schema:
+ type: boolean
requestBody:
content:
application/json:
@@ -4331,6 +4340,17 @@ components:
- $ref: '#/components/schemas/MachineLearningRulePatchProps'
- $ref: '#/components/schemas/NewTermsRulePatchProps'
- $ref: '#/components/schemas/EsqlRulePatchProps'
+ RulePreviewLoggedRequest:
+ type: object
+ properties:
+ description:
+ $ref: '#/components/schemas/NonEmptyString'
+ duration:
+ type: integer
+ request:
+ $ref: '#/components/schemas/NonEmptyString'
+ required:
+ - request
RulePreviewLogs:
type: object
properties:
@@ -4341,6 +4361,10 @@ components:
items:
$ref: '#/components/schemas/NonEmptyString'
type: array
+ requests:
+ items:
+ $ref: '#/components/schemas/RulePreviewLoggedRequest'
+ type: array
startedAt:
$ref: '#/components/schemas/NonEmptyString'
warnings:
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/__mocks__/preview_logs.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/__mocks__/preview_logs.ts
new file mode 100644
index 0000000000000..1e380d1bb4561
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/__mocks__/preview_logs.ts
@@ -0,0 +1,93 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { RulePreviewLogs } from '../../../../../../common/api/detection_engine';
+
+export const previewLogs: RulePreviewLogs[] = [
+ {
+ errors: [],
+ warnings: [],
+ startedAt: '2024-09-05T15:43:46.972Z',
+ duration: 149,
+ requests: [
+ {
+ request:
+ 'POST _query\n{\n "query": "FROM packetbeat-8.14.2 metadata _id, _version, _index | limit 101",\n "filter": {\n "bool": {\n "filter": [\n {\n "range": {\n "@timestamp": {\n "lte": "2024-09-05T15:43:46.972Z",\n "gte": "2024-09-05T15:22:46.972Z",\n "format": "strict_date_optional_time"\n }\n }\n },\n {\n "bool": {\n "must": [],\n "filter": [],\n "should": [],\n "must_not": []\n }\n }\n ]\n }\n }\n}',
+ description: 'ES|QL request to find all matches',
+ duration: 23,
+ },
+ {
+ request:
+ 'POST /packetbeat-8.14.2/_search?ignore_unavailable=true\n{\n "query": {\n "bool": {\n "filter": {\n "ids": {\n "values": [\n "yB7awpEBluhaSO8ejVKZ",\n "yR7awpEBluhaSO8ejVKZ",\n "yh7awpEBluhaSO8ejVKZ",\n "yx7awpEBluhaSO8ejVKZ",\n "zB7awpEBluhaSO8ejVKZ",\n "zR7awpEBluhaSO8ejVKZ",\n "zh7awpEBluhaSO8ejVKZ",\n "zx7awpEBluhaSO8ejVKZ",\n "0B7awpEBluhaSO8ejVKZ",\n "0R7awpEBluhaSO8ejVKZ",\n "0h7awpEBluhaSO8ejVKZ",\n "0x7awpEBluhaSO8ejVKZ",\n "1B7awpEBluhaSO8ejVKZ",\n "1R7awpEBluhaSO8ejVKZ",\n "1h7awpEBluhaSO8ejVKZ",\n "1x7awpEBluhaSO8ejVKZ",\n "2B7awpEBluhaSO8ejVKZ",\n "2R7awpEBluhaSO8ejVKZ",\n "2h7awpEBluhaSO8ejVKZ",\n "2x7awpEBluhaSO8ejVKZ",\n "3B7awpEBluhaSO8ejVKZ",\n "3R7awpEBluhaSO8ejVKZ",\n "3h7awpEBluhaSO8ejVKZ",\n "3x7awpEBluhaSO8ejVKZ",\n "4B7awpEBluhaSO8ejVKZ",\n "4R7awpEBluhaSO8ejVKZ",\n "4h7awpEBluhaSO8ejVKZ",\n "4x7awpEBluhaSO8ejVKZ",\n "5B7awpEBluhaSO8ejVKZ",\n "5R7awpEBluhaSO8ejVKZ",\n "5h7awpEBluhaSO8ejVKZ",\n "5x7awpEBluhaSO8ejVKZ",\n "6B7awpEBluhaSO8ejVKZ",\n "6R7awpEBluhaSO8ejVKZ",\n "6h7awpEBluhaSO8ejVKZ",\n "6x7awpEBluhaSO8ejVKZ",\n "7B7awpEBluhaSO8ejVKZ",\n "7R7awpEBluhaSO8ejVKZ",\n "7h7awpEBluhaSO8ejVKZ",\n "7x7awpEBluhaSO8ejVKZ",\n "8B7awpEBluhaSO8ejVKZ",\n "8R7awpEBluhaSO8ejVKZ",\n "8h7awpEBluhaSO8ejVKZ",\n "8x7awpEBluhaSO8ejVKZ",\n "9B7awpEBluhaSO8ejVKZ",\n "9R7awpEBluhaSO8ejVKZ",\n "9h7awpEBluhaSO8ejVKZ",\n "9x7awpEBluhaSO8ejVKZ",\n "-B7awpEBluhaSO8ejVKZ",\n "-R7awpEBluhaSO8ejVKZ",\n "-h7awpEBluhaSO8ejVKZ",\n "-x7awpEBluhaSO8ejVKZ",\n "_B7awpEBluhaSO8ejVKZ",\n "_R7awpEBluhaSO8ejVKZ",\n "_h7awpEBluhaSO8ejVKZ",\n "_x7awpEBluhaSO8ejVKZ",\n "AB7awpEBluhaSO8ejVOZ",\n "AR7awpEBluhaSO8ejVOZ",\n "Ah7awpEBluhaSO8ejVOZ",\n "Ax7awpEBluhaSO8ejVOZ",\n "BB7awpEBluhaSO8ejVOZ",\n "BR7awpEBluhaSO8ejVOZ",\n "Bh7awpEBluhaSO8ejVOZ",\n "Bx7awpEBluhaSO8ejVOZ",\n "CB7awpEBluhaSO8ejVOZ",\n "CR7awpEBluhaSO8ejVOZ",\n "Ch7awpEBluhaSO8ejVOZ",\n "Cx7awpEBluhaSO8ejVOZ",\n "DB7awpEBluhaSO8ejVOZ",\n "DR7awpEBluhaSO8ejVOZ",\n "Dh7awpEBluhaSO8ejVOZ",\n "Dx7awpEBluhaSO8ejVOZ",\n "EB7awpEBluhaSO8ejVOZ",\n "ER7awpEBluhaSO8ejVOZ",\n "Eh7awpEBluhaSO8ejVOZ",\n "Ex7awpEBluhaSO8ejVOZ",\n "FB7awpEBluhaSO8ejVOZ",\n "FR7awpEBluhaSO8ejVOZ",\n "Fh7awpEBluhaSO8ejVOZ",\n "Fx7awpEBluhaSO8ejVOZ",\n "GB7awpEBluhaSO8ejVOZ",\n "GR7awpEBluhaSO8ejVOZ",\n "Gh7awpEBluhaSO8ejVOZ",\n "Gx7awpEBluhaSO8ejVOZ",\n "HB7awpEBluhaSO8ejVOZ",\n "HR7awpEBluhaSO8ejVOZ",\n "Hh7awpEBluhaSO8ejVOZ",\n "Hx7awpEBluhaSO8ejVOZ",\n "IB7awpEBluhaSO8ejVOZ",\n "IR7awpEBluhaSO8ejVOZ",\n "Ih7awpEBluhaSO8ejVOZ",\n "Ix7awpEBluhaSO8ejVOZ",\n "JB7awpEBluhaSO8ejVOZ",\n "JR7awpEBluhaSO8ejVOZ",\n "Jh7awpEBluhaSO8ejVOZ",\n "Jx7awpEBluhaSO8ejVOZ",\n "KB7awpEBluhaSO8ejVOZ",\n "KR7awpEBluhaSO8ejVOZ",\n "Kh7awpEBluhaSO8ejVOZ",\n "Kx7awpEBluhaSO8ejVOZ",\n "LB7awpEBluhaSO8ejVOZ"\n ]\n }\n }\n }\n },\n "_source": false,\n "fields": [\n "*"\n ]\n}',
+ description: 'Retrieve source documents when ES|QL query is not aggregable',
+ duration: 8,
+ },
+ ],
+ },
+ {
+ errors: [],
+ warnings: [],
+ startedAt: '2024-09-05T16:03:46.972Z',
+ duration: 269,
+ requests: [
+ {
+ request:
+ 'POST _query\n{\n "query": "FROM packetbeat-8.14.2 metadata _id, _version, _index | limit 101",\n "filter": {\n "bool": {\n "filter": [\n {\n "range": {\n "@timestamp": {\n "lte": "2024-09-05T16:03:46.972Z",\n "gte": "2024-09-05T15:42:46.972Z",\n "format": "strict_date_optional_time"\n }\n }\n },\n {\n "bool": {\n "must": [],\n "filter": [],\n "should": [],\n "must_not": []\n }\n }\n ]\n }\n }\n}',
+ description: 'ES|QL request to find all matches',
+ duration: 30,
+ },
+ {
+ request:
+ 'POST /packetbeat-8.14.2/_search?ignore_unavailable=true\n{\n "query": {\n "bool": {\n "filter": {\n "ids": {\n "values": [\n "yB7awpEBluhaSO8ejVKZ",\n "yR7awpEBluhaSO8ejVKZ",\n "yh7awpEBluhaSO8ejVKZ",\n "yx7awpEBluhaSO8ejVKZ",\n "zB7awpEBluhaSO8ejVKZ",\n "zR7awpEBluhaSO8ejVKZ",\n "zh7awpEBluhaSO8ejVKZ",\n "zx7awpEBluhaSO8ejVKZ",\n "0B7awpEBluhaSO8ejVKZ",\n "0R7awpEBluhaSO8ejVKZ",\n "0h7awpEBluhaSO8ejVKZ",\n "0x7awpEBluhaSO8ejVKZ",\n "1B7awpEBluhaSO8ejVKZ",\n "1R7awpEBluhaSO8ejVKZ",\n "1h7awpEBluhaSO8ejVKZ",\n "1x7awpEBluhaSO8ejVKZ",\n "2B7awpEBluhaSO8ejVKZ",\n "2R7awpEBluhaSO8ejVKZ",\n "2h7awpEBluhaSO8ejVKZ",\n "2x7awpEBluhaSO8ejVKZ",\n "3B7awpEBluhaSO8ejVKZ",\n "3R7awpEBluhaSO8ejVKZ",\n "3h7awpEBluhaSO8ejVKZ",\n "3x7awpEBluhaSO8ejVKZ",\n "4B7awpEBluhaSO8ejVKZ",\n "4R7awpEBluhaSO8ejVKZ",\n "4h7awpEBluhaSO8ejVKZ",\n "4x7awpEBluhaSO8ejVKZ",\n "5B7awpEBluhaSO8ejVKZ",\n "5R7awpEBluhaSO8ejVKZ",\n "5h7awpEBluhaSO8ejVKZ",\n "5x7awpEBluhaSO8ejVKZ",\n "6B7awpEBluhaSO8ejVKZ",\n "6R7awpEBluhaSO8ejVKZ",\n "6h7awpEBluhaSO8ejVKZ",\n "6x7awpEBluhaSO8ejVKZ",\n "7B7awpEBluhaSO8ejVKZ",\n "7R7awpEBluhaSO8ejVKZ",\n "7h7awpEBluhaSO8ejVKZ",\n "7x7awpEBluhaSO8ejVKZ",\n "8B7awpEBluhaSO8ejVKZ",\n "8R7awpEBluhaSO8ejVKZ",\n "8h7awpEBluhaSO8ejVKZ",\n "8x7awpEBluhaSO8ejVKZ",\n "9B7awpEBluhaSO8ejVKZ",\n "9R7awpEBluhaSO8ejVKZ",\n "9h7awpEBluhaSO8ejVKZ",\n "9x7awpEBluhaSO8ejVKZ",\n "-B7awpEBluhaSO8ejVKZ",\n "-R7awpEBluhaSO8ejVKZ",\n "-h7awpEBluhaSO8ejVKZ",\n "-x7awpEBluhaSO8ejVKZ",\n "_B7awpEBluhaSO8ejVKZ",\n "_R7awpEBluhaSO8ejVKZ",\n "_h7awpEBluhaSO8ejVKZ",\n "_x7awpEBluhaSO8ejVKZ",\n "AB7awpEBluhaSO8ejVOZ",\n "AR7awpEBluhaSO8ejVOZ",\n "Ah7awpEBluhaSO8ejVOZ",\n "Ax7awpEBluhaSO8ejVOZ",\n "BB7awpEBluhaSO8ejVOZ",\n "BR7awpEBluhaSO8ejVOZ",\n "Bh7awpEBluhaSO8ejVOZ",\n "Bx7awpEBluhaSO8ejVOZ",\n "CB7awpEBluhaSO8ejVOZ",\n "CR7awpEBluhaSO8ejVOZ",\n "Ch7awpEBluhaSO8ejVOZ",\n "Cx7awpEBluhaSO8ejVOZ",\n "DB7awpEBluhaSO8ejVOZ",\n "DR7awpEBluhaSO8ejVOZ",\n "Dh7awpEBluhaSO8ejVOZ",\n "Dx7awpEBluhaSO8ejVOZ",\n "EB7awpEBluhaSO8ejVOZ",\n "ER7awpEBluhaSO8ejVOZ",\n "Eh7awpEBluhaSO8ejVOZ",\n "Ex7awpEBluhaSO8ejVOZ",\n "FB7awpEBluhaSO8ejVOZ",\n "FR7awpEBluhaSO8ejVOZ",\n "Fh7awpEBluhaSO8ejVOZ",\n "Fx7awpEBluhaSO8ejVOZ",\n "GB7awpEBluhaSO8ejVOZ",\n "GR7awpEBluhaSO8ejVOZ",\n "Gh7awpEBluhaSO8ejVOZ",\n "Gx7awpEBluhaSO8ejVOZ",\n "HB7awpEBluhaSO8ejVOZ",\n "HR7awpEBluhaSO8ejVOZ",\n "Hh7awpEBluhaSO8ejVOZ",\n "Hx7awpEBluhaSO8ejVOZ",\n "IB7awpEBluhaSO8ejVOZ",\n "IR7awpEBluhaSO8ejVOZ",\n "Ih7awpEBluhaSO8ejVOZ",\n "Ix7awpEBluhaSO8ejVOZ",\n "JB7awpEBluhaSO8ejVOZ",\n "JR7awpEBluhaSO8ejVOZ",\n "Jh7awpEBluhaSO8ejVOZ",\n "Jx7awpEBluhaSO8ejVOZ",\n "KB7awpEBluhaSO8ejVOZ",\n "KR7awpEBluhaSO8ejVOZ",\n "Kh7awpEBluhaSO8ejVOZ",\n "Kx7awpEBluhaSO8ejVOZ",\n "LB7awpEBluhaSO8ejVOZ"\n ]\n }\n }\n }\n },\n "_source": false,\n "fields": [\n "*"\n ]\n}',
+ description: 'Retrieve source documents when ES|QL query is not aggregable',
+ duration: 6,
+ },
+ {
+ request:
+ 'POST _query\n{\n "query": "FROM packetbeat-8.14.2 metadata _id, _version, _index | limit 201",\n "filter": {\n "bool": {\n "filter": [\n {\n "range": {\n "@timestamp": {\n "lte": "2024-09-05T16:03:46.972Z",\n "gte": "2024-09-05T15:42:46.972Z",\n "format": "strict_date_optional_time"\n }\n }\n },\n {\n "bool": {\n "must": [],\n "filter": [],\n "should": [],\n "must_not": []\n }\n }\n ]\n }\n }\n}',
+ description: 'ES|QL request to find all matches',
+ },
+ {
+ request:
+ 'POST /packetbeat-8.14.2/_search?ignore_unavailable=true\n{\n "query": {\n "bool": {\n "filter": {\n "ids": {\n "values": [\n "LB7awpEBluhaSO8ejVOZ",\n "LR7awpEBluhaSO8ejVOZ",\n "Lh7awpEBluhaSO8ejVOZ",\n "Lx7awpEBluhaSO8ejVOZ",\n "MB7awpEBluhaSO8ejVOZ",\n "MR7awpEBluhaSO8ejVOZ",\n "Mh7awpEBluhaSO8ejVOZ",\n "Mx7awpEBluhaSO8ejVOZ",\n "NB7awpEBluhaSO8ejVOZ",\n "NR7awpEBluhaSO8ejVOZ",\n "Nh7awpEBluhaSO8ejVOZ",\n "Nx7awpEBluhaSO8ejVOZ",\n "OB7awpEBluhaSO8ejVOZ",\n "OR7awpEBluhaSO8ejVOZ",\n "Oh7awpEBluhaSO8ejVOZ",\n "Ox7awpEBluhaSO8ejVOZ",\n "PB7awpEBluhaSO8ejVOZ",\n "PR7awpEBluhaSO8ejVOZ",\n "Ph7awpEBluhaSO8ejVOZ",\n "Px7awpEBluhaSO8ejVOZ",\n "QB7awpEBluhaSO8ejVOZ",\n "QR7awpEBluhaSO8ejVOZ",\n "Qh7awpEBluhaSO8ejVOZ",\n "Qx7awpEBluhaSO8ejVOZ",\n "RB7awpEBluhaSO8ejVOZ",\n "RR7awpEBluhaSO8ejVOZ",\n "Rh7awpEBluhaSO8ejVOZ",\n "Rx7awpEBluhaSO8ejVOZ",\n "SB7awpEBluhaSO8ejVOZ",\n "SR7awpEBluhaSO8ejVOZ",\n "Sx7awpEBluhaSO8ewFOg",\n "TB7awpEBluhaSO8ewFOg",\n "TR7awpEBluhaSO8ewFOg",\n "Th7awpEBluhaSO8ewFOg",\n "Tx7awpEBluhaSO8ewFOg",\n "UB7awpEBluhaSO8ewFOg",\n "UR7awpEBluhaSO8ewFOg",\n "Uh7awpEBluhaSO8ewFOh",\n "Ux7awpEBluhaSO8ewFOh",\n "VB7awpEBluhaSO8ewFOh",\n "VR7awpEBluhaSO8ewFOh",\n "Vh7awpEBluhaSO8ewFOh",\n "Vx7awpEBluhaSO8ewFOh",\n "WB7awpEBluhaSO8ewFOh",\n "WR7awpEBluhaSO8ewFOh",\n "Wh7awpEBluhaSO8ewFOh",\n "Wx7awpEBluhaSO8ewFOh",\n "XB7awpEBluhaSO8ewFOh",\n "XR7awpEBluhaSO8ewFOh",\n "Xh7awpEBluhaSO8ewFOh",\n "Xx7awpEBluhaSO8ewFOh",\n "YB7awpEBluhaSO8ewFOh",\n "YR7awpEBluhaSO8ewFOh",\n "Yh7awpEBluhaSO8ewFOh",\n "Yx7awpEBluhaSO8ewFOh",\n "ZB7awpEBluhaSO8ewFOh",\n "ZR7awpEBluhaSO8ewFOh",\n "Zh7awpEBluhaSO8ewFOh",\n "Zx7awpEBluhaSO8ewFOh",\n "aB7awpEBluhaSO8ewFOh",\n "aR7awpEBluhaSO8ewFOh",\n "ah7awpEBluhaSO8ewFOh",\n "ax7awpEBluhaSO8ewFOh",\n "bB7awpEBluhaSO8ewFOh",\n "bR7awpEBluhaSO8ewFOh",\n "bh7awpEBluhaSO8ewFOh",\n "bx7awpEBluhaSO8ewFOh",\n "cB7awpEBluhaSO8ewFOh",\n "cR7awpEBluhaSO8ewFOh",\n "ch7awpEBluhaSO8ewFOh",\n "cx7awpEBluhaSO8ewFOh",\n "dB7awpEBluhaSO8ewFOh",\n "dR7awpEBluhaSO8ewFOh",\n "dh7awpEBluhaSO8ewFOh",\n "dx7awpEBluhaSO8ewFOh",\n "eB7awpEBluhaSO8ewFOh",\n "eR7awpEBluhaSO8ewFOh",\n "eh7awpEBluhaSO8ewFOh",\n "ex7awpEBluhaSO8ewFOh",\n "fB7awpEBluhaSO8ewFOh",\n "fR7awpEBluhaSO8ewFOh",\n "fh7awpEBluhaSO8ewFOh",\n "fx7awpEBluhaSO8ewFOh",\n "gB7awpEBluhaSO8ewFOh",\n "gR7awpEBluhaSO8ewFOh",\n "gh7awpEBluhaSO8ewFOh",\n "gx7awpEBluhaSO8ewFOh",\n "hB7awpEBluhaSO8ewFOh",\n "hR7awpEBluhaSO8ewFOh",\n "hh7awpEBluhaSO8ewFOh",\n "hx7awpEBluhaSO8ewFOh",\n "iB7awpEBluhaSO8ewFOh",\n "iR7awpEBluhaSO8ewFOh",\n "ih7awpEBluhaSO8ewFOh",\n "ix7awpEBluhaSO8ewFOh",\n "jB7awpEBluhaSO8ewFOh",\n "jR7awpEBluhaSO8ewFOh",\n "jh7awpEBluhaSO8ewFOh",\n "jx7awpEBluhaSO8ewFOh",\n "kB7awpEBluhaSO8ewFOh",\n "kR7awpEBluhaSO8ewFOh"\n ]\n }\n }\n }\n },\n "_source": false,\n "fields": [\n "*"\n ]\n}',
+ description: 'Retrieve source documents when ES|QL query is not aggregable',
+ duration: 8,
+ },
+ {
+ request:
+ 'POST _query\n{\n "query": "FROM packetbeat-8.14.2 metadata _id, _version, _index | limit 301",\n "filter": {\n "bool": {\n "filter": [\n {\n "range": {\n "@timestamp": {\n "lte": "2024-09-05T16:03:46.972Z",\n "gte": "2024-09-05T15:42:46.972Z",\n "format": "strict_date_optional_time"\n }\n }\n },\n {\n "bool": {\n "must": [],\n "filter": [],\n "should": [],\n "must_not": []\n }\n }\n ]\n }\n }\n}',
+ description: 'ES|QL request to find all matches',
+ },
+ {
+ request:
+ 'POST /packetbeat-8.14.2/_search?ignore_unavailable=true\n{\n "query": {\n "bool": {\n "filter": {\n "ids": {\n "values": [\n "kR7awpEBluhaSO8ewFOh",\n "kh7awpEBluhaSO8ewFOh",\n "kx7awpEBluhaSO8ewFOh",\n "lB7awpEBluhaSO8ewFOh",\n "lR7awpEBluhaSO8ewFOh",\n "lh7awpEBluhaSO8ewFOh",\n "lx7awpEBluhaSO8ewFOh",\n "mB7awpEBluhaSO8ewFOh",\n "mR7awpEBluhaSO8ewFOh",\n "mh7awpEBluhaSO8ewFOh",\n "mx7awpEBluhaSO8ewFOh",\n "nB7awpEBluhaSO8ewFOh",\n "nR7awpEBluhaSO8ewFOh",\n "nh7awpEBluhaSO8ewFOh",\n "nx7awpEBluhaSO8ewFOh",\n "oB7awpEBluhaSO8ewFOh",\n "oR7awpEBluhaSO8ewFOh",\n "oh7awpEBluhaSO8ewFOh",\n "ox7awpEBluhaSO8ewFOh",\n "pB7awpEBluhaSO8ewFOh",\n "pR7awpEBluhaSO8ewFOh",\n "ph7awpEBluhaSO8ewFOh",\n "px7awpEBluhaSO8ewFOh",\n "qB7awpEBluhaSO8ewFOh",\n "qR7awpEBluhaSO8ewFOh",\n "qh7awpEBluhaSO8ewFOh",\n "qx7awpEBluhaSO8ewFOh",\n "rB7awpEBluhaSO8ewFOh",\n "rR7awpEBluhaSO8ewFOh",\n "rh7awpEBluhaSO8ewFOh",\n "rx7awpEBluhaSO8ewFOh",\n "sB7awpEBluhaSO8ewFOh",\n "sR7awpEBluhaSO8ewFOh",\n "sh7awpEBluhaSO8ewFOh",\n "sx7awpEBluhaSO8ewFOh",\n "tB7awpEBluhaSO8ewFOh",\n "tR7awpEBluhaSO8ewFOh",\n "th7awpEBluhaSO8ewFOh",\n "tx7awpEBluhaSO8ewFOh",\n "uB7awpEBluhaSO8ewFOh",\n "uR7awpEBluhaSO8ewFOh",\n "uh7awpEBluhaSO8ewFOh",\n "ux7awpEBluhaSO8ewFOh",\n "vB7awpEBluhaSO8ewFOh",\n "vR7awpEBluhaSO8ewFOh",\n "vh7awpEBluhaSO8ewFOh",\n "vx7awpEBluhaSO8ewFOh",\n "wB7awpEBluhaSO8ewFOh",\n "wR7awpEBluhaSO8ewFOh",\n "wh7awpEBluhaSO8ewFOh",\n "wx7awpEBluhaSO8ewFOh",\n "xB7awpEBluhaSO8ewFOh",\n "xR7awpEBluhaSO8ewFOh",\n "xh7awpEBluhaSO8ewFOh",\n "xx7awpEBluhaSO8ewFOh",\n "yB7awpEBluhaSO8ewFOh",\n "yR7awpEBluhaSO8ewFOh",\n "yh7awpEBluhaSO8ewFOh",\n "yx7awpEBluhaSO8ewFOh",\n "zB7awpEBluhaSO8ewFOh",\n "zR7awpEBluhaSO8ewFOh",\n "zh7awpEBluhaSO8ewFOh",\n "zx7awpEBluhaSO8ewFOh",\n "0B7awpEBluhaSO8ewFOh",\n "0R7awpEBluhaSO8ewFOh",\n "0h7awpEBluhaSO8ewFOh",\n "0x7awpEBluhaSO8ewFOh",\n "1B7awpEBluhaSO8ewFOh",\n "1R7awpEBluhaSO8ewFOh",\n "1h7awpEBluhaSO8ewFOh",\n "1x7awpEBluhaSO8ewFOh",\n "2B7awpEBluhaSO8ewFOh",\n "2R7awpEBluhaSO8ewFOh",\n "2h7awpEBluhaSO8ewFOh",\n "2x7awpEBluhaSO8ewFOh",\n "3B7awpEBluhaSO8ewFOh",\n "3R7awpEBluhaSO8ewFOh",\n "3h7awpEBluhaSO8ewFOh",\n "3x7awpEBluhaSO8ewFOh",\n "4B7awpEBluhaSO8ewFOh",\n "4R7awpEBluhaSO8ewFOh",\n "4h7awpEBluhaSO8ewFOh",\n "4x7awpEBluhaSO8ewFOh",\n "5B7awpEBluhaSO8ewFOh",\n "5R7awpEBluhaSO8ewFOh",\n "5h7awpEBluhaSO8ewFOh",\n "6h7awpEBluhaSO8e51Pb",\n "6x7awpEBluhaSO8e51Pb",\n "7B7awpEBluhaSO8e51Pb",\n "7R7awpEBluhaSO8e51Pb",\n "7h7awpEBluhaSO8e51Pb",\n "7x7awpEBluhaSO8e51Pb",\n "8B7awpEBluhaSO8e51Pb",\n "8R7awpEBluhaSO8e51Pb",\n "8h7awpEBluhaSO8e51Pb",\n "8x7awpEBluhaSO8e51Pb",\n "9B7awpEBluhaSO8e51Pb",\n "9R7awpEBluhaSO8e51Pb",\n "9h7awpEBluhaSO8e51Pb",\n "9x7awpEBluhaSO8e51Pb",\n "-B7awpEBluhaSO8e51Pb"\n ]\n }\n }\n }\n },\n "_source": false,\n "fields": [\n "*"\n ]\n}',
+ description: 'Retrieve source documents when ES|QL query is not aggregable',
+ duration: 7,
+ },
+ ],
+ },
+ {
+ errors: [],
+ warnings: [],
+ startedAt: '2024-09-05T16:23:46.972Z',
+ duration: 103,
+ requests: [
+ {
+ request:
+ 'POST _query\n{\n "query": "FROM packetbeat-8.14.2 metadata _id, _version, _index | limit 101",\n "filter": {\n "bool": {\n "filter": [\n {\n "range": {\n "@timestamp": {\n "lte": "2024-09-05T16:23:46.972Z",\n "gte": "2024-09-05T16:02:46.972Z",\n "format": "strict_date_optional_time"\n }\n }\n },\n {\n "bool": {\n "must": [],\n "filter": [],\n "should": [],\n "must_not": []\n }\n }\n ]\n }\n }\n}',
+ description: 'ES|QL request to find all matches',
+ duration: 19,
+ },
+ {
+ request:
+ 'POST /packetbeat-8.14.2/_search?ignore_unavailable=true\n{\n "query": {\n "bool": {\n "filter": {\n "ids": {\n "values": [\n "_B7_wpEBluhaSO8enqFT",\n "_R7_wpEBluhaSO8enqFT",\n "_h7_wpEBluhaSO8enqFT",\n "_x7_wpEBluhaSO8enqFT",\n "AB7_wpEBluhaSO8enqJT",\n "AR7_wpEBluhaSO8enqJT",\n "Ah7_wpEBluhaSO8enqJT",\n "Ax7_wpEBluhaSO8enqJT",\n "BB7_wpEBluhaSO8enqJT",\n "BR7_wpEBluhaSO8enqJT",\n "Bh7_wpEBluhaSO8enqJT",\n "Bx7_wpEBluhaSO8enqJT",\n "CB7_wpEBluhaSO8enqJT",\n "CR7_wpEBluhaSO8enqJT",\n "Ch7_wpEBluhaSO8enqJT",\n "Cx7_wpEBluhaSO8enqJT",\n "DB7_wpEBluhaSO8enqJT",\n "DR7_wpEBluhaSO8enqJT",\n "Dh7_wpEBluhaSO8enqJT",\n "Dx7_wpEBluhaSO8enqJT",\n "EB7_wpEBluhaSO8enqJT",\n "ER7_wpEBluhaSO8enqJT",\n "Eh7_wpEBluhaSO8enqJT",\n "Ex7_wpEBluhaSO8enqJT",\n "FB7_wpEBluhaSO8enqJT",\n "FR7_wpEBluhaSO8enqJT",\n "Fh7_wpEBluhaSO8enqJT",\n "Fx7_wpEBluhaSO8enqJT",\n "GB7_wpEBluhaSO8enqJT",\n "GR7_wpEBluhaSO8enqJT",\n "Gh7_wpEBluhaSO8enqJT",\n "Gx7_wpEBluhaSO8enqJT",\n "tR7wwpEBluhaSO8efnLO",\n "th7wwpEBluhaSO8efnLO",\n "tx7wwpEBluhaSO8efnLO",\n "uB7wwpEBluhaSO8efnLO",\n "uR7wwpEBluhaSO8efnLO",\n "uh7wwpEBluhaSO8efnLO",\n "ux7wwpEBluhaSO8efnLO",\n "vB7wwpEBluhaSO8efnLO",\n "vR7wwpEBluhaSO8efnLO",\n "vh7wwpEBluhaSO8efnLO",\n "vx7wwpEBluhaSO8efnLO",\n "wB7wwpEBluhaSO8efnLO",\n "wR7wwpEBluhaSO8efnLO",\n "wh7wwpEBluhaSO8efnLO",\n "wx7wwpEBluhaSO8efnLO",\n "xB7wwpEBluhaSO8efnLO",\n "xR7wwpEBluhaSO8efnLO",\n "xh7wwpEBluhaSO8efnLO",\n "xx7wwpEBluhaSO8efnLO",\n "yB7wwpEBluhaSO8efnLO",\n "yR7wwpEBluhaSO8efnLO",\n "yh7wwpEBluhaSO8efnLO",\n "yx7wwpEBluhaSO8efnLO",\n "zB7wwpEBluhaSO8efnLO",\n "zR7wwpEBluhaSO8efnLO",\n "zh7wwpEBluhaSO8efnLO",\n "zx7wwpEBluhaSO8efnLO",\n "0B7wwpEBluhaSO8efnLO",\n "0R7wwpEBluhaSO8efnLO",\n "0h7wwpEBluhaSO8efnLO",\n "0x7wwpEBluhaSO8efnLO",\n "1B7wwpEBluhaSO8efnLO",\n "1B7twpEBluhaSO8eu1-P",\n "1R7twpEBluhaSO8eu1-P",\n "1h7twpEBluhaSO8eu1-P",\n "1x7twpEBluhaSO8eu1-P",\n "2B7twpEBluhaSO8eu1-P",\n "2R7twpEBluhaSO8eu1-P",\n "2h7twpEBluhaSO8eu1-P",\n "2x7twpEBluhaSO8eu1-P",\n "3B7twpEBluhaSO8eu1-P",\n "3R7twpEBluhaSO8eu1-P",\n "3h7twpEBluhaSO8eu1-P",\n "3x7twpEBluhaSO8eu1-P",\n "4B7twpEBluhaSO8eu1-P",\n "4R7twpEBluhaSO8eu1-P",\n "4h7twpEBluhaSO8eu1-P",\n "4x7twpEBluhaSO8eu1-P",\n "5B7twpEBluhaSO8eu1-P",\n "5R7twpEBluhaSO8eu1-P",\n "5h7twpEBluhaSO8eu1-P",\n "5x7twpEBluhaSO8eu1-P",\n "6B7twpEBluhaSO8eu1-P",\n "6R7twpEBluhaSO8eu1-P",\n "6h7twpEBluhaSO8eu1-P",\n "6x7twpEBluhaSO8eu1-P",\n "7B7twpEBluhaSO8eu1-P",\n "7R7twpEBluhaSO8eu1-P",\n "7h7twpEBluhaSO8eu1-P",\n "7x7twpEBluhaSO8eu1-P",\n "8B7twpEBluhaSO8eu1-P",\n "8R7twpEBluhaSO8eu1-P",\n "8h7twpEBluhaSO8eu1-P",\n "8x7twpEBluhaSO8eu1-P",\n "HB7_wpEBluhaSO8enqJT",\n "HR7_wpEBluhaSO8enqJT",\n "Hh7_wpEBluhaSO8enqJT",\n "Hx7_wpEBluhaSO8enqJT",\n "IB7_wpEBluhaSO8enqJT"\n ]\n }\n }\n }\n },\n "_source": false,\n "fields": [\n "*"\n ]\n}',
+ description: 'Retrieve source documents when ES|QL query is not aggregable',
+ duration: 5,
+ },
+ ],
+ },
+];
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/index.test.tsx
index 69eebec3452d5..4ebb460177476 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/index.test.tsx
@@ -6,10 +6,11 @@
*/
import React from 'react';
-import { render } from '@testing-library/react';
+import { render, screen } from '@testing-library/react';
import type { DataViewBase } from '@kbn/es-query';
import { fields } from '@kbn/data-plugin/common/mocks';
+import type { Type } from '@kbn/securitysolution-io-ts-alerting-types';
import { TestProviders } from '../../../../common/mock';
import type { RulePreviewProps } from '.';
@@ -22,6 +23,7 @@ import {
stepDefineDefaultValue,
} from '../../../../detections/pages/detection_engine/rules/utils';
import { usePreviewInvocationCount } from './use_preview_invocation_count';
+import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
jest.mock('../../../../common/lib/kibana');
jest.mock('./use_preview_route');
@@ -34,6 +36,21 @@ jest.mock('../../../../common/containers/use_global_time', () => ({
}),
}));
jest.mock('./use_preview_invocation_count');
+jest.mock('../../../../common/hooks/use_experimental_features', () => ({
+ useIsExperimentalFeatureEnabled: jest.fn(),
+}));
+
+const useIsExperimentalFeatureEnabledMock = useIsExperimentalFeatureEnabled as jest.Mock;
+// rule types that do not support logged requests
+const doNotSupportLoggedRequests: Type[] = [
+ 'threshold',
+ 'threat_match',
+ 'machine_learning',
+ 'query',
+ 'new_terms',
+];
+
+const supportLoggedRequests: Type[] = ['esql', 'eql'];
const getMockIndexPattern = (): DataViewBase => ({
fields,
@@ -97,6 +114,8 @@ describe('PreviewQuery', () => {
});
(usePreviewInvocationCount as jest.Mock).mockReturnValue({ invocationCount: 500 });
+
+ useIsExperimentalFeatureEnabledMock.mockReturnValue(true);
});
afterEach(() => {
@@ -137,4 +156,51 @@ describe('PreviewQuery', () => {
expect(await wrapper.findByTestId('previewInvocationCountWarning')).toBeTruthy();
});
+
+ supportLoggedRequests.forEach((ruleType) => {
+ test(`renders "Show Elasticsearch requests" for ${ruleType} rule type`, () => {
+ render(
+
+
+
+ );
+
+ expect(screen.getByTestId('show-elasticsearch-requests')).toBeInTheDocument();
+ });
+ });
+
+ supportLoggedRequests.forEach((ruleType) => {
+ test(`does not render "Show Elasticsearch requests" for ${ruleType} rule type when feature is disabled`, () => {
+ useIsExperimentalFeatureEnabledMock.mockReturnValue(false);
+
+ render(
+
+
+
+ );
+
+ expect(screen.queryByTestId('show-elasticsearch-requests')).toBeNull();
+ });
+ });
+
+ doNotSupportLoggedRequests.forEach((ruleType) => {
+ test(`does not render "Show Elasticsearch requests" for ${ruleType} rule type`, () => {
+ render(
+
+
+
+ );
+
+ expect(screen.queryByTestId('show-elasticsearch-requests')).toBeNull();
+ });
+ });
});
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/index.tsx
index 8deb2ebd41863..2a86600d94e7a 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/index.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/index.tsx
@@ -18,9 +18,12 @@ import {
EuiText,
EuiTitle,
EuiFormRow,
+ EuiCheckbox,
} from '@elastic/eui';
import moment from 'moment';
import type { List } from '@kbn/securitysolution-io-ts-list-types';
+import type { Type } from '@kbn/securitysolution-io-ts-alerting-types';
+
import { isEqual } from 'lodash';
import * as i18n from './translations';
import { usePreviewRoute } from './use_preview_route';
@@ -37,9 +40,12 @@ import type {
TimeframePreviewOptions,
} from '../../../../detections/pages/detection_engine/rules/types';
import { usePreviewInvocationCount } from './use_preview_invocation_count';
+import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
export const REASONABLE_INVOCATION_COUNT = 200;
+const RULE_TYPES_SUPPORTING_LOGGED_REQUESTS: Type[] = ['esql', 'eql'];
+
const timeRanges = [
{ start: 'now/d', end: 'now', label: 'Today' },
{ start: 'now/w', end: 'now', label: 'This week' },
@@ -64,6 +70,7 @@ interface RulePreviewState {
aboutRuleData?: AboutStepRule;
scheduleRuleData?: ScheduleStepRule;
timeframeOptions: TimeframePreviewOptions;
+ enableLoggedRequests?: boolean;
}
const refreshedTimeframe = (startDate: string, endDate: string) => {
@@ -83,6 +90,8 @@ const RulePreviewComponent: React.FC = ({
const { indexPattern, ruleType } = defineRuleData;
const { spaces } = useKibana().services;
+ const isLoggingRequestsFeatureEnabled = useIsExperimentalFeatureEnabled('loggingRequestsEnabled');
+
const [spaceId, setSpaceId] = useState('');
useEffect(() => {
if (spaces) {
@@ -98,6 +107,8 @@ const RulePreviewComponent: React.FC = ({
const [timeframeStart, setTimeframeStart] = useState(moment().subtract(1, 'hour'));
const [timeframeEnd, setTimeframeEnd] = useState(moment());
+ const [showElasticsearchRequests, setShowElasticsearchRequests] = useState(false);
+
const [isDateRangeInvalid, setIsDateRangeInvalid] = useState(false);
useEffect(() => {
@@ -140,6 +151,7 @@ const RulePreviewComponent: React.FC = ({
scheduleRuleData: previewData.scheduleRuleData,
exceptionsList,
timeframeOptions: previewData.timeframeOptions,
+ enableLoggedRequests: previewData.enableLoggedRequests,
});
const { startTransaction } = useStartTransaction();
@@ -185,9 +197,18 @@ const RulePreviewComponent: React.FC = ({
interval: scheduleRuleData.interval,
lookback: scheduleRuleData.from,
},
+ enableLoggedRequests: showElasticsearchRequests,
});
setIsRefreshing(true);
- }, [aboutRuleData, defineRuleData, endDate, scheduleRuleData, startDate, startTransaction]);
+ }, [
+ aboutRuleData,
+ defineRuleData,
+ endDate,
+ scheduleRuleData,
+ startDate,
+ startTransaction,
+ showElasticsearchRequests,
+ ]);
const isDirty = useMemo(
() =>
@@ -261,6 +282,24 @@ const RulePreviewComponent: React.FC = ({
+ {isLoggingRequestsFeatureEnabled &&
+ RULE_TYPES_SUPPORTING_LOGGED_REQUESTS.includes(ruleType) ? (
+
+
+
+ {
+ setShowElasticsearchRequests(!showElasticsearchRequests);
+ }}
+ />
+
+
+
+ ) : null}
{isPreviewRequestInProgress && }
{!isPreviewRequestInProgress && previewId && spaceId && (
@@ -273,7 +312,12 @@ const RulePreviewComponent: React.FC = ({
timeframeOptions={previewData.timeframeOptions}
/>
)}
-
+
);
};
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/logged_requests.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/logged_requests.test.tsx
new file mode 100644
index 0000000000000..c0cf8870c162a
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/logged_requests.test.tsx
@@ -0,0 +1,68 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+
+import { TestProviders } from '../../../../common/mock/test_providers';
+import { LoggedRequests } from './logged_requests';
+
+import { previewLogs } from './__mocks__/preview_logs';
+
+describe('LoggedRequests', () => {
+ it('should not render component if logs are empty', () => {
+ render(, { wrapper: TestProviders });
+
+ expect(screen.queryByTestId('preview-logged-requests-accordion')).toBeNull();
+ });
+
+ it('should open accordion on click and render list of request items', async () => {
+ render(, { wrapper: TestProviders });
+
+ expect(screen.queryByTestId('preview-logged-requests-accordion')).toBeInTheDocument();
+
+ await userEvent.click(screen.getByText('Preview logged requests'));
+
+ expect(screen.getAllByTestId('preview-logged-requests-item-accordion')).toHaveLength(3);
+ });
+
+ it('should render code content on logged request item accordion click', async () => {
+ render(, { wrapper: TestProviders });
+
+ expect(screen.queryByTestId('preview-logged-requests-accordion')).toBeInTheDocument();
+
+ await userEvent.click(screen.getByText('Preview logged requests'));
+
+ // picking up second rule execution
+ const loggedRequestsItem = screen.getAllByTestId('preview-logged-requests-item-accordion')[1];
+
+ expect(loggedRequestsItem).toHaveTextContent('Rule execution started at');
+ expect(loggedRequestsItem).toHaveTextContent('[269ms]');
+
+ await userEvent.click(loggedRequestsItem.querySelector('button') as HTMLElement);
+
+ expect(screen.getAllByTestId('preview-logged-request-description')).toHaveLength(6);
+ expect(screen.getAllByTestId('preview-logged-request-code-block')).toHaveLength(6);
+
+ expect(screen.getAllByTestId('preview-logged-request-description')[0]).toHaveTextContent(
+ 'ES|QL request to find all matches [30ms]'
+ );
+
+ expect(screen.getAllByTestId('preview-logged-request-code-block')[0]).toHaveTextContent(
+ /FROM packetbeat-8\.14\.2 metadata _id, _version, _index \| limit 101/
+ );
+
+ expect(screen.getAllByTestId('preview-logged-request-description')[1]).toHaveTextContent(
+ 'Retrieve source documents when ES|QL query is not aggregable'
+ );
+
+ expect(screen.getAllByTestId('preview-logged-request-code-block')[1]).toHaveTextContent(
+ /POST \/packetbeat-8\.14\.2\/_search\?ignore_unavailable=true/
+ );
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/logged_requests.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/logged_requests.tsx
new file mode 100644
index 0000000000000..d7b62c6f08c69
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/logged_requests.tsx
@@ -0,0 +1,58 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { FC } from 'react';
+import React, { useMemo } from 'react';
+import { EuiSpacer } from '@elastic/eui';
+import { css } from '@emotion/css';
+
+import type { RulePreviewLogs } from '../../../../../common/api/detection_engine';
+import * as i18n from './translations';
+import { OptimizedAccordion } from './optimized_accordion';
+import { LoggedRequestsItem } from './logged_requests_item';
+import { useAccordionStyling } from './use_accordion_styling';
+
+const LoggedRequestsComponent: FC<{ logs: RulePreviewLogs[] }> = ({ logs }) => {
+ const cssStyles = useAccordionStyling();
+
+ const AccordionContent = useMemo(
+ () => (
+ <>
+
+ {logs.map((log) => (
+
+
+
+ ))}
+ >
+ ),
+ [logs]
+ );
+
+ if (logs.length === 0) {
+ return null;
+ }
+
+ return (
+ <>
+
+ {AccordionContent}
+
+ >
+ );
+};
+
+export const LoggedRequests = React.memo(LoggedRequestsComponent);
+LoggedRequests.displayName = 'LoggedRequests';
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/logged_requests_item.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/logged_requests_item.tsx
new file mode 100644
index 0000000000000..2f2e7d74bf826
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/logged_requests_item.tsx
@@ -0,0 +1,79 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { FC, PropsWithChildren } from 'react';
+import React from 'react';
+import { css } from '@emotion/css';
+
+import { EuiSpacer, EuiCodeBlock, useEuiPaddingSize, EuiFlexItem } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n-react';
+import type { RulePreviewLogs } from '../../../../../common/api/detection_engine';
+import * as i18n from './translations';
+import { PreferenceFormattedDate } from '../../../../common/components/formatted_date';
+import { OptimizedAccordion } from './optimized_accordion';
+import { useAccordionStyling } from './use_accordion_styling';
+
+const LoggedRequestsItemComponent: FC> = ({
+ startedAt,
+ duration,
+ requests,
+}) => {
+ const paddingLarge = useEuiPaddingSize('l');
+ const cssStyles = useAccordionStyling();
+
+ return (
+
+ {startedAt ? (
+ }}
+ />
+ ) : (
+ i18n.LOGGED_REQUEST_ITEM_ACCORDION_UNKNOWN_TIME_BUTTON
+ )}
+ {`[${duration}ms]`}
+ >
+ }
+ id={`ruleExecution-${startedAt}`}
+ css={css`
+ margin-left: ${paddingLarge};
+ ${cssStyles}
+ `}
+ >
+ {(requests ?? []).map((request, key) => (
+
+
+
+ {request?.description ?? null} {request?.duration ? `[${request.duration}ms]` : null}
+
+
+
+ {request.request}
+
+
+ ))}
+
+ );
+};
+
+export const LoggedRequestsItem = React.memo(LoggedRequestsItemComponent);
+LoggedRequestsItem.displayName = 'LoggedRequestsItem';
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/optimized_accordion.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/optimized_accordion.test.tsx
new file mode 100644
index 0000000000000..bab5751aa6ddf
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/optimized_accordion.test.tsx
@@ -0,0 +1,59 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+
+import { OptimizedAccordion } from './optimized_accordion';
+
+describe('OptimizedAccordion', () => {
+ it('should not render children content if accordion initially closed', () => {
+ render(
+
+ {'content'}
+
+ );
+
+ expect(screen.queryByText('content')).toBeNull();
+ });
+ it('should render children content if accordion initially opened', () => {
+ render(
+
+ {'content'}
+
+ );
+
+ expect(screen.getByText('content')).toBeInTheDocument();
+ });
+ it('should render children content when accordion opened', async () => {
+ render(
+
+ {'content'}
+
+ );
+
+ const toggleButton = screen.getByText('accordion button');
+ await userEvent.click(toggleButton);
+
+ expect(screen.getByText('content')).toBeVisible();
+ });
+ it('should not destroy children content when accordion closed', async () => {
+ render(
+
+ {'content'}
+
+ );
+
+ const toggleButton = screen.getByText('accordion button');
+ await userEvent.click(toggleButton);
+
+ expect(screen.getByText('content')).toBeVisible();
+
+ await userEvent.click(toggleButton);
+ expect(screen.getByText('content')).not.toBeVisible();
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/optimized_accordion.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/optimized_accordion.tsx
new file mode 100644
index 0000000000000..5d3ad0ab874be
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/optimized_accordion.tsx
@@ -0,0 +1,39 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { FC } from 'react';
+
+import React, { useState } from 'react';
+
+import type { EuiAccordionProps } from '@elastic/eui';
+import { EuiAccordion } from '@elastic/eui';
+
+/**
+ * component does not render children before it was opened
+ * once children rendered for the first time, they won't be re-rendered on subsequent accordion toggling
+ */
+const OptimizedAccordionComponent: FC = ({ children, ...props }) => {
+ const [trigger, setTrigger] = useState<'closed' | 'open'>('closed');
+ const [isRendered, setIsRendered] = useState(false);
+
+ const onToggle = (isOpen: boolean) => {
+ const newState = isOpen ? 'open' : 'closed';
+ if (isOpen) {
+ setIsRendered(true);
+ }
+ setTrigger(newState);
+ };
+
+ return (
+
+ {isRendered || props.forceState === 'open' ? children : null}
+
+ );
+};
+
+export const OptimizedAccordion = React.memo(OptimizedAccordionComponent);
+OptimizedAccordion.displayName = 'OptimizedAccordion';
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/preview_logs.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/preview_logs.tsx
index 912df2453cc46..196408cbc1371 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/preview_logs.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/preview_logs.tsx
@@ -7,14 +7,19 @@
import type { FC, PropsWithChildren } from 'react';
import React, { Fragment, useMemo } from 'react';
+import { css } from '@emotion/css';
import { EuiCallOut, EuiText, EuiSpacer, EuiAccordion } from '@elastic/eui';
+
import type { RulePreviewLogs } from '../../../../../common/api/detection_engine';
import * as i18n from './translations';
+import { LoggedRequests } from './logged_requests';
+import { useAccordionStyling } from './use_accordion_styling';
interface PreviewLogsProps {
logs: RulePreviewLogs[];
hasNoiseWarning: boolean;
isAborted: boolean;
+ showElasticsearchRequests: boolean;
}
interface SortedLogs {
@@ -43,7 +48,12 @@ const addLogs = (
allLogs: SortedLogs[]
) => (logs.length ? [{ startedAt, logs, duration }, ...allLogs] : allLogs);
-const PreviewLogsComponent: React.FC = ({ logs, hasNoiseWarning, isAborted }) => {
+const PreviewLogsComponent: React.FC = ({
+ logs,
+ hasNoiseWarning,
+ isAborted,
+ showElasticsearchRequests,
+}) => {
const sortedLogs = useMemo(
() =>
logs.reduce<{
@@ -66,6 +76,7 @@ const PreviewLogsComponent: React.FC = ({ logs, hasNoiseWarnin
{isAborted ? : null}
+ {showElasticsearchRequests ? : null}
>
);
};
@@ -74,6 +85,8 @@ export const PreviewLogs = React.memo(PreviewLogsComponent);
PreviewLogs.displayName = 'PreviewLogs';
const LogAccordion: FC> = ({ logs, isError, children }) => {
+ const cssStyles = useAccordionStyling();
+
const firstLog = logs[0];
if (!(children || firstLog)) return null;
@@ -96,6 +109,10 @@ const LogAccordion: FC> = ({ logs, isError,
buttonContent={
isError ? i18n.QUERY_PREVIEW_SEE_ALL_ERRORS : i18n.QUERY_PREVIEW_SEE_ALL_WARNINGS
}
+ borders="horizontal"
+ css={css`
+ ${cssStyles}
+ `}
>
{restOfLogs.map((log, key) => (
> = ({ logs, isError,
))}
) : null}
-
>
);
};
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/translations.ts
index 4ab9328ee806b..0b25071a76830 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/translations.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/translations.ts
@@ -158,6 +158,27 @@ export const VIEW_DETAILS = i18n.translate(
}
);
+export const ENABLED_LOGGED_REQUESTS_CHECKBOX = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.queryPreview.enabledLoggedRequestsLabel',
+ {
+ defaultMessage: 'Show Elasticsearch requests, ran during rule executions',
+ }
+);
+
+export const LOGGED_REQUESTS_ACCORDION_BUTTON = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.queryPreview.loggedRequestsAccordionButtonLabel',
+ {
+ defaultMessage: 'Preview logged requests',
+ }
+);
+
+export const LOGGED_REQUEST_ITEM_ACCORDION_UNKNOWN_TIME_BUTTON = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.queryPreview.loggedRequestItemAccordionUnknownTimeButtonLabel',
+ {
+ defaultMessage: 'Preview logged requests',
+ }
+);
+
export const VIEW_DETAILS_FOR_ROW = ({
ariaRowindex,
columnValues,
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/use_accordion_styling.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/use_accordion_styling.ts
new file mode 100644
index 0000000000000..be05d90836c94
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/use_accordion_styling.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { useEuiPaddingSize } from '@elastic/eui';
+
+export const useAccordionStyling = () => {
+ const paddingLarge = useEuiPaddingSize('l');
+ const paddingSmall = useEuiPaddingSize('s');
+
+ return `padding-bottom: ${paddingLarge};
+ padding-top: ${paddingSmall};`;
+};
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/use_preview_route.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/use_preview_route.tsx
index 25952956b2538..5684819106ada 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/use_preview_route.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/use_preview_route.tsx
@@ -24,6 +24,7 @@ interface PreviewRouteParams {
scheduleRuleData?: ScheduleStepRule;
exceptionsList?: List[];
timeframeOptions: TimeframePreviewOptions;
+ enableLoggedRequests?: boolean;
}
export const usePreviewRoute = ({
@@ -32,6 +33,7 @@ export const usePreviewRoute = ({
scheduleRuleData,
exceptionsList,
timeframeOptions,
+ enableLoggedRequests,
}: PreviewRouteParams) => {
const [isRequestTriggered, setIsRequestTriggered] = useState(false);
@@ -41,6 +43,7 @@ export const usePreviewRoute = ({
const { isLoading, response, rule, setRule } = usePreviewRule({
timeframeOptions,
+ enableLoggedRequests,
});
const [logs, setLogs] = useState(response.logs ?? []);
const [isAborted, setIsAborted] = useState(!!response.isAborted);
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/use_preview_rule.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/use_preview_rule.ts
index 9fb7417bca036..05c3b9fe10299 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/use_preview_rule.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/use_preview_rule.ts
@@ -27,8 +27,10 @@ const emptyPreviewRule: RulePreviewResponse = {
export const usePreviewRule = ({
timeframeOptions,
+ enableLoggedRequests,
}: {
timeframeOptions: TimeframePreviewOptions;
+ enableLoggedRequests?: boolean;
}) => {
const [rule, setRule] = useState(null);
const [response, setResponse] = useState(emptyPreviewRule);
@@ -66,6 +68,7 @@ export const usePreviewRule = ({
invocationCount,
timeframeEnd,
},
+ enableLoggedRequests,
signal: abortCtrl.signal,
});
if (isSubscribed) {
@@ -87,7 +90,7 @@ export const usePreviewRule = ({
isSubscribed = false;
abortCtrl.abort();
};
- }, [rule, addError, invocationCount, from, interval, timeframeEnd]);
+ }, [rule, addError, invocationCount, from, interval, timeframeEnd, enableLoggedRequests]);
return { isLoading, response, rule, setRule };
};
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts
index 5180f9fb891f0..d10bb4bb03e08 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts
@@ -117,6 +117,21 @@ describe('Detections Rules API', () => {
expect.objectContaining({
body: '{"description":"Detecting root and admin users","name":"Query with a rule id","query":"user.name: root or user.name: admin","severity":"high","type":"query","risk_score":55,"language":"kuery","rule_id":"rule-1","invocationCount":1,"timeframeEnd":"2015-03-12 05:17:10"}',
method: 'POST',
+ query: undefined,
+ })
+ );
+ });
+
+ test('sends enable_logged_requests in URL query', async () => {
+ const payload = getCreateRulesSchemaMock();
+ await previewRule({
+ rule: { ...payload, invocationCount: 1, timeframeEnd: '2015-03-12 05:17:10' },
+ enableLoggedRequests: true,
+ });
+ expect(fetchMock).toHaveBeenCalledWith(
+ '/api/detection_engine/rules/preview',
+ expect.objectContaining({
+ query: { enable_logged_requests: true },
})
);
});
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts
index 4254b58234400..c86606d0d8137 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts
@@ -150,6 +150,7 @@ export const patchRule = async ({
*/
export const previewRule = async ({
rule,
+ enableLoggedRequests,
signal,
}: PreviewRulesProps): Promise =>
KibanaServices.get().http.fetch(DETECTION_ENGINE_RULES_PREVIEW, {
@@ -157,6 +158,7 @@ export const previewRule = async ({
version: '2023-10-31',
body: JSON.stringify(rule),
signal,
+ query: enableLoggedRequests ? { enable_logged_requests: enableLoggedRequests } : undefined,
});
/**
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts
index 526d0a00389d7..e12442c97aa4c 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts
@@ -32,7 +32,11 @@ export interface CreateRulesProps {
}
export interface PreviewRulesProps {
- rule: RuleCreateProps & { invocationCount: number; timeframeEnd: string };
+ rule: RuleCreateProps & {
+ invocationCount: number;
+ timeframeEnd: string;
+ };
+ enableLoggedRequests?: boolean;
signal?: AbortSignal;
}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts
index 52f4d3739b1e2..50542592aa1d1 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts
@@ -31,7 +31,11 @@ import type {
RulePreviewResponse,
RulePreviewLogs,
} from '../../../../../../common/api/detection_engine';
-import { RulePreviewRequestBody } from '../../../../../../common/api/detection_engine';
+import {
+ RulePreviewRequestBody,
+ RulePreviewRequestQuery,
+} from '../../../../../../common/api/detection_engine';
+import type { RulePreviewLoggedRequest } from '../../../../../../common/api/detection_engine/rule_preview/rule_preview.gen';
import type { StartPlugins, SetupPlugins } from '../../../../../plugin';
import { buildSiemResponse } from '../../../routes/utils';
@@ -92,7 +96,12 @@ export const previewRulesRoute = (
.addVersion(
{
version: '2023-10-31',
- validate: { request: { body: buildRouteValidationWithZod(RulePreviewRequestBody) } },
+ validate: {
+ request: {
+ body: buildRouteValidationWithZod(RulePreviewRequestBody),
+ query: buildRouteValidationWithZod(RulePreviewRequestQuery),
+ },
+ },
},
async (context, request, response): Promise> => {
const siemResponse = buildSiemResponse(response);
@@ -143,7 +152,9 @@ export const previewRulesRoute = (
const username = security?.authc.getCurrentUser(request)?.username;
const loggedStatusChanges: Array = [];
const previewRuleExecutionLogger = createPreviewRuleExecutionLogger(loggedStatusChanges);
- const runState: Record = {};
+ const runState: Record = {
+ isLoggedRequestsEnabled: request.query.enable_logged_requests,
+ };
const logs: RulePreviewLogs[] = [];
let isAborted = false;
@@ -224,6 +235,7 @@ export const previewRulesRoute = (
}
) => {
let statePreview = runState as TState;
+ let loggedRequests = [];
const abortController = new AbortController();
setTimeout(() => {
@@ -268,7 +280,7 @@ export const previewRulesRoute = (
while (invocationCount > 0 && !isAborted) {
invocationStartTime = moment();
- ({ state: statePreview } = (await executor({
+ ({ state: statePreview, loggedRequests } = (await executor({
executionId: uuidv4(),
params,
previousStartedAt,
@@ -302,7 +314,7 @@ export const previewRulesRoute = (
const date = startedAt.toISOString();
return { dateStart: date, dateEnd: date };
},
- })) as { state: TState });
+ })) as { state: TState; loggedRequests: RulePreviewLoggedRequest[] });
const errors = loggedStatusChanges
.filter((item) => item.newStatus === RuleExecutionStatusEnum.failed)
@@ -317,6 +329,7 @@ export const previewRulesRoute = (
warnings,
startedAt: startedAt.toDate().toISOString(),
duration: moment().diff(invocationStartTime, 'milliseconds'),
+ ...(loggedRequests ? { requests: loggedRequests } : {}),
});
loggedStatusChanges.length = 0;
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts
index 1e5e70a37ae5f..f25a8429089b6 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts
@@ -467,6 +467,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper =
warning: warningMessages.length > 0,
warningMessages,
userError: runResult.userError,
+ ...(runResult.loggedRequests ? { loggedRequests: runResult.loggedRequests } : {}),
};
runState = runResult.state;
}
@@ -571,7 +572,10 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper =
});
}
- return { state: result.state };
+ return {
+ state: result.state,
+ ...(result.loggedRequests ? { loggedRequests: result.loggedRequests } : {}),
+ };
});
},
alerts: {
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/create_eql_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/create_eql_alert_type.ts
index ca16b38404e48..9de8641d7b17c 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/create_eql_alert_type.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/create_eql_alert_type.ts
@@ -111,7 +111,7 @@ export const createEqlAlertType = (
alertSuppression: completeRule.ruleParams.alertSuppression,
licensing,
});
- const result = await eqlExecutor({
+ const { result, loggedRequests } = await eqlExecutor({
completeRule,
tuple,
inputIndex,
@@ -131,9 +131,10 @@ export const createEqlAlertType = (
alertWithSuppression,
isAlertSuppressionActive: isNonSeqAlertSuppressionActive,
experimentalFeatures,
+ state,
scheduleNotificationResponseActionsService,
});
- return { ...result, state };
+ return { ...result, state, ...(loggedRequests ? { loggedRequests } : {}) };
},
};
};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/eql.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/eql.test.ts
index 9ef9faeb9de3a..4f5aa7d322c9e 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/eql.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/eql.test.ts
@@ -54,7 +54,7 @@ describe('eql_executor', () => {
describe('eqlExecutor', () => {
describe('warning scenarios', () => {
it('warns when exception list for eql rule contains value list exceptions', async () => {
- const result = await eqlExecutor({
+ const { result } = await eqlExecutor({
inputIndex: DEFAULT_INDEX_PATTERN,
runtimeMappings: {},
completeRule: eqlCompleteRule,
@@ -105,7 +105,7 @@ describe('eql_executor', () => {
},
});
- const result = await eqlExecutor({
+ const { result } = await eqlExecutor({
inputIndex: DEFAULT_INDEX_PATTERN,
runtimeMappings: {},
completeRule: ruleWithSequenceAndSuppression,
@@ -140,7 +140,7 @@ describe('eql_executor', () => {
message:
'verification_exception\n\tRoot causes:\n\t\tverification_exception: Found 1 problem\nline 1:1: Unknown column [event.category]',
});
- const result = await eqlExecutor({
+ const { result } = await eqlExecutor({
inputIndex: DEFAULT_INDEX_PATTERN,
runtimeMappings: {},
completeRule: eqlCompleteRule,
@@ -165,7 +165,7 @@ describe('eql_executor', () => {
});
it('should handle scheduleNotificationResponseActionsService call', async () => {
- const result = await eqlExecutor({
+ const { result } = await eqlExecutor({
inputIndex: DEFAULT_INDEX_PATTERN,
runtimeMappings: {},
completeRule: eqlCompleteRule,
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/eql.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/eql.ts
index 3379d0a0c6867..47e298392d7d9 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/eql.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/eql.ts
@@ -46,6 +46,9 @@ import type {
import type { IRuleExecutionLogForExecutors } from '../../rule_monitoring';
import { bulkCreateSuppressedAlertsInMemory } from '../utils/bulk_create_suppressed_alerts_in_memory';
import { getDataTierFilter } from '../utils/get_data_tier_filter';
+import type { RulePreviewLoggedRequest } from '../../../../../common/api/detection_engine/rule_preview/rule_preview.gen';
+import { logEqlRequest } from '../utils/logged_requests';
+import * as i18n from '../translations';
interface EqlExecutorParams {
inputIndex: string[];
@@ -67,6 +70,7 @@ interface EqlExecutorParams {
alertWithSuppression: SuppressedAlertService;
isAlertSuppressionActive: boolean;
experimentalFeatures: ExperimentalFeatures;
+ state?: Record;
scheduleNotificationResponseActionsService: CreateRuleAdditionalOptions['scheduleNotificationResponseActionsService'];
}
@@ -90,10 +94,17 @@ export const eqlExecutor = async ({
alertWithSuppression,
isAlertSuppressionActive,
experimentalFeatures,
+ state,
scheduleNotificationResponseActionsService,
-}: EqlExecutorParams): Promise => {
+}: EqlExecutorParams): Promise<{
+ result: SearchAfterAndBulkCreateReturnType;
+ loggedRequests?: RulePreviewLoggedRequest[];
+}> => {
const ruleParams = completeRule.ruleParams;
+ const isLoggedRequestsEnabled = state?.isLoggedRequestsEnabled ?? false;
+ const loggedRequests: RulePreviewLoggedRequest[] = [];
+ // eslint-disable-next-line complexity
return withSecuritySpan('eqlExecutor', async () => {
const result = createSearchAfterReturnType();
@@ -125,13 +136,24 @@ export const eqlExecutor = async ({
const eqlSignalSearchStart = performance.now();
try {
+ if (isLoggedRequestsEnabled) {
+ loggedRequests.push({
+ request: logEqlRequest(request),
+ description: i18n.EQL_SEARCH_REQUEST_DESCRIPTION,
+ });
+ }
+
const response = await services.scopedClusterClient.asCurrentUser.eql.search(
request
);
const eqlSignalSearchEnd = performance.now();
- const eqlSearchDuration = makeFloatString(eqlSignalSearchEnd - eqlSignalSearchStart);
- result.searchAfterTimes = [eqlSearchDuration];
+ const eqlSearchDuration = eqlSignalSearchEnd - eqlSignalSearchStart;
+ result.searchAfterTimes = [makeFloatString(eqlSearchDuration)];
+
+ if (isLoggedRequestsEnabled && loggedRequests[0]) {
+ loggedRequests[0].duration = Math.round(eqlSearchDuration);
+ }
let newSignals: Array> | undefined;
@@ -198,8 +220,7 @@ export const eqlExecutor = async ({
responseActions: completeRule.ruleParams.responseActions,
});
}
-
- return result;
+ return { result, ...(isLoggedRequestsEnabled ? { loggedRequests } : {}) };
} catch (error) {
if (
typeof error.message === 'string' &&
@@ -211,7 +232,7 @@ export const eqlExecutor = async ({
}
result.errors.push(error.message);
result.success = false;
- return result;
+ return { result, ...(isLoggedRequestsEnabled ? { loggedRequests } : {}) };
}
});
};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/esql.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/esql.ts
index 0dd2b0e50d4ba..1e5b1749e94f5 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/esql.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/esql.ts
@@ -27,8 +27,11 @@ import { createEnrichEventsFunction } from '../utils/enrichments';
import { rowToDocument } from './utils';
import { fetchSourceDocuments } from './fetch_source_documents';
import { buildReasonMessageForEsqlAlert } from '../utils/reason_formatters';
-
+import type { RulePreviewLoggedRequest } from '../../../../../common/api/detection_engine/rule_preview/rule_preview.gen';
import type { RunOpts, SignalSource, CreateRuleAdditionalOptions } from '../types';
+import { logEsqlRequest } from '../utils/logged_requests';
+import * as i18n from '../translations';
+
import {
addToSearchAfterReturn,
createSearchAfterReturnType,
@@ -66,13 +69,14 @@ export const esqlExecutor = async ({
}: {
runOpts: RunOpts;
services: RuleExecutorServices;
- state: object;
+ state: Record;
spaceId: string;
version: string;
experimentalFeatures: ExperimentalFeatures;
licensing: LicensingPluginSetup;
scheduleNotificationResponseActionsService: CreateRuleAdditionalOptions['scheduleNotificationResponseActionsService'];
}) => {
+ const loggedRequests: RulePreviewLoggedRequest[] = [];
const ruleParams = completeRule.ruleParams;
/**
* ES|QL returns results as a single page. max size of 10,000
@@ -80,91 +84,80 @@ export const esqlExecutor = async ({
* we don't want to overload ES/Kibana with large responses
*/
const ESQL_PAGE_SIZE_CIRCUIT_BREAKER = tuple.maxSignals * 3;
+ const isLoggedRequestsEnabled = state?.isLoggedRequestsEnabled ?? false;
return withSecuritySpan('esqlExecutor', async () => {
const result = createSearchAfterReturnType();
let size = tuple.maxSignals;
- while (
- result.createdSignalsCount <= tuple.maxSignals &&
- size <= ESQL_PAGE_SIZE_CIRCUIT_BREAKER
- ) {
- const esqlRequest = buildEsqlSearchRequest({
- query: ruleParams.query,
- from: tuple.from.toISOString(),
- to: tuple.to.toISOString(),
- size,
- filters: [],
- primaryTimestamp,
- secondaryTimestamp,
- exceptionFilter,
- });
-
- ruleExecutionLogger.debug(`ES|QL query request: ${JSON.stringify(esqlRequest)}`);
- const exceptionsWarning = getUnprocessedExceptionsWarnings(unprocessedExceptions);
- if (exceptionsWarning) {
- result.warningMessages.push(exceptionsWarning);
- }
+ try {
+ while (
+ result.createdSignalsCount <= tuple.maxSignals &&
+ size <= ESQL_PAGE_SIZE_CIRCUIT_BREAKER
+ ) {
+ const esqlRequest = buildEsqlSearchRequest({
+ query: ruleParams.query,
+ from: tuple.from.toISOString(),
+ to: tuple.to.toISOString(),
+ size,
+ filters: [],
+ primaryTimestamp,
+ secondaryTimestamp,
+ exceptionFilter,
+ });
- const esqlSignalSearchStart = performance.now();
+ if (isLoggedRequestsEnabled) {
+ loggedRequests.push({
+ request: logEsqlRequest(esqlRequest),
+ description: i18n.ESQL_SEARCH_REQUEST_DESCRIPTION,
+ });
+ }
- const response = await performEsqlRequest({
- esClient: services.scopedClusterClient.asCurrentUser,
- requestParams: esqlRequest,
- });
+ ruleExecutionLogger.debug(`ES|QL query request: ${JSON.stringify(esqlRequest)}`);
+ const exceptionsWarning = getUnprocessedExceptionsWarnings(unprocessedExceptions);
+ if (exceptionsWarning) {
+ result.warningMessages.push(exceptionsWarning);
+ }
- const esqlSearchDuration = makeFloatString(performance.now() - esqlSignalSearchStart);
- result.searchAfterTimes.push(esqlSearchDuration);
+ const esqlSignalSearchStart = performance.now();
- ruleExecutionLogger.debug(`ES|QL query request took: ${esqlSearchDuration}ms`);
+ const response = await performEsqlRequest({
+ esClient: services.scopedClusterClient.asCurrentUser,
+ requestParams: esqlRequest,
+ });
- const isRuleAggregating = computeIsESQLQueryAggregating(completeRule.ruleParams.query);
+ const esqlSearchDuration = performance.now() - esqlSignalSearchStart;
+ result.searchAfterTimes.push(makeFloatString(esqlSearchDuration));
- const results = response.values
- // slicing already processed results in previous iterations
- .slice(size - tuple.maxSignals)
- .map((row) => rowToDocument(response.columns, row));
+ if (isLoggedRequestsEnabled && loggedRequests[0]) {
+ loggedRequests[0].duration = Math.round(esqlSearchDuration);
+ }
- const index = getIndexListFromEsqlQuery(completeRule.ruleParams.query);
+ ruleExecutionLogger.debug(`ES|QL query request took: ${esqlSearchDuration}ms`);
- const sourceDocuments = await fetchSourceDocuments({
- esClient: services.scopedClusterClient.asCurrentUser,
- results,
- index,
- isRuleAggregating,
- });
+ const isRuleAggregating = computeIsESQLQueryAggregating(completeRule.ruleParams.query);
- const isAlertSuppressionActive = await getIsAlertSuppressionActive({
- alertSuppression: completeRule.ruleParams.alertSuppression,
- licensing,
- });
+ const results = response.values
+ // slicing already processed results in previous iterations
+ .slice(size - tuple.maxSignals)
+ .map((row) => rowToDocument(response.columns, row));
- const wrapHits = (events: Array>) =>
- wrapEsqlAlerts({
- events,
- spaceId,
- completeRule,
- mergeStrategy,
+ const index = getIndexListFromEsqlQuery(completeRule.ruleParams.query);
+
+ const sourceDocuments = await fetchSourceDocuments({
+ esClient: services.scopedClusterClient.asCurrentUser,
+ results,
+ index,
isRuleAggregating,
- alertTimestampOverride,
- ruleExecutionLogger,
- publicBaseUrl,
- tuple,
+ loggedRequests: isLoggedRequestsEnabled ? loggedRequests : undefined,
});
- const syntheticHits: Array> = results.map((document) => {
- const { _id, _version, _index, ...source } = document;
-
- return {
- _source: source as SignalSource,
- fields: _id ? sourceDocuments[_id]?.fields : {},
- _id: _id ?? '',
- _index: _index ?? '',
- };
- });
+ const isAlertSuppressionActive = await getIsAlertSuppressionActive({
+ alertSuppression: completeRule.ruleParams.alertSuppression,
+ licensing,
+ });
- if (isAlertSuppressionActive) {
- const wrapSuppressedHits = (events: Array>) =>
- wrapSuppressedEsqlAlerts({
+ const wrapHits = (events: Array>) =>
+ wrapEsqlAlerts({
events,
spaceId,
completeRule,
@@ -173,78 +166,108 @@ export const esqlExecutor = async ({
alertTimestampOverride,
ruleExecutionLogger,
publicBaseUrl,
- primaryTimestamp,
- secondaryTimestamp,
tuple,
});
- const bulkCreateResult = await bulkCreateSuppressedAlertsInMemory({
- enrichedEvents: syntheticHits,
- toReturn: result,
- wrapHits,
- bulkCreate,
- services,
- ruleExecutionLogger,
- tuple,
- alertSuppression: completeRule.ruleParams.alertSuppression,
- wrapSuppressedHits,
- alertTimestampOverride,
- alertWithSuppression,
- experimentalFeatures,
- buildReasonMessage: buildReasonMessageForEsqlAlert,
- mergeSourceAndFields: true,
- // passing 1 here since ES|QL does not support pagination
- maxNumberOfAlertsMultiplier: 1,
+ const syntheticHits: Array> = results.map((document) => {
+ const { _id, _version, _index, ...source } = document;
+
+ return {
+ _source: source as SignalSource,
+ fields: _id ? sourceDocuments[_id]?.fields : {},
+ _id: _id ?? '',
+ _index: _index ?? '',
+ };
});
- ruleExecutionLogger.debug(
- `Created ${bulkCreateResult.createdItemsCount} alerts. Suppressed ${bulkCreateResult.suppressedItemsCount} alerts`
- );
+ if (isAlertSuppressionActive) {
+ const wrapSuppressedHits = (events: Array>) =>
+ wrapSuppressedEsqlAlerts({
+ events,
+ spaceId,
+ completeRule,
+ mergeStrategy,
+ isRuleAggregating,
+ alertTimestampOverride,
+ ruleExecutionLogger,
+ publicBaseUrl,
+ primaryTimestamp,
+ secondaryTimestamp,
+ tuple,
+ });
- if (bulkCreateResult.alertsWereTruncated) {
- result.warningMessages.push(getSuppressionMaxSignalsWarning());
- break;
- }
- } else {
- const wrappedAlerts = wrapHits(syntheticHits);
+ const bulkCreateResult = await bulkCreateSuppressedAlertsInMemory({
+ enrichedEvents: syntheticHits,
+ toReturn: result,
+ wrapHits,
+ bulkCreate,
+ services,
+ ruleExecutionLogger,
+ tuple,
+ alertSuppression: completeRule.ruleParams.alertSuppression,
+ wrapSuppressedHits,
+ alertTimestampOverride,
+ alertWithSuppression,
+ experimentalFeatures,
+ buildReasonMessage: buildReasonMessageForEsqlAlert,
+ mergeSourceAndFields: true,
+ // passing 1 here since ES|QL does not support pagination
+ maxNumberOfAlertsMultiplier: 1,
+ });
- const enrichAlerts = createEnrichEventsFunction({
- services,
- logger: ruleExecutionLogger,
- });
- const bulkCreateResult = await bulkCreate(
- wrappedAlerts,
- tuple.maxSignals - result.createdSignalsCount,
- enrichAlerts
- );
+ ruleExecutionLogger.debug(
+ `Created ${bulkCreateResult.createdItemsCount} alerts. Suppressed ${bulkCreateResult.suppressedItemsCount} alerts`
+ );
- addToSearchAfterReturn({ current: result, next: bulkCreateResult });
- ruleExecutionLogger.debug(`Created ${bulkCreateResult.createdItemsCount} alerts`);
+ if (bulkCreateResult.alertsWereTruncated) {
+ result.warningMessages.push(getSuppressionMaxSignalsWarning());
+ break;
+ }
+ } else {
+ const wrappedAlerts = wrapHits(syntheticHits);
- if (bulkCreateResult.alertsWereTruncated) {
- result.warningMessages.push(getMaxSignalsWarning());
- break;
+ const enrichAlerts = createEnrichEventsFunction({
+ services,
+ logger: ruleExecutionLogger,
+ });
+ const bulkCreateResult = await bulkCreate(
+ wrappedAlerts,
+ tuple.maxSignals - result.createdSignalsCount,
+ enrichAlerts
+ );
+
+ addToSearchAfterReturn({ current: result, next: bulkCreateResult });
+ ruleExecutionLogger.debug(`Created ${bulkCreateResult.createdItemsCount} alerts`);
+
+ if (bulkCreateResult.alertsWereTruncated) {
+ result.warningMessages.push(getMaxSignalsWarning());
+ break;
+ }
+ }
+
+ if (scheduleNotificationResponseActionsService) {
+ scheduleNotificationResponseActionsService({
+ signals: result.createdSignals,
+ signalsCount: result.createdSignalsCount,
+ responseActions: completeRule.ruleParams.responseActions,
+ });
}
- }
- if (scheduleNotificationResponseActionsService) {
- scheduleNotificationResponseActionsService({
- signals: result.createdSignals,
- signalsCount: result.createdSignalsCount,
- responseActions: completeRule.ruleParams.responseActions,
- });
- }
- // no more results will be found
- if (response.values.length < size) {
- ruleExecutionLogger.debug(
- `End of search: Found ${response.values.length} results with page size ${size}`
- );
- break;
+ // no more results will be found
+ if (response.values.length < size) {
+ ruleExecutionLogger.debug(
+ `End of search: Found ${response.values.length} results with page size ${size}`
+ );
+ break;
+ }
+ // ES|QL does not support pagination so we need to increase size of response to be able to catch all events
+ size += tuple.maxSignals;
}
- // ES|QL does not support pagination so we need to increase size of response to be able to catch all events
- size += tuple.maxSignals;
+ } catch (error) {
+ result.errors.push(error.message);
+ result.success = false;
}
- return { ...result, state };
+ return { ...result, state, ...(isLoggedRequestsEnabled ? { loggedRequests } : {}) };
});
};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/fetch_source_documents.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/fetch_source_documents.ts
index 13828c0ed6770..7ee34183f0317 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/fetch_source_documents.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/fetch_source_documents.ts
@@ -7,12 +7,16 @@
import type { ElasticsearchClient } from '@kbn/core/server';
import type * as estypes from '@elastic/elasticsearch/lib/api/types';
+import type { RulePreviewLoggedRequest } from '../../../../../common/api/detection_engine/rule_preview/rule_preview.gen';
+import { logQueryRequest } from '../utils/logged_requests';
+import * as i18n from '../translations';
interface FetchSourceDocumentsArgs {
isRuleAggregating: boolean;
esClient: ElasticsearchClient;
index: string[];
results: Array>;
+ loggedRequests?: RulePreviewLoggedRequest[];
}
/**
* fetches source documents by list of their ids
@@ -24,6 +28,7 @@ export const fetchSourceDocuments = async ({
results,
esClient,
index,
+ loggedRequests,
}: FetchSourceDocumentsArgs): Promise> => {
const ids = results.reduce((acc, doc) => {
if (doc._id) {
@@ -47,16 +52,30 @@ export const fetchSourceDocuments = async ({
},
};
+ const searchBody = {
+ query: idsQuery.query,
+ _source: false,
+ fields: ['*'],
+ };
+ const ignoreUnavailable = true;
+
+ if (loggedRequests) {
+ loggedRequests.push({
+ request: logQueryRequest(searchBody, { index, ignoreUnavailable }),
+ description: i18n.FIND_SOURCE_DOCUMENTS_REQUEST_DESCRIPTION,
+ });
+ }
+
const response = await esClient.search({
index,
- body: {
- query: idsQuery.query,
- _source: false,
- fields: ['*'],
- },
- ignore_unavailable: true,
+ body: searchBody,
+ ignore_unavailable: ignoreUnavailable,
});
+ if (loggedRequests) {
+ loggedRequests[loggedRequests.length - 1].duration = response.took;
+ }
+
return response.hits.hits.reduce>(
(acc, hit) => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/translations.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/translations.ts
new file mode 100644
index 0000000000000..88445b0d41dc3
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/translations.ts
@@ -0,0 +1,29 @@
+/*
+ * 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';
+
+export const ESQL_SEARCH_REQUEST_DESCRIPTION = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.esqlRuleType.esqlSearchRequestDescription',
+ {
+ defaultMessage: 'ES|QL request to find all matches',
+ }
+);
+
+export const FIND_SOURCE_DOCUMENTS_REQUEST_DESCRIPTION = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.esqlRuleType.findSourceDocumentsRequestDescription',
+ {
+ defaultMessage: 'Retrieve source documents when ES|QL query is not aggregable',
+ }
+);
+
+export const EQL_SEARCH_REQUEST_DESCRIPTION = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.esqlRuleType.eqlSearchRequestDescription',
+ {
+ defaultMessage: 'EQL request to find all matches',
+ }
+);
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts
index a29beef7bbb20..78c0a729be10e 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts
@@ -35,6 +35,7 @@ import type { TypeOfFieldMap } from '@kbn/rule-registry-plugin/common/field_map'
import type { Filter, DataViewFieldBase } from '@kbn/es-query';
import type { LicensingPluginSetup } from '@kbn/licensing-plugin/server';
+import type { RulePreviewLoggedRequest } from '../../../../common/api/detection_engine/rule_preview/rule_preview.gen';
import type { RuleResponseAction } from '../../../../common/api/detection_engine/model/rule_response_actions';
import type { ConfigType } from '../../../config';
import type { SetupPlugins } from '../../../plugin';
@@ -74,6 +75,7 @@ export interface SecurityAlertTypeReturnValue {
warning: boolean;
warningMessages: string[];
suppressedAlertsCount?: number;
+ loggedRequests?: RulePreviewLoggedRequest[];
}
export interface RunOpts {
@@ -126,7 +128,12 @@ export type SecurityAlertType<
services: PersistenceServices;
runOpts: RunOpts;
}
- ) => Promise;
+ ) => Promise<
+ SearchAfterAndBulkCreateReturnType & {
+ state: TState;
+ loggedRequests?: RulePreviewLoggedRequest[];
+ }
+ >;
};
export interface CreateSecurityRuleTypeWrapperProps {
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/logged_requests/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/logged_requests/index.ts
new file mode 100644
index 0000000000000..7cea6e0d75fa6
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/logged_requests/index.ts
@@ -0,0 +1,10 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export * from './log_esql';
+export * from './log_eql';
+export * from './log_query';
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/logged_requests/log_eql.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/logged_requests/log_eql.ts
new file mode 100644
index 0000000000000..3f735e533b3b6
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/logged_requests/log_eql.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 type { EqlSearchRequest } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
+
+export const logEqlRequest = (request: EqlSearchRequest): string => {
+ const allowNoIndices =
+ request.allow_no_indices != null ? `?allow_no_indices=${request.allow_no_indices}` : '';
+
+ return `POST /${request.index}/_eql/search${allowNoIndices}\n${JSON.stringify(
+ request.body,
+ null,
+ 2
+ )}`;
+};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/logged_requests/log_esql.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/logged_requests/log_esql.ts
new file mode 100644
index 0000000000000..ea2d652dc5e5e
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/logged_requests/log_esql.ts
@@ -0,0 +1,15 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
+
+export const logEsqlRequest = (esqlRequest: {
+ query: string;
+ filter: QueryDslQueryContainer;
+}): string => {
+ return `POST _query\n${JSON.stringify(esqlRequest, null, 2)}`;
+};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/logged_requests/log_query.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/logged_requests/log_query.ts
new file mode 100644
index 0000000000000..8ea992793e31d
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/logged_requests/log_query.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.
+ */
+
+import type {
+ QueryDslQueryContainer,
+ SearchSourceConfig,
+ Indices,
+ Fields,
+} from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
+
+interface SearchRequest {
+ query?: QueryDslQueryContainer;
+ _source?: SearchSourceConfig;
+ fields?: Fields;
+}
+
+interface LogQueryRequestParams {
+ index: Indices;
+ ignoreUnavailable?: boolean;
+}
+
+export const logQueryRequest = (
+ searchRequest: SearchRequest,
+ { index, ignoreUnavailable = false }: LogQueryRequestParams
+): string => {
+ return `POST /${index}/_search?ignore_unavailable=${ignoreUnavailable}\n${JSON.stringify(
+ searchRequest,
+ null,
+ 2
+ )}`;
+};
diff --git a/x-pack/test/api_integration/services/security_solution_api.gen.ts b/x-pack/test/api_integration/services/security_solution_api.gen.ts
index 5bf7ac87908d3..44f928e98bd0f 100644
--- a/x-pack/test/api_integration/services/security_solution_api.gen.ts
+++ b/x-pack/test/api_integration/services/security_solution_api.gen.ts
@@ -116,7 +116,10 @@ import { PreviewRiskScoreRequestBodyInput } from '@kbn/security-solution-plugin/
import { ReadAlertsMigrationStatusRequestQueryInput } from '@kbn/security-solution-plugin/common/api/detection_engine/signals_migration/read_signals_migration_status/read_signals_migration_status.gen';
import { ReadRuleRequestQueryInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/crud/read_rule/read_rule_route.gen';
import { ResolveTimelineRequestQueryInput } from '@kbn/security-solution-plugin/common/api/timeline/resolve_timeline/resolve_timeline_route.gen';
-import { RulePreviewRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_preview/rule_preview.gen';
+import {
+ RulePreviewRequestQueryInput,
+ RulePreviewRequestBodyInput,
+} from '@kbn/security-solution-plugin/common/api/detection_engine/rule_preview/rule_preview.gen';
import { SearchAlertsRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/signals/query_signals/query_signals_route.gen';
import { SetAlertAssigneesRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/alert_assignees/set_alert_assignees_route.gen';
import { SetAlertsStatusRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/signals/set_signal_status/set_signals_status_route.gen';
@@ -1058,7 +1061,8 @@ detection engine rules.
.set('kbn-xsrf', 'true')
.set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
- .send(props.body as object);
+ .send(props.body as object)
+ .query(props.query);
},
scheduleRiskEngineNow() {
return supertest
@@ -1394,6 +1398,7 @@ export interface ResolveTimelineProps {
query: ResolveTimelineRequestQueryInput;
}
export interface RulePreviewProps {
+ query: RulePreviewRequestQueryInput;
body: RulePreviewRequestBodyInput;
}
export interface SearchAlertsProps {
diff --git a/x-pack/test/security_solution_api_integration/config/ess/config.base.ts b/x-pack/test/security_solution_api_integration/config/ess/config.base.ts
index 6cd2702857e0f..705c0b8686dd0 100644
--- a/x-pack/test/security_solution_api_integration/config/ess/config.base.ts
+++ b/x-pack/test/security_solution_api_integration/config/ess/config.base.ts
@@ -82,6 +82,7 @@ export function createTestConfig(options: CreateTestConfigOptions, testFiles?: s
'--xpack.ruleRegistry.unsafe.legacyMultiTenancy.enabled=true',
`--xpack.securitySolution.enableExperimental=${JSON.stringify([
'previewTelemetryUrlEnabled',
+ 'loggingRequestsEnabled',
'riskScoringPersistence',
'riskScoringRoutesEnabled',
'manualRuleRunEnabled',
diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/configs/serverless.config.ts
index 6691a781d4e1e..8f64a859b7002 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/configs/serverless.config.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/configs/serverless.config.ts
@@ -17,6 +17,9 @@ export default createTestConfig({
'testing_ignored.constant',
'/testing_regex*/',
])}`, // See tests within the file "ignore_fields.ts" which use these values in "alertIgnoreFields"
- `--xpack.securitySolution.enableExperimental=${JSON.stringify(['manualRuleRunEnabled'])}`,
+ `--xpack.securitySolution.enableExperimental=${JSON.stringify([
+ 'manualRuleRunEnabled',
+ 'loggingRequestsEnabled',
+ ])}`,
],
});
diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql.ts
index 53b2843399c62..aff2ccc6bccb3 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql.ts
@@ -1189,5 +1189,32 @@ export default ({ getService }: FtrProviderContext) => {
expect(updatedAlerts.hits.hits[0]._source?.[ALERT_SUPPRESSION_DOCS_COUNT]).equal(1);
});
});
+
+ // skipped on MKI since feature flags are not supported there
+ describe('@skipInServerlessMKI preview logged requests', () => {
+ it('should not return requests property when not enabled', async () => {
+ const { logs } = await previewRule({
+ supertest,
+ rule: getEqlRuleForAlertTesting(['auditbeat-*']),
+ });
+
+ expect(logs[0].requests).equal(undefined);
+ });
+ it('should return requests property when enable_logged_requests set to true', async () => {
+ const { logs } = await previewRule({
+ supertest,
+ rule: getEqlRuleForAlertTesting(['auditbeat-*']),
+ enableLoggedRequests: true,
+ });
+
+ const requests = logs[0].requests;
+
+ expect(requests).to.have.length(1);
+ expect(requests![0].description).to.be('EQL request to find all matches');
+ expect(requests![0].request).to.contain(
+ 'POST /auditbeat-*/_eql/search?allow_no_indices=true'
+ );
+ });
+ });
});
};
diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql.ts
index 9fbda25bdae64..166a62b9b08ad 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql.ts
@@ -1408,5 +1408,63 @@ export default ({ getService }: FtrProviderContext) => {
});
});
});
+
+ // skipped on MKI since feature flags are not supported there
+ describe('@skipInServerlessMKI preview logged requests', () => {
+ let rule: EsqlRuleCreateProps;
+ let id: string;
+ beforeEach(async () => {
+ id = uuidv4();
+ const interval: [string, string] = ['2020-10-28T06:00:00.000Z', '2020-10-28T06:10:00.000Z'];
+ const doc1 = { agent: { name: 'test-1' } };
+
+ rule = {
+ ...getCreateEsqlRulesSchemaMock('rule-1', true),
+ query: `from ecs_compliant metadata _id ${internalIdPipe(
+ id
+ )} | where agent.name=="test-1"`,
+ from: 'now-1h',
+ interval: '1h',
+ };
+
+ await indexEnhancedDocuments({ documents: [doc1], interval, id });
+ });
+
+ it('should not return requests property when not enabled', async () => {
+ const { logs } = await previewRule({
+ supertest,
+ rule,
+ timeframeEnd: new Date('2020-10-28T06:30:00.000Z'),
+ });
+
+ expect(logs[0]).not.toHaveProperty('requests');
+ });
+ it('should return requests property when enable_logged_requests set to true', async () => {
+ const { logs } = await previewRule({
+ supertest,
+ rule,
+ timeframeEnd: new Date('2020-10-28T06:30:00.000Z'),
+ enableLoggedRequests: true,
+ });
+
+ const requests = logs[0].requests;
+ expect(requests).toHaveLength(2);
+
+ expect(requests).toHaveProperty('0.description', 'ES|QL request to find all matches');
+ expect(requests).toHaveProperty('0.duration', expect.any(Number));
+ expect(requests![0].request).toContain(
+ `"query": "from ecs_compliant metadata _id | where id==\\\"${id}\\\" | where agent.name==\\\"test-1\\\" | limit 101",`
+ );
+
+ expect(requests).toHaveProperty(
+ '1.description',
+ 'Retrieve source documents when ES|QL query is not aggregable'
+ );
+ expect(requests).toHaveProperty('1.duration', expect.any(Number));
+ expect(requests![1].request).toContain(
+ 'POST /ecs_compliant/_search?ignore_unavailable=true'
+ );
+ });
+ });
});
};
diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/preview_rule.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/preview_rule.ts
index a601edde98168..79668b902f5d0 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/preview_rule.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/preview_rule.ts
@@ -26,11 +26,13 @@ export const previewRule = async ({
rule,
invocationCount = 1,
timeframeEnd = new Date(),
+ enableLoggedRequests,
}: {
supertest: SuperTest.Agent;
rule: RuleCreateProps;
invocationCount?: number;
timeframeEnd?: Date;
+ enableLoggedRequests?: boolean;
}): Promise<{
previewId: string;
logs: RulePreviewLogs[];
@@ -43,6 +45,7 @@ export const previewRule = async ({
};
const response = await supertest
.post(DETECTION_ENGINE_RULES_PREVIEW)
+ .query(enableLoggedRequests ? { enable_logged_requests: true } : {})
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '2023-10-31')
.send(previewRequest)
diff --git a/x-pack/test/security_solution_cypress/config.ts b/x-pack/test/security_solution_cypress/config.ts
index 58d873369d99d..88752eb1b5f93 100644
--- a/x-pack/test/security_solution_cypress/config.ts
+++ b/x-pack/test/security_solution_cypress/config.ts
@@ -44,7 +44,10 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
// See https://github.com/elastic/kibana/pull/125396 for details
'--xpack.alerting.rules.minimumScheduleInterval.value=1s',
'--xpack.ruleRegistry.unsafe.legacyMultiTenancy.enabled=true',
- `--xpack.securitySolution.enableExperimental=${JSON.stringify(['manualRuleRunEnabled'])}`,
+ `--xpack.securitySolution.enableExperimental=${JSON.stringify([
+ 'manualRuleRunEnabled',
+ 'loggingRequestsEnabled',
+ ])}`,
// mock cloud to enable the guided onboarding tour in e2e tests
'--xpack.cloud.id=test',
`--home.disableWelcomeScreen=true`,
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/preview.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/preview.cy.ts
new file mode 100644
index 0000000000000..ce298bafbfea0
--- /dev/null
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/preview.cy.ts
@@ -0,0 +1,85 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { getEsqlRule, getSimpleCustomQueryRule } from '../../../../objects/rule';
+
+import {
+ PREVIEW_LOGGED_REQUEST_DESCRIPTION,
+ PREVIEW_LOGGED_REQUEST_CODE_BLOCK,
+ PREVIEW_LOGGED_REQUESTS_CHECKBOX,
+ RULES_CREATION_PREVIEW_REFRESH_BUTTON,
+} from '../../../../screens/create_new_rule';
+
+import { createRule } from '../../../../tasks/api_calls/rules';
+
+import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common';
+import {
+ checkEnableLoggedRequests,
+ submitRulePreview,
+ toggleLoggedRequestsAccordion,
+ toggleLoggedRequestsItemAccordion,
+} from '../../../../tasks/create_new_rule';
+import { login } from '../../../../tasks/login';
+
+import { visitEditRulePage } from '../../../../tasks/edit_rule';
+
+const expectedValidEsqlQuery = 'from auditbeat* METADATA _id';
+
+describe(
+ 'Detection rules, preview',
+ {
+ // Currently FF are not supported on MKI environments, so this test should be skipped from MKI environments.
+ // Once `manualRuleRunEnabled` FF is removed, we can remove `@skipInServerlessMKI` as well
+ tags: ['@ess', '@serverless', '@skipInServerlessMKI'],
+ env: {
+ kbnServerArgs: [
+ `--xpack.securitySolution.enableExperimental=${JSON.stringify(['loggingRequestsEnabled'])}`,
+ ],
+ },
+ },
+ () => {
+ beforeEach(() => {
+ login();
+ deleteAlertsAndRules();
+ });
+
+ describe('supports preview logged requests', () => {
+ beforeEach(() => {
+ createRule({ ...getEsqlRule(), query: expectedValidEsqlQuery }).then((createdRule) => {
+ visitEditRulePage(createdRule.body.id);
+ });
+ });
+
+ it('shows preview logged requests', () => {
+ checkEnableLoggedRequests();
+ submitRulePreview();
+
+ toggleLoggedRequestsAccordion();
+ toggleLoggedRequestsItemAccordion();
+
+ cy.get(PREVIEW_LOGGED_REQUEST_DESCRIPTION)
+ .first()
+ .contains('ES|QL request to find all matches');
+
+ cy.get(PREVIEW_LOGGED_REQUEST_CODE_BLOCK).first().contains(expectedValidEsqlQuery);
+ });
+ });
+
+ describe('does not support preview logged requests', () => {
+ beforeEach(() => {
+ createRule(getSimpleCustomQueryRule()).then((createdRule) => {
+ visitEditRulePage(createdRule.body.id);
+ });
+ });
+
+ it('does not show preview logged requests checkbox', () => {
+ cy.get(RULES_CREATION_PREVIEW_REFRESH_BUTTON).should('be.visible');
+ cy.get(PREVIEW_LOGGED_REQUESTS_CHECKBOX).should('not.exist');
+ });
+ });
+ }
+);
diff --git a/x-pack/test/security_solution_cypress/cypress/screens/create_new_rule.ts b/x-pack/test/security_solution_cypress/cypress/screens/create_new_rule.ts
index 72d1104985d77..a191fc22aa339 100644
--- a/x-pack/test/security_solution_cypress/cypress/screens/create_new_rule.ts
+++ b/x-pack/test/security_solution_cypress/cypress/screens/create_new_rule.ts
@@ -296,3 +296,19 @@ export const RULE_INDICES =
'[data-test-subj="detectionEngineStepDefineRuleIndices"] [data-test-subj="comboBoxInput"]';
export const ALERTS_INDEX_BUTTON = 'span[title=".alerts-security.alerts-default"] button';
+
+export const PREVIEW_SUBMIT_BUTTON = '[data-test-subj="previewSubmitButton"]';
+
+export const PREVIEW_LOGGED_REQUESTS_CHECKBOX = '[data-test-subj="show-elasticsearch-requests"]';
+
+export const PREVIEW_LOGGED_REQUESTS_ACCORDION_BUTTON =
+ '[data-test-subj="preview-logged-requests-accordion"] button';
+
+export const PREVIEW_LOGGED_REQUESTS_ITEM_ACCORDION_BUTTON =
+ '[data-test-subj="preview-logged-requests-item-accordion"] button';
+
+export const PREVIEW_LOGGED_REQUEST_DESCRIPTION =
+ '[data-test-subj="preview-logged-request-description"]';
+
+export const PREVIEW_LOGGED_REQUEST_CODE_BLOCK =
+ '[data-test-subj="preview-logged-request-code-block"]';
diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/create_new_rule.ts b/x-pack/test/security_solution_cypress/cypress/tasks/create_new_rule.ts
index ecc37de80b456..68dc2cfffd908 100644
--- a/x-pack/test/security_solution_cypress/cypress/tasks/create_new_rule.ts
+++ b/x-pack/test/security_solution_cypress/cypress/tasks/create_new_rule.ts
@@ -130,6 +130,9 @@ import {
RELATED_INTEGRATION_COMBO_BOX_INPUT,
SAVE_WITH_ERRORS_MODAL,
SAVE_WITH_ERRORS_MODAL_CONFIRM_BTN,
+ PREVIEW_LOGGED_REQUESTS_ACCORDION_BUTTON,
+ PREVIEW_LOGGED_REQUESTS_ITEM_ACCORDION_BUTTON,
+ PREVIEW_LOGGED_REQUESTS_CHECKBOX,
} from '../screens/create_new_rule';
import {
INDEX_SELECTOR,
@@ -996,3 +999,20 @@ export const uncheckLoadQueryDynamically = () => {
export const openAddFilterPopover = () => {
cy.get(QUERY_BAR_ADD_FILTER).click();
};
+
+export const checkEnableLoggedRequests = () => {
+ cy.get(PREVIEW_LOGGED_REQUESTS_CHECKBOX).click();
+ cy.get(PREVIEW_LOGGED_REQUESTS_CHECKBOX).should('be.checked');
+};
+
+export const submitRulePreview = () => {
+ cy.get(RULES_CREATION_PREVIEW_REFRESH_BUTTON).click();
+};
+
+export const toggleLoggedRequestsAccordion = () => {
+ cy.get(PREVIEW_LOGGED_REQUESTS_ACCORDION_BUTTON).first().click();
+};
+
+export const toggleLoggedRequestsItemAccordion = () => {
+ cy.get(PREVIEW_LOGGED_REQUESTS_ITEM_ACCORDION_BUTTON).should('be.visible').first().click();
+};
diff --git a/x-pack/test/security_solution_cypress/serverless_config.ts b/x-pack/test/security_solution_cypress/serverless_config.ts
index 226f2db6dbbc0..13877fcbf5af4 100644
--- a/x-pack/test/security_solution_cypress/serverless_config.ts
+++ b/x-pack/test/security_solution_cypress/serverless_config.ts
@@ -34,7 +34,10 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
{ product_line: 'endpoint', product_tier: 'complete' },
{ product_line: 'cloud', product_tier: 'complete' },
])}`,
- `--xpack.securitySolution.enableExperimental=${JSON.stringify(['manualRuleRunEnabled'])}`,
+ `--xpack.securitySolution.enableExperimental=${JSON.stringify([
+ 'manualRuleRunEnabled',
+ 'loggingRequestsEnabled',
+ ])}`,
'--csp.strict=false',
'--csp.warnLegacyBrowsers=false',
],