From 462e4378100e8d5233e7379f9244da5609b7dd2c Mon Sep 17 00:00:00 2001
From: Drew Tate
Date: Mon, 16 Sep 2024 02:24:55 -0600
Subject: [PATCH 001/139] [ES|QL] exclude inactive integration data stream
suggestions (#192953)
## Summary
Close https://github.com/elastic/kibana/issues/187247
https://github.com/user-attachments/assets/9f56d941-016a-442a-a2cb-b2240b280b54
Co-authored-by: Stratoula Kalafateli
---
packages/kbn-text-based-editor/src/helpers.ts | 18 ++++++++++--------
1 file changed, 10 insertions(+), 8 deletions(-)
diff --git a/packages/kbn-text-based-editor/src/helpers.ts b/packages/kbn-text-based-editor/src/helpers.ts
index f83d7a97342e6..fb541c4fe39be 100644
--- a/packages/kbn-text-based-editor/src/helpers.ts
+++ b/packages/kbn-text-based-editor/src/helpers.ts
@@ -249,20 +249,22 @@ const getIntegrations = async (core: CoreStart) => {
// and this needs to be done in various places in the codebase which use the editor
// https://github.com/elastic/kibana/issues/186061
const response = (await core.http
- .get(INTEGRATIONS_API, { query: undefined, version: API_VERSION })
+ .get(INTEGRATIONS_API, { query: { showOnlyActiveDataStreams: true }, version: API_VERSION })
.catch((error) => {
// eslint-disable-next-line no-console
console.error('Failed to fetch integrations', error);
})) as IntegrationsResponse;
return (
- response?.items?.map((source) => ({
- name: source.name,
- hidden: false,
- title: source.title,
- dataStreams: source.dataStreams,
- type: 'Integration',
- })) ?? []
+ response?.items
+ ?.filter(({ dataStreams }) => dataStreams.length)
+ .map((source) => ({
+ name: source.name,
+ hidden: false,
+ title: source.title,
+ dataStreams: source.dataStreams,
+ type: 'Integration',
+ })) ?? []
);
};
From bce4a17f088969621ade141a8d19ff3fcde833b0 Mon Sep 17 00:00:00 2001
From: Alex Szabo
Date: Mon, 16 Sep 2024 10:43:12 +0200
Subject: [PATCH 002/139] [CI] Archive logs from `yarn es` docker runs
(#189231)
## Summary
The problem we're trying to solve here is to get access to
`elasticsearch-serverless` logs when they're started in docker
containers in the background (and `elasticsearch`, although currently we
don't test against that in docker for now).
## Solution
In essence:
- we needed to remove the `--rm` flag, this would allow for the
containers to stay present after they're done.
- after this, we can run `docker logs ...` on FTR post-hooks, save
these, then archive these files to buildkite
- because the containers are not removed upon finishing, we need to
clean up dangling containers before starting up
Backporting is probably not necessary, because this is only applicable
for serverless - and serverless is only supposed to run on main.
Solves: https://github.com/elastic/kibana/issues/191505
---
.buildkite/scripts/lifecycle/post_command.sh | 1 +
packages/kbn-es/src/utils/docker.test.ts | 6 +-
packages/kbn-es/src/utils/docker.ts | 43 +++++++----
.../src/utils/extract_and_archive_logs.ts | 73 +++++++++++++++++++
packages/kbn-es/src/utils/index.ts | 1 +
.../functional_tests/lib/run_elasticsearch.ts | 4 +-
6 files changed, 111 insertions(+), 17 deletions(-)
create mode 100644 packages/kbn-es/src/utils/extract_and_archive_logs.ts
diff --git a/.buildkite/scripts/lifecycle/post_command.sh b/.buildkite/scripts/lifecycle/post_command.sh
index 26578f9b9cce1..f90a4b451be1f 100755
--- a/.buildkite/scripts/lifecycle/post_command.sh
+++ b/.buildkite/scripts/lifecycle/post_command.sh
@@ -35,6 +35,7 @@ if [[ "$IS_TEST_EXECUTION_STEP" == "true" ]]; then
buildkite-agent artifact upload 'x-pack/test/functional/failure_debug/html/*.html'
buildkite-agent artifact upload '.es/**/*.hprof'
buildkite-agent artifact upload 'data/es_debug_*.tar.gz'
+ buildkite-agent artifact upload '.es/es*.log'
if [[ $BUILDKITE_COMMAND_EXIT_STATUS -ne 0 ]]; then
if [[ $BUILDKITE_TRIGGERED_FROM_BUILD_PIPELINE_SLUG == 'elasticsearch-serverless-intake' ]]; then
diff --git a/packages/kbn-es/src/utils/docker.test.ts b/packages/kbn-es/src/utils/docker.test.ts
index a128db03d6ad2..93dee967ee8ac 100644
--- a/packages/kbn-es/src/utils/docker.test.ts
+++ b/packages/kbn-es/src/utils/docker.test.ts
@@ -665,12 +665,13 @@ describe('runServerlessCluster()', () => {
// docker version (1)
// docker ps (1)
+ // docker container rm (3)
// docker network create (1)
// docker pull (1)
// docker inspect (1)
// docker run (3)
// docker logs (1)
- expect(execa.mock.calls).toHaveLength(9);
+ expect(execa.mock.calls).toHaveLength(12);
});
test(`should wait for serverless nodes to return 'green' status`, async () => {
@@ -806,11 +807,12 @@ describe('runDockerContainer()', () => {
await expect(runDockerContainer(log, {})).resolves.toBeUndefined();
// docker version (1)
// docker ps (1)
+ // docker container rm (3)
// docker network create (1)
// docker pull (1)
// docker inspect (1)
// docker run (1)
- expect(execa.mock.calls).toHaveLength(6);
+ expect(execa.mock.calls).toHaveLength(9);
});
});
diff --git a/packages/kbn-es/src/utils/docker.ts b/packages/kbn-es/src/utils/docker.ts
index c36ed5e8a4bae..6120cc2af0561 100644
--- a/packages/kbn-es/src/utils/docker.ts
+++ b/packages/kbn-es/src/utils/docker.ts
@@ -113,8 +113,6 @@ const DOCKER_REGISTRY = 'docker.elastic.co';
const DOCKER_BASE_CMD = [
'run',
- '--rm',
-
'-t',
'--net',
@@ -151,8 +149,6 @@ export const ES_SERVERLESS_DEFAULT_IMAGE = `${ES_SERVERLESS_REPO_KIBANA}:${ES_SE
const SHARED_SERVERLESS_PARAMS = [
'run',
- '--rm',
-
'--detach',
'--interactive',
@@ -391,7 +387,6 @@ const RETRYABLE_DOCKER_PULL_ERROR_MESSAGES = [
];
/**
- *
* Pull a Docker image if needed. Ensures latest image.
* Stops serverless from pulling the same image in each node's promise and
* gives better control of log output, instead of falling back to docker run.
@@ -443,6 +438,24 @@ export async function printESImageInfo(log: ToolingLog, image: string) {
log.info(`Using ES image: ${imageFullName} (${revisionUrl})`);
}
+export async function cleanUpDanglingContainers(log: ToolingLog) {
+ log.info(chalk.bold('Cleaning up dangling Docker containers.'));
+
+ try {
+ const serverlessContainerNames = SERVERLESS_NODES.map(({ name }) => name);
+
+ for (const name of serverlessContainerNames) {
+ await execa('docker', ['container', 'rm', name, '--force']).catch(() => {
+ // Ignore errors if the container doesn't exist
+ });
+ }
+
+ log.success('Cleaned up dangling Docker containers.');
+ } catch (e) {
+ log.error(e);
+ }
+}
+
export async function detectRunningNodes(
log: ToolingLog,
options: ServerlessOptions | DockerOptions
@@ -454,19 +467,19 @@ export async function detectRunningNodes(
}, []);
const { stdout } = await execa('docker', ['ps', '--quiet'].concat(namesCmd));
- const runningNodes = stdout.split(/\r?\n/).filter((s) => s);
+ const runningNodeIds = stdout.split(/\r?\n/).filter((s) => s);
- if (runningNodes.length) {
+ if (runningNodeIds.length) {
if (options.kill) {
log.info(chalk.bold('Killing running ES Nodes.'));
- await execa('docker', ['kill'].concat(runningNodes));
-
- return;
+ await execa('docker', ['kill'].concat(runningNodeIds));
+ } else {
+ throw createCliError(
+ 'ES has already been started, pass --kill to automatically stop the nodes on startup.'
+ );
}
-
- throw createCliError(
- 'ES has already been started, pass --kill to automatically stop the nodes on startup.'
- );
+ } else {
+ log.info('No running nodes detected.');
}
}
@@ -484,6 +497,7 @@ async function setupDocker({
}) {
await verifyDockerInstalled(log);
await detectRunningNodes(log, options);
+ await cleanUpDanglingContainers(log);
await maybeCreateDockerNetwork(log);
await maybePullDockerImage(log, image);
await printESImageInfo(log, image);
@@ -774,6 +788,7 @@ export async function runServerlessCluster(log: ToolingLog, options: ServerlessO
const volumeCmd = await setupServerlessVolumes(log, options);
const portCmd = resolvePort(options);
+ // This is where nodes are started
const nodeNames = await Promise.all(
SERVERLESS_NODES.map(async (node, i) => {
await runServerlessEsNode(log, {
diff --git a/packages/kbn-es/src/utils/extract_and_archive_logs.ts b/packages/kbn-es/src/utils/extract_and_archive_logs.ts
new file mode 100644
index 0000000000000..28dde547f6b0d
--- /dev/null
+++ b/packages/kbn-es/src/utils/extract_and_archive_logs.ts
@@ -0,0 +1,73 @@
+/*
+ * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
+ * Public License v 1"; you may not use this file except in compliance with, at
+ * your election, the "Elastic License 2.0", the "GNU Affero General Public
+ * License v3.0 only", or the "Server Side Public License, v 1".
+ */
+
+import type { ToolingLog } from '@kbn/tooling-log';
+
+import execa from 'execa';
+import Fsp from 'fs/promises';
+import { join } from 'path';
+
+import { REPO_ROOT } from '@kbn/repo-info';
+
+/**
+ * Extracts logs from Docker nodes, writes them to files, and returns the file paths.
+ */
+export async function extractAndArchiveLogs({
+ outputFolder,
+ log,
+ nodeNames,
+}: {
+ log: ToolingLog;
+ nodeNames?: string[];
+ outputFolder?: string;
+}) {
+ outputFolder = outputFolder || join(REPO_ROOT, '.es');
+ const logFiles: string[] = [];
+
+ if (!nodeNames) {
+ const { stdout: nodeNamesString } = await execa('docker', [
+ 'ps',
+ '-a',
+ '--format',
+ '{{.Names}}',
+ ]);
+ nodeNames = nodeNamesString.split('\n').filter(Boolean);
+ }
+
+ if (!nodeNames.length) {
+ log.info('No Docker nodes found to extract logs from');
+ return;
+ } else {
+ log.info(`Attempting to extract logs from Docker nodes to ${outputFolder}`);
+ }
+
+ for (const name of nodeNames) {
+ const { stdout: nodeId } = await execa('docker', [
+ 'ps',
+ '-a',
+ '--quiet',
+ '--filter',
+ `name=${name}`,
+ ]);
+ if (!nodeId) {
+ continue;
+ }
+
+ const { stdout } = await execa('docker', ['logs', name]);
+ const targetFile = `${name}-${nodeId}.log`;
+ const targetPath = join(outputFolder, targetFile);
+
+ await Fsp.writeFile(targetPath, stdout);
+ logFiles.push(targetFile);
+
+ log.info(`Archived logs for ${name} to ${targetPath}`);
+ }
+
+ return logFiles;
+}
diff --git a/packages/kbn-es/src/utils/index.ts b/packages/kbn-es/src/utils/index.ts
index dd57c54d4a101..e1a51ecb44685 100644
--- a/packages/kbn-es/src/utils/index.ts
+++ b/packages/kbn-es/src/utils/index.ts
@@ -20,3 +20,4 @@ export * from './parse_timeout_to_ms';
export * from './docker';
export * from './serverless_file_realm';
export * from './read_roles_from_resource';
+export * from './extract_and_archive_logs';
diff --git a/packages/kbn-test/src/functional_tests/lib/run_elasticsearch.ts b/packages/kbn-test/src/functional_tests/lib/run_elasticsearch.ts
index c87065fd1cdaf..724cf5bc2b25e 100644
--- a/packages/kbn-test/src/functional_tests/lib/run_elasticsearch.ts
+++ b/packages/kbn-test/src/functional_tests/lib/run_elasticsearch.ts
@@ -13,7 +13,7 @@ import type { ToolingLog } from '@kbn/tooling-log';
import getPort from 'get-port';
import { REPO_ROOT } from '@kbn/repo-info';
import type { ArtifactLicense, ServerlessProjectType } from '@kbn/es';
-import { isServerlessProjectType } from '@kbn/es/src/utils';
+import { isServerlessProjectType, extractAndArchiveLogs } from '@kbn/es/src/utils';
import type { Config } from '../../functional_test_runner';
import { createTestEsCluster, esTestConfig } from '../../es';
@@ -91,6 +91,7 @@ export async function runElasticsearch(
});
return async () => {
await node.cleanup();
+ await extractAndArchiveLogs({ outputFolder: logsDir, log });
};
}
@@ -119,6 +120,7 @@ export async function runElasticsearch(
return async () => {
await localNode.cleanup();
await remoteNode.cleanup();
+ await extractAndArchiveLogs({ outputFolder: logsDir, log });
};
}
From 708a96cca8f636347fb37669b13e5b61cd5015d1 Mon Sep 17 00:00:00 2001
From: Stratoula Kalafateli
Date: Mon, 16 Sep 2024 10:44:55 +0200
Subject: [PATCH 003/139] [ES|QL] Set drop null columns param correctly for
partial results (#192666)
## Summary
Closes https://github.com/elastic/kibana/issues/192595
We were not setting the `drop_null_columns ` queryString for partial
results and as a result it was returning the empty columns only for the
initial request, resulting in the weirdness that is being described in
the issue.
---
.../esql_async_search_strategy.ts | 2 +-
.../functional/apps/discover/group6/_sidebar.ts | 17 +++++++++++++----
2 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/src/plugins/data/server/search/strategies/esql_async_search/esql_async_search_strategy.ts b/src/plugins/data/server/search/strategies/esql_async_search/esql_async_search_strategy.ts
index ac337d237c89b..83b67e5ecb4fd 100644
--- a/src/plugins/data/server/search/strategies/esql_async_search/esql_async_search_strategy.ts
+++ b/src/plugins/data/server/search/strategies/esql_async_search/esql_async_search_strategy.ts
@@ -79,7 +79,7 @@ export const esqlAsyncSearchStrategyProvider = (
{
method: 'GET',
path: `/_query/async/${id}`,
- querystring: { ...params },
+ querystring: { ...params, drop_null_columns: dropNullColumns },
},
{ ...options.transport, signal: options.abortSignal, meta: true }
)
diff --git a/test/functional/apps/discover/group6/_sidebar.ts b/test/functional/apps/discover/group6/_sidebar.ts
index 3e746a3726bcc..51066cd3c04e0 100644
--- a/test/functional/apps/discover/group6/_sidebar.ts
+++ b/test/functional/apps/discover/group6/_sidebar.ts
@@ -114,12 +114,17 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
options = await find.allByCssSelector('[data-test-subj*="typeFilter"]');
expect(options).to.have.length(6);
- expect(await unifiedFieldList.getSidebarAriaDescription()).to.be('82 available fields.');
+ expect(await unifiedFieldList.getSidebarAriaDescription()).to.be(
+ '76 available fields. 6 empty fields.'
+ );
await testSubjects.click('typeFilter-number');
await retry.waitFor('updates', async () => {
- return (await unifiedFieldList.getSidebarAriaDescription()) === '6 available fields.';
+ return (
+ (await unifiedFieldList.getSidebarAriaDescription()) ===
+ '4 available fields. 2 empty fields.'
+ );
});
});
@@ -446,12 +451,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await header.waitUntilLoadingHasFinished();
await unifiedFieldList.waitUntilSidebarHasLoaded();
- expect(await unifiedFieldList.getSidebarAriaDescription()).to.be('82 available fields.');
+ expect(await unifiedFieldList.getSidebarAriaDescription()).to.be(
+ '76 available fields. 6 empty fields.'
+ );
await unifiedFieldList.clickFieldListItemRemove('extension');
await unifiedFieldList.waitUntilSidebarHasLoaded();
- expect(await unifiedFieldList.getSidebarAriaDescription()).to.be('82 available fields.');
+ expect(await unifiedFieldList.getSidebarAriaDescription()).to.be(
+ '76 available fields. 6 empty fields.'
+ );
const testQuery = `from logstash-* | limit 10 | stats countB = count(bytes) by geo.dest | sort countB`;
From 08b682fbd5f78ac82b42ddc7109e79f89e4ed961 Mon Sep 17 00:00:00 2001
From: Pierre Gayvallet
Date: Mon, 16 Sep 2024 11:06:54 +0200
Subject: [PATCH 004/139] OpenAI: Ignore chunks with an empty `choices` list
(bis) (#192961)
## Summary
Follow-up of https://github.com/elastic/kibana/pull/192951 to address
the missing bits (given I don't know how to grep)
---
.../actions/server/lib/get_token_count_from_invoke_stream.ts | 4 +++-
x-pack/plugins/elastic_assistant/server/lib/parse_stream.ts | 4 +++-
.../common/utils/process_openai_stream.ts | 2 +-
3 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/x-pack/plugins/actions/server/lib/get_token_count_from_invoke_stream.ts b/x-pack/plugins/actions/server/lib/get_token_count_from_invoke_stream.ts
index 604e623699136..909b7e09abda0 100644
--- a/x-pack/plugins/actions/server/lib/get_token_count_from_invoke_stream.ts
+++ b/x-pack/plugins/actions/server/lib/get_token_count_from_invoke_stream.ts
@@ -288,7 +288,9 @@ const parseOpenAIResponse = (responseBody: string) =>
delta: { content?: string; function_call?: { name?: string; arguments: string } };
}>;
} => {
- return 'object' in line && line.object === 'chat.completion.chunk';
+ return (
+ 'object' in line && line.object === 'chat.completion.chunk' && line.choices.length > 0
+ );
}
)
.reduce((prev, line) => {
diff --git a/x-pack/plugins/elastic_assistant/server/lib/parse_stream.ts b/x-pack/plugins/elastic_assistant/server/lib/parse_stream.ts
index 55be80cbb522d..3aef870be8116 100644
--- a/x-pack/plugins/elastic_assistant/server/lib/parse_stream.ts
+++ b/x-pack/plugins/elastic_assistant/server/lib/parse_stream.ts
@@ -85,7 +85,9 @@ const parseOpenAIResponse = (responseBody: string) =>
delta: { content?: string; function_call?: { name?: string; arguments: string } };
}>;
} => {
- return 'object' in line && line.object === 'chat.completion.chunk';
+ return (
+ 'object' in line && line.object === 'chat.completion.chunk' && line.choices.length > 0
+ );
}
)
.reduce((prev, line) => {
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/process_openai_stream.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/process_openai_stream.ts
index 11fe48721812c..184b4817abf64 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/process_openai_stream.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/process_openai_stream.ts
@@ -42,7 +42,7 @@ export function processOpenAiStream(logger: Logger) {
}),
filter(
(line): line is CreateChatCompletionResponseChunk =>
- 'object' in line && line.object === 'chat.completion.chunk'
+ 'object' in line && line.object === 'chat.completion.chunk' && line.choices.length > 0
),
map((chunk): ChatCompletionChunkEvent => {
const delta = chunk.choices[0].delta;
From 2b12950e71f6cc7a616eeec1e787e76f3009575c Mon Sep 17 00:00:00 2001
From: Konrad Szwarc
Date: Mon, 16 Sep 2024 11:51:49 +0200
Subject: [PATCH 005/139] [EDR Workflows][MKI] Skip event_filters.cy.ts in MKI
(#192969)
Skip due to interactions with internal indices which is not supported in
MKI
---
.../management/cypress/e2e/artifacts/event_filters.cy.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/event_filters.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/event_filters.cy.ts
index 469f3162a47df..af7310953e86e 100644
--- a/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/event_filters.cy.ts
+++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/event_filters.cy.ts
@@ -17,7 +17,8 @@ import { indexEndpointHosts } from '../../tasks/index_endpoint_hosts';
import { login } from '../../tasks/login';
import type { ReturnTypeFromChainable } from '../../types';
-describe('Event Filters', { tags: ['@ess', '@serverless'] }, () => {
+// Skipped in Serverless MKI due to interactions with internal indices
+describe('Event Filters', { tags: ['@ess', '@serverless', '@skipInServerlessMKI'] }, () => {
let endpointData: ReturnTypeFromChainable | undefined;
const CONDITION_VALUE = 'valuesAutocompleteMatch';
From a581087f530ce4a9143d54d76147b7b645a39da4 Mon Sep 17 00:00:00 2001
From: Stratoula Kalafateli
Date: Mon, 16 Sep 2024 12:03:58 +0200
Subject: [PATCH 006/139] [ES|QL] Enable cursor sync for timeseries charts
(#192837)
## Summary
Syncs the cursor for timeseries charts powered by ES|QL

### Checklist
- [ ] [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
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Marco Vettorello
---
.../active_cursor/active_cursor_utils.test.ts | 33 +++++++++++++++++++
.../active_cursor/active_cursor_utils.ts | 11 +++++++
src/plugins/charts/tsconfig.json | 1 +
3 files changed, 45 insertions(+)
diff --git a/src/plugins/charts/public/services/active_cursor/active_cursor_utils.test.ts b/src/plugins/charts/public/services/active_cursor/active_cursor_utils.test.ts
index 31f139eb41a4f..65b6ac7c408ee 100644
--- a/src/plugins/charts/public/services/active_cursor/active_cursor_utils.test.ts
+++ b/src/plugins/charts/public/services/active_cursor/active_cursor_utils.test.ts
@@ -137,6 +137,39 @@ describe('active_cursor_utils', () => {
}
`);
});
+
+ test('should return isDateHistogram true in case the datatable is powered by ES|QL', () => {
+ expect(
+ parseSyncOptions({
+ datatables: [
+ {
+ columns: [
+ {
+ id: 'timestamp',
+ meta: {
+ type: 'date',
+ },
+ },
+ {
+ id: 'count',
+ meta: {
+ type: 'number',
+ },
+ },
+ ],
+ meta: {
+ type: 'es_ql',
+ },
+ },
+ ] as unknown as Datatable[],
+ })
+ ).toMatchInlineSnapshot(`
+ Object {
+ "accessors": Array [],
+ "isDateHistogram": true,
+ }
+ `);
+ });
});
});
});
diff --git a/src/plugins/charts/public/services/active_cursor/active_cursor_utils.ts b/src/plugins/charts/public/services/active_cursor/active_cursor_utils.ts
index c2126e5efdc2b..8c7fd434a5c08 100644
--- a/src/plugins/charts/public/services/active_cursor/active_cursor_utils.ts
+++ b/src/plugins/charts/public/services/active_cursor/active_cursor_utils.ts
@@ -10,6 +10,7 @@
import { uniq } from 'lodash';
import type { Datatable } from '@kbn/expressions-plugin/public';
+import { ESQL_TABLE_TYPE } from '@kbn/data-plugin/common';
import type { ActiveCursorSyncOption, DateHistogramSyncOption } from './types';
import type { ActiveCursorPayload } from './types';
@@ -20,6 +21,16 @@ function isDateHistogramSyncOption(
}
const parseDatatable = (dataTables: Datatable[]) => {
+ const isEsqlMode = dataTables.some((t) => t?.meta?.type === ESQL_TABLE_TYPE);
+
+ if (isEsqlMode) {
+ return {
+ isDateHistogram:
+ Boolean(dataTables.length) &&
+ dataTables.every((t) => t.columns.some((c) => c.meta.type === 'date')),
+ accessors: [],
+ };
+ }
const isDateHistogram =
Boolean(dataTables.length) &&
dataTables.every((dataTable) =>
diff --git a/src/plugins/charts/tsconfig.json b/src/plugins/charts/tsconfig.json
index 42bbe987f45fa..8ad33a8517031 100644
--- a/src/plugins/charts/tsconfig.json
+++ b/src/plugins/charts/tsconfig.json
@@ -15,6 +15,7 @@
"@kbn/ui-theme",
"@kbn/shared-ux-utility",
"@kbn/config-schema",
+ "@kbn/data-plugin",
],
"exclude": [
"target/**/*",
From b89314ee70e35c0b4ea44dc4c581a9442f6e9431 Mon Sep 17 00:00:00 2001
From: "elastic-renovate-prod[bot]"
<174716857+elastic-renovate-prod[bot]@users.noreply.github.com>
Date: Mon, 16 Sep 2024 05:33:29 -0500
Subject: [PATCH 007/139] Update dependency @launchdarkly/node-server-sdk to
^9.5.4 (main) (#192138)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
|
[@launchdarkly/node-server-sdk](https://togithub.com/launchdarkly/js-core/tree/main/packages/sdk/server-node)
([source](https://togithub.com/launchdarkly/js-core)) | dependencies |
patch | [`^9.5.1` ->
`^9.5.4`](https://renovatebot.com/diffs/npm/@launchdarkly%2fnode-server-sdk/9.5.1/9.5.4)
|
---
### Configuration
📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).
🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.
♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.
---
- [ ] If you want to rebase/retry this PR, check
this box
---
This PR has been generated by [Renovate
Bot](https://togithub.com/renovatebot/renovate).
Co-authored-by: elastic-renovate-prod[bot] <174716857+elastic-renovate-prod[bot]@users.noreply.github.com>
Co-authored-by: Jean-Louis Leysens
---
package.json | 2 +-
yarn.lock | 28 ++++++++++++++--------------
2 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/package.json b/package.json
index 5aec5cd34dde8..760268042a27b 100644
--- a/package.json
+++ b/package.json
@@ -987,7 +987,7 @@
"@langchain/langgraph": "0.0.34",
"@langchain/openai": "^0.1.3",
"@langtrase/trace-attributes": "^3.0.8",
- "@launchdarkly/node-server-sdk": "^9.5.1",
+ "@launchdarkly/node-server-sdk": "^9.5.4",
"@loaders.gl/core": "^3.4.7",
"@loaders.gl/json": "^3.4.7",
"@loaders.gl/shapefile": "^3.4.7",
diff --git a/yarn.lock b/yarn.lock
index 41799c20a1def..b86acda2e0739 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7309,25 +7309,25 @@
dependencies:
ncp "^2.0.0"
-"@launchdarkly/js-sdk-common@2.6.0":
- version "2.6.0"
- resolved "https://registry.yarnpkg.com/@launchdarkly/js-sdk-common/-/js-sdk-common-2.6.0.tgz#3382051d282a413c8f30e10108eee25172fdbf22"
- integrity sha512-3zbwXZ0JM9i9pktTbjp+CRFMSXf6tZPJPuW3QtqC/qUYbNS8/+JmanO4tfNgSZHar7HUXD9Vy0OTCBqSRw2LWg==
+"@launchdarkly/js-sdk-common@2.8.0":
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/@launchdarkly/js-sdk-common/-/js-sdk-common-2.8.0.tgz#f9a0c29864ba52e5dc19f273164780005be5c8ab"
+ integrity sha512-XLYhB0A0vec1gwbnVNU+rzMd1JxdtMJXSA76y3ASJ6X0cG0qTRKQdFba4n4RwIs0y1bX3rncFHJMhBjrXtyPhA==
-"@launchdarkly/js-server-sdk-common@2.4.5":
- version "2.4.5"
- resolved "https://registry.yarnpkg.com/@launchdarkly/js-server-sdk-common/-/js-server-sdk-common-2.4.5.tgz#28ebd7838e1d90274bd50136ded965c36753a5fb"
- integrity sha512-v4/g6SBO6NDFD9UyP/YKUkbxXNJyBn3Suu/zvz7dHDVlucsryPpGw9TBFkJC6V6i3ACfMAyW14eNulRFP2gvNA==
+"@launchdarkly/js-server-sdk-common@2.6.1":
+ version "2.6.1"
+ resolved "https://registry.yarnpkg.com/@launchdarkly/js-server-sdk-common/-/js-server-sdk-common-2.6.1.tgz#e676641c83d8dcc0e9afa8620dd5142eae873127"
+ integrity sha512-PcyglkUi/P1fBT3GqwZ/K9MvLj2e5Rw5XLP/gC2qIIHJxIbni8oy8Zon+6bP890DYgZtqqeX6tKhAw/gwOYkhQ==
dependencies:
- "@launchdarkly/js-sdk-common" "2.6.0"
+ "@launchdarkly/js-sdk-common" "2.8.0"
semver "7.5.4"
-"@launchdarkly/node-server-sdk@^9.5.1":
- version "9.5.1"
- resolved "https://registry.yarnpkg.com/@launchdarkly/node-server-sdk/-/node-server-sdk-9.5.1.tgz#8c1dc68076c6d055ab39dfd0af55284149fb95db"
- integrity sha512-EfxgCjtZk1wpiv/89pHG5WBeyRLhCFQIgBc2GMUwFTgE7otzrFM6te0YsRYYMJs1Xv2h9LVR6KXDbxL0R+g4VQ==
+"@launchdarkly/node-server-sdk@^9.5.4":
+ version "9.5.4"
+ resolved "https://registry.yarnpkg.com/@launchdarkly/node-server-sdk/-/node-server-sdk-9.5.4.tgz#7d27456b503c72e05a917873289b94058b49e566"
+ integrity sha512-1cGKlRP9YY4kgoGhw5Lolv1CoGOH5ywBB++4iqy00PTinyIjbX3LeYrlCWFT3gFrVlmTuc2BoXJvo93qFgkGAA==
dependencies:
- "@launchdarkly/js-server-sdk-common" "2.4.5"
+ "@launchdarkly/js-server-sdk-common" "2.6.1"
https-proxy-agent "^5.0.1"
launchdarkly-eventsource "2.0.3"
From 4d692ad25fdfb351a6f3eea6c1af604f7b1a66c2 Mon Sep 17 00:00:00 2001
From: Giorgos Bamparopoulos
Date: Mon, 16 Sep 2024 14:25:54 +0300
Subject: [PATCH 008/139] Removing debug logs from setup and start functions
(#192884)
Removing debug logs from setup and start functions
Co-authored-by: Elastic Machine
---
.../observability_solution/dataset_quality/server/plugin.ts | 4 ----
.../observability_onboarding/server/plugin.ts | 3 ---
2 files changed, 7 deletions(-)
diff --git a/x-pack/plugins/observability_solution/dataset_quality/server/plugin.ts b/x-pack/plugins/observability_solution/dataset_quality/server/plugin.ts
index ff2826a30051e..5a1b7b947641e 100644
--- a/x-pack/plugins/observability_solution/dataset_quality/server/plugin.ts
+++ b/x-pack/plugins/observability_solution/dataset_quality/server/plugin.ts
@@ -27,8 +27,6 @@ export class DatasetQualityServerPlugin implements Plugin {
core: CoreSetup,
plugins: DatasetQualityPluginSetupDependencies
) {
- this.logger.debug('dataset_quality: Setup');
-
const resourcePlugins = mapValues(plugins, (value, key) => {
return {
setup: value,
@@ -59,8 +57,6 @@ export class DatasetQualityServerPlugin implements Plugin {
}
start() {
- this.logger.debug('dataset_quality: Started');
-
return {};
}
}
diff --git a/x-pack/plugins/observability_solution/observability_onboarding/server/plugin.ts b/x-pack/plugins/observability_solution/observability_onboarding/server/plugin.ts
index c686a500e86e3..ccb260a002cf2 100644
--- a/x-pack/plugins/observability_solution/observability_onboarding/server/plugin.ts
+++ b/x-pack/plugins/observability_solution/observability_onboarding/server/plugin.ts
@@ -53,7 +53,6 @@ export class ObservabilityOnboardingPlugin
>,
plugins: ObservabilityOnboardingPluginSetupDependencies
) {
- this.logger.debug('observability_onboarding: Setup');
this.esLegacyConfigService.setup(core.elasticsearch.legacy.config$);
core.savedObjects.registerType(observabilityOnboardingFlow);
@@ -109,8 +108,6 @@ export class ObservabilityOnboardingPlugin
}
public start(core: CoreStart) {
- this.logger.debug('observability_onboarding: Started');
-
return {};
}
From f3535e9838e675ded589f34d4b0839f1804b21b4 Mon Sep 17 00:00:00 2001
From: Marco Vettorello
Date: Mon, 16 Sep 2024 13:30:24 +0200
Subject: [PATCH 009/139] [Lens][ColorMapping] Fix color mapping nitpicks
(#192242)
## Summary
Fixed the edit button color.
I've removed completely the `cursor:pointer` and the click
handler/capability over the color band (`EuiColorPaletteDisplay`
component) because this component is not a button and is not keyboard
accessible even with the click handler.
It should be changed in EUI to button to be used correctly.
fix https://github.com/elastic/kibana/issues/191878
---
.../shared_components/coloring/palette_panel_container.tsx | 7 ++++++-
.../shared_components/setting_with_sibling_flyout.tsx | 2 +-
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/x-pack/plugins/lens/public/shared_components/coloring/palette_panel_container.tsx b/x-pack/plugins/lens/public/shared_components/coloring/palette_panel_container.tsx
index 0b394ec638d69..2cd066acc6617 100644
--- a/x-pack/plugins/lens/public/shared_components/coloring/palette_panel_container.tsx
+++ b/x-pack/plugins/lens/public/shared_components/coloring/palette_panel_container.tsx
@@ -9,8 +9,9 @@ import { i18n } from '@kbn/i18n';
import React, { MutableRefObject } from 'react';
import { EuiButtonIcon, EuiFlexItem, EuiColorPaletteDisplay, EuiToolTip } from '@elastic/eui';
import { FIXED_PROGRESSION } from '@kbn/coloring';
-import { SettingWithSiblingFlyout } from '../setting_with_sibling_flyout';
+import { css } from '@emotion/react';
+import { SettingWithSiblingFlyout } from '../setting_with_sibling_flyout';
export function PalettePanelContainer(props: {
palette: string[];
siblingRef: MutableRefObject;
@@ -30,6 +31,9 @@ export function PalettePanelContainer(props: {
palette={props.palette}
type={FIXED_PROGRESSION}
onClick={onClick}
+ css={css`
+ cursor: pointer;
+ `}
/>
@@ -46,6 +50,7 @@ export function PalettePanelContainer(props: {
iconType="controlsHorizontal"
onClick={onClick}
size="xs"
+ color="text"
/>
diff --git a/x-pack/plugins/lens/public/shared_components/setting_with_sibling_flyout.tsx b/x-pack/plugins/lens/public/shared_components/setting_with_sibling_flyout.tsx
index 2bcb6a71bf541..fed4702e697b9 100644
--- a/x-pack/plugins/lens/public/shared_components/setting_with_sibling_flyout.tsx
+++ b/x-pack/plugins/lens/public/shared_components/setting_with_sibling_flyout.tsx
@@ -64,7 +64,7 @@ export function SettingWithSiblingFlyout({
}, [isInlineEditing, isFlyoutOpen]);
return (
-
+
{isFlyoutOpen && siblingRef.current && (
From f168fb4791eea611053683150329115def854826 Mon Sep 17 00:00:00 2001
From: Ido Cohen <90558359+CohenIdo@users.noreply.github.com>
Date: Mon, 16 Sep 2024 14:41:19 +0300
Subject: [PATCH 010/139] [Cloud Security] Populate Missing Fields in
Vulnerabilities Flyout
---
.../schemas/csp_vulnerability_finding.ts | 10 +--
.../_mocks_/vulnerability.mock.ts | 72 +++++++++++++++++++
.../pages/vulnerabilities/test_subjects.ts | 2 +
.../vulnerability_finding_flyout.test.tsx | 23 +++++-
.../vulnerability_overview_tab.tsx | 50 ++++++++-----
5 files changed, 130 insertions(+), 27 deletions(-)
diff --git a/x-pack/plugins/cloud_security_posture/common/schemas/csp_vulnerability_finding.ts b/x-pack/plugins/cloud_security_posture/common/schemas/csp_vulnerability_finding.ts
index d47cef89d546e..10b3dbb96b1d3 100644
--- a/x-pack/plugins/cloud_security_posture/common/schemas/csp_vulnerability_finding.ts
+++ b/x-pack/plugins/cloud_security_posture/common/schemas/csp_vulnerability_finding.ts
@@ -71,15 +71,15 @@ export interface CspVulnerabilityFinding {
commit_time: string;
};
package: {
- version: string;
- name: string;
+ version?: string;
+ name?: string;
fixed_version?: string;
};
data_stream: { dataset: string };
}
export interface Vulnerability {
- published_date: string;
+ published_date?: string;
score?: {
version?: string;
base?: number;
@@ -89,12 +89,12 @@ export interface Vulnerability {
title: string;
reference: string;
severity?: VulnSeverity;
- cvss: {
+ cvss?: {
nvd: VectorScoreBase;
redhat?: VectorScoreBase;
ghsa?: VectorScoreBase;
};
- data_source: {
+ data_source?: {
ID: string;
Name: string;
URL: string;
diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/_mocks_/vulnerability.mock.ts b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/_mocks_/vulnerability.mock.ts
index 5df5f398b7fb6..e66f9b33d7e91 100644
--- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/_mocks_/vulnerability.mock.ts
+++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/_mocks_/vulnerability.mock.ts
@@ -90,3 +90,75 @@ export const mockVulnerabilityHit: CspVulnerabilityFinding = {
dataset: 'cloud_security_posture.vulnerabilities',
},
};
+export const mockWizVulnerabilityHit: CspVulnerabilityFinding = {
+ '@timestamp': '2023-03-30T10:27:35.013Z',
+ resource: { name: '634yfsdg2.dkr.ecr.eu-central-1.amazon.stage', id: 'ami_12328' },
+ agent: {
+ name: 'ip-172-31-33-74',
+ type: 'cloudbeat',
+ version: '8.8.0',
+ ephemeral_id: '49f19e6a-94e9-4f2b-81e3-2f3794a74068',
+ id: 'd0313a94-c168-4d95-b1f0-97a388dac29a',
+ },
+ cloud: {
+ availability_zone: 'eu-west-1c',
+ service: { name: 'EC2' },
+ account: { id: '704479110758' },
+ image: { id: 'ami-02dc8dbcc971f2c74' },
+ provider: 'aws',
+ instance: { id: 'i-0fb7759c6e5d400cf' },
+ machine: { type: 'c6g.medium' },
+ region: 'eu-west-1',
+ },
+ package: { fixed_version: '0.4.0', version: 'v0.2.0', name: 'golang.org/x/net' },
+ vulnerability: {
+ enumeration: 'CVE',
+ description:
+ 'An attacker can cause excessive memory growth in a Go server accepting HTTP/2 requests. HTTP/2 server connections contain a cache of HTTP header keys sent by the client. While the total number of entries in this cache is capped, an attacker sending very large keys can cause the server to allocate approximately 64 MiB per open connection.',
+ title:
+ 'golang: net/http: An attacker can cause excessive memory growth in a Go server accepting HTTP/2 requests',
+ reference: 'https://avd.aquasec.com/nvd/cve-2022-41717',
+ severity: 'MEDIUM',
+ scanner: { vendor: 'Trivy' },
+ score: { base: 5.3, version: '3.0' },
+ cwe: ['CWE-770'],
+ id: 'CVE-2022-41717',
+ classification: 'CVSS',
+ },
+ cloudbeat: {
+ commit_sha: 'b5c4b728f0a9268e7f2d195c00dad0320c8a74e6',
+ commit_time: '2023-03-30T07:47:06Z',
+ version: '8.8.0',
+ },
+ event: {
+ category: ['vulnerability'],
+ created: '2023-03-30T10:27:35.013537768Z',
+ id: '5cfbcbe5-7f90-47b8-b1d4-7f79313b2a6d',
+ kind: 'state',
+ sequence: 1680172055,
+ outcome: 'success',
+ type: ['info'],
+ },
+ ecs: { version: '8.0.0' },
+ host: {
+ os: {
+ kernel: '5.15.0-1028-aws',
+ codename: 'jammy',
+ type: 'linux',
+ platform: 'ubuntu',
+ version: '22.04.1 LTS (Jammy Jellyfish)',
+ family: 'debian',
+ name: 'Ubuntu',
+ },
+ id: 'ec2644f440799ed0cf8aa595a9a105cc',
+ containerized: false,
+ name: 'ip-172-31-33-74',
+ ip: ['172.31.33.74', 'fe80::85d:f0ff:fe91:c01b'],
+ mac: ['0A-5D-F0-91-C0-1B'],
+ hostname: 'ip-172-31-33-74',
+ architecture: 'aarch64',
+ },
+ data_stream: {
+ dataset: 'cloud_security_posture.vulnerabilities',
+ },
+};
diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/test_subjects.ts b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/test_subjects.ts
index d1020baecc8cc..b1eddf50f7c14 100644
--- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/test_subjects.ts
+++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/test_subjects.ts
@@ -9,6 +9,8 @@ export const FINDINGS_VULNERABILITY_FLYOUT_DESCRIPTION_LIST =
'vulnerability-flyout-description-list';
export const JSON_TAB_VULNERABILITY_FLYOUT = 'vulnerability_json_tab_flyout';
export const OVERVIEW_TAB_VULNERABILITY_FLYOUT = 'vulnerability_overview_tab_flyout';
+export const DATA_SOURCE_VULNERABILITY_FLYOUT = 'vulnerability_flyout_data_source_display_box';
+export const PUBLISHED_DATE_VULNERABILITY_FLYOUT = 'vulnerability_flyout_date_display_box';
export const TAB_ID_VULNERABILITY_FLYOUT = (tabId: string) =>
`vulnerability-finding-flyout-tab-${tabId}`;
diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_finding_flyout.test.tsx b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_finding_flyout.test.tsx
index 06ddae969d431..081eff33c5c96 100644
--- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_finding_flyout.test.tsx
+++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_finding_flyout.test.tsx
@@ -10,10 +10,15 @@ import { render, screen } from '@testing-library/react';
import '@kbn/code-editor-mock/jest_helper';
import { TestProvider } from '../../../test/test_provider';
import { VulnerabilityFindingFlyout } from './vulnerability_finding_flyout';
-import { mockVulnerabilityHit } from '../_mocks_/vulnerability.mock';
+import { mockVulnerabilityHit, mockWizVulnerabilityHit } from '../_mocks_/vulnerability.mock';
import { VulnerabilityOverviewTab } from './vulnerability_overview_tab';
import moment from 'moment';
-import { FINDINGS_VULNERABILITY_FLYOUT_DESCRIPTION_LIST } from '../test_subjects';
+import {
+ DATA_SOURCE_VULNERABILITY_FLYOUT,
+ FINDINGS_VULNERABILITY_FLYOUT_DESCRIPTION_LIST,
+ PUBLISHED_DATE_VULNERABILITY_FLYOUT,
+} from '../test_subjects';
+import { EMPTY_VALUE } from '../../configurations/findings_flyout/findings_flyout';
const onPaginate = jest.fn();
@@ -65,7 +70,7 @@ describe('', () => {
);
- getByText(mockVulnerabilityHit.vulnerability.data_source.ID);
+ getByText(mockVulnerabilityHit.vulnerability.data_source!.ID);
getByText('Elastic CSP');
getByText(moment(mockVulnerabilityHit.vulnerability.published_date).format('LL').toString());
getByText(mockVulnerabilityHit.vulnerability.description);
@@ -80,6 +85,18 @@ describe('', () => {
);
});
+ it('Overview Tab with Wiz vulnerability missing fields', () => {
+ const { getByTestId } = render(
+
+
+
+ );
+ const dataSource = getByTestId(DATA_SOURCE_VULNERABILITY_FLYOUT);
+ const publisedDate = getByTestId(PUBLISHED_DATE_VULNERABILITY_FLYOUT);
+ expect(dataSource.textContent).toEqual(`Data Source${EMPTY_VALUE}`);
+ expect(publisedDate.textContent).toEqual(`Published Date${EMPTY_VALUE}`);
+ });
+
it('show empty state for no fixes', () => {
const { getByText } = render(
diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_overview_tab.tsx b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_overview_tab.tsx
index 166b21e4f53ac..ecff474d5f50f 100644
--- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_overview_tab.tsx
+++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_overview_tab.tsx
@@ -26,7 +26,11 @@ import { NvdLogo } from '../../../assets/icons/nvd_logo_svg';
import { CVSScoreBadge } from '../../../components/vulnerability_badges';
import { CVSScoreProps, Vendor } from '../types';
import { getVectorScoreList } from '../utils/get_vector_score_list';
-import { OVERVIEW_TAB_VULNERABILITY_FLYOUT } from '../test_subjects';
+import {
+ DATA_SOURCE_VULNERABILITY_FLYOUT,
+ OVERVIEW_TAB_VULNERABILITY_FLYOUT,
+ PUBLISHED_DATE_VULNERABILITY_FLYOUT,
+} from '../test_subjects';
import redhatLogo from '../../../assets/icons/redhat_logo.svg';
import { VulnerabilityDetectionRuleCounter } from './vulnerability_detection_rule_counter';
@@ -164,38 +168,46 @@ const VulnerabilityOverviewTiles = ({ vulnerabilityRecord }: VulnerabilityTabPro
)}
- {vulnerability?.data_source?.ID && (
-
+ {
+
-
- {vulnerability.data_source.ID}
-
+ {vulnerability.data_source?.URL ? (
+
+ {vulnerability.data_source.ID}
+
+ ) : (
+ {EMPTY_VALUE}
+ )}
- )}
- {date && (
-
+ }
+ {
+
-
-
-
+ {vulnerability.data_source?.URL ? (
+
+
+
+ ) : (
+ EMPTY_VALUE
+ )}
- )}
+ }
);
};
From 3de252e688515e9f84e199b73e5e4246f76b8c19 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Yulia=20=C4=8Cech?=
<6585477+yuliacech@users.noreply.github.com>
Date: Mon, 16 Sep 2024 13:44:41 +0200
Subject: [PATCH 011/139] [Index Management] Added docs count and size for
serverless (#191985)
## Summary
Fixes https://github.com/elastic/kibana/issues/190131
This PR adds size and documents count to indices and data streams tables
in Index Management on serverless.
### Screenrecording
https://github.com/user-attachments/assets/51a933e2-e4ef-42a0-9c82-39bf6e194ee0
### Screenshots
### Checklist
Delete any items that are not applicable to this PR.
- [ ] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [ ] [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
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [ ] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [ ] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [ ] If a plugin configuration key changed, check if it needs to be
allowlisted in the cloud and added to the [docker
list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)
- [ ] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [ ] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)
### Risk Matrix
Delete this section if it is not applicable to this PR.
Before closing this PR, invite QA, stakeholders, and other developers to
identify risks that should be tested prior to the change/feature
release.
When forming the risk matrix, consider some of the following examples
and how they may potentially impact the change:
| Risk | Probability | Severity | Mitigation/Notes |
|---------------------------|-------------|----------|-------------------------|
| Multiple Spaces—unexpected behavior in non-default Kibana Space.
| Low | High | Integration tests will verify that all features are still
supported in non-default Kibana Space and when user switches between
spaces. |
| Multiple nodes—Elasticsearch polling might have race conditions
when multiple Kibana nodes are polling for the same tasks. | High | Low
| Tasks are idempotent, so executing them multiple times will not result
in logical error, but will degrade performance. To test for this case we
add plenty of unit tests around this logic and document manual testing
procedure. |
| Code should gracefully handle cases when feature X or plugin Y are
disabled. | Medium | High | Unit tests will verify that any feature flag
or plugin combination still results in our service operational. |
| [See more potential risk
examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) |
### For maintainers
- [ ] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
---
.../test_suites/core_plugins/rendering.ts | 1 +
.../home/data_streams_tab.helpers.ts | 12 +-
.../home/data_streams_tab.test.ts | 98 +++++++++++++---
.../home/indices_tab.test.tsx | 33 +++++-
...tion.test.ts => data_stream_utils.test.ts} | 4 +-
.../common/lib/data_stream_utils.ts | 64 ++++++++++
.../index_management/common/lib/index.ts | 6 +-
.../common/lib/template_serialization.ts | 2 +-
.../common/types/data_streams.ts | 5 +
.../component_template_create.test.tsx | 2 +-
.../component_template_form.tsx | 5 +-
.../template_form/template_form.tsx | 2 +-
.../data_stream_detail_panel.tsx | 110 ++++++++++++------
.../data_stream_list/data_stream_list.tsx | 61 +++++-----
.../data_stream_table/data_stream_table.tsx | 74 ++++++++----
.../details_page_overview.tsx | 5 +-
.../size_doc_count_details.tsx | 79 +++++++++++++
.../index_list/index_table/index_table.js | 43 ++++---
.../template_details/tabs/tab_summary.tsx | 2 +-
.../plugins/index_management/public/plugin.ts | 6 +-
.../plugins/index_management/server/config.ts | 1 +
.../lib/data_stream_serialization.ts | 68 ++---------
.../server/lib/fetch_indices.ts | 7 +-
.../index_management/server/lib/types.ts | 12 ++
.../api/data_streams/register_get_route.ts | 41 ++++++-
.../common/index_management/datastreams.ts | 3 +
26 files changed, 531 insertions(+), 215 deletions(-)
rename x-pack/plugins/index_management/common/lib/{data_stream_serialization.test.ts => data_stream_utils.test.ts} (81%)
create mode 100644 x-pack/plugins/index_management/common/lib/data_stream_utils.ts
create mode 100644 x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/size_doc_count_details.tsx
rename x-pack/plugins/index_management/{common => server}/lib/data_stream_serialization.ts (58%)
create mode 100644 x-pack/plugins/index_management/server/lib/types.ts
diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts
index 9bd43504d7db1..a8c9fef0cfb96 100644
--- a/test/plugin_functional/test_suites/core_plugins/rendering.ts
+++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts
@@ -295,6 +295,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
'xpack.index_management.enableLegacyTemplates (boolean?|never)',
'xpack.index_management.enableIndexStats (boolean?|never)',
'xpack.index_management.enableDataStreamStats (boolean?|never)',
+ 'xpack.index_management.enableSizeAndDocCount (boolean?|never)',
'xpack.index_management.editableIndexSettings (all?|limited?|never)',
'xpack.index_management.enableMappingsSourceFieldSection (boolean?|never)',
'xpack.index_management.dev.enableSemanticText (boolean?)',
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts
index 8b97b24eadb7a..bd119a77378af 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts
@@ -24,7 +24,7 @@ export interface DataStreamsTabTestBed extends TestBed {
actions: {
goToDataStreamsList: () => void;
clickEmptyPromptIndexTemplateLink: () => void;
- clickIncludeStatsSwitch: () => void;
+ clickIncludeStatsSwitch: () => Promise;
toggleViewFilterAt: (index: number) => void;
sortTableOnStorageSize: () => void;
sortTableOnName: () => void;
@@ -90,9 +90,13 @@ export const setup = async (
component.update();
};
- const clickIncludeStatsSwitch = () => {
- const { find } = testBed;
- find('includeStatsSwitch').simulate('click');
+ const clickIncludeStatsSwitch = async () => {
+ const { find, component } = testBed;
+
+ await act(async () => {
+ find('includeStatsSwitch').simulate('click');
+ });
+ component.update();
};
const toggleViewFilterAt = (index: number) => {
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts
index 26eb9eab172b3..a4ea7b9296e28 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts
@@ -156,6 +156,10 @@ describe('Data Streams tab', () => {
name: 'dataStream1',
storageSize: '5b',
storageSizeBytes: 5,
+ // metering API mock
+ meteringStorageSize: '156kb',
+ meteringStorageSizeBytes: 156000,
+ meteringDocsCount: 10000,
});
setLoadDataStreamsResponse([
@@ -164,6 +168,10 @@ describe('Data Streams tab', () => {
name: 'dataStream2',
storageSize: '1kb',
storageSizeBytes: 1000,
+ // metering API mock
+ meteringStorageSize: '156kb',
+ meteringStorageSizeBytes: 156000,
+ meteringDocsCount: 10000,
lifecycle: {
enabled: true,
data_retention: '7d',
@@ -224,15 +232,12 @@ describe('Data Streams tab', () => {
});
test('has a switch that will reload the data streams with additional stats when clicked', async () => {
- const { exists, actions, table, component } = testBed;
+ const { exists, actions, table } = testBed;
expect(exists('includeStatsSwitch')).toBe(true);
// Changing the switch will automatically reload the data streams.
- await act(async () => {
- actions.clickIncludeStatsSwitch();
- });
- component.update();
+ await actions.clickIncludeStatsSwitch();
expect(httpSetup.get).toHaveBeenLastCalledWith(
`${API_BASE_PATH}/data_streams`,
@@ -267,12 +272,9 @@ describe('Data Streams tab', () => {
test('sorting on stats sorts by bytes value instead of human readable value', async () => {
// Guards against regression of #86122.
- const { actions, table, component } = testBed;
+ const { actions, table } = testBed;
- await act(async () => {
- actions.clickIncludeStatsSwitch();
- });
- component.update();
+ await actions.clickIncludeStatsSwitch();
actions.sortTableOnStorageSize();
@@ -306,7 +308,7 @@ describe('Data Streams tab', () => {
actions.sortTableOnName();
});
- test('hides stats toggle if enableDataStreamStats===false', async () => {
+ test(`doesn't hide stats toggle if enableDataStreamStats===false`, async () => {
testBed = await setup(httpSetup, {
config: {
enableDataStreamStats: false,
@@ -321,14 +323,82 @@ describe('Data Streams tab', () => {
component.update();
- expect(exists('includeStatsSwitch')).toBeFalsy();
+ expect(exists('includeStatsSwitch')).toBeTruthy();
+ });
+
+ test('shows storage size and documents count if enableSizeAndDocCount===true, enableDataStreamStats==false', async () => {
+ testBed = await setup(httpSetup, {
+ config: {
+ enableSizeAndDocCount: true,
+ enableDataStreamStats: false,
+ },
+ });
+
+ const { actions, component, table } = testBed;
+
+ await act(async () => {
+ actions.goToDataStreamsList();
+ });
+
+ component.update();
+
+ await actions.clickIncludeStatsSwitch();
+
+ const { tableCellsValues } = table.getMetaData('dataStreamTable');
+ expect(tableCellsValues).toEqual([
+ ['', 'dataStream1', 'green', '156kb', '10000', '1', '7 days', 'Delete'],
+ ['', 'dataStream2', 'green', '156kb', '10000', '1', '5 days ', 'Delete'],
+ ]);
+ });
+
+ test('shows last updated and storage size if enableDataStreamStats===true, enableSizeAndDocCount===false', async () => {
+ testBed = await setup(httpSetup, {
+ config: {
+ enableDataStreamStats: true,
+ enableSizeAndDocCount: false,
+ },
+ });
+
+ const { actions, component, table } = testBed;
+
+ await act(async () => {
+ actions.goToDataStreamsList();
+ });
+
+ component.update();
+
+ await actions.clickIncludeStatsSwitch();
+
+ const { tableCellsValues } = table.getMetaData('dataStreamTable');
+ expect(tableCellsValues).toEqual([
+ [
+ '',
+ 'dataStream1',
+ 'green',
+ 'December 31st, 1969 7:00:00 PM',
+ '5b',
+ '1',
+ '7 days',
+ 'Delete',
+ ],
+ [
+ '',
+ 'dataStream2',
+ 'green',
+ 'December 31st, 1969 7:00:00 PM',
+ '1kb',
+ '1',
+ '5 days ',
+ 'Delete',
+ ],
+ ]);
});
test('clicking the indices count navigates to the backing indices', async () => {
const { table, actions } = testBed;
await actions.clickIndicesAt(0);
expect(table.getMetaData('indexTable').tableCellsValues).toEqual([
- ['', 'data-stream-index', '', '', '', '', '', '', 'dataStream1'],
+ ['', 'data-stream-index', '', '', '', '', '0', '', 'dataStream1'],
]);
});
@@ -707,7 +777,7 @@ describe('Data Streams tab', () => {
const { table, actions } = testBed;
await actions.clickIndicesAt(0);
expect(table.getMetaData('indexTable').tableCellsValues).toEqual([
- ['', 'data-stream-index', '', '', '', '', '', '', '%dataStream'],
+ ['', 'data-stream-index', '', '', '', '', '0', '', '%dataStream'],
]);
});
});
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.tsx
index 8a8a2fc23d54d..3af9b4b9a2f62 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.tsx
+++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.tsx
@@ -394,7 +394,7 @@ describe('', () => {
component.update();
});
- test('renders the table column with index stats by default', () => {
+ test('renders the table column with all index stats when enableIndexStats is true', () => {
const { table } = testBed;
const { tableCellsValues } = table.getMetaData('indexTable');
@@ -403,7 +403,7 @@ describe('', () => {
]);
});
- describe('Disabled', () => {
+ describe('renders only size and docs count when enableIndexStats is false, enableSizeAndDocCount is true', () => {
beforeEach(async () => {
await act(async () => {
testBed = await setup(httpSetup, {
@@ -411,6 +411,7 @@ describe('', () => {
enableLegacyTemplates: true,
enableIndexActions: true,
enableIndexStats: false,
+ enableSizeAndDocCount: true,
},
});
});
@@ -420,7 +421,33 @@ describe('', () => {
component.update();
});
- test('hides index stats information from table', async () => {
+ test('hides some index stats information from table', async () => {
+ const { table } = testBed;
+ const { tableCellsValues } = table.getMetaData('indexTable');
+
+ expect(tableCellsValues).toEqual([['', 'test', '10,000', '156kb', '']]);
+ });
+ });
+
+ describe('renders no index stats when enableIndexStats is false, enableSizeAndDocCount is false', () => {
+ beforeEach(async () => {
+ await act(async () => {
+ testBed = await setup(httpSetup, {
+ config: {
+ enableLegacyTemplates: true,
+ enableIndexActions: true,
+ enableIndexStats: false,
+ enableSizeAndDocCount: false,
+ },
+ });
+ });
+
+ const { component } = testBed;
+
+ component.update();
+ });
+
+ test('hides all index stats information from table', async () => {
const { table } = testBed;
const { tableCellsValues } = table.getMetaData('indexTable');
diff --git a/x-pack/plugins/index_management/common/lib/data_stream_serialization.test.ts b/x-pack/plugins/index_management/common/lib/data_stream_utils.test.ts
similarity index 81%
rename from x-pack/plugins/index_management/common/lib/data_stream_serialization.test.ts
rename to x-pack/plugins/index_management/common/lib/data_stream_utils.test.ts
index 334e6bbf97de0..afbcf7835f764 100644
--- a/x-pack/plugins/index_management/common/lib/data_stream_serialization.test.ts
+++ b/x-pack/plugins/index_management/common/lib/data_stream_utils.test.ts
@@ -5,9 +5,9 @@
* 2.0.
*/
-import { splitSizeAndUnits } from './data_stream_serialization';
+import { splitSizeAndUnits } from './data_stream_utils';
-describe('Data stream serialization', () => {
+describe('Data stream utils', () => {
test('can split size and units from lifecycle string', () => {
expect(splitSizeAndUnits('1h')).toEqual({ size: '1', unit: 'h' });
expect(splitSizeAndUnits('20micron')).toEqual({ size: '20', unit: 'micron' });
diff --git a/x-pack/plugins/index_management/common/lib/data_stream_utils.ts b/x-pack/plugins/index_management/common/lib/data_stream_utils.ts
new file mode 100644
index 0000000000000..443373f8a6b4b
--- /dev/null
+++ b/x-pack/plugins/index_management/common/lib/data_stream_utils.ts
@@ -0,0 +1,64 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { DataStream, DataRetention } from '../types';
+
+export const splitSizeAndUnits = (field: string): { size: string; unit: string } => {
+ let size = '';
+ let unit = '';
+
+ const result = /(\d+)(\w+)/.exec(field);
+ if (result) {
+ size = result[1];
+ unit = result[2];
+ }
+
+ return {
+ size,
+ unit,
+ };
+};
+
+export const serializeAsESLifecycle = (lifecycle?: DataRetention): DataStream['lifecycle'] => {
+ if (!lifecycle || !lifecycle?.enabled) {
+ return undefined;
+ }
+
+ const { infiniteDataRetention, value, unit } = lifecycle;
+
+ if (infiniteDataRetention) {
+ return {
+ enabled: true,
+ };
+ }
+
+ return {
+ enabled: true,
+ data_retention: `${value}${unit}`,
+ };
+};
+
+export const deserializeESLifecycle = (lifecycle?: DataStream['lifecycle']): DataRetention => {
+ if (!lifecycle || !lifecycle?.enabled) {
+ return { enabled: false };
+ }
+
+ if (!lifecycle.data_retention) {
+ return {
+ enabled: true,
+ infiniteDataRetention: true,
+ };
+ }
+
+ const { size, unit } = splitSizeAndUnits(lifecycle.data_retention as string);
+
+ return {
+ enabled: true,
+ value: Number(size),
+ unit,
+ };
+};
diff --git a/x-pack/plugins/index_management/common/lib/index.ts b/x-pack/plugins/index_management/common/lib/index.ts
index d46d3d8b6a1d4..da29f3289bcd4 100644
--- a/x-pack/plugins/index_management/common/lib/index.ts
+++ b/x-pack/plugins/index_management/common/lib/index.ts
@@ -6,10 +6,10 @@
*/
export {
- deserializeDataStream,
- deserializeDataStreamList,
splitSizeAndUnits,
-} from './data_stream_serialization';
+ serializeAsESLifecycle,
+ deserializeESLifecycle,
+} from './data_stream_utils';
export {
deserializeTemplate,
diff --git a/x-pack/plugins/index_management/common/lib/template_serialization.ts b/x-pack/plugins/index_management/common/lib/template_serialization.ts
index aacbc15aab3b8..f8b4ed47a22f7 100644
--- a/x-pack/plugins/index_management/common/lib/template_serialization.ts
+++ b/x-pack/plugins/index_management/common/lib/template_serialization.ts
@@ -12,7 +12,7 @@ import {
TemplateListItem,
TemplateType,
} from '../types';
-import { deserializeESLifecycle } from './data_stream_serialization';
+import { deserializeESLifecycle } from './data_stream_utils';
import { allowAutoCreateRadioValues, allowAutoCreateRadioIds } from '../constants';
const hasEntries = (data: object = {}) => Object.entries(data).length > 0;
diff --git a/x-pack/plugins/index_management/common/types/data_streams.ts b/x-pack/plugins/index_management/common/types/data_streams.ts
index 4e20252792d71..c44305edb9d8f 100644
--- a/x-pack/plugins/index_management/common/types/data_streams.ts
+++ b/x-pack/plugins/index_management/common/types/data_streams.ts
@@ -37,6 +37,8 @@ export interface EnhancedDataStreamFromEs extends IndicesDataStream {
store_size?: IndicesDataStreamsStatsDataStreamsStatsItem['store_size'];
store_size_bytes?: IndicesDataStreamsStatsDataStreamsStatsItem['store_size_bytes'];
maximum_timestamp?: IndicesDataStreamsStatsDataStreamsStatsItem['maximum_timestamp'];
+ metering_size_in_bytes?: number;
+ metering_doc_count?: number;
indices: DataStreamIndexFromEs[];
privileges: {
delete_index: boolean;
@@ -55,6 +57,9 @@ export interface DataStream {
storageSize?: ByteSize;
storageSizeBytes?: number;
maxTimeStamp?: number;
+ meteringStorageSizeBytes?: number;
+ meteringStorageSize?: string;
+ meteringDocsCount?: number;
_meta?: Metadata;
privileges: Privileges;
hidden: boolean;
diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_create.test.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_create.test.tsx
index 729ca7680ad6c..787aa68907730 100644
--- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_create.test.tsx
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_create.test.tsx
@@ -12,7 +12,7 @@ import { breadcrumbService, IndexManagementBreadcrumb } from '../../../../servic
import { setupEnvironment } from './helpers';
import { API_BASE_PATH } from './helpers/constants';
import { setup, ComponentTemplateCreateTestBed } from './helpers/component_template_create.helpers';
-import { serializeAsESLifecycle } from '../../../../../../common/lib/data_stream_serialization';
+import { serializeAsESLifecycle } from '../../../../../../common/lib';
jest.mock('@kbn/code-editor', () => {
const original = jest.requireActual('@kbn/code-editor');
diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/component_template_form.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/component_template_form.tsx
index 1cbc33fc05135..ca791311d2408 100644
--- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/component_template_form.tsx
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/component_template_form.tsx
@@ -19,10 +19,7 @@ import {
StepMappingsContainer,
StepAliasesContainer,
} from '../../shared_imports';
-import {
- serializeAsESLifecycle,
- deserializeESLifecycle,
-} from '../../../../../../common/lib/data_stream_serialization';
+import { serializeAsESLifecycle, deserializeESLifecycle } from '../../../../../../common/lib';
import { useComponentTemplatesContext } from '../../component_templates_context';
import { StepLogisticsContainer, StepReviewContainer } from './steps';
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx b/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx
index ac247500e3248..2da3eef609a65 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx
+++ b/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx
@@ -22,7 +22,7 @@ import {
} from '../shared';
import { documentationService } from '../../services/documentation';
import { SectionError } from '../section_error';
-import { serializeAsESLifecycle } from '../../../../common/lib/data_stream_serialization';
+import { serializeAsESLifecycle } from '../../../../common/lib';
import {
SimulateTemplateFlyoutContent,
SimulateTemplateProps,
diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx
index da53b0241095d..974ba6f082042 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx
@@ -52,13 +52,14 @@ import { useAppContext } from '../../../../app_context';
import { DataStreamsBadges } from '../data_stream_badges';
import { useIlmLocator } from '../../../../services/use_ilm_locator';
+interface Detail {
+ name: string;
+ toolTip: string;
+ content: any;
+ dataTestSubj: string;
+}
interface DetailsListProps {
- details: Array<{
- name: string;
- toolTip: string;
- content: any;
- dataTestSubj: string;
- }>;
+ details: Detail[];
}
const DetailsList: React.FunctionComponent = ({ details }) => {
@@ -162,6 +163,8 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({
ilmPolicyName,
storageSize,
maxTimeStamp,
+ meteringStorageSize,
+ meteringDocsCount,
lifecycle,
} = dataStream;
@@ -222,7 +225,7 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({
);
- const defaultDetails = [
+ const defaultDetails: Detail[] = [
{
name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.healthTitle', {
defaultMessage: 'Health',
@@ -233,34 +236,67 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({
content: ,
dataTestSubj: 'healthDetail',
},
- {
- name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.maxTimeStampTitle', {
- defaultMessage: 'Last updated',
- }),
- toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.maxTimeStampToolTip', {
- defaultMessage: 'The most recent document to be added to the data stream.',
- }),
- content: maxTimeStamp ? (
- humanizeTimeStamp(maxTimeStamp)
- ) : (
-
- {i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.maxTimeStampNoneMessage', {
- defaultMessage: `Never`,
- })}
-
- ),
- dataTestSubj: 'lastUpdatedDetail',
- },
- {
- name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.storageSizeTitle', {
- defaultMessage: 'Storage size',
- }),
- toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.storageSizeToolTip', {
- defaultMessage: `The total size of all shards in the data stream’s backing indices.`,
- }),
- content: storageSize,
- dataTestSubj: 'storageSizeDetail',
- },
+ ];
+
+ // add either documents count and size or last updated and size
+ if (config.enableSizeAndDocCount) {
+ defaultDetails.push(
+ {
+ name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.meteringDocsCountTitle', {
+ defaultMessage: 'Documents count',
+ }),
+ toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.meteringDocsCountToolTip', {
+ defaultMessage: 'The number of documents in this data stream.',
+ }),
+ content: meteringDocsCount,
+ dataTestSubj: 'docsCountDetail',
+ },
+ {
+ name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.storageSizeTitle', {
+ defaultMessage: 'Storage size',
+ }),
+ toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.storageSizeToolTip', {
+ defaultMessage: `The total size of all shards in the data stream’s backing indices.`,
+ }),
+ content: meteringStorageSize,
+ dataTestSubj: 'meteringStorageSizeDetail',
+ }
+ );
+ }
+ if (config.enableDataStreamStats) {
+ defaultDetails.push(
+ {
+ name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.maxTimeStampTitle', {
+ defaultMessage: 'Last updated',
+ }),
+ toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.maxTimeStampToolTip', {
+ defaultMessage: 'The most recent document to be added to the data stream.',
+ }),
+ content: maxTimeStamp ? (
+ humanizeTimeStamp(maxTimeStamp)
+ ) : (
+
+ {i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.maxTimeStampNoneMessage', {
+ defaultMessage: `Never`,
+ })}
+
+ ),
+ dataTestSubj: 'lastUpdatedDetail',
+ },
+ {
+ name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.storageSizeTitle', {
+ defaultMessage: 'Storage size',
+ }),
+ toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.storageSizeToolTip', {
+ defaultMessage: `The total size of all shards in the data stream’s backing indices.`,
+ }),
+ content: storageSize,
+ dataTestSubj: 'storageSizeDetail',
+ }
+ );
+ }
+
+ defaultDetails.push(
{
name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.indicesTitle', {
defaultMessage: 'Indices',
@@ -328,8 +364,8 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({
),
dataTestSubj: 'dataRetentionDetail',
- },
- ];
+ }
+ );
// If both rentention types are available, we wanna surface to the user both
if (lifecycle?.effective_retention && lifecycle?.data_retention) {
diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx
index 0103b51f1f51d..125f676897ffb 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx
@@ -60,11 +60,8 @@ export const DataStreamList: React.FunctionComponent
- {isDataStreamStatsEnabled && (
-
-
-
- setIsIncludeStatsChecked(e.target.checked)}
- data-test-subj="includeStatsSwitch"
- />
-
+
+
+
+ setIsIncludeStatsChecked(e.target.checked)}
+ data-test-subj="includeStatsSwitch"
+ />
+
-
-
-
-
-
- )}
+
+
+
+
+
filters={filters} onChange={setFilters} />
diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx
index b4dbb663e0859..47b170babc5a6 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx
@@ -104,31 +104,55 @@ export const DataStreamTable: React.FunctionComponent = ({
});
if (includeStats) {
- columns.push({
- field: 'maxTimeStamp',
- name: i18n.translate('xpack.idxMgmt.dataStreamList.table.maxTimeStampColumnTitle', {
- defaultMessage: 'Last updated',
- }),
- truncateText: true,
- sortable: true,
- render: (maxTimeStamp: DataStream['maxTimeStamp']) =>
- maxTimeStamp
- ? humanizeTimeStamp(maxTimeStamp)
- : i18n.translate('xpack.idxMgmt.dataStreamList.table.maxTimeStampColumnNoneMessage', {
- defaultMessage: 'Never',
- }),
- });
-
- columns.push({
- field: 'storageSizeBytes',
- name: i18n.translate('xpack.idxMgmt.dataStreamList.table.storageSizeColumnTitle', {
- defaultMessage: 'Storage size',
- }),
- truncateText: true,
- sortable: true,
- render: (storageSizeBytes: DataStream['storageSizeBytes'], dataStream: DataStream) =>
- dataStream.storageSize,
- });
+ if (config.enableSizeAndDocCount) {
+ // datastreams stats from metering API on serverless
+ columns.push({
+ field: 'meteringStorageSizeBytes',
+ name: i18n.translate('xpack.idxMgmt.dataStreamList.table.storageSizeColumnTitle', {
+ defaultMessage: 'Storage size',
+ }),
+ truncateText: true,
+ sortable: true,
+ render: (
+ meteringStorageSizeBytes: DataStream['meteringStorageSizeBytes'],
+ dataStream: DataStream
+ ) => dataStream.meteringStorageSize,
+ });
+ columns.push({
+ field: 'meteringDocsCount',
+ name: i18n.translate('xpack.idxMgmt.dataStreamList.table.docsCountColumnTitle', {
+ defaultMessage: 'Documents count',
+ }),
+ truncateText: true,
+ sortable: true,
+ });
+ }
+ if (config.enableDataStreamStats) {
+ columns.push({
+ field: 'maxTimeStamp',
+ name: i18n.translate('xpack.idxMgmt.dataStreamList.table.maxTimeStampColumnTitle', {
+ defaultMessage: 'Last updated',
+ }),
+ truncateText: true,
+ sortable: true,
+ render: (maxTimeStamp: DataStream['maxTimeStamp']) =>
+ maxTimeStamp
+ ? humanizeTimeStamp(maxTimeStamp)
+ : i18n.translate('xpack.idxMgmt.dataStreamList.table.maxTimeStampColumnNoneMessage', {
+ defaultMessage: 'Never',
+ }),
+ });
+ columns.push({
+ field: 'storageSizeBytes',
+ name: i18n.translate('xpack.idxMgmt.dataStreamList.table.storageSizeColumnTitle', {
+ defaultMessage: 'Storage size',
+ }),
+ truncateText: true,
+ sortable: true,
+ render: (storageSizeBytes: DataStream['storageSizeBytes'], dataStream: DataStream) =>
+ dataStream.storageSize,
+ });
+ }
}
columns.push({
diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/details_page_overview.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/details_page_overview.tsx
index c3e985a638fd5..a5fcb4a5c24a8 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/details_page_overview.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/details_page_overview.tsx
@@ -26,14 +26,15 @@ import {
getLanguageDefinitionCodeSnippet,
getConsoleRequest,
} from '@kbn/search-api-panels';
-import { StatusDetails } from './status_details';
import type { Index } from '../../../../../../../common';
import { useAppContext } from '../../../../../app_context';
import { documentationService } from '../../../../../services';
import { languageDefinitions, curlDefinition } from './languages';
+import { StatusDetails } from './status_details';
import { DataStreamDetails } from './data_stream_details';
import { StorageDetails } from './storage_details';
import { AliasesDetails } from './aliases_details';
+import { SizeDocCountDetails } from './size_doc_count_details';
interface Props {
indexDetails: Index;
@@ -85,6 +86,8 @@ export const DetailsPageOverview: React.FunctionComponent = ({ indexDetai
health={health}
/>
+
+
{dataStream && }
diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/size_doc_count_details.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/size_doc_count_details.tsx
new file mode 100644
index 0000000000000..40294d76b2698
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/size_doc_count_details.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 React, { FunctionComponent } from 'react';
+import { css } from '@emotion/react';
+import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText, EuiTextColor } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { euiThemeVars } from '@kbn/ui-theme';
+import type { Index } from '../../../../../../../common';
+import { useAppContext } from '../../../../../app_context';
+import { OverviewCard } from './overview_card';
+
+export const SizeDocCountDetails: FunctionComponent<{
+ size: Index['size'];
+ documents: Index['documents'];
+}> = ({ size, documents }) => {
+ const { config } = useAppContext();
+ if (!config.enableSizeAndDocCount) {
+ return null;
+ }
+ return (
+
+
+
+ {size}
+
+
+
+
+ {i18n.translate('xpack.idxMgmt.indexDetails.overviewTab.storage.totalSizeLabel', {
+ defaultMessage: 'Total',
+ })}
+
+
+
+ ),
+ right: null,
+ }}
+ footer={{
+ left: (
+
+
+
+
+ {documents}
+
+
+ {i18n.translate(
+ 'xpack.idxMgmt.indexDetails.overviewTab.status.meteringDocumentsLabel',
+ {
+ defaultMessage: '{documents, plural, one {Document} other {Documents}}',
+ values: {
+ documents,
+ },
+ }
+ )}
+
+
+
+ ),
+ }}
+ />
+ );
+};
diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js
index 89b14d3db05c9..bdc245fe57703 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js
+++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js
@@ -52,6 +52,7 @@ import { IndexTablePagination, PAGE_SIZE_OPTIONS } from './index_table_paginatio
const getColumnConfigs = ({
showIndexStats,
+ showSizeAndDocCount,
history,
filterChanged,
extensionsService,
@@ -100,6 +101,28 @@ const getColumnConfigs = ({
},
];
+ // size and docs count enabled by either "enableIndexStats" or "enableSizeAndDocCount" configs
+ if (showIndexStats || showSizeAndDocCount) {
+ columns.push(
+ {
+ fieldName: 'documents',
+ label: i18n.translate('xpack.idxMgmt.indexTable.headers.documentsHeader', {
+ defaultMessage: 'Documents count',
+ }),
+ order: 60,
+ render: (index) => {
+ return Number(index.documents ?? 0).toLocaleString();
+ },
+ },
+ {
+ fieldName: 'size',
+ label: i18n.translate('xpack.idxMgmt.indexTable.headers.storageSizeHeader', {
+ defaultMessage: 'Storage size',
+ }),
+ order: 70,
+ }
+ );
+ }
if (showIndexStats) {
columns.push(
{
@@ -130,25 +153,6 @@ const getColumnConfigs = ({
defaultMessage: 'Replicas',
}),
order: 50,
- },
- {
- fieldName: 'documents',
- label: i18n.translate('xpack.idxMgmt.indexTable.headers.documentsHeader', {
- defaultMessage: 'Docs count',
- }),
- order: 60,
- render: (index) => {
- if (index.documents) {
- return Number(index.documents).toLocaleString();
- }
- },
- },
- {
- fieldName: 'size',
- label: i18n.translate('xpack.idxMgmt.indexTable.headers.storageSizeHeader', {
- defaultMessage: 'Storage size',
- }),
- order: 70,
}
);
}
@@ -533,6 +537,7 @@ export class IndexTable extends Component {
const { extensionsService } = services;
const columnConfigs = getColumnConfigs({
showIndexStats: config.enableIndexStats,
+ showSizeAndDocCount: config.enableSizeAndDocCount,
extensionsService,
filterChanged,
history,
diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx
index c2aa548100b57..513377714ffe0 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx
@@ -22,7 +22,7 @@ import {
} from '@elastic/eui';
import { reactRouterNavigate } from '../../../../../../shared_imports';
import { useAppContext } from '../../../../../app_context';
-import { serializeAsESLifecycle } from '../../../../../../../common/lib/data_stream_serialization';
+import { serializeAsESLifecycle } from '../../../../../../../common/lib';
import { getLifecycleValue } from '../../../../../lib/data_streams';
import { TemplateDeserialized } from '../../../../../../../common';
import { ILM_PAGES_POLICY_EDIT } from '../../../../../constants';
diff --git a/x-pack/plugins/index_management/public/plugin.ts b/x-pack/plugins/index_management/public/plugin.ts
index 49693bb0d9aa9..4efe613fc2a04 100644
--- a/x-pack/plugins/index_management/public/plugin.ts
+++ b/x-pack/plugins/index_management/public/plugin.ts
@@ -44,8 +44,8 @@ export class IndexMgmtUIPlugin
enableIndexActions: boolean;
enableLegacyTemplates: boolean;
enableIndexStats: boolean;
- enableSizeAndDocCount: boolean;
enableDataStreamStats: boolean;
+ enableSizeAndDocCount: boolean;
editableIndexSettings: 'all' | 'limited';
isIndexManagementUiEnabled: boolean;
enableMappingsSourceFieldSection: boolean;
@@ -63,8 +63,8 @@ export class IndexMgmtUIPlugin
enableIndexActions,
enableLegacyTemplates,
enableIndexStats,
- enableSizeAndDocCount,
enableDataStreamStats,
+ enableSizeAndDocCount,
editableIndexSettings,
enableMappingsSourceFieldSection,
enableTogglingDataRetention,
@@ -75,8 +75,8 @@ export class IndexMgmtUIPlugin
enableIndexActions: enableIndexActions ?? true,
enableLegacyTemplates: enableLegacyTemplates ?? true,
enableIndexStats: enableIndexStats ?? true,
- enableSizeAndDocCount: enableSizeAndDocCount ?? true,
enableDataStreamStats: enableDataStreamStats ?? true,
+ enableSizeAndDocCount: enableSizeAndDocCount ?? false,
editableIndexSettings: editableIndexSettings ?? 'all',
enableMappingsSourceFieldSection: enableMappingsSourceFieldSection ?? true,
enableTogglingDataRetention: enableTogglingDataRetention ?? true,
diff --git a/x-pack/plugins/index_management/server/config.ts b/x-pack/plugins/index_management/server/config.ts
index 3480e380281e5..9bddc6417cc1b 100644
--- a/x-pack/plugins/index_management/server/config.ts
+++ b/x-pack/plugins/index_management/server/config.ts
@@ -83,6 +83,7 @@ const configLatest: PluginConfigDescriptor = {
enableLegacyTemplates: true,
enableIndexStats: true,
enableDataStreamStats: true,
+ enableSizeAndDocCount: true,
editableIndexSettings: true,
enableMappingsSourceFieldSection: true,
enableTogglingDataRetention: true,
diff --git a/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts b/x-pack/plugins/index_management/server/lib/data_stream_serialization.ts
similarity index 58%
rename from x-pack/plugins/index_management/common/lib/data_stream_serialization.ts
rename to x-pack/plugins/index_management/server/lib/data_stream_serialization.ts
index ceedd072139aa..ffe058907e000 100644
--- a/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts
+++ b/x-pack/plugins/index_management/server/lib/data_stream_serialization.ts
@@ -5,7 +5,8 @@
* 2.0.
*/
-import { DataStream, EnhancedDataStreamFromEs, Health, DataRetention } from '../types';
+import { ByteSizeValue } from '@kbn/config-schema';
+import type { DataStream, EnhancedDataStreamFromEs, Health } from '../../common';
export function deserializeDataStream(dataStreamFromEs: EnhancedDataStreamFromEs): DataStream {
const {
@@ -19,12 +20,18 @@ export function deserializeDataStream(dataStreamFromEs: EnhancedDataStreamFromEs
store_size: storageSize,
store_size_bytes: storageSizeBytes,
maximum_timestamp: maxTimeStamp,
+ metering_size_in_bytes: meteringStorageSizeBytes,
+ metering_doc_count: meteringDocsCount,
_meta,
privileges,
hidden,
lifecycle,
next_generation_managed_by: nextGenerationManagedBy,
} = dataStreamFromEs;
+ const meteringStorageSize =
+ meteringStorageSizeBytes !== undefined
+ ? new ByteSizeValue(meteringStorageSizeBytes).toString()
+ : undefined;
return {
name,
@@ -54,6 +61,9 @@ export function deserializeDataStream(dataStreamFromEs: EnhancedDataStreamFromEs
storageSize,
storageSizeBytes,
maxTimeStamp,
+ meteringStorageSize,
+ meteringStorageSizeBytes,
+ meteringDocsCount,
_meta,
privileges,
hidden,
@@ -67,59 +77,3 @@ export function deserializeDataStreamList(
): DataStream[] {
return dataStreamsFromEs.map((dataStream) => deserializeDataStream(dataStream));
}
-
-export const splitSizeAndUnits = (field: string): { size: string; unit: string } => {
- let size = '';
- let unit = '';
-
- const result = /(\d+)(\w+)/.exec(field);
- if (result) {
- size = result[1];
- unit = result[2];
- }
-
- return {
- size,
- unit,
- };
-};
-
-export const serializeAsESLifecycle = (lifecycle?: DataRetention): DataStream['lifecycle'] => {
- if (!lifecycle || !lifecycle?.enabled) {
- return undefined;
- }
-
- const { infiniteDataRetention, value, unit } = lifecycle;
-
- if (infiniteDataRetention) {
- return {
- enabled: true,
- };
- }
-
- return {
- enabled: true,
- data_retention: `${value}${unit}`,
- };
-};
-
-export const deserializeESLifecycle = (lifecycle?: DataStream['lifecycle']): DataRetention => {
- if (!lifecycle || !lifecycle?.enabled) {
- return { enabled: false };
- }
-
- if (!lifecycle.data_retention) {
- return {
- enabled: true,
- infiniteDataRetention: true,
- };
- }
-
- const { size, unit } = splitSizeAndUnits(lifecycle.data_retention as string);
-
- return {
- enabled: true,
- value: Number(size),
- unit,
- };
-};
diff --git a/x-pack/plugins/index_management/server/lib/fetch_indices.ts b/x-pack/plugins/index_management/server/lib/fetch_indices.ts
index 1df453d1042cd..0e82f03f7308f 100644
--- a/x-pack/plugins/index_management/server/lib/fetch_indices.ts
+++ b/x-pack/plugins/index_management/server/lib/fetch_indices.ts
@@ -10,13 +10,10 @@ import { IScopedClusterClient } from '@kbn/core/server';
import { IndexDataEnricher } from '../services';
import { Index } from '..';
import { RouteDependencies } from '../types';
+import type { MeteringStats } from './types';
interface MeteringStatsResponse {
- indices: Array<{
- name: string;
- num_docs: number;
- size_in_bytes: number;
- }>;
+ indices: MeteringStats[];
}
async function fetchIndicesCall(
diff --git a/x-pack/plugins/index_management/server/lib/types.ts b/x-pack/plugins/index_management/server/lib/types.ts
new file mode 100644
index 0000000000000..1657c4bbbb6e0
--- /dev/null
+++ b/x-pack/plugins/index_management/server/lib/types.ts
@@ -0,0 +1,12 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export interface MeteringStats {
+ name: string;
+ num_docs: number;
+ size_in_bytes: number;
+}
diff --git a/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts b/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts
index 11db019eacf6a..78c0328f52617 100644
--- a/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts
+++ b/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts
@@ -13,18 +13,27 @@ import {
IndicesDataStreamsStatsDataStreamsStatsItem,
SecurityHasPrivilegesResponse,
} from '@elastic/elasticsearch/lib/api/types';
-import { deserializeDataStream, deserializeDataStreamList } from '../../../../common/lib';
+import type { MeteringStats } from '../../../lib/types';
+import {
+ deserializeDataStream,
+ deserializeDataStreamList,
+} from '../../../lib/data_stream_serialization';
import { EnhancedDataStreamFromEs } from '../../../../common/types';
import { RouteDependencies } from '../../../types';
import { addBasePath } from '..';
+interface MeteringStatsResponse {
+ datastreams: MeteringStats[];
+}
const enhanceDataStreams = ({
dataStreams,
dataStreamsStats,
+ meteringStats,
dataStreamsPrivileges,
}: {
dataStreams: IndicesDataStream[];
dataStreamsStats?: IndicesDataStreamsStatsDataStreamsStatsItem[];
+ meteringStats?: MeteringStats[];
dataStreamsPrivileges?: SecurityHasPrivilegesResponse;
}): EnhancedDataStreamFromEs[] => {
return dataStreams.map((dataStream) => {
@@ -51,6 +60,14 @@ const enhanceDataStreams = ({
}
}
+ if (meteringStats) {
+ const datastreamMeteringStats = meteringStats.find((s) => s.name === dataStream.name);
+ if (datastreamMeteringStats) {
+ enhancedDataStream.metering_size_in_bytes = datastreamMeteringStats.size_in_bytes;
+ enhancedDataStream.metering_doc_count = datastreamMeteringStats.num_docs;
+ }
+ }
+
return enhancedDataStream;
});
};
@@ -70,6 +87,17 @@ const getDataStreamsStats = (client: IScopedClusterClient, name = '*') => {
});
};
+const getMeteringStats = (client: IScopedClusterClient, name?: string) => {
+ let path = `/_metering/stats`;
+ if (name) {
+ path = `${path}/${name}`;
+ }
+ return client.asSecondaryAuthUser.transport.request({
+ method: 'GET',
+ path,
+ });
+};
+
const getDataStreamsPrivileges = (client: IScopedClusterClient, names: string[]) => {
return client.asCurrentUser.security.hasPrivileges({
body: {
@@ -99,10 +127,14 @@ export function registerGetAllRoute({ router, lib: { handleEsError }, config }:
let dataStreamsStats;
let dataStreamsPrivileges;
+ let meteringStats;
if (includeStats && config.isDataStreamStatsEnabled !== false) {
({ data_streams: dataStreamsStats } = await getDataStreamsStats(client));
}
+ if (includeStats && config.isSizeAndDocCountEnabled !== false) {
+ ({ datastreams: meteringStats } = await getMeteringStats(client));
+ }
if (config.isSecurityEnabled() && dataStreams.length > 0) {
dataStreamsPrivileges = await getDataStreamsPrivileges(
@@ -114,6 +146,7 @@ export function registerGetAllRoute({ router, lib: { handleEsError }, config }:
const enhancedDataStreams = enhanceDataStreams({
dataStreams,
dataStreamsStats,
+ meteringStats,
dataStreamsPrivileges,
});
@@ -138,6 +171,7 @@ export function registerGetOneRoute({ router, lib: { handleEsError }, config }:
const { name } = request.params as TypeOf;
const { client } = (await context.core).elasticsearch;
let dataStreamsStats;
+ let meteringStats;
try {
const { data_streams: dataStreams } = await getDataStreams(client, name);
@@ -146,6 +180,10 @@ export function registerGetOneRoute({ router, lib: { handleEsError }, config }:
({ data_streams: dataStreamsStats } = await getDataStreamsStats(client, name));
}
+ if (config.isSizeAndDocCountEnabled !== false) {
+ ({ datastreams: meteringStats } = await getMeteringStats(client, name));
+ }
+
if (dataStreams[0]) {
let dataStreamsPrivileges;
@@ -156,6 +194,7 @@ export function registerGetOneRoute({ router, lib: { handleEsError }, config }:
const enhancedDataStreams = enhanceDataStreams({
dataStreams,
dataStreamsStats,
+ meteringStats,
dataStreamsPrivileges,
});
const body = deserializeDataStream(enhancedDataStreams[0]);
diff --git a/x-pack/test_serverless/api_integration/test_suites/common/index_management/datastreams.ts b/x-pack/test_serverless/api_integration/test_suites/common/index_management/datastreams.ts
index 961c9a73bdf47..de3a92587d6b9 100644
--- a/x-pack/test_serverless/api_integration/test_suites/common/index_management/datastreams.ts
+++ b/x-pack/test_serverless/api_integration/test_suites/common/index_management/datastreams.ts
@@ -118,6 +118,9 @@ export default function ({ getService }: FtrProviderContext) {
lifecycle: {
enabled: true,
},
+ meteringDocsCount: 0,
+ meteringStorageSize: '0b',
+ meteringStorageSizeBytes: 0,
});
});
});
From c26ee22db69059d1908cb7bf3982db13a354b77a Mon Sep 17 00:00:00 2001
From: Tiago Vila Verde
Date: Mon, 16 Sep 2024 14:07:03 +0200
Subject: [PATCH 012/139] [Entity Manager] Extract the entity manager out of
Observability solution (#190304)
## Summary
Moving the Entity Manager out of Observability and into a Kibana plugin
as per [this
ticket](https://github.com/elastic/security-team/issues/10156)
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
---
.github/CODEOWNERS | 2 +-
docs/developer/plugin-list.asciidoc | 2 +-
package.json | 2 +-
tsconfig.base.json | 4 ++--
.../entity_manager/README.md | 0
.../entity_manager/common/config.ts | 0
.../entity_manager/common/constants_entities.ts | 0
.../entity_manager/common/debug_log.ts | 0
.../entity_manager/common/errors.ts | 0
.../entity_manager/common/types_api.ts | 0
.../entity_manager/docs/entity_definitions.md | 0
.../entity_manager/jest.config.js | 9 ++++-----
.../entity_manager/kibana.jsonc | 0
.../entity_manager/public/index.ts | 0
.../entity_manager/public/lib/entity_client.ts | 0
.../entity_manager/public/lib/errors.ts | 0
.../entity_manager/public/plugin.ts | 0
.../entity_manager/public/types.ts | 0
.../entity_manager/server/index.ts | 0
.../entity_manager/server/lib/auth/api_key/api_key.ts | 0
.../server/lib/auth/api_key/saved_object.ts | 0
.../entity_manager/server/lib/auth/index.ts | 0
.../entity_manager/server/lib/auth/privileges.ts | 0
.../entity_manager/server/lib/client/index.ts | 0
.../server/lib/entities/built_in/constants.ts | 0
.../entity_manager/server/lib/entities/built_in/index.ts | 0
.../server/lib/entities/built_in/services.ts | 0
.../lib/entities/create_and_install_ingest_pipeline.ts | 0
.../server/lib/entities/create_and_install_transform.ts | 0
.../server/lib/entities/delete_entity_definition.ts | 0
.../entity_manager/server/lib/entities/delete_index.ts | 0
.../server/lib/entities/delete_ingest_pipeline.ts | 0
.../server/lib/entities/delete_transforms.ts | 0
.../lib/entities/errors/entity_definition_id_invalid.ts | 0
.../lib/entities/errors/entity_id_conflict_error.ts | 0
.../server/lib/entities/errors/entity_not_found.ts | 0
.../lib/entities/errors/entity_security_exception.ts | 0
.../lib/entities/errors/invalid_transform_error.ts | 0
.../server/lib/entities/find_entity_definition.ts | 0
.../server/lib/entities/helpers/calculate_offset.ts | 0
.../helpers/fixtures/builtin_entity_definition.ts | 0
.../lib/entities/helpers/fixtures/entity_definition.ts | 0
.../helpers/fixtures/entity_definition_with_backfill.ts | 0
.../server/lib/entities/helpers/fixtures/index.ts | 0
.../server/lib/entities/helpers/generate_component_id.ts | 0
.../entities/helpers/get_elasticsearch_query_or_throw.ts | 0
.../ingest_pipeline_script_processor_helpers.test.ts | 0
.../helpers/ingest_pipeline_script_processor_helpers.ts | 0
.../server/lib/entities/helpers/is_backfill_enabled.ts | 0
.../server/lib/entities/helpers/is_builtin_definition.ts | 0
.../lib/entities/helpers/merge_definition_update.ts | 0
.../entity_manager/server/lib/entities/helpers/retry.ts | 0
.../generate_history_processors.test.ts.snap | 0
.../generate_latest_processors.test.ts.snap | 0
.../ingest_pipeline/generate_history_processors.test.ts | 0
.../ingest_pipeline/generate_history_processors.ts | 0
.../ingest_pipeline/generate_latest_processors.test.ts | 0
.../ingest_pipeline/generate_latest_processors.ts | 0
.../lib/entities/install_entity_definition.test.ts | 0
.../server/lib/entities/install_entity_definition.ts | 0
.../server/lib/entities/read_entity_definition.ts | 0
.../server/lib/entities/save_entity_definition.ts | 0
.../server/lib/entities/start_transforms.ts | 0
.../server/lib/entities/stop_transforms.ts | 0
.../__snapshots__/entities_history_template.test.ts.snap | 0
.../__snapshots__/entities_latest_template.test.ts.snap | 0
.../entities/templates/entities_history_template.test.ts | 0
.../lib/entities/templates/entities_history_template.ts | 0
.../entities/templates/entities_latest_template.test.ts | 0
.../lib/entities/templates/entities_latest_template.ts | 0
.../generate_history_transform.test.ts.snap | 0
.../__snapshots__/generate_latest_transform.test.ts.snap | 0
.../transform/generate_history_transform.test.ts | 0
.../lib/entities/transform/generate_history_transform.ts | 0
.../entities/transform/generate_identity_aggregations.ts | 0
.../entities/transform/generate_latest_transform.test.ts | 0
.../lib/entities/transform/generate_latest_transform.ts | 0
.../transform/generate_metadata_aggregations.test.ts | 0
.../entities/transform/generate_metadata_aggregations.ts | 0
.../entities/transform/generate_metric_aggregations.ts | 0
.../entities/transform/validate_transform_ids.test.ts | 0
.../lib/entities/transform/validate_transform_ids.ts | 0
.../entity_manager/server/lib/entities/types.ts | 0
.../server/lib/entities/uninstall_entity_definition.ts | 0
.../server/lib/entities/upgrade_entity_definition.ts | 0
.../entity_manager/server/lib/entity_client.ts | 0
.../entity_manager/server/lib/errors.ts | 0
.../entity_manager/server/lib/manage_index_templates.ts | 0
.../entity_manager/server/lib/utils.ts | 0
.../server/lib/validators/validate_date_range.ts | 0
.../server/lib/validators/validation_error.ts | 0
.../entity_manager/server/plugin.ts | 0
.../server/routes/create_entity_manager_server_route.ts | 0
.../entity_manager/server/routes/enablement/check.ts | 0
.../entity_manager/server/routes/enablement/disable.ts | 0
.../entity_manager/server/routes/enablement/enable.ts | 0
.../entity_manager/server/routes/enablement/index.ts | 0
.../entity_manager/server/routes/entities/create.ts | 0
.../entity_manager/server/routes/entities/delete.ts | 0
.../entity_manager/server/routes/entities/get.ts | 0
.../entity_manager/server/routes/entities/index.ts | 0
.../entity_manager/server/routes/entities/reset.ts | 0
.../entity_manager/server/routes/entities/update.ts | 0
.../entity_manager/server/routes/index.ts | 0
.../entity_manager/server/routes/types.ts | 0
.../server/saved_objects/entity_definition.ts | 0
.../server/saved_objects/entity_discovery_api_key.ts | 0
.../entity_manager/server/saved_objects/index.ts | 0
.../server/templates/components/base_history.ts | 0
.../server/templates/components/base_latest.ts | 0
.../entity_manager/server/templates/components/entity.ts | 0
.../entity_manager/server/templates/components/event.ts | 0
.../server/templates/components/helpers.test.ts | 0
.../server/templates/components/helpers.ts | 0
.../entity_manager/server/types.ts | 0
.../entity_manager/tsconfig.json | 6 ++++--
yarn.lock | 2 +-
117 files changed, 14 insertions(+), 13 deletions(-)
rename x-pack/plugins/{observability_solution => }/entity_manager/README.md (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/common/config.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/common/constants_entities.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/common/debug_log.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/common/errors.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/common/types_api.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/docs/entity_definitions.md (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/jest.config.js (52%)
rename x-pack/plugins/{observability_solution => }/entity_manager/kibana.jsonc (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/public/index.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/public/lib/entity_client.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/public/lib/errors.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/public/plugin.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/public/types.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/index.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/auth/api_key/api_key.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/auth/api_key/saved_object.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/auth/index.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/auth/privileges.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/client/index.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/built_in/constants.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/built_in/index.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/built_in/services.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/create_and_install_ingest_pipeline.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/create_and_install_transform.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/delete_entity_definition.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/delete_index.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/delete_ingest_pipeline.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/delete_transforms.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/errors/entity_definition_id_invalid.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/errors/entity_id_conflict_error.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/errors/entity_not_found.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/errors/entity_security_exception.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/errors/invalid_transform_error.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/find_entity_definition.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/helpers/calculate_offset.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/helpers/fixtures/builtin_entity_definition.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/helpers/fixtures/entity_definition.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/helpers/fixtures/entity_definition_with_backfill.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/helpers/fixtures/index.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/helpers/generate_component_id.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/helpers/get_elasticsearch_query_or_throw.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/helpers/ingest_pipeline_script_processor_helpers.test.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/helpers/ingest_pipeline_script_processor_helpers.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/helpers/is_backfill_enabled.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/helpers/is_builtin_definition.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/helpers/merge_definition_update.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/helpers/retry.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/ingest_pipeline/__snapshots__/generate_history_processors.test.ts.snap (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/ingest_pipeline/__snapshots__/generate_latest_processors.test.ts.snap (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/ingest_pipeline/generate_history_processors.test.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/ingest_pipeline/generate_history_processors.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/ingest_pipeline/generate_latest_processors.test.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/ingest_pipeline/generate_latest_processors.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/install_entity_definition.test.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/install_entity_definition.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/read_entity_definition.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/save_entity_definition.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/start_transforms.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/stop_transforms.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/templates/__snapshots__/entities_history_template.test.ts.snap (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/templates/__snapshots__/entities_latest_template.test.ts.snap (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/templates/entities_history_template.test.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/templates/entities_history_template.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/templates/entities_latest_template.test.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/templates/entities_latest_template.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/transform/__snapshots__/generate_history_transform.test.ts.snap (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/transform/__snapshots__/generate_latest_transform.test.ts.snap (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/transform/generate_history_transform.test.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/transform/generate_history_transform.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/transform/generate_identity_aggregations.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/transform/generate_latest_transform.test.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/transform/generate_latest_transform.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/transform/generate_metadata_aggregations.test.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/transform/generate_metadata_aggregations.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/transform/generate_metric_aggregations.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/transform/validate_transform_ids.test.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/transform/validate_transform_ids.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/types.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/uninstall_entity_definition.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entities/upgrade_entity_definition.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/entity_client.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/errors.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/manage_index_templates.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/utils.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/validators/validate_date_range.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/lib/validators/validation_error.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/plugin.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/routes/create_entity_manager_server_route.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/routes/enablement/check.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/routes/enablement/disable.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/routes/enablement/enable.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/routes/enablement/index.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/routes/entities/create.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/routes/entities/delete.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/routes/entities/get.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/routes/entities/index.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/routes/entities/reset.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/routes/entities/update.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/routes/index.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/routes/types.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/saved_objects/entity_definition.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/saved_objects/entity_discovery_api_key.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/saved_objects/index.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/templates/components/base_history.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/templates/components/base_latest.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/templates/components/entity.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/templates/components/event.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/templates/components/helpers.test.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/templates/components/helpers.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/server/types.ts (100%)
rename x-pack/plugins/{observability_solution => }/entity_manager/tsconfig.json (90%)
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 3a4306292b8f9..9adb05cb22ecc 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -402,7 +402,7 @@ x-pack/plugins/enterprise_search @elastic/search-kibana
x-pack/plugins/observability_solution/entities_data_access @elastic/obs-entities
x-pack/packages/kbn-entities-schema @elastic/obs-entities
x-pack/test/api_integration/apis/entity_manager/fixture_plugin @elastic/obs-entities
-x-pack/plugins/observability_solution/entity_manager @elastic/obs-entities
+x-pack/plugins/entity_manager @elastic/obs-entities
examples/error_boundary @elastic/appex-sharedux
packages/kbn-es @elastic/kibana-operations
packages/kbn-es-archiver @elastic/kibana-operations @elastic/appex-qa
diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc
index 0010045ab3c92..7e31cd50aeb81 100644
--- a/docs/developer/plugin-list.asciidoc
+++ b/docs/developer/plugin-list.asciidoc
@@ -570,7 +570,7 @@ security and spaces filtering.
|Exposes services to access entities data.
-|{kib-repo}blob/{branch}/x-pack/plugins/observability_solution/entity_manager/README.md[entityManager]
+|{kib-repo}blob/{branch}/x-pack/plugins/entity_manager/README.md[entityManager]
|This plugin provides access to observed entity data, such as information about hosts, pods, containers, services, and more.
diff --git a/package.json b/package.json
index 760268042a27b..5b9e8fac1fb31 100644
--- a/package.json
+++ b/package.json
@@ -464,7 +464,7 @@
"@kbn/entities-data-access-plugin": "link:x-pack/plugins/observability_solution/entities_data_access",
"@kbn/entities-schema": "link:x-pack/packages/kbn-entities-schema",
"@kbn/entity-manager-fixture-plugin": "link:x-pack/test/api_integration/apis/entity_manager/fixture_plugin",
- "@kbn/entityManager-plugin": "link:x-pack/plugins/observability_solution/entity_manager",
+ "@kbn/entityManager-plugin": "link:x-pack/plugins/entity_manager",
"@kbn/error-boundary-example-plugin": "link:examples/error_boundary",
"@kbn/es-errors": "link:packages/kbn-es-errors",
"@kbn/es-query": "link:packages/kbn-es-query",
diff --git a/tsconfig.base.json b/tsconfig.base.json
index f931b2e3ea28d..ae8b6c2c2a95f 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -798,8 +798,8 @@
"@kbn/entities-schema/*": ["x-pack/packages/kbn-entities-schema/*"],
"@kbn/entity-manager-fixture-plugin": ["x-pack/test/api_integration/apis/entity_manager/fixture_plugin"],
"@kbn/entity-manager-fixture-plugin/*": ["x-pack/test/api_integration/apis/entity_manager/fixture_plugin/*"],
- "@kbn/entityManager-plugin": ["x-pack/plugins/observability_solution/entity_manager"],
- "@kbn/entityManager-plugin/*": ["x-pack/plugins/observability_solution/entity_manager/*"],
+ "@kbn/entityManager-plugin": ["x-pack/plugins/entity_manager"],
+ "@kbn/entityManager-plugin/*": ["x-pack/plugins/entity_manager/*"],
"@kbn/error-boundary-example-plugin": ["examples/error_boundary"],
"@kbn/error-boundary-example-plugin/*": ["examples/error_boundary/*"],
"@kbn/es": ["packages/kbn-es"],
diff --git a/x-pack/plugins/observability_solution/entity_manager/README.md b/x-pack/plugins/entity_manager/README.md
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/README.md
rename to x-pack/plugins/entity_manager/README.md
diff --git a/x-pack/plugins/observability_solution/entity_manager/common/config.ts b/x-pack/plugins/entity_manager/common/config.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/common/config.ts
rename to x-pack/plugins/entity_manager/common/config.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/common/constants_entities.ts b/x-pack/plugins/entity_manager/common/constants_entities.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/common/constants_entities.ts
rename to x-pack/plugins/entity_manager/common/constants_entities.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/common/debug_log.ts b/x-pack/plugins/entity_manager/common/debug_log.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/common/debug_log.ts
rename to x-pack/plugins/entity_manager/common/debug_log.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/common/errors.ts b/x-pack/plugins/entity_manager/common/errors.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/common/errors.ts
rename to x-pack/plugins/entity_manager/common/errors.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/common/types_api.ts b/x-pack/plugins/entity_manager/common/types_api.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/common/types_api.ts
rename to x-pack/plugins/entity_manager/common/types_api.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/docs/entity_definitions.md b/x-pack/plugins/entity_manager/docs/entity_definitions.md
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/docs/entity_definitions.md
rename to x-pack/plugins/entity_manager/docs/entity_definitions.md
diff --git a/x-pack/plugins/observability_solution/entity_manager/jest.config.js b/x-pack/plugins/entity_manager/jest.config.js
similarity index 52%
rename from x-pack/plugins/observability_solution/entity_manager/jest.config.js
rename to x-pack/plugins/entity_manager/jest.config.js
index 29fb7c37260fb..615d1c851895b 100644
--- a/x-pack/plugins/observability_solution/entity_manager/jest.config.js
+++ b/x-pack/plugins/entity_manager/jest.config.js
@@ -7,12 +7,11 @@
module.exports = {
preset: '@kbn/test',
- rootDir: '../../../..',
- roots: ['/x-pack/plugins/observability_solution/entity_manager'],
- coverageDirectory:
- '/target/kibana-coverage/jest/x-pack/plugins/observability_solution/entity_manager',
+ rootDir: '../../..',
+ roots: ['/x-pack/plugins/entity_manager'],
+ coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/entity_manager',
coverageReporters: ['text', 'html'],
collectCoverageFrom: [
- '/x-pack/plugins/observability_solution/entity_manager/{common,public,server}/**/*.{js,ts,tsx}',
+ '/x-pack/plugins/entity_manager/{common,public,server}/**/*.{js,ts,tsx}',
],
};
diff --git a/x-pack/plugins/observability_solution/entity_manager/kibana.jsonc b/x-pack/plugins/entity_manager/kibana.jsonc
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/kibana.jsonc
rename to x-pack/plugins/entity_manager/kibana.jsonc
diff --git a/x-pack/plugins/observability_solution/entity_manager/public/index.ts b/x-pack/plugins/entity_manager/public/index.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/public/index.ts
rename to x-pack/plugins/entity_manager/public/index.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/public/lib/entity_client.ts b/x-pack/plugins/entity_manager/public/lib/entity_client.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/public/lib/entity_client.ts
rename to x-pack/plugins/entity_manager/public/lib/entity_client.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/public/lib/errors.ts b/x-pack/plugins/entity_manager/public/lib/errors.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/public/lib/errors.ts
rename to x-pack/plugins/entity_manager/public/lib/errors.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/public/plugin.ts b/x-pack/plugins/entity_manager/public/plugin.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/public/plugin.ts
rename to x-pack/plugins/entity_manager/public/plugin.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/public/types.ts b/x-pack/plugins/entity_manager/public/types.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/public/types.ts
rename to x-pack/plugins/entity_manager/public/types.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/index.ts b/x-pack/plugins/entity_manager/server/index.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/index.ts
rename to x-pack/plugins/entity_manager/server/index.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/auth/api_key/api_key.ts b/x-pack/plugins/entity_manager/server/lib/auth/api_key/api_key.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/auth/api_key/api_key.ts
rename to x-pack/plugins/entity_manager/server/lib/auth/api_key/api_key.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/auth/api_key/saved_object.ts b/x-pack/plugins/entity_manager/server/lib/auth/api_key/saved_object.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/auth/api_key/saved_object.ts
rename to x-pack/plugins/entity_manager/server/lib/auth/api_key/saved_object.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/auth/index.ts b/x-pack/plugins/entity_manager/server/lib/auth/index.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/auth/index.ts
rename to x-pack/plugins/entity_manager/server/lib/auth/index.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/auth/privileges.ts b/x-pack/plugins/entity_manager/server/lib/auth/privileges.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/auth/privileges.ts
rename to x-pack/plugins/entity_manager/server/lib/auth/privileges.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/client/index.ts b/x-pack/plugins/entity_manager/server/lib/client/index.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/client/index.ts
rename to x-pack/plugins/entity_manager/server/lib/client/index.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/built_in/constants.ts b/x-pack/plugins/entity_manager/server/lib/entities/built_in/constants.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/built_in/constants.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/built_in/constants.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/built_in/index.ts b/x-pack/plugins/entity_manager/server/lib/entities/built_in/index.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/built_in/index.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/built_in/index.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/built_in/services.ts b/x-pack/plugins/entity_manager/server/lib/entities/built_in/services.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/built_in/services.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/built_in/services.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/create_and_install_ingest_pipeline.ts b/x-pack/plugins/entity_manager/server/lib/entities/create_and_install_ingest_pipeline.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/create_and_install_ingest_pipeline.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/create_and_install_ingest_pipeline.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/create_and_install_transform.ts b/x-pack/plugins/entity_manager/server/lib/entities/create_and_install_transform.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/create_and_install_transform.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/create_and_install_transform.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/delete_entity_definition.ts b/x-pack/plugins/entity_manager/server/lib/entities/delete_entity_definition.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/delete_entity_definition.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/delete_entity_definition.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/delete_index.ts b/x-pack/plugins/entity_manager/server/lib/entities/delete_index.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/delete_index.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/delete_index.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/delete_ingest_pipeline.ts b/x-pack/plugins/entity_manager/server/lib/entities/delete_ingest_pipeline.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/delete_ingest_pipeline.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/delete_ingest_pipeline.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/delete_transforms.ts b/x-pack/plugins/entity_manager/server/lib/entities/delete_transforms.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/delete_transforms.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/delete_transforms.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/errors/entity_definition_id_invalid.ts b/x-pack/plugins/entity_manager/server/lib/entities/errors/entity_definition_id_invalid.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/errors/entity_definition_id_invalid.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/errors/entity_definition_id_invalid.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/errors/entity_id_conflict_error.ts b/x-pack/plugins/entity_manager/server/lib/entities/errors/entity_id_conflict_error.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/errors/entity_id_conflict_error.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/errors/entity_id_conflict_error.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/errors/entity_not_found.ts b/x-pack/plugins/entity_manager/server/lib/entities/errors/entity_not_found.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/errors/entity_not_found.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/errors/entity_not_found.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/errors/entity_security_exception.ts b/x-pack/plugins/entity_manager/server/lib/entities/errors/entity_security_exception.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/errors/entity_security_exception.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/errors/entity_security_exception.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/errors/invalid_transform_error.ts b/x-pack/plugins/entity_manager/server/lib/entities/errors/invalid_transform_error.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/errors/invalid_transform_error.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/errors/invalid_transform_error.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/find_entity_definition.ts b/x-pack/plugins/entity_manager/server/lib/entities/find_entity_definition.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/find_entity_definition.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/find_entity_definition.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/calculate_offset.ts b/x-pack/plugins/entity_manager/server/lib/entities/helpers/calculate_offset.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/calculate_offset.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/helpers/calculate_offset.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/fixtures/builtin_entity_definition.ts b/x-pack/plugins/entity_manager/server/lib/entities/helpers/fixtures/builtin_entity_definition.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/fixtures/builtin_entity_definition.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/helpers/fixtures/builtin_entity_definition.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/fixtures/entity_definition.ts b/x-pack/plugins/entity_manager/server/lib/entities/helpers/fixtures/entity_definition.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/fixtures/entity_definition.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/helpers/fixtures/entity_definition.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/fixtures/entity_definition_with_backfill.ts b/x-pack/plugins/entity_manager/server/lib/entities/helpers/fixtures/entity_definition_with_backfill.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/fixtures/entity_definition_with_backfill.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/helpers/fixtures/entity_definition_with_backfill.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/fixtures/index.ts b/x-pack/plugins/entity_manager/server/lib/entities/helpers/fixtures/index.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/fixtures/index.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/helpers/fixtures/index.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/generate_component_id.ts b/x-pack/plugins/entity_manager/server/lib/entities/helpers/generate_component_id.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/generate_component_id.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/helpers/generate_component_id.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/get_elasticsearch_query_or_throw.ts b/x-pack/plugins/entity_manager/server/lib/entities/helpers/get_elasticsearch_query_or_throw.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/get_elasticsearch_query_or_throw.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/helpers/get_elasticsearch_query_or_throw.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/ingest_pipeline_script_processor_helpers.test.ts b/x-pack/plugins/entity_manager/server/lib/entities/helpers/ingest_pipeline_script_processor_helpers.test.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/ingest_pipeline_script_processor_helpers.test.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/helpers/ingest_pipeline_script_processor_helpers.test.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/ingest_pipeline_script_processor_helpers.ts b/x-pack/plugins/entity_manager/server/lib/entities/helpers/ingest_pipeline_script_processor_helpers.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/ingest_pipeline_script_processor_helpers.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/helpers/ingest_pipeline_script_processor_helpers.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/is_backfill_enabled.ts b/x-pack/plugins/entity_manager/server/lib/entities/helpers/is_backfill_enabled.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/is_backfill_enabled.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/helpers/is_backfill_enabled.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/is_builtin_definition.ts b/x-pack/plugins/entity_manager/server/lib/entities/helpers/is_builtin_definition.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/is_builtin_definition.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/helpers/is_builtin_definition.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/merge_definition_update.ts b/x-pack/plugins/entity_manager/server/lib/entities/helpers/merge_definition_update.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/merge_definition_update.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/helpers/merge_definition_update.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/retry.ts b/x-pack/plugins/entity_manager/server/lib/entities/helpers/retry.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/retry.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/helpers/retry.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/__snapshots__/generate_history_processors.test.ts.snap b/x-pack/plugins/entity_manager/server/lib/entities/ingest_pipeline/__snapshots__/generate_history_processors.test.ts.snap
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/__snapshots__/generate_history_processors.test.ts.snap
rename to x-pack/plugins/entity_manager/server/lib/entities/ingest_pipeline/__snapshots__/generate_history_processors.test.ts.snap
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/__snapshots__/generate_latest_processors.test.ts.snap b/x-pack/plugins/entity_manager/server/lib/entities/ingest_pipeline/__snapshots__/generate_latest_processors.test.ts.snap
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/__snapshots__/generate_latest_processors.test.ts.snap
rename to x-pack/plugins/entity_manager/server/lib/entities/ingest_pipeline/__snapshots__/generate_latest_processors.test.ts.snap
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/generate_history_processors.test.ts b/x-pack/plugins/entity_manager/server/lib/entities/ingest_pipeline/generate_history_processors.test.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/generate_history_processors.test.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/ingest_pipeline/generate_history_processors.test.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/generate_history_processors.ts b/x-pack/plugins/entity_manager/server/lib/entities/ingest_pipeline/generate_history_processors.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/generate_history_processors.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/ingest_pipeline/generate_history_processors.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/generate_latest_processors.test.ts b/x-pack/plugins/entity_manager/server/lib/entities/ingest_pipeline/generate_latest_processors.test.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/generate_latest_processors.test.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/ingest_pipeline/generate_latest_processors.test.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/generate_latest_processors.ts b/x-pack/plugins/entity_manager/server/lib/entities/ingest_pipeline/generate_latest_processors.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/generate_latest_processors.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/ingest_pipeline/generate_latest_processors.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/install_entity_definition.test.ts b/x-pack/plugins/entity_manager/server/lib/entities/install_entity_definition.test.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/install_entity_definition.test.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/install_entity_definition.test.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/install_entity_definition.ts b/x-pack/plugins/entity_manager/server/lib/entities/install_entity_definition.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/install_entity_definition.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/install_entity_definition.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/read_entity_definition.ts b/x-pack/plugins/entity_manager/server/lib/entities/read_entity_definition.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/read_entity_definition.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/read_entity_definition.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/save_entity_definition.ts b/x-pack/plugins/entity_manager/server/lib/entities/save_entity_definition.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/save_entity_definition.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/save_entity_definition.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/start_transforms.ts b/x-pack/plugins/entity_manager/server/lib/entities/start_transforms.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/start_transforms.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/start_transforms.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/stop_transforms.ts b/x-pack/plugins/entity_manager/server/lib/entities/stop_transforms.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/stop_transforms.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/stop_transforms.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/__snapshots__/entities_history_template.test.ts.snap b/x-pack/plugins/entity_manager/server/lib/entities/templates/__snapshots__/entities_history_template.test.ts.snap
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/__snapshots__/entities_history_template.test.ts.snap
rename to x-pack/plugins/entity_manager/server/lib/entities/templates/__snapshots__/entities_history_template.test.ts.snap
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/__snapshots__/entities_latest_template.test.ts.snap b/x-pack/plugins/entity_manager/server/lib/entities/templates/__snapshots__/entities_latest_template.test.ts.snap
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/__snapshots__/entities_latest_template.test.ts.snap
rename to x-pack/plugins/entity_manager/server/lib/entities/templates/__snapshots__/entities_latest_template.test.ts.snap
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_history_template.test.ts b/x-pack/plugins/entity_manager/server/lib/entities/templates/entities_history_template.test.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_history_template.test.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/templates/entities_history_template.test.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_history_template.ts b/x-pack/plugins/entity_manager/server/lib/entities/templates/entities_history_template.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_history_template.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/templates/entities_history_template.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_latest_template.test.ts b/x-pack/plugins/entity_manager/server/lib/entities/templates/entities_latest_template.test.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_latest_template.test.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/templates/entities_latest_template.test.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_latest_template.ts b/x-pack/plugins/entity_manager/server/lib/entities/templates/entities_latest_template.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_latest_template.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/templates/entities_latest_template.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/__snapshots__/generate_history_transform.test.ts.snap b/x-pack/plugins/entity_manager/server/lib/entities/transform/__snapshots__/generate_history_transform.test.ts.snap
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/__snapshots__/generate_history_transform.test.ts.snap
rename to x-pack/plugins/entity_manager/server/lib/entities/transform/__snapshots__/generate_history_transform.test.ts.snap
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/__snapshots__/generate_latest_transform.test.ts.snap b/x-pack/plugins/entity_manager/server/lib/entities/transform/__snapshots__/generate_latest_transform.test.ts.snap
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/__snapshots__/generate_latest_transform.test.ts.snap
rename to x-pack/plugins/entity_manager/server/lib/entities/transform/__snapshots__/generate_latest_transform.test.ts.snap
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/generate_history_transform.test.ts b/x-pack/plugins/entity_manager/server/lib/entities/transform/generate_history_transform.test.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/generate_history_transform.test.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/transform/generate_history_transform.test.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/generate_history_transform.ts b/x-pack/plugins/entity_manager/server/lib/entities/transform/generate_history_transform.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/generate_history_transform.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/transform/generate_history_transform.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/generate_identity_aggregations.ts b/x-pack/plugins/entity_manager/server/lib/entities/transform/generate_identity_aggregations.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/generate_identity_aggregations.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/transform/generate_identity_aggregations.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/generate_latest_transform.test.ts b/x-pack/plugins/entity_manager/server/lib/entities/transform/generate_latest_transform.test.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/generate_latest_transform.test.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/transform/generate_latest_transform.test.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/generate_latest_transform.ts b/x-pack/plugins/entity_manager/server/lib/entities/transform/generate_latest_transform.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/generate_latest_transform.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/transform/generate_latest_transform.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/generate_metadata_aggregations.test.ts b/x-pack/plugins/entity_manager/server/lib/entities/transform/generate_metadata_aggregations.test.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/generate_metadata_aggregations.test.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/transform/generate_metadata_aggregations.test.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/generate_metadata_aggregations.ts b/x-pack/plugins/entity_manager/server/lib/entities/transform/generate_metadata_aggregations.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/generate_metadata_aggregations.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/transform/generate_metadata_aggregations.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/generate_metric_aggregations.ts b/x-pack/plugins/entity_manager/server/lib/entities/transform/generate_metric_aggregations.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/generate_metric_aggregations.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/transform/generate_metric_aggregations.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/validate_transform_ids.test.ts b/x-pack/plugins/entity_manager/server/lib/entities/transform/validate_transform_ids.test.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/validate_transform_ids.test.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/transform/validate_transform_ids.test.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/validate_transform_ids.ts b/x-pack/plugins/entity_manager/server/lib/entities/transform/validate_transform_ids.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/validate_transform_ids.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/transform/validate_transform_ids.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/types.ts b/x-pack/plugins/entity_manager/server/lib/entities/types.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/types.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/types.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/uninstall_entity_definition.ts b/x-pack/plugins/entity_manager/server/lib/entities/uninstall_entity_definition.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/uninstall_entity_definition.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/uninstall_entity_definition.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/upgrade_entity_definition.ts b/x-pack/plugins/entity_manager/server/lib/entities/upgrade_entity_definition.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/upgrade_entity_definition.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/upgrade_entity_definition.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entity_client.ts b/x-pack/plugins/entity_manager/server/lib/entity_client.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entity_client.ts
rename to x-pack/plugins/entity_manager/server/lib/entity_client.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/errors.ts b/x-pack/plugins/entity_manager/server/lib/errors.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/errors.ts
rename to x-pack/plugins/entity_manager/server/lib/errors.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/manage_index_templates.ts b/x-pack/plugins/entity_manager/server/lib/manage_index_templates.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/manage_index_templates.ts
rename to x-pack/plugins/entity_manager/server/lib/manage_index_templates.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/utils.ts b/x-pack/plugins/entity_manager/server/lib/utils.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/utils.ts
rename to x-pack/plugins/entity_manager/server/lib/utils.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/validators/validate_date_range.ts b/x-pack/plugins/entity_manager/server/lib/validators/validate_date_range.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/validators/validate_date_range.ts
rename to x-pack/plugins/entity_manager/server/lib/validators/validate_date_range.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/validators/validation_error.ts b/x-pack/plugins/entity_manager/server/lib/validators/validation_error.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/lib/validators/validation_error.ts
rename to x-pack/plugins/entity_manager/server/lib/validators/validation_error.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/plugin.ts b/x-pack/plugins/entity_manager/server/plugin.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/plugin.ts
rename to x-pack/plugins/entity_manager/server/plugin.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/routes/create_entity_manager_server_route.ts b/x-pack/plugins/entity_manager/server/routes/create_entity_manager_server_route.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/routes/create_entity_manager_server_route.ts
rename to x-pack/plugins/entity_manager/server/routes/create_entity_manager_server_route.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/check.ts b/x-pack/plugins/entity_manager/server/routes/enablement/check.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/check.ts
rename to x-pack/plugins/entity_manager/server/routes/enablement/check.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/disable.ts b/x-pack/plugins/entity_manager/server/routes/enablement/disable.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/disable.ts
rename to x-pack/plugins/entity_manager/server/routes/enablement/disable.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/enable.ts b/x-pack/plugins/entity_manager/server/routes/enablement/enable.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/enable.ts
rename to x-pack/plugins/entity_manager/server/routes/enablement/enable.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/index.ts b/x-pack/plugins/entity_manager/server/routes/enablement/index.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/index.ts
rename to x-pack/plugins/entity_manager/server/routes/enablement/index.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/create.ts b/x-pack/plugins/entity_manager/server/routes/entities/create.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/routes/entities/create.ts
rename to x-pack/plugins/entity_manager/server/routes/entities/create.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/delete.ts b/x-pack/plugins/entity_manager/server/routes/entities/delete.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/routes/entities/delete.ts
rename to x-pack/plugins/entity_manager/server/routes/entities/delete.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/get.ts b/x-pack/plugins/entity_manager/server/routes/entities/get.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/routes/entities/get.ts
rename to x-pack/plugins/entity_manager/server/routes/entities/get.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/index.ts b/x-pack/plugins/entity_manager/server/routes/entities/index.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/routes/entities/index.ts
rename to x-pack/plugins/entity_manager/server/routes/entities/index.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/reset.ts b/x-pack/plugins/entity_manager/server/routes/entities/reset.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/routes/entities/reset.ts
rename to x-pack/plugins/entity_manager/server/routes/entities/reset.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/update.ts b/x-pack/plugins/entity_manager/server/routes/entities/update.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/routes/entities/update.ts
rename to x-pack/plugins/entity_manager/server/routes/entities/update.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/routes/index.ts b/x-pack/plugins/entity_manager/server/routes/index.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/routes/index.ts
rename to x-pack/plugins/entity_manager/server/routes/index.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/routes/types.ts b/x-pack/plugins/entity_manager/server/routes/types.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/routes/types.ts
rename to x-pack/plugins/entity_manager/server/routes/types.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/saved_objects/entity_definition.ts b/x-pack/plugins/entity_manager/server/saved_objects/entity_definition.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/saved_objects/entity_definition.ts
rename to x-pack/plugins/entity_manager/server/saved_objects/entity_definition.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/saved_objects/entity_discovery_api_key.ts b/x-pack/plugins/entity_manager/server/saved_objects/entity_discovery_api_key.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/saved_objects/entity_discovery_api_key.ts
rename to x-pack/plugins/entity_manager/server/saved_objects/entity_discovery_api_key.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/saved_objects/index.ts b/x-pack/plugins/entity_manager/server/saved_objects/index.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/saved_objects/index.ts
rename to x-pack/plugins/entity_manager/server/saved_objects/index.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/templates/components/base_history.ts b/x-pack/plugins/entity_manager/server/templates/components/base_history.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/templates/components/base_history.ts
rename to x-pack/plugins/entity_manager/server/templates/components/base_history.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/templates/components/base_latest.ts b/x-pack/plugins/entity_manager/server/templates/components/base_latest.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/templates/components/base_latest.ts
rename to x-pack/plugins/entity_manager/server/templates/components/base_latest.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/templates/components/entity.ts b/x-pack/plugins/entity_manager/server/templates/components/entity.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/templates/components/entity.ts
rename to x-pack/plugins/entity_manager/server/templates/components/entity.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/templates/components/event.ts b/x-pack/plugins/entity_manager/server/templates/components/event.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/templates/components/event.ts
rename to x-pack/plugins/entity_manager/server/templates/components/event.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/templates/components/helpers.test.ts b/x-pack/plugins/entity_manager/server/templates/components/helpers.test.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/templates/components/helpers.test.ts
rename to x-pack/plugins/entity_manager/server/templates/components/helpers.test.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/templates/components/helpers.ts b/x-pack/plugins/entity_manager/server/templates/components/helpers.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/templates/components/helpers.ts
rename to x-pack/plugins/entity_manager/server/templates/components/helpers.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/server/types.ts b/x-pack/plugins/entity_manager/server/types.ts
similarity index 100%
rename from x-pack/plugins/observability_solution/entity_manager/server/types.ts
rename to x-pack/plugins/entity_manager/server/types.ts
diff --git a/x-pack/plugins/observability_solution/entity_manager/tsconfig.json b/x-pack/plugins/entity_manager/tsconfig.json
similarity index 90%
rename from x-pack/plugins/observability_solution/entity_manager/tsconfig.json
rename to x-pack/plugins/entity_manager/tsconfig.json
index 7d1c0f378854f..29c100ee4c9d2 100644
--- a/x-pack/plugins/observability_solution/entity_manager/tsconfig.json
+++ b/x-pack/plugins/entity_manager/tsconfig.json
@@ -1,5 +1,5 @@
{
- "extends": "../../../../tsconfig.base.json",
+ "extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types"
},
@@ -10,7 +10,9 @@
"public/**/*",
"types/**/*"
],
- "exclude": ["target/**/*"],
+ "exclude": [
+ "target/**/*"
+ ],
"kbn_references": [
"@kbn/config-schema",
"@kbn/entities-schema",
diff --git a/yarn.lock b/yarn.lock
index b86acda2e0739..408d93ad5ee99 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4874,7 +4874,7 @@
version "0.0.0"
uid ""
-"@kbn/entityManager-plugin@link:x-pack/plugins/observability_solution/entity_manager":
+"@kbn/entityManager-plugin@link:x-pack/plugins/entity_manager":
version "0.0.0"
uid ""
From ca8e53fb3a85ba8abfcf9cc247cdcfcf30a79cef Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Efe=20G=C3=BCrkan=20YALAMAN?=
Date: Mon, 16 Sep 2024 14:31:38 +0200
Subject: [PATCH 013/139] [Search Playground] Search Preview and Document
Viewer (#192096)
## Summary
Adds Search preview functionality to Search Playground
Adds Unified Document Viewer flyout to the Search Playground
All of these code are under the same UI setting that we had for Search
Playground.
https://github.com/user-attachments/assets/cd590414-b22f-4cde-a7e1-ca139aefe178
This introduces new dependencies to the plugin, locally there was no
problem visible (i.e. no warnings or something weird in Kibana logs)
### Checklist
Delete any items that are not applicable to this PR.
- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [ ] [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
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [x] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [ ] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [x] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [x] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)
### Risk Matrix
| Risk | Probability | Severity | Mitigation/Notes |
|---------------------------|-------------|----------|-------------------------|
| Potential breaking in Chat Playground Need to be tested | Low | Medium
| It should be tested by at least another person feature flag off in
Chat playground both Stack and Serverless |
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
---
.../plugins/search_playground/common/index.ts | 8 ++
.../plugins/search_playground/common/types.ts | 7 +
x-pack/plugins/search_playground/kibana.jsonc | 4 +-
.../public/components/header.tsx | 2 +-
.../components/search_mode/empty_results.tsx | 34 +++++
.../components/search_mode/result_list.tsx | 133 +++++++++++++-----
.../components/search_mode/search_mode.tsx | 80 +++++++++--
.../public/components/toolbar.tsx | 7 +-
.../view_code/examples/dev_tools.tsx | 30 ++++
.../components/view_code/view_code_action.tsx | 10 +-
.../components/view_code/view_code_flyout.tsx | 67 +++++----
.../public/hooks/use_search_preview.ts | 87 ++++++++++++
.../public/providers/form_provider.test.tsx | 2 +
.../public/providers/form_provider.tsx | 10 +-
.../plugins/search_playground/public/types.ts | 8 +-
.../public/utils/pagination_helper.ts | 20 +++
.../search_playground/server/routes.ts | 65 +++++++++
.../plugins/search_playground/tsconfig.json | 6 +-
18 files changed, 493 insertions(+), 87 deletions(-)
create mode 100644 x-pack/plugins/search_playground/public/components/search_mode/empty_results.tsx
create mode 100644 x-pack/plugins/search_playground/public/components/view_code/examples/dev_tools.tsx
create mode 100644 x-pack/plugins/search_playground/public/hooks/use_search_preview.ts
create mode 100644 x-pack/plugins/search_playground/public/utils/pagination_helper.ts
diff --git a/x-pack/plugins/search_playground/common/index.ts b/x-pack/plugins/search_playground/common/index.ts
index 533dfa612ee44..95624681b6193 100644
--- a/x-pack/plugins/search_playground/common/index.ts
+++ b/x-pack/plugins/search_playground/common/index.ts
@@ -5,8 +5,16 @@
* 2.0.
*/
+import { Pagination } from './types';
+
export const PLUGIN_ID = 'searchPlayground';
export const PLUGIN_NAME = 'Playground';
export const PLUGIN_PATH = '/app/search_playground';
export const SEARCH_MODE_FEATURE_FLAG_ID = 'searchPlayground:searchModeEnabled';
+
+export const DEFAULT_PAGINATION: Pagination = {
+ from: 0,
+ size: 10,
+ total: 0,
+};
diff --git a/x-pack/plugins/search_playground/common/types.ts b/x-pack/plugins/search_playground/common/types.ts
index deeec8156a988..c0e3300fe7dff 100644
--- a/x-pack/plugins/search_playground/common/types.ts
+++ b/x-pack/plugins/search_playground/common/types.ts
@@ -50,6 +50,7 @@ export enum APIRoutes {
POST_CHAT_MESSAGE = '/internal/search_playground/chat',
POST_QUERY_SOURCE_FIELDS = '/internal/search_playground/query_source_fields',
GET_INDICES = '/internal/search_playground/indices',
+ POST_SEARCH_QUERY = '/internal/search_playground/search',
}
export enum LLMs {
@@ -82,3 +83,9 @@ export interface ModelProvider {
promptTokenLimit: number;
provider: LLMs;
}
+
+export interface Pagination {
+ from: number;
+ size: number;
+ total: number;
+}
diff --git a/x-pack/plugins/search_playground/kibana.jsonc b/x-pack/plugins/search_playground/kibana.jsonc
index f1913dbd0f345..e9dedf0fe716f 100644
--- a/x-pack/plugins/search_playground/kibana.jsonc
+++ b/x-pack/plugins/search_playground/kibana.jsonc
@@ -12,6 +12,7 @@
],
"requiredPlugins": [
"actions",
+ "data",
"encryptedSavedObjects",
"navigation",
"share",
@@ -25,7 +26,8 @@
"usageCollection",
],
"requiredBundles": [
- "kibanaReact"
+ "kibanaReact",
+ "unifiedDocViewer"
]
}
}
diff --git a/x-pack/plugins/search_playground/public/components/header.tsx b/x-pack/plugins/search_playground/public/components/header.tsx
index b5ef4b2b3024f..0d6d48a903462 100644
--- a/x-pack/plugins/search_playground/public/components/header.tsx
+++ b/x-pack/plugins/search_playground/public/components/header.tsx
@@ -115,7 +115,7 @@ export const Header: React.FC = ({
{showDocs && }
-
+
diff --git a/x-pack/plugins/search_playground/public/components/search_mode/empty_results.tsx b/x-pack/plugins/search_playground/public/components/search_mode/empty_results.tsx
new file mode 100644
index 0000000000000..ab5779e85ddd5
--- /dev/null
+++ b/x-pack/plugins/search_playground/public/components/search_mode/empty_results.tsx
@@ -0,0 +1,34 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+
+import { EuiEmptyPrompt } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
+export interface EmptyResultsArgs {
+ query?: string;
+}
+
+export const EmptyResults: React.FC = ({ query }) => {
+ return (
+
+ {query
+ ? i18n.translate('xpack.searchPlayground.resultList.emptyWithQuery.text', {
+ defaultMessage: 'No result found for: {query}',
+ values: { query },
+ })
+ : i18n.translate('xpack.searchPlayground.resultList.empty.text', {
+ defaultMessage: 'No results found',
+ })}
+
+ }
+ />
+ );
+};
diff --git a/x-pack/plugins/search_playground/public/components/search_mode/result_list.tsx b/x-pack/plugins/search_playground/public/components/search_mode/result_list.tsx
index ca6bc48549ada..02e1193e22332 100644
--- a/x-pack/plugins/search_playground/public/components/search_mode/result_list.tsx
+++ b/x-pack/plugins/search_playground/public/components/search_mode/result_list.tsx
@@ -5,54 +5,117 @@
* 2.0.
*/
+import React, { useEffect, useState } from 'react';
+
import {
EuiFlexGroup,
EuiFlexItem,
EuiHorizontalRule,
+ EuiPagination,
EuiPanel,
EuiText,
EuiTitle,
} from '@elastic/eui';
-import React from 'react';
-const DEMO_DATA = [
- { id: '123321', name: 'John Doe', age: 25 },
- { id: '123321', name: 'John Doe', age: 25 },
- { id: '123321', name: 'John Doe', age: 25 },
- { id: '123321', name: 'John Doe', age: 25 },
- { id: '123321', name: 'John Doe', age: 25 },
- { id: '123321', name: 'John Doe', age: 25 },
- { id: '123321', name: 'John Doe', age: 25 },
- { id: '123321', name: 'John Doe', age: 25 },
- { id: '123321', name: 'John Doe', age: 25 },
- { id: '123321', name: 'John Doe', age: 25 },
-];
+import { UnifiedDocViewerFlyout } from '@kbn/unified-doc-viewer-plugin/public';
+
+import type { DataView } from '@kbn/data-views-plugin/public';
+import type { EsHitRecord } from '@kbn/discover-utils/types';
+import type { SearchHit } from '@elastic/elasticsearch/lib/api/types';
+import { buildDataTableRecord } from '@kbn/discover-utils';
+import { i18n } from '@kbn/i18n';
+import { Pagination } from '../../types';
+import { getPageCounts } from '../../utils/pagination_helper';
+import { EmptyResults } from './empty_results';
+import { useKibana } from '../../hooks/use_kibana';
+
+export interface ResultListArgs {
+ searchResults: SearchHit[];
+ pagination: Pagination;
+ onPaginationChange: (nextPage: number) => void;
+ searchQuery?: string;
+}
-export const ResultList: React.FC = () => {
+export const ResultList: React.FC = ({
+ searchResults,
+ pagination,
+ onPaginationChange,
+ searchQuery = '',
+}) => {
+ const {
+ services: { data },
+ } = useKibana();
+ const [dataView, setDataView] = useState(null);
+ useEffect(() => {
+ data.dataViews.getDefaultDataView().then((d) => setDataView(d));
+ }, [data]);
+ const [flyoutDocId, setFlyoutDocId] = useState(undefined);
+ const { totalPage, page } = getPageCounts(pagination);
+ const hit =
+ flyoutDocId &&
+ buildDataTableRecord(searchResults.find((item) => item._id === flyoutDocId) as EsHitRecord);
return (
- {DEMO_DATA.map((item, index) => {
- return (
- <>
-
-
-
-
- {item.id}
-
-
-
-
- {item.name}
-
-
-
-
- {index !== DEMO_DATA.length - 1 && }
- >
- );
- })}
+ {searchResults.length === 0 && (
+
+
+
+ )}
+ {searchResults.length !== 0 &&
+ searchResults.map((item, index) => {
+ return (
+ <>
+ setFlyoutDocId(item._id)}
+ grow
+ >
+
+
+
+ ID:{item._id}
+
+
+
+
+
+ {i18n.translate('xpack.searchPlayground.resultList.result.score', {
+ defaultMessage: 'Document score: {score}',
+ values: { score: item._score },
+ })}
+
+
+
+
+
+ {index !== searchResults.length - 1 && }
+ >
+ );
+ })}
+ {searchResults.length !== 0 && (
+
+
+
+ )}
+ {flyoutDocId && dataView && hit && (
+ setFlyoutDocId(undefined)}
+ isEsqlQuery={false}
+ columns={[]}
+ hit={hit}
+ dataView={dataView}
+ onAddColumn={() => {}}
+ onRemoveColumn={() => {}}
+ setExpandedDoc={() => {}}
+ flyoutType="overlay"
+ />
+ )}
);
diff --git a/x-pack/plugins/search_playground/public/components/search_mode/search_mode.tsx b/x-pack/plugins/search_playground/public/components/search_mode/search_mode.tsx
index ed6b2ed70c188..967c5786eed63 100644
--- a/x-pack/plugins/search_playground/public/components/search_mode/search_mode.tsx
+++ b/x-pack/plugins/search_playground/public/components/search_mode/search_mode.tsx
@@ -6,20 +6,50 @@
*/
import {
- EuiButton,
EuiEmptyPrompt,
+ EuiFieldText,
EuiFlexGroup,
EuiFlexItem,
- EuiSearchBar,
+ EuiForm,
useEuiTheme,
} from '@elastic/eui';
import React from 'react';
import { css } from '@emotion/react';
+import { Controller, useController, useFormContext } from 'react-hook-form';
+import { i18n } from '@kbn/i18n';
+import { useQueryClient } from '@tanstack/react-query';
+import { DEFAULT_PAGINATION } from '../../../common';
import { ResultList } from './result_list';
+import { ChatForm, ChatFormFields, Pagination } from '../../types';
+import { useSearchPreview } from '../../hooks/use_search_preview';
+import { getPaginationFromPage } from '../../utils/pagination_helper';
export const SearchMode: React.FC = () => {
const { euiTheme } = useEuiTheme();
- const showResults = true; // TODO demo
+ const { control, handleSubmit } = useFormContext();
+ const {
+ field: { value: searchBarValue },
+ formState: { isSubmitting },
+ } = useController({
+ name: ChatFormFields.searchQuery,
+ });
+
+ const [searchQuery, setSearchQuery] = React.useState<{
+ query: string;
+ pagination: Pagination;
+ }>({ query: searchBarValue, pagination: DEFAULT_PAGINATION });
+
+ const { results, pagination } = useSearchPreview(searchQuery);
+
+ const queryClient = useQueryClient();
+ const handleSearch = async (query = searchBarValue, paginationParam = DEFAULT_PAGINATION) => {
+ queryClient.resetQueries({ queryKey: ['search-preview-results'] });
+ setSearchQuery({ query, pagination: paginationParam });
+ };
+
+ const onPagination = (page: number) => {
+ handleSearch(searchBarValue, getPaginationFromPage(page, pagination.size, pagination));
+ };
return (
@@ -31,25 +61,55 @@ export const SearchMode: React.FC = () => {
>
-
+ handleSearch())}>
+ (
+
+ )}
+ />
+
- {showResults ? (
-
+ {searchQuery.query ? (
+
) : (
Ready to search}
+ title={
+
+ {i18n.translate('xpack.searchPlayground.searchMode.readyToSearch', {
+ defaultMessage: 'Ready to search',
+ })}
+
+ }
body={
- Type in a query in the search bar above or view the query we automatically
- created for you.
+ {i18n.translate('xpack.searchPlayground.searchMode.searchPrompt', {
+ defaultMessage:
+ 'Type in a query in the search bar above or view the query we automatically created for you.',
+ })}
}
- actions={View the query}
/>
)}
diff --git a/x-pack/plugins/search_playground/public/components/toolbar.tsx b/x-pack/plugins/search_playground/public/components/toolbar.tsx
index 31ea3345cdcbe..80889bee833db 100644
--- a/x-pack/plugins/search_playground/public/components/toolbar.tsx
+++ b/x-pack/plugins/search_playground/public/components/toolbar.tsx
@@ -9,12 +9,15 @@ import { EuiFlexGroup } from '@elastic/eui';
import React from 'react';
import { DataActionButton } from './data_action_button';
import { ViewCodeAction } from './view_code/view_code_action';
+import { PlaygroundPageMode } from '../types';
-export const Toolbar: React.FC = () => {
+export const Toolbar: React.FC<{ selectedPageMode: PlaygroundPageMode }> = ({
+ selectedPageMode = PlaygroundPageMode.chat,
+}) => {
return (
-
+
);
};
diff --git a/x-pack/plugins/search_playground/public/components/view_code/examples/dev_tools.tsx b/x-pack/plugins/search_playground/public/components/view_code/examples/dev_tools.tsx
new file mode 100644
index 0000000000000..9059d58db821e
--- /dev/null
+++ b/x-pack/plugins/search_playground/public/components/view_code/examples/dev_tools.tsx
@@ -0,0 +1,30 @@
+/*
+ * 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 { EuiCodeBlock } from '@elastic/eui';
+import { useFormContext } from 'react-hook-form';
+import { ChatFormFields } from '../../../types';
+
+export const DevToolsCode: React.FC = () => {
+ const { getValues } = useFormContext();
+ const query = getValues(ChatFormFields.elasticsearchQuery) ?? '';
+ const indices = getValues(ChatFormFields.indices) ?? [];
+ const searchQuery = getValues(ChatFormFields.searchQuery) ?? '';
+ const replacedQuery = searchQuery
+ ? JSON.stringify(query, null, 2).replace(/\"{query}\"/g, JSON.stringify(searchQuery))
+ : JSON.stringify(query, null, 2);
+
+ return (
+
+ {`POST ${indices.join(',')}/_search
+${replacedQuery}
+`}
+
+ );
+};
diff --git a/x-pack/plugins/search_playground/public/components/view_code/view_code_action.tsx b/x-pack/plugins/search_playground/public/components/view_code/view_code_action.tsx
index 13b6a2119757a..768d176a337f0 100644
--- a/x-pack/plugins/search_playground/public/components/view_code/view_code_action.tsx
+++ b/x-pack/plugins/search_playground/public/components/view_code/view_code_action.tsx
@@ -9,17 +9,21 @@ import React, { useState } from 'react';
import { EuiButton } from '@elastic/eui';
import { useFormContext } from 'react-hook-form';
import { FormattedMessage } from '@kbn/i18n-react';
-import { ChatForm, ChatFormFields } from '../../types';
+import { ChatForm, ChatFormFields, PlaygroundPageMode } from '../../types';
import { ViewCodeFlyout } from './view_code_flyout';
-export const ViewCodeAction: React.FC = () => {
+export const ViewCodeAction: React.FC<{ selectedPageMode: PlaygroundPageMode }> = ({
+ selectedPageMode = PlaygroundPageMode.chat,
+}) => {
const { watch } = useFormContext();
const [showFlyout, setShowFlyout] = useState(false);
const selectedIndices = watch(ChatFormFields.indices);
return (
<>
- {showFlyout && setShowFlyout(false)} />}
+ {showFlyout && (
+ setShowFlyout(false)} />
+ )}
void;
+ selectedPageMode: PlaygroundPageMode;
}
export const ES_CLIENT_DETAILS = (cloud: CloudSetup | undefined) => {
@@ -50,7 +52,7 @@ es_client = Elasticsearch(
`;
};
-export const ViewCodeFlyout: React.FC = ({ onClose }) => {
+export const ViewCodeFlyout: React.FC = ({ onClose, selectedPageMode }) => {
const usageTracker = useUsageTracker();
const [selectedLanguage, setSelectedLanguage] = useState('py-es-client');
const { getValues } = useFormContext();
@@ -101,34 +103,43 @@ export const ViewCodeFlyout: React.FC = ({ onClose }) => {
-
-
-
-
-
-
-
+
+
-
-
-
+
+
+
+
+
+
+
+ )}
- {steps[selectedLanguage]}
+ {selectedPageMode === PlaygroundPageMode.chat && (
+ {steps[selectedLanguage]}
+ )}
+ {selectedPageMode === PlaygroundPageMode.search && (
+
+
+
+ )}
diff --git a/x-pack/plugins/search_playground/public/hooks/use_search_preview.ts b/x-pack/plugins/search_playground/public/hooks/use_search_preview.ts
new file mode 100644
index 0000000000000..54566563fcee5
--- /dev/null
+++ b/x-pack/plugins/search_playground/public/hooks/use_search_preview.ts
@@ -0,0 +1,87 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { SearchHit } from '@elastic/elasticsearch/lib/api/types';
+import { useQuery } from '@tanstack/react-query';
+import { useFormContext } from 'react-hook-form';
+import { APIRoutes, ChatForm, ChatFormFields, Pagination } from '../types';
+import { useKibana } from './use_kibana';
+import { DEFAULT_PAGINATION } from '../../common';
+
+export interface FetchSearchResultsArgs {
+ query: string;
+ pagination: Pagination;
+ indices: ChatForm[ChatFormFields.indices];
+ elasticsearchQuery: ChatForm[ChatFormFields.elasticsearchQuery];
+ http: ReturnType['services']['http'];
+}
+
+interface UseSearchPreviewData {
+ results: SearchHit[];
+ pagination: Pagination;
+}
+
+export interface UseSearchPreviewResponse {
+ fetchSearchResults: (args: FetchSearchResultsArgs) => Promise;
+ data: UseSearchPreviewData;
+}
+
+export const DEFAULT_SEARCH_PREVIEW_DATA: UseSearchPreviewData = {
+ results: [],
+ pagination: DEFAULT_PAGINATION,
+};
+
+export const fetchSearchResults = async ({
+ query,
+ indices,
+ elasticsearchQuery,
+ pagination: paginationParam = DEFAULT_PAGINATION,
+ http,
+}: FetchSearchResultsArgs): Promise => {
+ const { results, pagination: paginationResult } = await http.post<{
+ results: SearchHit[];
+ pagination: Pagination;
+ }>(APIRoutes.POST_SEARCH_QUERY, {
+ body: JSON.stringify({
+ search_query: query,
+ elasticsearch_query: JSON.stringify(elasticsearchQuery),
+ indices,
+ size: paginationParam.size,
+ from: paginationParam.from,
+ }),
+ });
+ return { results, pagination: paginationResult };
+};
+
+export const useSearchPreview = ({
+ query,
+ pagination,
+}: {
+ query: string;
+ pagination: Pagination;
+}) => {
+ const { services } = useKibana();
+ const { getValues } = useFormContext();
+ const { http } = services;
+ const indices = getValues(ChatFormFields.indices);
+ const elasticsearchQuery = getValues(ChatFormFields.elasticsearchQuery);
+
+ const { data } = useQuery({
+ queryKey: ['search-preview-results', query, indices, elasticsearchQuery, pagination],
+ queryFn: () => fetchSearchResults({ query, pagination, http, indices, elasticsearchQuery }),
+ initialData: DEFAULT_SEARCH_PREVIEW_DATA,
+ enabled: !!query,
+ refetchOnWindowFocus: false,
+ refetchOnReconnect: false,
+ refetchOnMount: false,
+ });
+
+ return {
+ pagination: data.pagination,
+ results: data.results,
+ };
+};
diff --git a/x-pack/plugins/search_playground/public/providers/form_provider.test.tsx b/x-pack/plugins/search_playground/public/providers/form_provider.test.tsx
index c946555e16f95..96c86ad184931 100644
--- a/x-pack/plugins/search_playground/public/providers/form_provider.test.tsx
+++ b/x-pack/plugins/search_playground/public/providers/form_provider.test.tsx
@@ -66,6 +66,7 @@ describe('FormProvider', () => {
doc_size: 3,
indices: [],
prompt: 'You are an assistant for question-answering tasks.',
+ search_query: '',
source_fields: {},
summarization_model: undefined,
});
@@ -171,6 +172,7 @@ describe('FormProvider', () => {
prompt: 'Loaded prompt',
doc_size: 3,
source_fields: {},
+ search_query: '',
indices: [],
summarization_model: undefined,
});
diff --git a/x-pack/plugins/search_playground/public/providers/form_provider.tsx b/x-pack/plugins/search_playground/public/providers/form_provider.tsx
index 1c804663af9e6..f352688fe89f1 100644
--- a/x-pack/plugins/search_playground/public/providers/form_provider.tsx
+++ b/x-pack/plugins/search_playground/public/providers/form_provider.tsx
@@ -37,8 +37,8 @@ const getLocalSession = (storage: Storage): PartialChatForm => {
};
const setLocalSession = (formState: PartialChatForm, storage: Storage) => {
- // omit question from the session state
- const { question, ...state } = formState;
+ // omit question and search_query from the session state
+ const { question, search_query: searchQuery, ...state } = formState;
storage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(state));
};
@@ -56,7 +56,11 @@ export const FormProvider: React.FC>
const index = useMemo(() => searchParams.get('default-index'), [searchParams]);
const sessionState = useMemo(() => getLocalSession(storage), [storage]);
const form = useForm({
- defaultValues: { ...sessionState, indices: index ? [index] : sessionState.indices },
+ defaultValues: {
+ ...sessionState,
+ indices: index ? [index] : sessionState.indices,
+ search_query: '',
+ },
});
useLoadFieldsByIndices({ watch: form.watch, setValue: form.setValue, getValues: form.getValues });
diff --git a/x-pack/plugins/search_playground/public/types.ts b/x-pack/plugins/search_playground/public/types.ts
index dd690c935312c..ff9c56ffb553e 100644
--- a/x-pack/plugins/search_playground/public/types.ts
+++ b/x-pack/plugins/search_playground/public/types.ts
@@ -21,6 +21,7 @@ import { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-
import { AppMountParameters } from '@kbn/core/public';
import { UsageCollectionStart } from '@kbn/usage-collection-plugin/public';
import type { ConsolePluginStart } from '@kbn/console-plugin/public';
+import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { ChatRequestData, MessageRole } from '../common/types';
import type { App } from './components/app';
import type { PlaygroundProvider as PlaygroundProviderComponent } from './providers/playground_provider';
@@ -52,6 +53,7 @@ export interface AppPluginStartDependencies {
triggersActionsUi: TriggersAndActionsUIPublicPluginStart;
share: SharePluginStart;
console?: ConsolePluginStart;
+ data: DataPublicPluginStart;
}
export interface AppServicesContext {
@@ -62,9 +64,7 @@ export interface AppServicesContext {
triggersActionsUi: TriggersAndActionsUIPublicPluginStart;
usageCollection?: UsageCollectionStart;
console?: ConsolePluginStart;
- featureFlags: {
- searchPlaygroundEnabled: boolean;
- };
+ data: DataPublicPluginStart;
}
export enum ChatFormFields {
@@ -77,6 +77,7 @@ export enum ChatFormFields {
sourceFields = 'source_fields',
docSize = 'doc_size',
queryFields = 'query_fields',
+ searchQuery = 'search_query',
}
export interface ChatForm {
@@ -89,6 +90,7 @@ export interface ChatForm {
[ChatFormFields.sourceFields]: { [index: string]: string[] };
[ChatFormFields.docSize]: number;
[ChatFormFields.queryFields]: { [index: string]: string[] };
+ [ChatFormFields.searchQuery]: string;
}
export interface Message {
diff --git a/x-pack/plugins/search_playground/public/utils/pagination_helper.ts b/x-pack/plugins/search_playground/public/utils/pagination_helper.ts
new file mode 100644
index 0000000000000..1379bbc257bd4
--- /dev/null
+++ b/x-pack/plugins/search_playground/public/utils/pagination_helper.ts
@@ -0,0 +1,20 @@
+/*
+ * 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 { Pagination } from '../../common/types';
+
+export const getPageCounts = (pagination: Pagination) => {
+ const { total, from, size } = pagination;
+ const totalPage = Math.ceil(total / size);
+ const page = Math.floor(from / size);
+ return { totalPage, total, page, size };
+};
+
+export const getPaginationFromPage = (page: number, size: number, previousValue: Pagination) => {
+ const from = page < 0 ? 0 : page * size;
+ return { ...previousValue, from, size, page };
+};
diff --git a/x-pack/plugins/search_playground/server/routes.ts b/x-pack/plugins/search_playground/server/routes.ts
index 8d7769020f445..d30904214d8df 100644
--- a/x-pack/plugins/search_playground/server/routes.ts
+++ b/x-pack/plugins/search_playground/server/routes.ts
@@ -240,4 +240,69 @@ export function defineRoutes({
});
})
);
+
+ router.post(
+ {
+ path: APIRoutes.POST_SEARCH_QUERY,
+ validate: {
+ body: schema.object({
+ search_query: schema.string(),
+ elasticsearch_query: schema.string(),
+ indices: schema.arrayOf(schema.string()),
+ size: schema.maybe(schema.number({ defaultValue: 10, min: 0 })),
+ from: schema.maybe(schema.number({ defaultValue: 0, min: 0 })),
+ }),
+ },
+ },
+ errorHandler(logger)(async (context, request, response) => {
+ const { client } = (await context.core).elasticsearch;
+ const { elasticsearch_query: elasticsearchQuery, indices, size, from } = request.body;
+
+ try {
+ if (indices.length === 0) {
+ return response.badRequest({
+ body: {
+ message: 'Indices cannot be empty',
+ },
+ });
+ }
+
+ const retriever = createRetriever(elasticsearchQuery)(request.body.search_query);
+ const searchResult = await client.asCurrentUser.search({
+ index: indices,
+ retriever: retriever.retriever,
+ from,
+ size,
+ });
+ const total = searchResult.hits.total
+ ? typeof searchResult.hits.total === 'object'
+ ? searchResult.hits.total.value
+ : searchResult.hits.total
+ : 0;
+
+ return response.ok({
+ body: {
+ results: searchResult.hits.hits,
+ pagination: {
+ from,
+ size,
+ total,
+ },
+ },
+ });
+ } catch (e) {
+ logger.error('Failed to search the query', e);
+
+ if (typeof e === 'object' && e.message) {
+ return response.badRequest({
+ body: {
+ message: e.message,
+ },
+ });
+ }
+
+ throw e;
+ }
+ })
+ );
}
diff --git a/x-pack/plugins/search_playground/tsconfig.json b/x-pack/plugins/search_playground/tsconfig.json
index dc481d35327f2..eebfd0df9a7b3 100644
--- a/x-pack/plugins/search_playground/tsconfig.json
+++ b/x-pack/plugins/search_playground/tsconfig.json
@@ -40,7 +40,11 @@
"@kbn/usage-collection-plugin",
"@kbn/console-plugin",
"@kbn/utility-types",
- "@kbn/kibana-utils-plugin"
+ "@kbn/kibana-utils-plugin",
+ "@kbn/unified-doc-viewer-plugin",
+ "@kbn/data-views-plugin",
+ "@kbn/discover-utils",
+ "@kbn/data-plugin"
],
"exclude": [
"target/**/*",
From 61fcb0f0722afab8f546315ae5e1dd75a730b741 Mon Sep 17 00:00:00 2001
From: Irene Blanco
Date: Mon, 16 Sep 2024 14:34:40 +0200
Subject: [PATCH 014/139] [APM[Services] Add custom events to new
ServiceTabEmptyState (#192385)
## Summary
Closes https://github.com/elastic/kibana/issues/190770.
With the new empty state added for certain tabs when a service only has
log signals, we're introducing telemetry to track its usage and impact.
The events being implemented have been defined
[here](https://github.com/elastic/observability-dev/issues/3462#issuecomment-2324980335)
and agreed upon with product.
|Button/Link|Sent event|
|-|-|
|Add APM||
|Try it||
|Learn more||
---------
Co-authored-by: Katerina
Co-authored-by: Caue Marcondes
---
.../components/app/service_overview/index.tsx | 4 +--
.../app/service_tab_empty_state/constants.ts | 2 +-
.../app/service_tab_empty_state/index.tsx | 35 ++++++++++++++++++-
.../services/telemetry/telemetry_client.ts | 9 +++++
.../services/telemetry/telemetry_events.ts | 28 +++++++++++++++
.../apm/public/services/telemetry/types.ts | 11 +++++-
6 files changed, 84 insertions(+), 5 deletions(-)
diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/service_overview/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/service_overview/index.tsx
index e406eba65f678..3475f622edebe 100644
--- a/x-pack/plugins/observability_solution/apm/public/components/app/service_overview/index.tsx
+++ b/x-pack/plugins/observability_solution/apm/public/components/app/service_overview/index.tsx
@@ -60,10 +60,10 @@ export function ServiceOverview() {
const hasSignal =
serviceEntitySummary?.dataStreamTypes && serviceEntitySummary?.dataStreamTypes?.length > 0;
- const hasLogsSignal = hasSignal && isLogsSignal(serviceEntitySummary.dataStreamTypes);
-
const hasLogsOnlySignal = hasSignal && isLogsOnlySignal(serviceEntitySummary.dataStreamTypes);
+ const hasLogsSignal = hasSignal && isLogsSignal(serviceEntitySummary.dataStreamTypes);
+
// Shows APM overview when entity has APM signal or when Entity centric is not enabled
const hasApmSignal = hasSignal && isApmSignal(serviceEntitySummary.dataStreamTypes);
diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/service_tab_empty_state/constants.ts b/x-pack/plugins/observability_solution/apm/public/components/app/service_tab_empty_state/constants.ts
index 8165ad991cfe2..8551745238e43 100644
--- a/x-pack/plugins/observability_solution/apm/public/components/app/service_tab_empty_state/constants.ts
+++ b/x-pack/plugins/observability_solution/apm/public/components/app/service_tab_empty_state/constants.ts
@@ -38,7 +38,7 @@ export const emptyStateDefinitions: Record = {
}),
content: i18n.translate('xpack.apm.serviceTabEmptyState.dependenciesContent', {
defaultMessage:
- 'See your services dependencies on both internal and third-party services by instrumenting with APM.',
+ "See your service's dependencies on both internal and third-party services by instrumenting with APM.",
}),
imgName: 'service_tab_empty_state_dependencies.png',
},
diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/service_tab_empty_state/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/service_tab_empty_state/index.tsx
index 8f4f86e2b92ed..a8962fcc1d2f7 100644
--- a/x-pack/plugins/observability_solution/apm/public/components/app/service_tab_empty_state/index.tsx
+++ b/x-pack/plugins/observability_solution/apm/public/components/app/service_tab_empty_state/index.tsx
@@ -4,6 +4,9 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
+
+/* eslint-disable @elastic/eui/href-or-on-click */
+
import {
EuiButton,
EuiButtonIcon,
@@ -19,6 +22,9 @@ import {
} from '@elastic/eui';
import React from 'react';
import { i18n } from '@kbn/i18n';
+import { useKibana } from '@kbn/kibana-react-plugin/public';
+import { EmptyStateClickParams, EntityInventoryAddDataParams } from '../../../services/telemetry';
+import { ApmPluginStartDeps, ApmServices } from '../../../plugin';
import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
import { useKibanaUrl } from '../../../hooks/use_kibana_url';
import { AddApmData } from '../../shared/add_data_buttons/buttons';
@@ -44,9 +50,17 @@ const learnMoreLink = {
};
const baseImgFolder = '/plugins/apm/assets/service_tab_empty_state';
+const defaultAddDataTelemetryParams: EntityInventoryAddDataParams = {
+ view: 'add_apm_cta',
+};
+
+const defaultClickTelemetryParams: EmptyStateClickParams = {
+ view: 'add_apm_cta',
+};
export function ServiceTabEmptyState({ id, onDissmiss }: ServiceTabEmptyStateProps) {
const { euiTheme } = useEuiTheme();
+ const { services } = useKibana();
const { core } = useApmPluginContext();
const imgFolder = `${baseImgFolder}/${
@@ -57,6 +71,18 @@ export function ServiceTabEmptyState({ id, onDissmiss }: ServiceTabEmptyStatePro
`${imgFolder}/${imgName ? imgName : 'service_tab_empty_state_overview.png'}`
);
+ function handleAddAPMClick() {
+ services.telemetry.reportEntityInventoryAddData(defaultAddDataTelemetryParams);
+ }
+
+ function handleTryItClick() {
+ services.telemetry.reportTryItClick(defaultClickTelemetryParams);
+ }
+
+ function handleLearnMoreClick() {
+ services.telemetry.reportLearnMoreClick(defaultClickTelemetryParams);
+ }
+
return (
<>
@@ -70,7 +96,12 @@ export function ServiceTabEmptyState({ id, onDissmiss }: ServiceTabEmptyStatePro
-
+
{tryItNowButton.label}
@@ -86,6 +118,7 @@ export function ServiceTabEmptyState({ id, onDissmiss }: ServiceTabEmptyStatePro
{
this.analytics.reportEvent(TelemetryEventTypes.ENTITY_INVENTORY_ADD_DATA, params);
};
+
+ public reportTryItClick = (params: EmptyStateClickParams) => {
+ this.analytics.reportEvent(TelemetryEventTypes.TRY_IT_CLICK, params);
+ };
+
+ public reportLearnMoreClick = (params: EmptyStateClickParams) => {
+ this.analytics.reportEvent(TelemetryEventTypes.LEARN_MORE_CLICK, params);
+ };
}
diff --git a/x-pack/plugins/observability_solution/apm/public/services/telemetry/telemetry_events.ts b/x-pack/plugins/observability_solution/apm/public/services/telemetry/telemetry_events.ts
index 9cc3cc772a621..2d00970a2b128 100644
--- a/x-pack/plugins/observability_solution/apm/public/services/telemetry/telemetry_events.ts
+++ b/x-pack/plugins/observability_solution/apm/public/services/telemetry/telemetry_events.ts
@@ -78,9 +78,37 @@ const entityInventoryAddDataEventType: TelemetryEvent = {
},
};
+const tryItClickEventType: TelemetryEvent = {
+ eventType: TelemetryEventTypes.TRY_IT_CLICK,
+ schema: {
+ view: {
+ type: 'keyword',
+ _meta: {
+ description:
+ 'Where the action was initiated (empty_state or add_data_button or add_apm_cta)',
+ },
+ },
+ },
+};
+
+const learnMoreClickEventType: TelemetryEvent = {
+ eventType: TelemetryEventTypes.LEARN_MORE_CLICK,
+ schema: {
+ view: {
+ type: 'keyword',
+ _meta: {
+ description:
+ 'Where the action was initiated (empty_state or add_data_button or add_apm_cta)',
+ },
+ },
+ },
+};
+
export const apmTelemetryEventBasedTypes = [
searchQuerySubmittedEventType,
entityExperienceStatusEventType,
entityInventoryPageStateEventType,
entityInventoryAddDataEventType,
+ tryItClickEventType,
+ learnMoreClickEventType,
];
diff --git a/x-pack/plugins/observability_solution/apm/public/services/telemetry/types.ts b/x-pack/plugins/observability_solution/apm/public/services/telemetry/types.ts
index 173831bbf0d7c..665f3c59d7612 100644
--- a/x-pack/plugins/observability_solution/apm/public/services/telemetry/types.ts
+++ b/x-pack/plugins/observability_solution/apm/public/services/telemetry/types.ts
@@ -34,17 +34,24 @@ export interface EntityInventoryAddDataParams {
journey?: 'add_apm_agent' | 'associate_existing_service_logs' | 'collect_new_service_logs';
}
+export interface EmptyStateClickParams {
+ view: Extract;
+}
+
export type TelemetryEventParams =
| SearchQuerySubmittedParams
| EntityExperienceStatusParams
| EntityInventoryPageStateParams
- | EntityInventoryAddDataParams;
+ | EntityInventoryAddDataParams
+ | EmptyStateClickParams;
export interface ITelemetryClient {
reportSearchQuerySubmitted(params: SearchQuerySubmittedParams): void;
reportEntityExperienceStatusChange(params: EntityExperienceStatusParams): void;
reportEntityInventoryPageState(params: EntityInventoryPageStateParams): void;
reportEntityInventoryAddData(params: EntityInventoryAddDataParams): void;
+ reportTryItClick(params: EmptyStateClickParams): void;
+ reportLearnMoreClick(params: EmptyStateClickParams): void;
}
export enum TelemetryEventTypes {
@@ -52,6 +59,8 @@ export enum TelemetryEventTypes {
ENTITY_EXPERIENCE_STATUS = 'entity_experience_status',
ENTITY_INVENTORY_PAGE_STATE = 'entity_inventory_page_state',
ENTITY_INVENTORY_ADD_DATA = 'entity_inventory_add_data',
+ TRY_IT_CLICK = 'try_it_click',
+ LEARN_MORE_CLICK = 'learn_more_click',
}
export interface TelemetryEvent {
From a0973d600212096ac9e530c179a87c14b7409db2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mike=20C=C3=B4t=C3=A9?=
Date: Mon, 16 Sep 2024 08:36:57 -0400
Subject: [PATCH 015/139] Make task manager code use the same logger (#192574)
In this PR, I'm making the sub-loggers within task manager use the main
logger so we can observe the logs under
`log.logger:"plugin.taskManager"`. To preserve separation, I moved the
sub-logger name within a tag so we can still filter the logs via
`tags:"taskClaimer"`.
The wrapped_logger.ts file is copied from
`x-pack/plugins/alerting/server/task_runner/lib/task_runner_logger.ts`.
---------
Co-authored-by: Elastic Machine
---
.../lib/setup_test_servers.ts | 8 --
.../server/lib/wrapped_logger.test.ts | 98 +++++++++++++++++++
.../task_manager/server/lib/wrapped_logger.ts | 74 ++++++++++++++
.../server/metrics/metrics_stream.ts | 3 +-
.../server/queries/task_claiming.test.ts | 3 +-
.../server/queries/task_claiming.ts | 3 +-
.../task_manager/server/routes/metrics.ts | 11 +--
.../task_claimers/strategy_mget.test.ts | 33 ++++---
.../server/task_claimers/strategy_mget.ts | 22 ++---
.../server/task_running/task_runner.ts | 3 +-
10 files changed, 205 insertions(+), 53 deletions(-)
create mode 100644 x-pack/plugins/task_manager/server/lib/wrapped_logger.test.ts
create mode 100644 x-pack/plugins/task_manager/server/lib/wrapped_logger.ts
diff --git a/x-pack/plugins/task_manager/server/integration_tests/lib/setup_test_servers.ts b/x-pack/plugins/task_manager/server/integration_tests/lib/setup_test_servers.ts
index 5ec8e724ae819..6abcac64c1d13 100644
--- a/x-pack/plugins/task_manager/server/integration_tests/lib/setup_test_servers.ts
+++ b/x-pack/plugins/task_manager/server/integration_tests/lib/setup_test_servers.ts
@@ -21,14 +21,6 @@ function createRoot(settings = {}) {
name: 'plugins.taskManager',
level: 'all',
},
- {
- name: 'plugins.taskManager.metrics-debugger',
- level: 'warn',
- },
- {
- name: 'plugins.taskManager.metrics-subscribe-debugger',
- level: 'warn',
- },
],
},
},
diff --git a/x-pack/plugins/task_manager/server/lib/wrapped_logger.test.ts b/x-pack/plugins/task_manager/server/lib/wrapped_logger.test.ts
new file mode 100644
index 0000000000000..12857c3fef845
--- /dev/null
+++ b/x-pack/plugins/task_manager/server/lib/wrapped_logger.test.ts
@@ -0,0 +1,98 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { loggingSystemMock } from '@kbn/core/server/mocks';
+import { LogLevel, LogRecord } from '@kbn/logging';
+import { createWrappedLogger } from './wrapped_logger';
+
+describe('createWrappedLogger', () => {
+ test('should inject baseline tags into log messages', () => {
+ const logger: ReturnType =
+ loggingSystemMock.createLogger();
+ const taskRunnerLogger = createWrappedLogger({ logger, tags: ['tag-1', 'tag-2'] });
+
+ taskRunnerLogger.trace('test trace message', { tags: ['tag-3'] });
+ taskRunnerLogger.debug('test debug message', { tags: ['tag-4'] });
+ taskRunnerLogger.info('test info message', { tags: ['tag-5'] });
+ taskRunnerLogger.warn('test warn message', { tags: ['tag-6'] });
+ taskRunnerLogger.error('test error message', { tags: ['tag-7'] });
+ taskRunnerLogger.fatal('test fatal message', { tags: ['tag-8'] });
+
+ expect(logger.trace).toHaveBeenCalledWith('test trace message', {
+ tags: ['tag-1', 'tag-2', 'tag-3'],
+ });
+ expect(logger.debug).toHaveBeenCalledWith('test debug message', {
+ tags: ['tag-1', 'tag-2', 'tag-4'],
+ });
+ expect(logger.info).toHaveBeenCalledWith('test info message', {
+ tags: ['tag-1', 'tag-2', 'tag-5'],
+ });
+ expect(logger.warn).toHaveBeenCalledWith('test warn message', {
+ tags: ['tag-1', 'tag-2', 'tag-6'],
+ });
+ expect(logger.error).toHaveBeenCalledWith('test error message', {
+ tags: ['tag-1', 'tag-2', 'tag-7'],
+ });
+ expect(logger.fatal).toHaveBeenCalledWith('test fatal message', {
+ tags: ['tag-1', 'tag-2', 'tag-8'],
+ });
+ });
+
+ test('should pass through other meta fields', () => {
+ const logger: ReturnType =
+ loggingSystemMock.createLogger();
+ const taskRunnerLogger = createWrappedLogger({ logger, tags: ['tag-1', 'tag-2'] });
+
+ taskRunnerLogger.trace('test trace message', { labels: { foo: 'bar' } });
+ taskRunnerLogger.debug('test debug message', { tags: ['tag-4'], host: { cpu: { usage: 3 } } });
+ taskRunnerLogger.info('test info message');
+ taskRunnerLogger.warn('test warn message', { user: { email: 'abc@124.com' } });
+ taskRunnerLogger.error('test error message', { agent: { id: 'agent-1' } });
+ taskRunnerLogger.fatal('test fatal message');
+
+ expect(logger.trace).toHaveBeenCalledWith('test trace message', {
+ tags: ['tag-1', 'tag-2'],
+ labels: { foo: 'bar' },
+ });
+ expect(logger.debug).toHaveBeenCalledWith('test debug message', {
+ tags: ['tag-1', 'tag-2', 'tag-4'],
+ host: { cpu: { usage: 3 } },
+ });
+ expect(logger.info).toHaveBeenCalledWith('test info message', { tags: ['tag-1', 'tag-2'] });
+ expect(logger.warn).toHaveBeenCalledWith('test warn message', {
+ tags: ['tag-1', 'tag-2'],
+ user: { email: 'abc@124.com' },
+ });
+ expect(logger.error).toHaveBeenCalledWith('test error message', {
+ tags: ['tag-1', 'tag-2'],
+ agent: { id: 'agent-1' },
+ });
+ expect(logger.fatal).toHaveBeenCalledWith('test fatal message', { tags: ['tag-1', 'tag-2'] });
+ });
+
+ test('should pass through other functions', () => {
+ const logger: ReturnType =
+ loggingSystemMock.createLogger();
+ const taskRunnerLogger = createWrappedLogger({ logger, tags: ['tag-1', 'tag-2'] });
+
+ taskRunnerLogger.isLevelEnabled('debug');
+ expect(logger.isLevelEnabled).toHaveBeenCalledWith('debug');
+
+ taskRunnerLogger.get('prefix', 'another');
+ expect(logger.get).toHaveBeenCalledWith('prefix', 'another');
+
+ const logRecord: LogRecord = {
+ timestamp: new Date(),
+ level: LogLevel.Info,
+ context: 'context',
+ message: 'message',
+ pid: 1,
+ };
+ taskRunnerLogger.log(logRecord);
+ expect(logger.log).toHaveBeenCalledWith(logRecord);
+ });
+});
diff --git a/x-pack/plugins/task_manager/server/lib/wrapped_logger.ts b/x-pack/plugins/task_manager/server/lib/wrapped_logger.ts
new file mode 100644
index 0000000000000..a1182924d8bee
--- /dev/null
+++ b/x-pack/plugins/task_manager/server/lib/wrapped_logger.ts
@@ -0,0 +1,74 @@
+/*
+ * 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 { Logger, LogMeta } from '@kbn/core/server';
+import { LogLevelId, LogMessageSource, LogRecord } from '@kbn/logging';
+
+interface WrappedLoggerOpts {
+ logger: Logger;
+ tags: string[];
+}
+
+export function createWrappedLogger(opts: WrappedLoggerOpts): Logger {
+ return new WrappedLogger(opts);
+}
+
+class WrappedLogger implements Logger {
+ private loggerMetaTags: string[] = [];
+
+ constructor(private readonly opts: WrappedLoggerOpts) {
+ this.loggerMetaTags = opts.tags;
+ }
+
+ trace(message: LogMessageSource, meta?: Meta) {
+ this.opts.logger.trace(message, { ...meta, tags: this.combineTags(meta?.tags) });
+ }
+
+ debug(message: LogMessageSource, meta?: Meta) {
+ this.opts.logger.debug(message, { ...meta, tags: this.combineTags(meta?.tags) });
+ }
+
+ info(message: LogMessageSource, meta?: Meta) {
+ this.opts.logger.info(message, { ...meta, tags: this.combineTags(meta?.tags) });
+ }
+
+ warn(errorOrMessage: LogMessageSource | Error, meta?: Meta) {
+ this.opts.logger.warn(errorOrMessage, { ...meta, tags: this.combineTags(meta?.tags) });
+ }
+
+ error(errorOrMessage: LogMessageSource | Error, meta?: Meta) {
+ this.opts.logger.error(errorOrMessage, { ...meta, tags: this.combineTags(meta?.tags) });
+ }
+
+ fatal(errorOrMessage: LogMessageSource | Error, meta?: Meta) {
+ this.opts.logger.fatal(errorOrMessage, { ...meta, tags: this.combineTags(meta?.tags) });
+ }
+
+ log(record: LogRecord) {
+ this.opts.logger.log(record);
+ }
+
+ isLevelEnabled(level: LogLevelId): boolean {
+ return this.opts.logger.isLevelEnabled(level);
+ }
+
+ get(...childContextPaths: string[]): Logger {
+ return this.opts.logger.get(...childContextPaths);
+ }
+
+ private combineTags(tags?: string[] | string): string[] {
+ if (!tags) {
+ return this.loggerMetaTags;
+ }
+
+ if (typeof tags === 'string') {
+ return [...new Set([...this.loggerMetaTags, tags])];
+ }
+
+ return [...new Set([...this.loggerMetaTags, ...tags])];
+ }
+}
diff --git a/x-pack/plugins/task_manager/server/metrics/metrics_stream.ts b/x-pack/plugins/task_manager/server/metrics/metrics_stream.ts
index 2c56e84104b1d..b9df16b95f2d7 100644
--- a/x-pack/plugins/task_manager/server/metrics/metrics_stream.ts
+++ b/x-pack/plugins/task_manager/server/metrics/metrics_stream.ts
@@ -12,6 +12,7 @@ import { Logger } from '@kbn/core/server';
import { TaskLifecycleEvent, TaskPollingLifecycle } from '../polling_lifecycle';
import { TaskManagerConfig } from '../config';
import { AggregatedStatProvider } from '../lib/runtime_statistics_aggregator';
+import { createWrappedLogger } from '../lib/wrapped_logger';
import {
isTaskManagerStatEvent,
isTaskManagerMetricEvent,
@@ -52,7 +53,7 @@ export function createMetricsAggregators({
taskManagerMetricsCollector,
}: CreateMetricsAggregatorsOpts): AggregatedStatProvider {
const aggregators: AggregatedStatProvider[] = [];
- const debugLogger = logger.get('metrics-debugger');
+ const debugLogger = createWrappedLogger({ logger, tags: ['metrics-debugger'] });
if (taskPollingLifecycle) {
aggregators.push(
createAggregator({
diff --git a/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts b/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts
index c4b9b6dd5836e..437af8e007bdb 100644
--- a/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts
+++ b/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts
@@ -91,7 +91,8 @@ describe('TaskClaiming', () => {
});
expect(taskManagerLogger.warn).toHaveBeenCalledWith(
- 'Unknown task claiming strategy "non-default", falling back to update_by_query'
+ 'Unknown task claiming strategy "non-default", falling back to update_by_query',
+ { tags: ['taskClaiming'] }
);
});
diff --git a/x-pack/plugins/task_manager/server/queries/task_claiming.ts b/x-pack/plugins/task_manager/server/queries/task_claiming.ts
index f5ef18452509b..5116c25c38f3f 100644
--- a/x-pack/plugins/task_manager/server/queries/task_claiming.ts
+++ b/x-pack/plugins/task_manager/server/queries/task_claiming.ts
@@ -28,6 +28,7 @@ import {
getTaskClaimer,
} from '../task_claimers';
import { TaskPartitioner } from '../lib/task_partitioner';
+import { createWrappedLogger } from '../lib/wrapped_logger';
export type { ClaimOwnershipResult } from '../task_claimers';
export interface TaskClaimingOpts {
@@ -107,7 +108,7 @@ export class TaskClaiming {
this.maxAttempts = opts.maxAttempts;
this.taskStore = opts.taskStore;
this.getAvailableCapacity = opts.getAvailableCapacity;
- this.logger = opts.logger.get('taskClaiming');
+ this.logger = createWrappedLogger({ logger: opts.logger, tags: ['taskClaiming'] });
this.taskClaimingBatchesByType = this.partitionIntoClaimingBatches(this.definitions);
this.taskMaxAttempts = Object.fromEntries(this.normalizeMaxAttempts(this.definitions));
this.excludedTaskTypes = opts.excludedTaskTypes;
diff --git a/x-pack/plugins/task_manager/server/routes/metrics.ts b/x-pack/plugins/task_manager/server/routes/metrics.ts
index 490cb869ba109..808675f25818b 100644
--- a/x-pack/plugins/task_manager/server/routes/metrics.ts
+++ b/x-pack/plugins/task_manager/server/routes/metrics.ts
@@ -37,15 +37,12 @@ const QuerySchema = schema.object({
});
export function metricsRoute(params: MetricsRouteParams) {
- const { router, logger, metrics$, resetMetrics$, taskManagerId } = params;
+ const { router, metrics$, resetMetrics$, taskManagerId } = params;
- const debugLogger = logger.get(`metrics-debugger`);
- const additionalDebugLogger = logger.get(`metrics-subscribe-debugger`);
let lastMetrics: NodeMetrics | null = null;
metrics$.subscribe((metrics) => {
lastMetrics = { process_uuid: taskManagerId, timestamp: new Date().toISOString(), ...metrics };
- additionalDebugLogger.debug(() => `subscribed metrics ${JSON.stringify(metrics)}`);
});
router.get(
@@ -68,12 +65,6 @@ export function metricsRoute(params: MetricsRouteParams) {
req: KibanaRequest, unknown>,
res: KibanaResponseFactory
): Promise {
- debugLogger.debug(
- () =>
- `/api/task_manager/metrics route accessed with reset=${req.query.reset} - metrics ${
- lastMetrics ? JSON.stringify(lastMetrics) : 'not available'
- }`
- );
if (req.query.reset) {
resetMetrics$.next(true);
}
diff --git a/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.test.ts b/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.test.ts
index cd1433d2e4009..3919860d27061 100644
--- a/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.test.ts
+++ b/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.test.ts
@@ -387,7 +387,7 @@ describe('TaskClaiming', () => {
expect(taskManagerLogger.debug).toHaveBeenCalledWith(
'task claimer claimed: 3; stale: 0; conflicts: 0; missing: 0; capacity reached: 3; updateErrors: 0; getErrors: 0; removed: 0;',
- { tags: ['claimAvailableTasksMget'] }
+ { tags: ['taskClaiming', 'claimAvailableTasksMget'] }
);
expect(store.msearch.mock.calls[0][0]?.[0]).toMatchObject({
@@ -497,7 +497,7 @@ describe('TaskClaiming', () => {
expect(taskManagerLogger.debug).toHaveBeenCalledWith(
'task claimer claimed: 1; stale: 0; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 0; getErrors: 0; removed: 2;',
- { tags: ['claimAvailableTasksMget'] }
+ { tags: ['taskClaiming', 'claimAvailableTasksMget'] }
);
expect(store.msearch.mock.calls[0][0]?.[0]).toMatchObject({
@@ -604,11 +604,11 @@ describe('TaskClaiming', () => {
expect(taskManagerLogger.warn).toHaveBeenCalledWith(
'Error updating task id-2:task to mark as unrecognized during claim: {"type":"document_missing_exception","reason":"[5]: document missing","index_uuid":"aAsFqTI0Tc2W0LCWgPNrOA","shard":"0","index":".kibana_task_manager_8.16.0_001"}',
- { tags: ['claimAvailableTasksMget'] }
+ { tags: ['taskClaiming', 'claimAvailableTasksMget'] }
);
expect(taskManagerLogger.debug).toHaveBeenCalledWith(
'task claimer claimed: 1; stale: 0; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 0; getErrors: 0; removed: 1;',
- { tags: ['claimAvailableTasksMget'] }
+ { tags: ['taskClaiming', 'claimAvailableTasksMget'] }
);
expect(store.msearch.mock.calls[0][0]?.[0]).toMatchObject({
@@ -701,11 +701,11 @@ describe('TaskClaiming', () => {
expect(taskManagerLogger.warn).toHaveBeenCalledWith(
'Error updating tasks to mark as unrecognized during claim: Error: Oh no',
- { tags: ['claimAvailableTasksMget'] }
+ { tags: ['taskClaiming', 'claimAvailableTasksMget'] }
);
expect(taskManagerLogger.debug).toHaveBeenCalledWith(
'task claimer claimed: 1; stale: 0; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 0; getErrors: 0; removed: 0;',
- { tags: ['claimAvailableTasksMget'] }
+ { tags: ['taskClaiming', 'claimAvailableTasksMget'] }
);
expect(store.msearch.mock.calls[0][0]?.[0]).toMatchObject({
@@ -855,7 +855,7 @@ describe('TaskClaiming', () => {
expect(taskManagerLogger.debug).toHaveBeenCalledWith(
'task claimer claimed: 2; stale: 0; conflicts: 0; missing: 1; capacity reached: 0; updateErrors: 0; getErrors: 0; removed: 0;',
- { tags: ['claimAvailableTasksMget'] }
+ { tags: ['taskClaiming', 'claimAvailableTasksMget'] }
);
expect(store.msearch.mock.calls[0][0]?.[0]).toMatchObject({
@@ -948,7 +948,7 @@ describe('TaskClaiming', () => {
expect(taskManagerLogger.debug).toHaveBeenCalledWith(
'task claimer claimed: 2; stale: 0; conflicts: 0; missing: 1; capacity reached: 0; updateErrors: 0; getErrors: 0; removed: 0;',
- { tags: ['claimAvailableTasksMget'] }
+ { tags: ['taskClaiming', 'claimAvailableTasksMget'] }
);
expect(store.msearch.mock.calls[0][0]?.[0]).toMatchObject({
@@ -1041,7 +1041,7 @@ describe('TaskClaiming', () => {
expect(taskManagerLogger.debug).toHaveBeenCalledWith(
'task claimer claimed: 2; stale: 1; conflicts: 1; missing: 0; capacity reached: 0; updateErrors: 0; getErrors: 0; removed: 0;',
- { tags: ['claimAvailableTasksMget'] }
+ { tags: ['taskClaiming', 'claimAvailableTasksMget'] }
);
expect(store.msearch.mock.calls[0][0]?.[0]).toMatchObject({
@@ -1140,7 +1140,7 @@ describe('TaskClaiming', () => {
expect(taskManagerLogger.debug).toHaveBeenCalledWith(
'task claimer claimed: 4; stale: 0; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 0; getErrors: 0; removed: 0;',
- { tags: ['claimAvailableTasksMget'] }
+ { tags: ['taskClaiming', 'claimAvailableTasksMget'] }
);
expect(store.msearch.mock.calls[0][0]?.[0]).toMatchObject({
@@ -1271,11 +1271,11 @@ describe('TaskClaiming', () => {
expect(taskManagerLogger.debug).toHaveBeenCalledWith(
'task claimer claimed: 3; stale: 0; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 0; getErrors: 1; removed: 0;',
- { tags: ['claimAvailableTasksMget'] }
+ { tags: ['taskClaiming', 'claimAvailableTasksMget'] }
);
expect(taskManagerLogger.error).toHaveBeenCalledWith(
'Error getting full task id-2:task during claim: Oh no',
- { tags: ['claimAvailableTasksMget'] }
+ { tags: ['taskClaiming', 'claimAvailableTasksMget'] }
);
expect(store.msearch.mock.calls[0][0]?.[0]).toMatchObject({
@@ -1511,11 +1511,11 @@ describe('TaskClaiming', () => {
expect(taskManagerLogger.debug).toHaveBeenCalledWith(
'task claimer claimed: 3; stale: 0; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 1; getErrors: 0; removed: 0;',
- { tags: ['claimAvailableTasksMget'] }
+ { tags: ['taskClaiming', 'claimAvailableTasksMget'] }
);
expect(taskManagerLogger.error).toHaveBeenCalledWith(
'Error updating task id-2:task during claim: {"type":"document_missing_exception","reason":"[5]: document missing","index_uuid":"aAsFqTI0Tc2W0LCWgPNrOA","shard":"0","index":".kibana_task_manager_8.16.0_001"}',
- { tags: ['claimAvailableTasksMget'] }
+ { tags: ['taskClaiming', 'claimAvailableTasksMget'] }
);
expect(store.msearch.mock.calls[0][0]?.[0]).toMatchObject({
@@ -1644,7 +1644,7 @@ describe('TaskClaiming', () => {
expect(taskManagerLogger.debug).toHaveBeenCalledWith(
'task claimer claimed: 3; stale: 0; conflicts: 1; missing: 0; capacity reached: 0; updateErrors: 0; getErrors: 0; removed: 0;',
- { tags: ['claimAvailableTasksMget'] }
+ { tags: ['taskClaiming', 'claimAvailableTasksMget'] }
);
expect(taskManagerLogger.error).not.toHaveBeenCalled();
@@ -2000,7 +2000,8 @@ describe('TaskClaiming', () => {
] = claimedResults;
expect(taskManagerLogger.warn).toHaveBeenCalledWith(
- 'Background task node "test" has no assigned partitions, claiming against all partitions'
+ 'Background task node "test" has no assigned partitions, claiming against all partitions',
+ { tags: ['taskClaiming', 'claimAvailableTasksMget'] }
);
expect(query).toMatchInlineSnapshot(`
Object {
diff --git a/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.ts b/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.ts
index 1fd35d408a3ec..432d7f183ce39 100644
--- a/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.ts
+++ b/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.ts
@@ -15,6 +15,7 @@
import apm, { Logger } from 'elastic-apm-node';
import { Subject, Observable } from 'rxjs';
+import { createWrappedLogger } from '../lib/wrapped_logger';
import { TaskTypeDictionary } from '../task_type_dictionary';
import {
@@ -105,9 +106,7 @@ async function claimAvailableTasksApm(opts: TaskClaimerOpts): Promise {
const { getCapacity, claimOwnershipUntil, batches, events$, taskStore, taskPartitioner } = opts;
const { definitions, unusedTypes, excludedTaskTypes, taskMaxAttempts } = opts;
- const { logger } = opts;
- const loggerTag = claimAvailableTasksMget.name;
- const logMeta = { tags: [loggerTag] };
+ const logger = createWrappedLogger({ logger: opts.logger, tags: [claimAvailableTasksMget.name] });
const initialCapacity = getCapacity();
const stopTaskTimer = startTaskTimer();
@@ -227,10 +226,7 @@ async function claimAvailableTasks(opts: TaskClaimerOpts): Promise> {
const { task } = this.instance;
- const debugLogger = this.logger.get(`metrics-debugger`);
+ const debugLogger = createWrappedLogger({ logger: this.logger, tags: [`metrics-debugger`] });
const taskHasExpired = this.isExpired;
From 0a385f30fd9b9ab509b3be091d5e1add789cadeb Mon Sep 17 00:00:00 2001
From: Liam Thompson <32779855+leemthompo@users.noreply.github.com>
Date: Mon, 16 Sep 2024 14:18:22 +0100
Subject: [PATCH 016/139] [DOCS][Playground] Mention local model compatibility
(#192911)
Note about openai sdk compatible local models + links to examples
---
docs/playground/index.asciidoc | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/docs/playground/index.asciidoc b/docs/playground/index.asciidoc
index f475c3e2747a2..efb9b6261d8dd 100644
--- a/docs/playground/index.asciidoc
+++ b/docs/playground/index.asciidoc
@@ -89,6 +89,17 @@ a|
|===
+[[playground-local-llms]]
+[TIP]
+====
+You can also use locally hosted LLMs that are compatible with the OpenAI SDK.
+Once you've set up your LLM, you can connect to it using the OpenAI connector.
+Refer to the following for examples:
+
+* {security-guide}/connect-to-byo-llm.html[Using LM Studio]
+* https://www.elastic.co/search-labs/blog/localai-for-text-embeddings[LocalAI with `docker-compose`]
+====
+
[float]
[[playground-getting-started]]
== Getting started
@@ -101,13 +112,15 @@ image::get-started.png[width=600]
=== Connect to LLM provider
To get started with {x}, you need to create a <> for your LLM provider.
-Follow these steps on the {x} landing page:
+You can also connect to <> which are compatible with the OpenAI API, by using the OpenAI connector.
+
+To connect to an LLM provider, follow these steps on the {x} landing page:
. Under *Connect to an LLM*, click *Create connector*.
. Select your *LLM provider*.
. *Name* your connector.
. Select a *URL endpoint* (or use the default).
-. Enter *access credentials* for your LLM provider.
+. Enter *access credentials* for your LLM provider. (If you're running a locally hosted LLM using the OpenAI connector, you must input a value in the API key form, but the specific value doesn't matter.)
[TIP]
====
From f4587149987aa21ec9345e887bc7121d75792ea5 Mon Sep 17 00:00:00 2001
From: Alex Szabo
Date: Mon, 16 Sep 2024 15:18:49 +0200
Subject: [PATCH 017/139] [ci] skip tests that fail on chrome 128+ (#192830)
## Summary
Currently `google-chrome-stable` is pinned to `v127.x.x` as with
`v128.x.x` we get a few FTR breakages (some of them on visual
inaccuracies, some other).
We'd like to unpin chrome, and move on to 128, and start fixing these
test failures. So we're skipping the failures temporarily, bumping
chrome to 128, then allow for unskipping and fixing these.
---
test/functional/apps/dashboard/group5/embed_mode.ts | 6 ++++--
test/functional/apps/kibana_overview/_analytics.ts | 2 +-
x-pack/test/functional/apps/lens/group6/workspace_size.ts | 3 ++-
.../apps/maps/group1/documents_source/search_hits.js | 3 ++-
4 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/test/functional/apps/dashboard/group5/embed_mode.ts b/test/functional/apps/dashboard/group5/embed_mode.ts
index 965f448b38a3f..5bba11d3b574a 100644
--- a/test/functional/apps/dashboard/group5/embed_mode.ts
+++ b/test/functional/apps/dashboard/group5/embed_mode.ts
@@ -57,7 +57,8 @@ export default function ({
await browser.setWindowSize(1300, 900);
});
- describe('default URL params', () => {
+ // Fails in with chrome 128+ https://github.com/elastic/kibana/issues/163207
+ describe.skip('default URL params', () => {
it('hides the chrome', async () => {
const globalNavShown = await globalNav.exists();
expect(globalNavShown).to.be(true);
@@ -91,7 +92,8 @@ export default function ({
});
});
- describe('non-default URL params', () => {
+ // Fails in with chrome 128+ https://github.com/elastic/kibana/issues/163207
+ describe.skip('non-default URL params', () => {
it('shows or hides elements based on URL params', async () => {
const currentUrl = await browser.getCurrentUrl();
const newUrl = [currentUrl].concat(urlParamExtensions).join('&');
diff --git a/test/functional/apps/kibana_overview/_analytics.ts b/test/functional/apps/kibana_overview/_analytics.ts
index b8c7e9ac47f8c..bd504e4d7a829 100644
--- a/test/functional/apps/kibana_overview/_analytics.ts
+++ b/test/functional/apps/kibana_overview/_analytics.ts
@@ -17,7 +17,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const kibanaServer = getService('kibanaServer');
const PageObjects = getPageObjects(['common', 'header']);
- // Failing: See https://github.com/elastic/kibana/issues/192509
+ // Fails in chrome 128+: See https://github.com/elastic/kibana/issues/192509
describe.skip('overview page - Analytics apps', function describeIndexTests() {
before(async () => {
await esArchiver.load('test/functional/fixtures/es_archiver/logstash_functional');
diff --git a/x-pack/test/functional/apps/lens/group6/workspace_size.ts b/x-pack/test/functional/apps/lens/group6/workspace_size.ts
index cda1631873033..9c5e7fec773f0 100644
--- a/x-pack/test/functional/apps/lens/group6/workspace_size.ts
+++ b/x-pack/test/functional/apps/lens/group6/workspace_size.ts
@@ -268,7 +268,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await assertWorkspaceDimensions('600px', '375px');
});
- it('gauge size (absolute pixels) - major arc', async () => {
+ // Fails in chrome 128+
+ it.skip('gauge size (absolute pixels) - major arc', async () => {
await lens.openVisualOptions();
await lens.setGaugeShape('Major arc');
await assertWorkspaceDimensions('600px', '430px');
diff --git a/x-pack/test/functional/apps/maps/group1/documents_source/search_hits.js b/x-pack/test/functional/apps/maps/group1/documents_source/search_hits.js
index 488f9e3ab9e5a..f9e856d0d0ee4 100644
--- a/x-pack/test/functional/apps/maps/group1/documents_source/search_hits.js
+++ b/x-pack/test/functional/apps/maps/group1/documents_source/search_hits.js
@@ -108,7 +108,8 @@ export default function ({ getPageObjects, getService }) {
expect(hits).to.equal('2');
});
- it('should apply layer query to fit to bounds', async () => {
+ // Fails in chrome 128+: https://github.com/elastic/kibana/issues/175378
+ it.skip('should apply layer query to fit to bounds', async () => {
// Set view to other side of world so no matching results
await maps.setView(-15, -100, 6);
await maps.clickFitToBounds('logstash');
From 7a0fe2e83683cf38c537694daa88a3a75bfe3e57 Mon Sep 17 00:00:00 2001
From: Ying Mao
Date: Mon, 16 Sep 2024 09:22:44 -0400
Subject: [PATCH 018/139] [Response Ops][Alerting] Creating global service for
fetching and caching rules settings (#192404)
Resolves https://github.com/elastic/kibana/issues/184321 and
https://github.com/elastic/kibana/issues/149884
## Summary
Created a `RulesSettingsService` that caches flapping and query delay
settings per space for 1 minute so rules are not accessing the SO index
with every execution.
Also moved all classes related to the rules settings into the
`rules_settings` folder so some of these changes are just renaming
changes.
## To Verify
1. Add some logging to
`x-pack/plugins/alerting/server/rules_settings/rules_settings_service.ts`
to indicate when settings are being fetched and when they're returning
from the cache.
2. Create and run some rules in different spaces to see that flapping
and query delay settings are being returned from the cache when the
cache has not expired.
---------
Co-authored-by: Elastic Machine
---
x-pack/plugins/alerting/server/config.test.ts | 3 +
x-pack/plugins/alerting/server/config.ts | 4 +
x-pack/plugins/alerting/server/plugin.ts | 48 ++--
.../server/routes/_mock_handler_arguments.ts | 5 +-
.../routes/get_flapping_settings.test.ts | 5 +-
.../apis/get/get_query_delay_settings.test.ts | 2 +-
.../update_query_delay_settings.test.ts | 2 +-
.../routes/update_flapping_settings.test.ts | 5 +-
.../rules_settings_flapping_client.test.ts | 0
.../rules_settings_flapping_client.ts | 0
.../index.ts | 3 +
.../rules_settings_query_delay_client.test.ts | 0
.../rules_settings_query_delay_client.ts | 0
.../rules_settings_client.mock.ts | 2 +-
.../rules_settings_client.test.ts | 0
.../rules_settings_client.ts | 0
.../rules_settings_client_factory.test.ts | 2 +-
.../rules_settings_client_factory.ts | 2 +-
.../rules_settings_feature.test.ts | 0
.../rules_settings_feature.ts | 2 +-
.../rules_settings_service.mock.ts | 23 ++
.../rules_settings_service.test.ts | 241 ++++++++++++++++++
.../rules_settings/rules_settings_service.ts | 112 ++++++++
.../schemas/flapping_schema.ts | 0
.../schemas/index.ts | 0
.../schemas/query_delay_schema.ts | 0
.../task_runner/ad_hoc_task_runner.test.ts | 11 +-
.../server/task_runner/task_runner.test.ts | 56 ++--
.../server/task_runner/task_runner.ts | 11 +-
.../task_runner_alerts_client.test.ts | 56 ++--
.../task_runner/task_runner_cancel.test.ts | 58 ++---
.../task_runner/task_runner_factory.test.ts | 45 ++--
.../alerting/server/task_runner/types.ts | 6 +-
.../alerting/server/test_utils/index.ts | 1 +
x-pack/plugins/alerting/server/types.ts | 2 +-
.../alerting_api_integration/common/config.ts | 1 +
.../group4/alerts_as_data/alerts_as_data.ts | 3 +
.../alerts_as_data/alerts_as_data_flapping.ts | 9 +
38 files changed, 563 insertions(+), 157 deletions(-)
rename x-pack/plugins/alerting/server/{rules_settings_client => rules_settings}/flapping/rules_settings_flapping_client.test.ts (100%)
rename x-pack/plugins/alerting/server/{rules_settings_client => rules_settings}/flapping/rules_settings_flapping_client.ts (100%)
rename x-pack/plugins/alerting/server/{rules_settings_client => rules_settings}/index.ts (75%)
rename x-pack/plugins/alerting/server/{rules_settings_client => rules_settings}/query_delay/rules_settings_query_delay_client.test.ts (100%)
rename x-pack/plugins/alerting/server/{rules_settings_client => rules_settings}/query_delay/rules_settings_query_delay_client.ts (100%)
rename x-pack/plugins/alerting/server/{ => rules_settings}/rules_settings_client.mock.ts (98%)
rename x-pack/plugins/alerting/server/{rules_settings_client => rules_settings}/rules_settings_client.test.ts (100%)
rename x-pack/plugins/alerting/server/{rules_settings_client => rules_settings}/rules_settings_client.ts (100%)
rename x-pack/plugins/alerting/server/{ => rules_settings}/rules_settings_client_factory.test.ts (98%)
rename x-pack/plugins/alerting/server/{ => rules_settings}/rules_settings_client_factory.ts (97%)
rename x-pack/plugins/alerting/server/{ => rules_settings}/rules_settings_feature.test.ts (100%)
rename x-pack/plugins/alerting/server/{ => rules_settings}/rules_settings_feature.ts (99%)
create mode 100644 x-pack/plugins/alerting/server/rules_settings/rules_settings_service.mock.ts
create mode 100644 x-pack/plugins/alerting/server/rules_settings/rules_settings_service.test.ts
create mode 100644 x-pack/plugins/alerting/server/rules_settings/rules_settings_service.ts
rename x-pack/plugins/alerting/server/{rules_settings_client => rules_settings}/schemas/flapping_schema.ts (100%)
rename x-pack/plugins/alerting/server/{rules_settings_client => rules_settings}/schemas/index.ts (100%)
rename x-pack/plugins/alerting/server/{rules_settings_client => rules_settings}/schemas/query_delay_schema.ts (100%)
diff --git a/x-pack/plugins/alerting/server/config.test.ts b/x-pack/plugins/alerting/server/config.test.ts
index 9e2d2d089ac62..fe5657bc7f0ea 100644
--- a/x-pack/plugins/alerting/server/config.test.ts
+++ b/x-pack/plugins/alerting/server/config.test.ts
@@ -37,6 +37,9 @@ describe('config validation', () => {
},
},
},
+ "rulesSettings": Object {
+ "cacheInterval": 60000,
+ },
}
`);
});
diff --git a/x-pack/plugins/alerting/server/config.ts b/x-pack/plugins/alerting/server/config.ts
index ccacc6440b03a..cbe1328bdef5f 100644
--- a/x-pack/plugins/alerting/server/config.ts
+++ b/x-pack/plugins/alerting/server/config.ts
@@ -7,6 +7,7 @@
import { schema, TypeOf } from '@kbn/config-schema';
import { validateDurationSchema, parseDuration } from './lib';
+import { DEFAULT_CACHE_INTERVAL_MS } from './rules_settings';
export const DEFAULT_MAX_ALERTS = 1000;
const ONE_DAY_IN_MS = 24 * 60 * 60 * 1000;
@@ -74,6 +75,9 @@ export const configSchema = schema.object({
enableFrameworkAlerts: schema.boolean({ defaultValue: true }),
cancelAlertsOnRuleTimeout: schema.boolean({ defaultValue: true }),
rules: rulesSchema,
+ rulesSettings: schema.object({
+ cacheInterval: schema.number({ defaultValue: DEFAULT_CACHE_INTERVAL_MS }),
+ }),
});
export type AlertingConfig = TypeOf;
diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts
index 447dab2b94715..a7595eb4c5bac 100644
--- a/x-pack/plugins/alerting/server/plugin.ts
+++ b/x-pack/plugins/alerting/server/plugin.ts
@@ -66,7 +66,11 @@ import { ServerlessPluginSetup } from '@kbn/serverless/server';
import { RuleTypeRegistry } from './rule_type_registry';
import { TaskRunnerFactory } from './task_runner';
import { RulesClientFactory } from './rules_client_factory';
-import { RulesSettingsClientFactory } from './rules_settings_client_factory';
+import {
+ RulesSettingsClientFactory,
+ RulesSettingsService,
+ getRulesSettingsFeature,
+} from './rules_settings';
import { MaintenanceWindowClientFactory } from './maintenance_window_client_factory';
import { ILicenseState, LicenseState } from './lib/license_state';
import { AlertingRequestHandlerContext, ALERTING_FEATURE_ID, RuleAlertData } from './types';
@@ -106,7 +110,6 @@ import {
type InitializationPromise,
errorResult,
} from './alerts_service';
-import { getRulesSettingsFeature } from './rules_settings_feature';
import { maintenanceWindowFeature } from './maintenance_window_feature';
import { ConnectorAdapterRegistry } from './connector_adapters/connector_adapter_registry';
import { ConnectorAdapter, ConnectorAdapterParams } from './connector_adapters/types';
@@ -588,33 +591,38 @@ export class AlertingPlugin {
};
taskRunnerFactory.initialize({
- logger,
+ actionsConfigMap: getActionsConfigMap(this.config.rules.run.actions),
+ actionsPlugin: plugins.actions,
+ alertsService: this.alertsService,
+ backfillClient: this.backfillClient!,
+ basePathService: core.http.basePath,
+ cancelAlertsOnRuleTimeout: this.config.cancelAlertsOnRuleTimeout,
+ connectorAdapterRegistry: this.connectorAdapterRegistry,
data: plugins.data,
- share: plugins.share,
dataViews: plugins.dataViews,
- savedObjects: core.savedObjects,
- uiSettings: core.uiSettings,
elasticsearch: core.elasticsearch,
- getRulesClientWithRequest,
- spaceIdToNamespace,
- actionsPlugin: plugins.actions,
encryptedSavedObjectsClient,
- basePathService: core.http.basePath,
eventLogger: this.eventLogger!,
executionContext: core.executionContext,
- ruleTypeRegistry: this.ruleTypeRegistry!,
- alertsService: this.alertsService,
+ getMaintenanceWindowClientWithRequest,
+ getRulesClientWithRequest,
kibanaBaseUrl: this.kibanaBaseUrl,
- supportsEphemeralTasks: plugins.taskManager.supportsEphemeralTasks(),
- maxEphemeralActionsPerRule: this.config.maxEphemeralActionsPerAlert,
- cancelAlertsOnRuleTimeout: this.config.cancelAlertsOnRuleTimeout,
+ logger,
maxAlerts: this.config.rules.run.alerts.max,
- actionsConfigMap: getActionsConfigMap(this.config.rules.run.actions),
+ maxEphemeralActionsPerRule: this.config.maxEphemeralActionsPerAlert,
+ ruleTypeRegistry: this.ruleTypeRegistry!,
+ rulesSettingsService: new RulesSettingsService({
+ cacheInterval: this.config.rulesSettings.cacheInterval,
+ getRulesSettingsClientWithRequest,
+ isServerless: !!plugins.serverless,
+ logger,
+ }),
+ savedObjects: core.savedObjects,
+ share: plugins.share,
+ spaceIdToNamespace,
+ supportsEphemeralTasks: plugins.taskManager.supportsEphemeralTasks(),
+ uiSettings: core.uiSettings,
usageCounter: this.usageCounter,
- getRulesSettingsClientWithRequest,
- getMaintenanceWindowClientWithRequest,
- backfillClient: this.backfillClient!,
- connectorAdapterRegistry: this.connectorAdapterRegistry,
});
this.eventLogService!.registerSavedObjectProvider(RULE_SAVED_OBJECT_TYPE, (request) => {
diff --git a/x-pack/plugins/alerting/server/routes/_mock_handler_arguments.ts b/x-pack/plugins/alerting/server/routes/_mock_handler_arguments.ts
index cab10e242cdad..b2f25d349ec7f 100644
--- a/x-pack/plugins/alerting/server/routes/_mock_handler_arguments.ts
+++ b/x-pack/plugins/alerting/server/routes/_mock_handler_arguments.ts
@@ -12,7 +12,10 @@ import { httpServerMock } from '@kbn/core/server/mocks';
import { actionsClientMock } from '@kbn/actions-plugin/server/mocks';
import type { ActionsClientMock } from '@kbn/actions-plugin/server/mocks';
import { rulesClientMock, RulesClientMock } from '../rules_client.mock';
-import { rulesSettingsClientMock, RulesSettingsClientMock } from '../rules_settings_client.mock';
+import {
+ rulesSettingsClientMock,
+ RulesSettingsClientMock,
+} from '../rules_settings/rules_settings_client.mock';
import {
maintenanceWindowClientMock,
MaintenanceWindowClientMock,
diff --git a/x-pack/plugins/alerting/server/routes/get_flapping_settings.test.ts b/x-pack/plugins/alerting/server/routes/get_flapping_settings.test.ts
index 80354da80b784..b1353a6e328db 100644
--- a/x-pack/plugins/alerting/server/routes/get_flapping_settings.test.ts
+++ b/x-pack/plugins/alerting/server/routes/get_flapping_settings.test.ts
@@ -8,7 +8,10 @@
import { httpServiceMock } from '@kbn/core/server/mocks';
import { licenseStateMock } from '../lib/license_state.mock';
import { mockHandlerArguments } from './_mock_handler_arguments';
-import { rulesSettingsClientMock, RulesSettingsClientMock } from '../rules_settings_client.mock';
+import {
+ rulesSettingsClientMock,
+ RulesSettingsClientMock,
+} from '../rules_settings/rules_settings_client.mock';
import { getFlappingSettingsRoute } from './get_flapping_settings';
let rulesSettingsClient: RulesSettingsClientMock;
diff --git a/x-pack/plugins/alerting/server/routes/rules_settings/apis/get/get_query_delay_settings.test.ts b/x-pack/plugins/alerting/server/routes/rules_settings/apis/get/get_query_delay_settings.test.ts
index 4102aa80b29af..e025fcf84a25b 100644
--- a/x-pack/plugins/alerting/server/routes/rules_settings/apis/get/get_query_delay_settings.test.ts
+++ b/x-pack/plugins/alerting/server/routes/rules_settings/apis/get/get_query_delay_settings.test.ts
@@ -11,7 +11,7 @@ import { mockHandlerArguments } from '../../../_mock_handler_arguments';
import {
rulesSettingsClientMock,
RulesSettingsClientMock,
-} from '../../../../rules_settings_client.mock';
+} from '../../../../rules_settings/rules_settings_client.mock';
import { getQueryDelaySettingsRoute } from './get_query_delay_settings';
let rulesSettingsClient: RulesSettingsClientMock;
diff --git a/x-pack/plugins/alerting/server/routes/rules_settings/apis/update/update_query_delay_settings.test.ts b/x-pack/plugins/alerting/server/routes/rules_settings/apis/update/update_query_delay_settings.test.ts
index 8a506809131ab..34d3c8dfefe08 100644
--- a/x-pack/plugins/alerting/server/routes/rules_settings/apis/update/update_query_delay_settings.test.ts
+++ b/x-pack/plugins/alerting/server/routes/rules_settings/apis/update/update_query_delay_settings.test.ts
@@ -11,7 +11,7 @@ import { mockHandlerArguments } from '../../../_mock_handler_arguments';
import {
rulesSettingsClientMock,
RulesSettingsClientMock,
-} from '../../../../rules_settings_client.mock';
+} from '../../../../rules_settings/rules_settings_client.mock';
import { updateQueryDelaySettingsRoute } from './update_query_delay_settings';
let rulesSettingsClient: RulesSettingsClientMock;
diff --git a/x-pack/plugins/alerting/server/routes/update_flapping_settings.test.ts b/x-pack/plugins/alerting/server/routes/update_flapping_settings.test.ts
index 84fb238b8509b..81ea7dd087b16 100644
--- a/x-pack/plugins/alerting/server/routes/update_flapping_settings.test.ts
+++ b/x-pack/plugins/alerting/server/routes/update_flapping_settings.test.ts
@@ -8,7 +8,10 @@
import { httpServiceMock } from '@kbn/core/server/mocks';
import { licenseStateMock } from '../lib/license_state.mock';
import { mockHandlerArguments } from './_mock_handler_arguments';
-import { rulesSettingsClientMock, RulesSettingsClientMock } from '../rules_settings_client.mock';
+import {
+ rulesSettingsClientMock,
+ RulesSettingsClientMock,
+} from '../rules_settings/rules_settings_client.mock';
import { updateFlappingSettingsRoute } from './update_flapping_settings';
let rulesSettingsClient: RulesSettingsClientMock;
diff --git a/x-pack/plugins/alerting/server/rules_settings_client/flapping/rules_settings_flapping_client.test.ts b/x-pack/plugins/alerting/server/rules_settings/flapping/rules_settings_flapping_client.test.ts
similarity index 100%
rename from x-pack/plugins/alerting/server/rules_settings_client/flapping/rules_settings_flapping_client.test.ts
rename to x-pack/plugins/alerting/server/rules_settings/flapping/rules_settings_flapping_client.test.ts
diff --git a/x-pack/plugins/alerting/server/rules_settings_client/flapping/rules_settings_flapping_client.ts b/x-pack/plugins/alerting/server/rules_settings/flapping/rules_settings_flapping_client.ts
similarity index 100%
rename from x-pack/plugins/alerting/server/rules_settings_client/flapping/rules_settings_flapping_client.ts
rename to x-pack/plugins/alerting/server/rules_settings/flapping/rules_settings_flapping_client.ts
diff --git a/x-pack/plugins/alerting/server/rules_settings_client/index.ts b/x-pack/plugins/alerting/server/rules_settings/index.ts
similarity index 75%
rename from x-pack/plugins/alerting/server/rules_settings_client/index.ts
rename to x-pack/plugins/alerting/server/rules_settings/index.ts
index fcbf30b0bcb6c..d1b0525bd3754 100644
--- a/x-pack/plugins/alerting/server/rules_settings_client/index.ts
+++ b/x-pack/plugins/alerting/server/rules_settings/index.ts
@@ -8,3 +8,6 @@
export * from './rules_settings_client';
export * from './flapping/rules_settings_flapping_client';
export * from './query_delay/rules_settings_query_delay_client';
+export * from './rules_settings_client_factory';
+export * from './rules_settings_feature';
+export * from './rules_settings_service';
diff --git a/x-pack/plugins/alerting/server/rules_settings_client/query_delay/rules_settings_query_delay_client.test.ts b/x-pack/plugins/alerting/server/rules_settings/query_delay/rules_settings_query_delay_client.test.ts
similarity index 100%
rename from x-pack/plugins/alerting/server/rules_settings_client/query_delay/rules_settings_query_delay_client.test.ts
rename to x-pack/plugins/alerting/server/rules_settings/query_delay/rules_settings_query_delay_client.test.ts
diff --git a/x-pack/plugins/alerting/server/rules_settings_client/query_delay/rules_settings_query_delay_client.ts b/x-pack/plugins/alerting/server/rules_settings/query_delay/rules_settings_query_delay_client.ts
similarity index 100%
rename from x-pack/plugins/alerting/server/rules_settings_client/query_delay/rules_settings_query_delay_client.ts
rename to x-pack/plugins/alerting/server/rules_settings/query_delay/rules_settings_query_delay_client.ts
diff --git a/x-pack/plugins/alerting/server/rules_settings_client.mock.ts b/x-pack/plugins/alerting/server/rules_settings/rules_settings_client.mock.ts
similarity index 98%
rename from x-pack/plugins/alerting/server/rules_settings_client.mock.ts
rename to x-pack/plugins/alerting/server/rules_settings/rules_settings_client.mock.ts
index 5e3479b10e0ab..cc7601a56fcd1 100644
--- a/x-pack/plugins/alerting/server/rules_settings_client.mock.ts
+++ b/x-pack/plugins/alerting/server/rules_settings/rules_settings_client.mock.ts
@@ -11,7 +11,7 @@ import {
RulesSettingsQueryDelayClientApi,
DEFAULT_FLAPPING_SETTINGS,
DEFAULT_QUERY_DELAY_SETTINGS,
-} from './types';
+} from '../types';
export type RulesSettingsClientMock = jest.Mocked;
export type RulesSettingsFlappingClientMock = jest.Mocked;
diff --git a/x-pack/plugins/alerting/server/rules_settings_client/rules_settings_client.test.ts b/x-pack/plugins/alerting/server/rules_settings/rules_settings_client.test.ts
similarity index 100%
rename from x-pack/plugins/alerting/server/rules_settings_client/rules_settings_client.test.ts
rename to x-pack/plugins/alerting/server/rules_settings/rules_settings_client.test.ts
diff --git a/x-pack/plugins/alerting/server/rules_settings_client/rules_settings_client.ts b/x-pack/plugins/alerting/server/rules_settings/rules_settings_client.ts
similarity index 100%
rename from x-pack/plugins/alerting/server/rules_settings_client/rules_settings_client.ts
rename to x-pack/plugins/alerting/server/rules_settings/rules_settings_client.ts
diff --git a/x-pack/plugins/alerting/server/rules_settings_client_factory.test.ts b/x-pack/plugins/alerting/server/rules_settings/rules_settings_client_factory.test.ts
similarity index 98%
rename from x-pack/plugins/alerting/server/rules_settings_client_factory.test.ts
rename to x-pack/plugins/alerting/server/rules_settings/rules_settings_client_factory.test.ts
index f107fbac3a541..8942fb31acd32 100644
--- a/x-pack/plugins/alerting/server/rules_settings_client_factory.test.ts
+++ b/x-pack/plugins/alerting/server/rules_settings/rules_settings_client_factory.test.ts
@@ -18,7 +18,7 @@ import {
} from '@kbn/core/server/mocks';
import { AuthenticatedUser } from '@kbn/security-plugin/common';
import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server';
-import { RULES_SETTINGS_SAVED_OBJECT_TYPE } from '../common';
+import { RULES_SETTINGS_SAVED_OBJECT_TYPE } from '../../common';
jest.mock('./rules_settings_client');
diff --git a/x-pack/plugins/alerting/server/rules_settings_client_factory.ts b/x-pack/plugins/alerting/server/rules_settings/rules_settings_client_factory.ts
similarity index 97%
rename from x-pack/plugins/alerting/server/rules_settings_client_factory.ts
rename to x-pack/plugins/alerting/server/rules_settings/rules_settings_client_factory.ts
index 707c38b508c1a..a55529f995783 100644
--- a/x-pack/plugins/alerting/server/rules_settings_client_factory.ts
+++ b/x-pack/plugins/alerting/server/rules_settings/rules_settings_client_factory.ts
@@ -13,7 +13,7 @@ import {
SecurityServiceStart,
} from '@kbn/core/server';
import { RulesSettingsClient } from './rules_settings_client';
-import { RULES_SETTINGS_SAVED_OBJECT_TYPE } from '../common';
+import { RULES_SETTINGS_SAVED_OBJECT_TYPE } from '../../common';
export interface RulesSettingsClientFactoryOpts {
logger: Logger;
diff --git a/x-pack/plugins/alerting/server/rules_settings_feature.test.ts b/x-pack/plugins/alerting/server/rules_settings/rules_settings_feature.test.ts
similarity index 100%
rename from x-pack/plugins/alerting/server/rules_settings_feature.test.ts
rename to x-pack/plugins/alerting/server/rules_settings/rules_settings_feature.test.ts
diff --git a/x-pack/plugins/alerting/server/rules_settings_feature.ts b/x-pack/plugins/alerting/server/rules_settings/rules_settings_feature.ts
similarity index 99%
rename from x-pack/plugins/alerting/server/rules_settings_feature.ts
rename to x-pack/plugins/alerting/server/rules_settings/rules_settings_feature.ts
index 5087ae3eaf03a..57a4e333c0b56 100644
--- a/x-pack/plugins/alerting/server/rules_settings_feature.ts
+++ b/x-pack/plugins/alerting/server/rules_settings/rules_settings_feature.ts
@@ -17,7 +17,7 @@ import {
RULES_SETTINGS_SAVED_OBJECT_TYPE,
ALL_QUERY_DELAY_SETTINGS_SUB_FEATURE_ID,
READ_QUERY_DELAY_SETTINGS_SUB_FEATURE_ID,
-} from '../common';
+} from '../../common';
export function getRulesSettingsFeature(isServerless: boolean): KibanaFeatureConfig {
const settings = {
diff --git a/x-pack/plugins/alerting/server/rules_settings/rules_settings_service.mock.ts b/x-pack/plugins/alerting/server/rules_settings/rules_settings_service.mock.ts
new file mode 100644
index 0000000000000..d4fea3bcc0da0
--- /dev/null
+++ b/x-pack/plugins/alerting/server/rules_settings/rules_settings_service.mock.ts
@@ -0,0 +1,23 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { DEFAULT_FLAPPING_SETTINGS, DEFAULT_QUERY_DELAY_SETTINGS } from '../types';
+
+const createRulesSettingsServiceMock = () => {
+ return jest.fn().mockImplementation(() => {
+ return {
+ getSettings: jest.fn().mockReturnValue({
+ queryDelaySettings: DEFAULT_QUERY_DELAY_SETTINGS,
+ flappingSettings: DEFAULT_FLAPPING_SETTINGS,
+ }),
+ };
+ });
+};
+
+export const rulesSettingsServiceMock = {
+ create: createRulesSettingsServiceMock(),
+};
diff --git a/x-pack/plugins/alerting/server/rules_settings/rules_settings_service.test.ts b/x-pack/plugins/alerting/server/rules_settings/rules_settings_service.test.ts
new file mode 100644
index 0000000000000..7a42b2d9b92cc
--- /dev/null
+++ b/x-pack/plugins/alerting/server/rules_settings/rules_settings_service.test.ts
@@ -0,0 +1,241 @@
+/*
+ * 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 sinon from 'sinon';
+import { loggingSystemMock } from '@kbn/core/server/mocks';
+import { KibanaRequest } from '@kbn/core/server';
+import { rulesSettingsClientMock } from './rules_settings_client.mock';
+import { RulesSettingsService } from './rules_settings_service';
+import { DEFAULT_QUERY_DELAY_SETTINGS, DEFAULT_SERVERLESS_QUERY_DELAY_SETTINGS } from '../types';
+
+const fakeRequest = {
+ headers: {},
+ getBasePath: () => '',
+ path: '/',
+ route: { settings: {} },
+ url: {
+ href: '/',
+ },
+ raw: {
+ req: {
+ url: '/',
+ },
+ },
+ getSavedObjectsClient: jest.fn(),
+} as unknown as KibanaRequest;
+let fakeTimer: sinon.SinonFakeTimers;
+
+const logger = loggingSystemMock.create().get();
+
+describe('RulesSettingsService', () => {
+ beforeAll(() => {
+ fakeTimer = sinon.useFakeTimers(new Date('2023-02-27T08:15:00.000Z'));
+ });
+ beforeEach(() => {
+ fakeTimer.reset();
+ });
+ afterAll(() => fakeTimer.restore());
+
+ for (const isServerless of [false, true]) {
+ const label = isServerless ? 'serverless' : 'non-serverless';
+ describe(`getSettings in ${label}`, () => {
+ afterEach(() => {
+ jest.resetAllMocks();
+ jest.clearAllMocks();
+ });
+
+ test('should fetch settings if none in cache', async () => {
+ const rulesSettingsClient = rulesSettingsClientMock.create();
+ const rulesSettingsService = new RulesSettingsService({
+ isServerless,
+ logger,
+ getRulesSettingsClientWithRequest: jest.fn().mockReturnValue(rulesSettingsClient),
+ });
+ // @ts-ignore - accessing private variable
+ expect(rulesSettingsService.settings.get('default')).toBeUndefined();
+
+ const settings = await rulesSettingsService.getSettings(fakeRequest, 'default');
+ expect(rulesSettingsClient.queryDelay().get).toHaveBeenCalledTimes(1);
+ expect(rulesSettingsClient.flapping().get).toHaveBeenCalledTimes(1);
+
+ // @ts-ignore - accessing private variable
+ expect(rulesSettingsService.settings.get('default')).toEqual({
+ lastUpdated: 1677485700000,
+ queryDelaySettings: { delay: 0 },
+ flappingSettings: { enabled: true, lookBackWindow: 20, statusChangeThreshold: 4 },
+ });
+
+ expect(settings.queryDelaySettings).toEqual({ delay: 0 });
+ expect(settings.flappingSettings).toEqual(getFlappingSettings(20, 4));
+ });
+
+ test('should return defaults if fetch settings errors and nothing in cache', async () => {
+ const rulesSettingsClient = rulesSettingsClientMock.create();
+ (rulesSettingsClient.queryDelay().get as jest.Mock).mockImplementationOnce(() => {
+ throw new Error('no!');
+ });
+
+ const rulesSettingsService = new RulesSettingsService({
+ isServerless,
+ logger,
+ getRulesSettingsClientWithRequest: jest.fn().mockReturnValue(rulesSettingsClient),
+ });
+
+ // @ts-ignore - accessing private variable
+ expect(rulesSettingsService.settings.get('default')).toBeUndefined();
+
+ const settings = await rulesSettingsService.getSettings(fakeRequest, 'default');
+ expect(rulesSettingsClient.queryDelay().get).toHaveBeenCalledTimes(1);
+ expect(rulesSettingsClient.flapping().get).toHaveBeenCalledTimes(0);
+
+ // @ts-ignore - accessing private variable
+ expect(rulesSettingsService.settings.get('default')).toBeUndefined();
+
+ expect(settings.queryDelaySettings).toEqual(
+ isServerless ? DEFAULT_SERVERLESS_QUERY_DELAY_SETTINGS : DEFAULT_QUERY_DELAY_SETTINGS
+ );
+ expect(settings.flappingSettings).toEqual(getFlappingSettings(20, 4));
+
+ expect(logger.debug).toHaveBeenCalledWith(
+ `Failed to fetch initial rules settings, using default settings: no!`
+ );
+ });
+
+ test('should fetch settings per space', async () => {
+ const rulesSettingsClient = rulesSettingsClientMock.create();
+ (rulesSettingsClient.queryDelay().get as jest.Mock).mockResolvedValueOnce({ delay: 13 });
+ (rulesSettingsClient.flapping().get as jest.Mock).mockResolvedValueOnce(
+ getFlappingSettings(45, 2)
+ );
+ const rulesSettingsService = new RulesSettingsService({
+ isServerless,
+ logger,
+ getRulesSettingsClientWithRequest: jest.fn().mockReturnValue(rulesSettingsClient),
+ });
+ // @ts-ignore - accessing private variable
+ expect(rulesSettingsService.settings.get('default')).toBeUndefined();
+
+ // @ts-ignore - accessing private variable
+ expect(rulesSettingsService.settings.get('new-space')).toBeUndefined();
+
+ const settingsDefault = await rulesSettingsService.getSettings(fakeRequest, 'default');
+ const settingsNewSpace = await rulesSettingsService.getSettings(fakeRequest, 'new-space');
+ expect(rulesSettingsClient.queryDelay().get).toHaveBeenCalledTimes(2);
+ expect(rulesSettingsClient.flapping().get).toHaveBeenCalledTimes(2);
+
+ // @ts-ignore - accessing private variable
+ expect(rulesSettingsService.settings.get('default')).toEqual({
+ lastUpdated: 1677485700000,
+ queryDelaySettings: { delay: 13 },
+ flappingSettings: getFlappingSettings(45, 2),
+ });
+
+ // @ts-ignore - accessing private variable
+ expect(rulesSettingsService.settings.get('new-space')).toEqual({
+ lastUpdated: 1677485700000,
+ queryDelaySettings: { delay: 0 },
+ flappingSettings: getFlappingSettings(20, 4),
+ });
+
+ expect(settingsNewSpace.queryDelaySettings).toEqual({ delay: 0 });
+ expect(settingsNewSpace.flappingSettings).toEqual(getFlappingSettings(20, 4));
+
+ expect(settingsDefault.queryDelaySettings).toEqual({ delay: 13 });
+ expect(settingsDefault.flappingSettings).toEqual(getFlappingSettings(45, 2));
+ });
+
+ test('should use cached settings if cache has not expired', async () => {
+ const rulesSettingsClient = rulesSettingsClientMock.create();
+ (rulesSettingsClient.queryDelay().get as jest.Mock).mockResolvedValueOnce({ delay: 5 });
+ (rulesSettingsClient.flapping().get as jest.Mock).mockResolvedValueOnce(
+ getFlappingSettings(30, 3)
+ );
+ const rulesSettingsService = new RulesSettingsService({
+ isServerless,
+ logger,
+ getRulesSettingsClientWithRequest: jest.fn().mockReturnValue(rulesSettingsClient),
+ });
+
+ const settings1 = await rulesSettingsService.getSettings(fakeRequest, 'default');
+ fakeTimer.tick(30000);
+ const settings2 = await rulesSettingsService.getSettings(fakeRequest, 'default');
+
+ expect(rulesSettingsClient.queryDelay().get).toHaveBeenCalledTimes(1);
+ expect(rulesSettingsClient.flapping().get).toHaveBeenCalledTimes(1);
+
+ expect(settings1.queryDelaySettings).toEqual({ delay: 5 });
+ expect(settings1.flappingSettings).toEqual(getFlappingSettings(30, 3));
+ expect(settings1).toEqual(settings2);
+ });
+
+ test('should refetch settings if cache has expired', async () => {
+ const rulesSettingsClient = rulesSettingsClientMock.create();
+ (rulesSettingsClient.queryDelay().get as jest.Mock).mockResolvedValueOnce({ delay: 5 });
+ (rulesSettingsClient.flapping().get as jest.Mock).mockResolvedValueOnce(
+ getFlappingSettings(30, 3)
+ );
+ (rulesSettingsClient.queryDelay().get as jest.Mock).mockResolvedValueOnce({ delay: 21 });
+ (rulesSettingsClient.flapping().get as jest.Mock).mockResolvedValueOnce(
+ getFlappingSettings(11, 44)
+ );
+ const rulesSettingsService = new RulesSettingsService({
+ isServerless,
+ logger,
+ getRulesSettingsClientWithRequest: jest.fn().mockReturnValue(rulesSettingsClient),
+ });
+
+ const settings1 = await rulesSettingsService.getSettings(fakeRequest, 'default');
+ fakeTimer.tick(61000);
+ const settings2 = await rulesSettingsService.getSettings(fakeRequest, 'default');
+
+ expect(rulesSettingsClient.queryDelay().get).toHaveBeenCalledTimes(2);
+ expect(rulesSettingsClient.flapping().get).toHaveBeenCalledTimes(2);
+
+ expect(settings1.queryDelaySettings).toEqual({ delay: 5 });
+ expect(settings1.flappingSettings).toEqual(getFlappingSettings(30, 3));
+ expect(settings2.queryDelaySettings).toEqual({ delay: 21 });
+ expect(settings2.flappingSettings).toEqual(getFlappingSettings(11, 44));
+ });
+
+ test('should return cached settings if refetching throws an error', async () => {
+ const rulesSettingsClient = rulesSettingsClientMock.create();
+ (rulesSettingsClient.queryDelay().get as jest.Mock).mockResolvedValueOnce({ delay: 13 });
+ (rulesSettingsClient.flapping().get as jest.Mock).mockResolvedValueOnce(
+ getFlappingSettings(11, 44)
+ );
+ (rulesSettingsClient.queryDelay().get as jest.Mock).mockImplementationOnce(() => {
+ throw new Error('no!');
+ });
+ const rulesSettingsService = new RulesSettingsService({
+ isServerless,
+ logger,
+ getRulesSettingsClientWithRequest: jest.fn().mockReturnValue(rulesSettingsClient),
+ });
+
+ const settings1 = await rulesSettingsService.getSettings(fakeRequest, 'default');
+ fakeTimer.tick(61000);
+ const settings2 = await rulesSettingsService.getSettings(fakeRequest, 'default');
+
+ expect(rulesSettingsClient.queryDelay().get).toHaveBeenCalledTimes(2);
+
+ expect(settings1.queryDelaySettings).toEqual({ delay: 13 });
+ expect(settings1.flappingSettings).toEqual(getFlappingSettings(11, 44));
+ expect(settings1).toEqual(settings2);
+
+ expect(logger.debug).toHaveBeenCalledWith(
+ `Failed to fetch rules settings after cache expiration, using cached settings: no!`
+ );
+ });
+ });
+ }
+});
+
+const getFlappingSettings = (lookback: number, threshold: number) => ({
+ enabled: true,
+ lookBackWindow: lookback,
+ statusChangeThreshold: threshold,
+});
diff --git a/x-pack/plugins/alerting/server/rules_settings/rules_settings_service.ts b/x-pack/plugins/alerting/server/rules_settings/rules_settings_service.ts
new file mode 100644
index 0000000000000..2dd264e5d9a69
--- /dev/null
+++ b/x-pack/plugins/alerting/server/rules_settings/rules_settings_service.ts
@@ -0,0 +1,112 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { KibanaRequest, Logger } from '@kbn/core/server';
+import {
+ DEFAULT_FLAPPING_SETTINGS,
+ DEFAULT_QUERY_DELAY_SETTINGS,
+ DEFAULT_SERVERLESS_QUERY_DELAY_SETTINGS,
+ RulesSettingsClientApi,
+ RulesSettingsFlappingProperties,
+ RulesSettingsQueryDelayProperties,
+} from '../types';
+import { withAlertingSpan } from '../task_runner/lib';
+
+export const DEFAULT_CACHE_INTERVAL_MS = 60000; // 1 minute cache
+
+export interface RulesSettingsServiceConstructorOptions {
+ readonly isServerless: boolean;
+ cacheInterval?: number;
+ logger: Logger;
+ getRulesSettingsClientWithRequest(request: KibanaRequest): RulesSettingsClientApi;
+}
+
+interface Settings {
+ queryDelaySettings: RulesSettingsQueryDelayProperties;
+ flappingSettings: RulesSettingsFlappingProperties;
+}
+
+type LastUpdatedSettings = Settings & { lastUpdated: number };
+
+export class RulesSettingsService {
+ private cacheIntervalMs = DEFAULT_CACHE_INTERVAL_MS;
+ private defaultQueryDelaySettings: RulesSettingsQueryDelayProperties;
+ private settings: Map = new Map();
+
+ constructor(private readonly options: RulesSettingsServiceConstructorOptions) {
+ if (options.cacheInterval) {
+ this.cacheIntervalMs = options.cacheInterval;
+ }
+ this.defaultQueryDelaySettings = options.isServerless
+ ? DEFAULT_SERVERLESS_QUERY_DELAY_SETTINGS
+ : DEFAULT_QUERY_DELAY_SETTINGS;
+ }
+
+ public async getSettings(request: KibanaRequest, spaceId: string): Promise {
+ const now = Date.now();
+ if (this.settings.has(spaceId)) {
+ const settingsFromLastUpdate = this.settings.get(spaceId)!;
+ const lastUpdated = new Date(settingsFromLastUpdate.lastUpdated).getTime();
+ const currentFlappingSettings = settingsFromLastUpdate.flappingSettings;
+ const currentQueryDelaySettings = settingsFromLastUpdate.queryDelaySettings;
+
+ if (now - lastUpdated >= this.cacheIntervalMs) {
+ // cache expired, refetch settings
+ try {
+ return await this.fetchSettings(request, spaceId, now);
+ } catch (err) {
+ // return cached settings on error
+ this.options.logger.debug(
+ `Failed to fetch rules settings after cache expiration, using cached settings: ${err.message}`
+ );
+ return {
+ queryDelaySettings: currentQueryDelaySettings,
+ flappingSettings: currentFlappingSettings,
+ };
+ }
+ } else {
+ return {
+ queryDelaySettings: currentQueryDelaySettings,
+ flappingSettings: currentFlappingSettings,
+ };
+ }
+ } else {
+ // nothing in cache, fetch settings
+ try {
+ return await this.fetchSettings(request, spaceId, now);
+ } catch (err) {
+ // return default settings on error
+ this.options.logger.debug(
+ `Failed to fetch initial rules settings, using default settings: ${err.message}`
+ );
+ return {
+ queryDelaySettings: this.defaultQueryDelaySettings,
+ flappingSettings: DEFAULT_FLAPPING_SETTINGS,
+ };
+ }
+ }
+ }
+
+ private async fetchSettings(
+ request: KibanaRequest,
+ spaceId: string,
+ now: number
+ ): Promise {
+ const rulesSettingsClient = this.options.getRulesSettingsClientWithRequest(request);
+ const queryDelaySettings = await withAlertingSpan('alerting:get-query-delay-settings', () =>
+ rulesSettingsClient.queryDelay().get()
+ );
+
+ const flappingSettings = await withAlertingSpan('alerting:get-flapping-settings', () =>
+ rulesSettingsClient.flapping().get()
+ );
+
+ this.settings.set(spaceId, { lastUpdated: now, queryDelaySettings, flappingSettings });
+
+ return { flappingSettings, queryDelaySettings };
+ }
+}
diff --git a/x-pack/plugins/alerting/server/rules_settings_client/schemas/flapping_schema.ts b/x-pack/plugins/alerting/server/rules_settings/schemas/flapping_schema.ts
similarity index 100%
rename from x-pack/plugins/alerting/server/rules_settings_client/schemas/flapping_schema.ts
rename to x-pack/plugins/alerting/server/rules_settings/schemas/flapping_schema.ts
diff --git a/x-pack/plugins/alerting/server/rules_settings_client/schemas/index.ts b/x-pack/plugins/alerting/server/rules_settings/schemas/index.ts
similarity index 100%
rename from x-pack/plugins/alerting/server/rules_settings_client/schemas/index.ts
rename to x-pack/plugins/alerting/server/rules_settings/schemas/index.ts
diff --git a/x-pack/plugins/alerting/server/rules_settings_client/schemas/query_delay_schema.ts b/x-pack/plugins/alerting/server/rules_settings/schemas/query_delay_schema.ts
similarity index 100%
rename from x-pack/plugins/alerting/server/rules_settings_client/schemas/query_delay_schema.ts
rename to x-pack/plugins/alerting/server/rules_settings/schemas/query_delay_schema.ts
diff --git a/x-pack/plugins/alerting/server/task_runner/ad_hoc_task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/ad_hoc_task_runner.test.ts
index 9b65f6613baaf..f90420ba85036 100644
--- a/x-pack/plugins/alerting/server/task_runner/ad_hoc_task_runner.test.ts
+++ b/x-pack/plugins/alerting/server/task_runner/ad_hoc_task_runner.test.ts
@@ -32,7 +32,6 @@ import { TaskRunnerContext } from './types';
import { backfillClientMock } from '../backfill_client/backfill_client.mock';
import { maintenanceWindowClientMock } from '../maintenance_window_client.mock';
import { rulesClientMock } from '../rules_client.mock';
-import { rulesSettingsClientMock } from '../rules_settings_client.mock';
import { ruleTypeRegistryMock } from '../rule_type_registry.mock';
import {
AlertingEventLogger,
@@ -94,6 +93,7 @@ import { validateRuleTypeParams } from '../lib/validate_rule_type_params';
import { ruleRunMetricsStoreMock } from '../lib/rule_run_metrics_store.mock';
import { RuleRunMetricsStore } from '../lib/rule_run_metrics_store';
import { ConnectorAdapterRegistry } from '../connector_adapters/connector_adapter_registry';
+import { rulesSettingsServiceMock } from '../rules_settings/rules_settings_service.mock';
const UUID = '5f6aa57d-3e22-484e-bae8-cbed868f4d28';
@@ -145,17 +145,14 @@ const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const maintenanceWindowClient = maintenanceWindowClientMock.create();
const rulesClient = rulesClientMock.create();
const ruleRunMetricsStore = ruleRunMetricsStoreMock.create();
+const rulesSettingsService = rulesSettingsServiceMock.create();
const ruleTypeRegistry = ruleTypeRegistryMock.create();
const savedObjectsService = savedObjectsServiceMock.createInternalStartContract();
const services = alertsMock.createRuleExecutorServices();
const uiSettingsService = uiSettingsServiceMock.createStartContract();
const taskRunnerFactoryInitializerParams: TaskRunnerFactoryInitializerParamsType = {
- actionsConfigMap: {
- default: {
- max: 10000,
- },
- },
+ actionsConfigMap: { default: { max: 1000 } },
actionsPlugin: actionsMock.createStart(),
alertsService,
backfillClient,
@@ -170,12 +167,12 @@ const taskRunnerFactoryInitializerParams: TaskRunnerFactoryInitializerParamsType
executionContext: executionContextServiceMock.createInternalStartContract(),
getMaintenanceWindowClientWithRequest: jest.fn().mockReturnValue(maintenanceWindowClient),
getRulesClientWithRequest: jest.fn().mockReturnValue(rulesClient),
- getRulesSettingsClientWithRequest: jest.fn().mockReturnValue(rulesSettingsClientMock.create()),
kibanaBaseUrl: 'https://localhost:5601',
logger,
maxAlerts: 1000,
maxEphemeralActionsPerRule: 10,
ruleTypeRegistry,
+ rulesSettingsService,
savedObjects: savedObjectsService,
share: {} as SharePluginStart,
spaceIdToNamespace: jest.fn().mockReturnValue(undefined),
diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts
index c6cec600090ba..833393504fdeb 100644
--- a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts
+++ b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts
@@ -17,6 +17,8 @@ import {
Rule,
RuleAction,
RuleAlertData,
+ DEFAULT_FLAPPING_SETTINGS,
+ DEFAULT_QUERY_DELAY_SETTINGS,
} from '../types';
import {
ConcreteTaskInstance,
@@ -81,7 +83,6 @@ import { alertingEventLoggerMock } from '../lib/alerting_event_logger/alerting_e
import { SharePluginStart } from '@kbn/share-plugin/server';
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
import { DataViewsServerPluginStart } from '@kbn/data-views-plugin/server';
-import { rulesSettingsClientMock } from '../rules_settings_client.mock';
import { maintenanceWindowClientMock } from '../maintenance_window_client.mock';
import { alertsServiceMock } from '../alerts_service/alerts_service.mock';
import { ConnectorAdapterRegistry } from '../connector_adapters/connector_adapter_registry';
@@ -95,6 +96,7 @@ import { ruleResultServiceMock } from '../monitoring/rule_result_service.mock';
import { backfillClientMock } from '../backfill_client/backfill_client.mock';
import { UntypedNormalizedRuleType } from '../rule_type_registry';
import * as getExecutorServicesModule from './get_executor_services';
+import { rulesSettingsServiceMock } from '../rules_settings/rules_settings_service.mock';
jest.mock('uuid', () => ({
v4: () => '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
@@ -157,6 +159,7 @@ describe('Task Runner', () => {
const alertsService = alertsServiceMock.create();
const maintenanceWindowClient = maintenanceWindowClientMock.create();
const connectorAdapterRegistry = new ConnectorAdapterRegistry();
+ const rulesSettingsService = rulesSettingsServiceMock.create();
type TaskRunnerFactoryInitializerParamsType = jest.Mocked & {
actionsPlugin: jest.Mocked;
@@ -165,37 +168,35 @@ describe('Task Runner', () => {
};
const taskRunnerFactoryInitializerParams: TaskRunnerFactoryInitializerParamsType = {
+ actionsConfigMap: { default: { max: 1000 } },
+ actionsPlugin: actionsMock.createStart(),
+ alertsService,
+ backfillClient,
+ basePathService: httpServiceMock.createBasePath(),
+ cancelAlertsOnRuleTimeout: true,
+ connectorAdapterRegistry,
data: dataPlugin,
dataViews: dataViewsMock,
- savedObjects: savedObjectsService,
- share: {} as SharePluginStart,
- uiSettings: uiSettingsService,
elasticsearch: elasticsearchService,
- actionsPlugin: actionsMock.createStart(),
- getRulesClientWithRequest: jest.fn().mockReturnValue(rulesClient),
encryptedSavedObjectsClient,
- logger,
- backfillClient,
- executionContext: executionContextServiceMock.createInternalStartContract(),
- spaceIdToNamespace: jest.fn().mockReturnValue(undefined),
- basePathService: httpServiceMock.createBasePath(),
eventLogger: eventLoggerMock.create(),
- ruleTypeRegistry,
- alertsService,
+ executionContext: executionContextServiceMock.createInternalStartContract(),
+ getMaintenanceWindowClientWithRequest: jest
+ .fn()
+ .mockReturnValue(maintenanceWindowClientMock.create()),
+ getRulesClientWithRequest: jest.fn().mockReturnValue(rulesClient),
kibanaBaseUrl: 'https://localhost:5601',
- supportsEphemeralTasks: false,
- maxEphemeralActionsPerRule: 10,
+ logger,
maxAlerts: 1000,
- cancelAlertsOnRuleTimeout: true,
+ maxEphemeralActionsPerRule: 10,
+ ruleTypeRegistry,
+ rulesSettingsService,
+ savedObjects: savedObjectsService,
+ share: {} as SharePluginStart,
+ spaceIdToNamespace: jest.fn().mockReturnValue(undefined),
+ supportsEphemeralTasks: false,
+ uiSettings: uiSettingsService,
usageCounter: mockUsageCounter,
- actionsConfigMap: {
- default: {
- max: 10000,
- },
- },
- getRulesSettingsClientWithRequest: jest.fn().mockReturnValue(rulesSettingsClientMock.create()),
- getMaintenanceWindowClientWithRequest: jest.fn().mockReturnValue(maintenanceWindowClient),
- connectorAdapterRegistry,
};
const ephemeralTestParams: Array<
@@ -242,13 +243,14 @@ describe('Task Runner', () => {
taskRunnerFactoryInitializerParams.actionsPlugin.renderActionParameterTemplates.mockImplementation(
(actionTypeId, actionId, params) => params
);
+ rulesSettingsService.getSettings.mockResolvedValue({
+ flappingSettings: DEFAULT_FLAPPING_SETTINGS,
+ queryDelaySettings: DEFAULT_QUERY_DELAY_SETTINGS,
+ });
ruleTypeRegistry.get.mockReturnValue(ruleType);
taskRunnerFactoryInitializerParams.executionContext.withContext.mockImplementation((ctx, fn) =>
fn()
);
- taskRunnerFactoryInitializerParams.getRulesSettingsClientWithRequest.mockReturnValue(
- rulesSettingsClientMock.create()
- );
taskRunnerFactoryInitializerParams.getMaintenanceWindowClientWithRequest.mockReturnValue(
maintenanceWindowClient
);
diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts
index 409427bb34ca2..936ff2bc64797 100644
--- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts
+++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts
@@ -291,21 +291,16 @@ export class TaskRunner<
state: { previousStartedAt },
} = this.taskInstance;
- const rulesSettingsClient = this.context.getRulesSettingsClientWithRequest(fakeRequest);
+ const { queryDelaySettings, flappingSettings } =
+ await this.context.rulesSettingsService.getSettings(fakeRequest, spaceId);
const ruleRunMetricsStore = new RuleRunMetricsStore();
const ruleLabel = `${this.ruleType.id}:${ruleId}: '${rule.name}'`;
- const queryDelay = await withAlertingSpan('alerting:get-query-delay-settings', () =>
- rulesSettingsClient.queryDelay().get()
- );
- const flappingSettings = await withAlertingSpan('alerting:get-flapping-settings', () =>
- rulesSettingsClient.flapping().get()
- );
const ruleTypeRunnerContext = {
alertingEventLogger: this.alertingEventLogger,
flappingSettings,
namespace: this.context.spaceIdToNamespace(spaceId),
- queryDelaySec: queryDelay.delay,
+ queryDelaySec: queryDelaySettings.delay,
ruleId,
ruleLogPrefix: ruleLabel,
ruleRunMetricsStore,
diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_alerts_client.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_alerts_client.test.ts
index 851bdddaed62a..cee8a2ea3b345 100644
--- a/x-pack/plugins/alerting/server/task_runner/task_runner_alerts_client.test.ts
+++ b/x-pack/plugins/alerting/server/task_runner/task_runner_alerts_client.test.ts
@@ -15,6 +15,8 @@ import {
AlertInstanceContext,
Rule,
RuleAlertData,
+ DEFAULT_FLAPPING_SETTINGS,
+ DEFAULT_QUERY_DELAY_SETTINGS,
} from '../types';
import { ConcreteTaskInstance } from '@kbn/task-manager-plugin/server';
import { TaskRunnerContext } from './types';
@@ -55,7 +57,6 @@ import { alertingEventLoggerMock } from '../lib/alerting_event_logger/alerting_e
import { SharePluginStart } from '@kbn/share-plugin/server';
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
import { DataViewsServerPluginStart } from '@kbn/data-views-plugin/server';
-import { rulesSettingsClientMock } from '../rules_settings_client.mock';
import { maintenanceWindowClientMock } from '../maintenance_window_client.mock';
import { alertsServiceMock } from '../alerts_service/alerts_service.mock';
import { UntypedNormalizedRuleType } from '../rule_type_registry';
@@ -103,6 +104,7 @@ import {
import { backfillClientMock } from '../backfill_client/backfill_client.mock';
import { ConnectorAdapterRegistry } from '../connector_adapters/connector_adapter_registry';
import { createTaskRunnerLogger } from './lib';
+import { rulesSettingsServiceMock } from '../rules_settings/rules_settings_service.mock';
jest.mock('uuid', () => ({
v4: () => '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
@@ -161,6 +163,7 @@ describe('Task Runner', () => {
const backfillClient = backfillClientMock.create();
const services = alertsMock.createRuleExecutorServices();
const actionsClient = actionsClientMock.create();
+ const rulesSettingsService = rulesSettingsServiceMock.create();
const rulesClient = rulesClientMock.create();
const ruleTypeRegistry = ruleTypeRegistryMock.create();
const savedObjectsService = savedObjectsServiceMock.createInternalStartContract();
@@ -189,39 +192,33 @@ describe('Task Runner', () => {
};
const taskRunnerFactoryInitializerParams: TaskRunnerFactoryInitializerParamsType = {
+ actionsConfigMap: { default: { max: 1000 } },
+ actionsPlugin: actionsMock.createStart(),
+ alertsService: mockAlertsService,
+ backfillClient,
+ basePathService: httpServiceMock.createBasePath(),
+ cancelAlertsOnRuleTimeout: true,
+ connectorAdapterRegistry,
data: dataPlugin,
dataViews: dataViewsMock,
- savedObjects: savedObjectsService,
- share: {} as SharePluginStart,
- uiSettings: uiSettingsService,
elasticsearch: elasticsearchService,
- actionsPlugin: actionsMock.createStart(),
- getRulesClientWithRequest: jest.fn().mockReturnValue(rulesClient),
encryptedSavedObjectsClient,
- logger,
- executionContext: executionContextServiceMock.createInternalStartContract(),
- spaceIdToNamespace: jest.fn().mockReturnValue(undefined),
- basePathService: httpServiceMock.createBasePath(),
eventLogger: eventLoggerMock.create(),
- backfillClient,
- ruleTypeRegistry,
- alertsService: mockAlertsService,
+ executionContext: executionContextServiceMock.createInternalStartContract(),
+ getMaintenanceWindowClientWithRequest: jest.fn().mockReturnValue(maintenanceWindowClient),
+ getRulesClientWithRequest: jest.fn().mockReturnValue(rulesClient),
kibanaBaseUrl: 'https://localhost:5601',
- supportsEphemeralTasks: false,
- maxEphemeralActionsPerRule: 10,
+ logger,
maxAlerts: 1000,
- cancelAlertsOnRuleTimeout: true,
+ maxEphemeralActionsPerRule: 10,
+ ruleTypeRegistry,
+ rulesSettingsService,
+ savedObjects: savedObjectsService,
+ share: {} as SharePluginStart,
+ spaceIdToNamespace: jest.fn().mockReturnValue(undefined),
+ supportsEphemeralTasks: false,
+ uiSettings: uiSettingsService,
usageCounter: mockUsageCounter,
- actionsConfigMap: {
- default: {
- max: 10000,
- },
- },
- getRulesSettingsClientWithRequest: jest
- .fn()
- .mockReturnValue(rulesSettingsClientMock.create()),
- getMaintenanceWindowClientWithRequest: jest.fn().mockReturnValue(maintenanceWindowClient),
- connectorAdapterRegistry,
};
describe(`using ${label} for alert indices`, () => {
@@ -251,9 +248,10 @@ describe('Task Runner', () => {
taskRunnerFactoryInitializerParams.executionContext.withContext.mockImplementation(
(ctx, fn) => fn()
);
- taskRunnerFactoryInitializerParams.getRulesSettingsClientWithRequest.mockReturnValue(
- rulesSettingsClientMock.create()
- );
+ rulesSettingsService.getSettings.mockResolvedValue({
+ flappingSettings: DEFAULT_FLAPPING_SETTINGS,
+ queryDelaySettings: DEFAULT_QUERY_DELAY_SETTINGS,
+ });
taskRunnerFactoryInitializerParams.getMaintenanceWindowClientWithRequest.mockReturnValue(
maintenanceWindowClient
);
diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts
index fdc05d208aba1..b9fb6284c3911 100644
--- a/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts
+++ b/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts
@@ -15,6 +15,8 @@ import {
AlertInstanceContext,
Rule,
RuleAlertData,
+ DEFAULT_FLAPPING_SETTINGS,
+ DEFAULT_QUERY_DELAY_SETTINGS,
} from '../types';
import { ConcreteTaskInstance } from '@kbn/task-manager-plugin/server';
import { TaskRunner } from './task_runner';
@@ -54,7 +56,6 @@ import { EVENT_LOG_ACTIONS } from '../plugin';
import { SharePluginStart } from '@kbn/share-plugin/server';
import { DataViewsServerPluginStart } from '@kbn/data-views-plugin/server';
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
-import { rulesSettingsClientMock } from '../rules_settings_client.mock';
import { maintenanceWindowClientMock } from '../maintenance_window_client.mock';
import { alertsServiceMock } from '../alerts_service/alerts_service.mock';
import { ConnectorAdapterRegistry } from '../connector_adapters/connector_adapter_registry';
@@ -62,6 +63,7 @@ import { RULE_SAVED_OBJECT_TYPE } from '../saved_objects';
import { TaskRunnerContext } from './types';
import { backfillClientMock } from '../backfill_client/backfill_client.mock';
import { UntypedNormalizedRuleType } from '../rule_type_registry';
+import { rulesSettingsServiceMock } from '../rules_settings/rules_settings_service.mock';
jest.mock('uuid', () => ({
v4: () => '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
@@ -116,6 +118,7 @@ describe('Task Runner Cancel', () => {
const dataPlugin = dataPluginMock.createStartContract();
const inMemoryMetrics = inMemoryMetricsMock.create();
const connectorAdapterRegistry = new ConnectorAdapterRegistry();
+ const rulesSettingsService = rulesSettingsServiceMock.create();
type TaskRunnerFactoryInitializerParamsType = jest.Mocked & {
actionsPlugin: jest.Mocked;
@@ -124,39 +127,35 @@ describe('Task Runner Cancel', () => {
};
const taskRunnerFactoryInitializerParams: TaskRunnerFactoryInitializerParamsType = {
+ actionsConfigMap: { default: { max: 1000 } },
+ actionsPlugin: actionsMock.createStart(),
+ alertsService,
+ backfillClient,
+ basePathService: httpServiceMock.createBasePath(),
+ cancelAlertsOnRuleTimeout: true,
+ connectorAdapterRegistry,
data: dataPlugin,
dataViews: dataViewsMock,
- savedObjects: savedObjectsService,
- share: {} as SharePluginStart,
- uiSettings: uiSettingsService,
elasticsearch: elasticsearchService,
- actionsPlugin: actionsMock.createStart(),
- getRulesClientWithRequest: jest.fn().mockReturnValue(rulesClient),
encryptedSavedObjectsClient,
- logger,
- executionContext: executionContextServiceMock.createInternalStartContract(),
- spaceIdToNamespace: jest.fn().mockReturnValue(undefined),
- basePathService: httpServiceMock.createBasePath(),
eventLogger: eventLoggerMock.create(),
- backfillClient,
- ruleTypeRegistry,
- alertsService,
- kibanaBaseUrl: 'https://localhost:5601',
- supportsEphemeralTasks: false,
- maxEphemeralActionsPerRule: 10,
- maxAlerts: 1000,
- cancelAlertsOnRuleTimeout: true,
- usageCounter: mockUsageCounter,
- actionsConfigMap: {
- default: {
- max: 1000,
- },
- },
- getRulesSettingsClientWithRequest: jest.fn().mockReturnValue(rulesSettingsClientMock.create()),
+ executionContext: executionContextServiceMock.createInternalStartContract(),
getMaintenanceWindowClientWithRequest: jest
.fn()
.mockReturnValue(maintenanceWindowClientMock.create()),
- connectorAdapterRegistry,
+ getRulesClientWithRequest: jest.fn().mockReturnValue(rulesClient),
+ kibanaBaseUrl: 'https://localhost:5601',
+ logger,
+ maxAlerts: 1000,
+ maxEphemeralActionsPerRule: 10,
+ ruleTypeRegistry,
+ rulesSettingsService,
+ savedObjects: savedObjectsService,
+ share: {} as SharePluginStart,
+ spaceIdToNamespace: jest.fn().mockReturnValue(undefined),
+ supportsEphemeralTasks: false,
+ uiSettings: uiSettingsService,
+ usageCounter: mockUsageCounter,
};
beforeEach(() => {
@@ -184,9 +183,10 @@ describe('Task Runner Cancel', () => {
taskRunnerFactoryInitializerParams.executionContext.withContext.mockImplementation((ctx, fn) =>
fn()
);
- taskRunnerFactoryInitializerParams.getRulesSettingsClientWithRequest.mockReturnValue(
- rulesSettingsClientMock.create()
- );
+ rulesSettingsService.getSettings.mockResolvedValue({
+ flappingSettings: DEFAULT_FLAPPING_SETTINGS,
+ queryDelaySettings: DEFAULT_QUERY_DELAY_SETTINGS,
+ });
taskRunnerFactoryInitializerParams.getMaintenanceWindowClientWithRequest.mockReturnValue(
maintenanceWindowClientMock.create()
);
diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts
index a29d9f3c0ad91..d2e863ef865b2 100644
--- a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts
+++ b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts
@@ -28,16 +28,17 @@ import { inMemoryMetricsMock } from '../monitoring/in_memory_metrics.mock';
import { SharePluginStart } from '@kbn/share-plugin/server';
import { DataViewsServerPluginStart } from '@kbn/data-views-plugin/server';
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
-import { rulesSettingsClientMock } from '../rules_settings_client.mock';
import { maintenanceWindowClientMock } from '../maintenance_window_client.mock';
import { alertsServiceMock } from '../alerts_service/alerts_service.mock';
import { schema } from '@kbn/config-schema';
import { ConnectorAdapterRegistry } from '../connector_adapters/connector_adapter_registry';
import { TaskRunnerContext } from './types';
import { backfillClientMock } from '../backfill_client/backfill_client.mock';
+import { rulesSettingsServiceMock } from '../rules_settings/rules_settings_service.mock';
const inMemoryMetrics = inMemoryMetricsMock.create();
const backfillClient = backfillClientMock.create();
+const rulesSettingsService = rulesSettingsServiceMock.create();
const executionContext = executionContextServiceMock.createSetupContract();
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
@@ -103,39 +104,35 @@ describe('Task Runner Factory', () => {
const connectorAdapterRegistry = new ConnectorAdapterRegistry();
const taskRunnerFactoryInitializerParams: jest.Mocked = {
+ actionsConfigMap: { default: { max: 1000 } },
+ actionsPlugin: actionsMock.createStart(),
+ alertsService: mockAlertService,
backfillClient,
+ basePathService: httpServiceMock.createBasePath(),
+ cancelAlertsOnRuleTimeout: true,
+ connectorAdapterRegistry,
data: dataPlugin,
dataViews: dataViewsMock,
- savedObjects: savedObjectsService,
- share: {} as SharePluginStart,
- uiSettings: uiSettingsService,
elasticsearch: elasticsearchService,
- getRulesClientWithRequest: jest.fn().mockReturnValue(rulesClient),
- actionsPlugin: actionsMock.createStart(),
encryptedSavedObjectsClient: encryptedSavedObjectsPlugin.getClient(),
- logger: loggingSystemMock.create().get(),
- spaceIdToNamespace: jest.fn().mockReturnValue(undefined),
- basePathService: httpServiceMock.createBasePath(),
eventLogger: eventLoggerMock.create(),
- ruleTypeRegistry: ruleTypeRegistryMock.create(),
- alertsService: mockAlertService,
- kibanaBaseUrl: 'https://localhost:5601',
- supportsEphemeralTasks: true,
- maxEphemeralActionsPerRule: 10,
- maxAlerts: 1000,
- cancelAlertsOnRuleTimeout: true,
executionContext,
- usageCounter: mockUsageCounter,
- actionsConfigMap: {
- default: {
- max: 1000,
- },
- },
- getRulesSettingsClientWithRequest: jest.fn().mockReturnValue(rulesSettingsClientMock.create()),
getMaintenanceWindowClientWithRequest: jest
.fn()
.mockReturnValue(maintenanceWindowClientMock.create()),
- connectorAdapterRegistry,
+ getRulesClientWithRequest: jest.fn().mockReturnValue(rulesClient),
+ kibanaBaseUrl: 'https://localhost:5601',
+ logger: loggingSystemMock.create().get(),
+ maxAlerts: 1000,
+ maxEphemeralActionsPerRule: 10,
+ ruleTypeRegistry: ruleTypeRegistryMock.create(),
+ rulesSettingsService,
+ savedObjects: savedObjectsService,
+ share: {} as SharePluginStart,
+ spaceIdToNamespace: jest.fn().mockReturnValue(undefined),
+ supportsEphemeralTasks: true,
+ uiSettings: uiSettingsService,
+ usageCounter: mockUsageCounter,
};
beforeEach(() => {
diff --git a/x-pack/plugins/alerting/server/task_runner/types.ts b/x-pack/plugins/alerting/server/task_runner/types.ts
index 9d40c186bcead..18bf53cdc60b9 100644
--- a/x-pack/plugins/alerting/server/task_runner/types.ts
+++ b/x-pack/plugins/alerting/server/task_runner/types.ts
@@ -48,7 +48,6 @@ import {
MaintenanceWindowClientApi,
RawRule,
RulesClientApi,
- RulesSettingsClientApi,
RuleTypeRegistry,
SpaceIdToNamespaceFunction,
} from '../types';
@@ -57,6 +56,7 @@ import { AlertingEventLogger } from '../lib/alerting_event_logger/alerting_event
import { BackfillClient } from '../backfill_client/backfill_client';
import { ElasticsearchError } from '../lib';
import { ConnectorAdapterRegistry } from '../connector_adapters/connector_adapter_registry';
+import { RulesSettingsService } from '../rules_settings';
export interface RuleTaskRunResult {
state: RuleTaskState;
@@ -160,6 +160,7 @@ export interface TaskRunnerContext {
backfillClient: BackfillClient;
basePathService: IBasePath;
cancelAlertsOnRuleTimeout: boolean;
+ connectorAdapterRegistry: ConnectorAdapterRegistry;
data: DataPluginStart;
dataViews: DataViewsPluginStart;
elasticsearch: ElasticsearchServiceStart;
@@ -168,17 +169,16 @@ export interface TaskRunnerContext {
executionContext: ExecutionContextStart;
getMaintenanceWindowClientWithRequest(request: KibanaRequest): MaintenanceWindowClientApi;
getRulesClientWithRequest(request: KibanaRequest): RulesClientApi;
- getRulesSettingsClientWithRequest(request: KibanaRequest): RulesSettingsClientApi;
kibanaBaseUrl: string | undefined;
logger: Logger;
maxAlerts: number;
maxEphemeralActionsPerRule: number;
ruleTypeRegistry: RuleTypeRegistry;
+ rulesSettingsService: RulesSettingsService;
savedObjects: SavedObjectsServiceStart;
share: SharePluginStart;
spaceIdToNamespace: SpaceIdToNamespaceFunction;
supportsEphemeralTasks: boolean;
uiSettings: UiSettingsServiceStart;
usageCounter?: UsageCounter;
- connectorAdapterRegistry: ConnectorAdapterRegistry;
}
diff --git a/x-pack/plugins/alerting/server/test_utils/index.ts b/x-pack/plugins/alerting/server/test_utils/index.ts
index 9985f43348eb0..036454f2f80c6 100644
--- a/x-pack/plugins/alerting/server/test_utils/index.ts
+++ b/x-pack/plugins/alerting/server/test_utils/index.ts
@@ -71,5 +71,6 @@ export function generateAlertingConfig(): AlertingConfig {
},
},
},
+ rulesSettings: { cacheInterval: 60000 },
};
}
diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerting/server/types.ts
index 3df6f241061c3..58404268efaa8 100644
--- a/x-pack/plugins/alerting/server/types.ts
+++ b/x-pack/plugins/alerting/server/types.ts
@@ -36,7 +36,7 @@ import {
RulesSettingsClient,
RulesSettingsFlappingClient,
RulesSettingsQueryDelayClient,
-} from './rules_settings_client';
+} from './rules_settings';
import { MaintenanceWindowClient } from './maintenance_window_client';
export * from '../common';
import {
diff --git a/x-pack/test/alerting_api_integration/common/config.ts b/x-pack/test/alerting_api_integration/common/config.ts
index 80439bc1cb489..1faadc6041634 100644
--- a/x-pack/test/alerting_api_integration/common/config.ts
+++ b/x-pack/test/alerting_api_integration/common/config.ts
@@ -208,6 +208,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions)
{ id: 'test.capped', max: '1' },
])}`,
`--xpack.alerting.enableFrameworkAlerts=true`,
+ `--xpack.alerting.rulesSettings.cacheInterval=10000`,
`--xpack.actions.enabledActionTypes=${JSON.stringify(enabledActionTypes)}`,
`--xpack.actions.rejectUnauthorized=${rejectUnauthorized}`,
`--xpack.actions.microsoftGraphApiUrl=${servers.kibana.protocol}://${servers.kibana.hostname}:${servers.kibana.port}/api/_actions-FTS-external-service-simulators/exchange/users/test@/sendMail`,
diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data.ts
index 5ce7ffcd18ef5..c0c52ddc73b5a 100644
--- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data.ts
+++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data.ts
@@ -8,6 +8,7 @@
import expect from '@kbn/expect';
import { SearchHit } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { IValidatedEvent } from '@kbn/event-log-plugin/server';
+import { setTimeout as setTimeoutAsync } from 'timers/promises';
import type { Alert } from '@kbn/alerts-as-data-utils';
import { omit } from 'lodash';
import {
@@ -87,6 +88,8 @@ export default function createAlertsAsDataInstallResourcesTest({ getService }: F
it(`should write alert docs during rule execution with flapping.enabled: ${enableFlapping}`, async () => {
await setFlappingSettings(enableFlapping);
+ // wait so cache expires
+ await setTimeoutAsync(10000);
const pattern = {
alertA: [true, true, true], // stays active across executions
diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data_flapping.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data_flapping.ts
index 6dce47b54ebce..de1f4351e0fa8 100644
--- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data_flapping.ts
+++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data_flapping.ts
@@ -9,6 +9,7 @@ import expect from '@kbn/expect';
import { SearchHit } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { Alert } from '@kbn/alerts-as-data-utils';
import { RuleNotifyWhen } from '@kbn/alerting-plugin/common';
+import { setTimeout as setTimeoutAsync } from 'timers/promises';
import { ALERT_FLAPPING, ALERT_FLAPPING_HISTORY, ALERT_RULE_UUID } from '@kbn/rule-data-utils';
import { FtrProviderContext } from '../../../../../common/ftr_provider_context';
import { Spaces } from '../../../../scenarios';
@@ -57,6 +58,8 @@ export default function createAlertsAsDataFlappingTest({ getService }: FtrProvid
status_change_threshold: 4,
})
.expect(200);
+ // wait so cache expires
+ await setTimeoutAsync(10000);
const pattern = {
alertA: [true, false, false, true, false, true, false, true, false].concat(
@@ -188,6 +191,8 @@ export default function createAlertsAsDataFlappingTest({ getService }: FtrProvid
status_change_threshold: 4,
})
.expect(200);
+ // wait so cache expires
+ await setTimeoutAsync(10000);
const pattern = {
alertA: [true, false, false, true, false, true, false, true, false, true].concat(
@@ -316,6 +321,8 @@ export default function createAlertsAsDataFlappingTest({ getService }: FtrProvid
status_change_threshold: 3,
})
.expect(200);
+ // wait so cache expires
+ await setTimeoutAsync(10000);
const pattern = {
alertA: [true, false, true, false, false, false, false, false, false],
@@ -374,6 +381,8 @@ export default function createAlertsAsDataFlappingTest({ getService }: FtrProvid
status_change_threshold: 5,
})
.expect(200);
+ // wait so cache expires
+ await setTimeoutAsync(10000);
const pattern = {
alertA: [true, false, false, true, false, true, false, true, false].concat(
From 636873e10c93941baefc65477ea445c1c3a3bc04 Mon Sep 17 00:00:00 2001
From: Yehor Shvedov <146825775+ev1yehor@users.noreply.github.com>
Date: Mon, 16 Sep 2024 16:35:28 +0300
Subject: [PATCH 019/139] Add vault bot to allowed list (#190977)
## Summary
Summarize your PR. If it involves visual changes include a screenshot or
gif.
Adding github vault bot to allowed list to allow triggering buildkite
builds.
Related to PR https://github.com/elastic/package-storage-infra/pull/787
And issue: https://github.com/elastic/ingest-dev/issues/3495
---
.buildkite/pull_requests.json | 1 +
1 file changed, 1 insertion(+)
diff --git a/.buildkite/pull_requests.json b/.buildkite/pull_requests.json
index 0758e0255247f..1f45c01042888 100644
--- a/.buildkite/pull_requests.json
+++ b/.buildkite/pull_requests.json
@@ -8,6 +8,7 @@
"enabled": true,
"allow_org_users": true,
"allowed_repo_permissions": ["admin", "write"],
+ "allowed_list": ["elastic-vault-github-plugin-prod[bot]"],
"set_commit_status": true,
"commit_status_context": "kibana-ci",
"build_on_commit": true,
From a5a7f1554b731f98e51a18efccdbf6e771077144 Mon Sep 17 00:00:00 2001
From: Or Ouziel
Date: Mon, 16 Sep 2024 16:38:42 +0300
Subject: [PATCH 020/139] [Cloud Security] Fix GCP service account deployment
copy-paste command (#192959)
---
.../gcp_credentials_form/gcp_credentials_form_agentless.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/gcp_credentials_form/gcp_credentials_form_agentless.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/gcp_credentials_form/gcp_credentials_form_agentless.tsx
index 9cced3c87729b..4cec65cc695bb 100644
--- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/gcp_credentials_form/gcp_credentials_form_agentless.tsx
+++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/gcp_credentials_form/gcp_credentials_form_agentless.tsx
@@ -194,8 +194,8 @@ export const GcpCredentialsFormAgentless = ({
)?.replace(TEMPLATE_URL_ACCOUNT_TYPE_ENV_VAR, accountType);
const commandText = `gcloud config set project ${
- isOrganization ? ` && ORD_ID=` : ``
- } && ./deploy_service_account.sh`;
+ isOrganization ? ` && ORG_ID=` : ``
+ } ./deploy_service_account.sh`;
return (
<>
From e404a3992e220735ae51918c532a1a032e7f7993 Mon Sep 17 00:00:00 2001
From: Drew Tate
Date: Mon, 16 Sep 2024 07:41:38 -0600
Subject: [PATCH 021/139] [ES|QL] open suggestions automatically in sources
lists and `ENRICH` (#191312)
## Summary
Part of https://github.com/elastic/kibana/issues/189662
Also, a follow-on to https://github.com/elastic/kibana/issues/187184
with certain source names (e.g. `foo$bar`, `my-policy`) and fields in
the `ENRICH` command.
During this effort I discovered
https://github.com/elastic/kibana/issues/191321 and a bug with the
validation of field names in the "ENRICH ... WITH" list (documented
[here](https://github.com/elastic/kibana/issues/177699)), both of which
will be addressed separately.
## Sources
### General flow
https://github.com/user-attachments/assets/4b103621-0e66-4c36-807f-4932f0cb8faf
### Works with wild-card matches
https://github.com/user-attachments/assets/6b47fffc-e922-4e2d-b6aa-3d9a2fc2236c
### `METADATA` field list
https://github.com/user-attachments/assets/d3bdf4dc-1d0c-4d56-81d7-af6bc4e25a4a
## ENRICH
Autosuggest now helps you along
https://github.com/user-attachments/assets/d627484c-e729-4dc7-9e7b-795395a31d4f
Also, fixed this bug (follow on to
https://github.com/elastic/kibana/issues/187184 )
https://github.com/user-attachments/assets/aa62a0c3-6db5-434a-829a-59f14c5c4c85
### Checklist
- [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
---------
Co-authored-by: Elastic Machine
Co-authored-by: Stratoula Kalafateli
---
packages/kbn-esql-ast/src/ast_helpers.ts | 2 +-
.../src/utils/query_parsing_helpers.ts | 2 +-
.../autocomplete.command.from.test.ts | 26 +-
.../src/autocomplete/__tests__/helpers.ts | 7 +-
.../src/autocomplete/autocomplete.test.ts | 425 +++++++++++++-----
.../src/autocomplete/autocomplete.ts | 195 +++++---
.../src/autocomplete/factories.ts | 9 +-
.../src/autocomplete/helper.ts | 4 +-
.../src/shared/helpers.ts | 10 +
.../src/shared/types.ts | 19 +-
10 files changed, 491 insertions(+), 208 deletions(-)
diff --git a/packages/kbn-esql-ast/src/ast_helpers.ts b/packages/kbn-esql-ast/src/ast_helpers.ts
index 7dff8cda3566a..76f576f1ec019 100644
--- a/packages/kbn-esql-ast/src/ast_helpers.ts
+++ b/packages/kbn-esql-ast/src/ast_helpers.ts
@@ -405,9 +405,9 @@ export function createSource(
index,
name: text,
sourceType: type,
- text,
location: getPosition(ctx.start, ctx.stop),
incomplete: Boolean(ctx.exception || text === ''),
+ text: ctx?.getText(),
};
}
diff --git a/packages/kbn-esql-utils/src/utils/query_parsing_helpers.ts b/packages/kbn-esql-utils/src/utils/query_parsing_helpers.ts
index 734acac70fd7d..53ce6e06bb536 100644
--- a/packages/kbn-esql-utils/src/utils/query_parsing_helpers.ts
+++ b/packages/kbn-esql-utils/src/utils/query_parsing_helpers.ts
@@ -24,7 +24,7 @@ export function getIndexPatternFromESQLQuery(esql?: string) {
const sourceCommand = ast.find(({ name }) => ['from', 'metrics'].includes(name));
const args = (sourceCommand?.args ?? []) as ESQLSource[];
const indices = args.filter((arg) => arg.sourceType === 'index');
- return indices?.map((index) => index.text).join(',');
+ return indices?.map((index) => index.name).join(',');
}
// For ES|QL we consider stats and keep transformational command
diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.from.test.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.from.test.ts
index 4d37ca078dd88..fa2a969384a09 100644
--- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.from.test.ts
+++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.from.test.ts
@@ -15,9 +15,6 @@ const visibleIndices = indexes
.map(({ name, suggestedAs }) => suggestedAs || name)
.sort();
-const addTrailingSpace = (strings: string[], predicate: (s: string) => boolean = (_s) => true) =>
- strings.map((string) => (predicate(string) ? `${string} ` : string));
-
const metadataFields = [...METADATA_FIELDS].sort();
describe('autocomplete.suggest', () => {
@@ -37,17 +34,17 @@ describe('autocomplete.suggest', () => {
test('suggests visible indices on space', async () => {
const { assertSuggestions } = await setup();
- await assertSuggestions('from /', addTrailingSpace(visibleIndices));
- await assertSuggestions('FROM /', addTrailingSpace(visibleIndices));
- await assertSuggestions('from /index', addTrailingSpace(visibleIndices));
+ await assertSuggestions('from /', visibleIndices);
+ await assertSuggestions('FROM /', visibleIndices);
+ await assertSuggestions('from /index', visibleIndices);
});
test('suggests visible indices on comma', async () => {
const { assertSuggestions } = await setup();
- await assertSuggestions('FROM a,/', addTrailingSpace(visibleIndices));
- await assertSuggestions('FROM a, /', addTrailingSpace(visibleIndices));
- await assertSuggestions('from *,/', addTrailingSpace(visibleIndices));
+ await assertSuggestions('FROM a,/', visibleIndices);
+ await assertSuggestions('FROM a, /', visibleIndices);
+ await assertSuggestions('from *,/', visibleIndices);
});
test('can suggest integration data sources', async () => {
@@ -56,10 +53,7 @@ describe('autocomplete.suggest', () => {
.filter(({ hidden }) => !hidden)
.map(({ name, suggestedAs }) => suggestedAs || name)
.sort();
- const expectedSuggestions = addTrailingSpace(
- visibleDataSources,
- (s) => !integrations.find(({ name }) => name === s)
- );
+ const expectedSuggestions = visibleDataSources;
const { assertSuggestions, callbacks } = await setup();
const cb = {
...callbacks,
@@ -75,7 +69,7 @@ describe('autocomplete.suggest', () => {
});
describe('... METADATA ', () => {
- const metadataFieldsSandIndex = metadataFields.filter((field) => field !== '_index');
+ const metadataFieldsAndIndex = metadataFields.filter((field) => field !== '_index');
test('on SPACE without comma ",", suggests adding metadata', async () => {
const { assertSuggestions } = await setup();
@@ -103,8 +97,8 @@ describe('autocomplete.suggest', () => {
test('filters out already used metadata fields', async () => {
const { assertSuggestions } = await setup();
- await assertSuggestions('from a, b [metadata _index, /]', metadataFieldsSandIndex);
- await assertSuggestions('from a, b metadata _index, /', metadataFieldsSandIndex);
+ await assertSuggestions('from a, b [metadata _index, /]', metadataFieldsAndIndex);
+ await assertSuggestions('from a, b metadata _index, /', metadataFieldsAndIndex);
});
});
});
diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/helpers.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/helpers.ts
index 70b1d717f6e4e..7d09f2c82c4b7 100644
--- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/helpers.ts
+++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/helpers.ts
@@ -17,7 +17,7 @@ import { groupingFunctionDefinitions } from '../../definitions/grouping';
import * as autocomplete from '../autocomplete';
import type { ESQLCallbacks } from '../../shared/types';
import type { EditorContext, SuggestionRawDefinition } from '../types';
-import { TIME_SYSTEM_PARAMS } from '../factories';
+import { TIME_SYSTEM_PARAMS, getSafeInsertText } from '../factories';
import { getFunctionSignatures } from '../../definitions/helpers';
import { ESQLRealField } from '../../validation/types';
import {
@@ -280,10 +280,7 @@ export function createCompletionContext(triggerCharacter?: string) {
export function getPolicyFields(policyName: string) {
return policies
.filter(({ name }) => name === policyName)
- .flatMap(({ enrichFields }) =>
- // ok, this is a bit of cheating as it's using the same logic as in the helper
- enrichFields.map((field) => (/[^a-zA-Z\d_\.@]/.test(field) ? `\`${field}\`` : field))
- );
+ .flatMap(({ enrichFields }) => enrichFields.map((field) => getSafeInsertText(field)));
}
export interface SuggestOptions {
diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts
index 80ff7148f8623..a58a55f124c4e 100644
--- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts
+++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts
@@ -392,27 +392,44 @@ describe('autocomplete', () => {
describe('enrich', () => {
const modes = ['any', 'coordinator', 'remote'];
- const policyNames = policies.map(({ name, suggestedAs }) => suggestedAs || name);
+ const expectedPolicyNameSuggestions = policies
+ .map(({ name, suggestedAs }) => suggestedAs || name)
+ .map((name) => `${name} `);
for (const prevCommand of [
'',
// '| enrich other-policy ',
// '| enrich other-policy on b ',
// '| enrich other-policy with c ',
]) {
- testSuggestions(`from a ${prevCommand}| enrich /`, policyNames);
+ testSuggestions(`from a ${prevCommand}| enrich /`, expectedPolicyNameSuggestions);
testSuggestions(
`from a ${prevCommand}| enrich _/`,
modes.map((mode) => `_${mode}:$0`),
'_'
);
for (const mode of modes) {
- testSuggestions(`from a ${prevCommand}| enrich _${mode}:/`, policyNames, ':');
- testSuggestions(`from a ${prevCommand}| enrich _${mode.toUpperCase()}:/`, policyNames, ':');
- testSuggestions(`from a ${prevCommand}| enrich _${camelCase(mode)}:/`, policyNames, ':');
+ testSuggestions(
+ `from a ${prevCommand}| enrich _${mode}:/`,
+ expectedPolicyNameSuggestions,
+ ':'
+ );
+ testSuggestions(
+ `from a ${prevCommand}| enrich _${mode.toUpperCase()}:/`,
+ expectedPolicyNameSuggestions,
+ ':'
+ );
+ testSuggestions(
+ `from a ${prevCommand}| enrich _${camelCase(mode)}:/`,
+ expectedPolicyNameSuggestions,
+ ':'
+ );
}
testSuggestions(`from a ${prevCommand}| enrich policy /`, ['ON $0', 'WITH $0', '| ']);
- testSuggestions(`from a ${prevCommand}| enrich policy on /`, getFieldNamesByType('any'));
- testSuggestions(`from a ${prevCommand}| enrich policy on b /`, ['WITH $0', ',', '| ']);
+ testSuggestions(
+ `from a ${prevCommand}| enrich policy on /`,
+ getFieldNamesByType('any').map((v) => `${v} `)
+ );
+ testSuggestions(`from a ${prevCommand}| enrich policy on b /`, ['WITH $0', '| ']);
testSuggestions(
`from a ${prevCommand}| enrich policy on b with /`,
['var0 = ', ...getPolicyFields('policy')],
@@ -453,14 +470,8 @@ describe('autocomplete', () => {
// @TODO: get updated eval block from main
describe('values suggestions', () => {
- testSuggestions('FROM "a/"', ['a ', 'b '], undefined, [
- ,
- [
- { name: 'a', hidden: false },
- { name: 'b', hidden: false },
- ],
- ]);
- testSuggestions('FROM " /"', [], ' ');
+ testSuggestions('FROM "i/"', ['index'], undefined, [, [{ name: 'index', hidden: false }]]);
+ testSuggestions('FROM "index/"', ['index'], undefined, [, [{ name: 'index', hidden: false }]]);
// TODO — re-enable these tests when we can support this case
testSuggestions.skip('FROM " a/"', []);
testSuggestions.skip('FROM "foo b/"', []);
@@ -564,7 +575,7 @@ describe('autocomplete', () => {
});
// FROM source
- testSuggestions('FROM k/', ['index1 ', 'index2 '], undefined, [
+ testSuggestions('FROM k/', ['index1', 'index2'], undefined, [
,
[
{ name: 'index1', hidden: false },
@@ -604,14 +615,17 @@ describe('autocomplete', () => {
// ENRICH policy
testSuggestions(
'FROM index1 | ENRICH p/',
- policies.map(({ name }) => getSafeInsertText(name))
+ policies.map(({ name }) => getSafeInsertText(name) + ' ')
);
// ENRICH policy ON
testSuggestions('FROM index1 | ENRICH policy O/', ['ON $0', 'WITH $0', '| ']);
// ENRICH policy ON field
- testSuggestions('FROM index1 | ENRICH policy ON f/', getFieldNamesByType('any'));
+ testSuggestions(
+ 'FROM index1 | ENRICH policy ON f/',
+ getFieldNamesByType('any').map((name) => `${name} `)
+ );
// ENRICH policy WITH policyfield
testSuggestions('FROM index1 | ENRICH policy WITH v/', [
@@ -714,6 +728,10 @@ describe('autocomplete', () => {
});
describe('advancing the cursor and opening the suggestion menu automatically ✨', () => {
+ /**
+ * NOTE: Monaco uses an Invoke trigger kind when the show suggestions action is triggered (e.g. accepting the "FROM" suggestion)
+ */
+
const attachTriggerCommand = (
s: string | PartialSuggestionWithText
): PartialSuggestionWithText =>
@@ -819,24 +837,122 @@ describe('autocomplete', () => {
]);
// FROM source
- //
- // Using an Invoke trigger kind here because that's what Monaco uses when the show suggestions
- // action is triggered (e.g. accepting the "FROM" suggestion)
- testSuggestions(
- 'FROM /',
- [
- { text: 'index1 ', command: TRIGGER_SUGGESTION_COMMAND },
- { text: 'index2 ', command: TRIGGER_SUGGESTION_COMMAND },
- ],
- undefined,
- [
- ,
+ describe('sources', () => {
+ testSuggestions(
+ 'FROM /',
[
- { name: 'index1', hidden: false },
- { name: 'index2', hidden: false },
+ { text: 'index1', command: TRIGGER_SUGGESTION_COMMAND },
+ { text: 'index2', command: TRIGGER_SUGGESTION_COMMAND },
],
- ]
- );
+ undefined,
+ [
+ ,
+ [
+ { name: 'index1', hidden: false },
+ { name: 'index2', hidden: false },
+ ],
+ ]
+ );
+
+ testSuggestions(
+ 'FROM index/',
+ [
+ { text: 'index1', command: TRIGGER_SUGGESTION_COMMAND },
+ { text: 'index2', command: TRIGGER_SUGGESTION_COMMAND },
+ ],
+ undefined,
+ [
+ ,
+ [
+ { name: 'index1', hidden: false },
+ { name: 'index2', hidden: false },
+ ],
+ ]
+ );
+
+ testSuggestions(
+ 'FROM index1/',
+ [
+ { text: 'index1 | ', filterText: 'index1', command: TRIGGER_SUGGESTION_COMMAND },
+ { text: 'index1, ', filterText: 'index1', command: TRIGGER_SUGGESTION_COMMAND },
+ { text: 'index1 METADATA ', filterText: 'index1', command: TRIGGER_SUGGESTION_COMMAND },
+ ],
+ undefined,
+ [
+ ,
+ [
+ { name: 'index1', hidden: false },
+ { name: 'index2', hidden: false },
+ ],
+ ]
+ );
+
+ testSuggestions(
+ 'FROM index1, index2/',
+ [
+ { text: 'index2 | ', filterText: 'index2', command: TRIGGER_SUGGESTION_COMMAND },
+ { text: 'index2, ', filterText: 'index2', command: TRIGGER_SUGGESTION_COMMAND },
+ { text: 'index2 METADATA ', filterText: 'index2', command: TRIGGER_SUGGESTION_COMMAND },
+ ],
+ undefined,
+ [
+ ,
+ [
+ { name: 'index1', hidden: false },
+ { name: 'index2', hidden: false },
+ ],
+ ]
+ );
+
+ // This is a source name that contains a special character
+ // meaning that Monaco by default will only set the replacement
+ // range to cover "bar" and not "foo$bar". We have to make sure
+ // we're setting it ourselves.
+ testSuggestions(
+ 'FROM foo$bar/',
+ [
+ {
+ text: 'foo$bar | ',
+ filterText: 'foo$bar',
+ command: TRIGGER_SUGGESTION_COMMAND,
+ rangeToReplace: { start: 6, end: 13 },
+ },
+ {
+ text: 'foo$bar, ',
+ filterText: 'foo$bar',
+ command: TRIGGER_SUGGESTION_COMMAND,
+ rangeToReplace: { start: 6, end: 13 },
+ },
+ {
+ text: 'foo$bar METADATA ',
+ filterText: 'foo$bar',
+ asSnippet: false, // important because the text includes "$"
+ command: TRIGGER_SUGGESTION_COMMAND,
+ rangeToReplace: { start: 6, end: 13 },
+ },
+ ],
+ undefined,
+ [, [{ name: 'foo$bar', hidden: false }]]
+ );
+
+ // This is an identifier that matches multiple sources
+ testSuggestions(
+ 'FROM i*/',
+ [
+ { text: 'i* | ', filterText: 'i*', command: TRIGGER_SUGGESTION_COMMAND },
+ { text: 'i*, ', filterText: 'i*', command: TRIGGER_SUGGESTION_COMMAND },
+ { text: 'i* METADATA ', filterText: 'i*', command: TRIGGER_SUGGESTION_COMMAND },
+ ],
+ undefined,
+ [
+ ,
+ [
+ { name: 'index1', hidden: false },
+ { name: 'index2', hidden: false },
+ ],
+ ]
+ );
+ });
// FROM source METADATA
testSuggestions('FROM index1 M/', [
@@ -845,6 +961,57 @@ describe('autocomplete', () => {
'| ',
]);
+ describe('ENRICH', () => {
+ testSuggestions(
+ 'FROM a | ENRICH /',
+ policies.map((p) => `${getSafeInsertText(p.name)} `).map(attachTriggerCommand)
+ );
+ testSuggestions(
+ 'FROM a | ENRICH pol/',
+ policies
+ .map((p) => `${getSafeInsertText(p.name)} `)
+ .map(attachTriggerCommand)
+ .map((s) => ({ ...s, rangeToReplace: { start: 17, end: 20 } }))
+ );
+ testSuggestions(
+ 'FROM a | ENRICH policy /',
+ ['ON $0', 'WITH $0', '| '].map(attachTriggerCommand)
+ );
+ testSuggestions(
+ 'FROM a | ENRICH policy ON /',
+ getFieldNamesByType('any')
+ .map((name) => `${name} `)
+ .map(attachTriggerCommand)
+ );
+ testSuggestions(
+ 'FROM a | ENRICH policy ON @timestamp /',
+ ['WITH $0', '| '].map(attachTriggerCommand)
+ );
+ // nothing fancy with this field list
+ testSuggestions('FROM a | ENRICH policy ON @timestamp WITH /', [
+ 'var0 = ',
+ ...getPolicyFields('policy').map((name) => ({ text: name, command: undefined })),
+ ]);
+ describe('replacement range', () => {
+ testSuggestions('FROM a | ENRICH policy ON @timestamp WITH othe/', [
+ 'var0 = ',
+ ...getPolicyFields('policy').map((name) => ({
+ text: name,
+ command: undefined,
+ rangeToReplace: { start: 43, end: 47 },
+ })),
+ ]);
+ testSuggestions(
+ 'FROM a | ENRICH policy ON @timestamp WITH var0 = othe/',
+ getPolicyFields('policy').map((name) => ({
+ text: name,
+ command: undefined,
+ rangeToReplace: { start: 50, end: 54 },
+ }))
+ );
+ });
+ });
+
// LIMIT number
testSuggestions('FROM a | LIMIT /', ['10 ', '100 ', '1000 '].map(attachTriggerCommand));
@@ -937,80 +1104,126 @@ describe('autocomplete', () => {
['keyword']
).map((s) => (s.text.toLowerCase().includes('null') ? s : attachTriggerCommand(s)))
);
- describe('field lists', () => {
- // KEEP field
- testSuggestions('FROM a | KEEP /', getFieldNamesByType('any').map(attachTriggerCommand));
- testSuggestions(
- 'FROM a | KEEP d/',
- getFieldNamesByType('any')
- .map((text) => ({
- text,
- rangeToReplace: { start: 15, end: 16 },
- }))
- .map(attachTriggerCommand)
- );
- testSuggestions(
- 'FROM a | KEEP doubleFiel/',
- getFieldNamesByType('any').map(attachTriggerCommand)
- );
- testSuggestions(
- 'FROM a | KEEP doubleField/',
- ['doubleField, ', 'doubleField | ']
- .map((text) => ({
- text,
- filterText: 'doubleField',
- rangeToReplace: { start: 15, end: 26 },
- }))
- .map(attachTriggerCommand)
- );
- testSuggestions('FROM a | KEEP doubleField /', ['| ', ',']);
- // Let's get funky with the field names
- testSuggestions(
- 'FROM a | KEEP @timestamp/',
- ['@timestamp, ', '@timestamp | ']
- .map((text) => ({
- text,
- filterText: '@timestamp',
- rangeToReplace: { start: 15, end: 25 },
- }))
- .map(attachTriggerCommand),
- undefined,
- [[{ name: '@timestamp', type: 'date' }]]
- );
- testSuggestions(
- 'FROM a | KEEP foo.bar/',
- ['foo.bar, ', 'foo.bar | ']
- .map((text) => ({
- text,
- filterText: 'foo.bar',
- rangeToReplace: { start: 15, end: 22 },
- }))
- .map(attachTriggerCommand),
- undefined,
- [[{ name: 'foo.bar', type: 'double' }]]
- );
-
- // @todo re-enable these tests when we can use AST to support this case
- testSuggestions.skip('FROM a | KEEP `foo.bar`/', ['foo.bar, ', 'foo.bar | '], undefined, [
- [{ name: 'foo.bar', type: 'double' }],
- ]);
- testSuggestions.skip('FROM a | KEEP `foo`.`bar`/', ['foo.bar, ', 'foo.bar | '], undefined, [
- [{ name: 'foo.bar', type: 'double' }],
- ]);
- testSuggestions.skip('FROM a | KEEP `any#Char$Field`/', [
- '`any#Char$Field`, ',
- '`any#Char$Field` | ',
- ]);
+ describe('field lists', () => {
+ describe('METADATA ', () => {
+ // METADATA field
+ testSuggestions('FROM a METADATA /', METADATA_FIELDS.map(attachTriggerCommand));
+ testSuggestions('FROM a METADATA _i/', METADATA_FIELDS.map(attachTriggerCommand));
+ testSuggestions(
+ 'FROM a METADATA _id/',
+ [
+ { filterText: '_id', text: '_id, ' },
+ { filterText: '_id', text: '_id | ' },
+ ].map(attachTriggerCommand)
+ );
+ testSuggestions(
+ 'FROM a METADATA _id, /',
+ METADATA_FIELDS.filter((field) => field !== '_id').map(attachTriggerCommand)
+ );
+ testSuggestions(
+ 'FROM a METADATA _id, _ignored/',
+ [
+ { filterText: '_ignored', text: '_ignored, ' },
+ { filterText: '_ignored', text: '_ignored | ' },
+ ].map(attachTriggerCommand)
+ );
+ // comma if there's even one more field
+ testSuggestions('FROM a METADATA _id, _ignored, _index, _source/', [
+ { filterText: '_source', text: '_source | ', command: TRIGGER_SUGGESTION_COMMAND },
+ { filterText: '_source', text: '_source, ', command: TRIGGER_SUGGESTION_COMMAND },
+ ]);
+ // no comma if there are no more fields
+ testSuggestions('FROM a METADATA _id, _ignored, _index, _source, _version/', [
+ { filterText: '_version', text: '_version | ', command: TRIGGER_SUGGESTION_COMMAND },
+ ]);
+ });
- // Subsequent fields
- testSuggestions(
- 'FROM a | KEEP doubleField, dateFiel/',
- getFieldNamesByType('any')
- .filter((s) => s !== 'doubleField')
- .map(attachTriggerCommand)
- );
- testSuggestions('FROM a | KEEP doubleField, dateField/', ['dateField, ', 'dateField | ']);
+ describe('KEEP ', () => {
+ // KEEP field
+ testSuggestions('FROM a | KEEP /', getFieldNamesByType('any').map(attachTriggerCommand));
+ testSuggestions(
+ 'FROM a | KEEP d/',
+ getFieldNamesByType('any')
+ .map((text) => ({
+ text,
+ rangeToReplace: { start: 15, end: 16 },
+ }))
+ .map(attachTriggerCommand)
+ );
+ testSuggestions(
+ 'FROM a | KEEP doubleFiel/',
+ getFieldNamesByType('any').map(attachTriggerCommand)
+ );
+ testSuggestions(
+ 'FROM a | KEEP doubleField/',
+ ['doubleField, ', 'doubleField | ']
+ .map((text) => ({
+ text,
+ filterText: 'doubleField',
+ rangeToReplace: { start: 15, end: 26 },
+ }))
+ .map(attachTriggerCommand)
+ );
+ testSuggestions('FROM a | KEEP doubleField /', ['| ', ',']);
+
+ // Let's get funky with the field names
+ testSuggestions(
+ 'FROM a | KEEP @timestamp/',
+ ['@timestamp, ', '@timestamp | ']
+ .map((text) => ({
+ text,
+ filterText: '@timestamp',
+ rangeToReplace: { start: 15, end: 25 },
+ }))
+ .map(attachTriggerCommand),
+ undefined,
+ [[{ name: '@timestamp', type: 'date' }]]
+ );
+ testSuggestions(
+ 'FROM a | KEEP foo.bar/',
+ ['foo.bar, ', 'foo.bar | ']
+ .map((text) => ({
+ text,
+ filterText: 'foo.bar',
+ rangeToReplace: { start: 15, end: 22 },
+ }))
+ .map(attachTriggerCommand),
+ undefined,
+ [[{ name: 'foo.bar', type: 'double' }]]
+ );
+
+ describe('escaped field names', () => {
+ // This isn't actually the behavior we want, but this test is here
+ // to make sure no weird suggestions start cropping up in this case.
+ testSuggestions('FROM a | KEEP `foo.bar`/', ['foo.bar'], undefined, [
+ [{ name: 'foo.bar', type: 'double' }],
+ ]);
+ // @todo re-enable these tests when we can use AST to support this case
+ testSuggestions.skip('FROM a | KEEP `foo.bar`/', ['foo.bar, ', 'foo.bar | '], undefined, [
+ [{ name: 'foo.bar', type: 'double' }],
+ ]);
+ testSuggestions.skip(
+ 'FROM a | KEEP `foo`.`bar`/',
+ ['foo.bar, ', 'foo.bar | '],
+ undefined,
+ [[{ name: 'foo.bar', type: 'double' }]]
+ );
+ testSuggestions.skip('FROM a | KEEP `any#Char$Field`/', [
+ '`any#Char$Field`, ',
+ '`any#Char$Field` | ',
+ ]);
+ });
+
+ // Subsequent fields
+ testSuggestions(
+ 'FROM a | KEEP doubleField, dateFiel/',
+ getFieldNamesByType('any')
+ .filter((s) => s !== 'doubleField')
+ .map(attachTriggerCommand)
+ );
+ testSuggestions('FROM a | KEEP doubleField, dateField/', ['dateField, ', 'dateField | ']);
+ });
});
});
diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts
index f80ebf862c0cf..a1be88b0b1436 100644
--- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts
+++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts
@@ -47,6 +47,8 @@ import {
noCaseCompare,
correctQuerySyntax,
getColumnByName,
+ sourceExists,
+ findFinalWord,
} from '../shared/helpers';
import { collectVariables, excludeVariablesFromCurrentCommand } from '../shared/variables';
import type { ESQLPolicy, ESQLRealField, ESQLVariable, ReferenceMaps } from '../validation/types';
@@ -89,7 +91,7 @@ import {
getPolicyHelper,
getSourcesHelper,
} from '../shared/resources_helpers';
-import { ESQLCallbacks } from '../shared/types';
+import { ESQLCallbacks, ESQLSourceResult } from '../shared/types';
import {
getFunctionsToIgnoreForStats,
getOverlapRange,
@@ -107,12 +109,9 @@ import {
isParameterType,
isReturnType,
} from '../definitions/types';
+import { metadataOption } from '../definitions/options';
import { comparisonFunctions } from '../definitions/builtin';
-type GetSourceFn = () => Promise;
-type GetDataStreamsForIntegrationFn = (
- sourceName: string
-) => Promise | undefined>;
type GetFieldsByTypeFn = (
type: string | string[],
ignored?: string[],
@@ -178,8 +177,7 @@ export async function suggest(
queryForFields,
resourceRetriever
);
- const getSources = getSourcesRetriever(resourceRetriever);
- const getDatastreamsForIntegration = getDatastreamsForIntegrationRetriever(resourceRetriever);
+ const getSources = getSourcesHelper(resourceRetriever);
const { getPolicies, getPolicyMetadata } = getPolicyRetriever(resourceRetriever);
if (astContext.type === 'newCommand') {
@@ -201,7 +199,6 @@ export async function suggest(
ast,
astContext,
getSources,
- getDatastreamsForIntegration,
getFieldsByType,
getFieldsMap,
getPolicies,
@@ -287,29 +284,15 @@ function getPolicyRetriever(resourceRetriever?: ESQLCallbacks) {
};
}
-function getSourcesRetriever(resourceRetriever?: ESQLCallbacks) {
- const helper = getSourcesHelper(resourceRetriever);
- return async () => {
- const list = (await helper()) || [];
- // hide indexes that start with .
- return buildSourcesDefinitions(
- list
- .filter(({ hidden }) => !hidden)
- .map(({ name, dataStreams, title, type }) => {
- return { name, isIntegration: Boolean(dataStreams && dataStreams.length), title, type };
- })
- );
- };
-}
-
-function getDatastreamsForIntegrationRetriever(
- resourceRetriever?: ESQLCallbacks
-): GetDataStreamsForIntegrationFn {
- const helper = getSourcesHelper(resourceRetriever);
- return async (sourceName: string) => {
- const list = (await helper()) || [];
- return list.find(({ name }) => name === sourceName)?.dataStreams;
- };
+function getSourceSuggestions(sources: ESQLSourceResult[]) {
+ // hide indexes that start with .
+ return buildSourcesDefinitions(
+ sources
+ .filter(({ hidden }) => !hidden)
+ .map(({ name, dataStreams, title, type }) => {
+ return { name, isIntegration: Boolean(dataStreams && dataStreams.length), title, type };
+ })
+ );
}
function findNewVariable(variables: Map) {
@@ -487,8 +470,7 @@ async function getExpressionSuggestionsByType(
option: ESQLCommandOption | undefined;
node: ESQLSingleAstItem | undefined;
},
- getSources: GetSourceFn,
- getDatastreamsForIntegration: GetDataStreamsForIntegrationFn,
+ getSources: () => Promise,
getFieldsByType: GetFieldsByTypeFn,
getFieldsMap: GetFieldsMapFn,
getPolicies: GetPoliciesFn,
@@ -626,13 +608,12 @@ async function getExpressionSuggestionsByType(
);
/**
- * @TODO — this string manipulation is crude and can't support all cases
+ * @TODO — this string scanning is crude and can't support all cases
* Checking for a partial word and computing the replacement range should
* really be done using the AST node, but we'll have to refactor further upstream
* to make that available. This is a quick fix to support the most common case.
*/
- const words = innerText.split(/\s+/);
- const lastWord = words[words.length - 1];
+ const lastWord = findFinalWord(innerText);
if (lastWord !== '') {
// ... |
@@ -649,16 +630,13 @@ async function getExpressionSuggestionsByType(
}
// now we know that the user has already entered a column,
// so suggest comma and pipe
- // const NON_ALPHANUMERIC_REGEXP = /[^a-zA-Z\d]/g;
- // const textToUse = lastWord.replace(NON_ALPHANUMERIC_REGEXP, '');
- const textToUse = lastWord;
return [
{ ...pipeCompleteItem, text: ' | ' },
{ ...commaCompleteItem, text: ', ' },
].map((s) => ({
...s,
- filterText: textToUse,
- text: textToUse + s.text,
+ filterText: lastWord,
+ text: lastWord + s.text,
command: TRIGGER_SUGGESTION_COMMAND,
rangeToReplace,
}));
@@ -912,9 +890,22 @@ async function getExpressionSuggestionsByType(
if (argDef.innerTypes?.includes('policy')) {
// ... | ENRICH
const policies = await getPolicies();
+ const lastWord = findFinalWord(innerText);
+ if (lastWord !== '') {
+ policies.forEach((suggestion) => {
+ suggestions.push({
+ ...suggestion,
+ rangeToReplace: {
+ start: innerText.length - lastWord.length + 1,
+ end: innerText.length + 1,
+ },
+ });
+ });
+ }
suggestions.push(...(policies.length ? policies : [buildNoPoliciesAvailableDefinition()]));
} else {
- const index = getSourcesFromCommands(commands, 'index');
+ const indexes = getSourcesFromCommands(commands, 'index');
+ const lastIndex = indexes[indexes.length - 1];
const canRemoveQuote = isNewExpression && innerText.includes('"');
// Function to add suggestions based on canRemoveQuote
const addSuggestionsBasedOnQuote = async (definitions: SuggestionRawDefinition[]) => {
@@ -923,24 +914,58 @@ async function getExpressionSuggestionsByType(
);
};
- if (index && index.text && index.text !== EDITOR_MARKER) {
- const source = index.text.replace(EDITOR_MARKER, '');
- const dataStreams = await getDatastreamsForIntegration(source);
+ if (lastIndex && lastIndex.text && lastIndex.text !== EDITOR_MARKER) {
+ const sources = await getSources();
+ const sourceIdentifier = lastIndex.text.replace(EDITOR_MARKER, '');
+ if (sourceExists(sourceIdentifier, new Set(sources.map(({ name }) => name)))) {
+ const exactMatch = sources.find(({ name: _name }) => _name === sourceIdentifier);
+ if (exactMatch?.dataStreams) {
+ // this is an integration name, suggest the datastreams
+ addSuggestionsBasedOnQuote(
+ buildSourcesDefinitions(
+ exactMatch.dataStreams.map(({ name }) => ({ name, isIntegration: false }))
+ )
+ );
+ } else {
+ // this is a complete source name
+ const rangeToReplace = {
+ start: innerText.length - sourceIdentifier.length + 1,
+ end: innerText.length + 1,
+ };
- if (dataStreams) {
- // Integration name, suggest the datastreams
- await addSuggestionsBasedOnQuote(
- buildSourcesDefinitions(
- dataStreams.map(({ name }) => ({ name, isIntegration: false }))
- )
- );
+ const suggestionsToAdd: SuggestionRawDefinition[] = [
+ {
+ ...pipeCompleteItem,
+ filterText: sourceIdentifier,
+ text: sourceIdentifier + ' | ',
+ command: TRIGGER_SUGGESTION_COMMAND,
+ rangeToReplace,
+ },
+ {
+ ...commaCompleteItem,
+ filterText: sourceIdentifier,
+ text: sourceIdentifier + ', ',
+ command: TRIGGER_SUGGESTION_COMMAND,
+ rangeToReplace,
+ },
+ {
+ ...buildOptionDefinition(metadataOption),
+ filterText: sourceIdentifier,
+ text: sourceIdentifier + ' METADATA ',
+ asSnippet: false, // turn this off because $ could be contained within the source name
+ rangeToReplace,
+ },
+ ];
+
+ addSuggestionsBasedOnQuote(suggestionsToAdd);
+ }
} else {
- // Not an integration, just a partial source name
- await addSuggestionsBasedOnQuote(await getSources());
+ // Just a partial source name
+ await addSuggestionsBasedOnQuote(getSourceSuggestions(sources));
}
} else {
// FROM or no index/text
- await addSuggestionsBasedOnQuote(await getSources());
+ await addSuggestionsBasedOnQuote(getSourceSuggestions(await getSources()));
}
}
}
@@ -1541,7 +1566,7 @@ async function getOptionArgsSuggestions(
// if it's a new expression, suggest fields to match on
if (
isNewExpression ||
- findPreviousWord(innerText) === 'ON' ||
+ noCaseCompare(findPreviousWord(innerText), 'ON') ||
(option && isAssignment(option.args[0]) && !option.args[1])
) {
const policyName = isSourceItem(command.args[0]) ? command.args[0].name : undefined;
@@ -1561,7 +1586,7 @@ async function getOptionArgsSuggestions(
suggestions.push(
buildOptionDefinition(getCommandOption('with')!),
...getFinalSuggestions({
- comma: true,
+ comma: false,
})
);
}
@@ -1588,7 +1613,23 @@ async function getOptionArgsSuggestions(
if (policyMetadata) {
if (isNewExpression || (assignFn && !isAssignmentComplete(assignFn))) {
// ... | ENRICH ... WITH a =
- suggestions.push(...buildFieldsDefinitions(policyMetadata.enrichFields));
+ // ... | ENRICH ... WITH b
+ const fieldSuggestions = buildFieldsDefinitions(policyMetadata.enrichFields);
+ // in this case, we don't want to open the suggestions menu when the field is accepted
+ // because we're keeping the suggestions simple here for now. Could always revisit.
+ fieldSuggestions.forEach((s) => (s.command = undefined));
+
+ // attach the replacement range if needed
+ const lastWord = findFinalWord(innerText);
+ if (lastWord) {
+ // ENRICH ... WITH a
+ const rangeToReplace = {
+ start: innerText.length - lastWord.length + 1,
+ end: innerText.length + 1,
+ };
+ fieldSuggestions.forEach((s) => (s.rangeToReplace = rangeToReplace));
+ }
+ suggestions.push(...fieldSuggestions);
}
}
@@ -1640,13 +1681,41 @@ async function getOptionArgsSuggestions(
if (option.name === 'metadata') {
const existingFields = new Set(option.args.filter(isColumnItem).map(({ name }) => name));
const filteredMetaFields = METADATA_FIELDS.filter((name) => !existingFields.has(name));
- if (isNewExpression) {
+ const lastWord = findFinalWord(innerText);
+ if (lastWord) {
+ // METADATA something
+ const isField = METADATA_FIELDS.includes(lastWord);
+ if (isField) {
+ // METADATA field
+ suggestions.push({
+ ...pipeCompleteItem,
+ text: lastWord + ' | ',
+ filterText: lastWord,
+ command: TRIGGER_SUGGESTION_COMMAND,
+ });
+ if (filteredMetaFields.length > 1) {
+ suggestions.push({
+ ...commaCompleteItem,
+ text: lastWord + ', ',
+ filterText: lastWord,
+ command: TRIGGER_SUGGESTION_COMMAND,
+ });
+ }
+ } else {
+ suggestions.push(...buildFieldsDefinitions(filteredMetaFields));
+ }
+ } else if (isNewExpression) {
+ // METADATA
+ // METADATA field,
suggestions.push(...buildFieldsDefinitions(filteredMetaFields));
- } else if (existingFields.size > 0) {
- if (filteredMetaFields.length > 0) {
- suggestions.push(commaCompleteItem);
+ } else {
+ if (existingFields.size > 0) {
+ // METADATA field
+ if (filteredMetaFields.length > 0) {
+ suggestions.push(commaCompleteItem);
+ }
+ suggestions.push(pipeCompleteItem);
}
- suggestions.push(pipeCompleteItem);
}
}
diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts
index d085588c934e7..b33d705711f05 100644
--- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts
+++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts
@@ -175,6 +175,7 @@ export const buildFieldsDefinitions = (fields: string[]): SuggestionRawDefinitio
defaultMessage: `Field specified by the input table`,
}),
sortText: 'D',
+ command: TRIGGER_SUGGESTION_COMMAND,
}));
};
export const buildVariablesDefinitions = (variables: string[]): SuggestionRawDefinition[] =>
@@ -196,7 +197,7 @@ export const buildSourcesDefinitions = (
): SuggestionRawDefinition[] =>
sources.map(({ name, isIntegration, title, type }) => ({
label: title ?? name,
- text: getSafeInsertSourceText(name) + (!isIntegration ? ' ' : ''),
+ text: getSafeInsertSourceText(name),
isSnippet: isIntegration,
kind: isIntegration ? 'Class' : 'Issue',
detail: isIntegration
@@ -272,7 +273,7 @@ export const buildPoliciesDefinitions = (
): SuggestionRawDefinition[] =>
policies.map(({ name: label, sourceIndices }) => ({
label,
- text: getSafeInsertText(label, { dashSupported: true }),
+ text: getSafeInsertText(label, { dashSupported: true }) + ' ',
kind: 'Class',
detail: i18n.translate('kbn-esql-validation-autocomplete.esql.autocomplete.policyDefinition', {
defaultMessage: `Policy defined on {count, plural, one {index} other {indices}}: {indices}`,
@@ -282,6 +283,7 @@ export const buildPoliciesDefinitions = (
},
}),
sortText: 'D',
+ command: TRIGGER_SUGGESTION_COMMAND,
}));
export const buildMatchingFieldsDefinition = (
@@ -290,7 +292,7 @@ export const buildMatchingFieldsDefinition = (
): SuggestionRawDefinition[] =>
fields.map((label) => ({
label,
- text: getSafeInsertText(label),
+ text: getSafeInsertText(label) + ' ',
kind: 'Variable',
detail: i18n.translate(
'kbn-esql-validation-autocomplete.esql.autocomplete.matchingFieldDefinition',
@@ -302,6 +304,7 @@ export const buildMatchingFieldsDefinition = (
}
),
sortText: 'D',
+ command: TRIGGER_SUGGESTION_COMMAND,
}));
export const buildOptionDefinition = (
diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/helper.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/helper.ts
index deba0b045563d..f42d7de5a38ab 100644
--- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/helper.ts
+++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/helper.ts
@@ -94,9 +94,7 @@ export function getQueryForFields(queryString: string, commands: ESQLCommand[])
export function getSourcesFromCommands(commands: ESQLCommand[], sourceType: 'index' | 'policy') {
const fromCommand = commands.find(({ name }) => name === 'from');
const args = (fromCommand?.args ?? []) as ESQLSource[];
- const sources = args.filter((arg) => arg.sourceType === sourceType);
-
- return sources.length === 1 ? sources[0] : undefined;
+ return args.filter((arg) => arg.sourceType === sourceType);
}
export function removeQuoteForSuggestedSources(suggestions: SuggestionRawDefinition[]) {
diff --git a/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts b/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts
index eb51bfa79227b..d58101e9ff8eb 100644
--- a/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts
+++ b/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts
@@ -640,6 +640,16 @@ export function findPreviousWord(text: string) {
return words[words.length - 2];
}
+/**
+ * Returns the word at the end of the text if there is one.
+ * @param text
+ * @returns
+ */
+export function findFinalWord(text: string) {
+ const words = text.split(/\s+/);
+ return words[words.length - 1];
+}
+
export function shouldBeQuotedSource(text: string) {
// Based on lexer `fragment UNQUOTED_SOURCE_PART`
return /[:"=|,[\]\/ \t\r\n]/.test(text);
diff --git a/packages/kbn-esql-validation-autocomplete/src/shared/types.ts b/packages/kbn-esql-validation-autocomplete/src/shared/types.ts
index 45f1fc0e9311d..d27b84dc4cb27 100644
--- a/packages/kbn-esql-validation-autocomplete/src/shared/types.ts
+++ b/packages/kbn-esql-validation-autocomplete/src/shared/types.ts
@@ -13,17 +13,16 @@ import type { ESQLRealField } from '../validation/types';
type CallbackFn = (ctx?: Options) => Result[] | Promise;
/** @public **/
+export interface ESQLSourceResult {
+ name: string;
+ hidden: boolean;
+ title?: string;
+ dataStreams?: Array<{ name: string; title?: string }>;
+ type?: string;
+}
+
export interface ESQLCallbacks {
- getSources?: CallbackFn<
- {},
- {
- name: string;
- hidden: boolean;
- title?: string;
- dataStreams?: Array<{ name: string; title?: string }>;
- type?: string;
- }
- >;
+ getSources?: CallbackFn<{}, ESQLSourceResult>;
getFieldsFor?: CallbackFn<{ query: string }, ESQLRealField>;
getPolicies?: CallbackFn<
{},
From 1f673dc9f12e90a6aa41a903fee8b0adafcdcaf9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mike=20C=C3=B4t=C3=A9?=
Date: Mon, 16 Sep 2024 10:10:36 -0400
Subject: [PATCH 022/139] Consistent scheduling when tasks run within the poll
interval of their original time (#190093)
Resolves https://github.com/elastic/kibana/issues/189114
In this PR, I'm changing the logic to calculate the task's next run at.
Whenever the gap between the task's runAt and when it was picked up is
less than the poll interval, we'll use the `runAt` to schedule the next.
This way we don't continuously add time to the task's next run (ex:
running every 1m turns into every 1m 3s).
I've had to modify a few tests to have a more increased interval because
this made tasks run more frequently (on time), which introduced
flakiness.
## To verify
1. Create an alerting rule that runs every 10s
2. Apply the following diff to your code
```
diff --git a/x-pack/plugins/task_manager/server/lib/get_next_run_at.ts b/x-pack/plugins/task_manager/server/lib/get_next_run_at.ts
index 55d5f85e5d3..4342dcdd845 100644
--- a/x-pack/plugins/task_manager/server/lib/get_next_run_at.ts
+++ b/x-pack/plugins/task_manager/server/lib/get_next_run_at.ts
@@ -31,5 +31,7 @@ export function getNextRunAt(
Date.now()
);
+ console.log(`*** Next run at: ${new Date(nextCalculatedRunAt).toISOString()}, interval=${newSchedule?.interval ?? schedule.interval}, originalRunAt=${originalRunAt.toISOString()}, startedAt=${startedAt.toISOString()}`);
+
return new Date(nextCalculatedRunAt);
}
```
3. Observe the logs, the gap between runAt and startedAt should be less
than the poll interval, so the next run at is based on `runAt` instead
of `startedAt`.
4. Stop Kibana for 15 seconds then start it again
5. Observe the first logs when the rule runs again and notice now that
the gap between runAt and startedAt is larger than the poll interval,
the next run at is based on `startedAt` instead of `runAt` to spread the
tasks out evenly.
---------
Co-authored-by: Elastic Machine
---
.../task_manager/server/config.mock.ts | 17 ++++++
.../server/lib/get_next_run_at.test.ts | 58 +++++++++++++++++++
.../server/lib/get_next_run_at.ts | 25 ++++++++
.../task_manager/server/polling_lifecycle.ts | 5 +-
.../server/task_running/task_runner.test.ts | 26 +++++++--
.../server/task_running/task_runner.ts | 38 ++++++++----
.../alerting/group1/get_action_error_log.ts | 2 +-
.../builtin_alert_types/es_query/common.ts | 2 +-
.../builtin_alert_types/es_query/esql_only.ts | 12 ++--
.../builtin_alert_types/es_query/rule.ts | 24 ++++----
10 files changed, 173 insertions(+), 36 deletions(-)
create mode 100644 x-pack/plugins/task_manager/server/config.mock.ts
create mode 100644 x-pack/plugins/task_manager/server/lib/get_next_run_at.test.ts
create mode 100644 x-pack/plugins/task_manager/server/lib/get_next_run_at.ts
diff --git a/x-pack/plugins/task_manager/server/config.mock.ts b/x-pack/plugins/task_manager/server/config.mock.ts
new file mode 100644
index 0000000000000..513dea71d39dd
--- /dev/null
+++ b/x-pack/plugins/task_manager/server/config.mock.ts
@@ -0,0 +1,17 @@
+/*
+ * 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 TaskManagerConfig, configSchema } from './config';
+
+const createConfigMock = (overwrites: Partial = {}) => {
+ const mocked: TaskManagerConfig = configSchema.validate(overwrites);
+ return mocked;
+};
+
+export const configMock = {
+ create: createConfigMock,
+};
diff --git a/x-pack/plugins/task_manager/server/lib/get_next_run_at.test.ts b/x-pack/plugins/task_manager/server/lib/get_next_run_at.test.ts
new file mode 100644
index 0000000000000..efa7cf90ae15f
--- /dev/null
+++ b/x-pack/plugins/task_manager/server/lib/get_next_run_at.test.ts
@@ -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 { taskManagerMock } from '../mocks';
+
+import { getNextRunAt } from './get_next_run_at';
+
+describe('getNextRunAt', () => {
+ test('should use startedAt when the task delay is greater than the threshold', () => {
+ const now = new Date();
+ // Use time in the past to ensure the task delay calculation isn't relative to "now"
+ const fiveSecondsAgo = new Date(now.getTime() - 5000);
+ const fourSecondsAgo = new Date(now.getTime() - 4000);
+ const nextRunAt = getNextRunAt(
+ taskManagerMock.createTask({
+ schedule: { interval: '1m' },
+ runAt: fiveSecondsAgo,
+ startedAt: fourSecondsAgo,
+ }),
+ 500
+ );
+ expect(nextRunAt).toEqual(new Date(fourSecondsAgo.getTime() + 60000));
+ });
+
+ test('should use runAt when the task delay is greater than the threshold', () => {
+ const now = new Date();
+ // Use time in the past to ensure the task delay calculation isn't relative to "now"
+ const fiveSecondsAgo = new Date(now.getTime() - 5000);
+ const aBitLessThanFiveSecondsAgo = new Date(now.getTime() - 4995);
+ const nextRunAt = getNextRunAt(
+ taskManagerMock.createTask({
+ schedule: { interval: '1m' },
+ runAt: fiveSecondsAgo,
+ startedAt: aBitLessThanFiveSecondsAgo,
+ }),
+ 500
+ );
+ expect(nextRunAt).toEqual(new Date(fiveSecondsAgo.getTime() + 60000));
+ });
+
+ test('should not schedule in the past', () => {
+ const testStart = new Date();
+ const fiveMinsAgo = new Date(Date.now() - 300000);
+ const nextRunAt = getNextRunAt(
+ taskManagerMock.createTask({
+ schedule: { interval: '1m' },
+ runAt: fiveMinsAgo,
+ startedAt: fiveMinsAgo,
+ }),
+ 0
+ );
+ expect(nextRunAt.getTime()).toBeGreaterThanOrEqual(testStart.getTime());
+ });
+});
diff --git a/x-pack/plugins/task_manager/server/lib/get_next_run_at.ts b/x-pack/plugins/task_manager/server/lib/get_next_run_at.ts
new file mode 100644
index 0000000000000..a25960e61ee29
--- /dev/null
+++ b/x-pack/plugins/task_manager/server/lib/get_next_run_at.ts
@@ -0,0 +1,25 @@
+/*
+ * 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 { intervalFromDate } from './intervals';
+import type { ConcreteTaskInstance } from '../task';
+
+export function getNextRunAt(
+ { runAt, startedAt, schedule }: Pick,
+ taskDelayThresholdForPreciseScheduling: number = 0
+): Date {
+ const taskDelay = startedAt!.getTime() - runAt.getTime();
+ const scheduleFromDate = taskDelay < taskDelayThresholdForPreciseScheduling ? runAt : startedAt!;
+
+ // Ensure we also don't schedule in the past by performing the Math.max with Date.now()
+ const nextCalculatedRunAt = Math.max(
+ intervalFromDate(scheduleFromDate, schedule!.interval)!.getTime(),
+ Date.now()
+ );
+
+ return new Date(nextCalculatedRunAt);
+}
diff --git a/x-pack/plugins/task_manager/server/polling_lifecycle.ts b/x-pack/plugins/task_manager/server/polling_lifecycle.ts
index b8d41391f1411..81a65009391f6 100644
--- a/x-pack/plugins/task_manager/server/polling_lifecycle.ts
+++ b/x-pack/plugins/task_manager/server/polling_lifecycle.ts
@@ -84,6 +84,7 @@ export class TaskPollingLifecycle implements ITaskEventEmitter;
private logger: Logger;
public pool: TaskPool;
@@ -122,6 +123,7 @@ export class TaskPollingLifecycle implements ITaskEventEmitter this.events$.next(event);
@@ -220,9 +222,10 @@ export class TaskPollingLifecycle implements ITaskEventEmitter secondsFromNow(mins * 60);
+const getNextRunAtSpy = jest.spyOn(nextRunAtUtils, 'getNextRunAt');
let fakeTimer: sinon.SinonFakeTimers;
@@ -977,6 +981,8 @@ describe('TaskManagerRunner', () => {
expect(instance.params).toEqual({ a: 'b' });
expect(instance.state).toEqual({ hey: 'there' });
expect(instance.enabled).not.toBeDefined();
+
+ expect(getNextRunAtSpy).not.toHaveBeenCalled();
});
test('reschedules tasks that have an schedule', async () => {
@@ -1007,6 +1013,8 @@ describe('TaskManagerRunner', () => {
expect(instance.runAt.getTime()).toBeGreaterThan(minutesFromNow(9).getTime());
expect(instance.runAt.getTime()).toBeLessThanOrEqual(minutesFromNow(10).getTime());
expect(instance.enabled).not.toBeDefined();
+
+ expect(getNextRunAtSpy).toHaveBeenCalled();
});
test('expiration returns time after which timeout will have elapsed from start', async () => {
@@ -1084,6 +1092,8 @@ describe('TaskManagerRunner', () => {
expect(store.update).toHaveBeenCalledWith(expect.objectContaining({ runAt }), {
validate: true,
});
+
+ expect(getNextRunAtSpy).not.toHaveBeenCalled();
});
test('reschedules tasks that return a schedule', async () => {
@@ -1114,6 +1124,11 @@ describe('TaskManagerRunner', () => {
expect(store.update).toHaveBeenCalledWith(expect.objectContaining({ runAt }), {
validate: true,
});
+
+ expect(getNextRunAtSpy).toHaveBeenCalledWith(
+ expect.objectContaining({ schedule }),
+ expect.any(Number)
+ );
});
test(`doesn't reschedule recurring tasks that throw an unrecoverable error`, async () => {
@@ -2479,12 +2494,15 @@ describe('TaskManagerRunner', () => {
onTaskEvent: opts.onTaskEvent,
executionContext,
usageCounter,
- eventLoopDelayConfig: {
- monitor: true,
- warn_threshold: 5000,
- },
+ config: configMock.create({
+ event_loop_delay: {
+ monitor: true,
+ warn_threshold: 5000,
+ },
+ }),
allowReadingInvalidState: opts.allowReadingInvalidState || false,
strategy: opts.strategy ?? CLAIM_STRATEGY_UPDATE_BY_QUERY,
+ pollIntervalConfiguration$: new BehaviorSubject(500),
});
if (stage === TaskRunningStage.READY_TO_RUN) {
diff --git a/x-pack/plugins/task_manager/server/task_running/task_runner.ts b/x-pack/plugins/task_manager/server/task_running/task_runner.ts
index b92c363a7972c..32b48c5caf58b 100644
--- a/x-pack/plugins/task_manager/server/task_running/task_runner.ts
+++ b/x-pack/plugins/task_manager/server/task_running/task_runner.ts
@@ -11,6 +11,7 @@
* rescheduling, middleware application, etc.
*/
+import { Observable } from 'rxjs';
import apm from 'elastic-apm-node';
import { v4 as uuidv4 } from 'uuid';
import { withSpan } from '@kbn/apm-utils';
@@ -55,9 +56,10 @@ import {
} from '../task';
import { TaskTypeDictionary } from '../task_type_dictionary';
import { isUnrecoverableError } from './errors';
-import { CLAIM_STRATEGY_MGET, type EventLoopDelayConfig } from '../config';
+import { CLAIM_STRATEGY_MGET, type TaskManagerConfig } from '../config';
import { TaskValidator } from '../task_validator';
import { getRetryAt, getRetryDate, getTimeout } from '../lib/get_retry_at';
+import { getNextRunAt } from '../lib/get_next_run_at';
export const EMPTY_RUN_RESULT: SuccessfulRunResult = { state: {} };
@@ -108,9 +110,10 @@ type Opts = {
defaultMaxAttempts: number;
executionContext: ExecutionContextStart;
usageCounter?: UsageCounter;
- eventLoopDelayConfig: EventLoopDelayConfig;
+ config: TaskManagerConfig;
allowReadingInvalidState: boolean;
strategy: string;
+ pollIntervalConfiguration$: Observable;
} & Pick;
export enum TaskRunResult {
@@ -160,9 +163,10 @@ export class TaskManagerRunner implements TaskRunner {
private uuid: string;
private readonly executionContext: ExecutionContextStart;
private usageCounter?: UsageCounter;
- private eventLoopDelayConfig: EventLoopDelayConfig;
+ private config: TaskManagerConfig;
private readonly taskValidator: TaskValidator;
private readonly claimStrategy: string;
+ private currentPollInterval: number;
/**
* Creates an instance of TaskManagerRunner.
@@ -185,9 +189,10 @@ export class TaskManagerRunner implements TaskRunner {
onTaskEvent = identity,
executionContext,
usageCounter,
- eventLoopDelayConfig,
+ config,
allowReadingInvalidState,
strategy,
+ pollIntervalConfiguration$,
}: Opts) {
this.instance = asPending(sanitizeInstance(instance));
this.definitions = definitions;
@@ -200,13 +205,17 @@ export class TaskManagerRunner implements TaskRunner {
this.executionContext = executionContext;
this.usageCounter = usageCounter;
this.uuid = uuidv4();
- this.eventLoopDelayConfig = eventLoopDelayConfig;
+ this.config = config;
this.taskValidator = new TaskValidator({
logger: this.logger,
definitions: this.definitions,
allowReadingInvalidState,
});
this.claimStrategy = strategy;
+ this.currentPollInterval = config.poll_interval;
+ pollIntervalConfiguration$.subscribe((pollInterval) => {
+ this.currentPollInterval = pollInterval;
+ });
}
/**
@@ -335,7 +344,7 @@ export class TaskManagerRunner implements TaskRunner {
const apmTrans = apm.startTransaction(this.taskType, TASK_MANAGER_RUN_TRANSACTION_TYPE, {
childOf: this.instance.task.traceparent,
});
- const stopTaskTimer = startTaskTimerWithEventLoopMonitoring(this.eventLoopDelayConfig);
+ const stopTaskTimer = startTaskTimerWithEventLoopMonitoring(this.config.event_loop_delay);
// Validate state
const stateValidationResult = this.validateTaskState(this.instance.task);
@@ -637,13 +646,20 @@ export class TaskManagerRunner implements TaskRunner {
return asOk({ status: TaskStatus.ShouldDelete });
}
- const { startedAt, schedule } = this.instance.task;
-
+ const updatedTaskSchedule = reschedule ?? this.instance.task.schedule;
return asOk({
runAt:
- runAt || intervalFromDate(startedAt!, reschedule?.interval ?? schedule?.interval)!,
+ runAt ||
+ getNextRunAt(
+ {
+ runAt: this.instance.task.runAt,
+ startedAt: this.instance.task.startedAt,
+ schedule: updatedTaskSchedule,
+ },
+ this.currentPollInterval
+ ),
state,
- schedule: reschedule ?? schedule,
+ schedule: updatedTaskSchedule,
attempts,
status: TaskStatus.Idle,
});
@@ -791,7 +807,7 @@ export class TaskManagerRunner implements TaskRunner {
const { eventLoopBlockMs = 0 } = taskTiming;
const taskLabel = `${this.taskType} ${this.instance.task.id}`;
- if (eventLoopBlockMs > this.eventLoopDelayConfig.warn_threshold) {
+ if (eventLoopBlockMs > this.config.event_loop_delay.warn_threshold) {
this.logger.warn(
`event loop blocked for at least ${eventLoopBlockMs} ms while running task ${taskLabel}`,
{
diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/get_action_error_log.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/get_action_error_log.ts
index dbb8cee8673b8..2a25cf481407e 100644
--- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/get_action_error_log.ts
+++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/get_action_error_log.ts
@@ -146,7 +146,7 @@ export default function createGetActionErrorLogTests({ getService }: FtrProvider
.send(
getTestRuleData({
rule_type_id: 'test.cumulative-firing',
- schedule: { interval: '5s' },
+ schedule: { interval: '6s' },
actions: [
{
id: createdConnector1.id,
diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/common.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/common.ts
index 26d8c64a30296..a08dba15f77ba 100644
--- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/common.ts
+++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/common.ts
@@ -20,7 +20,7 @@ export const ES_TEST_OUTPUT_INDEX_NAME = `${ES_TEST_INDEX_NAME}-output`;
export const ES_TEST_DATA_STREAM_NAME = 'test-data-stream';
export const RULE_INTERVALS_TO_WRITE = 5;
-export const RULE_INTERVAL_SECONDS = 4;
+export const RULE_INTERVAL_SECONDS = 6;
export const RULE_INTERVAL_MILLIS = RULE_INTERVAL_SECONDS * 1000;
export const ES_GROUPS_TO_WRITE = 3;
diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/esql_only.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/esql_only.ts
index 10aaf21a28a07..e748b56bd64cb 100644
--- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/esql_only.ts
+++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/esql_only.ts
@@ -82,7 +82,7 @@ export default function ruleTests({ getService }: FtrProviderContext) {
});
const docs = await waitForDocs(2);
- const messagePattern = /Document count is \d+ in the last 20s. Alert when greater than 0./;
+ const messagePattern = /Document count is \d+ in the last 30s. Alert when greater than 0./;
for (let i = 0; i < docs.length; i++) {
const doc = docs[i];
@@ -136,7 +136,7 @@ export default function ruleTests({ getService }: FtrProviderContext) {
expect(name).to.be('always fire');
expect(title).to.be(`rule 'always fire' matched query`);
- const messagePattern = /Document count is \d+ in the last 20s. Alert when greater than 0./;
+ const messagePattern = /Document count is \d+ in the last 30s. Alert when greater than 0./;
expect(message).to.match(messagePattern);
expect(hits).not.to.be.empty();
}
@@ -156,7 +156,7 @@ export default function ruleTests({ getService }: FtrProviderContext) {
expect(name).to.be('always fire');
expect(title).to.be(`rule 'always fire' matched query`);
- const messagePattern = /Document count is \d+ in the last 20s. Alert when greater than 0./;
+ const messagePattern = /Document count is \d+ in the last 30s. Alert when greater than 0./;
expect(message).to.match(messagePattern);
expect(hits).not.to.be.empty();
}
@@ -186,7 +186,7 @@ export default function ruleTests({ getService }: FtrProviderContext) {
expect(activeTitle).to.be(`rule 'fire then recovers' matched query`);
expect(activeValue).to.be('1');
expect(activeMessage).to.match(
- /Document count is \d+ in the last 4s. Alert when greater than 0./
+ /Document count is \d+ in the last 6s. Alert when greater than 0./
);
await createEsDocumentsInGroups(1, endDate);
docs = await waitForDocs(2);
@@ -200,7 +200,7 @@ export default function ruleTests({ getService }: FtrProviderContext) {
expect(recoveredName).to.be('fire then recovers');
expect(recoveredTitle).to.be(`rule 'fire then recovers' recovered`);
expect(recoveredMessage).to.match(
- /Document count is \d+ in the last 4s. Alert when greater than 0./
+ /Document count is \d+ in the last 6s. Alert when greater than 0./
);
});
@@ -223,7 +223,7 @@ export default function ruleTests({ getService }: FtrProviderContext) {
'from test-data-stream | stats c = count(@timestamp) by host.hostname, host.name, host.id | where c > -1',
});
- const messagePattern = /Document count is \d+ in the last 20s. Alert when greater than 0./;
+ const messagePattern = /Document count is \d+ in the last 30s. Alert when greater than 0./;
const docs = await waitForDocs(2);
for (let i = 0; i < docs.length; i++) {
diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/rule.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/rule.ts
index d53c985f616f3..5ad588a6924de 100644
--- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/rule.ts
+++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/rule.ts
@@ -151,7 +151,7 @@ export default function ruleTests({ getService }: FtrProviderContext) {
const docs = await waitForDocs(2);
const messagePattern =
- /Document count is \d+.?\d* in the last 20s in .kibana-alerting-test-data (?:index|data view). Alert when greater than -1./;
+ /Document count is \d+.?\d* in the last 30s in .kibana-alerting-test-data (?:index|data view). Alert when greater than -1./;
for (let i = 0; i < docs.length; i++) {
const doc = docs[i];
@@ -269,7 +269,7 @@ export default function ruleTests({ getService }: FtrProviderContext) {
await initData();
const messagePattern =
- /Document count is \d+.?\d* in the last 20s in .kibana-alerting-test-data (?:index|data view). Alert when greater than -1./;
+ /Document count is \d+.?\d* in the last 30s in .kibana-alerting-test-data (?:index|data view). Alert when greater than -1./;
const docs = await waitForDocs(2);
for (let i = 0; i < docs.length; i++) {
@@ -391,7 +391,7 @@ export default function ruleTests({ getService }: FtrProviderContext) {
await initData();
const messagePattern =
- /Document count is \d+.?\d* in the last 20s for group-\d+ in .kibana-alerting-test-data (?:index|data view). Alert when greater than -1./;
+ /Document count is \d+.?\d* in the last 30s for group-\d+ in .kibana-alerting-test-data (?:index|data view). Alert when greater than -1./;
const titlePattern = /rule 'always fire' matched query for group group-\d/;
const conditionPattern =
/Number of matching documents for group "group-\d" is greater than -1/;
@@ -478,7 +478,7 @@ export default function ruleTests({ getService }: FtrProviderContext) {
await initData();
const messagePattern =
- /Document count is \d+.?\d* in the last 20s for group-\d+,\d+ in .kibana-alerting-test-data (?:index|data view). Alert when greater than -1./;
+ /Document count is \d+.?\d* in the last 30s for group-\d+,\d+ in .kibana-alerting-test-data (?:index|data view). Alert when greater than -1./;
const titlePattern = /rule 'always fire' matched query for group group-\d+,\d+/;
const conditionPattern =
/Number of matching documents for group "group-\d+,\d+" is greater than -1/;
@@ -608,7 +608,7 @@ export default function ruleTests({ getService }: FtrProviderContext) {
const titlePattern = /rule 'always fire' matched query for group group-\d/;
expect(title).to.match(titlePattern);
const messagePattern =
- /Document count is \d+.?\d* in the last 20s for group-\d+ in .kibana-alerting-test-data (?:index|data view). Alert when greater than -1./;
+ /Document count is \d+.?\d* in the last 30s for group-\d+ in .kibana-alerting-test-data (?:index|data view). Alert when greater than -1./;
expect(message).to.match(messagePattern);
expect(hits).not.to.be.empty();
@@ -696,7 +696,7 @@ export default function ruleTests({ getService }: FtrProviderContext) {
expect(name).to.be('always fire');
expect(title).to.be(`rule 'always fire' matched query`);
const messagePattern =
- /Document count is \d+.?\d* in the last 20s in .kibana-alerting-test-data (?:index|data view). ./;
+ /Document count is \d+.?\d* in the last 30s in .kibana-alerting-test-data (?:index|data view). ./;
expect(message).to.match(messagePattern);
expect(hits).not.to.be.empty();
@@ -806,7 +806,7 @@ export default function ruleTests({ getService }: FtrProviderContext) {
expect(name).to.be('fires once');
expect(title).to.be(`rule 'fires once' matched query`);
const messagePattern =
- /Document count is \d+.?\d* in the last 20s in .kibana-alerting-test-data (?:index|data view). Alert when greater than or equal to 0./;
+ /Document count is \d+.?\d* in the last 30s in .kibana-alerting-test-data (?:index|data view). Alert when greater than or equal to 0./;
expect(message).to.match(messagePattern);
expect(hits).not.to.be.empty();
expect(previousTimestamp).to.be.empty();
@@ -866,7 +866,7 @@ export default function ruleTests({ getService }: FtrProviderContext) {
expect(name).to.be('always fire');
expect(title).to.be(`rule 'always fire' matched query`);
const messagePattern =
- /Document count is \d+.?\d* in the last 20s in .kibana-alerting-test-data (?:index|data view). Alert when less than 1./;
+ /Document count is \d+.?\d* in the last 30s in .kibana-alerting-test-data (?:index|data view). Alert when less than 1./;
expect(message).to.match(messagePattern);
expect(hits).to.be.empty();
@@ -944,7 +944,7 @@ export default function ruleTests({ getService }: FtrProviderContext) {
expect(activeTitle).to.be(`rule 'fire then recovers' matched query`);
expect(activeValue).to.be('0');
expect(activeMessage).to.match(
- /Document count is \d+.?\d* in the last 4s in .kibana-alerting-test-data (?:index|data view). Alert when less than 1./
+ /Document count is \d+.?\d* in the last 6s in .kibana-alerting-test-data (?:index|data view). Alert when less than 1./
);
await createEsDocumentsInGroups(1, endDate);
@@ -959,7 +959,7 @@ export default function ruleTests({ getService }: FtrProviderContext) {
expect(recoveredName).to.be('fire then recovers');
expect(recoveredTitle).to.be(`rule 'fire then recovers' recovered`);
expect(recoveredMessage).to.match(
- /Document count is \d+.?\d* in the last 4s in .kibana-alerting-test-data (?:index|data view). Alert when less than 1./
+ /Document count is \d+.?\d* in the last 6s in .kibana-alerting-test-data (?:index|data view). Alert when less than 1./
);
})
);
@@ -1044,7 +1044,7 @@ export default function ruleTests({ getService }: FtrProviderContext) {
await initData();
const messagePattern =
- /Document count is \d+.?\d* in the last 20s in test-data-stream (?:index|data view). Alert when greater than -1./;
+ /Document count is \d+.?\d* in the last 30s in test-data-stream (?:index|data view). Alert when greater than -1./;
const docs = await waitForDocs(2);
for (let i = 0; i < docs.length; i++) {
@@ -1179,7 +1179,7 @@ export default function ruleTests({ getService }: FtrProviderContext) {
expect(docs[0]._source.hits.length).greaterThan(0);
const messagePattern =
- /Document count is \d+.?\d* in the last 20s in .kibana-alerting-test-data (?:index|data view). Alert when greater than 0./;
+ /Document count is \d+.?\d* in the last 30s in .kibana-alerting-test-data (?:index|data view). Alert when greater than 0./;
expect(docs[0]._source.params.message).to.match(messagePattern);
expect(docs[1]._source.hits.length).to.be(0);
From fd67f2d92ef299e11274c89a7195ed2cf6de1808 Mon Sep 17 00:00:00 2001
From: Bharat Pasupula <123897612+bhapas@users.noreply.github.com>
Date: Mon, 16 Sep 2024 16:33:04 +0200
Subject: [PATCH 023/139] [Automatic Import] Change the integrations owner type
to community (#193002)
## Summary
Currently the integrations created by `Automatic Import` are set to
`elastic`. But `community` fits better.
Once the custom integrations generated by Automatic Import are moved
into upstream `elastic/integrations` repository appropriate owner type
can be defined in the contribution PR.
- Closes https://github.com/elastic/kibana/issues/192917
### For maintainers
- [ ] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
---
.../server/integration_builder/build_integration.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/x-pack/plugins/integration_assistant/server/integration_builder/build_integration.ts b/x-pack/plugins/integration_assistant/server/integration_builder/build_integration.ts
index 73113f6bf7b04..0598ee3ba2cca 100644
--- a/x-pack/plugins/integration_assistant/server/integration_builder/build_integration.ts
+++ b/x-pack/plugins/integration_assistant/server/integration_builder/build_integration.ts
@@ -174,7 +174,7 @@ function createPackageManifestDict(
],
owner: {
github: package_owner,
- type: 'elastic',
+ type: 'community',
},
};
From 8e94a4ba57508f36cf7ec38d52dc141565757a5a Mon Sep 17 00:00:00 2001
From: Tre
Date: Mon, 16 Sep 2024 15:40:45 +0100
Subject: [PATCH 024/139] [FTR][ML] Cleanup ML Job Table and JobDetails
Services (#191720)
## Summary
Services such as `MachineLearningJobExpandedDetailsProvider` and
`MachineLearningJobTableProvider` share some responsibility.
We need to clarify what goes where, such that the design of the services
makes the most sense with respect to where the methods live.
- Dropped "class" syntax of job table service, for simple object literal
(not super important, but "I was in the neighborhood")
- Mv `assertJobRowDetailsCounts` jobTable -> jobExpandedDetails
- Mv `clickJobRowCalendarWithAssertion` jobTable -> jobExpandedDetails
- Mv `assertJobRowCalendars` jobTable -> jobExpandedDetails
- Mv `openAnnotationsTab` jobTable -> jobExpandedDetails
- Mv `assertJobListMultiSelectionText` jobExpandedDetails -> jobTable
---------
Co-authored-by: Elastic Machine
---
.../ml/anomaly_detection_jobs/advanced_job.ts | 4 +-
.../categorization_job.ts | 4 +-
.../anomaly_detection_jobs/date_nanos_job.ts | 2 +-
.../apps/ml/anomaly_detection_jobs/geo_job.ts | 4 +-
.../job_expanded_details.ts | 4 +-
.../multi_metric_job.ts | 4 +-
.../anomaly_detection_jobs/population_job.ts | 4 +-
.../saved_search_job.ts | 2 +-
.../single_metric_job.ts | 4 +-
...ingle_metric_job_without_datafeed_start.ts | 2 +-
.../annotations.ts | 10 +-
.../short_tests/settings/calendar_creation.ts | 11 +-
.../services/ml/job_expanded_details.ts | 59 ++-
.../test/functional/services/ml/job_table.ts | 365 +++++++-----------
14 files changed, 223 insertions(+), 256 deletions(-)
diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/advanced_job.ts b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/advanced_job.ts
index ea5d70fcbe069..370580fe604dc 100644
--- a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/advanced_job.ts
+++ b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/advanced_job.ts
@@ -423,7 +423,7 @@ export default function ({ getService }: FtrProviderContext) {
...testData.expected.row,
});
- await ml.jobTable.assertJobRowDetailsCounts(
+ await ml.jobExpandedDetails.assertJobRowDetailsCounts(
testData.jobId,
{
job_id: testData.jobId,
@@ -638,7 +638,7 @@ export default function ({ getService }: FtrProviderContext) {
...testData.expected.row,
});
- await ml.jobTable.assertJobRowDetailsCounts(
+ await ml.jobExpandedDetails.assertJobRowDetailsCounts(
testData.jobIdClone,
{
job_id: testData.jobIdClone,
diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/categorization_job.ts b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/categorization_job.ts
index 07929a8f9b6f9..eb3708c129205 100644
--- a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/categorization_job.ts
+++ b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/categorization_job.ts
@@ -228,7 +228,7 @@ export default function ({ getService }: FtrProviderContext) {
);
await ml.jobTable.assertJobRowFields(jobId, getExpectedRow(jobId, jobGroups));
- await ml.jobTable.assertJobRowDetailsCounts(
+ await ml.jobExpandedDetails.assertJobRowDetailsCounts(
jobId,
getExpectedCounts(jobId),
getExpectedModelSizeStats(jobId)
@@ -343,7 +343,7 @@ export default function ({ getService }: FtrProviderContext) {
);
await ml.jobTable.assertJobRowFields(jobIdClone, getExpectedRow(jobIdClone, jobGroupsClone));
- await ml.jobTable.assertJobRowDetailsCounts(
+ await ml.jobExpandedDetails.assertJobRowDetailsCounts(
jobIdClone,
getExpectedCounts(jobIdClone),
getExpectedModelSizeStats(jobIdClone)
diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/date_nanos_job.ts b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/date_nanos_job.ts
index e5ffd4c193949..c513e1ee10bdb 100644
--- a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/date_nanos_job.ts
+++ b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/date_nanos_job.ts
@@ -298,7 +298,7 @@ export default function ({ getService }: FtrProviderContext) {
...testData.expected.row,
});
- await ml.jobTable.assertJobRowDetailsCounts(
+ await ml.jobExpandedDetails.assertJobRowDetailsCounts(
testData.jobId,
{
job_id: testData.jobId,
diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/geo_job.ts b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/geo_job.ts
index a95ba4782c413..f5ed246f939d2 100644
--- a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/geo_job.ts
+++ b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/geo_job.ts
@@ -219,7 +219,7 @@ export default function ({ getService }: FtrProviderContext) {
);
await ml.jobTable.assertJobRowFields(jobId, getExpectedRow(jobId, jobGroups));
- await ml.jobTable.assertJobRowDetailsCounts(
+ await ml.jobExpandedDetails.assertJobRowDetailsCounts(
jobId,
getExpectedCounts(jobId),
getExpectedModelSizeStats(jobId)
@@ -339,7 +339,7 @@ export default function ({ getService }: FtrProviderContext) {
);
await ml.jobTable.assertJobRowFields(jobIdClone, getExpectedRow(jobIdClone, jobGroupsClone));
- await ml.jobTable.assertJobRowDetailsCounts(
+ await ml.jobExpandedDetails.assertJobRowDetailsCounts(
jobIdClone,
getExpectedCounts(jobIdClone),
getExpectedModelSizeStats(jobIdClone)
diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/job_expanded_details.ts b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/job_expanded_details.ts
index e48ca875bb1f2..bddcd564bdd18 100644
--- a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/job_expanded_details.ts
+++ b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/job_expanded_details.ts
@@ -113,10 +113,10 @@ export default function ({ getService }: FtrProviderContext) {
it('multi-selection with one opened job should only present the opened job when job list is filtered by the Opened button', async () => {
await ml.jobTable.selectAllJobs();
- await ml.jobExpandedDetails.assertJobListMultiSelectionText('2 jobs selected');
+ await ml.jobTable.assertJobListMultiSelectionText('2 jobs selected');
await ml.jobTable.filterByState(QuickFilterButtonTypes.Opened);
await ml.jobTable.assertJobsInTable([jobId]);
- await ml.jobExpandedDetails.assertJobListMultiSelectionText('1 job selected');
+ await ml.jobTable.assertJobListMultiSelectionText('1 job selected');
});
it('multi-selection with one closed job should only present the closed job when job list is filtered by the Closed button', async () => {
diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/multi_metric_job.ts b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/multi_metric_job.ts
index 24f385704bd71..c60c4d21bc92b 100644
--- a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/multi_metric_job.ts
+++ b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/multi_metric_job.ts
@@ -244,7 +244,7 @@ export default function ({ getService }: FtrProviderContext) {
);
await ml.jobTable.assertJobRowFields(jobId, getExpectedRow(jobId, jobGroups));
- await ml.jobTable.assertJobRowDetailsCounts(
+ await ml.jobExpandedDetails.assertJobRowDetailsCounts(
jobId,
getExpectedCounts(jobId),
getExpectedModelSizeStats(jobId)
@@ -376,7 +376,7 @@ export default function ({ getService }: FtrProviderContext) {
);
await ml.jobTable.assertJobRowFields(jobIdClone, getExpectedRow(jobIdClone, jobGroupsClone));
- await ml.jobTable.assertJobRowDetailsCounts(
+ await ml.jobExpandedDetails.assertJobRowDetailsCounts(
jobIdClone,
getExpectedCounts(jobIdClone),
getExpectedModelSizeStats(jobIdClone)
diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/population_job.ts b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/population_job.ts
index 1dd7801fa334c..9ef7aea22bb6b 100644
--- a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/population_job.ts
+++ b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/population_job.ts
@@ -259,7 +259,7 @@ export default function ({ getService }: FtrProviderContext) {
);
await ml.jobTable.assertJobRowFields(jobId, getExpectedRow(jobId, jobGroups));
- await ml.jobTable.assertJobRowDetailsCounts(
+ await ml.jobExpandedDetails.assertJobRowDetailsCounts(
jobId,
getExpectedCounts(jobId),
getExpectedModelSizeStats(jobId)
@@ -402,7 +402,7 @@ export default function ({ getService }: FtrProviderContext) {
);
await ml.jobTable.assertJobRowFields(jobIdClone, getExpectedRow(jobIdClone, jobGroupsClone));
- await ml.jobTable.assertJobRowDetailsCounts(
+ await ml.jobExpandedDetails.assertJobRowDetailsCounts(
jobIdClone,
getExpectedCounts(jobIdClone),
getExpectedModelSizeStats(jobIdClone)
diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/saved_search_job.ts b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/saved_search_job.ts
index 414230b0b73a1..342a8a13eebbe 100644
--- a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/saved_search_job.ts
+++ b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/saved_search_job.ts
@@ -424,7 +424,7 @@ export default function ({ getService }: FtrProviderContext) {
...testData.expected.row,
});
- await ml.jobTable.assertJobRowDetailsCounts(
+ await ml.jobExpandedDetails.assertJobRowDetailsCounts(
testData.jobId,
{
job_id: testData.jobId,
diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/single_metric_job.ts b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/single_metric_job.ts
index 957ac090e1ade..411b013deb64c 100644
--- a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/single_metric_job.ts
+++ b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/single_metric_job.ts
@@ -219,7 +219,7 @@ export default function ({ getService }: FtrProviderContext) {
);
await ml.jobTable.assertJobRowFields(jobId, getExpectedRow(jobId, jobGroups));
- await ml.jobTable.assertJobRowDetailsCounts(
+ await ml.jobExpandedDetails.assertJobRowDetailsCounts(
jobId,
getExpectedCounts(jobId),
getExpectedModelSizeStats(jobId)
@@ -357,7 +357,7 @@ export default function ({ getService }: FtrProviderContext) {
);
await ml.jobTable.assertJobRowFields(jobIdClone, getExpectedRow(jobIdClone, jobGroupsClone));
- await ml.jobTable.assertJobRowDetailsCounts(
+ await ml.jobExpandedDetails.assertJobRowDetailsCounts(
jobIdClone,
getExpectedCounts(jobIdClone),
getExpectedModelSizeStats(jobIdClone)
diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/single_metric_job_without_datafeed_start.ts b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/single_metric_job_without_datafeed_start.ts
index e137f366628e7..89fbd1213e6e8 100644
--- a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/single_metric_job_without_datafeed_start.ts
+++ b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/single_metric_job_without_datafeed_start.ts
@@ -143,7 +143,7 @@ export default function ({ getService }: FtrProviderContext) {
);
await ml.jobTable.assertJobRowFields(jobId, getExpectedRow(jobId));
- await ml.jobTable.assertJobRowDetailsCounts(
+ await ml.jobExpandedDetails.assertJobRowDetailsCounts(
jobId,
getExpectedCounts(jobId),
getExpectedModelSizeStats(jobId)
diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_result_views/annotations.ts b/x-pack/test/functional/apps/ml/anomaly_detection_result_views/annotations.ts
index ab1177d2dbc84..acae757510aa4 100644
--- a/x-pack/test/functional/apps/ml/anomaly_detection_result_views/annotations.ts
+++ b/x-pack/test/functional/apps/ml/anomaly_detection_result_views/annotations.ts
@@ -90,7 +90,7 @@ export default function ({ getService }: FtrProviderContext) {
await ml.testExecution.logTestStep('should display created annotation in job list');
await ml.navigation.navigateToJobManagement();
await ml.jobTable.filterWithSearchString(jobId, 1);
- await ml.jobTable.openAnnotationsTab(jobId);
+ await ml.jobExpandedDetails.openAnnotationsTab(jobId);
await ml.jobAnnotations.assertAnnotationExists({
annotation: newText,
event: 'user',
@@ -124,7 +124,7 @@ export default function ({ getService }: FtrProviderContext) {
await ml.navigation.navigateToJobManagement();
await ml.jobTable.filterWithSearchString(jobId, 1);
- await ml.jobTable.openAnnotationsTab(jobId);
+ await ml.jobExpandedDetails.openAnnotationsTab(jobId);
await ml.jobAnnotations.assertAnnotationContentById(
annotationId,
expectedOriginalAnnotation
@@ -177,7 +177,7 @@ export default function ({ getService }: FtrProviderContext) {
await ml.testExecution.logTestStep('should display edited annotation in job list');
await ml.navigation.navigateToJobManagement();
await ml.jobTable.filterWithSearchString(jobId, 1);
- await ml.jobTable.openAnnotationsTab(jobId);
+ await ml.jobExpandedDetails.openAnnotationsTab(jobId);
await ml.jobAnnotations.assertAnnotationContentById(annotationId, expectedEditedAnnotation);
});
});
@@ -197,7 +197,7 @@ export default function ({ getService }: FtrProviderContext) {
await ml.navigation.navigateToMl();
await ml.navigation.navigateToJobManagement();
await ml.jobTable.filterWithSearchString(jobId, 1);
- await ml.jobTable.openAnnotationsTab(jobId);
+ await ml.jobExpandedDetails.openAnnotationsTab(jobId);
await ml.jobAnnotations.openDatafeedChartFlyout(annotationId, jobId);
await ml.jobAnnotations.assertDelayedDataChartExists();
@@ -252,7 +252,7 @@ export default function ({ getService }: FtrProviderContext) {
await ml.testExecution.logTestStep('does not show the deleted annotation in job list');
await ml.navigation.navigateToJobManagement();
await ml.jobTable.filterWithSearchString(jobId, 1);
- await ml.jobTable.openAnnotationsTab(jobId);
+ await ml.jobExpandedDetails.openAnnotationsTab(jobId);
await ml.jobAnnotations.assertAnnotationsRowMissing(annotationId);
});
});
diff --git a/x-pack/test/functional/apps/ml/short_tests/settings/calendar_creation.ts b/x-pack/test/functional/apps/ml/short_tests/settings/calendar_creation.ts
index 15eac59357928..78a15a64ce0bd 100644
--- a/x-pack/test/functional/apps/ml/short_tests/settings/calendar_creation.ts
+++ b/x-pack/test/functional/apps/ml/short_tests/settings/calendar_creation.ts
@@ -146,8 +146,11 @@ export default function ({ getService }: FtrProviderContext) {
await ml.navigation.navigateToAnomalyDetection();
- await ml.jobTable.assertJobRowCalendars('test_calendar_ad_1', [calendarId]);
- await ml.jobTable.clickJobRowCalendarWithAssertion('test_calendar_ad_1', calendarId);
+ await ml.jobExpandedDetails.assertJobRowCalendars('test_calendar_ad_1', [calendarId]);
+ await ml.jobExpandedDetails.clickJobRowCalendarWithAssertion(
+ 'test_calendar_ad_1',
+ calendarId
+ );
await ml.testExecution.logTestStep(
'created calendars can be connected to job groups after creation'
@@ -161,8 +164,8 @@ export default function ({ getService }: FtrProviderContext) {
'multi-metric',
]);
await ml.navigation.navigateToAnomalyDetection();
- await ml.jobTable.assertJobRowCalendars('test_calendar_ad_4', [calendarId]);
- await ml.jobTable.assertJobRowCalendars('test_calendar_ad_3', [calendarId], false);
+ await ml.jobExpandedDetails.assertJobRowCalendars('test_calendar_ad_4', [calendarId]);
+ await ml.jobExpandedDetails.assertJobRowCalendars('test_calendar_ad_3', [calendarId], false);
});
async function assignJobToCalendar(
diff --git a/x-pack/test/functional/services/ml/job_expanded_details.ts b/x-pack/test/functional/services/ml/job_expanded_details.ts
index d9c82d72eabc4..bf5c7b2c87b5b 100644
--- a/x-pack/test/functional/services/ml/job_expanded_details.ts
+++ b/x-pack/test/functional/services/ml/job_expanded_details.ts
@@ -21,6 +21,14 @@ export function MachineLearningJobExpandedDetailsProvider(
const headerPage = getPageObject('header');
return {
+ async openAnnotationsTab(jobId: string) {
+ await retry.tryForTime(10000, async () => {
+ await jobTable.ensureDetailsOpen(jobId);
+ await testSubjects.click(jobTable.detailsSelector(jobId, 'mlJobListTab-annotations'));
+ await testSubjects.existOrFail('mlAnnotationsTable');
+ });
+ },
+
async clickEditAnnotationAction(jobId: string, annotationId: string) {
await jobAnnotationsTable.ensureAnnotationsActionsMenuOpen(annotationId);
await testSubjects.click('mlAnnotationsActionEdit');
@@ -77,7 +85,7 @@ export function MachineLearningJobExpandedDetailsProvider(
const { _id: annotationId }: { _id: string } = annotationsFromApi[0];
await jobTable.ensureDetailsOpen(jobId);
- await jobTable.openAnnotationsTab(jobId);
+ await this.openAnnotationsTab(jobId);
await this.clearSearchButton();
await jobAnnotationsTable.ensureAnnotationsActionsMenuOpen(annotationId);
await testSubjects.click('mlAnnotationsActionOpenInSingleMetricViewer');
@@ -92,7 +100,7 @@ export function MachineLearningJobExpandedDetailsProvider(
await this.assertAnnotationsFromApi(annotationsFromApi);
await jobTable.ensureDetailsOpen(jobId);
- await jobTable.openAnnotationsTab(jobId);
+ await this.openAnnotationsTab(jobId);
await this.clearSearchButton();
const { _id: annotationId }: { _id: string } = annotationsFromApi[0];
@@ -107,7 +115,7 @@ export function MachineLearningJobExpandedDetailsProvider(
await jobTable.ensureDetailsClosed(jobId);
await jobTable.withDetailsOpen(jobId, async () => {
- await jobTable.openAnnotationsTab(jobId);
+ await this.openAnnotationsTab(jobId);
await this.clearSearchButton();
const visibleText = await testSubjects.getVisibleText(
jobTable.detailsSelector(jobId, 'mlAnnotationsColumnAnnotation')
@@ -118,7 +126,7 @@ export function MachineLearningJobExpandedDetailsProvider(
async assertDataFeedFlyout(jobId: string): Promise {
await jobTable.withDetailsOpen(jobId, async () => {
- await jobTable.openAnnotationsTab(jobId);
+ await this.openAnnotationsTab(jobId);
await this.clearSearchButton();
await testSubjects.click(jobTable.detailsSelector(jobId, 'euiCollapsedItemActionsButton'));
await testSubjects.click('mlAnnotationsActionViewDatafeed');
@@ -162,9 +170,46 @@ export function MachineLearningJobExpandedDetailsProvider(
});
},
- async assertJobListMultiSelectionText(expectedMsg: string): Promise {
- const visibleText = await testSubjects.getVisibleText('~mlADJobListMultiSelectActionsArea');
- expect(visibleText).to.be(expectedMsg);
+ async clickJobRowCalendarWithAssertion(jobId: string, calendarId: string): Promise {
+ await jobTable.ensureDetailsOpen(jobId);
+ const calendarSelector = `mlJobDetailsCalendar-${calendarId}`;
+ await testSubjects.existOrFail(calendarSelector, {
+ timeout: 3_000,
+ });
+ await testSubjects.click(calendarSelector, 3_000);
+ await testSubjects.existOrFail('mlPageCalendarEdit > mlCalendarFormEdit', {
+ timeout: 3_000,
+ });
+ const calendarTitleVisibleText = await testSubjects.getVisibleText('mlCalendarTitle');
+ expect(calendarTitleVisibleText).to.contain(
+ calendarId,
+ `Calendar page title should contain [${calendarId}], got [${calendarTitleVisibleText}]`
+ );
+ },
+
+ async assertJobRowDetailsCounts(
+ jobId: string,
+ expectedCounts: object,
+ expectedModelSizeStats: object
+ ) {
+ const { counts, modelSizeStats } = await jobTable.parseJobCounts(jobId);
+
+ // Only check for expected keys / values, ignore additional properties
+ // This way the tests stay stable when new properties are added on the ES side
+ for (const [key, value] of Object.entries(expectedCounts)) {
+ expect(counts)
+ .to.have.property(key)
+ .eql(value, `Expected counts property '${key}' to exist with value '${value}'`);
+ }
+
+ for (const [key, value] of Object.entries(expectedModelSizeStats)) {
+ expect(modelSizeStats)
+ .to.have.property(key)
+ .eql(
+ value,
+ `Expected model size stats property '${key}' to exist with value '${value}')`
+ );
+ }
},
};
}
diff --git a/x-pack/test/functional/services/ml/job_table.ts b/x-pack/test/functional/services/ml/job_table.ts
index 97ce1858bc2f1..bd19a31f62b54 100644
--- a/x-pack/test/functional/services/ml/job_table.ts
+++ b/x-pack/test/functional/services/ml/job_table.ts
@@ -55,21 +55,21 @@ export function MachineLearningJobTableProvider(
const testSubjects = getService('testSubjects');
const retry = getService('retry');
- return new (class MlJobTable {
- public async selectAllJobs(): Promise {
+ return {
+ async selectAllJobs(): Promise {
await testSubjects.click('checkboxSelectAll');
- }
+ },
- public async assertJobsInTable(expectedJobIds: string[]) {
+ async assertJobsInTable(expectedJobIds: string[]) {
const sortedExpectedIds = expectedJobIds.sort();
const sortedActualJobIds = (await this.parseJobTable()).map((row) => row.id).sort();
expect(sortedActualJobIds).to.eql(
sortedExpectedIds,
`Expected jobs in table to be [${sortedExpectedIds}], got [${sortedActualJobIds}]`
);
- }
+ },
- public async filterByState(quickFilterButton: QuickFilterButtonTypes): Promise {
+ async filterByState(quickFilterButton: QuickFilterButtonTypes): Promise {
const searchBar: WebElementWrapper = await testSubjects.find('mlJobListSearchBar');
const quickFilter: WebElementWrapper = await searchBar.findByCssSelector(
`span[data-text="${quickFilterButton}"]`
@@ -86,46 +86,9 @@ export function MachineLearningJobTableProvider(
quickFilterButton,
`Expected visible text of pressed quick filter button to equal [${quickFilterButton}], but got [${pressedBttnText}]`
);
- }
+ },
- public async clickJobRowCalendarWithAssertion(
- jobId: string,
- calendarId: string
- ): Promise {
- await this.ensureDetailsOpen(jobId);
- const calendarSelector = `mlJobDetailsCalendar-${calendarId}`;
- await testSubjects.existOrFail(calendarSelector, {
- timeout: 3_000,
- });
- await testSubjects.click(calendarSelector, 3_000);
- await testSubjects.existOrFail('mlPageCalendarEdit > mlCalendarFormEdit', {
- timeout: 3_000,
- });
- const calendarTitleVisibleText = await testSubjects.getVisibleText('mlCalendarTitle');
- expect(calendarTitleVisibleText).to.contain(
- calendarId,
- `Calendar page title should contain [${calendarId}], got [${calendarTitleVisibleText}]`
- );
- }
-
- public async assertJobRowCalendars(
- jobId: string,
- expectedCalendars: string[],
- checkForExists: boolean = true
- ): Promise {
- await this.withDetailsOpen(jobId, async function verifyJobRowCalendars(): Promise {
- for await (const expectedCalendar of expectedCalendars) {
- const calendarSelector = `mlJobDetailsCalendar-${expectedCalendar}`;
- await testSubjects[checkForExists ? 'existOrFail' : 'missingOrFail'](calendarSelector, {
- timeout: 3_000,
- });
- if (checkForExists)
- expect(await testSubjects.getVisibleText(calendarSelector)).to.be(expectedCalendar);
- }
- });
- }
-
- public async parseJobTable(
+ async parseJobTable(
tableEnvironment: 'mlAnomalyDetection' | 'stackMgmtJobList' = 'mlAnomalyDetection'
) {
const table = await testSubjects.find('~mlJobListTable');
@@ -215,9 +178,10 @@ export function MachineLearningJobTableProvider(
}
return rows;
- }
+ },
- public async parseJobCounts(jobId: string) {
+ // TODO: Mv this fn over too
+ async parseJobCounts(jobId: string) {
return await this.withDetailsOpen(jobId, async () => {
// click counts tab
await testSubjects.click(this.detailsSelector(jobId, 'mlJobListTab-counts'));
@@ -248,59 +212,51 @@ export function MachineLearningJobTableProvider(
modelSizeStats: await parseTable(modelSizeStatsTable),
};
});
- }
+ },
- public rowSelector(jobId: string, subSelector?: string) {
+ rowSelector(jobId: string, subSelector?: string) {
const row = `~mlJobListTable > ~row-${jobId}`;
return !subSelector ? row : `${row} > ${subSelector}`;
- }
+ },
- public detailsSelector(jobId: string, subSelector?: string) {
+ detailsSelector(jobId: string, subSelector?: string) {
const row = `~mlJobListTable > ~details-${jobId}`;
return !subSelector ? row : `${row} > ${subSelector}`;
- }
+ },
- public async withDetailsOpen(jobId: string, block: () => Promise): Promise {
+ async withDetailsOpen(jobId: string, block: () => Promise): Promise {
await this.ensureDetailsOpen(jobId);
try {
return await block();
} finally {
await this.ensureDetailsClosed(jobId);
}
- }
+ },
- public async ensureDetailsOpen(jobId: string) {
+ async ensureDetailsOpen(jobId: string) {
await retry.tryForTime(10000, async () => {
if (!(await testSubjects.exists(this.detailsSelector(jobId)))) {
await testSubjects.click(this.rowSelector(jobId, 'mlJobListRowDetailsToggle'));
await testSubjects.existOrFail(this.detailsSelector(jobId), { timeout: 1000 });
}
});
- }
+ },
- public async ensureDetailsClosed(jobId: string) {
+ async ensureDetailsClosed(jobId: string) {
await retry.tryForTime(10000, async () => {
if (await testSubjects.exists(this.detailsSelector(jobId))) {
await testSubjects.click(this.rowSelector(jobId, 'mlJobListRowDetailsToggle'));
await testSubjects.missingOrFail(this.detailsSelector(jobId), { timeout: 1000 });
}
});
- }
-
- public async openAnnotationsTab(jobId: string) {
- await retry.tryForTime(10000, async () => {
- await this.ensureDetailsOpen(jobId);
- await testSubjects.click(this.detailsSelector(jobId, 'mlJobListTab-annotations'));
- await testSubjects.existOrFail('mlAnnotationsTable');
- });
- }
+ },
- public async waitForRefreshButtonLoaded(buttonTestSubj: string) {
+ async waitForRefreshButtonLoaded(buttonTestSubj: string) {
await testSubjects.existOrFail(`~${buttonTestSubj}`, { timeout: 10 * 1000 });
await testSubjects.existOrFail(`${buttonTestSubj} loaded`, { timeout: 30 * 1000 });
- }
+ },
- public async refreshJobList(
+ async refreshJobList(
tableEnvironment: 'mlAnomalyDetection' | 'stackMgmtJobList' = 'mlAnomalyDetection'
) {
const testSubjStr =
@@ -312,14 +268,14 @@ export function MachineLearningJobTableProvider(
await testSubjects.click(`~${testSubjStr}`);
await this.waitForRefreshButtonLoaded(testSubjStr);
await this.waitForJobsToLoad();
- }
+ },
- public async waitForJobsToLoad() {
+ async waitForJobsToLoad() {
await testSubjects.existOrFail('~mlJobListTable', { timeout: 60 * 1000 });
await testSubjects.existOrFail('mlJobListTable loaded', { timeout: 30 * 1000 });
- }
+ },
- public async filterWithSearchString(
+ async filterWithSearchString(
filter: string,
expectedRowCount: number = 1,
tableEnvironment: 'mlAnomalyDetection' | 'stackMgmtJobList' = 'mlAnomalyDetection'
@@ -339,9 +295,9 @@ export function MachineLearningJobTableProvider(
filteredRows
)}')`
);
- }
+ },
- public async assertJobRowFields(jobId: string, expectedRow: object) {
+ async assertJobRowFields(jobId: string, expectedRow: object) {
await retry.tryForTime(5000, async () => {
await this.refreshJobList();
const rows = await this.parseJobTable();
@@ -353,46 +309,18 @@ export function MachineLearningJobTableProvider(
)}')`
);
});
- }
+ },
- public async assertJobRowJobId(jobId: string) {
+ async assertJobRowJobId(jobId: string) {
await retry.tryForTime(5000, async () => {
await this.refreshJobList();
const rows = await this.parseJobTable();
const jobRowMatch = rows.find((row) => row.id === jobId);
expect(jobRowMatch).to.not.eql(undefined, `Expected row with job ID ${jobId} to exist`);
});
- }
+ },
- public async assertJobRowDetailsCounts(
- jobId: string,
- expectedCounts: object,
- expectedModelSizeStats: object
- ) {
- const { counts, modelSizeStats } = await this.parseJobCounts(jobId);
-
- // Only check for expected keys / values, ignore additional properties
- // This way the tests stay stable when new properties are added on the ES side
- for (const [key, value] of Object.entries(expectedCounts)) {
- expect(counts)
- .to.have.property(key)
- .eql(value, `Expected counts property '${key}' to exist with value '${value}'`);
- }
-
- for (const [key, value] of Object.entries(expectedModelSizeStats)) {
- expect(modelSizeStats)
- .to.have.property(key)
- .eql(
- value,
- `Expected model size stats property '${key}' to exist with value '${value}')`
- );
- }
- }
-
- public async assertJobActionSingleMetricViewerButtonEnabled(
- jobId: string,
- expectedValue: boolean
- ) {
+ async assertJobActionSingleMetricViewerButtonEnabled(jobId: string, expectedValue: boolean) {
const isEnabled = await testSubjects.isEnabled(
this.rowSelector(jobId, 'mlOpenJobsInSingleMetricViewerButton')
);
@@ -402,12 +330,9 @@ export function MachineLearningJobTableProvider(
expectedValue ? 'enabled' : 'disabled'
}' (got '${isEnabled ? 'enabled' : 'disabled'}')`
);
- }
+ },
- public async assertJobActionAnomalyExplorerButtonEnabled(
- jobId: string,
- expectedValue: boolean
- ) {
+ async assertJobActionAnomalyExplorerButtonEnabled(jobId: string, expectedValue: boolean) {
const isEnabled = await testSubjects.isEnabled(
this.rowSelector(jobId, 'mlOpenJobsInAnomalyExplorerButton')
);
@@ -417,9 +342,9 @@ export function MachineLearningJobTableProvider(
expectedValue ? 'enabled' : 'disabled'
}' (got '${isEnabled ? 'enabled' : 'disabled'}')`
);
- }
+ },
- public async assertJobActionsMenuButtonEnabled(jobId: string, expectedValue: boolean) {
+ async assertJobActionsMenuButtonEnabled(jobId: string, expectedValue: boolean) {
const isEnabled = await testSubjects.isEnabled(
this.rowSelector(jobId, 'euiCollapsedItemActionsButton')
);
@@ -429,9 +354,9 @@ export function MachineLearningJobTableProvider(
expectedValue ? 'enabled' : 'disabled'
}' (got '${isEnabled ? 'enabled' : 'disabled'}')`
);
- }
+ },
- public async assertJobActionStartDatafeedButtonEnabled(jobId: string, expectedValue: boolean) {
+ async assertJobActionStartDatafeedButtonEnabled(jobId: string, expectedValue: boolean) {
await this.ensureJobActionsMenuOpen(jobId);
const isEnabled = await testSubjects.isEnabled('mlActionButtonStartDatafeed');
expect(isEnabled).to.eql(
@@ -440,9 +365,9 @@ export function MachineLearningJobTableProvider(
expectedValue ? 'enabled' : 'disabled'
}' (got '${isEnabled ? 'enabled' : 'disabled'}')`
);
- }
+ },
- public async assertJobActionResetJobButtonEnabled(jobId: string, expectedValue: boolean) {
+ async assertJobActionResetJobButtonEnabled(jobId: string, expectedValue: boolean) {
await this.ensureJobActionsMenuOpen(jobId);
const isEnabled = await testSubjects.isEnabled('mlActionButtonResetJob');
expect(isEnabled).to.eql(
@@ -451,9 +376,9 @@ export function MachineLearningJobTableProvider(
expectedValue ? 'enabled' : 'disabled'
}' (got '${isEnabled ? 'enabled' : 'disabled'}')`
);
- }
+ },
- public async assertJobActionCloneJobButtonEnabled(jobId: string, expectedValue: boolean) {
+ async assertJobActionCloneJobButtonEnabled(jobId: string, expectedValue: boolean) {
await this.ensureJobActionsMenuOpen(jobId);
const isEnabled = await testSubjects.isEnabled('mlActionButtonCloneJob');
expect(isEnabled).to.eql(
@@ -462,12 +387,9 @@ export function MachineLearningJobTableProvider(
expectedValue ? 'enabled' : 'disabled'
}' (got '${isEnabled ? 'enabled' : 'disabled'}')`
);
- }
+ },
- public async assertJobActionViewDatafeedCountsButtonEnabled(
- jobId: string,
- expectedValue: boolean
- ) {
+ async assertJobActionViewDatafeedCountsButtonEnabled(jobId: string, expectedValue: boolean) {
await this.ensureJobActionsMenuOpen(jobId);
const isEnabled = await testSubjects.isEnabled('mlActionButtonViewDatafeedChart');
expect(isEnabled).to.eql(
@@ -476,9 +398,9 @@ export function MachineLearningJobTableProvider(
expectedValue ? 'enabled' : 'disabled'
}' (got '${isEnabled ? 'enabled' : 'disabled'}')`
);
- }
+ },
- public async assertJobActionEditJobButtonEnabled(jobId: string, expectedValue: boolean) {
+ async assertJobActionEditJobButtonEnabled(jobId: string, expectedValue: boolean) {
await this.ensureJobActionsMenuOpen(jobId);
const isEnabled = await testSubjects.isEnabled('mlActionButtonEditJob');
expect(isEnabled).to.eql(
@@ -487,9 +409,9 @@ export function MachineLearningJobTableProvider(
expectedValue ? 'enabled' : 'disabled'
}' (got '${isEnabled ? 'enabled' : 'disabled'}')`
);
- }
+ },
- public async assertJobActionDeleteJobButtonEnabled(jobId: string, expectedValue: boolean) {
+ async assertJobActionDeleteJobButtonEnabled(jobId: string, expectedValue: boolean) {
await this.ensureJobActionsMenuOpen(jobId);
const isEnabled = await testSubjects.isEnabled('mlActionButtonDeleteJob');
expect(isEnabled).to.eql(
@@ -498,51 +420,51 @@ export function MachineLearningJobTableProvider(
expectedValue ? 'enabled' : 'disabled'
}' (got '${isEnabled ? 'enabled' : 'disabled'}')`
);
- }
+ },
- public async ensureJobActionsMenuOpen(jobId: string) {
+ async ensureJobActionsMenuOpen(jobId: string) {
await retry.tryForTime(30 * 1000, async () => {
if (!(await testSubjects.exists('mlActionButtonDeleteJob'))) {
await testSubjects.click(this.rowSelector(jobId, 'euiCollapsedItemActionsButton'));
await testSubjects.existOrFail('mlActionButtonDeleteJob', { timeout: 5000 });
}
});
- }
+ },
- public async clickCloneJobAction(jobId: string) {
+ async clickCloneJobAction(jobId: string) {
await this.ensureJobActionsMenuOpen(jobId);
await testSubjects.click('mlActionButtonCloneJob');
await testSubjects.existOrFail('~mlPageJobWizard');
- }
+ },
- public async clickCloneJobActionWhenNoDataViewExists(jobId: string) {
+ async clickCloneJobActionWhenNoDataViewExists(jobId: string) {
await this.ensureJobActionsMenuOpen(jobId);
await testSubjects.click('mlActionButtonCloneJob');
await this.assertNoDataViewForCloneJobWarningToastExist();
- }
+ },
- public async assertNoDataViewForCloneJobWarningToastExist() {
+ async assertNoDataViewForCloneJobWarningToastExist() {
await testSubjects.existOrFail('mlCloneJobNoDataViewExistsWarningToast', { timeout: 5000 });
- }
+ },
- public async clickEditJobAction(jobId: string) {
+ async clickEditJobAction(jobId: string) {
await this.ensureJobActionsMenuOpen(jobId);
await testSubjects.click('mlActionButtonEditJob');
await testSubjects.existOrFail('mlJobEditFlyout');
- }
+ },
- public async clickDeleteJobAction(jobId: string) {
+ async clickDeleteJobAction(jobId: string) {
await this.ensureJobActionsMenuOpen(jobId);
await testSubjects.click('mlActionButtonDeleteJob');
await testSubjects.existOrFail('mlDeleteJobConfirmModal');
- }
+ },
- public async confirmDeleteJobModal() {
+ async confirmDeleteJobModal() {
await testSubjects.click('mlDeleteJobConfirmModal > mlDeleteJobConfirmModalButton');
await testSubjects.missingOrFail('mlDeleteJobConfirmModal', { timeout: 30 * 1000 });
- }
+ },
- public async clickDeleteAnnotationsInDeleteJobModal(checked: boolean) {
+ async clickDeleteAnnotationsInDeleteJobModal(checked: boolean) {
await testSubjects.setEuiSwitch(
'mlDeleteJobConfirmModal > mlDeleteJobConfirmModalDeleteAnnotationsSwitch',
checked ? 'check' : 'uncheck'
@@ -552,23 +474,23 @@ export function MachineLearningJobTableProvider(
);
expect(isChecked).to.eql(checked, `Expected delete annotations switch to be ${checked}`);
- }
+ },
- public async clickOpenJobInSingleMetricViewerButton(jobId: string) {
+ async clickOpenJobInSingleMetricViewerButton(jobId: string) {
await testSubjects.click(this.rowSelector(jobId, 'mlOpenJobsInSingleMetricViewerButton'));
await testSubjects.existOrFail('~mlPageSingleMetricViewer');
- }
+ },
- public async clickOpenJobInAnomalyExplorerButton(jobId: string) {
+ async clickOpenJobInAnomalyExplorerButton(jobId: string) {
await testSubjects.click(this.rowSelector(jobId, 'mlOpenJobsInAnomalyExplorerButton'));
await testSubjects.existOrFail('~mlPageAnomalyExplorer');
- }
+ },
- public async isJobRowSelected(jobId: string): Promise {
+ async isJobRowSelected(jobId: string): Promise {
return await testSubjects.isChecked(this.rowSelector(jobId, `checkboxSelectRow-${jobId}`));
- }
+ },
- public async assertJobRowSelected(jobId: string, expectedValue: boolean) {
+ async assertJobRowSelected(jobId: string, expectedValue: boolean) {
const isSelected = await this.isJobRowSelected(jobId);
expect(isSelected).to.eql(
expectedValue,
@@ -576,37 +498,37 @@ export function MachineLearningJobTableProvider(
expectedValue ? 'selected' : 'deselected'
}' (got '${isSelected ? 'selected' : 'deselected'}')`
);
- }
+ },
- public async selectJobRow(jobId: string) {
+ async selectJobRow(jobId: string) {
if ((await this.isJobRowSelected(jobId)) === false) {
await testSubjects.click(this.rowSelector(jobId, `checkboxSelectRow-${jobId}`));
}
await this.assertJobRowSelected(jobId, true);
await this.assertMultiSelectActionsAreaActive();
- }
+ },
- public async deselectJobRow(jobId: string) {
+ async deselectJobRow(jobId: string) {
if ((await this.isJobRowSelected(jobId)) === true) {
await testSubjects.click(this.rowSelector(jobId, `checkboxSelectRow-${jobId}`));
}
await this.assertJobRowSelected(jobId, false);
await this.assertMultiSelectActionsAreaInactive();
- }
+ },
- public async assertMultiSelectActionsAreaActive() {
+ async assertMultiSelectActionsAreaActive() {
await testSubjects.existOrFail('mlADJobListMultiSelectActionsArea active');
- }
+ },
- public async assertMultiSelectActionsAreaInactive() {
+ async assertMultiSelectActionsAreaInactive() {
await testSubjects.existOrFail('mlADJobListMultiSelectActionsArea inactive', {
allowHidden: true,
});
- }
+ },
- public async assertMultiSelectActionSingleMetricViewerButtonEnabled(expectedValue: boolean) {
+ async assertMultiSelectActionSingleMetricViewerButtonEnabled(expectedValue: boolean) {
const isEnabled = await testSubjects.isEnabled(
'~mlADJobListMultiSelectActionsArea > mlOpenJobsInSingleMetricViewerButton'
);
@@ -616,9 +538,9 @@ export function MachineLearningJobTableProvider(
expectedValue ? 'enabled' : 'disabled'
}' (got '${isEnabled ? 'enabled' : 'disabled'}')`
);
- }
+ },
- public async assertMultiSelectActionAnomalyExplorerButtonEnabled(expectedValue: boolean) {
+ async assertMultiSelectActionAnomalyExplorerButtonEnabled(expectedValue: boolean) {
const isEnabled = await testSubjects.isEnabled(
'~mlADJobListMultiSelectActionsArea > mlOpenJobsInAnomalyExplorerButton'
);
@@ -628,9 +550,9 @@ export function MachineLearningJobTableProvider(
expectedValue ? 'enabled' : 'disabled'
}' (got '${isEnabled ? 'enabled' : 'disabled'}')`
);
- }
+ },
- public async assertMultiSelectActionEditJobGroupsButtonEnabled(expectedValue: boolean) {
+ async assertMultiSelectActionEditJobGroupsButtonEnabled(expectedValue: boolean) {
const isEnabled = await testSubjects.isEnabled(
'~mlADJobListMultiSelectActionsArea > mlADJobListMultiSelectEditJobGroupsButton'
);
@@ -640,9 +562,9 @@ export function MachineLearningJobTableProvider(
expectedValue ? 'enabled' : 'disabled'
}' (got '${isEnabled ? 'enabled' : 'disabled'}')`
);
- }
+ },
- public async assertMultiSelectManagementActionsButtonEnabled(expectedValue: boolean) {
+ async assertMultiSelectManagementActionsButtonEnabled(expectedValue: boolean) {
const isEnabled = await testSubjects.isEnabled(
'~mlADJobListMultiSelectActionsArea > mlADJobListMultiSelectManagementActionsButton'
);
@@ -652,9 +574,9 @@ export function MachineLearningJobTableProvider(
expectedValue ? 'enabled' : 'disabled'
}' (got '${isEnabled ? 'enabled' : 'disabled'}')`
);
- }
+ },
- public async assertMultiSelectStartDatafeedActionButtonEnabled(expectedValue: boolean) {
+ async assertMultiSelectStartDatafeedActionButtonEnabled(expectedValue: boolean) {
await this.ensureMultiSelectManagementActionsMenuOpen();
const isEnabled = await testSubjects.isEnabled(
'mlADJobListMultiSelectStartDatafeedActionButton'
@@ -665,9 +587,9 @@ export function MachineLearningJobTableProvider(
expectedValue ? 'enabled' : 'disabled'
}' (got '${isEnabled ? 'enabled' : 'disabled'}')`
);
- }
+ },
- public async assertMultiSelectDeleteJobActionButtonEnabled(expectedValue: boolean) {
+ async assertMultiSelectDeleteJobActionButtonEnabled(expectedValue: boolean) {
await this.ensureMultiSelectManagementActionsMenuOpen();
const isEnabled = await testSubjects.isEnabled('mlADJobListMultiSelectDeleteJobActionButton');
expect(isEnabled).to.eql(
@@ -676,9 +598,9 @@ export function MachineLearningJobTableProvider(
expectedValue ? 'enabled' : 'disabled'
}' (got '${isEnabled ? 'enabled' : 'disabled'}')`
);
- }
+ },
- public async ensureMultiSelectManagementActionsMenuOpen() {
+ async ensureMultiSelectManagementActionsMenuOpen() {
await retry.tryForTime(30 * 1000, async () => {
if (!(await testSubjects.exists('mlADJobListMultiSelectDeleteJobActionButton'))) {
await testSubjects.click('mlADJobListMultiSelectManagementActionsButton');
@@ -687,48 +609,44 @@ export function MachineLearningJobTableProvider(
});
}
});
- }
+ },
- public async openEditCustomUrlsForJobTab(jobId: string) {
+ async openEditCustomUrlsForJobTab(jobId: string) {
await this.clickEditJobAction(jobId);
// click Custom URLs tab
await testSubjects.click('mlEditJobFlyout-customUrls');
await this.ensureEditCustomUrlTabOpen();
await headerPage.waitUntilLoadingHasFinished();
- }
+ },
- public async ensureEditCustomUrlTabOpen() {
+ async ensureEditCustomUrlTabOpen() {
await testSubjects.existOrFail('mlJobOpenCustomUrlFormButton', { timeout: 5000 });
- }
+ },
- public async closeEditJobFlyout() {
+ async closeEditJobFlyout() {
if (await testSubjects.exists('mlEditJobFlyoutCloseButton')) {
await testSubjects.click('mlEditJobFlyoutCloseButton');
await testSubjects.missingOrFail('mlJobEditFlyout');
}
- }
+ },
- public async saveEditJobFlyoutChanges() {
+ async saveEditJobFlyoutChanges() {
await testSubjects.click('mlEditJobFlyoutSaveButton');
await testSubjects.missingOrFail('mlJobEditFlyout', { timeout: 5000 });
- }
+ },
- public async clickOpenCustomUrlEditor() {
+ async clickOpenCustomUrlEditor() {
await this.ensureEditCustomUrlTabOpen();
await testSubjects.click('mlJobOpenCustomUrlFormButton');
await testSubjects.existOrFail('mlJobCustomUrlForm');
- }
+ },
- public async getExistingCustomUrlCount(): Promise {
+ async getExistingCustomUrlCount(): Promise {
const existingCustomUrls = await testSubjects.findAll('mlJobEditCustomUrlItemLabel');
return existingCustomUrls.length;
- }
+ },
- public async saveCustomUrl(
- expectedLabel: string,
- expectedIndex: number,
- expectedValue?: string
- ) {
+ async saveCustomUrl(expectedLabel: string, expectedIndex: number, expectedValue?: string) {
await retry.tryForTime(5000, async () => {
await testSubjects.click('mlJobAddCustomUrl');
await customUrls.assertCustomUrlLabel(expectedIndex, expectedLabel);
@@ -737,9 +655,9 @@ export function MachineLearningJobTableProvider(
if (expectedValue !== undefined) {
await customUrls.assertCustomUrlUrlValue(expectedIndex, expectedValue);
}
- }
+ },
- public async fillInDiscoverUrlForm(customUrl: DiscoverUrlConfig) {
+ async fillInDiscoverUrlForm(customUrl: DiscoverUrlConfig) {
await this.clickOpenCustomUrlEditor();
await customUrls.setCustomUrlLabel(customUrl.label);
await mlCommonUI.selectRadioGroupValue(
@@ -758,9 +676,9 @@ export function MachineLearningJobTableProvider(
if (customUrl.timeRange === TIME_RANGE_TYPE.INTERVAL) {
await customUrls.setCustomUrlTimeRangeInterval(customUrl.timeRangeInterval!);
}
- }
+ },
- public async fillInDashboardUrlForm(customUrl: DashboardUrlConfig) {
+ async fillInDashboardUrlForm(customUrl: DashboardUrlConfig) {
await this.clickOpenCustomUrlEditor();
await customUrls.setCustomUrlLabel(customUrl.label);
await mlCommonUI.selectRadioGroupValue(
@@ -779,16 +697,16 @@ export function MachineLearningJobTableProvider(
if (customUrl.timeRange === TIME_RANGE_TYPE.INTERVAL) {
await customUrls.setCustomUrlTimeRangeInterval(customUrl.timeRangeInterval!);
}
- }
+ },
- public async fillInOtherUrlForm(customUrl: OtherUrlConfig) {
+ async fillInOtherUrlForm(customUrl: OtherUrlConfig) {
await this.clickOpenCustomUrlEditor();
await customUrls.setCustomUrlLabel(customUrl.label);
await mlCommonUI.selectRadioGroupValue(`mlJobCustomUrlLinkToTypeInput`, URL_TYPE.OTHER);
await customUrls.setCustomUrlOtherTypeUrl(customUrl.url);
- }
+ },
- public async addDiscoverCustomUrl(jobId: string, customUrl: DiscoverUrlConfig) {
+ async addDiscoverCustomUrl(jobId: string, customUrl: DiscoverUrlConfig) {
await retry.tryForTime(30 * 1000, async () => {
await this.closeEditJobFlyout();
await this.openEditCustomUrlsForJobTab(jobId);
@@ -800,9 +718,9 @@ export function MachineLearningJobTableProvider(
// Save the job
await this.saveEditJobFlyoutChanges();
- }
+ },
- public async addDashboardCustomUrl(
+ async addDashboardCustomUrl(
jobId: string,
customUrl: DashboardUrlConfig,
expectedResult: { index: number; url: string }
@@ -816,9 +734,9 @@ export function MachineLearningJobTableProvider(
// Save the job
await this.saveEditJobFlyoutChanges();
- }
+ },
- public async addOtherTypeCustomUrl(jobId: string, customUrl: OtherUrlConfig) {
+ async addOtherTypeCustomUrl(jobId: string, customUrl: OtherUrlConfig) {
await retry.tryForTime(30 * 1000, async () => {
await this.closeEditJobFlyout();
await this.openEditCustomUrlsForJobTab(jobId);
@@ -830,9 +748,9 @@ export function MachineLearningJobTableProvider(
// Save the job
await this.saveEditJobFlyoutChanges();
- }
+ },
- public async editCustomUrl(
+ async editCustomUrl(
jobId: string,
indexInList: number,
customUrl: { label: string; url: string }
@@ -843,9 +761,9 @@ export function MachineLearningJobTableProvider(
// Save the edit
await this.saveEditJobFlyoutChanges();
- }
+ },
- public async deleteCustomUrl(jobId: string, indexInList: number) {
+ async deleteCustomUrl(jobId: string, indexInList: number) {
await this.openEditCustomUrlsForJobTab(jobId);
const beforeCustomUrls = await testSubjects.findAll('mlJobEditCustomUrlItemLabel');
await customUrls.deleteCustomUrl(indexInList);
@@ -855,30 +773,31 @@ export function MachineLearningJobTableProvider(
await this.openEditCustomUrlsForJobTab(jobId);
await customUrls.assertCustomUrlsLength(beforeCustomUrls.length - 1);
await this.closeEditJobFlyout();
- }
+ },
- public async openTestCustomUrl(jobId: string, indexInList: number) {
+ async openTestCustomUrl(jobId: string, indexInList: number) {
await this.openEditCustomUrlsForJobTab(jobId);
await customUrls.clickTestCustomUrl(indexInList);
- }
+ },
- public async testDiscoverCustomUrlAction(expectedHitCountFormatted: string) {
+ async testDiscoverCustomUrlAction(expectedHitCountFormatted: string) {
await customUrls.assertDiscoverCustomUrlAction(expectedHitCountFormatted);
- }
+ },
- public async testDashboardCustomUrlAction(expectedPanelCount: number) {
+ async testDashboardCustomUrlAction(expectedPanelCount: number) {
await customUrls.assertDashboardCustomUrlAction(expectedPanelCount);
- }
+ },
- public async testOtherTypeCustomUrlAction(
- jobId: string,
- indexInList: number,
- expectedUrl: string
- ) {
+ async testOtherTypeCustomUrlAction(jobId: string, indexInList: number, expectedUrl: string) {
// Can't test the contents of the external page, so just check the expected URL.
await this.openEditCustomUrlsForJobTab(jobId);
await customUrls.assertCustomUrlUrlValue(indexInList, expectedUrl);
await this.closeEditJobFlyout();
- }
- })();
+ },
+
+ async assertJobListMultiSelectionText(expectedMsg: string): Promise {
+ const visibleText = await testSubjects.getVisibleText('~mlADJobListMultiSelectActionsArea');
+ expect(visibleText).to.be(expectedMsg);
+ },
+ };
}
From 9d22e8c6a8d92449055fd99b67b745d1995b5107 Mon Sep 17 00:00:00 2001
From: Tre
Date: Mon, 16 Sep 2024 15:42:06 +0100
Subject: [PATCH 025/139] [FTR] Collapse Alerting API Helpers Impl (#192216)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Summary
Resolves: https://github.com/elastic/kibana/issues/192201
- Expose `TryWithRetriesOptions`
- Tune timeouts to pass ci
- Add attempt count debug info to `retry/retry_for_success.ts`
- Helps with tuning timeout params
- Move exposure of `AlertingApiProvider` from
`x-pack/test_serverless/api_integration/services/index.ts` ->
`x-pack/test_serverless/shared/services/deployment_agnostic_services.ts`
- This exposes the alerting api under Deployment Agnostic Services (DA),
and DA is exposed within
`x-pack/test_serverless/functional/services/index.ts` (Shared Services
[Serverless])
- Collapse helper script functions into just another object literal
stanza within `AlertingApiProvider`
- Update all references
- Refactor alerting api to use `retry` service, instead of p-retry
(following [this pr](https://github.com/elastic/kibana/pull/178660))
### Additional debug logging
Run in debug mode (add `-v`):
```
node scripts/functional_tests \
--config x-pack/test_serverless/api_integration/test_suites/search/common_configs/config.group1.ts \
--grep "Summary actions"
-v
```
#### After
```
│ sill retry.tryWithRetries('Alerting API - waitForDocumentInIndex, retryOptions: {"retryCount":5,"retryDelay":200}', [object AsyncFunction], [object Object])
│ debg --- retry.tryWithRetries error: index_not_found_exception
│ Root causes:
│ index_not_found_exception: no such index [alert-action-es-query] - Attempt #: 1
│ sill es.search([object Object])
│ debg --- retry.tryWithRetries failed again with the same message... - Attempt #: 2
│ sill es.search([object Object])
│ debg --- retry.tryWithRetries failed again with the same message... - Attempt #: 3
│ sill es.search([object Object])
│ debg --- retry.tryWithRetries failed again with the same message... - Attempt #: 4
│ sill es.search([object Object])
│ debg --- retry.tryWithRetries failed again with the same message... - Attempt #: 5
...
// Msg after all attempts fail:
│ Error: retry.tryWithRetries reached the limit of attempts waiting for 'Alerting API - waitForDocumentInIndex, retryOptions: {"retryCount":5,"retryDelay":200}': 5 out of 5
│ ResponseError: index_not_found_exception
│ Root causes:
│ index_not_found_exception: no such index [alert-action-es-query]
│ at SniffingTransport._request (node_modules/@elastic/transport/src/Transport.ts:601:17)
│ at processTicksAndRejections (node:internal/process/task_queues:95:5)
│ at /Users/trezworkbox/dev/main.worktrees/cleanup-alerting-api/node_modules/@elastic/transport/src/Transport.ts:704:22
│ at SniffingTransport.request (node_modules/@elastic/transport/src/Transport.ts:701:14)
│ at Proxy.SearchApi (node_modules/@elastic/elasticsearch/src/api/api/search.ts:96:10)
│ at alerting_api.ts:123:28
│ at runAttempt (retry_for_success.ts:30:15)
│ at retryForSuccess (retry_for_success.ts:99:21)
│ at Proxy.tryWithRetries (retry.ts:113:12)
│ at Object.waitForDocumentInIndex (alerting_api.ts:120:14)
│ at Context. (summary_actions.ts:146:20)
│ at Object.apply (wrap_function.js:74:16)
│ at Object.apply (wrap_function.js:74:16)
│ at onFailure (retry_for_success.ts:18:9)
│ at retryForSuccess (retry_for_success.ts:75:7)
│ at Proxy.tryWithRetries (retry.ts:113:12)
│ at Object.waitForDocumentInIndex (alerting_api.ts:120:14)
│ at Context. (summary_actions.ts:146:20)
│ at Object.apply (wrap_function.js:74:16)
│ at Object.apply (wrap_function.js:74:16)
```
### Notes
Was put back in draft to additional scope detailed in issue linked
above.
---------
Co-authored-by: Elastic Machine
---
.../index.ts | 2 +-
.../services/retry/index.ts | 2 +-
.../services/retry/retry.ts | 2 +-
.../services/retry/retry_for_success.ts | 10 +-
.../api_integration/services/alerting_api.ts | 176 ---
.../api_integration/services/index.ts | 2 -
.../common/alerting/alert_documents.ts | 35 +-
.../alerting/helpers/alerting_api_helper.ts | 478 --------
.../helpers/alerting_wait_for_helpers.ts | 402 -------
.../test_suites/common/alerting/rules.ts | 234 ++--
.../common/alerting/summary_actions.ts | 87 +-
.../es_query_rule/es_query_rule.ts | 6 +-
.../functional/services/index.ts | 3 +-
.../observability/rules/rules_list.ts | 221 ++--
.../test_suites/search/rules/rule_details.ts | 56 +-
.../shared/services/alerting_api.ts | 1032 +++++++++++++++++
.../services/deployment_agnostic_services.ts | 3 +-
17 files changed, 1306 insertions(+), 1445 deletions(-)
delete mode 100644 x-pack/test_serverless/api_integration/services/alerting_api.ts
delete mode 100644 x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_api_helper.ts
delete mode 100644 x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_wait_for_helpers.ts
create mode 100644 x-pack/test_serverless/shared/services/alerting_api.ts
diff --git a/packages/kbn-ftr-common-functional-services/index.ts b/packages/kbn-ftr-common-functional-services/index.ts
index e156949f0daf9..506566216c721 100644
--- a/packages/kbn-ftr-common-functional-services/index.ts
+++ b/packages/kbn-ftr-common-functional-services/index.ts
@@ -14,7 +14,7 @@ import { KibanaServerProvider } from './services/kibana_server';
export { KibanaServerProvider } from './services/kibana_server';
export type KibanaServer = ProvidedType;
-export { RetryService } from './services/retry';
+export { RetryService, type TryWithRetriesOptions } from './services/retry';
import { EsArchiverProvider } from './services/es_archiver';
export type EsArchiver = ProvidedType;
diff --git a/packages/kbn-ftr-common-functional-services/services/retry/index.ts b/packages/kbn-ftr-common-functional-services/services/retry/index.ts
index 6f42e0368364d..f96e413da2680 100644
--- a/packages/kbn-ftr-common-functional-services/services/retry/index.ts
+++ b/packages/kbn-ftr-common-functional-services/services/retry/index.ts
@@ -7,4 +7,4 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
-export { RetryService } from './retry';
+export { RetryService, type TryWithRetriesOptions } from './retry';
diff --git a/packages/kbn-ftr-common-functional-services/services/retry/retry.ts b/packages/kbn-ftr-common-functional-services/services/retry/retry.ts
index 9ddd13ea583a7..614f57064512c 100644
--- a/packages/kbn-ftr-common-functional-services/services/retry/retry.ts
+++ b/packages/kbn-ftr-common-functional-services/services/retry/retry.ts
@@ -11,7 +11,7 @@ import { FtrService } from '../ftr_provider_context';
import { retryForSuccess } from './retry_for_success';
import { retryForTruthy } from './retry_for_truthy';
-interface TryWithRetriesOptions {
+export interface TryWithRetriesOptions {
retryCount: number;
retryDelay?: number;
timeout?: number;
diff --git a/packages/kbn-ftr-common-functional-services/services/retry/retry_for_success.ts b/packages/kbn-ftr-common-functional-services/services/retry/retry_for_success.ts
index 5401eb21286d1..921efacd88fcc 100644
--- a/packages/kbn-ftr-common-functional-services/services/retry/retry_for_success.ts
+++ b/packages/kbn-ftr-common-functional-services/services/retry/retry_for_success.ts
@@ -92,7 +92,7 @@ export async function retryForSuccess(log: ToolingLog, options: Options) {
if (lastError && onFailureBlock) {
const before = await runAttempt(onFailureBlock);
if ('error' in before) {
- log.debug(`--- onRetryBlock error: ${before.error.message}`);
+ log.debug(`--- onRetryBlock error: ${before.error.message} - Attempt #: ${attemptCounter}`);
}
}
@@ -104,9 +104,13 @@ export async function retryForSuccess(log: ToolingLog, options: Options) {
if ('error' in attempt) {
if (lastError && lastError.message === attempt.error.message) {
- log.debug(`--- ${methodName} failed again with the same message...`);
+ log.debug(
+ `--- ${methodName} failed again with the same message... - Attempt #: ${attemptCounter}`
+ );
} else {
- log.debug(`--- ${methodName} error: ${attempt.error.message}`);
+ log.debug(
+ `--- ${methodName} error: ${attempt.error.message} - Attempt #: ${attemptCounter}`
+ );
}
lastError = attempt.error;
diff --git a/x-pack/test_serverless/api_integration/services/alerting_api.ts b/x-pack/test_serverless/api_integration/services/alerting_api.ts
deleted file mode 100644
index 6000e9d8bdc88..0000000000000
--- a/x-pack/test_serverless/api_integration/services/alerting_api.ts
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import type {
- AggregationsAggregate,
- SearchResponse,
-} from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
-
-import { MetricThresholdParams } from '@kbn/infra-plugin/common/alerting/metrics';
-import { ThresholdParams } from '@kbn/observability-plugin/common/custom_threshold_rule/types';
-import { RoleCredentials } from '../../shared/services';
-import { SloBurnRateRuleParams } from './slo_api';
-import { FtrProviderContext } from '../ftr_provider_context';
-
-export function AlertingApiProvider({ getService }: FtrProviderContext) {
- const retry = getService('retry');
- const es = getService('es');
- const requestTimeout = 30 * 1000;
- const retryTimeout = 120 * 1000;
- const logger = getService('log');
- const svlCommonApi = getService('svlCommonApi');
- const supertestWithoutAuth = getService('supertestWithoutAuth');
-
- return {
- async waitForRuleStatus({
- roleAuthc,
- ruleId,
- expectedStatus,
- }: {
- roleAuthc: RoleCredentials;
- ruleId: string;
- expectedStatus: string;
- }) {
- if (!ruleId) {
- throw new Error(`'ruleId' is undefined`);
- }
- return await retry.tryForTime(retryTimeout, async () => {
- const response = await supertestWithoutAuth
- .get(`/api/alerting/rule/${ruleId}`)
- .set(svlCommonApi.getInternalRequestHeader())
- .set(roleAuthc.apiKeyHeader)
- .timeout(requestTimeout);
- const { execution_status: executionStatus } = response.body || {};
- const { status } = executionStatus || {};
- if (status !== expectedStatus) {
- throw new Error(`waitForStatus(${expectedStatus}): got ${status}`);
- }
- return executionStatus?.status;
- });
- },
-
- async waitForDocumentInIndex({
- indexName,
- docCountTarget = 1,
- }: {
- indexName: string;
- docCountTarget?: number;
- }): Promise>> {
- return await retry.tryForTime(retryTimeout, async () => {
- const response = await es.search({
- index: indexName,
- rest_total_hits_as_int: true,
- });
- logger.debug(`Found ${response.hits.total} docs, looking for atleast ${docCountTarget}.`);
- if (!response.hits.total || (response.hits.total as number) < docCountTarget) {
- throw new Error('No hits found');
- }
- return response;
- });
- },
-
- async waitForAlertInIndex({
- indexName,
- ruleId,
- }: {
- indexName: string;
- ruleId: string;
- }): Promise>> {
- if (!ruleId) {
- throw new Error(`'ruleId' is undefined`);
- }
- return await retry.tryForTime(retryTimeout, async () => {
- const response = await es.search({
- index: indexName,
- body: {
- query: {
- term: {
- 'kibana.alert.rule.uuid': ruleId,
- },
- },
- },
- });
- if (response.hits.hits.length === 0) {
- throw new Error('No hits found');
- }
- return response;
- });
- },
-
- async createIndexConnector({
- roleAuthc,
- name,
- indexName,
- }: {
- roleAuthc: RoleCredentials;
- name: string;
- indexName: string;
- }) {
- const { body } = await supertestWithoutAuth
- .post(`/api/actions/connector`)
- .set(svlCommonApi.getInternalRequestHeader())
- .set(roleAuthc.apiKeyHeader)
- .send({
- name,
- config: {
- index: indexName,
- refresh: true,
- },
- connector_type_id: '.index',
- });
- return body.id as string;
- },
-
- async createRule({
- roleAuthc,
- name,
- ruleTypeId,
- params,
- actions = [],
- tags = [],
- schedule,
- consumer,
- }: {
- roleAuthc: RoleCredentials;
- ruleTypeId: string;
- name: string;
- params: MetricThresholdParams | ThresholdParams | SloBurnRateRuleParams;
- actions?: any[];
- tags?: any[];
- schedule?: { interval: string };
- consumer: string;
- }) {
- const { body } = await supertestWithoutAuth
- .post(`/api/alerting/rule`)
- .set(svlCommonApi.getInternalRequestHeader())
- .set(roleAuthc.apiKeyHeader)
- .send({
- params,
- consumer,
- schedule: schedule || {
- interval: '5m',
- },
- tags,
- name,
- rule_type_id: ruleTypeId,
- actions,
- });
- return body;
- },
-
- async findRule(roleAuthc: RoleCredentials, ruleId: string) {
- if (!ruleId) {
- throw new Error(`'ruleId' is undefined`);
- }
- const response = await supertestWithoutAuth
- .get('/api/alerting/rules/_find')
- .set(svlCommonApi.getInternalRequestHeader())
- .set(roleAuthc.apiKeyHeader);
- return response.body.data.find((obj: any) => obj.id === ruleId);
- },
- };
-}
diff --git a/x-pack/test_serverless/api_integration/services/index.ts b/x-pack/test_serverless/api_integration/services/index.ts
index 347fc1f68b0ca..22ce9b3bb4794 100644
--- a/x-pack/test_serverless/api_integration/services/index.ts
+++ b/x-pack/test_serverless/api_integration/services/index.ts
@@ -9,7 +9,6 @@ import { GenericFtrProviderContext } from '@kbn/test';
import { services as deploymentAgnosticSharedServices } from '../../shared/services/deployment_agnostic_services';
import { services as svlSharedServices } from '../../shared/services';
-import { AlertingApiProvider } from './alerting_api';
import { SamlToolsProvider } from './saml_tools';
import { SvlCasesServiceProvider } from './svl_cases';
import { SloApiProvider } from './slo_api';
@@ -35,7 +34,6 @@ export const services = {
// serverless FTR services
...svlSharedServices,
- alertingApi: AlertingApiProvider,
samlTools: SamlToolsProvider,
svlCases: SvlCasesServiceProvider,
sloApi: SloApiProvider,
diff --git a/x-pack/test_serverless/api_integration/test_suites/common/alerting/alert_documents.ts b/x-pack/test_serverless/api_integration/test_suites/common/alerting/alert_documents.ts
index 93dd4e5565db5..cf727fd9fd1bc 100644
--- a/x-pack/test_serverless/api_integration/test_suites/common/alerting/alert_documents.ts
+++ b/x-pack/test_serverless/api_integration/test_suites/common/alerting/alert_documents.ts
@@ -42,22 +42,18 @@ import {
ALERT_PREVIOUS_ACTION_GROUP,
} from '@kbn/rule-data-utils';
import { FtrProviderContext } from '../../../ftr_provider_context';
-import { createEsQueryRule } from './helpers/alerting_api_helper';
-import { waitForAlertInIndex, waitForNumRuleRuns } from './helpers/alerting_wait_for_helpers';
import { ObjectRemover } from '../../../../shared/lib';
-import { InternalRequestHeader, RoleCredentials } from '../../../../shared/services';
+import { RoleCredentials } from '../../../../shared/services';
const OPEN_OR_ACTIVE = new Set(['open', 'active']);
export default function ({ getService }: FtrProviderContext) {
- const svlCommonApi = getService('svlCommonApi');
const svlUserManager = getService('svlUserManager');
- const supertestWithoutAuth = getService('supertestWithoutAuth');
let roleAdmin: RoleCredentials;
- let internalReqHeader: InternalRequestHeader;
const supertest = getService('supertest');
const esClient = getService('es');
const objectRemover = new ObjectRemover(supertest);
+ const alertingApi = getService('alertingApi');
describe('Alert documents', function () {
// Timeout of 360000ms exceeded
@@ -68,7 +64,6 @@ export default function ({ getService }: FtrProviderContext) {
before(async () => {
roleAdmin = await svlUserManager.createM2mApiKeyWithRoleScope('admin');
- internalReqHeader = svlCommonApi.getInternalRequestHeader();
});
afterEach(async () => {
@@ -80,10 +75,8 @@ export default function ({ getService }: FtrProviderContext) {
});
it('should generate an alert document for an active alert', async () => {
- const createdRule = await createEsQueryRule({
- supertestWithoutAuth,
+ const createdRule = await alertingApi.helpers.createEsQueryRule({
roleAuthc: roleAdmin,
- internalReqHeader,
consumer: 'alerts',
name: 'always fire',
ruleTypeId: RULE_TYPE_ID,
@@ -103,17 +96,15 @@ export default function ({ getService }: FtrProviderContext) {
// get the first alert document written
const testStart1 = new Date();
- await waitForNumRuleRuns({
- supertestWithoutAuth,
+ await alertingApi.helpers.waitForNumRuleRuns({
roleAuthc: roleAdmin,
- internalReqHeader,
numOfRuns: 1,
ruleId,
esClient,
testStart: testStart1,
});
- const alResp1 = await waitForAlertInIndex({
+ const alResp1 = await alertingApi.helpers.waitForAlertInIndex({
esClient,
filter: testStart1,
indexName: ALERT_INDEX,
@@ -206,10 +197,8 @@ export default function ({ getService }: FtrProviderContext) {
});
it('should update an alert document for an ongoing alert', async () => {
- const createdRule = await createEsQueryRule({
- supertestWithoutAuth,
+ const createdRule = await alertingApi.helpers.createEsQueryRule({
roleAuthc: roleAdmin,
- internalReqHeader,
consumer: 'alerts',
name: 'always fire',
ruleTypeId: RULE_TYPE_ID,
@@ -229,17 +218,15 @@ export default function ({ getService }: FtrProviderContext) {
// get the first alert document written
const testStart1 = new Date();
- await waitForNumRuleRuns({
- supertestWithoutAuth,
+ await alertingApi.helpers.waitForNumRuleRuns({
roleAuthc: roleAdmin,
- internalReqHeader,
numOfRuns: 1,
ruleId,
esClient,
testStart: testStart1,
});
- const alResp1 = await waitForAlertInIndex({
+ const alResp1 = await alertingApi.helpers.waitForAlertInIndex({
esClient,
filter: testStart1,
indexName: ALERT_INDEX,
@@ -249,17 +236,15 @@ export default function ({ getService }: FtrProviderContext) {
// wait for another run, get the updated alert document
const testStart2 = new Date();
- await waitForNumRuleRuns({
- supertestWithoutAuth,
+ await alertingApi.helpers.waitForNumRuleRuns({
roleAuthc: roleAdmin,
- internalReqHeader,
numOfRuns: 1,
ruleId,
esClient,
testStart: testStart2,
});
- const alResp2 = await waitForAlertInIndex({
+ const alResp2 = await alertingApi.helpers.waitForAlertInIndex({
esClient,
filter: testStart2,
indexName: ALERT_INDEX,
diff --git a/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_api_helper.ts b/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_api_helper.ts
deleted file mode 100644
index f7a909c688d0e..0000000000000
--- a/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_api_helper.ts
+++ /dev/null
@@ -1,478 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import moment from 'moment';
-import { v4 as uuidv4 } from 'uuid';
-import type { Agent as SuperTestAgent } from 'supertest';
-import {
- InternalRequestHeader,
- RoleCredentials,
- SupertestWithoutAuthProviderType,
-} from '../../../../../shared/services';
-
-interface CreateEsQueryRuleParams {
- size: number;
- thresholdComparator: string;
- threshold: number[];
- timeWindowSize?: number;
- timeWindowUnit?: string;
- esQuery?: string;
- timeField?: string;
- searchConfiguration?: unknown;
- indexName?: string;
- excludeHitsFromPreviousRun?: boolean;
- aggType?: string;
- aggField?: string;
- groupBy?: string;
- termField?: string;
- termSize?: number;
- index?: string[];
-}
-
-export async function createIndexConnector({
- supertestWithoutAuth,
- roleAuthc,
- internalReqHeader,
- name,
- indexName,
-}: {
- supertestWithoutAuth: SupertestWithoutAuthProviderType;
- roleAuthc: RoleCredentials;
- internalReqHeader: InternalRequestHeader;
- name: string;
- indexName: string;
-}) {
- const { body } = await supertestWithoutAuth
- .post(`/api/actions/connector`)
- .set(internalReqHeader)
- .set(roleAuthc.apiKeyHeader)
- .send({
- name,
- config: {
- index: indexName,
- refresh: true,
- },
- connector_type_id: '.index',
- })
- .expect(200);
- return body;
-}
-
-export async function createSlackConnector({
- supertestWithoutAuth,
- roleAuthc,
- internalReqHeader,
- name,
-}: {
- supertestWithoutAuth: SupertestWithoutAuthProviderType;
- roleAuthc: RoleCredentials;
- internalReqHeader: InternalRequestHeader;
- name: string;
-}) {
- const { body } = await supertestWithoutAuth
- .post(`/api/actions/connector`)
- .set(internalReqHeader)
- .set(roleAuthc.apiKeyHeader)
- .send({
- name,
- config: {},
- secrets: {
- webhookUrl: 'http://test',
- },
- connector_type_id: '.slack',
- })
- .expect(200);
- return body;
-}
-
-export async function createEsQueryRule({
- supertestWithoutAuth,
- roleAuthc,
- internalReqHeader,
- name,
- ruleTypeId,
- params,
- actions = [],
- tags = [],
- schedule,
- consumer,
- notifyWhen,
- enabled = true,
-}: {
- supertestWithoutAuth: SupertestWithoutAuthProviderType;
- roleAuthc: RoleCredentials;
- internalReqHeader: InternalRequestHeader;
- ruleTypeId: string;
- name: string;
- params: CreateEsQueryRuleParams;
- consumer: string;
- actions?: any[];
- tags?: any[];
- schedule?: { interval: string };
- notifyWhen?: string;
- enabled?: boolean;
-}) {
- const { body } = await supertestWithoutAuth
- .post(`/api/alerting/rule`)
- .set(internalReqHeader)
- .set(roleAuthc.apiKeyHeader)
- .send({
- enabled,
- params,
- consumer,
- schedule: schedule || {
- interval: '1h',
- },
- tags,
- name,
- rule_type_id: ruleTypeId,
- actions,
- ...(notifyWhen ? { notify_when: notifyWhen, throttle: '5m' } : {}),
- })
- .expect(200);
- return body;
-}
-
-export const generateUniqueKey = () => uuidv4().replace(/-/g, '');
-
-export async function createAnomalyRule({
- supertest,
- name = generateUniqueKey(),
- actions = [],
- tags = ['foo', 'bar'],
- schedule,
- consumer = 'alerts',
- notifyWhen,
- enabled = true,
- ruleTypeId = 'apm.anomaly',
- params,
-}: {
- supertest: SuperTestAgent;
- name?: string;
- consumer?: string;
- actions?: any[];
- tags?: any[];
- schedule?: { interval: string };
- notifyWhen?: string;
- enabled?: boolean;
- ruleTypeId?: string;
- params?: any;
-}) {
- const { body } = await supertest
- .post(`/api/alerting/rule`)
- .set('kbn-xsrf', 'foo')
- .set('x-elastic-internal-origin', 'foo')
- .send({
- enabled,
- params: params || {
- anomalySeverityType: 'critical',
- anomalyDetectorTypes: ['txLatency'],
- environment: 'ENVIRONMENT_ALL',
- windowSize: 30,
- windowUnit: 'm',
- },
- consumer,
- schedule: schedule || {
- interval: '1m',
- },
- tags,
- name,
- rule_type_id: ruleTypeId,
- actions,
- ...(notifyWhen ? { notify_when: notifyWhen, throttle: '5m' } : {}),
- })
- .expect(200);
- return body;
-}
-
-export async function createLatencyThresholdRule({
- supertest,
- name = generateUniqueKey(),
- actions = [],
- tags = ['foo', 'bar'],
- schedule,
- consumer = 'apm',
- notifyWhen,
- enabled = true,
- ruleTypeId = 'apm.transaction_duration',
- params,
-}: {
- supertest: SuperTestAgent;
- name?: string;
- consumer?: string;
- actions?: any[];
- tags?: any[];
- schedule?: { interval: string };
- notifyWhen?: string;
- enabled?: boolean;
- ruleTypeId?: string;
- params?: any;
-}) {
- const { body } = await supertest
- .post(`/api/alerting/rule`)
- .set('kbn-xsrf', 'foo')
- .set('x-elastic-internal-origin', 'foo')
- .send({
- enabled,
- params: params || {
- aggregationType: 'avg',
- environment: 'ENVIRONMENT_ALL',
- threshold: 1500,
- windowSize: 5,
- windowUnit: 'm',
- },
- consumer,
- schedule: schedule || {
- interval: '1m',
- },
- tags,
- name,
- rule_type_id: ruleTypeId,
- actions,
- ...(notifyWhen ? { notify_when: notifyWhen, throttle: '5m' } : {}),
- });
- return body;
-}
-
-export async function createInventoryRule({
- supertest,
- name = generateUniqueKey(),
- actions = [],
- tags = ['foo', 'bar'],
- schedule,
- consumer = 'alerts',
- notifyWhen,
- enabled = true,
- ruleTypeId = 'metrics.alert.inventory.threshold',
- params,
-}: {
- supertest: SuperTestAgent;
- name?: string;
- consumer?: string;
- actions?: any[];
- tags?: any[];
- schedule?: { interval: string };
- notifyWhen?: string;
- enabled?: boolean;
- ruleTypeId?: string;
- params?: any;
-}) {
- const { body } = await supertest
- .post(`/api/alerting/rule`)
- .set('kbn-xsrf', 'foo')
- .set('x-elastic-internal-origin', 'foo')
- .send({
- enabled,
- params: params || {
- nodeType: 'host',
- criteria: [
- {
- metric: 'cpu',
- comparator: '>',
- threshold: [5],
- timeSize: 1,
- timeUnit: 'm',
- customMetric: {
- type: 'custom',
- id: 'alert-custom-metric',
- field: '',
- aggregation: 'avg',
- },
- },
- ],
- sourceId: 'default',
- },
- consumer,
- schedule: schedule || {
- interval: '1m',
- },
- tags,
- name,
- rule_type_id: ruleTypeId,
- actions,
- ...(notifyWhen ? { notify_when: notifyWhen, throttle: '5m' } : {}),
- })
- .expect(200);
- return body;
-}
-
-export async function disableRule({
- supertest,
- ruleId,
-}: {
- supertest: SuperTestAgent;
- ruleId: string;
-}) {
- const { body } = await supertest
- .post(`/api/alerting/rule/${ruleId}/_disable`)
- .set('kbn-xsrf', 'foo')
- .set('x-elastic-internal-origin', 'foo')
- .expect(204);
- return body;
-}
-
-export async function updateEsQueryRule({
- supertest,
- ruleId,
- updates,
-}: {
- supertest: SuperTestAgent;
- ruleId: string;
- updates: any;
-}) {
- const { body: r } = await supertest
- .get(`/api/alerting/rule/${ruleId}`)
- .set('kbn-xsrf', 'foo')
- .set('x-elastic-internal-origin', 'foo')
- .expect(200);
- const body = await supertest
- .put(`/api/alerting/rule/${ruleId}`)
- .set('kbn-xsrf', 'foo')
- .set('x-elastic-internal-origin', 'foo')
- .send({
- ...{
- name: r.name,
- schedule: r.schedule,
- throttle: r.throttle,
- tags: r.tags,
- params: r.params,
- notify_when: r.notifyWhen,
- actions: r.actions.map((action: any) => ({
- group: action.group,
- params: action.params,
- id: action.id,
- frequency: action.frequency,
- })),
- },
- ...updates,
- })
- .expect(200);
- return body;
-}
-
-export async function runRule({
- supertestWithoutAuth,
- roleAuthc,
- internalReqHeader,
- ruleId,
-}: {
- supertestWithoutAuth: SupertestWithoutAuthProviderType;
- roleAuthc: RoleCredentials;
- internalReqHeader: InternalRequestHeader;
- ruleId: string;
-}) {
- const response = await supertestWithoutAuth
- .post(`/internal/alerting/rule/${ruleId}/_run_soon`)
- .set(internalReqHeader)
- .set(roleAuthc.apiKeyHeader)
- .expect(204);
- return response;
-}
-
-export async function muteRule({
- supertest,
- ruleId,
-}: {
- supertest: SuperTestAgent;
- ruleId: string;
-}) {
- const { body } = await supertest
- .post(`/api/alerting/rule/${ruleId}/_mute_all`)
- .set('kbn-xsrf', 'foo')
- .set('x-elastic-internal-origin', 'foo')
- .expect(204);
- return body;
-}
-
-export async function enableRule({
- supertest,
- ruleId,
-}: {
- supertest: SuperTestAgent;
- ruleId: string;
-}) {
- const { body } = await supertest
- .post(`/api/alerting/rule/${ruleId}/_enable`)
- .set('kbn-xsrf', 'foo')
- .set('x-elastic-internal-origin', 'foo')
- .expect(204);
- return body;
-}
-
-export async function muteAlert({
- supertest,
- ruleId,
- alertId,
-}: {
- supertest: SuperTestAgent;
- ruleId: string;
- alertId: string;
-}) {
- const { body } = await supertest
- .post(`/api/alerting/rule/${ruleId}/alert/${alertId}/_mute`)
- .set('kbn-xsrf', 'foo')
- .set('x-elastic-internal-origin', 'foo')
- .expect(204);
- return body;
-}
-
-export async function unmuteRule({
- supertest,
- ruleId,
-}: {
- supertest: SuperTestAgent;
- ruleId: string;
-}) {
- const { body } = await supertest
- .post(`/api/alerting/rule/${ruleId}/_unmute_all`)
- .set('kbn-xsrf', 'foo')
- .set('x-elastic-internal-origin', 'foo')
- .expect(204);
- return body;
-}
-
-export async function snoozeRule({
- supertest,
- ruleId,
-}: {
- supertest: SuperTestAgent;
- ruleId: string;
-}) {
- const { body } = await supertest
- .post(`/internal/alerting/rule/${ruleId}/_snooze`)
- .set('kbn-xsrf', 'foo')
- .set('x-elastic-internal-origin', 'foo')
- .send({
- snooze_schedule: {
- duration: 100000000,
- rRule: {
- count: 1,
- dtstart: moment().format(),
- tzid: 'UTC',
- },
- },
- })
- .expect(204);
- return body;
-}
-
-export async function findRule({
- supertest,
- ruleId,
-}: {
- supertest: SuperTestAgent;
- ruleId: string;
-}) {
- if (!ruleId) {
- throw new Error(`'ruleId' is undefined`);
- }
- const response = await supertest
- .get(`/api/alerting/rule/${ruleId}`)
- .set('kbn-xsrf', 'foo')
- .set('x-elastic-internal-origin', 'foo');
- return response.body || {};
-}
diff --git a/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_wait_for_helpers.ts b/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_wait_for_helpers.ts
deleted file mode 100644
index c7f2ac357e4a2..0000000000000
--- a/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_wait_for_helpers.ts
+++ /dev/null
@@ -1,402 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import pRetry from 'p-retry';
-import type { Client } from '@elastic/elasticsearch';
-import type {
- AggregationsAggregate,
- SearchResponse,
-} from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
-import { runRule } from './alerting_api_helper';
-import type { SupertestWithoutAuthProviderType } from '../../../../../shared/services';
-import { RoleCredentials } from '../../../../../shared/services';
-import { InternalRequestHeader } from '../../../../../shared/services';
-
-export async function waitForDocumentInIndex({
- esClient,
- indexName,
- ruleId,
- num = 1,
- sort = 'desc',
-}: {
- esClient: Client;
- indexName: string;
- ruleId: string;
- num?: number;
- sort?: 'asc' | 'desc';
-}): Promise {
- return await pRetry(
- async () => {
- const response = await esClient.search({
- index: indexName,
- sort: `date:${sort}`,
- body: {
- query: {
- bool: {
- must: [
- {
- term: {
- 'ruleId.keyword': ruleId,
- },
- },
- ],
- },
- },
- },
- });
- if (response.hits.hits.length < num) {
- throw new Error(`Only found ${response.hits.hits.length} / ${num} documents`);
- }
- return response;
- },
- { retries: 10 }
- );
-}
-
-export async function getDocumentsInIndex({
- esClient,
- indexName,
- ruleId,
-}: {
- esClient: Client;
- indexName: string;
- ruleId: string;
-}): Promise {
- return await esClient.search({
- index: indexName,
- body: {
- query: {
- bool: {
- must: [
- {
- term: {
- 'ruleId.keyword': ruleId,
- },
- },
- ],
- },
- },
- },
- });
-}
-
-export async function createIndex({
- esClient,
- indexName,
-}: {
- esClient: Client;
- indexName: string;
-}) {
- return await esClient.indices.create(
- {
- index: indexName,
- body: {},
- },
- { meta: true }
- );
-}
-
-export async function waitForAlertInIndex({
- esClient,
- filter,
- indexName,
- ruleId,
- num = 1,
-}: {
- esClient: Client;
- filter: Date;
- indexName: string;
- ruleId: string;
- num: number;
-}): Promise>> {
- return await pRetry(
- async () => {
- const response = await esClient.search({
- index: indexName,
- body: {
- query: {
- bool: {
- must: [
- {
- term: {
- 'kibana.alert.rule.uuid': ruleId,
- },
- },
- {
- range: {
- '@timestamp': {
- gte: filter.getTime().toString(),
- },
- },
- },
- ],
- },
- },
- },
- });
- if (response.hits.hits.length < num) {
- throw new Error(`Only found ${response.hits.hits.length} / ${num} documents`);
- }
- return response;
- },
- { retries: 10 }
- );
-}
-
-export async function waitForAllTasksIdle({
- esClient,
- filter,
-}: {
- esClient: Client;
- filter: Date;
-}): Promise {
- return await pRetry(
- async () => {
- const response = await esClient.search({
- index: '.kibana_task_manager',
- body: {
- query: {
- bool: {
- must: [
- {
- terms: {
- 'task.scope': ['actions', 'alerting'],
- },
- },
- {
- range: {
- 'task.scheduledAt': {
- gte: filter.getTime().toString(),
- },
- },
- },
- ],
- must_not: [
- {
- term: {
- 'task.status': 'idle',
- },
- },
- ],
- },
- },
- },
- });
- if (response.hits.hits.length !== 0) {
- throw new Error(`Expected 0 hits but received ${response.hits.hits.length}`);
- }
- return response;
- },
- { retries: 10 }
- );
-}
-
-export async function waitForAllTasks({
- esClient,
- filter,
- taskType,
- attempts,
-}: {
- esClient: Client;
- filter: Date;
- taskType: string;
- attempts: number;
-}): Promise {
- return await pRetry(
- async () => {
- const response = await esClient.search({
- index: '.kibana_task_manager',
- body: {
- query: {
- bool: {
- must: [
- {
- term: {
- 'task.status': 'idle',
- },
- },
- {
- term: {
- 'task.attempts': attempts,
- },
- },
- {
- terms: {
- 'task.scope': ['actions', 'alerting'],
- },
- },
- {
- term: {
- 'task.taskType': taskType,
- },
- },
- {
- range: {
- 'task.scheduledAt': {
- gte: filter.getTime().toString(),
- },
- },
- },
- ],
- },
- },
- },
- });
- if (response.hits.hits.length === 0) {
- throw new Error('No hits found');
- }
- return response;
- },
- { retries: 10 }
- );
-}
-
-export async function waitForDisabled({
- esClient,
- ruleId,
- filter,
-}: {
- esClient: Client;
- ruleId: string;
- filter: Date;
-}): Promise {
- return await pRetry(
- async () => {
- const response = await esClient.search({
- index: '.kibana_task_manager',
- body: {
- query: {
- bool: {
- must: [
- {
- term: {
- 'task.id': `task:${ruleId}`,
- },
- },
- {
- terms: {
- 'task.scope': ['actions', 'alerting'],
- },
- },
- {
- range: {
- 'task.scheduledAt': {
- gte: filter.getTime().toString(),
- },
- },
- },
- {
- term: {
- 'task.enabled': true,
- },
- },
- ],
- },
- },
- },
- });
- if (response.hits.hits.length !== 0) {
- throw new Error(`Expected 0 hits but received ${response.hits.hits.length}`);
- }
- return response;
- },
- { retries: 10 }
- );
-}
-
-export async function waitForExecutionEventLog({
- esClient,
- filter,
- ruleId,
- num = 1,
-}: {
- esClient: Client;
- filter: Date;
- ruleId: string;
- num?: number;
-}): Promise {
- return await pRetry(
- async () => {
- const response = await esClient.search({
- index: '.kibana-event-log*',
- body: {
- query: {
- bool: {
- filter: [
- {
- term: {
- 'rule.id': {
- value: ruleId,
- },
- },
- },
- {
- term: {
- 'event.provider': {
- value: 'alerting',
- },
- },
- },
- {
- term: {
- 'event.action': 'execute',
- },
- },
- {
- range: {
- '@timestamp': {
- gte: filter.getTime().toString(),
- },
- },
- },
- ],
- },
- },
- },
- });
- if (response.hits.hits.length < num) {
- throw new Error('No hits found');
- }
- return response;
- },
- { retries: 10 }
- );
-}
-
-export async function waitForNumRuleRuns({
- supertestWithoutAuth,
- roleAuthc,
- internalReqHeader,
- numOfRuns,
- ruleId,
- esClient,
- testStart,
-}: {
- supertestWithoutAuth: SupertestWithoutAuthProviderType;
- roleAuthc: RoleCredentials;
- internalReqHeader: InternalRequestHeader;
- numOfRuns: number;
- ruleId: string;
- esClient: Client;
- testStart: Date;
-}) {
- for (let i = 0; i < numOfRuns; i++) {
- await pRetry(
- async () => {
- await runRule({ supertestWithoutAuth, roleAuthc, internalReqHeader, ruleId });
- await waitForExecutionEventLog({
- esClient,
- filter: testStart,
- ruleId,
- num: i + 1,
- });
- await waitForAllTasksIdle({ esClient, filter: testStart });
- },
- { retries: 10 }
- );
- }
-}
diff --git a/x-pack/test_serverless/api_integration/test_suites/common/alerting/rules.ts b/x-pack/test_serverless/api_integration/test_suites/common/alerting/rules.ts
index 37b78a5e1b36f..593c10f371f09 100644
--- a/x-pack/test_serverless/api_integration/test_suites/common/alerting/rules.ts
+++ b/x-pack/test_serverless/api_integration/test_suites/common/alerting/rules.ts
@@ -9,28 +9,6 @@ import { RULE_SAVED_OBJECT_TYPE } from '@kbn/alerting-plugin/server';
import expect from '@kbn/expect';
import { omit } from 'lodash';
import { FtrProviderContext } from '../../../ftr_provider_context';
-import {
- createIndexConnector,
- createEsQueryRule,
- disableRule,
- updateEsQueryRule,
- runRule,
- muteRule,
- enableRule,
- muteAlert,
- unmuteRule,
- createSlackConnector,
-} from './helpers/alerting_api_helper';
-import {
- createIndex,
- getDocumentsInIndex,
- waitForAllTasks,
- waitForAllTasksIdle,
- waitForDisabled,
- waitForDocumentInIndex,
- waitForExecutionEventLog,
- waitForNumRuleRuns,
-} from './helpers/alerting_wait_for_helpers';
import type { InternalRequestHeader, RoleCredentials } from '../../../../shared/services';
export default function ({ getService }: FtrProviderContext) {
@@ -39,7 +17,8 @@ export default function ({ getService }: FtrProviderContext) {
const esDeleteAllIndices = getService('esDeleteAllIndices');
const svlCommonApi = getService('svlCommonApi');
const svlUserManager = getService('svlUserManager');
- const supertestWithoutAuth = getService('supertestWithoutAuth');
+ const alertingApi = getService('alertingApi');
+
let roleAdmin: RoleCredentials;
let internalReqHeader: InternalRequestHeader;
@@ -73,19 +52,15 @@ export default function ({ getService }: FtrProviderContext) {
it('should schedule task, run rule and schedule actions when appropriate', async () => {
const testStart = new Date();
- const createdConnector = await createIndexConnector({
- supertestWithoutAuth,
+ const createdConnector = await alertingApi.helpers.createIndexConnector({
roleAuthc: roleAdmin,
- internalReqHeader,
name: 'Index Connector: Alerting API test',
indexName: ALERT_ACTION_INDEX,
});
connectorId = createdConnector.id;
- const createdRule = await createEsQueryRule({
- supertestWithoutAuth,
+ const createdRule = await alertingApi.helpers.createEsQueryRule({
roleAuthc: roleAdmin,
- internalReqHeader,
consumer: 'alerts',
name: 'always fire',
ruleTypeId: RULE_TYPE_ID,
@@ -130,10 +105,14 @@ export default function ({ getService }: FtrProviderContext) {
ruleId = createdRule.id;
// Wait for the action to index a document before disabling the alert and waiting for tasks to finish
- const resp = await waitForDocumentInIndex({
+ const resp = await alertingApi.helpers.waitForDocumentInIndex({
esClient,
indexName: ALERT_ACTION_INDEX,
ruleId,
+ retryOptions: {
+ retryCount: 12,
+ retryDelay: 2000,
+ },
});
expect(resp.hits.hits.length).to.be(1);
@@ -151,7 +130,7 @@ export default function ({ getService }: FtrProviderContext) {
tags: '',
});
- const eventLogResp = await waitForExecutionEventLog({
+ const eventLogResp = await alertingApi.helpers.waiting.waitForExecutionEventLog({
esClient,
filter: testStart,
ruleId,
@@ -171,19 +150,15 @@ export default function ({ getService }: FtrProviderContext) {
it('should pass updated rule params to executor', async () => {
const testStart = new Date();
- const createdConnector = await createIndexConnector({
- supertestWithoutAuth,
+ const createdConnector = await alertingApi.helpers.createIndexConnector({
roleAuthc: roleAdmin,
- internalReqHeader,
name: 'Index Connector: Alerting API test',
indexName: ALERT_ACTION_INDEX,
});
connectorId = createdConnector.id;
- const createdRule = await createEsQueryRule({
- supertestWithoutAuth,
+ const createdRule = await alertingApi.helpers.createEsQueryRule({
roleAuthc: roleAdmin,
- internalReqHeader,
consumer: 'alerts',
name: 'always fire',
ruleTypeId: RULE_TYPE_ID,
@@ -228,10 +203,11 @@ export default function ({ getService }: FtrProviderContext) {
ruleId = createdRule.id;
// Wait for the action to index a document before disabling the alert and waiting for tasks to finish
- const resp = await waitForDocumentInIndex({
+ const resp = await alertingApi.helpers.waitForDocumentInIndex({
esClient,
indexName: ALERT_ACTION_INDEX,
ruleId,
+ retryOptions: { retryDelay: 800, retryCount: 10 },
});
expect(resp.hits.hits.length).to.be(1);
@@ -249,13 +225,13 @@ export default function ({ getService }: FtrProviderContext) {
tags: '',
});
- await waitForAllTasksIdle({
+ await alertingApi.helpers.waiting.waitForAllTasksIdle({
esClient,
filter: testStart,
});
- await updateEsQueryRule({
- supertest,
+ await alertingApi.helpers.updateEsQueryRule({
+ roleAuthc: roleAdmin,
ruleId,
updates: {
name: 'def',
@@ -263,15 +239,13 @@ export default function ({ getService }: FtrProviderContext) {
},
});
- await runRule({
- supertestWithoutAuth,
+ await alertingApi.helpers.runRule({
roleAuthc: roleAdmin,
- internalReqHeader,
ruleId,
});
// make sure alert info passed to executor is correct
- const resp2 = await waitForDocumentInIndex({
+ const resp2 = await alertingApi.helpers.waitForDocumentInIndex({
esClient,
indexName: ALERT_ACTION_INDEX,
ruleId,
@@ -298,18 +272,14 @@ export default function ({ getService }: FtrProviderContext) {
const testStart = new Date();
// Should fail
- const createdConnector = await createSlackConnector({
- supertestWithoutAuth,
+ const createdConnector = await alertingApi.helpers.createSlackConnector({
roleAuthc: roleAdmin,
- internalReqHeader,
name: 'Slack Connector: Alerting API test',
});
connectorId = createdConnector.id;
- const createdRule = await createEsQueryRule({
- supertestWithoutAuth,
+ const createdRule = await alertingApi.helpers.createEsQueryRule({
roleAuthc: roleAdmin,
- internalReqHeader,
consumer: 'alerts',
name: 'always fire',
ruleTypeId: RULE_TYPE_ID,
@@ -341,7 +311,7 @@ export default function ({ getService }: FtrProviderContext) {
ruleId = createdRule.id;
// Should retry when the the action fails
- const resp = await waitForAllTasks({
+ const resp = await alertingApi.helpers.waiting.waitForAllTasks({
esClient,
filter: testStart,
taskType: 'actions:.slack',
@@ -353,19 +323,15 @@ export default function ({ getService }: FtrProviderContext) {
it('should throttle alerts when appropriate', async () => {
const testStart = new Date();
- const createdConnector = await createIndexConnector({
- supertestWithoutAuth,
+ const createdConnector = await alertingApi.helpers.createIndexConnector({
roleAuthc: roleAdmin,
- internalReqHeader,
name: 'Index Connector: Alerting API test',
indexName: ALERT_ACTION_INDEX,
});
connectorId = createdConnector.id;
- const createdRule = await createEsQueryRule({
- supertestWithoutAuth,
+ const createdRule = await alertingApi.helpers.createEsQueryRule({
roleAuthc: roleAdmin,
- internalReqHeader,
consumer: 'alerts',
name: 'always fire',
ruleTypeId: RULE_TYPE_ID,
@@ -407,29 +373,27 @@ export default function ({ getService }: FtrProviderContext) {
ruleId = createdRule.id;
// Wait until alerts ran at least 3 times before disabling the alert and waiting for tasks to finish
- await waitForNumRuleRuns({
- supertestWithoutAuth,
+ await alertingApi.helpers.waitForNumRuleRuns({
roleAuthc: roleAdmin,
- internalReqHeader,
numOfRuns: 3,
ruleId,
esClient,
testStart,
});
- await disableRule({
- supertest,
+ await alertingApi.helpers.disableRule({
+ roleAuthc: roleAdmin,
ruleId,
});
- await waitForDisabled({
+ await alertingApi.helpers.waiting.waitForDisabled({
esClient,
ruleId,
filter: testStart,
});
// Ensure actions only executed once
- const resp = await waitForDocumentInIndex({
+ const resp = await alertingApi.helpers.waitForDocumentInIndex({
esClient,
indexName: ALERT_ACTION_INDEX,
ruleId,
@@ -440,19 +404,15 @@ export default function ({ getService }: FtrProviderContext) {
it('should throttle alerts with throttled action when appropriate', async () => {
const testStart = new Date();
- const createdConnector = await createIndexConnector({
- supertestWithoutAuth,
+ const createdConnector = await alertingApi.helpers.createIndexConnector({
roleAuthc: roleAdmin,
- internalReqHeader,
name: 'Index Connector: Alerting API test',
indexName: ALERT_ACTION_INDEX,
});
connectorId = createdConnector.id;
- const createdRule = await createEsQueryRule({
- supertestWithoutAuth,
+ const createdRule = await alertingApi.helpers.createEsQueryRule({
roleAuthc: roleAdmin,
- internalReqHeader,
consumer: 'alerts',
name: 'always fire',
ruleTypeId: RULE_TYPE_ID,
@@ -498,29 +458,27 @@ export default function ({ getService }: FtrProviderContext) {
ruleId = createdRule.id;
// Wait until alerts ran at least 3 times before disabling the alert and waiting for tasks to finish
- await waitForNumRuleRuns({
- supertestWithoutAuth,
+ await alertingApi.helpers.waitForNumRuleRuns({
roleAuthc: roleAdmin,
- internalReqHeader,
numOfRuns: 3,
ruleId,
esClient,
testStart,
});
- await disableRule({
- supertest,
+ await alertingApi.helpers.disableRule({
+ roleAuthc: roleAdmin,
ruleId,
});
- await waitForDisabled({
+ await alertingApi.helpers.waiting.waitForDisabled({
esClient,
ruleId,
filter: testStart,
});
// Ensure actions only executed once
- const resp = await waitForDocumentInIndex({
+ const resp = await alertingApi.helpers.waitForDocumentInIndex({
esClient,
indexName: ALERT_ACTION_INDEX,
ruleId,
@@ -531,19 +489,15 @@ export default function ({ getService }: FtrProviderContext) {
it('should reset throttle window when not firing and should not throttle when changing groups', async () => {
const testStart = new Date();
- const createdConnector = await createIndexConnector({
- supertestWithoutAuth,
+ const createdConnector = await alertingApi.helpers.createIndexConnector({
roleAuthc: roleAdmin,
- internalReqHeader,
name: 'Index Connector: Alerting API test',
indexName: ALERT_ACTION_INDEX,
});
connectorId = createdConnector.id;
- const createdRule = await createEsQueryRule({
- supertestWithoutAuth,
+ const createdRule = await alertingApi.helpers.createEsQueryRule({
roleAuthc: roleAdmin,
- internalReqHeader,
consumer: 'alerts',
name: 'always fire',
ruleTypeId: RULE_TYPE_ID,
@@ -614,21 +568,21 @@ export default function ({ getService }: FtrProviderContext) {
ruleId = createdRule.id;
// Wait for the action to index a document
- const resp = await waitForDocumentInIndex({
+ const resp = await alertingApi.helpers.waiting.waitForDocumentInIndex({
esClient,
indexName: ALERT_ACTION_INDEX,
ruleId,
});
expect(resp.hits.hits.length).to.be(1);
- await waitForAllTasksIdle({
+ await alertingApi.helpers.waiting.waitForAllTasksIdle({
esClient,
filter: testStart,
});
// Update the rule to recover
- await updateEsQueryRule({
- supertest,
+ await alertingApi.helpers.updateEsQueryRule({
+ roleAuthc: roleAdmin,
ruleId,
updates: {
name: 'never fire',
@@ -645,34 +599,36 @@ export default function ({ getService }: FtrProviderContext) {
},
});
- await runRule({
- supertestWithoutAuth,
+ await alertingApi.helpers.runRule({
roleAuthc: roleAdmin,
- internalReqHeader,
ruleId,
});
- const eventLogResp = await waitForExecutionEventLog({
+ const eventLogResp = await alertingApi.helpers.waiting.waitForExecutionEventLog({
esClient,
filter: testStart,
ruleId,
num: 2,
+ retryOptions: {
+ retryCount: 12,
+ retryDelay: 2000,
+ },
});
expect(eventLogResp.hits.hits.length).to.be(2);
- await disableRule({
- supertest,
+ await alertingApi.helpers.disableRule({
+ roleAuthc: roleAdmin,
ruleId,
});
- await waitForDisabled({
+ await alertingApi.helpers.waiting.waitForDisabled({
esClient,
ruleId,
filter: testStart,
});
// Ensure only 2 actions are executed
- const resp2 = await waitForDocumentInIndex({
+ const resp2 = await alertingApi.helpers.waitForDocumentInIndex({
esClient,
indexName: ALERT_ACTION_INDEX,
ruleId,
@@ -683,21 +639,17 @@ export default function ({ getService }: FtrProviderContext) {
it(`shouldn't schedule actions when alert is muted`, async () => {
const testStart = new Date();
- await createIndex({ esClient, indexName: ALERT_ACTION_INDEX });
+ await alertingApi.helpers.waiting.createIndex({ esClient, indexName: ALERT_ACTION_INDEX });
- const createdConnector = await createIndexConnector({
- supertestWithoutAuth,
+ const createdConnector = await alertingApi.helpers.createIndexConnector({
roleAuthc: roleAdmin,
- internalReqHeader,
name: 'Index Connector: Alerting API test',
indexName: ALERT_ACTION_INDEX,
});
connectorId = createdConnector.id;
- const createdRule = await createEsQueryRule({
- supertestWithoutAuth,
+ const createdRule = await alertingApi.helpers.createEsQueryRule({
roleAuthc: roleAdmin,
- internalReqHeader,
enabled: false,
consumer: 'alerts',
name: 'always fire',
@@ -742,41 +694,39 @@ export default function ({ getService }: FtrProviderContext) {
});
ruleId = createdRule.id;
- await muteRule({
- supertest,
+ await alertingApi.helpers.muteRule({
+ roleAuthc: roleAdmin,
ruleId,
});
- await enableRule({
- supertest,
+ await alertingApi.helpers.enableRule({
+ roleAuthc: roleAdmin,
ruleId,
});
// Wait until alerts schedule actions twice to ensure actions had a chance to skip
// execution once before disabling the alert and waiting for tasks to finish
- await waitForNumRuleRuns({
- supertestWithoutAuth,
+ await alertingApi.helpers.waitForNumRuleRuns({
roleAuthc: roleAdmin,
- internalReqHeader,
numOfRuns: 2,
ruleId,
esClient,
testStart,
});
- await disableRule({
- supertest,
+ await alertingApi.helpers.disableRule({
+ roleAuthc: roleAdmin,
ruleId,
});
- await waitForDisabled({
+ await alertingApi.helpers.waiting.waitForDisabled({
esClient,
ruleId,
filter: testStart,
});
// Should not have executed any action
- const resp2 = await getDocumentsInIndex({
+ const resp2 = await alertingApi.helpers.waiting.getDocumentsInIndex({
esClient,
indexName: ALERT_ACTION_INDEX,
ruleId,
@@ -786,21 +736,17 @@ export default function ({ getService }: FtrProviderContext) {
it(`shouldn't schedule actions when alert instance is muted`, async () => {
const testStart = new Date();
- await createIndex({ esClient, indexName: ALERT_ACTION_INDEX });
+ await alertingApi.helpers.waiting.createIndex({ esClient, indexName: ALERT_ACTION_INDEX });
- const createdConnector = await createIndexConnector({
- supertestWithoutAuth,
+ const createdConnector = await alertingApi.helpers.createIndexConnector({
roleAuthc: roleAdmin,
- internalReqHeader,
name: 'Index Connector: Alerting API test',
indexName: ALERT_ACTION_INDEX,
});
connectorId = createdConnector.id;
- const createdRule = await createEsQueryRule({
- supertestWithoutAuth,
+ const createdRule = await alertingApi.helpers.createEsQueryRule({
roleAuthc: roleAdmin,
- internalReqHeader,
enabled: false,
consumer: 'alerts',
name: 'always fire',
@@ -845,42 +791,40 @@ export default function ({ getService }: FtrProviderContext) {
});
ruleId = createdRule.id;
- await muteAlert({
- supertest,
+ await alertingApi.helpers.muteAlert({
+ roleAuthc: roleAdmin,
ruleId,
alertId: 'query matched',
});
- await enableRule({
- supertest,
+ await alertingApi.helpers.enableRule({
+ roleAuthc: roleAdmin,
ruleId,
});
// Wait until alerts schedule actions twice to ensure actions had a chance to skip
// execution once before disabling the alert and waiting for tasks to finish
- await waitForNumRuleRuns({
- supertestWithoutAuth,
+ await alertingApi.helpers.waitForNumRuleRuns({
roleAuthc: roleAdmin,
- internalReqHeader,
numOfRuns: 2,
ruleId,
esClient,
testStart,
});
- await disableRule({
- supertest,
+ await alertingApi.helpers.disableRule({
+ roleAuthc: roleAdmin,
ruleId,
});
- await waitForDisabled({
+ await alertingApi.helpers.waiting.waitForDisabled({
esClient,
ruleId,
filter: testStart,
});
// Should not have executed any action
- const resp2 = await getDocumentsInIndex({
+ const resp2 = await alertingApi.helpers.waiting.getDocumentsInIndex({
esClient,
indexName: ALERT_ACTION_INDEX,
ruleId,
@@ -889,19 +833,15 @@ export default function ({ getService }: FtrProviderContext) {
});
it(`should unmute all instances when unmuting an alert`, async () => {
- const createdConnector = await createIndexConnector({
- supertestWithoutAuth,
+ const createdConnector = await alertingApi.helpers.createIndexConnector({
roleAuthc: roleAdmin,
- internalReqHeader,
name: 'Index Connector: Alerting API test',
indexName: ALERT_ACTION_INDEX,
});
connectorId = createdConnector.id;
- const createdRule = await createEsQueryRule({
- supertestWithoutAuth,
+ const createdRule = await alertingApi.helpers.createEsQueryRule({
roleAuthc: roleAdmin,
- internalReqHeader,
enabled: false,
consumer: 'alerts',
name: 'always fire',
@@ -946,29 +886,29 @@ export default function ({ getService }: FtrProviderContext) {
});
ruleId = createdRule.id;
- await muteAlert({
- supertest,
+ await alertingApi.helpers.muteAlert({
+ roleAuthc: roleAdmin,
ruleId,
alertId: 'query matched',
});
- await muteRule({
- supertest,
+ await alertingApi.helpers.muteRule({
+ roleAuthc: roleAdmin,
ruleId,
});
- await unmuteRule({
- supertest,
+ await alertingApi.helpers.unmuteRule({
+ roleAuthc: roleAdmin,
ruleId,
});
- await enableRule({
- supertest,
+ await alertingApi.helpers.enableRule({
+ roleAuthc: roleAdmin,
ruleId,
});
// Should have one document indexed by the action
- const resp = await waitForDocumentInIndex({
+ const resp = await alertingApi.helpers.waitForDocumentInIndex({
esClient,
indexName: ALERT_ACTION_INDEX,
ruleId,
diff --git a/x-pack/test_serverless/api_integration/test_suites/common/alerting/summary_actions.ts b/x-pack/test_serverless/api_integration/test_suites/common/alerting/summary_actions.ts
index 995a7ee197610..2726af585e28f 100644
--- a/x-pack/test_serverless/api_integration/test_suites/common/alerting/summary_actions.ts
+++ b/x-pack/test_serverless/api_integration/test_suites/common/alerting/summary_actions.ts
@@ -29,25 +29,15 @@ import {
} from '@kbn/rule-data-utils';
import { omit, padStart } from 'lodash';
import { FtrProviderContext } from '../../../ftr_provider_context';
-import { createIndexConnector, createEsQueryRule } from './helpers/alerting_api_helper';
-import {
- createIndex,
- getDocumentsInIndex,
- waitForAlertInIndex,
- waitForDocumentInIndex,
-} from './helpers/alerting_wait_for_helpers';
-import { InternalRequestHeader, RoleCredentials } from '../../../../shared/services';
+import { RoleCredentials } from '../../../../shared/services';
export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
const esClient = getService('es');
const esDeleteAllIndices = getService('esDeleteAllIndices');
-
- const svlCommonApi = getService('svlCommonApi');
const svlUserManager = getService('svlUserManager');
- const supertestWithoutAuth = getService('supertestWithoutAuth');
+ const alertingApi = getService('alertingApi');
let roleAdmin: RoleCredentials;
- let internalReqHeader: InternalRequestHeader;
describe('Summary actions', function () {
const RULE_TYPE_ID = '.es-query';
@@ -75,7 +65,6 @@ export default function ({ getService }: FtrProviderContext) {
before(async () => {
roleAdmin = await svlUserManager.createM2mApiKeyWithRoleScope('admin');
- internalReqHeader = svlCommonApi.getInternalRequestHeader();
});
afterEach(async () => {
@@ -98,19 +87,15 @@ export default function ({ getService }: FtrProviderContext) {
it('should schedule actions for summary of alerts per rule run', async () => {
const testStart = new Date();
- const createdConnector = await createIndexConnector({
- supertestWithoutAuth,
+ const createdConnector = await alertingApi.helpers.createIndexConnector({
roleAuthc: roleAdmin,
- internalReqHeader,
name: 'Index Connector: Alerting API test',
indexName: ALERT_ACTION_INDEX,
});
connectorId = createdConnector.id;
- const createdRule = await createEsQueryRule({
- supertestWithoutAuth,
+ const createdRule = await alertingApi.helpers.createEsQueryRule({
roleAuthc: roleAdmin,
- internalReqHeader,
consumer: 'alerts',
name: 'always fire',
ruleTypeId: RULE_TYPE_ID,
@@ -158,19 +143,27 @@ export default function ({ getService }: FtrProviderContext) {
});
ruleId = createdRule.id;
- const resp = await waitForDocumentInIndex({
+ const resp = await alertingApi.helpers.waitForDocumentInIndex({
esClient,
indexName: ALERT_ACTION_INDEX,
ruleId,
+ retryOptions: {
+ retryCount: 20,
+ retryDelay: 15_000,
+ },
});
expect(resp.hits.hits.length).to.be(1);
- const resp2 = await waitForAlertInIndex({
+ const resp2 = await alertingApi.helpers.waitForAlertInIndex({
esClient,
filter: testStart,
indexName: ALERT_INDEX,
ruleId,
num: 1,
+ retryOptions: {
+ retryCount: 20,
+ retryDelay: 15_000,
+ },
});
expect(resp2.hits.hits.length).to.be(1);
@@ -228,19 +221,15 @@ export default function ({ getService }: FtrProviderContext) {
it('should filter alerts by kql', async () => {
const testStart = new Date();
- const createdConnector = await createIndexConnector({
- supertestWithoutAuth,
+ const createdConnector = await alertingApi.helpers.createIndexConnector({
roleAuthc: roleAdmin,
- internalReqHeader,
name: 'Index Connector: Alerting API test',
indexName: ALERT_ACTION_INDEX,
});
connectorId = createdConnector.id;
- const createdRule = await createEsQueryRule({
- supertestWithoutAuth,
+ const createdRule = await alertingApi.helpers.createEsQueryRule({
roleAuthc: roleAdmin,
- internalReqHeader,
consumer: 'alerts',
name: 'always fire',
ruleTypeId: RULE_TYPE_ID,
@@ -288,19 +277,27 @@ export default function ({ getService }: FtrProviderContext) {
});
ruleId = createdRule.id;
- const resp = await waitForDocumentInIndex({
+ const resp = await alertingApi.helpers.waitForDocumentInIndex({
esClient,
indexName: ALERT_ACTION_INDEX,
ruleId,
+ retryOptions: {
+ retryCount: 20,
+ retryDelay: 15_000,
+ },
});
expect(resp.hits.hits.length).to.be(1);
- const resp2 = await waitForAlertInIndex({
+ const resp2 = await alertingApi.helpers.waitForAlertInIndex({
esClient,
filter: testStart,
indexName: ALERT_INDEX,
ruleId,
num: 1,
+ retryOptions: {
+ retryCount: 20,
+ retryDelay: 15_000,
+ },
});
expect(resp2.hits.hits.length).to.be(1);
@@ -365,21 +362,17 @@ export default function ({ getService }: FtrProviderContext) {
const start = `${hour}:${minutes}`;
const end = `${hour}:${minutes}`;
- await createIndex({ esClient, indexName: ALERT_ACTION_INDEX });
+ await alertingApi.helpers.waiting.createIndex({ esClient, indexName: ALERT_ACTION_INDEX });
- const createdConnector = await createIndexConnector({
- supertestWithoutAuth,
+ const createdConnector = await alertingApi.helpers.createIndexConnector({
roleAuthc: roleAdmin,
- internalReqHeader,
name: 'Index Connector: Alerting API test',
indexName: ALERT_ACTION_INDEX,
});
connectorId = createdConnector.id;
- const createdRule = await createEsQueryRule({
- supertestWithoutAuth,
+ const createdRule = await alertingApi.helpers.createEsQueryRule({
roleAuthc: roleAdmin,
- internalReqHeader,
consumer: 'alerts',
name: 'always fire',
ruleTypeId: RULE_TYPE_ID,
@@ -433,7 +426,7 @@ export default function ({ getService }: FtrProviderContext) {
ruleId = createdRule.id;
// Should not have executed any action
- const resp = await getDocumentsInIndex({
+ const resp = await alertingApi.helpers.waiting.getDocumentsInIndex({
esClient,
indexName: ALERT_ACTION_INDEX,
ruleId,
@@ -443,19 +436,15 @@ export default function ({ getService }: FtrProviderContext) {
it('should schedule actions for summary of alerts on a custom interval', async () => {
const testStart = new Date();
- const createdConnector = await createIndexConnector({
- supertestWithoutAuth,
+ const createdConnector = await alertingApi.helpers.createIndexConnector({
roleAuthc: roleAdmin,
- internalReqHeader,
name: 'Index Connector: Alerting API test',
indexName: ALERT_ACTION_INDEX,
});
connectorId = createdConnector.id;
- const createdRule = await createEsQueryRule({
- supertestWithoutAuth,
+ const createdRule = await alertingApi.helpers.createEsQueryRule({
roleAuthc: roleAdmin,
- internalReqHeader,
consumer: 'alerts',
name: 'always fire',
ruleTypeId: RULE_TYPE_ID,
@@ -501,20 +490,28 @@ export default function ({ getService }: FtrProviderContext) {
});
ruleId = createdRule.id;
- const resp = await waitForDocumentInIndex({
+ const resp = await alertingApi.helpers.waitForDocumentInIndex({
esClient,
indexName: ALERT_ACTION_INDEX,
ruleId,
num: 2,
sort: 'asc',
+ retryOptions: {
+ retryCount: 20,
+ retryDelay: 10_000,
+ },
});
- const resp2 = await waitForAlertInIndex({
+ const resp2 = await alertingApi.helpers.waitForAlertInIndex({
esClient,
filter: testStart,
indexName: ALERT_INDEX,
ruleId,
num: 1,
+ retryOptions: {
+ retryCount: 20,
+ retryDelay: 15_000,
+ },
});
expect(resp2.hits.hits.length).to.be(1);
diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/es_query_rule/es_query_rule.ts b/x-pack/test_serverless/api_integration/test_suites/observability/es_query_rule/es_query_rule.ts
index 39edd9ba01eb9..8d627413ecbc0 100644
--- a/x-pack/test_serverless/api_integration/test_suites/observability/es_query_rule/es_query_rule.ts
+++ b/x-pack/test_serverless/api_integration/test_suites/observability/es_query_rule/es_query_rule.ts
@@ -12,7 +12,6 @@
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../ftr_provider_context';
-import { createEsQueryRule } from '../../common/alerting/helpers/alerting_api_helper';
import { InternalRequestHeader, RoleCredentials } from '../../../../shared/services';
export default function ({ getService }: FtrProviderContext) {
@@ -22,7 +21,6 @@ export default function ({ getService }: FtrProviderContext) {
const alertingApi = getService('alertingApi');
const svlCommonApi = getService('svlCommonApi');
const svlUserManager = getService('svlUserManager');
- const supertestWithoutAuth = getService('supertestWithoutAuth');
let roleAuthc: RoleCredentials;
let internalReqHeader: InternalRequestHeader;
@@ -58,10 +56,8 @@ export default function ({ getService }: FtrProviderContext) {
indexName: ALERT_ACTION_INDEX,
});
- const createdRule = await createEsQueryRule({
- supertestWithoutAuth,
+ const createdRule = await alertingApi.helpers.createEsQueryRule({
roleAuthc,
- internalReqHeader,
consumer: 'observability',
name: 'always fire',
ruleTypeId: RULE_TYPE_ID,
diff --git a/x-pack/test_serverless/functional/services/index.ts b/x-pack/test_serverless/functional/services/index.ts
index c63a16b4402f1..770cdbb88c97a 100644
--- a/x-pack/test_serverless/functional/services/index.ts
+++ b/x-pack/test_serverless/functional/services/index.ts
@@ -7,7 +7,6 @@
import { services as deploymentAgnosticFunctionalServices } from './deployment_agnostic_services';
import { services as svlSharedServices } from '../../shared/services';
-
import { SvlCommonNavigationServiceProvider } from './svl_common_navigation';
import { SvlObltNavigationServiceProvider } from './svl_oblt_navigation';
import { SvlSearchNavigationServiceProvider } from './svl_search_navigation';
@@ -17,6 +16,7 @@ import { SvlCasesServiceProvider } from '../../api_integration/services/svl_case
import { MachineLearningProvider } from './ml';
import { LogsSynthtraceProvider } from './log';
import { UISettingsServiceProvider } from './ui_settings';
+import { services as SvlApiIntegrationSvcs } from '../../api_integration/services';
export const services = {
// deployment agnostic FTR services
@@ -34,4 +34,5 @@ export const services = {
uiSettings: UISettingsServiceProvider,
// log services
svlLogsSynthtraceClient: LogsSynthtraceProvider,
+ alertingApi: SvlApiIntegrationSvcs.alertingApi,
};
diff --git a/x-pack/test_serverless/functional/test_suites/observability/rules/rules_list.ts b/x-pack/test_serverless/functional/test_suites/observability/rules/rules_list.ts
index c2b878511a4dd..6a0d515afd232 100644
--- a/x-pack/test_serverless/functional/test_suites/observability/rules/rules_list.ts
+++ b/x-pack/test_serverless/functional/test_suites/observability/rules/rules_list.ts
@@ -7,17 +7,7 @@
import { expect } from 'expect';
import { FtrProviderContext } from '../../../ftr_provider_context';
-import {
- createAnomalyRule as createRule,
- disableRule,
- enableRule,
- runRule,
- createIndexConnector,
- snoozeRule,
- createLatencyThresholdRule,
- createEsQueryRule,
-} from '../../../../api_integration/test_suites/common/alerting/helpers/alerting_api_helper';
-import { InternalRequestHeader, RoleCredentials } from '../../../../shared/services';
+import { RoleCredentials } from '../../../../shared/services';
export default ({ getPageObject, getService }: FtrProviderContext) => {
const svlCommonPage = getPageObject('svlCommonPage');
@@ -31,11 +21,9 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
const retry = getService('retry');
const toasts = getService('toasts');
const log = getService('log');
- const svlCommonApi = getService('svlCommonApi');
const svlUserManager = getService('svlUserManager');
- const supertestWithoutAuth = getService('supertestWithoutAuth');
+ const alertingApi = getService('alertingApi');
let roleAuthc: RoleCredentials;
- let internalReqHeader: InternalRequestHeader;
async function refreshRulesList() {
const existsClearFilter = await testSubjects.exists('rules-list-clear-filter');
@@ -57,10 +45,13 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
numAttempts: number;
}) {
for (let i = 0; i < numAttempts; i++) {
- await runRule({ supertestWithoutAuth, roleAuthc, internalReqHeader, ruleId });
+ await alertingApi.helpers.runRule({ roleAuthc, ruleId });
await new Promise((resolve) => setTimeout(resolve, intervalMilliseconds));
- await disableRule({ supertest, ruleId });
+ await alertingApi.helpers.disableRule({
+ roleAuthc,
+ ruleId,
+ });
await new Promise((resolve) => setTimeout(resolve, intervalMilliseconds));
await refreshRulesList();
@@ -68,7 +59,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
const rulesStatuses = result.map((item: { status: string }) => item.status);
if (rulesStatuses.includes('Failed')) return;
- await enableRule({ supertest, ruleId });
+ await alertingApi.helpers.enableRule({ roleAuthc, ruleId });
}
}
@@ -84,7 +75,6 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
before(async () => {
roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin');
- internalReqHeader = svlCommonApi.getInternalRequestHeader();
await svlCommonPage.loginWithPrivilegedRole();
await svlObltNavigation.navigateToLandingPage();
await svlCommonNavigation.sidenav.clickLink({ text: 'Alerts' });
@@ -107,10 +97,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
});
it('should create an ES Query Rule and display it when consumer is observability', async () => {
- const esQuery = await createEsQueryRule({
- supertestWithoutAuth,
+ const esQuery = await alertingApi.helpers.createEsQueryRule({
roleAuthc,
- internalReqHeader,
name: 'ES Query',
consumer: 'observability',
ruleTypeId: '.es-query',
@@ -134,10 +122,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
});
it('should create an ES Query rule but not display it when consumer is stackAlerts', async () => {
- const esQuery = await createEsQueryRule({
- supertestWithoutAuth,
+ const esQuery = await alertingApi.helpers.createEsQueryRule({
roleAuthc,
- internalReqHeader,
name: 'ES Query',
consumer: 'stackAlerts',
ruleTypeId: '.es-query',
@@ -159,7 +145,10 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
});
it('should create and display an APM latency rule', async () => {
- const apmLatency = await createLatencyThresholdRule({ supertest, name: 'Apm latency' });
+ const apmLatency = await alertingApi.helpers.createLatencyThresholdRule({
+ roleAuthc,
+ name: 'Apm latency',
+ });
ruleIdList = [apmLatency.id];
await refreshRulesList();
@@ -169,16 +158,16 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
});
it('should display rules in alphabetical order', async () => {
- const rule1 = await createRule({
- supertest,
+ const rule1 = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
name: 'b',
});
- const rule2 = await createRule({
- supertest,
+ const rule2 = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
name: 'c',
});
- const rule3 = await createRule({
- supertest,
+ const rule3 = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
name: 'a',
});
@@ -194,8 +183,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
});
it('should search for rule', async () => {
- const rule1 = await createRule({
- supertest,
+ const rule1 = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
});
ruleIdList = [rule1.id];
@@ -215,13 +204,13 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
});
it('should update rule list on the search clear button click', async () => {
- const rule1 = await createRule({
- supertest,
+ const rule1 = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
name: 'a',
});
- const rule2 = await createRule({
- supertest,
+ const rule2 = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
name: 'b',
tags: [],
});
@@ -266,8 +255,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
});
it('should search for tags', async () => {
- const rule1 = await createRule({
- supertest,
+ const rule1 = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
name: 'a',
tags: ['tag', 'tagtag', 'taggity tag'],
});
@@ -289,8 +278,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
});
it('should display an empty list when search did not return any rules', async () => {
- const rule1 = await createRule({
- supertest,
+ const rule1 = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
});
ruleIdList = [rule1.id];
@@ -301,8 +290,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
});
it('should disable single rule', async () => {
- const rule1 = await createRule({
- supertest,
+ const rule1 = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
});
ruleIdList = [rule1.id];
@@ -329,14 +318,17 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
});
it('should re-enable single rule', async () => {
- const rule1 = await createRule({
- supertest,
+ const rule1 = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
name: 'a',
});
ruleIdList = [rule1.id];
- await disableRule({ supertest, ruleId: rule1.id });
+ await alertingApi.helpers.disableRule({
+ roleAuthc,
+ ruleId: rule1.id,
+ });
await refreshRulesList();
await svlTriggersActionsUI.searchRules(rule1.name);
@@ -360,13 +352,13 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
});
it('should delete single rule', async () => {
- const rule1 = await createRule({
- supertest,
+ const rule1 = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
name: 'a',
});
- const rule2 = await createRule({
- supertest,
+ const rule2 = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
name: 'b',
});
@@ -392,8 +384,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
});
it('should disable all selection', async () => {
- const createdRule1 = await createRule({
- supertest,
+ const createdRule1 = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
});
ruleIdList = [createdRule1.id];
@@ -422,13 +414,16 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
});
it('should enable all selection', async () => {
- const rule1 = await createRule({
- supertest,
+ const rule1 = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
});
ruleIdList = [rule1.id];
- await disableRule({ supertest, ruleId: rule1.id });
+ await alertingApi.helpers.disableRule({
+ roleAuthc,
+ ruleId: rule1.id,
+ });
await refreshRulesList();
await svlTriggersActionsUI.searchRules(rule1.name);
@@ -445,8 +440,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
});
it('should render percentile column and cells correctly', async () => {
- const rule1 = await createRule({
- supertest,
+ const rule1 = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
});
ruleIdList = [rule1.id];
@@ -481,8 +476,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
});
it('should delete all selection', async () => {
- const createdRule1 = await createRule({
- supertest,
+ const createdRule1 = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
});
ruleIdList = [createdRule1.id];
@@ -508,12 +503,12 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
});
it.skip('should filter rules by the status', async () => {
- const rule1 = await createRule({
- supertest,
+ const rule1 = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
});
- const failedRule = await createRule({
- supertest,
+ const failedRule = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
});
ruleIdList = [rule1.id, failedRule.id];
@@ -558,8 +553,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
});
it.skip('should display total rules by status and error banner only when exists rules with status error', async () => {
- const rule1 = await createRule({
- supertest,
+ const rule1 = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
});
await refreshRulesList();
@@ -582,8 +577,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
);
expect(alertsErrorBannerWhenNoErrors).toHaveLength(0);
- const failedRule = await createRule({
- supertest,
+ const failedRule = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
});
ruleIdList = [rule1.id, failedRule.id];
@@ -617,8 +612,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
});
it.skip('Expand error in rules table when there is rule with an error associated', async () => {
- const rule1 = await createRule({
- supertest,
+ const rule1 = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
name: 'a',
});
@@ -639,8 +634,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
let expandRulesErrorLink = await find.allByCssSelector('[data-test-subj="expandRulesError"]');
expect(expandRulesErrorLink).toHaveLength(0);
- const failedRule = await createRule({
- supertest,
+ const failedRule = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
});
ruleIdList = [rule1.id, failedRule.id];
@@ -666,12 +661,12 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
});
it('should filter rules by the rule type', async () => {
- const rule1 = await createRule({
- supertest,
+ const rule1 = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
});
- const rule2 = await createLatencyThresholdRule({
- supertest,
+ const rule2 = await alertingApi.helpers.createLatencyThresholdRule({
+ roleAuthc,
});
ruleIdList = [rule1.id, rule2.id];
@@ -730,36 +725,36 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
};
// Enabled alert
- const rule1 = await createRule({
- supertest,
+ const rule1 = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
});
- const disabledRule = await createRule({
- supertest,
+ const disabledRule = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
});
- await disableRule({
- supertest,
+ await alertingApi.helpers.disableRule({
+ roleAuthc,
ruleId: disabledRule.id,
});
- const snoozedRule = await createRule({
- supertest,
+ const snoozedRule = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
});
- await snoozeRule({
- supertest,
+ await alertingApi.helpers.snoozeRule({
+ roleAuthc,
ruleId: snoozedRule.id,
});
- const snoozedAndDisabledRule = await createRule({
- supertest,
+ const snoozedAndDisabledRule = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
});
- await snoozeRule({
- supertest,
+ await alertingApi.helpers.snoozeRule({
+ roleAuthc,
ruleId: snoozedAndDisabledRule.id,
});
- await disableRule({
- supertest,
+ await alertingApi.helpers.disableRule({
+ roleAuthc,
ruleId: snoozedAndDisabledRule.id,
});
@@ -801,28 +796,28 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
});
it('should filter rules by the tag', async () => {
- const rule1 = await createRule({
- supertest,
+ const rule1 = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
tags: ['a'],
});
- const rule2 = await createRule({
- supertest,
+ const rule2 = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
tags: ['b'],
});
- const rule3 = await createRule({
- supertest,
+ const rule3 = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
tags: ['a', 'b'],
});
- const rule4 = await createRule({
- supertest,
+ const rule4 = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
tags: ['b', 'c'],
});
- const rule5 = await createRule({
- supertest,
+ const rule5 = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
tags: ['c'],
});
@@ -864,17 +859,15 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
});
it('should not prevent rules with action execution capabilities from being edited', async () => {
- const action = await createIndexConnector({
- supertestWithoutAuth,
+ const action = await alertingApi.helpers.createIndexConnector({
roleAuthc,
- internalReqHeader,
name: 'Index Connector: Alerting API test',
indexName: '.alerts-observability.apm.alerts-default',
});
expect(action).not.toBe(undefined);
- const rule1 = await createRule({
- supertest,
+ const rule1 = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
actions: [
{
group: 'threshold_met',
@@ -902,8 +895,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
});
it('should allow rules to be snoozed using the right side dropdown', async () => {
- const rule1 = await createRule({
- supertest,
+ const rule1 = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
});
ruleIdList = [rule1.id];
@@ -922,8 +915,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
});
it('should allow rules to be snoozed indefinitely using the right side dropdown', async () => {
- const rule1 = await createRule({
- supertest,
+ const rule1 = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
});
ruleIdList = [rule1.id];
@@ -942,14 +935,14 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
});
it('should allow snoozed rules to be unsnoozed using the right side dropdown', async () => {
- const rule1 = await createRule({
- supertest,
+ const rule1 = await alertingApi.helpers.createAnomalyRule({
+ roleAuthc,
});
ruleIdList = [rule1.id];
- await snoozeRule({
- supertest,
+ await alertingApi.helpers.snoozeRule({
+ roleAuthc,
ruleId: rule1.id,
});
diff --git a/x-pack/test_serverless/functional/test_suites/search/rules/rule_details.ts b/x-pack/test_serverless/functional/test_suites/search/rules/rule_details.ts
index 40d57101693bc..00363f21299de 100644
--- a/x-pack/test_serverless/functional/test_suites/search/rules/rule_details.ts
+++ b/x-pack/test_serverless/functional/test_suites/search/rules/rule_details.ts
@@ -7,13 +7,8 @@
import { expect } from 'expect';
import { v4 as uuidv4 } from 'uuid';
-import { InternalRequestHeader, RoleCredentials } from '../../../../shared/services';
+import { RoleCredentials } from '../../../../shared/services';
import { FtrProviderContext } from '../../../ftr_provider_context';
-import {
- createEsQueryRule as createRule,
- createSlackConnector,
- createIndexConnector,
-} from '../../../../api_integration/test_suites/common/alerting/helpers/alerting_api_helper';
export enum RuleNotifyWhen {
CHANGE = 'onActionGroupChange',
@@ -34,6 +29,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
const toasts = getService('toasts');
const comboBox = getService('comboBox');
const config = getService('config');
+ const alertingApi = getService('alertingApi');
const openFirstRule = async (ruleName: string) => {
await svlTriggersActionsUI.searchRules(ruleName);
@@ -66,15 +62,11 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
let ruleIdList: string[];
let connectorIdList: string[];
- const svlCommonApi = getService('svlCommonApi');
const svlUserManager = getService('svlUserManager');
- const supertestWithoutAuth = getService('supertestWithoutAuth');
let roleAuthc: RoleCredentials;
- let internalReqHeader: InternalRequestHeader;
before(async () => {
roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin');
- internalReqHeader = svlCommonApi.getInternalRequestHeader();
await svlCommonPage.loginAsViewer();
});
@@ -88,10 +80,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
const RULE_TYPE_ID = '.es-query';
before(async () => {
- const rule = await createRule({
- supertestWithoutAuth,
+ const rule = await alertingApi.helpers.createEsQueryRule({
roleAuthc,
- internalReqHeader,
consumer: 'alerts',
name: ruleName,
ruleTypeId: RULE_TYPE_ID,
@@ -261,10 +251,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
const RULE_TYPE_ID = '.es-query';
before(async () => {
- const rule = await createRule({
- supertestWithoutAuth,
+ const rule = await alertingApi.helpers.createEsQueryRule({
roleAuthc,
- internalReqHeader,
consumer: 'alerts',
name: ruleName,
ruleTypeId: RULE_TYPE_ID,
@@ -369,26 +357,20 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
it('should show and update deleted connectors when there are existing connectors of the same type', async () => {
const testRunUuid = uuidv4();
- const connector1 = await createSlackConnector({
- supertestWithoutAuth,
+ const connector1 = await alertingApi.helpers.createSlackConnector({
roleAuthc,
- internalReqHeader,
name: `slack-${testRunUuid}-${0}`,
});
- const connector2 = await createSlackConnector({
- supertestWithoutAuth,
+ const connector2 = await alertingApi.helpers.createSlackConnector({
roleAuthc,
- internalReqHeader,
name: `slack-${testRunUuid}-${1}`,
});
connectorIdList = [connector2.id];
- const rule = await createRule({
- supertestWithoutAuth,
+ const rule = await alertingApi.helpers.createEsQueryRule({
roleAuthc,
- internalReqHeader,
consumer: 'alerts',
name: testRunUuid,
ruleTypeId: RULE_TYPE_ID,
@@ -450,18 +432,14 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
it('should show and update deleted connectors when there are no existing connectors of the same type', async () => {
const testRunUuid = uuidv4();
- const connector = await createIndexConnector({
- supertestWithoutAuth,
+ const connector = await alertingApi.helpers.createIndexConnector({
roleAuthc,
- internalReqHeader,
name: `index-${testRunUuid}-${2}`,
indexName: ALERT_ACTION_INDEX,
});
- const rule = await createRule({
- supertestWithoutAuth,
+ const rule = await alertingApi.helpers.createEsQueryRule({
roleAuthc,
- internalReqHeader,
consumer: 'alerts',
name: testRunUuid,
ruleTypeId: RULE_TYPE_ID,
@@ -576,26 +554,20 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
const testRunUuid = uuidv4();
const RULE_TYPE_ID = '.es-query';
- const connector1 = await createSlackConnector({
- supertestWithoutAuth,
+ const connector1 = await alertingApi.helpers.createSlackConnector({
roleAuthc,
- internalReqHeader,
name: `slack-${testRunUuid}-${0}`,
});
- const connector2 = await createSlackConnector({
- supertestWithoutAuth,
+ const connector2 = await alertingApi.helpers.createSlackConnector({
roleAuthc,
- internalReqHeader,
name: `slack-${testRunUuid}-${1}`,
});
connectorIdList = [connector1.id, connector2.id];
- const rule = await createRule({
- supertestWithoutAuth,
+ const rule = await alertingApi.helpers.createEsQueryRule({
roleAuthc,
- internalReqHeader,
consumer: 'alerts',
name: `test-rule-${testRunUuid}`,
ruleTypeId: RULE_TYPE_ID,
@@ -670,10 +642,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
});
it('renders a disabled rule details view in app button', async () => {
- const rule = await createRule({
- supertestWithoutAuth,
+ const rule = await alertingApi.helpers.createEsQueryRule({
roleAuthc,
- internalReqHeader,
consumer: 'alerts',
name: ruleName,
ruleTypeId: RULE_TYPE_ID,
diff --git a/x-pack/test_serverless/shared/services/alerting_api.ts b/x-pack/test_serverless/shared/services/alerting_api.ts
new file mode 100644
index 0000000000000..afed22fbe2c9a
--- /dev/null
+++ b/x-pack/test_serverless/shared/services/alerting_api.ts
@@ -0,0 +1,1032 @@
+/*
+ * 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 moment from 'moment';
+import type {
+ AggregationsAggregate,
+ SearchResponse,
+} from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
+import type { Client } from '@elastic/elasticsearch';
+import { MetricThresholdParams } from '@kbn/infra-plugin/common/alerting/metrics';
+import { ThresholdParams } from '@kbn/observability-plugin/common/custom_threshold_rule/types';
+import { v4 as uuidv4 } from 'uuid';
+import type { TryWithRetriesOptions } from '@kbn/ftr-common-functional-services';
+import { RoleCredentials } from '.';
+import type { SloBurnRateRuleParams } from '../../api_integration/services/slo_api';
+import { FtrProviderContext } from '../../functional/ftr_provider_context';
+
+interface CreateEsQueryRuleParams {
+ size: number;
+ thresholdComparator: string;
+ threshold: number[];
+ timeWindowSize?: number;
+ timeWindowUnit?: string;
+ esQuery?: string;
+ timeField?: string;
+ searchConfiguration?: unknown;
+ indexName?: string;
+ excludeHitsFromPreviousRun?: boolean;
+ aggType?: string;
+ aggField?: string;
+ groupBy?: string;
+ termField?: string;
+ termSize?: number;
+ index?: string[];
+}
+const RETRY_COUNT = 10;
+const RETRY_DELAY = 1000;
+
+export function AlertingApiProvider({ getService }: FtrProviderContext) {
+ const retry = getService('retry');
+ const es = getService('es');
+ const requestTimeout = 30 * 1000;
+ const retryTimeout = 120 * 1000;
+ const logger = getService('log');
+ const svlCommonApi = getService('svlCommonApi');
+ const supertestWithoutAuth = getService('supertestWithoutAuth');
+
+ const generateUniqueKey = () => uuidv4().replace(/-/g, '');
+
+ const helpers = {
+ async waitForAlertInIndex({
+ esClient,
+ filter,
+ indexName,
+ ruleId,
+ num = 1,
+ retryOptions = { retryCount: RETRY_COUNT, retryDelay: RETRY_DELAY },
+ }: {
+ esClient: Client;
+ filter: Date;
+ indexName: string;
+ ruleId: string;
+ num: number;
+ retryOptions?: TryWithRetriesOptions;
+ }): Promise>> {
+ return await retry.tryWithRetries(
+ `Alerting API - waitForAlertInIndex, retryOptions: ${JSON.stringify(retryOptions)}`,
+ async () => {
+ const response = await esClient.search({
+ index: indexName,
+ body: {
+ query: {
+ bool: {
+ must: [
+ {
+ term: {
+ 'kibana.alert.rule.uuid': ruleId,
+ },
+ },
+ {
+ range: {
+ '@timestamp': {
+ gte: filter.getTime().toString(),
+ },
+ },
+ },
+ ],
+ },
+ },
+ },
+ });
+ if (response.hits.hits.length < num)
+ throw new Error(`Only found ${response.hits.hits.length} / ${num} documents`);
+
+ return response;
+ },
+ retryOptions
+ );
+ },
+
+ async waitForDocumentInIndex({
+ esClient,
+ indexName,
+ ruleId,
+ num = 1,
+ sort = 'desc',
+ retryOptions = { retryCount: RETRY_COUNT, retryDelay: RETRY_DELAY },
+ }: {
+ esClient: Client;
+ indexName: string;
+ ruleId: string;
+ num?: number;
+ sort?: 'asc' | 'desc';
+ retryOptions?: TryWithRetriesOptions;
+ }): Promise {
+ return await retry.tryWithRetries(
+ `Alerting API - waitForDocumentInIndex, retryOptions: ${JSON.stringify(retryOptions)}`,
+ async () => {
+ const response = await esClient.search({
+ index: indexName,
+ sort: `date:${sort}`,
+ body: {
+ query: {
+ bool: {
+ must: [
+ {
+ term: {
+ 'ruleId.keyword': ruleId,
+ },
+ },
+ ],
+ },
+ },
+ },
+ });
+ if (response.hits.hits.length < num) {
+ throw new Error(`Only found ${response.hits.hits.length} / ${num} documents`);
+ }
+ return response;
+ },
+ retryOptions
+ );
+ },
+
+ async createIndexConnector({
+ roleAuthc,
+ name,
+ indexName,
+ }: {
+ roleAuthc: RoleCredentials;
+ name: string;
+ indexName: string;
+ }) {
+ const { body } = await supertestWithoutAuth
+ .post(`/api/actions/connector`)
+ .set(svlCommonApi.getInternalRequestHeader())
+ .set(roleAuthc.apiKeyHeader)
+ .send({
+ name,
+ config: {
+ index: indexName,
+ refresh: true,
+ },
+ connector_type_id: '.index',
+ })
+ .expect(200);
+ return body;
+ },
+
+ async createSlackConnector({ roleAuthc, name }: { roleAuthc: RoleCredentials; name: string }) {
+ const { body } = await supertestWithoutAuth
+ .post(`/api/actions/connector`)
+ .set(svlCommonApi.getInternalRequestHeader())
+ .set(roleAuthc.apiKeyHeader)
+ .send({
+ name,
+ config: {},
+ secrets: {
+ webhookUrl: 'http://test',
+ },
+ connector_type_id: '.slack',
+ })
+ .expect(200);
+ return body;
+ },
+
+ async createEsQueryRule({
+ roleAuthc,
+ name,
+ ruleTypeId,
+ params,
+ actions = [],
+ tags = [],
+ schedule,
+ consumer,
+ notifyWhen,
+ enabled = true,
+ }: {
+ roleAuthc: RoleCredentials;
+ ruleTypeId: string;
+ name: string;
+ params: CreateEsQueryRuleParams;
+ consumer: string;
+ actions?: any[];
+ tags?: any[];
+ schedule?: { interval: string };
+ notifyWhen?: string;
+ enabled?: boolean;
+ }) {
+ const { body } = await supertestWithoutAuth
+ .post(`/api/alerting/rule`)
+ .set(svlCommonApi.getInternalRequestHeader())
+ .set(roleAuthc.apiKeyHeader)
+ .send({
+ enabled,
+ params,
+ consumer,
+ schedule: schedule || {
+ interval: '1h',
+ },
+ tags,
+ name,
+ rule_type_id: ruleTypeId,
+ actions,
+ ...(notifyWhen ? { notify_when: notifyWhen, throttle: '5m' } : {}),
+ })
+ .expect(200);
+ return body;
+ },
+
+ async createAnomalyRule({
+ roleAuthc,
+ name = generateUniqueKey(),
+ actions = [],
+ tags = ['foo', 'bar'],
+ schedule,
+ consumer = 'alerts',
+ notifyWhen,
+ enabled = true,
+ ruleTypeId = 'apm.anomaly',
+ params,
+ }: {
+ roleAuthc: RoleCredentials;
+ name?: string;
+ consumer?: string;
+ actions?: any[];
+ tags?: any[];
+ schedule?: { interval: string };
+ notifyWhen?: string;
+ enabled?: boolean;
+ ruleTypeId?: string;
+ params?: any;
+ }) {
+ const { body } = await supertestWithoutAuth
+ .post(`/api/alerting/rule`)
+ .set(svlCommonApi.getInternalRequestHeader())
+ .set(roleAuthc.apiKeyHeader)
+ .send({
+ enabled,
+ params: params || {
+ anomalySeverityType: 'critical',
+ anomalyDetectorTypes: ['txLatency'],
+ environment: 'ENVIRONMENT_ALL',
+ windowSize: 30,
+ windowUnit: 'm',
+ },
+ consumer,
+ schedule: schedule || {
+ interval: '1m',
+ },
+ tags,
+ name,
+ rule_type_id: ruleTypeId,
+ actions,
+ ...(notifyWhen ? { notify_when: notifyWhen, throttle: '5m' } : {}),
+ })
+ .expect(200);
+ return body;
+ },
+
+ async createLatencyThresholdRule({
+ roleAuthc,
+ name = generateUniqueKey(),
+ actions = [],
+ tags = ['foo', 'bar'],
+ schedule,
+ consumer = 'apm',
+ notifyWhen,
+ enabled = true,
+ ruleTypeId = 'apm.transaction_duration',
+ params,
+ }: {
+ roleAuthc: RoleCredentials;
+ name?: string;
+ consumer?: string;
+ actions?: any[];
+ tags?: any[];
+ schedule?: { interval: string };
+ notifyWhen?: string;
+ enabled?: boolean;
+ ruleTypeId?: string;
+ params?: any;
+ }) {
+ const { body } = await supertestWithoutAuth
+ .post(`/api/alerting/rule`)
+ .set(svlCommonApi.getInternalRequestHeader())
+ .set(roleAuthc.apiKeyHeader)
+ .send({
+ enabled,
+ params: params || {
+ aggregationType: 'avg',
+ environment: 'ENVIRONMENT_ALL',
+ threshold: 1500,
+ windowSize: 5,
+ windowUnit: 'm',
+ },
+ consumer,
+ schedule: schedule || {
+ interval: '1m',
+ },
+ tags,
+ name,
+ rule_type_id: ruleTypeId,
+ actions,
+ ...(notifyWhen ? { notify_when: notifyWhen, throttle: '5m' } : {}),
+ });
+ return body;
+ },
+
+ async createInventoryRule({
+ roleAuthc,
+ name = generateUniqueKey(),
+ actions = [],
+ tags = ['foo', 'bar'],
+ schedule,
+ consumer = 'alerts',
+ notifyWhen,
+ enabled = true,
+ ruleTypeId = 'metrics.alert.inventory.threshold',
+ params,
+ }: {
+ roleAuthc: RoleCredentials;
+ name?: string;
+ consumer?: string;
+ actions?: any[];
+ tags?: any[];
+ schedule?: { interval: string };
+ notifyWhen?: string;
+ enabled?: boolean;
+ ruleTypeId?: string;
+ params?: any;
+ }) {
+ const { body } = await supertestWithoutAuth
+ .post(`/api/alerting/rule`)
+ .set(svlCommonApi.getInternalRequestHeader())
+ .set(roleAuthc.apiKeyHeader)
+ .send({
+ enabled,
+ params: params || {
+ nodeType: 'host',
+ criteria: [
+ {
+ metric: 'cpu',
+ comparator: '>',
+ threshold: [5],
+ timeSize: 1,
+ timeUnit: 'm',
+ customMetric: {
+ type: 'custom',
+ id: 'alert-custom-metric',
+ field: '',
+ aggregation: 'avg',
+ },
+ },
+ ],
+ sourceId: 'default',
+ },
+ consumer,
+ schedule: schedule || {
+ interval: '1m',
+ },
+ tags,
+ name,
+ rule_type_id: ruleTypeId,
+ actions,
+ ...(notifyWhen ? { notify_when: notifyWhen, throttle: '5m' } : {}),
+ })
+ .expect(200);
+ return body;
+ },
+
+ async disableRule({ roleAuthc, ruleId }: { roleAuthc: RoleCredentials; ruleId: string }) {
+ const { body } = await supertestWithoutAuth
+ .post(`/api/alerting/rule/${ruleId}/_disable`)
+ .set(svlCommonApi.getInternalRequestHeader())
+ .set(roleAuthc.apiKeyHeader)
+ .expect(204);
+ return body;
+ },
+
+ async updateEsQueryRule({
+ roleAuthc,
+ ruleId,
+ updates,
+ }: {
+ roleAuthc: RoleCredentials;
+ ruleId: string;
+ updates: any;
+ }) {
+ const { body: r } = await supertestWithoutAuth
+ .get(`/api/alerting/rule/${ruleId}`)
+ .set(svlCommonApi.getInternalRequestHeader())
+ .set(roleAuthc.apiKeyHeader)
+ .expect(200);
+ const body = await supertestWithoutAuth
+ .put(`/api/alerting/rule/${ruleId}`)
+ .set(svlCommonApi.getInternalRequestHeader())
+ .set(roleAuthc.apiKeyHeader)
+ .send({
+ ...{
+ name: r.name,
+ schedule: r.schedule,
+ throttle: r.throttle,
+ tags: r.tags,
+ params: r.params,
+ notify_when: r.notifyWhen,
+ actions: r.actions.map((action: any) => ({
+ group: action.group,
+ params: action.params,
+ id: action.id,
+ frequency: action.frequency,
+ })),
+ },
+ ...updates,
+ })
+ .expect(200);
+ return body;
+ },
+
+ async runRule({ roleAuthc, ruleId }: { roleAuthc: RoleCredentials; ruleId: string }) {
+ const response = await supertestWithoutAuth
+ .post(`/internal/alerting/rule/${ruleId}/_run_soon`)
+ .set(svlCommonApi.getInternalRequestHeader())
+ .set(roleAuthc.apiKeyHeader)
+ .expect(204);
+ return response;
+ },
+
+ async waitForNumRuleRuns({
+ roleAuthc,
+ numOfRuns,
+ ruleId,
+ esClient,
+ testStart,
+ retryOptions = { retryCount: RETRY_COUNT, retryDelay: RETRY_DELAY },
+ }: {
+ roleAuthc: RoleCredentials;
+ numOfRuns: number;
+ ruleId: string;
+ esClient: Client;
+ testStart: Date;
+ retryOptions?: TryWithRetriesOptions;
+ }) {
+ for (let i = 0; i < numOfRuns; i++) {
+ await retry.tryWithRetries(
+ `Alerting API - waitForNumRuleRuns, retryOptions: ${JSON.stringify(retryOptions)}`,
+ async () => {
+ await this.runRule({ roleAuthc, ruleId });
+ await this.waiting.waitForExecutionEventLog({
+ esClient,
+ filter: testStart,
+ ruleId,
+ num: i + 1,
+ });
+ await this.waiting.waitForAllTasksIdle({ esClient, filter: testStart });
+ },
+ retryOptions
+ );
+ }
+ },
+
+ async muteRule({ roleAuthc, ruleId }: { roleAuthc: RoleCredentials; ruleId: string }) {
+ const { body } = await supertestWithoutAuth
+ .post(`/api/alerting/rule/${ruleId}/_mute_all`)
+ .set(svlCommonApi.getInternalRequestHeader())
+ .set(roleAuthc.apiKeyHeader)
+ .expect(204);
+ return body;
+ },
+
+ async enableRule({ roleAuthc, ruleId }: { roleAuthc: RoleCredentials; ruleId: string }) {
+ const { body } = await supertestWithoutAuth
+ .post(`/api/alerting/rule/${ruleId}/_enable`)
+ .set(svlCommonApi.getInternalRequestHeader())
+ .set(roleAuthc.apiKeyHeader)
+ .expect(204);
+ return body;
+ },
+
+ async muteAlert({
+ roleAuthc,
+ ruleId,
+ alertId,
+ }: {
+ roleAuthc: RoleCredentials;
+ ruleId: string;
+ alertId: string;
+ }) {
+ const { body } = await supertestWithoutAuth
+ .post(`/api/alerting/rule/${ruleId}/alert/${alertId}/_mute`)
+ .set(svlCommonApi.getInternalRequestHeader())
+ .set(roleAuthc.apiKeyHeader)
+ .expect(204);
+ return body;
+ },
+
+ async unmuteRule({ roleAuthc, ruleId }: { roleAuthc: RoleCredentials; ruleId: string }) {
+ const { body } = await supertestWithoutAuth
+ .post(`/api/alerting/rule/${ruleId}/_unmute_all`)
+ .set(svlCommonApi.getInternalRequestHeader())
+ .set(roleAuthc.apiKeyHeader)
+ .expect(204);
+ return body;
+ },
+
+ async snoozeRule({ roleAuthc, ruleId }: { roleAuthc: RoleCredentials; ruleId: string }) {
+ const { body } = await supertestWithoutAuth
+ .post(`/internal/alerting/rule/${ruleId}/_snooze`)
+ .set(svlCommonApi.getInternalRequestHeader())
+ .set(roleAuthc.apiKeyHeader)
+ .send({
+ snooze_schedule: {
+ duration: 100000000,
+ rRule: {
+ count: 1,
+ dtstart: moment().format(),
+ tzid: 'UTC',
+ },
+ },
+ })
+ .expect(204);
+ return body;
+ },
+
+ async findRule({ roleAuthc, ruleId }: { roleAuthc: RoleCredentials; ruleId: string }) {
+ if (!ruleId) {
+ throw new Error(`'ruleId' is undefined`);
+ }
+ const response = await supertestWithoutAuth
+ .get(`/api/alerting/rule/${ruleId}`)
+ .set(svlCommonApi.getInternalRequestHeader())
+ .set(roleAuthc.apiKeyHeader);
+ return response.body || {};
+ },
+
+ waiting: {
+ async waitForDocumentInIndex({
+ esClient,
+ indexName,
+ ruleId,
+ num = 1,
+ sort = 'desc',
+ retryOptions = { retryCount: RETRY_COUNT, retryDelay: RETRY_DELAY },
+ }: {
+ esClient: Client;
+ indexName: string;
+ ruleId: string;
+ num?: number;
+ sort?: 'asc' | 'desc';
+ retryOptions?: TryWithRetriesOptions;
+ }): Promise {
+ return await retry.tryWithRetries(
+ `Alerting API - waiting.waitForDocumentInIndex, retryOptions: ${JSON.stringify(
+ retryOptions
+ )}`,
+ async () => {
+ const response = await esClient.search({
+ index: indexName,
+ sort: `date:${sort}`,
+ body: {
+ query: {
+ bool: {
+ must: [
+ {
+ term: {
+ 'ruleId.keyword': ruleId,
+ },
+ },
+ ],
+ },
+ },
+ },
+ });
+ if (response.hits.hits.length < num) {
+ throw new Error(`Only found ${response.hits.hits.length} / ${num} documents`);
+ }
+ return response;
+ },
+ retryOptions
+ );
+ },
+
+ async getDocumentsInIndex({
+ esClient,
+ indexName,
+ ruleId,
+ }: {
+ esClient: Client;
+ indexName: string;
+ ruleId: string;
+ }): Promise {
+ return await esClient.search({
+ index: indexName,
+ body: {
+ query: {
+ bool: {
+ must: [
+ {
+ term: {
+ 'ruleId.keyword': ruleId,
+ },
+ },
+ ],
+ },
+ },
+ },
+ });
+ },
+
+ async waitForAllTasksIdle({
+ esClient,
+ filter,
+ retryOptions = { retryCount: RETRY_COUNT, retryDelay: RETRY_DELAY },
+ }: {
+ esClient: Client;
+ filter: Date;
+ retryOptions?: TryWithRetriesOptions;
+ }): Promise {
+ return await retry.tryWithRetries(
+ `Alerting API - waiting.waitForAllTasksIdle, retryOptions: ${JSON.stringify(
+ retryOptions
+ )}`,
+ async () => {
+ const response = await esClient.search({
+ index: '.kibana_task_manager',
+ body: {
+ query: {
+ bool: {
+ must: [
+ {
+ terms: {
+ 'task.scope': ['actions', 'alerting'],
+ },
+ },
+ {
+ range: {
+ 'task.scheduledAt': {
+ gte: filter.getTime().toString(),
+ },
+ },
+ },
+ ],
+ must_not: [
+ {
+ term: {
+ 'task.status': 'idle',
+ },
+ },
+ ],
+ },
+ },
+ },
+ });
+ if (response.hits.hits.length !== 0) {
+ throw new Error(`Expected 0 hits but received ${response.hits.hits.length}`);
+ }
+ return response;
+ },
+ retryOptions
+ );
+ },
+
+ async waitForExecutionEventLog({
+ esClient,
+ filter,
+ ruleId,
+ num = 1,
+ retryOptions = { retryCount: RETRY_COUNT, retryDelay: RETRY_DELAY },
+ }: {
+ esClient: Client;
+ filter: Date;
+ ruleId: string;
+ num?: number;
+ retryOptions?: TryWithRetriesOptions;
+ }): Promise {
+ return await retry.tryWithRetries(
+ `Alerting API - waiting.waitForExecutionEventLog, retryOptions: ${JSON.stringify(
+ retryOptions
+ )}`,
+ async () => {
+ const response = await esClient.search({
+ index: '.kibana-event-log*',
+ body: {
+ query: {
+ bool: {
+ filter: [
+ {
+ term: {
+ 'rule.id': {
+ value: ruleId,
+ },
+ },
+ },
+ {
+ term: {
+ 'event.provider': {
+ value: 'alerting',
+ },
+ },
+ },
+ {
+ term: {
+ 'event.action': 'execute',
+ },
+ },
+ {
+ range: {
+ '@timestamp': {
+ gte: filter.getTime().toString(),
+ },
+ },
+ },
+ ],
+ },
+ },
+ },
+ });
+ if (response.hits.hits.length < num) {
+ throw new Error('No hits found');
+ }
+ return response;
+ },
+ retryOptions
+ );
+ },
+
+ async createIndex({ esClient, indexName }: { esClient: Client; indexName: string }) {
+ return await esClient.indices.create(
+ {
+ index: indexName,
+ body: {},
+ },
+ { meta: true }
+ );
+ },
+
+ async waitForAllTasks({
+ esClient,
+ filter,
+ taskType,
+ attempts,
+ retryOptions = { retryCount: RETRY_COUNT, retryDelay: RETRY_DELAY },
+ }: {
+ esClient: Client;
+ filter: Date;
+ taskType: string;
+ attempts: number;
+ retryOptions?: TryWithRetriesOptions;
+ }): Promise {
+ return await retry.tryWithRetries(
+ `Alerting API - waiting.waitForAllTasks, retryOptions: ${JSON.stringify(retryOptions)}`,
+ async () => {
+ const response = await esClient.search({
+ index: '.kibana_task_manager',
+ body: {
+ query: {
+ bool: {
+ must: [
+ {
+ term: {
+ 'task.status': 'idle',
+ },
+ },
+ {
+ term: {
+ 'task.attempts': attempts,
+ },
+ },
+ {
+ terms: {
+ 'task.scope': ['actions', 'alerting'],
+ },
+ },
+ {
+ term: {
+ 'task.taskType': taskType,
+ },
+ },
+ {
+ range: {
+ 'task.scheduledAt': {
+ gte: filter.getTime().toString(),
+ },
+ },
+ },
+ ],
+ },
+ },
+ },
+ });
+ if (response.hits.hits.length === 0) {
+ throw new Error('No hits found');
+ }
+ return response;
+ },
+ retryOptions
+ );
+ },
+
+ async waitForDisabled({
+ esClient,
+ ruleId,
+ filter,
+ retryOptions = { retryCount: RETRY_COUNT, retryDelay: RETRY_DELAY },
+ }: {
+ esClient: Client;
+ ruleId: string;
+ filter: Date;
+ retryOptions?: TryWithRetriesOptions;
+ }): Promise {
+ return await retry.tryWithRetries(
+ `Alerting API - waiting.waitForDisabled, retryOptions: ${JSON.stringify(retryOptions)}`,
+ async () => {
+ const response = await esClient.search({
+ index: '.kibana_task_manager',
+ body: {
+ query: {
+ bool: {
+ must: [
+ {
+ term: {
+ 'task.id': `task:${ruleId}`,
+ },
+ },
+ {
+ terms: {
+ 'task.scope': ['actions', 'alerting'],
+ },
+ },
+ {
+ range: {
+ 'task.scheduledAt': {
+ gte: filter.getTime().toString(),
+ },
+ },
+ },
+ {
+ term: {
+ 'task.enabled': true,
+ },
+ },
+ ],
+ },
+ },
+ },
+ });
+ if (response.hits.hits.length !== 0) {
+ throw new Error(`Expected 0 hits but received ${response.hits.hits.length}`);
+ }
+ return response;
+ },
+ retryOptions
+ );
+ },
+ },
+ };
+
+ return {
+ helpers,
+
+ async waitForRuleStatus({
+ roleAuthc,
+ ruleId,
+ expectedStatus,
+ }: {
+ roleAuthc: RoleCredentials;
+ ruleId: string;
+ expectedStatus: string;
+ }) {
+ if (!ruleId) {
+ throw new Error(`'ruleId' is undefined`);
+ }
+ return await retry.tryForTime(retryTimeout, async () => {
+ const response = await supertestWithoutAuth
+ .get(`/api/alerting/rule/${ruleId}`)
+ .set(svlCommonApi.getInternalRequestHeader())
+ .set(roleAuthc.apiKeyHeader)
+ .timeout(requestTimeout);
+ const { execution_status: executionStatus } = response.body || {};
+ const { status } = executionStatus || {};
+ if (status !== expectedStatus) {
+ throw new Error(`waitForStatus(${expectedStatus}): got ${status}`);
+ }
+ return executionStatus?.status;
+ });
+ },
+
+ async waitForDocumentInIndex({
+ indexName,
+ docCountTarget = 1,
+ }: {
+ indexName: string;
+ docCountTarget?: number;
+ }): Promise>> {
+ return await retry.tryForTime(retryTimeout, async () => {
+ const response = await es.search({
+ index: indexName,
+ rest_total_hits_as_int: true,
+ });
+ logger.debug(`Found ${response.hits.total} docs, looking for at least ${docCountTarget}.`);
+ if (!response.hits.total || (response.hits.total as number) < docCountTarget) {
+ throw new Error('No hits found');
+ }
+ return response;
+ });
+ },
+
+ async waitForAlertInIndex({
+ indexName,
+ ruleId,
+ }: {
+ indexName: string;
+ ruleId: string;
+ }): Promise>> {
+ if (!ruleId) {
+ throw new Error(`'ruleId' is undefined`);
+ }
+ return await retry.tryForTime(retryTimeout, async () => {
+ const response = await es.search({
+ index: indexName,
+ body: {
+ query: {
+ term: {
+ 'kibana.alert.rule.uuid': ruleId,
+ },
+ },
+ },
+ });
+ if (response.hits.hits.length === 0) {
+ throw new Error('No hits found');
+ }
+ return response;
+ });
+ },
+
+ async createIndexConnector({
+ roleAuthc,
+ name,
+ indexName,
+ }: {
+ roleAuthc: RoleCredentials;
+ name: string;
+ indexName: string;
+ }) {
+ const { body } = await supertestWithoutAuth
+ .post(`/api/actions/connector`)
+ .set(svlCommonApi.getInternalRequestHeader())
+ .set(roleAuthc.apiKeyHeader)
+ .send({
+ name,
+ config: {
+ index: indexName,
+ refresh: true,
+ },
+ connector_type_id: '.index',
+ });
+ return body.id as string;
+ },
+
+ async createRule({
+ roleAuthc,
+ name,
+ ruleTypeId,
+ params,
+ actions = [],
+ tags = [],
+ schedule,
+ consumer,
+ }: {
+ roleAuthc: RoleCredentials;
+ ruleTypeId: string;
+ name: string;
+ params: MetricThresholdParams | ThresholdParams | SloBurnRateRuleParams;
+ actions?: any[];
+ tags?: any[];
+ schedule?: { interval: string };
+ consumer: string;
+ }) {
+ const { body } = await supertestWithoutAuth
+ .post(`/api/alerting/rule`)
+ .set(svlCommonApi.getInternalRequestHeader())
+ .set(roleAuthc.apiKeyHeader)
+ .send({
+ params,
+ consumer,
+ schedule: schedule || {
+ interval: '5m',
+ },
+ tags,
+ name,
+ rule_type_id: ruleTypeId,
+ actions,
+ });
+ return body;
+ },
+
+ async findRule(roleAuthc: RoleCredentials, ruleId: string) {
+ if (!ruleId) {
+ throw new Error(`'ruleId' is undefined`);
+ }
+ const response = await supertestWithoutAuth
+ .get('/api/alerting/rules/_find')
+ .set(svlCommonApi.getInternalRequestHeader())
+ .set(roleAuthc.apiKeyHeader);
+ return response.body.data.find((obj: any) => obj.id === ruleId);
+ },
+ };
+}
diff --git a/x-pack/test_serverless/shared/services/deployment_agnostic_services.ts b/x-pack/test_serverless/shared/services/deployment_agnostic_services.ts
index 97a5963bd9e3b..2272890e52eb4 100644
--- a/x-pack/test_serverless/shared/services/deployment_agnostic_services.ts
+++ b/x-pack/test_serverless/shared/services/deployment_agnostic_services.ts
@@ -8,7 +8,7 @@
import _ from 'lodash';
import { services as apiIntegrationServices } from '@kbn/test-suites-xpack/api_integration/services';
-
+import { AlertingApiProvider } from './alerting_api';
/*
* Some FTR services from api integration stateful tests are compatible with serverless environment
* While adding a new one, make sure to verify that it works on both Kibana CI and MKI
@@ -35,4 +35,5 @@ const deploymentAgnosticApiIntegrationServices = _.pick(apiIntegrationServices,
export const services = {
// deployment agnostic FTR services
...deploymentAgnosticApiIntegrationServices,
+ alertingApi: AlertingApiProvider,
};
From 6689169687977595f03a536ee2bbfda7b0135fb1 Mon Sep 17 00:00:00 2001
From: Nick Peihl
Date: Mon, 16 Sep 2024 10:50:50 -0400
Subject: [PATCH 026/139] Move @elastic/kibana-gis ownership to
@elastic/kibana-presentation (#192521)
## Summary
The legacy `@elastic/kibana-gis` team is now a part of
`@elastic/kibana-presentation`. So we should move ownership of all code
to the correct team.
---
.github/CODEOWNERS | 12 ++++++------
packages/kbn-mapbox-gl/kibana.jsonc | 2 +-
renovate.json | 1 -
src/plugins/maps_ems/kibana.jsonc | 2 +-
.../third_party_maps_source_example/kibana.jsonc | 2 +-
x-pack/packages/maps/vector_tile_utils/kibana.jsonc | 2 +-
x-pack/plugins/file_upload/kibana.jsonc | 2 +-
x-pack/plugins/maps/kibana.jsonc | 2 +-
8 files changed, 12 insertions(+), 13 deletions(-)
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 9adb05cb22ecc..457458ec5b1c6 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -461,7 +461,7 @@ src/plugins/field_formats @elastic/kibana-data-discovery
packages/kbn-field-types @elastic/kibana-data-discovery
packages/kbn-field-utils @elastic/kibana-data-discovery
x-pack/plugins/fields_metadata @elastic/obs-ux-logs-team
-x-pack/plugins/file_upload @elastic/kibana-gis @elastic/ml-ui
+x-pack/plugins/file_upload @elastic/kibana-presentation @elastic/ml-ui
examples/files_example @elastic/appex-sharedux
src/plugins/files_management @elastic/appex-sharedux
src/plugins/files @elastic/appex-sharedux
@@ -583,11 +583,11 @@ packages/kbn-management/settings/types @elastic/kibana-management
packages/kbn-management/settings/utilities @elastic/kibana-management
packages/kbn-management/storybook/config @elastic/kibana-management
test/plugin_functional/plugins/management_test_plugin @elastic/kibana-management
-packages/kbn-mapbox-gl @elastic/kibana-gis
-x-pack/examples/third_party_maps_source_example @elastic/kibana-gis
-src/plugins/maps_ems @elastic/kibana-gis
-x-pack/plugins/maps @elastic/kibana-gis
-x-pack/packages/maps/vector_tile_utils @elastic/kibana-gis
+packages/kbn-mapbox-gl @elastic/kibana-presentation
+x-pack/examples/third_party_maps_source_example @elastic/kibana-presentation
+src/plugins/maps_ems @elastic/kibana-presentation
+x-pack/plugins/maps @elastic/kibana-presentation
+x-pack/packages/maps/vector_tile_utils @elastic/kibana-presentation
x-pack/plugins/observability_solution/metrics_data_access @elastic/obs-knowledge-team @elastic/obs-ux-infra_services-team
x-pack/packages/ml/agg_utils @elastic/ml-ui
x-pack/packages/ml/anomaly_utils @elastic/ml-ui
diff --git a/packages/kbn-mapbox-gl/kibana.jsonc b/packages/kbn-mapbox-gl/kibana.jsonc
index 4238b33f6aefd..6cc7e1f7b2b30 100644
--- a/packages/kbn-mapbox-gl/kibana.jsonc
+++ b/packages/kbn-mapbox-gl/kibana.jsonc
@@ -1,5 +1,5 @@
{
"type": "shared-common",
"id": "@kbn/mapbox-gl",
- "owner": "@elastic/kibana-gis"
+ "owner": "@elastic/kibana-presentation"
}
diff --git a/renovate.json b/renovate.json
index 02ec0d0c127a4..6f3b61c6e1b12 100644
--- a/renovate.json
+++ b/renovate.json
@@ -371,7 +371,6 @@
"team:kibana-presentation",
"team:kibana-data-discovery",
"team:kibana-management",
- "team:kibana-gis",
"team:security-solution"
],
"matchBaseBranches": ["main"],
diff --git a/src/plugins/maps_ems/kibana.jsonc b/src/plugins/maps_ems/kibana.jsonc
index f71542e94ae71..a341ad05f4e4b 100644
--- a/src/plugins/maps_ems/kibana.jsonc
+++ b/src/plugins/maps_ems/kibana.jsonc
@@ -1,7 +1,7 @@
{
"type": "plugin",
"id": "@kbn/maps-ems-plugin",
- "owner": "@elastic/kibana-gis",
+ "owner": "@elastic/kibana-presentation",
"plugin": {
"id": "mapsEms",
"server": true,
diff --git a/x-pack/examples/third_party_maps_source_example/kibana.jsonc b/x-pack/examples/third_party_maps_source_example/kibana.jsonc
index 6b1317437401d..5b987dcd966ab 100644
--- a/x-pack/examples/third_party_maps_source_example/kibana.jsonc
+++ b/x-pack/examples/third_party_maps_source_example/kibana.jsonc
@@ -1,7 +1,7 @@
{
"type": "plugin",
"id": "@kbn/maps-custom-raster-source-plugin",
- "owner": "@elastic/kibana-gis",
+ "owner": "@elastic/kibana-presentation",
"description": "An example plugin for creating a custom raster source for Elastic Maps",
"plugin": {
"id": "mapsCustomRasterSource",
diff --git a/x-pack/packages/maps/vector_tile_utils/kibana.jsonc b/x-pack/packages/maps/vector_tile_utils/kibana.jsonc
index 7fa54b903a4a5..5e1e9957ecdf3 100644
--- a/x-pack/packages/maps/vector_tile_utils/kibana.jsonc
+++ b/x-pack/packages/maps/vector_tile_utils/kibana.jsonc
@@ -1,5 +1,5 @@
{
"type": "shared-common",
"id": "@kbn/maps-vector-tile-utils",
- "owner": "@elastic/kibana-gis"
+ "owner": "@elastic/kibana-presentation"
}
diff --git a/x-pack/plugins/file_upload/kibana.jsonc b/x-pack/plugins/file_upload/kibana.jsonc
index 6c6e3fddd0e7c..9d8143dafcb46 100644
--- a/x-pack/plugins/file_upload/kibana.jsonc
+++ b/x-pack/plugins/file_upload/kibana.jsonc
@@ -1,7 +1,7 @@
{
"type": "plugin",
"id": "@kbn/file-upload-plugin",
- "owner": ["@elastic/kibana-gis", "@elastic/ml-ui"],
+ "owner": ["@elastic/kibana-presentation", "@elastic/ml-ui"],
"description": "The file upload plugin contains components and services for uploading a file, analyzing its data, and then importing the data into an Elasticsearch index. Supported file types include CSV, TSV, newline-delimited JSON and GeoJSON.",
"plugin": {
"id": "fileUpload",
diff --git a/x-pack/plugins/maps/kibana.jsonc b/x-pack/plugins/maps/kibana.jsonc
index b042d0250b0c2..421817e87344f 100644
--- a/x-pack/plugins/maps/kibana.jsonc
+++ b/x-pack/plugins/maps/kibana.jsonc
@@ -1,7 +1,7 @@
{
"type": "plugin",
"id": "@kbn/maps-plugin",
- "owner": "@elastic/kibana-gis",
+ "owner": "@elastic/kibana-presentation",
"plugin": {
"id": "maps",
"server": true,
From f029f8086a6731b5f435775c915d46e110a34658 Mon Sep 17 00:00:00 2001
From: Carlos Crespo
Date: Mon, 16 Sep 2024 16:58:13 +0200
Subject: [PATCH 027/139] [Observability] Create observability-specific setting
for excluding data tiers from queries (#192570)
part of [#190559](https://github.com/elastic/kibana/issues/190559)
## Summary
This PR introduces a new `Advanced Settings` under `Observabilty` to
provide a way of configuring the exclusion of indices in the `data_cold`
and/or `data_frozen` tiers from queries.
The change will help to address issues encountered in O11y, most
specifically in APM, and could also affect Infra and other features,
with unbounded queries targeting the frozen tier.
### For reviewers
This PR replaces https://github.com/elastic/kibana/pull/192276
---------
Co-authored-by: Elastic Machine
---
.../settings/setting_ids/index.ts | 1 +
.../settings/observability_project/index.ts | 1 +
.../server/collectors/management/schema.ts | 7 +++++++
.../server/collectors/management/types.ts | 1 +
src/plugins/telemetry/schema/oss_plugins.json | 9 +++++++++
.../observability/common/ui_settings_keys.ts | 1 +
.../observability/server/ui_settings.ts | 19 +++++++++++++++++++
7 files changed, 39 insertions(+)
diff --git a/packages/kbn-management/settings/setting_ids/index.ts b/packages/kbn-management/settings/setting_ids/index.ts
index 08ce7f3579229..0f79a5fff0506 100644
--- a/packages/kbn-management/settings/setting_ids/index.ts
+++ b/packages/kbn-management/settings/setting_ids/index.ts
@@ -150,6 +150,7 @@ export const OBSERVABILITY_AI_ASSISTANT_SIMULATED_FUNCTION_CALLING =
'observability:aiAssistantSimulatedFunctionCalling';
export const OBSERVABILITY_AI_ASSISTANT_SEARCH_CONNECTOR_INDEX_PATTERN =
'observability:aiAssistantSearchConnectorIndexPattern';
+export const OBSERVABILITY_SEARCH_EXCLUDED_DATA_TIERS = 'observability:searchExcludedDataTiers';
// Reporting settings
export const XPACK_REPORTING_CUSTOM_PDF_LOGO_ID = 'xpackReporting:customPdfLogo';
diff --git a/packages/serverless/settings/observability_project/index.ts b/packages/serverless/settings/observability_project/index.ts
index 470964954d166..85f6327bf0a07 100644
--- a/packages/serverless/settings/observability_project/index.ts
+++ b/packages/serverless/settings/observability_project/index.ts
@@ -37,4 +37,5 @@ export const OBSERVABILITY_PROJECT_SETTINGS = [
settings.OBSERVABILITY_AI_ASSISTANT_LOGS_INDEX_PATTERN_ID,
settings.OBSERVABILITY_AI_ASSISTANT_SIMULATED_FUNCTION_CALLING,
settings.OBSERVABILITY_AI_ASSISTANT_SEARCH_CONNECTOR_INDEX_PATTERN,
+ settings.OBSERVABILITY_SEARCH_EXCLUDED_DATA_TIERS,
];
diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts
index d1ab81f3e60a7..52c0df738246a 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts
@@ -694,4 +694,11 @@ export const stackManagementSchema: MakeSchemaFrom = {
type: 'boolean',
_meta: { description: 'Non-default value of setting.' },
},
+ 'observability:searchExcludedDataTiers': {
+ type: 'array',
+ items: {
+ type: 'keyword',
+ _meta: { description: 'Non-default value of setting.' },
+ },
+ },
};
diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts
index c66f4f07a296e..0a0ebe8ebbac6 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts
@@ -181,4 +181,5 @@ export interface UsageStats {
'aiAssistant:preferredAIAssistantType': string;
'observability:profilingFetchTopNFunctionsFromStacktraces': boolean;
'securitySolution:excludedDataTiersForRuleExecution': string[];
+ 'observability:searchExcludedDataTiers': string[];
}
diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json
index 70fbeec73bc5d..77e050334803b 100644
--- a/src/plugins/telemetry/schema/oss_plugins.json
+++ b/src/plugins/telemetry/schema/oss_plugins.json
@@ -10355,6 +10355,15 @@
}
}
},
+ "observability:searchExcludedDataTiers": {
+ "type": "array",
+ "items": {
+ "type": "keyword",
+ "_meta": {
+ "description": "Non-default value of setting."
+ }
+ }
+ },
"banners:placement": {
"type": "keyword",
"_meta": {
diff --git a/x-pack/plugins/observability_solution/observability/common/ui_settings_keys.ts b/x-pack/plugins/observability_solution/observability/common/ui_settings_keys.ts
index fe43cd30705db..efceaca9a0427 100644
--- a/x-pack/plugins/observability_solution/observability/common/ui_settings_keys.ts
+++ b/x-pack/plugins/observability_solution/observability/common/ui_settings_keys.ts
@@ -48,3 +48,4 @@ export const profilingAzureCostDiscountRate = 'observability:profilingAzureCostD
export const apmEnableTransactionProfiling = 'observability:apmEnableTransactionProfiling';
export const profilingFetchTopNFunctionsFromStacktraces =
'observability:profilingFetchTopNFunctionsFromStacktraces';
+export const searchExcludedDataTiers = 'observability:searchExcludedDataTiers';
diff --git a/x-pack/plugins/observability_solution/observability/server/ui_settings.ts b/x-pack/plugins/observability_solution/observability/server/ui_settings.ts
index d404606b4ce79..81c0596722106 100644
--- a/x-pack/plugins/observability_solution/observability/server/ui_settings.ts
+++ b/x-pack/plugins/observability_solution/observability/server/ui_settings.ts
@@ -46,6 +46,7 @@ import {
apmEnableServiceInventoryTableSearchBar,
profilingFetchTopNFunctionsFromStacktraces,
enableInfrastructureContainerAssetView,
+ searchExcludedDataTiers,
} from '../common/ui_settings_keys';
const betaLabel = i18n.translate('xpack.observability.uiSettings.betaLabel', {
@@ -640,6 +641,24 @@ export const uiSettings: Record = {
schema: schema.boolean(),
requiresPageReload: false,
},
+ [searchExcludedDataTiers]: {
+ category: [observabilityFeatureId],
+ name: i18n.translate('xpack.observability.searchExcludedDataTiers', {
+ defaultMessage: 'Excluded data tiers from search',
+ }),
+ description: i18n.translate(
+ 'xpack.observability.advancedSettings.searchExcludedDataTiersDesc',
+ {
+ defaultMessage: `Specify the data tiers to exclude from search, such as data_cold and/or data_frozen.
+ When configured, indices allocated in the selected tiers will be ignored from search requests. Affected apps: APM`,
+ }
+ ),
+ value: [],
+ schema: schema.arrayOf(
+ schema.oneOf([schema.literal('data_cold'), schema.literal('data_frozen')])
+ ),
+ requiresPageReload: false,
+ },
};
function throttlingDocsLink({ href }: { href: string }) {
From cd964f1229b1fdc919677768dae22cf1c05fa3e2 Mon Sep 17 00:00:00 2001
From: Tiago Vila Verde
Date: Mon, 16 Sep 2024 17:15:10 +0200
Subject: [PATCH 028/139] [Security Solution][Entity Analytics] APIs for Entity
Store engine (#191986)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This PR introduces the following API routes for setting up Entity Store
"engines":
Initialise Engine | POST /api/entity_store/engines//init
-- | --
Start Engine | POST /api/entity_store/engines//start
Stop Engine | POST /api/entity_store/engines//stop
Delete Engine | DELETE /api/entity_store/engines/
Get engine | GET /api/entity_store/engines/
List Engines | GET /api/entity_store/engines
The PR includes the following:
- Adding the `EntityManager` plugin (see elastic/obs-entities) as a
dependency of the Security Solution
- The OpenAPI schemas for the new routes
- The actual Kibana side endpoints
- A `Saved Object` to track the installed engines
- A new `EntityStoreDataClient`
- A new feature flag `entityStoreEngineRoutesEnabled`
### How to test
1. Add some host/user data
* Easiest is to use
[elastic/security-data-generator](https://github.com/elastic/security-documents-generator)
2. Make sure to add `entityStoreEngineRoutesEnabled` under
`xpack.securitySolution.enableExperimental` in your `kibana.dev.yml`
3. In kibana dev tools or your terminal, call the `INIT` route for
either `user` or `host`.
4. You should now see 2 transforms in kibana. Make sure to re-trigger
them if needed so they process the documents.
5. Check that new entities have been observed by querying the new
entities index via:
* `GET .entities.v1.latest.ea*/_search`
6. Check the other endpoints are working (`START`, `STOP`, `LIST`, etc)
7. Calling `DELETE` should remove the transforms
Implements https://github.com/elastic/security-team/issues/10230
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Elastic Machine
---
.../current_fields.json | 6 +
.../current_mappings.json | 17 ++
.../check_registered_types.test.ts | 1 +
.../group3/type_registrations.test.ts | 1 +
.../entity_store/common.gen.ts | 38 ++++
.../entity_store/common.schema.yaml | 37 ++++
.../entity_store/engine/delete.gen.ts | 43 ++++
.../entity_store/engine/delete.schema.yaml | 37 ++++
.../entity_store/engine/get.gen.ts | 33 +++
.../entity_store/engine/get.schema.yaml | 25 +++
.../entity_store/engine/init.gen.ts | 38 ++++
.../entity_store/engine/init.schema.yaml | 39 ++++
.../entity_store/engine/list.gen.ts | 25 +++
.../entity_store/engine/list.schema.yaml | 25 +++
.../entity_store/engine/start.gen.ts | 33 +++
.../entity_store/engine/start.schema.yaml | 31 +++
.../entity_store/engine/stats.gen.ts | 39 ++++
.../entity_store/engine/stats.schema.yaml | 41 ++++
.../entity_store/engine/stop.gen.ts | 33 +++
.../entity_store/engine/stop.schema.yaml | 30 +++
.../common/api/quickstart_client.gen.ts | 134 ++++++++++++
.../common/experimental_features.ts | 5 +
...alytics_api_2023_10_31.bundled.schema.yaml | 205 ++++++++++++++++++
...alytics_api_2023_10_31.bundled.schema.yaml | 205 ++++++++++++++++++
x-pack/plugins/security_solution/kibana.jsonc | 5 +-
.../routes/__mocks__/request_context.ts | 3 +
.../entity_store/constants.ts | 21 ++
.../entity_store/definition.ts | 56 +++++
.../entity_store_data_client.mock.ts | 20 ++
.../entity_store/entity_store_data_client.ts | 120 ++++++++++
.../entity_store/routes/delete.ts | 64 ++++++
.../entity_store/routes/get.ts | 62 ++++++
.../entity_store/routes/index.ts | 8 +
.../entity_store/routes/init.ts | 65 ++++++
.../entity_store/routes/list.ts | 57 +++++
.../routes/register_entity_store_routes.ts | 23 ++
.../entity_store/routes/start.ts | 59 +++++
.../entity_store/routes/stats.ts | 58 +++++
.../entity_store/routes/stop.ts | 59 +++++
.../saved_object/engine_descriptor.ts | 76 +++++++
.../saved_object/engine_descriptor_type.ts | 36 +++
.../entity_store/saved_object/index.ts | 8 +
.../entity_store/utils/utils.ts | 33 +++
.../register_entity_analytics_routes.ts | 4 +
.../server/request_context_factory.ts | 18 ++
.../security_solution/server/saved_objects.ts | 2 +
.../plugins/security_solution/server/types.ts | 2 +
.../plugins/security_solution/tsconfig.json | 2 +
.../services/security_solution_api.gen.ts | 83 +++++++
.../platform_security/authorization.ts | 34 +++
50 files changed, 2097 insertions(+), 2 deletions(-)
create mode 100644 x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts
create mode 100644 x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml
create mode 100644 x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/delete.gen.ts
create mode 100644 x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/delete.schema.yaml
create mode 100644 x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/get.gen.ts
create mode 100644 x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/get.schema.yaml
create mode 100644 x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/init.gen.ts
create mode 100644 x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/init.schema.yaml
create mode 100644 x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/list.gen.ts
create mode 100644 x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/list.schema.yaml
create mode 100644 x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/start.gen.ts
create mode 100644 x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/start.schema.yaml
create mode 100644 x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/stats.gen.ts
create mode 100644 x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/stats.schema.yaml
create mode 100644 x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/stop.gen.ts
create mode 100644 x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/stop.schema.yaml
create mode 100644 x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/constants.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/definition.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.mock.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/delete.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/get.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/index.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/init.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/list.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/register_entity_store_routes.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/start.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/stats.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/stop.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/saved_object/engine_descriptor.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/saved_object/engine_descriptor_type.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/saved_object/index.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/utils/utils.ts
diff --git a/packages/kbn-check-mappings-update-cli/current_fields.json b/packages/kbn-check-mappings-update-cli/current_fields.json
index 0447ba6a226dd..ec14f4519d344 100644
--- a/packages/kbn-check-mappings-update-cli/current_fields.json
+++ b/packages/kbn-check-mappings-update-cli/current_fields.json
@@ -312,6 +312,12 @@
"entity-discovery-api-key": [
"apiKey"
],
+ "entity-engine-status": [
+ "filter",
+ "indexPattern",
+ "status",
+ "type"
+ ],
"epm-packages": [
"additional_spaces_installed_kibana",
"es_index_patterns",
diff --git a/packages/kbn-check-mappings-update-cli/current_mappings.json b/packages/kbn-check-mappings-update-cli/current_mappings.json
index bda2270001bd9..2bdf6e75ad1cb 100644
--- a/packages/kbn-check-mappings-update-cli/current_mappings.json
+++ b/packages/kbn-check-mappings-update-cli/current_mappings.json
@@ -1057,6 +1057,23 @@
}
}
},
+ "entity-engine-status": {
+ "dynamic": false,
+ "properties": {
+ "filter": {
+ "type": "keyword"
+ },
+ "indexPattern": {
+ "type": "keyword"
+ },
+ "status": {
+ "type": "keyword"
+ },
+ "type": {
+ "type": "keyword"
+ }
+ }
+ },
"epm-packages": {
"properties": {
"additional_spaces_installed_kibana": {
diff --git a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts
index 92de0c925951b..170cfa5958782 100644
--- a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts
+++ b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts
@@ -93,6 +93,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
"enterprise_search_telemetry": "9ac912e1417fc8681e0cd383775382117c9e3d3d",
"entity-definition": "61be3e95966045122b55e181bb39658b1dc9bbe9",
"entity-discovery-api-key": "c267a65c69171d1804362155c1378365f5acef88",
+ "entity-engine-status": "0738aa1a06d3361911740f8f166071ea43a00927",
"epm-packages": "8042d4a1522f6c4e6f5486e791b3ffe3a22f88fd",
"epm-packages-assets": "7a3e58efd9a14191d0d1a00b8aaed30a145fd0b1",
"event-annotation-group": "715ba867d8c68f3c9438052210ea1c30a9362582",
diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts
index 4320b0eb689d9..e95a82e63d0ff 100644
--- a/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts
+++ b/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts
@@ -124,6 +124,7 @@ const previouslyRegisteredTypes = [
'security-rule',
'security-solution-signals-migration',
'risk-engine-configuration',
+ 'entity-engine-status',
'server',
'siem-detection-engine-rule-actions',
'siem-detection-engine-rule-execution-info',
diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts
new file mode 100644
index 0000000000000..e5f8c631fcbae
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts
@@ -0,0 +1,38 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+/*
+ * NOTICE: Do not edit this file manually.
+ * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
+ *
+ * info:
+ * title: Entity Store Common Schema
+ * version: 1
+ */
+
+import { z } from '@kbn/zod';
+
+export type EntityType = z.infer;
+export const EntityType = z.enum(['user', 'host']);
+export type EntityTypeEnum = typeof EntityType.enum;
+export const EntityTypeEnum = EntityType.enum;
+
+export type IndexPattern = z.infer;
+export const IndexPattern = z.string();
+
+export type EngineStatus = z.infer;
+export const EngineStatus = z.enum(['installing', 'started', 'stopped']);
+export type EngineStatusEnum = typeof EngineStatus.enum;
+export const EngineStatusEnum = EngineStatus.enum;
+
+export type EngineDescriptor = z.infer;
+export const EngineDescriptor = z.object({
+ type: EntityType.optional(),
+ indexPattern: IndexPattern.optional(),
+ status: EngineStatus.optional(),
+ filter: z.string().optional(),
+});
diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml
new file mode 100644
index 0000000000000..dc17ad6193ee5
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml
@@ -0,0 +1,37 @@
+openapi: 3.0.0
+info:
+ title: Entity Store Common Schema
+ description: Common schema for Entity Store
+ version: '1'
+paths: {}
+components:
+ schemas:
+
+ EntityType:
+ type: string
+ enum:
+ - user
+ - host
+
+ EngineDescriptor:
+ type: object
+ properties:
+ type:
+ $ref: '#/components/schemas/EntityType'
+ indexPattern:
+ $ref: '#/components/schemas/IndexPattern'
+ status:
+ $ref: '#/components/schemas/EngineStatus'
+ filter:
+ type: string
+
+ EngineStatus:
+ type: string
+ enum:
+ - installing
+ - started
+ - stopped
+
+ IndexPattern:
+ type: string
+
\ No newline at end of file
diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/delete.gen.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/delete.gen.ts
new file mode 100644
index 0000000000000..34acf2a802076
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/delete.gen.ts
@@ -0,0 +1,43 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+/*
+ * NOTICE: Do not edit this file manually.
+ * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
+ *
+ * info:
+ * title: Delete the entity store engine
+ * version: 2023-10-31
+ */
+
+import { z } from '@kbn/zod';
+import { BooleanFromString } from '@kbn/zod-helpers';
+
+import { EntityType } from '../common.gen';
+
+export type DeleteEntityStoreRequestQuery = z.infer;
+export const DeleteEntityStoreRequestQuery = z.object({
+ /**
+ * Control flag to also delete the entity data.
+ */
+ data: BooleanFromString.optional(),
+});
+export type DeleteEntityStoreRequestQueryInput = z.input;
+
+export type DeleteEntityStoreRequestParams = z.infer;
+export const DeleteEntityStoreRequestParams = z.object({
+ /**
+ * The entity type of the store (either 'user' or 'host').
+ */
+ entityType: EntityType,
+});
+export type DeleteEntityStoreRequestParamsInput = z.input;
+
+export type DeleteEntityStoreResponse = z.infer;
+export const DeleteEntityStoreResponse = z.object({
+ deleted: z.boolean().optional(),
+});
diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/delete.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/delete.schema.yaml
new file mode 100644
index 0000000000000..c766d9895c5fa
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/delete.schema.yaml
@@ -0,0 +1,37 @@
+openapi: 3.0.0
+
+info:
+ title: Delete the entity store engine
+ version: '2023-10-31'
+paths:
+ /api/entity_store/engines/{entityType}:
+ delete:
+ x-labels: [ess, serverless]
+ x-codegen-enabled: true
+ operationId: DeleteEntityStore
+ summary: Delete the Entity Store engine
+ parameters:
+ - name: entityType
+ in: path
+ required: true
+ schema:
+ $ref: '../common.schema.yaml#/components/schemas/EntityType'
+ description: The entity type of the store (either 'user' or 'host').
+
+ - name: data
+ in: query
+ required: false
+ schema:
+ type: boolean
+ description: Control flag to also delete the entity data.
+ responses:
+ '200':
+ description: Successful response
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ deleted:
+ type: boolean
+
\ No newline at end of file
diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/get.gen.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/get.gen.ts
new file mode 100644
index 0000000000000..44f6f45844fc1
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/get.gen.ts
@@ -0,0 +1,33 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+/*
+ * NOTICE: Do not edit this file manually.
+ * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
+ *
+ * info:
+ * title: Get Entity Store engine
+ * version: 2023-10-31
+ */
+
+import { z } from '@kbn/zod';
+
+import { EntityType, EngineDescriptor } from '../common.gen';
+
+export type GetEntityStoreEngineRequestParams = z.infer;
+export const GetEntityStoreEngineRequestParams = z.object({
+ /**
+ * The entity type of the store (either 'user' or 'host').
+ */
+ entityType: EntityType,
+});
+export type GetEntityStoreEngineRequestParamsInput = z.input<
+ typeof GetEntityStoreEngineRequestParams
+>;
+
+export type GetEntityStoreEngineResponse = z.infer;
+export const GetEntityStoreEngineResponse = EngineDescriptor;
diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/get.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/get.schema.yaml
new file mode 100644
index 0000000000000..d65a5906e54d9
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/get.schema.yaml
@@ -0,0 +1,25 @@
+openapi: 3.0.0
+info:
+ title: Get Entity Store engine
+ version: '2023-10-31'
+paths:
+ /api/entity_store/engines/{entityType}:
+ get:
+ x-labels: [ess, serverless]
+ x-codegen-enabled: true
+ operationId: GetEntityStoreEngine
+ summary: Get the Entity Store engine
+ parameters:
+ - name: entityType
+ in: path
+ required: true
+ schema:
+ $ref: '../common.schema.yaml#/components/schemas/EntityType'
+ description: The entity type of the store (either 'user' or 'host').
+ responses:
+ '200':
+ description: Successful response
+ content:
+ application/json:
+ schema:
+ $ref: '../common.schema.yaml#/components/schemas/EngineDescriptor'
diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/init.gen.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/init.gen.ts
new file mode 100644
index 0000000000000..07f32f4cb7144
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/init.gen.ts
@@ -0,0 +1,38 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+/*
+ * NOTICE: Do not edit this file manually.
+ * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
+ *
+ * info:
+ * title: Init Entity Store types
+ * version: 2023-10-31
+ */
+
+import { z } from '@kbn/zod';
+
+import { EntityType, IndexPattern, EngineDescriptor } from '../common.gen';
+
+export type InitEntityStoreRequestParams = z.infer;
+export const InitEntityStoreRequestParams = z.object({
+ /**
+ * The entity type of the store (either 'user' or 'host').
+ */
+ entityType: EntityType,
+});
+export type InitEntityStoreRequestParamsInput = z.input;
+
+export type InitEntityStoreRequestBody = z.infer;
+export const InitEntityStoreRequestBody = z.object({
+ indexPattern: IndexPattern.optional(),
+ filter: z.string().optional(),
+});
+export type InitEntityStoreRequestBodyInput = z.input;
+
+export type InitEntityStoreResponse = z.infer;
+export const InitEntityStoreResponse = EngineDescriptor;
diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/init.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/init.schema.yaml
new file mode 100644
index 0000000000000..8e826d57ce40a
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/init.schema.yaml
@@ -0,0 +1,39 @@
+openapi: 3.0.0
+
+info:
+ title: Init Entity Store types
+ version: '2023-10-31'
+paths:
+ /api/entity_store/engines/{entityType}/init:
+ post:
+ x-labels: [ess, serverless]
+ x-codegen-enabled: true
+ operationId: InitEntityStore
+ summary: Initialize the Entity Store
+ parameters:
+ - name: entityType
+ in: path
+ required: true
+ schema:
+ $ref: '../common.schema.yaml#/components/schemas/EntityType'
+ description: The entity type of the store (either 'user' or 'host').
+ requestBody:
+ description: Schema for the engine initialization
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ indexPattern:
+ $ref: '../common.schema.yaml#/components/schemas/IndexPattern'
+ filter:
+ type: string
+ responses:
+ '200':
+ description: Successful response
+ content:
+ application/json:
+ schema:
+ $ref: '../common.schema.yaml#/components/schemas/EngineDescriptor'
+
diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/list.gen.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/list.gen.ts
new file mode 100644
index 0000000000000..926549a329a4b
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/list.gen.ts
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+/*
+ * NOTICE: Do not edit this file manually.
+ * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
+ *
+ * info:
+ * title: List Entity Store engines
+ * version: 2023-10-31
+ */
+
+import { z } from '@kbn/zod';
+
+import { EngineDescriptor } from '../common.gen';
+
+export type ListEntityStoreEnginesResponse = z.infer;
+export const ListEntityStoreEnginesResponse = z.object({
+ count: z.number().int().optional(),
+ engines: z.array(EngineDescriptor).optional(),
+});
diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/list.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/list.schema.yaml
new file mode 100644
index 0000000000000..efad1a4380352
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/list.schema.yaml
@@ -0,0 +1,25 @@
+openapi: 3.0.0
+info:
+ title: List Entity Store engines
+ version: '2023-10-31'
+paths:
+ /api/entity_store/engines:
+ get:
+ x-labels: [ess, serverless]
+ x-codegen-enabled: true
+ operationId: ListEntityStoreEngines
+ summary: List the Entity Store engines
+ responses:
+ '200':
+ description: Successful response
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ count:
+ type: integer
+ engines:
+ type: array
+ items:
+ $ref: '../common.schema.yaml#/components/schemas/EngineDescriptor'
\ No newline at end of file
diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/start.gen.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/start.gen.ts
new file mode 100644
index 0000000000000..b8e94d00551c0
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/start.gen.ts
@@ -0,0 +1,33 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+/*
+ * NOTICE: Do not edit this file manually.
+ * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
+ *
+ * info:
+ * title: Start the entity store engine
+ * version: 2023-10-31
+ */
+
+import { z } from '@kbn/zod';
+
+import { EntityType } from '../common.gen';
+
+export type StartEntityStoreRequestParams = z.infer;
+export const StartEntityStoreRequestParams = z.object({
+ /**
+ * The entity type of the store (either 'user' or 'host').
+ */
+ entityType: EntityType,
+});
+export type StartEntityStoreRequestParamsInput = z.input;
+
+export type StartEntityStoreResponse = z.infer;
+export const StartEntityStoreResponse = z.object({
+ started: z.boolean().optional(),
+});
diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/start.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/start.schema.yaml
new file mode 100644
index 0000000000000..5c048bf3d973c
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/start.schema.yaml
@@ -0,0 +1,31 @@
+openapi: 3.0.0
+
+info:
+ title: Start the entity store engine
+ version: '2023-10-31'
+paths:
+ /api/entity_store/engines/{entityType}/start:
+ post:
+ x-labels: [ess, serverless]
+ x-codegen-enabled: true
+ operationId: StartEntityStore
+ summary: Start the Entity Store engine
+ parameters:
+ - name: entityType
+ in: path
+ required: true
+ schema:
+ $ref: '../common.schema.yaml#/components/schemas/EntityType'
+ description: The entity type of the store (either 'user' or 'host').
+ responses:
+ '200':
+ description: Successful response
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ started:
+ type: boolean
+
+
\ No newline at end of file
diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/stats.gen.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/stats.gen.ts
new file mode 100644
index 0000000000000..631399faebc96
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/stats.gen.ts
@@ -0,0 +1,39 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+/*
+ * NOTICE: Do not edit this file manually.
+ * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
+ *
+ * info:
+ * title: Get the entity store engine stats
+ * version: 2023-10-31
+ */
+
+import { z } from '@kbn/zod';
+
+import { EntityType, IndexPattern, EngineStatus } from '../common.gen';
+
+export type GetEntityStoreStatsRequestParams = z.infer;
+export const GetEntityStoreStatsRequestParams = z.object({
+ /**
+ * The entity type of the store (either 'user' or 'host').
+ */
+ entityType: EntityType,
+});
+export type GetEntityStoreStatsRequestParamsInput = z.input<
+ typeof GetEntityStoreStatsRequestParams
+>;
+
+export type GetEntityStoreStatsResponse = z.infer;
+export const GetEntityStoreStatsResponse = z.object({
+ type: EntityType.optional(),
+ indexPattern: IndexPattern.optional(),
+ status: EngineStatus.optional(),
+ transforms: z.array(z.object({})).optional(),
+ indices: z.array(z.object({})).optional(),
+});
diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/stats.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/stats.schema.yaml
new file mode 100644
index 0000000000000..8d8327d5e6468
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/stats.schema.yaml
@@ -0,0 +1,41 @@
+openapi: 3.0.0
+
+info:
+ title: Get the entity store engine stats
+ version: '2023-10-31'
+paths:
+ /api/entity_store/engines/{entityType}/stats:
+ post:
+ x-labels: [ess, serverless]
+ x-codegen-enabled: true
+ operationId: GetEntityStoreStats
+ summary: Get the Entity Store engine stats
+ parameters:
+ - name: entityType
+ in: path
+ required: true
+ schema:
+ $ref: '../common.schema.yaml#/components/schemas/EntityType'
+ description: The entity type of the store (either 'user' or 'host').
+ responses:
+ '200':
+ description: Successful response
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ type:
+ $ref : '../common.schema.yaml#/components/schemas/EntityType'
+ indexPattern:
+ $ref : '../common.schema.yaml#/components/schemas/IndexPattern'
+ status:
+ $ref : '../common.schema.yaml#/components/schemas/EngineStatus'
+ transforms:
+ type: array
+ items:
+ type: object
+ indices:
+ type: array
+ items:
+ type: object
diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/stop.gen.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/stop.gen.ts
new file mode 100644
index 0000000000000..ff3ef7a2f3eac
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/stop.gen.ts
@@ -0,0 +1,33 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+/*
+ * NOTICE: Do not edit this file manually.
+ * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
+ *
+ * info:
+ * title: Stop the entity store engine
+ * version: 2023-10-31
+ */
+
+import { z } from '@kbn/zod';
+
+import { EntityType } from '../common.gen';
+
+export type StopEntityStoreRequestParams = z.infer;
+export const StopEntityStoreRequestParams = z.object({
+ /**
+ * The entity type of the store (either 'user' or 'host').
+ */
+ entityType: EntityType,
+});
+export type StopEntityStoreRequestParamsInput = z.input;
+
+export type StopEntityStoreResponse = z.infer;
+export const StopEntityStoreResponse = z.object({
+ stopped: z.boolean().optional(),
+});
diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/stop.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/stop.schema.yaml
new file mode 100644
index 0000000000000..214f803a76e34
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/stop.schema.yaml
@@ -0,0 +1,30 @@
+openapi: 3.0.0
+
+info:
+ title: Stop the entity store engine
+ version: '2023-10-31'
+paths:
+ /api/entity_store/engines/{entityType}/stop:
+ post:
+ x-labels: [ess, serverless]
+ x-codegen-enabled: true
+ operationId: StopEntityStore
+ summary: Stop the Entity Store engine
+ parameters:
+ - name: entityType
+ in: path
+ required: true
+ schema:
+ $ref: '../common.schema.yaml#/components/schemas/EntityType'
+ description: The entity type of the store (either 'user' or 'host').
+ responses:
+ '200':
+ description: Successful response
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ stopped:
+ type: boolean
+
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 edd0bfe89fc8c..c08f807d4926b 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
@@ -242,6 +242,33 @@ import type {
InternalUploadAssetCriticalityRecordsResponse,
UploadAssetCriticalityRecordsResponse,
} from './entity_analytics/asset_criticality/upload_asset_criticality_csv.gen';
+import type {
+ DeleteEntityStoreRequestQueryInput,
+ DeleteEntityStoreRequestParamsInput,
+ DeleteEntityStoreResponse,
+} from './entity_analytics/entity_store/engine/delete.gen';
+import type {
+ GetEntityStoreEngineRequestParamsInput,
+ GetEntityStoreEngineResponse,
+} from './entity_analytics/entity_store/engine/get.gen';
+import type {
+ InitEntityStoreRequestParamsInput,
+ InitEntityStoreRequestBodyInput,
+ InitEntityStoreResponse,
+} from './entity_analytics/entity_store/engine/init.gen';
+import type { ListEntityStoreEnginesResponse } from './entity_analytics/entity_store/engine/list.gen';
+import type {
+ StartEntityStoreRequestParamsInput,
+ StartEntityStoreResponse,
+} from './entity_analytics/entity_store/engine/start.gen';
+import type {
+ GetEntityStoreStatsRequestParamsInput,
+ GetEntityStoreStatsResponse,
+} from './entity_analytics/entity_store/engine/stats.gen';
+import type {
+ StopEntityStoreRequestParamsInput,
+ StopEntityStoreResponse,
+} from './entity_analytics/entity_store/engine/stop.gen';
import type { DisableRiskEngineResponse } from './entity_analytics/risk_engine/engine_disable_route.gen';
import type { EnableRiskEngineResponse } from './entity_analytics/risk_engine/engine_enable_route.gen';
import type { InitRiskEngineResponse } from './entity_analytics/risk_engine/engine_init_route.gen';
@@ -620,6 +647,20 @@ Migrations are initiated per index. While the process is neither destructive nor
})
.catch(catchAxiosErrorFormatAndThrow);
}
+ async deleteEntityStore(props: DeleteEntityStoreProps) {
+ this.log.info(`${new Date().toISOString()} Calling API DeleteEntityStore`);
+ return this.kbnClient
+ .request({
+ path: replaceParams('/api/entity_store/engines/{entityType}', props.params),
+ headers: {
+ [ELASTIC_HTTP_VERSION_HEADER]: '2023-10-31',
+ },
+ method: 'DELETE',
+
+ query: props.query,
+ })
+ .catch(catchAxiosErrorFormatAndThrow);
+ }
async deleteNote(props: DeleteNoteProps) {
this.log.info(`${new Date().toISOString()} Calling API DeleteNote`);
return this.kbnClient
@@ -1155,6 +1196,30 @@ finalize it.
})
.catch(catchAxiosErrorFormatAndThrow);
}
+ async getEntityStoreEngine(props: GetEntityStoreEngineProps) {
+ this.log.info(`${new Date().toISOString()} Calling API GetEntityStoreEngine`);
+ return this.kbnClient
+ .request({
+ path: replaceParams('/api/entity_store/engines/{entityType}', props.params),
+ headers: {
+ [ELASTIC_HTTP_VERSION_HEADER]: '2023-10-31',
+ },
+ method: 'GET',
+ })
+ .catch(catchAxiosErrorFormatAndThrow);
+ }
+ async getEntityStoreStats(props: GetEntityStoreStatsProps) {
+ this.log.info(`${new Date().toISOString()} Calling API GetEntityStoreStats`);
+ return this.kbnClient
+ .request({
+ path: replaceParams('/api/entity_store/engines/{entityType}/stats', props.params),
+ headers: {
+ [ELASTIC_HTTP_VERSION_HEADER]: '2023-10-31',
+ },
+ method: 'POST',
+ })
+ .catch(catchAxiosErrorFormatAndThrow);
+ }
/**
* Gets notes
*/
@@ -1311,6 +1376,19 @@ finalize it.
})
.catch(catchAxiosErrorFormatAndThrow);
}
+ async initEntityStore(props: InitEntityStoreProps) {
+ this.log.info(`${new Date().toISOString()} Calling API InitEntityStore`);
+ return this.kbnClient
+ .request({
+ path: replaceParams('/api/entity_store/engines/{entityType}/init', props.params),
+ headers: {
+ [ELASTIC_HTTP_VERSION_HEADER]: '2023-10-31',
+ },
+ method: 'POST',
+ body: props.body,
+ })
+ .catch(catchAxiosErrorFormatAndThrow);
+ }
/**
* Initializes the Risk Engine by creating the necessary indices and mappings, removing old transforms, and starting the new risk engine
*/
@@ -1367,6 +1445,18 @@ finalize it.
})
.catch(catchAxiosErrorFormatAndThrow);
}
+ async listEntityStoreEngines() {
+ this.log.info(`${new Date().toISOString()} Calling API ListEntityStoreEngines`);
+ return this.kbnClient
+ .request({
+ path: '/api/entity_store/engines',
+ headers: {
+ [ELASTIC_HTTP_VERSION_HEADER]: '2023-10-31',
+ },
+ method: 'GET',
+ })
+ .catch(catchAxiosErrorFormatAndThrow);
+ }
/**
* Update specific fields of an existing detection rule using the `rule_id` or `id` field.
*/
@@ -1699,6 +1789,30 @@ detection engine rules.
})
.catch(catchAxiosErrorFormatAndThrow);
}
+ async startEntityStore(props: StartEntityStoreProps) {
+ this.log.info(`${new Date().toISOString()} Calling API StartEntityStore`);
+ return this.kbnClient
+ .request({
+ path: replaceParams('/api/entity_store/engines/{entityType}/start', props.params),
+ headers: {
+ [ELASTIC_HTTP_VERSION_HEADER]: '2023-10-31',
+ },
+ method: 'POST',
+ })
+ .catch(catchAxiosErrorFormatAndThrow);
+ }
+ async stopEntityStore(props: StopEntityStoreProps) {
+ this.log.info(`${new Date().toISOString()} Calling API StopEntityStore`);
+ return this.kbnClient
+ .request({
+ path: replaceParams('/api/entity_store/engines/{entityType}/stop', props.params),
+ headers: {
+ [ELASTIC_HTTP_VERSION_HEADER]: '2023-10-31',
+ },
+ method: 'POST',
+ })
+ .catch(catchAxiosErrorFormatAndThrow);
+ }
/**
* Suggests user profiles.
*/
@@ -1809,6 +1923,10 @@ export interface CreateUpdateProtectionUpdatesNoteProps {
export interface DeleteAssetCriticalityRecordProps {
query: DeleteAssetCriticalityRecordRequestQueryInput;
}
+export interface DeleteEntityStoreProps {
+ query: DeleteEntityStoreRequestQueryInput;
+ params: DeleteEntityStoreRequestParamsInput;
+}
export interface DeleteNoteProps {
body: DeleteNoteRequestBodyInput;
}
@@ -1902,6 +2020,12 @@ export interface GetEndpointSuggestionsProps {
params: GetEndpointSuggestionsRequestParamsInput;
body: GetEndpointSuggestionsRequestBodyInput;
}
+export interface GetEntityStoreEngineProps {
+ params: GetEntityStoreEngineRequestParamsInput;
+}
+export interface GetEntityStoreStatsProps {
+ params: GetEntityStoreStatsRequestParamsInput;
+}
export interface GetNotesProps {
query: GetNotesRequestQueryInput;
}
@@ -1932,6 +2056,10 @@ export interface ImportRulesProps {
export interface ImportTimelinesProps {
body: ImportTimelinesRequestBodyInput;
}
+export interface InitEntityStoreProps {
+ params: InitEntityStoreRequestParamsInput;
+ body: InitEntityStoreRequestBodyInput;
+}
export interface InstallPrepackedTimelinesProps {
body: InstallPrepackedTimelinesRequestBodyInput;
}
@@ -1984,6 +2112,12 @@ export interface SetAlertsStatusProps {
export interface SetAlertTagsProps {
body: SetAlertTagsRequestBodyInput;
}
+export interface StartEntityStoreProps {
+ params: StartEntityStoreRequestParamsInput;
+}
+export interface StopEntityStoreProps {
+ params: StopEntityStoreRequestParamsInput;
+}
export interface SuggestUserProfilesProps {
query: SuggestUserProfilesRequestQueryInput;
}
diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts
index 7d3edafedd1a9..e11965653526f 100644
--- a/x-pack/plugins/security_solution/common/experimental_features.ts
+++ b/x-pack/plugins/security_solution/common/experimental_features.ts
@@ -223,6 +223,11 @@ export const allowedExperimentalValues = Object.freeze({
* Enables the new data ingestion hub
*/
dataIngestionHubEnabled: false,
+
+ /**
+ * Enables the new Entity Store engine routes
+ */
+ entityStoreEnabled: false,
});
type ExperimentalConfigKeys = Array;
diff --git a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml
index 7ee7a3748df4b..9e56395f2af75 100644
--- a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml
+++ b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml
@@ -256,6 +256,187 @@ paths:
summary: List Asset Criticality Records
tags:
- Security Solution Entity Analytics API
+ /api/entity_store/engines:
+ get:
+ operationId: ListEntityStoreEngines
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ count:
+ type: integer
+ engines:
+ items:
+ $ref: '#/components/schemas/EngineDescriptor'
+ type: array
+ description: Successful response
+ summary: List the Entity Store engines
+ tags:
+ - Security Solution Entity Analytics API
+ '/api/entity_store/engines/{entityType}':
+ delete:
+ operationId: DeleteEntityStore
+ parameters:
+ - description: The entity type of the store (either 'user' or 'host').
+ in: path
+ name: entityType
+ required: true
+ schema:
+ $ref: '#/components/schemas/EntityType'
+ - description: Control flag to also delete the entity data.
+ in: query
+ name: data
+ required: false
+ schema:
+ type: boolean
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ deleted:
+ type: boolean
+ description: Successful response
+ summary: Delete the Entity Store engine
+ tags:
+ - Security Solution Entity Analytics API
+ get:
+ operationId: GetEntityStoreEngine
+ parameters:
+ - description: The entity type of the store (either 'user' or 'host').
+ in: path
+ name: entityType
+ required: true
+ schema:
+ $ref: '#/components/schemas/EntityType'
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/EngineDescriptor'
+ description: Successful response
+ summary: Get the Entity Store engine
+ tags:
+ - Security Solution Entity Analytics API
+ '/api/entity_store/engines/{entityType}/init':
+ post:
+ operationId: InitEntityStore
+ parameters:
+ - description: The entity type of the store (either 'user' or 'host').
+ in: path
+ name: entityType
+ required: true
+ schema:
+ $ref: '#/components/schemas/EntityType'
+ requestBody:
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ filter:
+ type: string
+ indexPattern:
+ $ref: '#/components/schemas/IndexPattern'
+ description: Schema for the engine initialization
+ required: true
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/EngineDescriptor'
+ description: Successful response
+ summary: Initialize the Entity Store
+ tags:
+ - Security Solution Entity Analytics API
+ '/api/entity_store/engines/{entityType}/start':
+ post:
+ operationId: StartEntityStore
+ parameters:
+ - description: The entity type of the store (either 'user' or 'host').
+ in: path
+ name: entityType
+ required: true
+ schema:
+ $ref: '#/components/schemas/EntityType'
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ started:
+ type: boolean
+ description: Successful response
+ summary: Start the Entity Store engine
+ tags:
+ - Security Solution Entity Analytics API
+ '/api/entity_store/engines/{entityType}/stats':
+ post:
+ operationId: GetEntityStoreStats
+ parameters:
+ - description: The entity type of the store (either 'user' or 'host').
+ in: path
+ name: entityType
+ required: true
+ schema:
+ $ref: '#/components/schemas/EntityType'
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ indexPattern:
+ $ref: '#/components/schemas/IndexPattern'
+ indices:
+ items:
+ type: object
+ type: array
+ status:
+ $ref: '#/components/schemas/EngineStatus'
+ transforms:
+ items:
+ type: object
+ type: array
+ type:
+ $ref: '#/components/schemas/EntityType'
+ description: Successful response
+ summary: Get the Entity Store engine stats
+ tags:
+ - Security Solution Entity Analytics API
+ '/api/entity_store/engines/{entityType}/stop':
+ post:
+ operationId: StopEntityStore
+ parameters:
+ - description: The entity type of the store (either 'user' or 'host').
+ in: path
+ name: entityType
+ required: true
+ schema:
+ $ref: '#/components/schemas/EntityType'
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ stopped:
+ type: boolean
+ description: Successful response
+ summary: Stop the Entity Store engine
+ tags:
+ - Security Solution Entity Analytics API
/api/risk_score/engine/schedule_now:
post:
operationId: ScheduleRiskEngineNow
@@ -351,11 +532,35 @@ components:
$ref: '#/components/schemas/AssetCriticalityLevel'
required:
- criticality_level
+ EngineDescriptor:
+ type: object
+ properties:
+ filter:
+ type: string
+ indexPattern:
+ $ref: '#/components/schemas/IndexPattern'
+ status:
+ $ref: '#/components/schemas/EngineStatus'
+ type:
+ $ref: '#/components/schemas/EntityType'
+ EngineStatus:
+ enum:
+ - installing
+ - started
+ - stopped
+ type: string
+ EntityType:
+ enum:
+ - user
+ - host
+ type: string
IdField:
enum:
- host.name
- user.name
type: string
+ IndexPattern:
+ type: string
RiskEngineScheduleNowErrorResponse:
type: object
properties:
diff --git a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml
index 845b4ced91545..754c8f94d1c63 100644
--- a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml
+++ b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml
@@ -256,6 +256,187 @@ paths:
summary: List Asset Criticality Records
tags:
- Security Solution Entity Analytics API
+ /api/entity_store/engines:
+ get:
+ operationId: ListEntityStoreEngines
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ count:
+ type: integer
+ engines:
+ items:
+ $ref: '#/components/schemas/EngineDescriptor'
+ type: array
+ description: Successful response
+ summary: List the Entity Store engines
+ tags:
+ - Security Solution Entity Analytics API
+ '/api/entity_store/engines/{entityType}':
+ delete:
+ operationId: DeleteEntityStore
+ parameters:
+ - description: The entity type of the store (either 'user' or 'host').
+ in: path
+ name: entityType
+ required: true
+ schema:
+ $ref: '#/components/schemas/EntityType'
+ - description: Control flag to also delete the entity data.
+ in: query
+ name: data
+ required: false
+ schema:
+ type: boolean
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ deleted:
+ type: boolean
+ description: Successful response
+ summary: Delete the Entity Store engine
+ tags:
+ - Security Solution Entity Analytics API
+ get:
+ operationId: GetEntityStoreEngine
+ parameters:
+ - description: The entity type of the store (either 'user' or 'host').
+ in: path
+ name: entityType
+ required: true
+ schema:
+ $ref: '#/components/schemas/EntityType'
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/EngineDescriptor'
+ description: Successful response
+ summary: Get the Entity Store engine
+ tags:
+ - Security Solution Entity Analytics API
+ '/api/entity_store/engines/{entityType}/init':
+ post:
+ operationId: InitEntityStore
+ parameters:
+ - description: The entity type of the store (either 'user' or 'host').
+ in: path
+ name: entityType
+ required: true
+ schema:
+ $ref: '#/components/schemas/EntityType'
+ requestBody:
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ filter:
+ type: string
+ indexPattern:
+ $ref: '#/components/schemas/IndexPattern'
+ description: Schema for the engine initialization
+ required: true
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/EngineDescriptor'
+ description: Successful response
+ summary: Initialize the Entity Store
+ tags:
+ - Security Solution Entity Analytics API
+ '/api/entity_store/engines/{entityType}/start':
+ post:
+ operationId: StartEntityStore
+ parameters:
+ - description: The entity type of the store (either 'user' or 'host').
+ in: path
+ name: entityType
+ required: true
+ schema:
+ $ref: '#/components/schemas/EntityType'
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ started:
+ type: boolean
+ description: Successful response
+ summary: Start the Entity Store engine
+ tags:
+ - Security Solution Entity Analytics API
+ '/api/entity_store/engines/{entityType}/stats':
+ post:
+ operationId: GetEntityStoreStats
+ parameters:
+ - description: The entity type of the store (either 'user' or 'host').
+ in: path
+ name: entityType
+ required: true
+ schema:
+ $ref: '#/components/schemas/EntityType'
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ indexPattern:
+ $ref: '#/components/schemas/IndexPattern'
+ indices:
+ items:
+ type: object
+ type: array
+ status:
+ $ref: '#/components/schemas/EngineStatus'
+ transforms:
+ items:
+ type: object
+ type: array
+ type:
+ $ref: '#/components/schemas/EntityType'
+ description: Successful response
+ summary: Get the Entity Store engine stats
+ tags:
+ - Security Solution Entity Analytics API
+ '/api/entity_store/engines/{entityType}/stop':
+ post:
+ operationId: StopEntityStore
+ parameters:
+ - description: The entity type of the store (either 'user' or 'host').
+ in: path
+ name: entityType
+ required: true
+ schema:
+ $ref: '#/components/schemas/EntityType'
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ stopped:
+ type: boolean
+ description: Successful response
+ summary: Stop the Entity Store engine
+ tags:
+ - Security Solution Entity Analytics API
/api/risk_score/engine/schedule_now:
post:
operationId: ScheduleRiskEngineNow
@@ -351,11 +532,35 @@ components:
$ref: '#/components/schemas/AssetCriticalityLevel'
required:
- criticality_level
+ EngineDescriptor:
+ type: object
+ properties:
+ filter:
+ type: string
+ indexPattern:
+ $ref: '#/components/schemas/IndexPattern'
+ status:
+ $ref: '#/components/schemas/EngineStatus'
+ type:
+ $ref: '#/components/schemas/EntityType'
+ EngineStatus:
+ enum:
+ - installing
+ - started
+ - stopped
+ type: string
+ EntityType:
+ enum:
+ - user
+ - host
+ type: string
IdField:
enum:
- host.name
- user.name
type: string
+ IndexPattern:
+ type: string
RiskEngineScheduleNowErrorResponse:
type: object
properties:
diff --git a/x-pack/plugins/security_solution/kibana.jsonc b/x-pack/plugins/security_solution/kibana.jsonc
index f682ca478a17f..e5840a6662e79 100644
--- a/x-pack/plugins/security_solution/kibana.jsonc
+++ b/x-pack/plugins/security_solution/kibana.jsonc
@@ -53,7 +53,8 @@
"notifications",
"savedSearch",
"unifiedDocViewer",
- "charts"
+ "charts",
+ "entityManager"
],
"optionalPlugins": [
"cloudExperiments",
@@ -87,4 +88,4 @@
"common"
]
}
-}
+}
\ No newline at end of file
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts
index b39cba0cf4952..a5e0c8c60b1fc 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts
@@ -36,6 +36,7 @@ import { getEndpointAuthzInitialStateMock } from '../../../../../common/endpoint
import type { EndpointAuthz } from '../../../../../common/endpoint/types/authz';
import { riskEngineDataClientMock } from '../../../entity_analytics/risk_engine/risk_engine_data_client.mock';
import { riskScoreDataClientMock } from '../../../entity_analytics/risk_score/risk_score_data_client.mock';
+import { entityStoreDataClientMock } from '../../../entity_analytics/entity_store/entity_store_data_client.mock';
import { assetCriticalityDataClientMock } from '../../../entity_analytics/asset_criticality/asset_criticality_data_client.mock';
import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks';
import { detectionRulesClientMock } from '../../rule_management/logic/detection_rules_client/__mocks__/detection_rules_client';
@@ -72,6 +73,7 @@ export const createMockClients = () => {
riskEngineDataClient: riskEngineDataClientMock.create(),
riskScoreDataClient: riskScoreDataClientMock.create(),
assetCriticalityDataClient: assetCriticalityDataClientMock.create(),
+ entityStoreDataClient: entityStoreDataClientMock.create(),
internalFleetServices: {
packages: packageServiceMock.createClient(),
@@ -159,6 +161,7 @@ const createSecuritySolutionRequestContextMock = (
getRiskScoreDataClient: jest.fn(() => clients.riskScoreDataClient),
getAssetCriticalityDataClient: jest.fn(() => clients.assetCriticalityDataClient),
getAuditLogger: jest.fn(() => mockAuditLogger),
+ getEntityStoreDataClient: jest.fn(() => clients.entityStoreDataClient),
};
};
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/constants.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/constants.ts
new file mode 100644
index 0000000000000..ce5a61fa7e6c9
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/constants.ts
@@ -0,0 +1,21 @@
+/*
+ * 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 { EngineStatus } from '../../../../common/api/entity_analytics/entity_store/common.gen';
+import { DEFAULT_INDEX_PATTERN } from '../../../../common/constants';
+
+/**
+ * Default index pattern for entity store
+ * This is the same as the default index pattern for the SIEM app but might diverge in the future
+ */
+export const ENTITY_STORE_DEFAULT_SOURCE_INDICES = DEFAULT_INDEX_PATTERN;
+
+export const ENGINE_STATUS: Record, EngineStatus> = {
+ INSTALLING: 'installing',
+ STARTED: 'started',
+ STOPPED: 'stopped',
+};
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/definition.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/definition.ts
new file mode 100644
index 0000000000000..32859b9841e7f
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/definition.ts
@@ -0,0 +1,56 @@
+/*
+ * 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 { entityDefinitionSchema, type EntityDefinition } from '@kbn/entities-schema';
+import { ENTITY_STORE_DEFAULT_SOURCE_INDICES } from './constants';
+
+export const HOST_ENTITY_DEFINITION: EntityDefinition = entityDefinitionSchema.parse({
+ id: 'ea_host_entity_store',
+ name: 'EA Host Store',
+ type: 'host',
+ indexPatterns: ENTITY_STORE_DEFAULT_SOURCE_INDICES,
+ identityFields: ['host.name'],
+ displayNameTemplate: '{{host.name}}',
+ metadata: [
+ 'host.domain',
+ 'host.hostname',
+ 'host.id',
+ 'host.ip',
+ 'host.mac',
+ 'host.name',
+ 'host.type',
+ 'host.architecture',
+ ],
+ history: {
+ timestampField: '@timestamp',
+ interval: '1m',
+ },
+ version: '1.0.0',
+});
+
+export const USER_ENTITY_DEFINITION: EntityDefinition = entityDefinitionSchema.parse({
+ id: 'ea_user_entity_store',
+ name: 'EA User Store',
+ type: 'user',
+ indexPatterns: ENTITY_STORE_DEFAULT_SOURCE_INDICES,
+ identityFields: ['user.name'],
+ displayNameTemplate: '{{user.name}}',
+ metadata: [
+ 'user.domain',
+ 'user.email',
+ 'user.full_name',
+ 'user.hash',
+ 'user.id',
+ 'user.name',
+ 'user.roles',
+ ],
+ history: {
+ timestampField: '@timestamp',
+ interval: '1m',
+ },
+ version: '1.0.0',
+});
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.mock.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.mock.ts
new file mode 100644
index 0000000000000..095565343e130
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.mock.ts
@@ -0,0 +1,20 @@
+/*
+ * 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 { EntityStoreDataClient } from './entity_store_data_client';
+
+const createEntityStoreDataClientMock = () =>
+ ({
+ init: jest.fn(),
+ start: jest.fn(),
+ stop: jest.fn(),
+ get: jest.fn(),
+ list: jest.fn(),
+ delete: jest.fn(),
+ } as unknown as jest.Mocked);
+
+export const entityStoreDataClientMock = { create: createEntityStoreDataClientMock };
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts
new file mode 100644
index 0000000000000..cb4d59139a25f
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts
@@ -0,0 +1,120 @@
+/*
+ * 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 { Logger, ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server';
+import type { EntityClient } from '@kbn/entityManager-plugin/server/lib/entity_client';
+
+import type {
+ InitEntityStoreRequestBody,
+ InitEntityStoreResponse,
+} from '../../../../common/api/entity_analytics/entity_store/engine/init.gen';
+import type {
+ EngineDescriptor,
+ EntityType,
+} from '../../../../common/api/entity_analytics/entity_store/common.gen';
+import { entityEngineDescriptorTypeName } from './saved_object';
+import { EngineDescriptorClient } from './saved_object/engine_descriptor';
+import { getEntityDefinition } from './utils/utils';
+import { ENGINE_STATUS } from './constants';
+
+interface EntityStoreClientOpts {
+ logger: Logger;
+ esClient: ElasticsearchClient;
+ entityClient: EntityClient;
+ namespace: string;
+ soClient: SavedObjectsClientContract;
+}
+
+export class EntityStoreDataClient {
+ private engineClient: EngineDescriptorClient;
+ constructor(private readonly options: EntityStoreClientOpts) {
+ this.engineClient = new EngineDescriptorClient(options.soClient);
+ }
+
+ public async init(
+ entityType: EntityType,
+ { indexPattern = '', filter = '' }: InitEntityStoreRequestBody
+ ): Promise {
+ const definition = getEntityDefinition(entityType);
+
+ this.options.logger.info(`Initializing entity store for ${entityType}`);
+
+ const descriptor = await this.engineClient.init(entityType, definition, filter);
+ await this.options.entityClient.createEntityDefinition({
+ definition: {
+ ...definition,
+ filter,
+ indexPatterns: indexPattern
+ ? [...definition.indexPatterns, ...indexPattern.split(',')]
+ : definition.indexPatterns,
+ },
+ });
+ const updated = await this.engineClient.update(definition.id, ENGINE_STATUS.STARTED);
+
+ return { ...descriptor, ...updated };
+ }
+
+ public async start(entityType: EntityType) {
+ const definition = getEntityDefinition(entityType);
+
+ const descriptor = await this.engineClient.get(entityType);
+
+ if (descriptor.status !== ENGINE_STATUS.STOPPED) {
+ throw new Error(
+ `Cannot start Entity engine for ${entityType} when current status is: ${descriptor.status}`
+ );
+ }
+
+ this.options.logger.info(`Starting entity store for ${entityType}`);
+ await this.options.entityClient.startEntityDefinition(definition);
+
+ return this.engineClient.update(definition.id, ENGINE_STATUS.STARTED);
+ }
+
+ public async stop(entityType: EntityType) {
+ const definition = getEntityDefinition(entityType);
+
+ const descriptor = await this.engineClient.get(entityType);
+
+ if (descriptor.status !== ENGINE_STATUS.STARTED) {
+ throw new Error(
+ `Cannot stop Entity engine for ${entityType} when current status is: ${descriptor.status}`
+ );
+ }
+
+ this.options.logger.info(`Stopping entity store for ${entityType}`);
+ await this.options.entityClient.stopEntityDefinition(definition);
+
+ return this.engineClient.update(definition.id, ENGINE_STATUS.STOPPED);
+ }
+
+ public async get(entityType: EntityType) {
+ return this.engineClient.get(entityType);
+ }
+
+ public async list() {
+ return this.options.soClient
+ .find({
+ type: entityEngineDescriptorTypeName,
+ })
+ .then(({ saved_objects: engines }) => ({
+ engines: engines.map((engine) => engine.attributes),
+ count: engines.length,
+ }));
+ }
+
+ public async delete(entityType: EntityType, deleteData: boolean) {
+ const { id } = getEntityDefinition(entityType);
+
+ this.options.logger.info(`Deleting entity store for ${entityType}`);
+
+ await this.options.entityClient.deleteEntityDefinition({ id, deleteData });
+ await this.engineClient.delete(id);
+
+ return { deleted: true };
+ }
+}
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/delete.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/delete.ts
new file mode 100644
index 0000000000000..44352cfa47c57
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/delete.ts
@@ -0,0 +1,64 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { IKibanaResponse, Logger } from '@kbn/core/server';
+import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils';
+import { transformError } from '@kbn/securitysolution-es-utils';
+import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
+
+import type { DeleteEntityStoreResponse } from '../../../../../common/api/entity_analytics/entity_store/engine/delete.gen';
+import {
+ DeleteEntityStoreRequestQuery,
+ DeleteEntityStoreRequestParams,
+} from '../../../../../common/api/entity_analytics/entity_store/engine/delete.gen';
+import { API_VERSIONS, APP_ID } from '../../../../../common/constants';
+import type { EntityAnalyticsRoutesDeps } from '../../types';
+
+export const deleteEntityEngineRoute = (
+ router: EntityAnalyticsRoutesDeps['router'],
+ logger: Logger
+) => {
+ router.versioned
+ .delete({
+ access: 'public',
+ path: '/api/entity_store/engines/{entityType}',
+ options: {
+ tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`],
+ },
+ })
+ .addVersion(
+ {
+ version: API_VERSIONS.public.v1,
+ validate: {
+ request: {
+ query: buildRouteValidationWithZod(DeleteEntityStoreRequestQuery),
+ params: buildRouteValidationWithZod(DeleteEntityStoreRequestParams),
+ },
+ },
+ },
+
+ async (context, request, response): Promise> => {
+ const siemResponse = buildSiemResponse(response);
+
+ try {
+ const secSol = await context.securitySolution;
+ const body = await secSol
+ .getEntityStoreDataClient()
+ .delete(request.params.entityType, !!request.query.data);
+
+ return response.ok({ body });
+ } catch (e) {
+ logger.error('Error in DeleteEntityStore:', e);
+ const error = transformError(e);
+ return siemResponse.error({
+ statusCode: error.statusCode,
+ body: error.message,
+ });
+ }
+ }
+ );
+};
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/get.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/get.ts
new file mode 100644
index 0000000000000..79a74303c49c2
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/get.ts
@@ -0,0 +1,62 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { IKibanaResponse, Logger } from '@kbn/core/server';
+import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils';
+import { transformError } from '@kbn/securitysolution-es-utils';
+import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
+
+import type { GetEntityStoreEngineResponse } from '../../../../../common/api/entity_analytics/entity_store/engine/get.gen';
+import { GetEntityStoreEngineRequestParams } from '../../../../../common/api/entity_analytics/entity_store/engine/get.gen';
+import { API_VERSIONS, APP_ID } from '../../../../../common/constants';
+import type { EntityAnalyticsRoutesDeps } from '../../types';
+
+export const getEntityEngineRoute = (
+ router: EntityAnalyticsRoutesDeps['router'],
+ logger: Logger
+) => {
+ router.versioned
+ .get({
+ access: 'public',
+ path: '/api/entity_store/engines/{entityType}',
+ options: {
+ tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`],
+ },
+ })
+ .addVersion(
+ {
+ version: API_VERSIONS.public.v1,
+ validate: {
+ request: {
+ params: buildRouteValidationWithZod(GetEntityStoreEngineRequestParams),
+ },
+ },
+ },
+
+ async (
+ context,
+ request,
+ response
+ ): Promise> => {
+ const siemResponse = buildSiemResponse(response);
+
+ try {
+ const secSol = await context.securitySolution;
+ const body = await secSol.getEntityStoreDataClient().get(request.params.entityType);
+
+ return response.ok({ body });
+ } catch (e) {
+ logger.error('Error in GetEntityStoreEngine:', e);
+ const error = transformError(e);
+ return siemResponse.error({
+ statusCode: error.statusCode,
+ body: error.message,
+ });
+ }
+ }
+ );
+};
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/index.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/index.ts
new file mode 100644
index 0000000000000..52aa6b22c2df8
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/index.ts
@@ -0,0 +1,8 @@
+/*
+ * 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 { registerEntityStoreRoutes } from './register_entity_store_routes';
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/init.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/init.ts
new file mode 100644
index 0000000000000..6159cd584b06d
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/init.ts
@@ -0,0 +1,65 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { IKibanaResponse, Logger } from '@kbn/core/server';
+import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils';
+import { transformError } from '@kbn/securitysolution-es-utils';
+import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
+
+import type { InitEntityStoreResponse } from '../../../../../common/api/entity_analytics/entity_store/engine/init.gen';
+import {
+ InitEntityStoreRequestBody,
+ InitEntityStoreRequestParams,
+} from '../../../../../common/api/entity_analytics/entity_store/engine/init.gen';
+import { API_VERSIONS, APP_ID } from '../../../../../common/constants';
+import type { EntityAnalyticsRoutesDeps } from '../../types';
+
+export const initEntityEngineRoute = (
+ router: EntityAnalyticsRoutesDeps['router'],
+ logger: Logger
+) => {
+ router.versioned
+ .post({
+ access: 'public',
+ path: '/api/entity_store/engines/{entityType}/init',
+ options: {
+ tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`],
+ },
+ })
+ .addVersion(
+ {
+ version: API_VERSIONS.public.v1,
+ validate: {
+ request: {
+ params: buildRouteValidationWithZod(InitEntityStoreRequestParams),
+ body: buildRouteValidationWithZod(InitEntityStoreRequestBody),
+ },
+ },
+ },
+
+ async (context, request, response): Promise> => {
+ const siemResponse = buildSiemResponse(response);
+
+ try {
+ const secSol = await context.securitySolution;
+
+ const body: InitEntityStoreResponse = await secSol
+ .getEntityStoreDataClient()
+ .init(request.params.entityType, request.body);
+
+ return response.ok({ body });
+ } catch (e) {
+ logger.error('Error in InitEntityStore:', e);
+ const error = transformError(e);
+ return siemResponse.error({
+ statusCode: error.statusCode,
+ body: error.message,
+ });
+ }
+ }
+ );
+};
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/list.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/list.ts
new file mode 100644
index 0000000000000..53d9a8521ce00
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/list.ts
@@ -0,0 +1,57 @@
+/*
+ * 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 { IKibanaResponse, Logger } from '@kbn/core/server';
+import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils';
+import { transformError } from '@kbn/securitysolution-es-utils';
+
+import type { ListEntityStoreEnginesResponse } from '../../../../../common/api/entity_analytics/entity_store/engine/list.gen';
+import { API_VERSIONS, APP_ID } from '../../../../../common/constants';
+
+import type { EntityAnalyticsRoutesDeps } from '../../types';
+
+export const listEntityEnginesRoute = (
+ router: EntityAnalyticsRoutesDeps['router'],
+ logger: Logger
+) => {
+ router.versioned
+ .get({
+ access: 'public',
+ path: '/api/entity_store/engines',
+ options: {
+ tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`],
+ },
+ })
+ .addVersion(
+ {
+ version: API_VERSIONS.public.v1,
+ validate: {},
+ },
+
+ async (
+ context,
+ request,
+ response
+ ): Promise> => {
+ const siemResponse = buildSiemResponse(response);
+
+ try {
+ const secSol = await context.securitySolution;
+ const body = await secSol.getEntityStoreDataClient().list();
+
+ return response.ok({ body });
+ } catch (e) {
+ logger.error('Error in ListEntityStoreEngines:', e);
+ const error = transformError(e);
+ return siemResponse.error({
+ statusCode: error.statusCode,
+ body: error.message,
+ });
+ }
+ }
+ );
+};
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/register_entity_store_routes.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/register_entity_store_routes.ts
new file mode 100644
index 0000000000000..b78316b02c91e
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/register_entity_store_routes.ts
@@ -0,0 +1,23 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { EntityAnalyticsRoutesDeps } from '../../types';
+import { deleteEntityEngineRoute } from './delete';
+import { getEntityEngineRoute } from './get';
+import { initEntityEngineRoute } from './init';
+import { listEntityEnginesRoute } from './list';
+import { startEntityEngineRoute } from './start';
+import { stopEntityEngineRoute } from './stop';
+
+export const registerEntityStoreRoutes = ({ router, logger }: EntityAnalyticsRoutesDeps) => {
+ initEntityEngineRoute(router, logger);
+ startEntityEngineRoute(router, logger);
+ stopEntityEngineRoute(router, logger);
+ deleteEntityEngineRoute(router, logger);
+ getEntityEngineRoute(router, logger);
+ listEntityEnginesRoute(router, logger);
+};
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/start.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/start.ts
new file mode 100644
index 0000000000000..6ec6674a5473d
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/start.ts
@@ -0,0 +1,59 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { IKibanaResponse, Logger } from '@kbn/core/server';
+import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils';
+import { transformError } from '@kbn/securitysolution-es-utils';
+import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
+
+import type { StartEntityStoreResponse } from '../../../../../common/api/entity_analytics/entity_store/engine/start.gen';
+import { StartEntityStoreRequestParams } from '../../../../../common/api/entity_analytics/entity_store/engine/start.gen';
+import { API_VERSIONS, APP_ID } from '../../../../../common/constants';
+import type { EntityAnalyticsRoutesDeps } from '../../types';
+import { ENGINE_STATUS } from '../constants';
+
+export const startEntityEngineRoute = (
+ router: EntityAnalyticsRoutesDeps['router'],
+ logger: Logger
+) => {
+ router.versioned
+ .post({
+ access: 'public',
+ path: '/api/entity_store/engines/{entityType}/start',
+ options: {
+ tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`],
+ },
+ })
+ .addVersion(
+ {
+ version: API_VERSIONS.public.v1,
+ validate: {
+ request: {
+ params: buildRouteValidationWithZod(StartEntityStoreRequestParams),
+ },
+ },
+ },
+
+ async (context, request, response): Promise> => {
+ const siemResponse = buildSiemResponse(response);
+
+ try {
+ const secSol = await context.securitySolution;
+ const engine = await secSol.getEntityStoreDataClient().start(request.params.entityType);
+
+ return response.ok({ body: { started: engine.status === ENGINE_STATUS.STARTED } });
+ } catch (e) {
+ logger.error('Error in StartEntityStore:', e);
+ const error = transformError(e);
+ return siemResponse.error({
+ statusCode: error.statusCode,
+ body: error.message,
+ });
+ }
+ }
+ );
+};
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/stats.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/stats.ts
new file mode 100644
index 0000000000000..1d7534c17f747
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/stats.ts
@@ -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 { IKibanaResponse, Logger } from '@kbn/core/server';
+import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils';
+import { transformError } from '@kbn/securitysolution-es-utils';
+import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
+
+import type { GetEntityStoreStatsResponse } from '../../../../../common/api/entity_analytics/entity_store/engine/stats.gen';
+import { GetEntityStoreStatsRequestParams } from '../../../../../common/api/entity_analytics/entity_store/engine/stats.gen';
+import { API_VERSIONS, APP_ID } from '../../../../../common/constants';
+import type { EntityAnalyticsRoutesDeps } from '../../types';
+
+export const getEntityEngineStatsRoute = (
+ router: EntityAnalyticsRoutesDeps['router'],
+ logger: Logger
+) => {
+ router.versioned
+ .post({
+ access: 'public',
+ path: '/api/entity_store/engines/{entityType}/stats',
+ options: {
+ tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`],
+ },
+ })
+ .addVersion(
+ {
+ version: API_VERSIONS.public.v1,
+ validate: {
+ request: {
+ params: buildRouteValidationWithZod(GetEntityStoreStatsRequestParams),
+ },
+ },
+ },
+
+ async (context, request, response): Promise> => {
+ const siemResponse = buildSiemResponse(response);
+
+ try {
+ // TODO
+ throw new Error('Not implemented');
+
+ // return response.ok({ body });
+ } catch (e) {
+ logger.error('Error in GetEntityStoreStats:', e);
+ const error = transformError(e);
+ return siemResponse.error({
+ statusCode: error.statusCode,
+ body: error.message,
+ });
+ }
+ }
+ );
+};
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/stop.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/stop.ts
new file mode 100644
index 0000000000000..e1ddb464d1204
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/stop.ts
@@ -0,0 +1,59 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { IKibanaResponse, Logger } from '@kbn/core/server';
+import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils';
+import { transformError } from '@kbn/securitysolution-es-utils';
+import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
+
+import type { StopEntityStoreResponse } from '../../../../../common/api/entity_analytics/entity_store/engine/stop.gen';
+import { StopEntityStoreRequestParams } from '../../../../../common/api/entity_analytics/entity_store/engine/stop.gen';
+import { API_VERSIONS, APP_ID } from '../../../../../common/constants';
+import type { EntityAnalyticsRoutesDeps } from '../../types';
+import { ENGINE_STATUS } from '../constants';
+
+export const stopEntityEngineRoute = (
+ router: EntityAnalyticsRoutesDeps['router'],
+ logger: Logger
+) => {
+ router.versioned
+ .post({
+ access: 'public',
+ path: '/api/entity_store/engines/{entityType}/stop',
+ options: {
+ tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`],
+ },
+ })
+ .addVersion(
+ {
+ version: API_VERSIONS.public.v1,
+ validate: {
+ request: {
+ params: buildRouteValidationWithZod(StopEntityStoreRequestParams),
+ },
+ },
+ },
+
+ async (context, request, response): Promise> => {
+ const siemResponse = buildSiemResponse(response);
+
+ try {
+ const secSol = await context.securitySolution;
+ const engine = await secSol.getEntityStoreDataClient().stop(request.params.entityType);
+
+ return response.ok({ body: { stopped: engine.status === ENGINE_STATUS.STOPPED } });
+ } catch (e) {
+ logger.error('Error in StopEntityStore:', e);
+ const error = transformError(e);
+ return siemResponse.error({
+ statusCode: error.statusCode,
+ body: error.message,
+ });
+ }
+ }
+ );
+};
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/saved_object/engine_descriptor.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/saved_object/engine_descriptor.ts
new file mode 100644
index 0000000000000..9d6a7821a2a9b
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/saved_object/engine_descriptor.ts
@@ -0,0 +1,76 @@
+/*
+ * 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 {
+ SavedObjectsClientContract,
+ SavedObjectsFindResponse,
+} from '@kbn/core-saved-objects-api-server';
+import type { EntityDefinition } from '@kbn/entities-schema';
+import type {
+ EngineDescriptor,
+ EngineStatus,
+ EntityType,
+} from '../../../../../common/api/entity_analytics/entity_store/common.gen';
+
+import { entityEngineDescriptorTypeName } from './engine_descriptor_type';
+import { getByEntityTypeQuery, getEntityDefinition } from '../utils/utils';
+import { ENGINE_STATUS } from '../constants';
+
+export class EngineDescriptorClient {
+ constructor(private readonly soClient: SavedObjectsClientContract) {}
+
+ async init(entityType: EntityType, definition: EntityDefinition, filter: string) {
+ const engineDescriptor = await this.find(entityType);
+
+ if (engineDescriptor.total > 0)
+ throw new Error(`Entity engine for ${entityType} already exists`);
+
+ const { attributes } = await this.soClient.create(
+ entityEngineDescriptorTypeName,
+ {
+ status: ENGINE_STATUS.INSTALLING,
+ type: entityType,
+ indexPattern: definition.indexPatterns.join(','),
+ filter,
+ },
+ { id: definition.id }
+ );
+ return attributes;
+ }
+
+ async update(id: string, status: EngineStatus) {
+ const { attributes } = await this.soClient.update(
+ entityEngineDescriptorTypeName,
+ id,
+ { status },
+ { refresh: 'wait_for' }
+ );
+ return attributes;
+ }
+
+ async find(entityType: EntityType): Promise> {
+ return this.soClient.find({
+ type: entityEngineDescriptorTypeName,
+ filter: getByEntityTypeQuery(entityType),
+ });
+ }
+
+ async get(entityType: EntityType): Promise {
+ const { id } = getEntityDefinition(entityType);
+
+ const { attributes } = await this.soClient.get(
+ entityEngineDescriptorTypeName,
+ id
+ );
+
+ return attributes;
+ }
+
+ async delete(id: string) {
+ return this.soClient.delete(entityEngineDescriptorTypeName, id);
+ }
+}
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/saved_object/engine_descriptor_type.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/saved_object/engine_descriptor_type.ts
new file mode 100644
index 0000000000000..8513dfc018623
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/saved_object/engine_descriptor_type.ts
@@ -0,0 +1,36 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server';
+import type { SavedObjectsType } from '@kbn/core/server';
+
+export const entityEngineDescriptorTypeName = 'entity-engine-status';
+
+export const entityEngineDescriptorTypeMappings: SavedObjectsType['mappings'] = {
+ dynamic: false,
+ properties: {
+ indexPattern: {
+ type: 'keyword',
+ },
+ filter: {
+ type: 'keyword',
+ },
+ type: {
+ type: 'keyword', // EntityType: user | host
+ },
+ status: {
+ type: 'keyword', // EngineStatus: installing | started | stopped
+ },
+ },
+};
+export const entityEngineDescriptorType: SavedObjectsType = {
+ name: entityEngineDescriptorTypeName,
+ indexPattern: SECURITY_SOLUTION_SAVED_OBJECT_INDEX,
+ hidden: false,
+ namespaceType: 'multiple-isolated',
+ mappings: entityEngineDescriptorTypeMappings,
+};
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/saved_object/index.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/saved_object/index.ts
new file mode 100644
index 0000000000000..d86800da1b5be
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/saved_object/index.ts
@@ -0,0 +1,8 @@
+/*
+ * 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 './engine_descriptor_type';
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/utils/utils.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/utils/utils.ts
new file mode 100644
index 0000000000000..864fdb2367eb5
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/utils/utils.ts
@@ -0,0 +1,33 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { SavedObjectsFindResponse } from '@kbn/core-saved-objects-api-server';
+import type {
+ EngineDescriptor,
+ EntityType,
+} from '../../../../../common/api/entity_analytics/entity_store/common.gen';
+import { HOST_ENTITY_DEFINITION, USER_ENTITY_DEFINITION } from '../definition';
+import { entityEngineDescriptorTypeName } from '../saved_object';
+
+export const getEntityDefinition = (entityType: EntityType) => {
+ if (entityType === 'host') return HOST_ENTITY_DEFINITION;
+ if (entityType === 'user') return USER_ENTITY_DEFINITION;
+
+ throw new Error(`Unsupported entity type: ${entityType}`);
+};
+
+export const ensureEngineExists =
+ (entityType: EntityType) => (results: SavedObjectsFindResponse) => {
+ if (results.total === 0) {
+ throw new Error(`Entity engine for ${entityType} does not exist`);
+ }
+ return results.saved_objects[0].attributes;
+ };
+
+export const getByEntityTypeQuery = (entityType: EntityType) => {
+ return `${entityEngineDescriptorTypeName}.attributes.type: ${entityType}`;
+};
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/register_entity_analytics_routes.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/register_entity_analytics_routes.ts
index 31a7ccbb6f30c..b4eb0d36e21fb 100644
--- a/x-pack/plugins/security_solution/server/lib/entity_analytics/register_entity_analytics_routes.ts
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/register_entity_analytics_routes.ts
@@ -9,9 +9,13 @@ import { registerAssetCriticalityRoutes } from './asset_criticality/routes';
import { registerRiskScoreRoutes } from './risk_score/routes';
import { registerRiskEngineRoutes } from './risk_engine/routes';
import type { EntityAnalyticsRoutesDeps } from './types';
+import { registerEntityStoreRoutes } from './entity_store/routes';
export const registerEntityAnalyticsRoutes = (routeDeps: EntityAnalyticsRoutesDeps) => {
registerAssetCriticalityRoutes(routeDeps);
registerRiskScoreRoutes(routeDeps);
registerRiskEngineRoutes(routeDeps);
+ if (routeDeps.config.experimentalFeatures.entityStoreEnabled) {
+ registerEntityStoreRoutes(routeDeps);
+ }
};
diff --git a/x-pack/plugins/security_solution/server/request_context_factory.ts b/x-pack/plugins/security_solution/server/request_context_factory.ts
index 4bda7e0338aa8..6316ed3622841 100644
--- a/x-pack/plugins/security_solution/server/request_context_factory.ts
+++ b/x-pack/plugins/security_solution/server/request_context_factory.ts
@@ -10,6 +10,7 @@ import { memoize } from 'lodash';
import type { Logger, KibanaRequest, RequestHandlerContext } from '@kbn/core/server';
import type { BuildFlavor } from '@kbn/config';
+import { EntityClient } from '@kbn/entityManager-plugin/server/lib/entity_client';
import { DEFAULT_SPACE_ID } from '../common/constants';
import { AppClientFactory } from './client';
import type { ConfigType } from './config';
@@ -31,6 +32,7 @@ import { RiskScoreDataClient } from './lib/entity_analytics/risk_score/risk_scor
import { AssetCriticalityDataClient } from './lib/entity_analytics/asset_criticality';
import { createDetectionRulesClient } from './lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client';
import { buildMlAuthz } from './lib/machine_learning/authz';
+import { EntityStoreDataClient } from './lib/entity_analytics/entity_store/entity_store_data_client';
export interface IRequestContextFactory {
create(
@@ -190,6 +192,22 @@ export class RequestContextFactory implements IRequestContextFactory {
auditLogger: getAuditLogger(),
})
),
+ getEntityStoreDataClient: memoize(() => {
+ const esClient = coreContext.elasticsearch.client.asCurrentUser;
+ const logger = options.logger;
+ const soClient = coreContext.savedObjects.client;
+ return new EntityStoreDataClient({
+ namespace: getSpaceId(),
+ esClient,
+ logger,
+ soClient,
+ entityClient: new EntityClient({
+ esClient,
+ soClient,
+ logger,
+ }),
+ });
+ }),
};
}
}
diff --git a/x-pack/plugins/security_solution/server/saved_objects.ts b/x-pack/plugins/security_solution/server/saved_objects.ts
index 3659b15a04714..9412e62e6315c 100644
--- a/x-pack/plugins/security_solution/server/saved_objects.ts
+++ b/x-pack/plugins/security_solution/server/saved_objects.ts
@@ -15,6 +15,7 @@ import { prebuiltRuleAssetType } from './lib/detection_engine/prebuilt_rules';
import { type as signalsMigrationType } from './lib/detection_engine/migrations/saved_objects';
import { manifestType, unifiedManifestType } from './endpoint/lib/artifacts/saved_object_mappings';
import { riskEngineConfigurationType } from './lib/entity_analytics/risk_engine/saved_object';
+import { entityEngineDescriptorType } from './lib/entity_analytics/entity_store/saved_object';
const types = [
noteType,
@@ -26,6 +27,7 @@ const types = [
unifiedManifestType,
signalsMigrationType,
riskEngineConfigurationType,
+ entityEngineDescriptorType,
protectionUpdatesNoteType,
];
diff --git a/x-pack/plugins/security_solution/server/types.ts b/x-pack/plugins/security_solution/server/types.ts
index 121eb7b1758f4..31e10b70adbcf 100644
--- a/x-pack/plugins/security_solution/server/types.ts
+++ b/x-pack/plugins/security_solution/server/types.ts
@@ -34,6 +34,7 @@ import type { RiskEngineDataClient } from './lib/entity_analytics/risk_engine/ri
import type { RiskScoreDataClient } from './lib/entity_analytics/risk_score/risk_score_data_client';
import type { AssetCriticalityDataClient } from './lib/entity_analytics/asset_criticality';
import type { IDetectionRulesClient } from './lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client_interface';
+import type { EntityStoreDataClient } from './lib/entity_analytics/entity_store/entity_store_data_client';
export { AppClient };
export interface SecuritySolutionApiRequestHandlerContext {
@@ -55,6 +56,7 @@ export interface SecuritySolutionApiRequestHandlerContext {
getRiskEngineDataClient: () => RiskEngineDataClient;
getRiskScoreDataClient: () => RiskScoreDataClient;
getAssetCriticalityDataClient: () => AssetCriticalityDataClient;
+ getEntityStoreDataClient: () => EntityStoreDataClient;
}
export type SecuritySolutionRequestHandlerContext = CustomRequestHandlerContext<{
diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json
index 6ccd61fd34394..8264a50988956 100644
--- a/x-pack/plugins/security_solution/tsconfig.json
+++ b/x-pack/plugins/security_solution/tsconfig.json
@@ -223,5 +223,7 @@
"@kbn/cloud-security-posture",
"@kbn/security-solution-distribution-bar",
"@kbn/cloud-security-posture-common",
+ "@kbn/entityManager-plugin",
+ "@kbn/entities-schema",
]
}
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 cf9722e89b408..6a3d0cf8f3dce 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
@@ -37,6 +37,10 @@ import {
CreateUpdateProtectionUpdatesNoteRequestBodyInput,
} from '@kbn/security-solution-plugin/common/api/endpoint/protection_updates_note/protection_updates_note.gen';
import { DeleteAssetCriticalityRecordRequestQueryInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/asset_criticality/delete_asset_criticality.gen';
+import {
+ DeleteEntityStoreRequestQueryInput,
+ DeleteEntityStoreRequestParamsInput,
+} from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/engine/delete.gen';
import { DeleteNoteRequestBodyInput } from '@kbn/security-solution-plugin/common/api/timeline/delete_note/delete_note_route.gen';
import { DeleteRuleRequestQueryInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/crud/delete_rule/delete_rule_route.gen';
import { DeleteTimelinesRequestBodyInput } from '@kbn/security-solution-plugin/common/api/timeline/delete_timelines/delete_timelines_route.gen';
@@ -76,6 +80,8 @@ import {
GetEndpointSuggestionsRequestParamsInput,
GetEndpointSuggestionsRequestBodyInput,
} from '@kbn/security-solution-plugin/common/api/endpoint/suggestions/get_suggestions.gen';
+import { GetEntityStoreEngineRequestParamsInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/engine/get.gen';
+import { GetEntityStoreStatsRequestParamsInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/engine/stats.gen';
import { GetNotesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/timeline/get_notes/get_notes_route.gen';
import { GetPolicyResponseRequestQueryInput } from '@kbn/security-solution-plugin/common/api/endpoint/policy/policy_response.gen';
import { GetProtectionUpdatesNoteRequestParamsInput } from '@kbn/security-solution-plugin/common/api/endpoint/protection_updates_note/protection_updates_note.gen';
@@ -91,6 +97,10 @@ import { GetTimelineRequestQueryInput } from '@kbn/security-solution-plugin/comm
import { GetTimelinesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/timeline/get_timelines/get_timelines_route.gen';
import { ImportRulesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/import_rules/import_rules_route.gen';
import { ImportTimelinesRequestBodyInput } from '@kbn/security-solution-plugin/common/api/timeline/import_timelines/import_timelines_route.gen';
+import {
+ InitEntityStoreRequestParamsInput,
+ InitEntityStoreRequestBodyInput,
+} from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/engine/init.gen';
import { InstallPrepackedTimelinesRequestBodyInput } from '@kbn/security-solution-plugin/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route.gen';
import { PatchRuleRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/crud/patch_rule/patch_rule_route.gen';
import { PatchTimelineRequestBodyInput } from '@kbn/security-solution-plugin/common/api/timeline/patch_timelines/patch_timeline_route.gen';
@@ -110,6 +120,8 @@ import { SearchAlertsRequestBodyInput } from '@kbn/security-solution-plugin/comm
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';
import { SetAlertTagsRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/alert_tags/set_alert_tags/set_alert_tags.gen';
+import { StartEntityStoreRequestParamsInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/engine/start.gen';
+import { StopEntityStoreRequestParamsInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/engine/stop.gen';
import { SuggestUserProfilesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/detection_engine/users/suggest_user_profiles_route.gen';
import { TriggerRiskScoreCalculationRequestBodyInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/risk_engine/entity_calculation_route.gen';
import { UpdateRuleRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/crud/update_rule/update_rule_route.gen';
@@ -313,6 +325,14 @@ Migrations are initiated per index. While the process is neither destructive nor
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.query(props.query);
},
+ deleteEntityStore(props: DeleteEntityStoreProps) {
+ return supertest
+ .delete(replaceParams('/api/entity_store/engines/{entityType}', props.params))
+ .set('kbn-xsrf', 'true')
+ .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31')
+ .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
+ .query(props.query);
+ },
deleteNote(props: DeleteNoteProps) {
return supertest
.delete('/api/note')
@@ -668,6 +688,20 @@ finalize it.
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send(props.body as object);
},
+ getEntityStoreEngine(props: GetEntityStoreEngineProps) {
+ return supertest
+ .get(replaceParams('/api/entity_store/engines/{entityType}', props.params))
+ .set('kbn-xsrf', 'true')
+ .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31')
+ .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana');
+ },
+ getEntityStoreStats(props: GetEntityStoreStatsProps) {
+ return supertest
+ .post(replaceParams('/api/entity_store/engines/{entityType}/stats', props.params))
+ .set('kbn-xsrf', 'true')
+ .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31')
+ .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana');
+ },
/**
* Gets notes
*/
@@ -764,6 +798,14 @@ finalize it.
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send(props.body as object);
},
+ initEntityStore(props: InitEntityStoreProps) {
+ return supertest
+ .post(replaceParams('/api/entity_store/engines/{entityType}/init', props.params))
+ .set('kbn-xsrf', 'true')
+ .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31')
+ .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
+ .send(props.body as object);
+ },
/**
* Initializes the Risk Engine by creating the necessary indices and mappings, removing old transforms, and starting the new risk engine
*/
@@ -799,6 +841,13 @@ finalize it.
.set(ELASTIC_HTTP_VERSION_HEADER, '1')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana');
},
+ listEntityStoreEngines() {
+ return supertest
+ .get('/api/entity_store/engines')
+ .set('kbn-xsrf', 'true')
+ .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31')
+ .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana');
+ },
/**
* Update specific fields of an existing detection rule using the `rule_id` or `id` field.
*/
@@ -1018,6 +1067,20 @@ detection engine rules.
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send(props.body as object);
},
+ startEntityStore(props: StartEntityStoreProps) {
+ return supertest
+ .post(replaceParams('/api/entity_store/engines/{entityType}/start', props.params))
+ .set('kbn-xsrf', 'true')
+ .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31')
+ .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana');
+ },
+ stopEntityStore(props: StopEntityStoreProps) {
+ return supertest
+ .post(replaceParams('/api/entity_store/engines/{entityType}/stop', props.params))
+ .set('kbn-xsrf', 'true')
+ .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31')
+ .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana');
+ },
/**
* Suggests user profiles.
*/
@@ -1107,6 +1170,10 @@ export interface CreateUpdateProtectionUpdatesNoteProps {
export interface DeleteAssetCriticalityRecordProps {
query: DeleteAssetCriticalityRecordRequestQueryInput;
}
+export interface DeleteEntityStoreProps {
+ query: DeleteEntityStoreRequestQueryInput;
+ params: DeleteEntityStoreRequestParamsInput;
+}
export interface DeleteNoteProps {
body: DeleteNoteRequestBodyInput;
}
@@ -1200,6 +1267,12 @@ export interface GetEndpointSuggestionsProps {
params: GetEndpointSuggestionsRequestParamsInput;
body: GetEndpointSuggestionsRequestBodyInput;
}
+export interface GetEntityStoreEngineProps {
+ params: GetEntityStoreEngineRequestParamsInput;
+}
+export interface GetEntityStoreStatsProps {
+ params: GetEntityStoreStatsRequestParamsInput;
+}
export interface GetNotesProps {
query: GetNotesRequestQueryInput;
}
@@ -1229,6 +1302,10 @@ export interface ImportRulesProps {
export interface ImportTimelinesProps {
body: ImportTimelinesRequestBodyInput;
}
+export interface InitEntityStoreProps {
+ params: InitEntityStoreRequestParamsInput;
+ body: InitEntityStoreRequestBodyInput;
+}
export interface InstallPrepackedTimelinesProps {
body: InstallPrepackedTimelinesRequestBodyInput;
}
@@ -1278,6 +1355,12 @@ export interface SetAlertsStatusProps {
export interface SetAlertTagsProps {
body: SetAlertTagsRequestBodyInput;
}
+export interface StartEntityStoreProps {
+ params: StartEntityStoreRequestParamsInput;
+}
+export interface StopEntityStoreProps {
+ params: StopEntityStoreRequestParamsInput;
+}
export interface SuggestUserProfilesProps {
query: SuggestUserProfilesRequestQueryInput;
}
diff --git a/x-pack/test_serverless/api_integration/test_suites/security/platform_security/authorization.ts b/x-pack/test_serverless/api_integration/test_suites/security/platform_security/authorization.ts
index 07dbcf7ded031..5cf491188ba96 100644
--- a/x-pack/test_serverless/api_integration/test_suites/security/platform_security/authorization.ts
+++ b/x-pack/test_serverless/api_integration/test_suites/security/platform_security/authorization.ts
@@ -349,6 +349,18 @@ export default function ({ getService }: FtrProviderContext) {
"saved_object:risk-engine-configuration/delete",
"saved_object:risk-engine-configuration/bulk_delete",
"saved_object:risk-engine-configuration/share_to_space",
+ "saved_object:entity-engine-status/bulk_get",
+ "saved_object:entity-engine-status/get",
+ "saved_object:entity-engine-status/find",
+ "saved_object:entity-engine-status/open_point_in_time",
+ "saved_object:entity-engine-status/close_point_in_time",
+ "saved_object:entity-engine-status/create",
+ "saved_object:entity-engine-status/bulk_create",
+ "saved_object:entity-engine-status/update",
+ "saved_object:entity-engine-status/bulk_update",
+ "saved_object:entity-engine-status/delete",
+ "saved_object:entity-engine-status/bulk_delete",
+ "saved_object:entity-engine-status/share_to_space",
"saved_object:policy-settings-protection-updates-note/bulk_get",
"saved_object:policy-settings-protection-updates-note/get",
"saved_object:policy-settings-protection-updates-note/find",
@@ -1182,6 +1194,18 @@ export default function ({ getService }: FtrProviderContext) {
"saved_object:risk-engine-configuration/delete",
"saved_object:risk-engine-configuration/bulk_delete",
"saved_object:risk-engine-configuration/share_to_space",
+ "saved_object:entity-engine-status/bulk_get",
+ "saved_object:entity-engine-status/get",
+ "saved_object:entity-engine-status/find",
+ "saved_object:entity-engine-status/open_point_in_time",
+ "saved_object:entity-engine-status/close_point_in_time",
+ "saved_object:entity-engine-status/create",
+ "saved_object:entity-engine-status/bulk_create",
+ "saved_object:entity-engine-status/update",
+ "saved_object:entity-engine-status/bulk_update",
+ "saved_object:entity-engine-status/delete",
+ "saved_object:entity-engine-status/bulk_delete",
+ "saved_object:entity-engine-status/share_to_space",
"saved_object:policy-settings-protection-updates-note/bulk_get",
"saved_object:policy-settings-protection-updates-note/get",
"saved_object:policy-settings-protection-updates-note/find",
@@ -1779,6 +1803,11 @@ export default function ({ getService }: FtrProviderContext) {
"saved_object:risk-engine-configuration/find",
"saved_object:risk-engine-configuration/open_point_in_time",
"saved_object:risk-engine-configuration/close_point_in_time",
+ "saved_object:entity-engine-status/bulk_get",
+ "saved_object:entity-engine-status/get",
+ "saved_object:entity-engine-status/find",
+ "saved_object:entity-engine-status/open_point_in_time",
+ "saved_object:entity-engine-status/close_point_in_time",
"saved_object:policy-settings-protection-updates-note/bulk_get",
"saved_object:policy-settings-protection-updates-note/get",
"saved_object:policy-settings-protection-updates-note/find",
@@ -2135,6 +2164,11 @@ export default function ({ getService }: FtrProviderContext) {
"saved_object:risk-engine-configuration/find",
"saved_object:risk-engine-configuration/open_point_in_time",
"saved_object:risk-engine-configuration/close_point_in_time",
+ "saved_object:entity-engine-status/bulk_get",
+ "saved_object:entity-engine-status/get",
+ "saved_object:entity-engine-status/find",
+ "saved_object:entity-engine-status/open_point_in_time",
+ "saved_object:entity-engine-status/close_point_in_time",
"saved_object:policy-settings-protection-updates-note/bulk_get",
"saved_object:policy-settings-protection-updates-note/get",
"saved_object:policy-settings-protection-updates-note/find",
From 6755cc180860653c9ec6f3891231a8f69f929c69 Mon Sep 17 00:00:00 2001
From: Stef Nestor <26751266+stefnestor@users.noreply.github.com>
Date: Mon, 16 Sep 2024 09:38:46 -0600
Subject: [PATCH 029/139] (Doc+) link video to checking health (#193023)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Summary
👋 howdy team! Ongoing improvement for common support topic, this links
[this video
walkthrough](https://www.youtube.com/watch?v=AlgGYcpGvOA&list=PL_mJOmq4zsHbQlfEMEh_30_LuV_hZp-3d&index=3)
on checking Kibana health.
### Checklist
NA
### Risk Matrix
NA
### For maintainers
NA
---------
Co-authored-by: florent-leborgne
---
docs/setup/access.asciidoc | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/setup/access.asciidoc b/docs/setup/access.asciidoc
index a0bd1207a6a35..3b6457b42f04d 100644
--- a/docs/setup/access.asciidoc
+++ b/docs/setup/access.asciidoc
@@ -65,4 +65,4 @@ For example:
* When {kib} is unable to connect to a healthy {es} cluster, errors like `master_not_discovered_exception` or `unable to revive connection` or `license is not available` errors appear.
* When one or more {kib}-backing indices are unhealthy, the `index_not_green_timeout` error appears.
-For more information, refer to our https://www.elastic.co/blog/troubleshooting-kibana-health[walkthrough on troubleshooting Kibana Health].
+You can find a Kibana health troubleshooting walkthrough in https://www.elastic.co/blog/troubleshooting-kibana-health[this blog] or in link:https://www.youtube.com/watch?v=AlgGYcpGvOA[this video].
From 4d488818dc5503c40617d01512cd89c05e1e0345 Mon Sep 17 00:00:00 2001
From: Joe McElroy
Date: Mon, 16 Sep 2024 17:03:01 +0100
Subject: [PATCH 030/139] [Onboarding] Connection details + Quick Stats
(#192636)
## Summary
Adding in the connection details and quickstats for the search_details
page.



### Checklist
Delete any items that are not applicable to this PR.
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Liam Thompson <32779855+leemthompo@users.noreply.github.com>
---
packages/kbn-doc-links/src/get_doc_links.ts | 1 +
packages/kbn-doc-links/src/types.ts | 1 +
.../search_indices/common/doc_links.ts | 2 +
.../connection_details/connection_details.tsx | 73 ++++++++
.../components/indices/details_page.tsx | 32 +++-
.../quick_stats/mappings_convertor.test.ts | 41 +++++
.../quick_stats/mappings_convertor.ts | 62 +++++++
.../components/quick_stats/quick_stat.tsx | 116 ++++++++++++
.../components/quick_stats/quick_stats.tsx | 167 ++++++++++++++++++
.../components/start/create_index_code.tsx | 10 +-
.../public/hooks/api/use_index_mappings.ts | 22 +++
.../public/hooks/use_elasticsearch_url.ts | 18 ++
x-pack/plugins/search_indices/public/types.ts | 8 +
.../svl_search_index_detail_page.ts | 34 ++++
.../test_suites/search/search_index_detail.ts | 25 +++
15 files changed, 603 insertions(+), 9 deletions(-)
create mode 100644 x-pack/plugins/search_indices/public/components/connection_details/connection_details.tsx
create mode 100644 x-pack/plugins/search_indices/public/components/quick_stats/mappings_convertor.test.ts
create mode 100644 x-pack/plugins/search_indices/public/components/quick_stats/mappings_convertor.ts
create mode 100644 x-pack/plugins/search_indices/public/components/quick_stats/quick_stat.tsx
create mode 100644 x-pack/plugins/search_indices/public/components/quick_stats/quick_stats.tsx
create mode 100644 x-pack/plugins/search_indices/public/hooks/api/use_index_mappings.ts
create mode 100644 x-pack/plugins/search_indices/public/hooks/use_elasticsearch_url.ts
diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts
index d2ac9c6678797..3ad5d271bde47 100644
--- a/packages/kbn-doc-links/src/get_doc_links.ts
+++ b/packages/kbn-doc-links/src/get_doc_links.ts
@@ -219,6 +219,7 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D
searchApplicationsSearch: `${ELASTICSEARCH_DOCS}search-application-client.html`,
searchLabs: `${SEARCH_LABS_URL}`,
searchLabsRepo: `${SEARCH_LABS_REPO}`,
+ semanticSearch: `${ELASTICSEARCH_DOCS}semantic-search.html`,
searchTemplates: `${ELASTICSEARCH_DOCS}search-template.html`,
semanticTextField: `${ELASTICSEARCH_DOCS}semantic-text.html`,
start: `${ENTERPRISE_SEARCH_DOCS}start.html`,
diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts
index ae6e56a9ac385..cbf085623c3a6 100644
--- a/packages/kbn-doc-links/src/types.ts
+++ b/packages/kbn-doc-links/src/types.ts
@@ -183,6 +183,7 @@ export interface DocLinks {
readonly searchApplicationsSearch: string;
readonly searchLabs: string;
readonly searchLabsRepo: string;
+ readonly semanticSearch: string;
readonly searchTemplates: string;
readonly semanticTextField: string;
readonly start: string;
diff --git a/x-pack/plugins/search_indices/common/doc_links.ts b/x-pack/plugins/search_indices/common/doc_links.ts
index dbffa8f9f0f33..8cceb45041ab9 100644
--- a/x-pack/plugins/search_indices/common/doc_links.ts
+++ b/x-pack/plugins/search_indices/common/doc_links.ts
@@ -9,11 +9,13 @@ import { DocLinks } from '@kbn/doc-links';
class SearchIndicesDocLinks {
public apiReference: string = '';
+ public setupSemanticSearch: string = '';
constructor() {}
setDocLinks(newDocLinks: DocLinks) {
this.apiReference = newDocLinks.apiReference;
+ this.setupSemanticSearch = newDocLinks.enterpriseSearch.semanticSearch;
}
}
export const docLinks = new SearchIndicesDocLinks();
diff --git a/x-pack/plugins/search_indices/public/components/connection_details/connection_details.tsx b/x-pack/plugins/search_indices/public/components/connection_details/connection_details.tsx
new file mode 100644
index 0000000000000..d7ce8f308b683
--- /dev/null
+++ b/x-pack/plugins/search_indices/public/components/connection_details/connection_details.tsx
@@ -0,0 +1,73 @@
+/*
+ * 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 {
+ EuiButtonIcon,
+ EuiCopy,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiTitle,
+ useEuiTheme,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
+import { FormattedMessage } from '@kbn/i18n-react';
+import { useElasticsearchUrl } from '../../hooks/use_elasticsearch_url';
+
+export const ConnectionDetails: React.FC = () => {
+ const { euiTheme } = useEuiTheme();
+ const elasticsearchUrl = useElasticsearchUrl();
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {elasticsearchUrl}
+
+
+
+
+ {(copy) => (
+
+ )}
+
+
+
+ );
+};
diff --git a/x-pack/plugins/search_indices/public/components/indices/details_page.tsx b/x-pack/plugins/search_indices/public/components/indices/details_page.tsx
index afa798814d864..85021e79edbf2 100644
--- a/x-pack/plugins/search_indices/public/components/indices/details_page.tsx
+++ b/x-pack/plugins/search_indices/public/components/indices/details_page.tsx
@@ -27,13 +27,22 @@ import { i18n } from '@kbn/i18n';
import { SectionLoading } from '@kbn/es-ui-shared-plugin/public';
import { useIndex } from '../../hooks/api/use_index';
import { useKibana } from '../../hooks/use_kibana';
+import { ConnectionDetails } from '../connection_details/connection_details';
+import { QuickStats } from '../quick_stats/quick_stats';
+import { useIndexMapping } from '../../hooks/api/use_index_mappings';
import { DeleteIndexModal } from './delete_index_modal';
import { IndexloadingError } from './details_page_loading_error';
export const SearchIndexDetailsPage = () => {
const indexName = decodeURIComponent(useParams<{ indexName: string }>().indexName);
const { console: consolePlugin, docLinks, application } = useKibana().services;
- const { data: index, refetch, isSuccess, isInitialLoading } = useIndex(indexName);
+
+ const { data: index, refetch, isError: isIndexError, isInitialLoading } = useIndex(indexName);
+ const {
+ data: mappings,
+ isError: isMappingsError,
+ isInitialLoading: isMappingsInitialLoading,
+ } = useIndexMapping(indexName);
const embeddableConsole = useMemo(
() => (consolePlugin?.EmbeddableConsole ? : null),
@@ -87,7 +96,7 @@ export const SearchIndexDetailsPage = () => {
/>
);
- if (isInitialLoading) {
+ if (isInitialLoading || isMappingsInitialLoading) {
return (
{i18n.translate('xpack.searchIndices.loadingDescription', {
@@ -103,9 +112,10 @@ export const SearchIndexDetailsPage = () => {
restrictWidth={false}
data-test-subj="searchIndicesDetailsPage"
grow={false}
- bottomBorder={false}
+ panelled
+ bottomBorder
>
- {!isSuccess || !index ? (
+ {isIndexError || isMappingsError || !index || !mappings ? (
{
navigateToIndexListPage={navigateToIndexListPage}
/>
)}
+
+
+
+
+
+ {/* TODO: API KEY */}
+
+
+
-
+
+
+
+
>
)}
{embeddableConsole}
diff --git a/x-pack/plugins/search_indices/public/components/quick_stats/mappings_convertor.test.ts b/x-pack/plugins/search_indices/public/components/quick_stats/mappings_convertor.test.ts
new file mode 100644
index 0000000000000..da182123ab4c1
--- /dev/null
+++ b/x-pack/plugins/search_indices/public/components/quick_stats/mappings_convertor.test.ts
@@ -0,0 +1,41 @@
+/*
+ * 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 { Mappings } from '../../types';
+import { countVectorBasedTypesFromMappings } from './mappings_convertor';
+
+describe('mappings convertor', () => {
+ it('should count vector based types from mappings', () => {
+ const mappings = {
+ mappings: {
+ properties: {
+ field1: {
+ type: 'dense_vector',
+ },
+ field2: {
+ type: 'dense_vector',
+ },
+ field3: {
+ type: 'sparse_vector',
+ },
+ field4: {
+ type: 'dense_vector',
+ },
+ field5: {
+ type: 'semantic_text',
+ },
+ },
+ },
+ };
+ const result = countVectorBasedTypesFromMappings(mappings as unknown as Mappings);
+ expect(result).toEqual({
+ dense_vector: 3,
+ sparse_vector: 1,
+ semantic_text: 1,
+ });
+ });
+});
diff --git a/x-pack/plugins/search_indices/public/components/quick_stats/mappings_convertor.ts b/x-pack/plugins/search_indices/public/components/quick_stats/mappings_convertor.ts
new file mode 100644
index 0000000000000..749fe05de1f54
--- /dev/null
+++ b/x-pack/plugins/search_indices/public/components/quick_stats/mappings_convertor.ts
@@ -0,0 +1,62 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type {
+ MappingProperty,
+ MappingPropertyBase,
+} from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
+import type { Mappings } from '../../types';
+
+interface VectorFieldTypes {
+ semantic_text: number;
+ dense_vector: number;
+ sparse_vector: number;
+}
+
+export function countVectorBasedTypesFromMappings(mappings: Mappings): VectorFieldTypes {
+ const typeCounts: VectorFieldTypes = {
+ semantic_text: 0,
+ dense_vector: 0,
+ sparse_vector: 0,
+ };
+
+ const typeCountKeys = Object.keys(typeCounts);
+
+ function recursiveCount(fields: MappingProperty | Mappings | MappingPropertyBase['fields']) {
+ if (!fields) {
+ return;
+ }
+ if ('mappings' in fields) {
+ recursiveCount(fields.mappings);
+ }
+ if ('properties' in fields && fields.properties) {
+ Object.keys(fields.properties).forEach((key) => {
+ const value = (fields.properties as Record)?.[key];
+
+ if (value && value.type) {
+ if (typeCountKeys.includes(value.type)) {
+ const type = value.type as keyof VectorFieldTypes;
+ typeCounts[type] = typeCounts[type] + 1;
+ }
+
+ if ('fields' in value) {
+ recursiveCount(value.fields);
+ }
+
+ if ('properties' in value) {
+ recursiveCount(value.properties);
+ }
+ } else if (value.properties || value.fields) {
+ recursiveCount(value);
+ }
+ });
+ }
+ }
+
+ recursiveCount(mappings);
+ return typeCounts;
+}
diff --git a/x-pack/plugins/search_indices/public/components/quick_stats/quick_stat.tsx b/x-pack/plugins/search_indices/public/components/quick_stats/quick_stat.tsx
new file mode 100644
index 0000000000000..0d72835ad5779
--- /dev/null
+++ b/x-pack/plugins/search_indices/public/components/quick_stats/quick_stat.tsx
@@ -0,0 +1,116 @@
+/*
+ * 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 {
+ EuiAccordion,
+ EuiDescriptionList,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiIcon,
+ EuiPanel,
+ EuiText,
+ useEuiTheme,
+ useGeneratedHtmlId,
+} from '@elastic/eui';
+
+interface BaseQuickStatProps {
+ icon: string;
+ iconColor: string;
+ title: string;
+ secondaryTitle: React.ReactNode;
+ open: boolean;
+ content?: React.ReactNode;
+ stats: Array<{
+ title: string;
+ description: NonNullable;
+ }>;
+ setOpen: (open: boolean) => void;
+ first?: boolean;
+}
+
+export const QuickStat: React.FC = ({
+ icon,
+ title,
+ stats,
+ open,
+ setOpen,
+ first,
+ secondaryTitle,
+ iconColor,
+ content,
+ ...rest
+}) => {
+ const { euiTheme } = useEuiTheme();
+
+ const id = useGeneratedHtmlId({
+ prefix: 'formAccordion',
+ suffix: title,
+ });
+
+ return (
+ setOpen(!open)}
+ paddingSize="none"
+ id={id}
+ buttonElement="div"
+ arrowDisplay="right"
+ {...rest}
+ css={{
+ borderLeft: euiTheme.border.thin,
+ ...(first ? { borderLeftWidth: 0 } : {}),
+ '.euiAccordion__arrow': {
+ marginRight: euiTheme.size.s,
+ },
+ '.euiAccordion__triggerWrapper': {
+ background: euiTheme.colors.ghost,
+ },
+ '.euiAccordion__children': {
+ borderTop: euiTheme.border.thin,
+ padding: euiTheme.size.m,
+ },
+ }}
+ buttonContent={
+
+
+
+
+
+
+
+ {title}
+
+
+
+ {secondaryTitle}
+
+
+
+ }
+ >
+ {content ? (
+ content
+ ) : (
+
+
+
+
+
+ )}
+
+ );
+};
diff --git a/x-pack/plugins/search_indices/public/components/quick_stats/quick_stats.tsx b/x-pack/plugins/search_indices/public/components/quick_stats/quick_stats.tsx
new file mode 100644
index 0000000000000..cece2b1d39910
--- /dev/null
+++ b/x-pack/plugins/search_indices/public/components/quick_stats/quick_stats.tsx
@@ -0,0 +1,167 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { useMemo, useState } from 'react';
+import type { Index } from '@kbn/index-management-shared-types';
+
+import {
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiI18nNumber,
+ EuiPanel,
+ EuiText,
+ useEuiTheme,
+ EuiButton,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { Mappings } from '../../types';
+import { countVectorBasedTypesFromMappings } from './mappings_convertor';
+import { QuickStat } from './quick_stat';
+import { useKibana } from '../../hooks/use_kibana';
+
+export interface QuickStatsProps {
+ index: Index;
+ mappings: Mappings;
+}
+
+export const SetupAISearchButton: React.FC = () => {
+ const {
+ services: { docLinks },
+ } = useKibana();
+ return (
+
+
+
+
+
+ {i18n.translate('xpack.searchIndices.quickStats.setup_ai_search_description', {
+ defaultMessage: 'Build AI-powered search experiences with Elastic',
+ })}
+
+
+
+
+
+ {i18n.translate('xpack.searchIndices.quickStats.setup_ai_search_button', {
+ defaultMessage: 'Set up now',
+ })}
+
+
+
+
+ );
+};
+
+export const QuickStats: React.FC = ({ index, mappings }) => {
+ const [open, setOpen] = useState(false);
+ const { euiTheme } = useEuiTheme();
+ const mappingStats = useMemo(() => countVectorBasedTypesFromMappings(mappings), [mappings]);
+ const vectorFieldCount =
+ mappingStats.sparse_vector + mappingStats.dense_vector + mappingStats.semantic_text;
+
+ return (
+ ({
+ border: euiTheme.border.thin,
+ background: euiTheme.colors.lightestShade,
+ overflow: 'hidden',
+ })}
+ >
+
+
+ }
+ stats={[
+ {
+ title: i18n.translate('xpack.searchIndices.quickStats.documents.totalTitle', {
+ defaultMessage: 'Total',
+ }),
+ description: ,
+ },
+ {
+ title: i18n.translate('xpack.searchIndices.quickStats.documents.indexSize', {
+ defaultMessage: 'Index Size',
+ }),
+ description: index.size ?? '0b',
+ },
+ ]}
+ first
+ />
+
+
+ 0
+ ? i18n.translate('xpack.searchIndices.quickStats.total_count', {
+ defaultMessage: '{value, plural, one {# Field} other {# Fields}}',
+ values: {
+ value: vectorFieldCount,
+ },
+ })
+ : i18n.translate('xpack.searchIndices.quickStats.no_vector_fields', {
+ defaultMessage: 'Not configured',
+ })
+ }
+ content={vectorFieldCount === 0 && }
+ stats={[
+ {
+ title: i18n.translate('xpack.searchIndices.quickStats.sparse_vector', {
+ defaultMessage: 'Sparse Vector',
+ }),
+ description: i18n.translate('xpack.searchIndices.quickStats.sparse_vector_count', {
+ defaultMessage: '{value, plural, one {# Field} other {# Fields}}',
+ values: { value: mappingStats.sparse_vector },
+ }),
+ },
+ {
+ title: i18n.translate('xpack.searchIndices.quickStats.dense_vector', {
+ defaultMessage: 'Dense Vector',
+ }),
+ description: i18n.translate('xpack.searchIndices.quickStats.dense_vector_count', {
+ defaultMessage: '{value, plural, one {# Field} other {# Fields}}',
+ values: { value: mappingStats.dense_vector },
+ }),
+ },
+ {
+ title: i18n.translate('xpack.searchIndices.quickStats.semantic_text', {
+ defaultMessage: 'Semantic Text',
+ }),
+ description: i18n.translate('xpack.searchIndices.quickStats.semantic_text_count', {
+ defaultMessage: '{value, plural, one {# Field} other {# Fields}}',
+ values: { value: mappingStats.semantic_text },
+ }),
+ },
+ ]}
+ />
+
+
+
+ );
+};
diff --git a/x-pack/plugins/search_indices/public/components/start/create_index_code.tsx b/x-pack/plugins/search_indices/public/components/start/create_index_code.tsx
index b58bf6c0926f1..4901847eeed22 100644
--- a/x-pack/plugins/search_indices/public/components/start/create_index_code.tsx
+++ b/x-pack/plugins/search_indices/public/components/start/create_index_code.tsx
@@ -12,12 +12,12 @@ import { TryInConsoleButton } from '@kbn/try-in-console';
import { useKibana } from '../../hooks/use_kibana';
import { CodeSample } from './code_sample';
import { CreateIndexFormState } from './types';
-import { ELASTICSEARCH_URL_PLACEHOLDER } from '../../constants';
import { Languages, AvailableLanguages, LanguageOptions } from '../../code_examples';
import { DenseVectorSeverlessCodeExamples } from '../../code_examples/create_index';
import { LanguageSelector } from '../shared/language_selector';
+import { useElasticsearchUrl } from '../../hooks/use_elasticsearch_url';
export interface CreateIndexCodeViewProps {
createIndexForm: CreateIndexFormState;
@@ -27,15 +27,17 @@ export interface CreateIndexCodeViewProps {
const SelectedCodeExamples = DenseVectorSeverlessCodeExamples;
export const CreateIndexCodeView = ({ createIndexForm }: CreateIndexCodeViewProps) => {
- const { application, cloud, share, console: consolePlugin } = useKibana().services;
+ const { application, share, console: consolePlugin } = useKibana().services;
// TODO: initing this should be dynamic and possibly saved in the form state
const [selectedLanguage, setSelectedLanguage] = useState('python');
+ const elasticsearchUrl = useElasticsearchUrl();
+
const codeParams = useMemo(() => {
return {
indexName: createIndexForm.indexName || undefined,
- elasticsearchURL: cloud?.elasticsearchUrl ?? ELASTICSEARCH_URL_PLACEHOLDER,
+ elasticsearchURL: elasticsearchUrl,
};
- }, [createIndexForm.indexName, cloud]);
+ }, [createIndexForm.indexName, elasticsearchUrl]);
const selectedCodeExample = useMemo(() => {
return SelectedCodeExamples[selectedLanguage];
}, [selectedLanguage]);
diff --git a/x-pack/plugins/search_indices/public/hooks/api/use_index_mappings.ts b/x-pack/plugins/search_indices/public/hooks/api/use_index_mappings.ts
new file mode 100644
index 0000000000000..a91198f70b4e8
--- /dev/null
+++ b/x-pack/plugins/search_indices/public/hooks/api/use_index_mappings.ts
@@ -0,0 +1,22 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { useQuery } from '@tanstack/react-query';
+import { useKibana } from '../use_kibana';
+import { Mappings } from '../../types';
+
+export const useIndexMapping = (indexName: string) => {
+ const { http } = useKibana().services;
+ const queryKey = ['fetchMapping', indexName];
+ const result = useQuery({
+ queryKey,
+ refetchOnWindowFocus: 'always',
+ queryFn: () =>
+ http.fetch(`/api/index_management/mapping/${encodeURIComponent(indexName)}`),
+ });
+ return { queryKey, ...result };
+};
diff --git a/x-pack/plugins/search_indices/public/hooks/use_elasticsearch_url.ts b/x-pack/plugins/search_indices/public/hooks/use_elasticsearch_url.ts
new file mode 100644
index 0000000000000..d07cc62b210de
--- /dev/null
+++ b/x-pack/plugins/search_indices/public/hooks/use_elasticsearch_url.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { useKibana } from './use_kibana';
+
+import { ELASTICSEARCH_URL_PLACEHOLDER } from '../constants';
+
+export const useElasticsearchUrl = (): string => {
+ const {
+ services: { cloud },
+ } = useKibana();
+
+ return cloud?.elasticsearchUrl ?? ELASTICSEARCH_URL_PLACEHOLDER;
+};
diff --git a/x-pack/plugins/search_indices/public/types.ts b/x-pack/plugins/search_indices/public/types.ts
index 8e7853543f76f..6e0192e34f87c 100644
--- a/x-pack/plugins/search_indices/public/types.ts
+++ b/x-pack/plugins/search_indices/public/types.ts
@@ -11,6 +11,7 @@ import type { AppMountParameters, CoreStart } from '@kbn/core/public';
import type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public';
import type { SharePluginStart } from '@kbn/share-plugin/public';
import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public';
+import type { MappingPropertyBase } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
export interface SearchIndicesPluginSetup {
enabled: boolean;
@@ -44,11 +45,18 @@ export interface AppUsageTracker {
load: (eventName: string | string[]) => void;
}
+export interface Mappings {
+ mappings: {
+ properties: MappingPropertyBase['properties'];
+ };
+}
+
export interface CodeSnippetParameters {
indexName?: string;
apiKey?: string;
elasticsearchURL: string;
}
+
export type CodeSnippetFunction = (params: CodeSnippetParameters) => string;
export interface CodeLanguage {
diff --git a/x-pack/test_serverless/functional/page_objects/svl_search_index_detail_page.ts b/x-pack/test_serverless/functional/page_objects/svl_search_index_detail_page.ts
index 5ac440ce6c4f4..09b69aaed5332 100644
--- a/x-pack/test_serverless/functional/page_objects/svl_search_index_detail_page.ts
+++ b/x-pack/test_serverless/functional/page_objects/svl_search_index_detail_page.ts
@@ -32,6 +32,40 @@ export function SvlSearchIndexDetailPageProvider({ getService }: FtrProviderCont
async expectBackToIndicesButtonRedirectsToListPage() {
await testSubjects.existOrFail('indicesList');
},
+ async expectConnectionDetails() {
+ await testSubjects.existOrFail('connectionDetailsEndpoint', { timeout: 2000 });
+ expect(await (await testSubjects.find('connectionDetailsEndpoint')).getVisibleText()).to.be(
+ 'https://fakeprojectid.es.fake-domain.cld.elstc.co:443'
+ );
+ },
+ async expectQuickStats() {
+ await testSubjects.existOrFail('quickStats', { timeout: 2000 });
+ const quickStatsElem = await testSubjects.find('quickStats');
+ const quickStatsDocumentElem = await quickStatsElem.findByTestSubject(
+ 'QuickStatsDocumentCount'
+ );
+ expect(await quickStatsDocumentElem.getVisibleText()).to.contain('Document count\n0');
+ expect(await quickStatsDocumentElem.getVisibleText()).not.to.contain('Index Size\n0b');
+ await quickStatsDocumentElem.click();
+ expect(await quickStatsDocumentElem.getVisibleText()).to.contain('Index Size\n0b');
+ },
+ async expectQuickStatsAIMappings() {
+ await testSubjects.existOrFail('quickStats', { timeout: 2000 });
+ const quickStatsElem = await testSubjects.find('quickStats');
+ const quickStatsAIMappingsElem = await quickStatsElem.findByTestSubject(
+ 'QuickStatsAIMappings'
+ );
+ await quickStatsAIMappingsElem.click();
+ await testSubjects.existOrFail('setupAISearchButton', { timeout: 2000 });
+ },
+
+ async expectQuickStatsAIMappingsToHaveVectorFields() {
+ const quickStatsDocumentElem = await testSubjects.find('QuickStatsAIMappings');
+ await quickStatsDocumentElem.click();
+ expect(await quickStatsDocumentElem.getVisibleText()).to.contain('AI Search\n1 Field');
+ await testSubjects.missingOrFail('setupAISearchButton', { timeout: 2000 });
+ },
+
async expectMoreOptionsActionButtonExists() {
await testSubjects.existOrFail('moreOptionsActionButton');
},
diff --git a/x-pack/test_serverless/functional/test_suites/search/search_index_detail.ts b/x-pack/test_serverless/functional/test_suites/search/search_index_detail.ts
index 9450dca44df57..cd39079274d0a 100644
--- a/x-pack/test_serverless/functional/test_suites/search/search_index_detail.ts
+++ b/x-pack/test_serverless/functional/test_suites/search/search_index_detail.ts
@@ -26,6 +26,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
after(async () => {
await esDeleteAllIndices(indexName);
});
+
describe('index details page overview', () => {
before(async () => {
await es.indices.create({ index: indexName });
@@ -41,11 +42,35 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
it('should have embedded dev console', async () => {
await testHasEmbeddedConsole(pageObjects);
});
+ it('should have connection details', async () => {
+ await pageObjects.svlSearchIndexDetailPage.expectConnectionDetails();
+ });
+
+ it('should have quick stats', async () => {
+ await pageObjects.svlSearchIndexDetailPage.expectQuickStats();
+ await pageObjects.svlSearchIndexDetailPage.expectQuickStatsAIMappings();
+ await es.indices.putMapping({
+ index: indexName,
+ body: {
+ properties: {
+ my_field: {
+ type: 'dense_vector',
+ dims: 3,
+ },
+ },
+ },
+ });
+ await svlSearchNavigation.navigateToIndexDetailPage(indexName);
+
+ await pageObjects.svlSearchIndexDetailPage.expectQuickStatsAIMappingsToHaveVectorFields();
+ });
+
it('back to indices button should redirect to list page', async () => {
await pageObjects.svlSearchIndexDetailPage.expectBackToIndicesButtonExists();
await pageObjects.svlSearchIndexDetailPage.clickBackToIndicesButton();
await pageObjects.svlSearchIndexDetailPage.expectBackToIndicesButtonRedirectsToListPage();
});
+
describe('page loading error', () => {
before(async () => {
await svlSearchNavigation.navigateToIndexDetailPage(indexName);
From 684da232053c70c8bf3009fb40357c33e607a640 Mon Sep 17 00:00:00 2001
From: Kevin Delemme
Date: Mon, 16 Sep 2024 12:16:07 -0400
Subject: [PATCH 031/139] chore(rca): Hide X axis on ESQL item (#192696)
---
.../items/esql_item/register_esql_item.tsx | 28 ++++++++-----------
1 file changed, 11 insertions(+), 17 deletions(-)
diff --git a/x-pack/plugins/observability_solution/investigate_app/public/items/esql_item/register_esql_item.tsx b/x-pack/plugins/observability_solution/investigate_app/public/items/esql_item/register_esql_item.tsx
index 5f2f95807b4e0..7e64db5557fc2 100644
--- a/x-pack/plugins/observability_solution/investigate_app/public/items/esql_item/register_esql_item.tsx
+++ b/x-pack/plugins/observability_solution/investigate_app/public/items/esql_item/register_esql_item.tsx
@@ -4,8 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui';
-import { css } from '@emotion/css';
+import { EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui';
import type { DataView } from '@kbn/data-views-plugin/common';
import type { ESQLSearchResponse } from '@kbn/es-types';
import { i18n } from '@kbn/i18n';
@@ -123,29 +122,24 @@ export function EsqlWidget({ suggestion, dataView, esqlQuery, dateHistogramResul
[dataView, lens, dateHistogramResults]
);
+ // in the case of a lnsDatatable, we want to render the preview of the histogram and not the datable (input) itself
if (input.attributes.visualizationType === 'lnsDatatable') {
let innerElement: React.ReactElement;
if (previewInput.error) {
innerElement = ;
} else if (previewInput.value) {
- innerElement = ;
+ innerElement = (
+
+ );
} else {
innerElement = ;
}
- return (
-
- div {
- height: 128px;
- }
- `}
- >
- {innerElement}
-
-
- );
+
+ return {innerElement};
}
return (
From 3ad697b74fdf09049464c4a7a8f2cd44550709f0 Mon Sep 17 00:00:00 2001
From: Dominique Clarke
Date: Mon, 16 Sep 2024 12:20:33 -0400
Subject: [PATCH 032/139] [Synthetics] persist refresh interval in local
storage (#191333)
## Summary
Stores refresh interval and refresh paused state in local storage.
Also defaults refresh to paused rather than active.
---------
Co-authored-by: Shahzad
---
.../constants/synthetics/client_defaults.ts | 6 +-
.../common/components/auto_refresh_button.tsx | 64 ++-----------------
.../common/components/last_refreshed.tsx | 6 +-
.../hooks/use_selected_monitor.tsx | 5 +-
.../contexts/synthetics_refresh_context.tsx | 55 ++++++++++++++--
.../apps/synthetics/state/ui/actions.ts | 2 -
.../public/apps/synthetics/state/ui/index.ts | 14 ----
.../apps/synthetics/state/ui/selectors.ts | 9 ---
.../__mocks__/synthetics_store.mock.ts | 2 -
.../get_supported_url_params.test.ts | 9 +--
.../url_params/get_supported_url_params.ts | 11 +---
.../url_params/stringify_url_params.test.ts | 10 +--
.../utils/url_params/stringify_url_params.ts | 9 +--
13 files changed, 64 insertions(+), 138 deletions(-)
diff --git a/x-pack/plugins/observability_solution/synthetics/common/constants/synthetics/client_defaults.ts b/x-pack/plugins/observability_solution/synthetics/common/constants/synthetics/client_defaults.ts
index 6ae9dbfef955f..3e5722ce59f10 100644
--- a/x-pack/plugins/observability_solution/synthetics/common/constants/synthetics/client_defaults.ts
+++ b/x-pack/plugins/observability_solution/synthetics/common/constants/synthetics/client_defaults.ts
@@ -16,11 +16,11 @@ export const CLIENT_DEFAULTS_SYNTHETICS = {
DATE_RANGE_END: 'now',
/**
- * The application auto refreshes every 30s by default.
+ * The application auto refreshes every 60s by default.
*/
AUTOREFRESH_INTERVAL_SECONDS: 60,
/**
- * The application's autorefresh feature is enabled.
+ * The application's autorefresh feature is disabled by default.
*/
- AUTOREFRESH_IS_PAUSED: false,
+ AUTOREFRESH_IS_PAUSED: true,
};
diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/components/auto_refresh_button.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/components/auto_refresh_button.tsx
index 6f40b000a6873..cea6a7d726926 100644
--- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/components/auto_refresh_button.tsx
+++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/components/auto_refresh_button.tsx
@@ -5,69 +5,17 @@
* 2.0.
*/
-import React, { useEffect, useRef } from 'react';
+import React from 'react';
import { EuiAutoRefreshButton, OnRefreshChangeProps } from '@elastic/eui';
-import { useDispatch, useSelector } from 'react-redux';
-import { CLIENT_DEFAULTS_SYNTHETICS } from '../../../../../../common/constants/synthetics/client_defaults';
-import { SyntheticsUrlParams } from '../../../utils/url_params';
-import { useUrlParams } from '../../../hooks';
-import {
- selectRefreshInterval,
- selectRefreshPaused,
- setRefreshIntervalAction,
- setRefreshPausedAction,
-} from '../../../state';
-const { AUTOREFRESH_INTERVAL_SECONDS, AUTOREFRESH_IS_PAUSED } = CLIENT_DEFAULTS_SYNTHETICS;
+import { useSyntheticsRefreshContext } from '../../../contexts/synthetics_refresh_context';
-const replaceDefaults = ({ refreshPaused, refreshInterval }: Partial) => {
- return {
- refreshInterval: refreshInterval === AUTOREFRESH_INTERVAL_SECONDS ? undefined : refreshInterval,
- refreshPaused: refreshPaused === AUTOREFRESH_IS_PAUSED ? undefined : refreshPaused,
- };
-};
export const AutoRefreshButton = () => {
- const dispatch = useDispatch();
-
- const refreshPaused = useSelector(selectRefreshPaused);
- const refreshInterval = useSelector(selectRefreshInterval);
-
- const [getUrlsParams, updateUrlParams] = useUrlParams();
-
- const { refreshInterval: urlRefreshInterval, refreshPaused: urlIsPaused } = getUrlsParams();
-
- const isFirstRender = useRef(true);
-
- useEffect(() => {
- if (isFirstRender.current) {
- // sync url state with redux state on first render
- dispatch(setRefreshIntervalAction(urlRefreshInterval));
- dispatch(setRefreshPausedAction(urlIsPaused));
- isFirstRender.current = false;
- } else {
- // sync redux state with url state on subsequent renders
- if (urlRefreshInterval !== refreshInterval || urlIsPaused !== refreshPaused) {
- updateUrlParams(
- replaceDefaults({
- refreshInterval,
- refreshPaused,
- }),
- true
- );
- }
- }
- }, [updateUrlParams, refreshInterval, refreshPaused, urlRefreshInterval, urlIsPaused, dispatch]);
+ const { refreshInterval, setRefreshInterval, refreshPaused, setRefreshPaused } =
+ useSyntheticsRefreshContext();
const onRefreshChange = (newProps: OnRefreshChangeProps) => {
- dispatch(setRefreshIntervalAction(newProps.refreshInterval / 1000));
- dispatch(setRefreshPausedAction(newProps.isPaused));
-
- updateUrlParams(
- replaceDefaults({
- refreshInterval: newProps.refreshInterval / 1000,
- refreshPaused: newProps.isPaused,
- }),
- true
- );
+ setRefreshPaused(newProps.isPaused);
+ setRefreshInterval(newProps.refreshInterval / 1000);
};
return (
diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/components/last_refreshed.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/components/last_refreshed.tsx
index bc086f67c822b..210170b7e3b8f 100644
--- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/components/last_refreshed.tsx
+++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/components/last_refreshed.tsx
@@ -9,16 +9,12 @@ import React, { useEffect, useState } from 'react';
import moment from 'moment';
import { EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
-import { useSelector } from 'react-redux';
import { useSyntheticsRefreshContext } from '../../../contexts';
-import { selectRefreshPaused } from '../../../state';
export function LastRefreshed() {
- const { lastRefresh: lastRefreshed } = useSyntheticsRefreshContext();
+ const { lastRefresh: lastRefreshed, refreshPaused } = useSyntheticsRefreshContext();
const [refresh, setRefresh] = useState(() => Date.now());
- const refreshPaused = useSelector(selectRefreshPaused);
-
useEffect(() => {
const interVal = setInterval(() => {
setRefresh(Date.now());
diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_monitor.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_monitor.tsx
index 622dcff46e902..1f2eadb7c09fc 100644
--- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_monitor.tsx
+++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_monitor.tsx
@@ -4,7 +4,6 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-
import { useEffect, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
@@ -16,7 +15,6 @@ import {
selectMonitorListState,
selectorMonitorDetailsState,
selectorError,
- selectRefreshInterval,
} from '../../../state';
export const useSelectedMonitor = (monId?: string) => {
@@ -27,14 +25,13 @@ export const useSelectedMonitor = (monId?: string) => {
}
const monitorsList = useSelector(selectEncryptedSyntheticsSavedMonitors);
const { loading: monitorListLoading } = useSelector(selectMonitorListState);
- const refreshInterval = useSelector(selectRefreshInterval);
const monitorFromList = useMemo(
() => monitorsList.find((monitor) => monitor[ConfigKey.CONFIG_ID] === monitorId) ?? null,
[monitorId, monitorsList]
);
const error = useSelector(selectorError);
- const { lastRefresh } = useSyntheticsRefreshContext();
+ const { lastRefresh, refreshInterval } = useSyntheticsRefreshContext();
const { syntheticsMonitor, syntheticsMonitorLoading, syntheticsMonitorDispatchedAt } =
useSelector(selectorMonitorDetailsState);
const dispatch = useDispatch();
diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/synthetics_refresh_context.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/synthetics_refresh_context.tsx
index b53620921fdd1..9f3902b8ccaf2 100644
--- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/synthetics_refresh_context.tsx
+++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/contexts/synthetics_refresh_context.tsx
@@ -14,15 +14,21 @@ import React, {
useState,
FC,
} from 'react';
-import { useSelector } from 'react-redux';
+import useLocalStorage from 'react-use/lib/useLocalStorage';
import { useEvent } from 'react-use';
import moment from 'moment';
import { Subject } from 'rxjs';
-import { selectRefreshInterval, selectRefreshPaused } from '../state';
+import { i18n } from '@kbn/i18n';
+import { CLIENT_DEFAULTS_SYNTHETICS } from '../../../../common/constants/synthetics/client_defaults';
+const { AUTOREFRESH_INTERVAL_SECONDS, AUTOREFRESH_IS_PAUSED } = CLIENT_DEFAULTS_SYNTHETICS;
interface SyntheticsRefreshContext {
lastRefresh: number;
refreshApp: () => void;
+ refreshInterval: number;
+ refreshPaused: boolean;
+ setRefreshInterval: (interval: number) => void;
+ setRefreshPaused: (paused: boolean) => void;
}
const defaultContext: SyntheticsRefreshContext = {
@@ -30,6 +36,22 @@ const defaultContext: SyntheticsRefreshContext = {
refreshApp: () => {
throw new Error('App refresh was not initialized, set it when you invoke the context');
},
+ refreshInterval: AUTOREFRESH_INTERVAL_SECONDS,
+ refreshPaused: AUTOREFRESH_IS_PAUSED,
+ setRefreshInterval: () => {
+ throw new Error(
+ i18n.translate('xpack.synthetics.refreshContext.intervalNotInitialized', {
+ defaultMessage: 'Refresh interval was not initialized, set it when you invoke the context',
+ })
+ );
+ },
+ setRefreshPaused: () => {
+ throw new Error(
+ i18n.translate('xpack.synthetics.refreshContext.pausedNotInitialized', {
+ defaultMessage: 'Refresh paused was not initialized, set it when you invoke the context',
+ })
+ );
+ },
};
export const SyntheticsRefreshContext = createContext(defaultContext);
@@ -41,8 +63,14 @@ export const SyntheticsRefreshContextProvider: FC<
> = ({ children, reload$ }) => {
const [lastRefresh, setLastRefresh] = useState(Date.now());
- const refreshPaused = useSelector(selectRefreshPaused);
- const refreshInterval = useSelector(selectRefreshInterval);
+ const [refreshInterval, setRefreshInterval] = useLocalStorage(
+ 'xpack.synthetics.refreshInterval',
+ AUTOREFRESH_INTERVAL_SECONDS
+ );
+ const [refreshPaused, setRefreshPaused] = useLocalStorage(
+ 'xpack.synthetics.refreshPaused',
+ AUTOREFRESH_IS_PAUSED
+ );
const refreshApp = useCallback(() => {
const refreshTime = Date.now();
@@ -66,13 +94,26 @@ export const SyntheticsRefreshContextProvider: FC<
return {
lastRefresh,
refreshApp,
+ refreshInterval: refreshInterval ?? AUTOREFRESH_INTERVAL_SECONDS,
+ refreshPaused: refreshPaused ?? AUTOREFRESH_IS_PAUSED,
+ setRefreshInterval,
+ setRefreshPaused,
};
- }, [lastRefresh, refreshApp]);
+ }, [
+ lastRefresh,
+ refreshApp,
+ refreshInterval,
+ refreshPaused,
+ setRefreshInterval,
+ setRefreshPaused,
+ ]);
useEvent(
'visibilitychange',
() => {
- const isOutdated = moment().diff(new Date(lastRefresh), 'seconds') > refreshInterval;
+ const isOutdated =
+ moment().diff(new Date(lastRefresh), 'seconds') >
+ (refreshInterval || AUTOREFRESH_INTERVAL_SECONDS);
if (document.visibilityState !== 'hidden' && !refreshPaused && isOutdated) {
refreshApp();
}
@@ -88,7 +129,7 @@ export const SyntheticsRefreshContextProvider: FC<
if (document.visibilityState !== 'hidden') {
refreshApp();
}
- }, refreshInterval * 1000);
+ }, (refreshInterval || AUTOREFRESH_INTERVAL_SECONDS) * 1000);
return () => clearInterval(interval);
}, [refreshPaused, refreshApp, refreshInterval]);
diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/ui/actions.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/ui/actions.ts
index e3738f3737cf0..06b9506ead191 100644
--- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/ui/actions.ts
+++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/ui/actions.ts
@@ -31,5 +31,3 @@ export const toggleIntegrationsPopover = createAction(
);
export const setSelectedMonitorId = createAction('[UI] SET MONITOR ID');
-export const setRefreshPausedAction = createAction('[UI] SET REFRESH PAUSED');
-export const setRefreshIntervalAction = createAction('[UI] SET REFRESH INTERVAL');
diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/ui/index.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/ui/index.ts
index 6c6ef93bbf3a7..2c7d5e5ce3d4c 100644
--- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/ui/index.ts
+++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/ui/index.ts
@@ -11,7 +11,6 @@ import {
SYNTHETICS_STATUS_RULE,
SYNTHETICS_TLS_RULE,
} from '../../../../../common/constants/synthetics_alerts';
-import { CLIENT_DEFAULTS_SYNTHETICS } from '../../../../../common/constants/synthetics/client_defaults';
import {
PopoverState,
toggleIntegrationsPopover,
@@ -20,10 +19,7 @@ import {
setAlertFlyoutVisible,
setSearchTextAction,
setSelectedMonitorId,
- setRefreshPausedAction,
- setRefreshIntervalAction,
} from './actions';
-const { AUTOREFRESH_INTERVAL_SECONDS, AUTOREFRESH_IS_PAUSED } = CLIENT_DEFAULTS_SYNTHETICS;
export interface UiState {
alertFlyoutVisible: typeof SYNTHETICS_TLS_RULE | typeof SYNTHETICS_STATUS_RULE | null;
@@ -32,8 +28,6 @@ export interface UiState {
searchText: string;
integrationsPopoverOpen: PopoverState | null;
monitorId: string;
- refreshInterval: number;
- refreshPaused: boolean;
}
const initialState: UiState = {
@@ -43,8 +37,6 @@ const initialState: UiState = {
searchText: '',
integrationsPopoverOpen: null,
monitorId: '',
- refreshInterval: AUTOREFRESH_INTERVAL_SECONDS,
- refreshPaused: AUTOREFRESH_IS_PAUSED,
};
export const uiReducer = createReducer(initialState, (builder) => {
@@ -66,12 +58,6 @@ export const uiReducer = createReducer(initialState, (builder) => {
})
.addCase(setSelectedMonitorId, (state, action) => {
state.monitorId = action.payload;
- })
- .addCase(setRefreshPausedAction, (state, action) => {
- state.refreshPaused = action.payload;
- })
- .addCase(setRefreshIntervalAction, (state, action) => {
- state.refreshInterval = action.payload;
});
});
diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/ui/selectors.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/ui/selectors.ts
index 4e365d8343555..f02b1fb564c37 100644
--- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/ui/selectors.ts
+++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/ui/selectors.ts
@@ -14,12 +14,3 @@ export const selectAlertFlyoutVisibility = createSelector(
uiStateSelector,
({ alertFlyoutVisible }) => alertFlyoutVisible
);
-
-export const selectRefreshPaused = createSelector(
- uiStateSelector,
- ({ refreshPaused }) => refreshPaused
-);
-export const selectRefreshInterval = createSelector(
- uiStateSelector,
- ({ refreshInterval }) => refreshInterval
-);
diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts
index b861fe36b9b96..aa52c54c21b78 100644
--- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts
+++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts
@@ -30,8 +30,6 @@ export const mockState: SyntheticsAppState = {
integrationsPopoverOpen: null,
searchText: '',
monitorId: '',
- refreshInterval: 60,
- refreshPaused: true,
},
serviceLocations: {
throttling: DEFAULT_THROTTLING,
diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/url_params/get_supported_url_params.test.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/url_params/get_supported_url_params.test.ts
index efabb2034e434..2a01b9d7aeefb 100644
--- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/url_params/get_supported_url_params.test.ts
+++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/url_params/get_supported_url_params.test.ts
@@ -51,12 +51,7 @@ describe('getSupportedUrlParams', () => {
it('returns default values', () => {
const { FILTERS, SEARCH, STATUS_FILTER } = CLIENT_DEFAULTS;
- const {
- DATE_RANGE_START,
- DATE_RANGE_END,
- AUTOREFRESH_INTERVAL_SECONDS,
- AUTOREFRESH_IS_PAUSED,
- } = CLIENT_DEFAULTS_SYNTHETICS;
+ const { DATE_RANGE_START, DATE_RANGE_END } = CLIENT_DEFAULTS_SYNTHETICS;
const result = getSupportedUrlParams({});
expect(result).toEqual({
absoluteDateRangeStart: MOCK_DATE_VALUE,
@@ -75,8 +70,6 @@ describe('getSupportedUrlParams', () => {
projects: [],
schedules: [],
tags: [],
- refreshInterval: AUTOREFRESH_INTERVAL_SECONDS,
- refreshPaused: AUTOREFRESH_IS_PAUSED,
});
});
diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/url_params/get_supported_url_params.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/url_params/get_supported_url_params.ts
index ce2eb6f30829f..8b4612b1e0f39 100644
--- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/url_params/get_supported_url_params.ts
+++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/url_params/get_supported_url_params.ts
@@ -7,8 +7,6 @@
import { MonitorOverviewState } from '../../state';
import { CLIENT_DEFAULTS_SYNTHETICS } from '../../../../../common/constants/synthetics/client_defaults';
-import { parseIsPaused } from './parse_is_paused';
-import { parseUrlInt } from './parse_url_int';
import { CLIENT_DEFAULTS } from '../../../../../common/constants';
import { parseAbsoluteDate } from './parse_absolute_date';
@@ -16,8 +14,6 @@ import { parseAbsoluteDate } from './parse_absolute_date';
export interface SyntheticsUrlParams {
absoluteDateRangeStart: number;
absoluteDateRangeEnd: number;
- refreshInterval: number;
- refreshPaused: boolean;
dateRangeStart: string;
dateRangeEnd: string;
pagination?: string;
@@ -43,8 +39,7 @@ export interface SyntheticsUrlParams {
const { ABSOLUTE_DATE_RANGE_START, ABSOLUTE_DATE_RANGE_END, SEARCH, FILTERS, STATUS_FILTER } =
CLIENT_DEFAULTS;
-const { DATE_RANGE_START, DATE_RANGE_END, AUTOREFRESH_INTERVAL_SECONDS, AUTOREFRESH_IS_PAUSED } =
- CLIENT_DEFAULTS_SYNTHETICS;
+const { DATE_RANGE_START, DATE_RANGE_END } = CLIENT_DEFAULTS_SYNTHETICS;
/**
* Gets the current URL values for the application. If no item is present
@@ -76,8 +71,6 @@ export const getSupportedUrlParams = (params: {
});
const {
- refreshInterval,
- refreshPaused,
dateRangeStart,
dateRangeEnd,
filters,
@@ -112,8 +105,6 @@ export const getSupportedUrlParams = (params: {
ABSOLUTE_DATE_RANGE_END,
{ roundUp: true }
),
- refreshInterval: parseUrlInt(refreshInterval, AUTOREFRESH_INTERVAL_SECONDS),
- refreshPaused: parseIsPaused(refreshPaused, AUTOREFRESH_IS_PAUSED),
dateRangeStart: dateRangeStart || DATE_RANGE_START,
dateRangeEnd: dateRangeEnd || DATE_RANGE_END,
filters: filters || FILTERS,
diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/url_params/stringify_url_params.test.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/url_params/stringify_url_params.test.ts
index 6f9ace8634d64..c8f8649fd56db 100644
--- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/url_params/stringify_url_params.test.ts
+++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/url_params/stringify_url_params.test.ts
@@ -12,8 +12,6 @@ describe('stringifyUrlParams', () => {
const result = stringifyUrlParams({
absoluteDateRangeStart: 1000,
absoluteDateRangeEnd: 2000,
- refreshInterval: 50000,
- refreshPaused: false,
dateRangeStart: 'now-15m',
dateRangeEnd: 'now',
filters: 'monitor.id: bar',
@@ -22,7 +20,7 @@ describe('stringifyUrlParams', () => {
statusFilter: 'up',
});
expect(result).toMatchInlineSnapshot(
- `"?absoluteDateRangeStart=1000&absoluteDateRangeEnd=2000&refreshInterval=50000&refreshPaused=false&dateRangeStart=now-15m&dateRangeEnd=now&filters=monitor.id%3A%20bar&focusConnectorField=true&search=monitor.id%3A%20foo&statusFilter=up"`
+ `"?absoluteDateRangeStart=1000&absoluteDateRangeEnd=2000&dateRangeStart=now-15m&dateRangeEnd=now&filters=monitor.id%3A%20bar&focusConnectorField=true&search=monitor.id%3A%20foo&statusFilter=up"`
);
});
@@ -31,8 +29,6 @@ describe('stringifyUrlParams', () => {
{
absoluteDateRangeStart: 1000,
absoluteDateRangeEnd: 2000,
- refreshInterval: 50000,
- refreshPaused: false,
dateRangeStart: 'now-15m',
dateRangeEnd: 'now',
filters: 'monitor.id: bar',
@@ -43,9 +39,7 @@ describe('stringifyUrlParams', () => {
},
true
);
- expect(result).toMatchInlineSnapshot(
- `"?refreshInterval=50000&dateRangeStart=now-15m&filters=monitor.id%3A%20bar"`
- );
+ expect(result).toMatchInlineSnapshot(`"?dateRangeStart=now-15m&filters=monitor.id%3A%20bar"`);
expect(result.includes('pagination')).toBeFalsy();
expect(result.includes('search')).toBeFalsy();
diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/url_params/stringify_url_params.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/url_params/stringify_url_params.ts
index 7f0dd94237593..7f465e7272dc6 100644
--- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/url_params/stringify_url_params.ts
+++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/utils/url_params/stringify_url_params.ts
@@ -12,8 +12,7 @@ import { CLIENT_DEFAULTS } from '../../../../../common/constants';
const { FOCUS_CONNECTOR_FIELD } = CLIENT_DEFAULTS;
-const { DATE_RANGE_START, DATE_RANGE_END, AUTOREFRESH_INTERVAL_SECONDS, AUTOREFRESH_IS_PAUSED } =
- CLIENT_DEFAULTS_SYNTHETICS;
+const { DATE_RANGE_START, DATE_RANGE_END } = CLIENT_DEFAULTS_SYNTHETICS;
export const stringifyUrlParams = (params: Partial, ignoreEmpty = false) => {
if (ignoreEmpty) {
@@ -41,12 +40,6 @@ const replaceDefaults = (params: Partial) => {
if (key === 'dateRangeEnd' && val === DATE_RANGE_END) {
delete params[key];
}
- if (key === 'refreshPaused' && val === AUTOREFRESH_IS_PAUSED) {
- delete params[key];
- }
- if (key === 'refreshInterval' && val === AUTOREFRESH_INTERVAL_SECONDS) {
- delete params[key];
- }
if (key === 'focusConnectorField' && val === FOCUS_CONNECTOR_FIELD) {
delete params[key];
}
From 010a362f34869f01ef39d21c0986d90cffc21f50 Mon Sep 17 00:00:00 2001
From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Date: Tue, 17 Sep 2024 02:55:22 +1000
Subject: [PATCH 033/139] skip failing test suite (#193036)
---
.../test_suites/common/index_management/inference_endpoints.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/test_serverless/api_integration/test_suites/common/index_management/inference_endpoints.ts b/x-pack/test_serverless/api_integration/test_suites/common/index_management/inference_endpoints.ts
index f5f712fc7d5a1..83ac02779b5f0 100644
--- a/x-pack/test_serverless/api_integration/test_suites/common/index_management/inference_endpoints.ts
+++ b/x-pack/test_serverless/api_integration/test_suites/common/index_management/inference_endpoints.ts
@@ -26,7 +26,8 @@ export default function ({ getService }: FtrProviderContext) {
let roleAuthc: RoleCredentials;
let internalReqHeader: InternalRequestHeader;
- describe('Inference endpoints', function () {
+ // Failing: See https://github.com/elastic/kibana/issues/193036
+ describe.skip('Inference endpoints', function () {
before(async () => {
roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin');
internalReqHeader = svlCommonApi.getInternalRequestHeader();
From 6680f35ef94286d34dd5c56e28575b31eab70836 Mon Sep 17 00:00:00 2001
From: Davis Plumlee <56367316+dplumlee@users.noreply.github.com>
Date: Mon, 16 Sep 2024 13:19:39 -0400
Subject: [PATCH 034/139] [Security Solution] Test plan for `query` fields diff
algorithm (#192529)
## Summary
Related ticket: https://github.com/elastic/kibana/issues/187658
Adds test plan for diff algorithm for arrays of scalar values
implemented here: https://github.com/elastic/kibana/pull/190179
### For maintainers
- [ ] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
---
.../upgrade_review_algorithms.md | 144 ++++++++++++------
1 file changed, 96 insertions(+), 48 deletions(-)
diff --git a/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/upgrade_review_algorithms.md b/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/upgrade_review_algorithms.md
index 26b01da200903..e65d366e0f44c 100644
--- a/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/upgrade_review_algorithms.md
+++ b/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/upgrade_review_algorithms.md
@@ -29,10 +29,11 @@ Status: `in progress`.
- [**Scenario: `ABC` - Rule field is an array of scalar values**](#scenario-abc---rule-field-is-an-array-of-scalar-values)
- [**Scenario: `ABC` - Rule field is a solvable `data_source` object**](#scenario-abc---rule-field-is-a-solvable-data_source-object)
- [**Scenario: `ABC` - Rule field is a non-solvable `data_source` object**](#scenario-abc---rule-field-is-a-non-solvable-data_source-object)
+ - [**Scenario: `ABC` - Rule field is a `kql_query`, `eql_query`, or `esql_query` object**](#scenario-abc---rule-field-is-a-kql_query-eql_query-or-esql_query-object)
- [Rule field has an update and a custom value that are the same and the rule base version doesn't exist - `-AA`](#rule-field-has-an-update-and-a-custom-value-that-are-the-same-and-the-rule-base-version-doesnt-exist----aa)
- [**Scenario: `-AA` - Rule field is any type**](#scenario--aa---rule-field-is-any-type)
- [Rule field has an update and a custom value that are NOT the same and the rule base version doesn't exist - `-AB`](#rule-field-has-an-update-and-a-custom-value-that-are-not-the-same-and-the-rule-base-version-doesnt-exist----ab)
- - [**Scenario: `-AB` - Rule field is a number or single line string**](#scenario--ab---rule-field-is-a-number-or-single-line-string)
+ - [**Scenario: `-AB` - Rule field is a number, single line string, multi line string, `data_source` object, `kql_query` object, `eql_query` object, or `esql_query` object**](#scenario--ab---rule-field-is-a-number-single-line-string-multi-line-string-data_source-object-kql_query-object-eql_query-object-or-esql_query-object)
- [**Scenario: `-AB` - Rule field is an array of scalar values**](#scenario--ab---rule-field-is-an-array-of-scalar-values)
- [**Scenario: `-AB` - Rule field is a solvable `data_source` object**](#scenario--ab---rule-field-is-a-solvable-data_source-object)
- [**Scenario: `-AB` - Rule field is a non-solvable `data_source` object**](#scenario--ab---rule-field-is-a-non-solvable-data_source-object)
@@ -58,6 +59,9 @@ Status: `in progress`.
- **Grouped fields**
- `data_source`: an object that contains a `type` field with a value of `data_view_id` or `index_patterns` and another field that's either `data_view_id` of type string OR `index_patterns` of type string array
+ - `kql_query`: an object that contains a `type` field with a value of `inline_query` or `saved_query` and other fields based on whichever type is defined. If it's `inline_query`, the object contains a `query` string field, a `language` field that's either `kuery` or `lucene`, and a `filters` field which is an array of kibana filters. If the type field is `saved_query`, the object only contains a `saved_query_id` string field.
+ - `eql_query`: an object that contains a `query` string field, a `language` field that always has the value: `eql`, and a `filters` field that contains an array of kibana filters.
+ - `esql_query`: an object that contains a `query` string field and a `language` field that always has the value: `esql`.
### Assumptions
@@ -70,7 +74,7 @@ Status: `in progress`.
#### **Scenario: `AAA` - Rule field is any type**
-**Automation**: 6 integration tests with mock rules + a set of unit tests for each algorithm
+**Automation**: 10 integration tests with mock rules + a set of unit tests for each algorithm
```Gherkin
Given field is not customized by the user (current version == base version)
@@ -80,20 +84,24 @@ And field should not be returned from the `upgrade/_review` API end
And field should not be shown in the upgrade preview UI
Examples:
-| algorithm | field_name | base_version | current_version | target_version | merged_version |
-| single line string | name | "A" | "A" | "A" | "A" |
-| multi line string | description | "My description.\nThis is a second line." | "My description.\nThis is a second line." | "My description.\nThis is a second line." | "My description.\nThis is a second line." |
-| number | risk_score | 1 | 1 | 1 | 1 |
-| array of scalars | tags | ["one", "two", "three"] | ["one", "three", "two"] | ["three", "one", "two"] | ["one", "three", "two"] |
-| data_source | data_source | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} |
-| data_source | data_source | {type: "data_view", "data_view_id": "A"} | {type: "data_view", "data_view_id": "A"} | {type: "data_view", "data_view_id": "A"} | {type: "data_view", "data_view_id": "A"} |
+| algorithm | field_name | base_version | current_version | target_version | merged_version |
+| single line string | name | "A" | "A" | "A" | "A" |
+| multi line string | description | "My description.\nThis is a second line." | "My description.\nThis is a second line." | "My description.\nThis is a second line." | "My description.\nThis is a second line." |
+| number | risk_score | 1 | 1 | 1 | 1 |
+| array of scalars | tags | ["one", "two", "three"] | ["one", "three", "two"] | ["three", "one", "two"] | ["one", "three", "two"] |
+| data_source | data_source | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} |
+| data_source | data_source | {type: "data_view", "data_view_id": "A"} | {type: "data_view", "data_view_id": "A"} | {type: "data_view", "data_view_id": "A"} | {type: "data_view", "data_view_id": "A"} |
+| kql_query | kql_query | {type: "inline_query", query: "query string = true", language: "kuery", filters: []} | {type: "inline_query", query: "query string = true", language: "kuery", filters: []} | {type: "inline_query", query: "query string = true", language: "kuery", filters: []} | {type: "inline_query", query: "query string = true", language: "kuery", filters: []} |
+| kql_query | kql_query | {type: "saved_query", saved_query_id: 'saved-query-id'} | {type: "saved_query", saved_query_id: 'saved-query-id'} | {type: "saved_query", saved_query_id: 'saved-query-id'} | {type: "saved_query", saved_query_id: 'saved-query-id'} |
+| eql_query | eql_query | {query: "query where true", language: "eql", filters: []} | {query: "query where true", language: "eql", filters: []} | {query: "query where true", language: "eql", filters: []} | {query: "query where true", language: "eql", filters: []} |
+| esql_query | esql_query | {query: "FROM query WHERE true", language: "esql"} | {query: "FROM query WHERE true", language: "esql"} | {query: "FROM query WHERE true", language: "esql"} | {query: "FROM query WHERE true", language: "esql"} |
```
### Rule field doesn't have an update but has a custom value - `ABA`
#### **Scenario: `ABA` - Rule field is any type**
-**Automation**: 6 integration tests with mock rules + a set of unit tests for each algorithm
+**Automation**: 10 integration tests with mock rules + a set of unit tests for each algorithm
```Gherkin
Given field is customized by the user (current version != base version)
@@ -103,20 +111,24 @@ And field should be returned from the `upgrade/_review` API endpoin
And field should be shown in the upgrade preview UI
Examples:
-| algorithm | field_name | base_version | current_version | target_version | merged_version |
-| single line string | name | "A" | "B" | "A" | "B" |
-| multi line string | description | "My description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | "My description.\nThis is a second line." | "My GREAT description.\nThis is a second line." |
-| number | risk_score | 1 | 2 | 1 | 2 |
-| array of scalars | tags | ["one", "two", "three"] | ["one", "two", "four"] | ["one", "two", "three"] | ["one", "two", "four"] |
-| data_source | data_source | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "data_view", "data_view_id": "A"} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "data_view", "data_view_id": "A"} |
-| data_source | data_source | {type: "data_view", "data_view_id": "A"} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "data_view", "data_view_id": "A"} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} |
+| algorithm | field_name | base_version | current_version | target_version | merged_version |
+| single line string | name | "A" | "B" | "A" | "B" |
+| multi line string | description | "My description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | "My description.\nThis is a second line." | "My GREAT description.\nThis is a second line." |
+| number | risk_score | 1 | 2 | 1 | 2 |
+| array of scalars | tags | ["one", "two", "three"] | ["one", "two", "four"] | ["one", "two", "three"] | ["one", "two", "four"] |
+| data_source | data_source | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "data_view", "data_view_id": "A"} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "data_view", "data_view_id": "A"} |
+| data_source | data_source | {type: "data_view", "data_view_id": "A"} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "data_view", "data_view_id": "A"} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} |
+| kql_query | kql_query | {type: "inline_query", query: "query string = true", language: "kuery", filters: []} | {type: "inline_query", query: "query string = false", language: "kuery", filters: []} | {type: "inline_query", query: "query string = true", language: "kuery", filters: []} | {type: "inline_query", query: "query string = false", language: "kuery", filters: []} |
+| kql_query | kql_query | {type: "saved_query", saved_query_id: 'saved-query-id'} | {type: "saved_query", saved_query_id: 'new-saved-query-id'} | {type: "saved_query", saved_query_id: 'saved-query-id'} | {type: "saved_query", saved_query_id: 'new-saved-query-id'} |
+| eql_query | eql_query | {query: "query where true", language: "eql", filters: []} | {query: "query where false", language: "eql", filters: []} | {query: "query where true", language: "eql", filters: []} | {query: "query where false", language: "eql", filters: []} |
+| esql_query | esql_query | {query: "FROM query WHERE true", language: "esql"} | {query: "FROM query WHERE false", language: "esql"} | {query: "FROM query WHERE true", language: "esql"} | {query: "FROM query WHERE false", language: "esql"} |
```
### Rule field has an update and doesn't have a custom value - `AAB`
#### **Scenario: `AAB` - Rule field is any type**
-**Automation**: 6 integration tests with mock rules + a set of unit tests for each algorithm
+**Automation**: 10 integration tests with mock rules + a set of unit tests for each algorithm
```Gherkin
Given field is not customized by the user (current version == base version)
@@ -126,20 +138,24 @@ And field should be returned from the `upgrade/_review` API endpoin
And field should be shown in the upgrade preview UI
Examples:
-| algorithm | field_name | base_version | current_version | target_version | merged_version |
-| single line string | name | "A" | "A" | "B" | "B" |
-| multi line string | description | "My description.\nThis is a second line." | "My description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | "My GREAT description.\nThis is a second line." |
-| number | risk_score | 1 | 1 | 2 | 2 |
-| array of scalars | tags | ["one", "two", "three"] | ["one", "two", "three"] | ["one", "two", "four"] | ["one", "two", "four"] |
-| data_source | data_source | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "data_view", "data_view_id": "A"} | {type: "data_view", "data_view_id": "A"} |
-| data_source | data_source | {type: "data_view", "data_view_id": "A"} | {type: "data_view", "data_view_id": "A"} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} |
+| algorithm | field_name | base_version | current_version | target_version | merged_version |
+| single line string | name | "A" | "A" | "B" | "B" |
+| multi line string | description | "My description.\nThis is a second line." | "My description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | "My GREAT description.\nThis is a second line." |
+| number | risk_score | 1 | 1 | 2 | 2 |
+| array of scalars | tags | ["one", "two", "three"] | ["one", "two", "three"] | ["one", "two", "four"] | ["one", "two", "four"] |
+| data_source | data_source | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "data_view", "data_view_id": "A"} | {type: "data_view", "data_view_id": "A"} |
+| data_source | data_source | {type: "data_view", "data_view_id": "A"} | {type: "data_view", "data_view_id": "A"} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} |
+| kql_query | kql_query | {type: "inline_query", query: "query string = true", language: "kuery", filters: []} | {type: "inline_query", query: "query string = true", language: "kuery", filters: []} | {type: "saved_query", saved_query_id: 'saved-query-id'} | {type: "saved_query", saved_query_id: 'saved-query-id'} |
+| kql_query | kql_query | {type: "saved_query", saved_query_id: 'saved-query-id'} | {type: "saved_query", saved_query_id: 'saved-query-id'} | {type: "inline_query", query: "query string = true", language: "kuery", filters: []} | {type: "inline_query", query: "query string = true", language: "kuery", filters: []} |
+| eql_query | eql_query | {query: "query where true", language: "eql", filters: []} | {query: "query where true", language: "eql", filters: []} | {query: "query where true", language: "eql", filters: [{ field: 'some query' }]} | {query: "query where true", language: "eql", filters: [{ field: 'some query' }]} |
+| esql_query | esql_query | {query: "FROM query WHERE true", language: "esql"} | {query: "FROM query WHERE true", language: "esql"} | {query: "FROM query WHERE false", language: "esql"} | {query: "FROM query WHERE false", language: "esql"} |
```
### Rule field has an update and a custom value that are the same - `ABB`
#### **Scenario: `ABB` - Rule field is any type**
-**Automation**: 6 integration tests with mock rules + a set of unit tests for each algorithm
+**Automation**: 10 integration tests with mock rules + a set of unit tests for each algorithm
```Gherkin
Given field is customized by the user (current version != base version)
@@ -150,13 +166,17 @@ And field should be returned from the `upgrade/_review` API endpoin
And field should be shown in the upgrade preview UI
Examples:
-| algorithm | field_name | base_version | current_version | target_version | merged_version |
-| single line string | name | "A" | "B" | "B" | "B" |
-| multi line string | description | "My description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | "My GREAT description.\nThis is a second line." |
-| number | risk_score | 1 | 2 | 2 | 2 |
-| array of scalars | tags | ["one", "two", "three"] | ["one", "two", "four"] | ["one", "two", "four"] | ["one", "two", "four"] |
-| data_source | data_source | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "data_view", "data_view_id": "A"} | {type: "data_view", "data_view_id": "A"} | {type: "data_view", "data_view_id": "A"} |
-| data_source | data_source | {type: "data_view", "data_view_id": "A"} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} |
+| algorithm | field_name | base_version | current_version | target_version | merged_version |
+| single line string | name | "A" | "B" | "B" | "B" |
+| multi line string | description | "My description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | "My GREAT description.\nThis is a second line." |
+| number | risk_score | 1 | 2 | 2 | 2 |
+| array of scalars | tags | ["one", "two", "three"] | ["one", "two", "four"] | ["one", "two", "four"] | ["one", "two", "four"] |
+| data_source | data_source | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "data_view", "data_view_id": "A"} | {type: "data_view", "data_view_id": "A"} | {type: "data_view", "data_view_id": "A"} |
+| data_source | data_source | {type: "data_view", "data_view_id": "A"} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} |
+| kql_query | kql_query | {type: "inline_query", query: "query string = true", language: "kuery", filters: []} | {type: "inline_query", query: "query string = true", language: "lucene", filters: []} | {type: "inline_query", query: "query string = true", language: "lucene", filters: []} | {type: "inline_query", query: "query string = true", language: "lucene", filters: []} |
+| kql_query | kql_query | {type: "saved_query", saved_query_id: 'saved-query-id'} | {type: "saved_query", saved_query_id: 'new-saved-query-id'} | {type: "saved_query", saved_query_id: 'new-saved-query-id'} | {type: "saved_query", saved_query_id: 'new-saved-query-id'} |
+| eql_query | eql_query | {query: "query where true", language: "eql", filters: []} | {query: "query where false", language: "eql", filters: []} | {query: "query where false", language: "eql", filters: []} | {query: "query where false", language: "eql", filters: []} |
+| esql_query | esql_query | {query: "FROM query WHERE true", language: "esql"} | {query: "FROM query WHERE false", language: "esql"} | {query: "FROM query WHERE false", language: "esql"} | {query: "FROM query WHERE false", language: "esql"} |
```
### Rule field has an update and a custom value that are NOT the same - `ABC`
@@ -284,11 +304,31 @@ Examples:
| data_source | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "data_view", "data_view_id": "A"} | {type: "index_patterns", "index_patterns": ["one", "two", "five"]} | {type: "data_view", "data_view_id": "A"} |
```
+#### **Scenario: `ABC` - Rule field is a `kql_query`, `eql_query`, or `esql_query` object**
+
+**Automation**: 4 integration tests with mock rules + a set of unit tests for the algorithms
+
+```Gherkin
+Given field is customized by the user (current version != base version)
+And field is updated by Elastic in this upgrade (target version != base version)
+And customized field is different than the Elastic update in this upgrade (current version != target version)
+Then for field the diff algorithm should output the current version as the merged one with a non-solvable conflict
+And field should be returned from the `upgrade/_review` API endpoint
+And