diff --git a/.buildkite/disabled_jest_configs.json b/.buildkite/disabled_jest_configs.json
index 4b37f3d9be6b6..a64c34ae741b4 100644
--- a/.buildkite/disabled_jest_configs.json
+++ b/.buildkite/disabled_jest_configs.json
@@ -1,4 +1,3 @@
[
- "x-pack/plugins/watcher/jest.config.js",
- "src/core/server/integration_tests/ui_settings/jest.integration.config.js"
+ "x-pack/plugins/watcher/jest.config.js"
]
diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml
index 1c466c001cb97..6c0ee435b5e20 100644
--- a/.buildkite/ftr_configs.yml
+++ b/.buildkite/ftr_configs.yml
@@ -252,6 +252,9 @@ enabled:
- x-pack/test/functional/apps/lens/group1/config.ts
- x-pack/test/functional/apps/lens/group2/config.ts
- x-pack/test/functional/apps/lens/group3/config.ts
+ - x-pack/test/functional/apps/lens/group4/config.ts
+ - x-pack/test/functional/apps/lens/group5/config.ts
+ - x-pack/test/functional/apps/lens/group6/config.ts
- x-pack/test/functional/apps/lens/open_in_lens/tsvb/config.ts
- x-pack/test/functional/apps/lens/open_in_lens/agg_based/config.ts
- x-pack/test/functional/apps/lens/open_in_lens/dashboard/config.ts
diff --git a/config/serverless.oblt.yml b/config/serverless.oblt.yml
index ba76648238348..945142d48d8db 100644
--- a/config/serverless.oblt.yml
+++ b/config/serverless.oblt.yml
@@ -1 +1,2 @@
+uiSettings.overrides.defaultRoute: /app/observability/overview
xpack.infra.logs.app_target: discover
diff --git a/docs/settings/alert-action-settings.asciidoc b/docs/settings/alert-action-settings.asciidoc
index 383fae89bad6b..ba41d20112ad1 100644
--- a/docs/settings/alert-action-settings.asciidoc
+++ b/docs/settings/alert-action-settings.asciidoc
@@ -1,10 +1,14 @@
-[role="xpack"]
[[alert-action-settings-kb]]
-=== Alerting and action settings in {kib}
+== Alerting and action settings in {kib}
++++
Alerting and action settings
++++
+:description: Learn about the settings that affect {kib} {alert-features}.
+:tags-products: [kibana, alerting]
+:tags-content-type: [reference]
+:tags-user-goals: [configure]
+
Alerting and actions are enabled by default in {kib}, but require you to configure the following:
. <>.
@@ -15,7 +19,7 @@ You can configure the following settings in the `kibana.yml` file.
[float]
[[general-alert-action-settings]]
-==== General settings
+=== General settings
`xpack.encryptedSavedObjects.encryptionKey`::
A string of 32 or more characters used to encrypt sensitive properties on alerting rules and actions before they're stored in {es}. Third party credentials — such as the username and password used to connect to an SMTP service — are an example of encrypted properties.
@@ -29,7 +33,7 @@ Be sure to back up the encryption key value somewhere safe, as your alerting rul
[float]
[[action-settings]]
-==== Action settings
+=== Action settings
`xpack.actions.allowedHosts` {ess-icon}::
A list of hostnames that {kib} is allowed to connect to when built-in actions are triggered. It defaults to `[*]`, allowing any host, but keep in mind the potential for SSRF attacks when hosts are not explicitly added to the allowed hosts. An empty list `[]` can be used to block built-in actions from making any external connections.
@@ -71,7 +75,7 @@ xpack.actions.customHostSettings:
ssl:
verificationMode: 'none'
--
-
++
The settings in `xpack.actions.customHostSettings` can be used to override the
global option `xpack.actions.ssl.verificationMode` and provide customized TLS
settings on a per-server basis. Set `xpack.actions.ssl.verificationMode` to the
@@ -107,7 +111,7 @@ The options `smtp.ignoreTLS` and `smtp.requireTLS` can not both be set to true.
Default: `false`.
`xpack.actions.customHostSettings[n].ssl.rejectUnauthorized`::
-Deprecated. Use <> instead. A boolean value indicating whether to bypass server certificate validation.
+deprecated:[8.0.0] Use <> instead. A boolean value indicating whether to bypass server certificate validation.
Overrides the general `xpack.actions.rejectUnauthorized` configuration
for requests made for this hostname/port.
@@ -127,7 +131,7 @@ the files cannot be made available.
[[action-config-email-domain-allowlist]] `xpack.actions.email.domain_allowlist` {ess-icon}::
A list of allowed email domains which can be used with the email connector. When this setting is not used, all email domains are allowed. When this setting is used, if any email is attempted to be sent that (a) includes an addressee with an email domain that is not in the allowlist, or (b) includes a from address domain that is not in the allowlist, it will fail with a message indicating the email is not allowed.
-
++
WARNING: This feature is available in {kib} 7.17.4 and 8.3.0 onwards but is not supported in {kib} 8.0, 8.1 or 8.2. As such, this setting should be removed before upgrading from 7.17 to 8.0, 8.1 or 8.2. It is possible to configure the settings in 7.17.4 and then upgrade to 8.3.0 directly.
`xpack.actions.enableFooterInEmail` {ess-icon}::
@@ -160,8 +164,6 @@ proxy in tunneling mode, and display some of the interaction between the client
--
curl --verbose --proxytunnel --proxy http://localhost:8080 http://example.com
--
-+
-
`xpack.actions.proxyBypassHosts` {ess-icon}::
Specifies hostnames which should not use the proxy, if using a proxy for actions. The value is an array of hostnames as strings. By default, all hosts will use the proxy, but if an action's hostname is in this list, the proxy will not be used. The settings `xpack.actions.proxyBypassHosts` and `xpack.actions.proxyOnlyHosts` cannot be used at the same time.
@@ -173,14 +175,14 @@ Specifies hostnames which should only use the proxy, if using a proxy for action
Specifies HTTP headers for the proxy, if using a proxy for actions. Default: {}.
`xpack.actions.proxyRejectUnauthorizedCertificates` {ess-icon}::
-Deprecated. Use <> instead. Set to `false` to bypass certificate validation for the proxy, if using a proxy for actions. Default: `true`.
+deprecated:[8.0.0] Use <> instead. Set to `false` to bypass certificate validation for the proxy, if using a proxy for actions. Default: `true`.
[[action-config-proxy-verification-mode]]`xpack.actions.ssl.proxyVerificationMode` {ess-icon}::
Controls the verification for the proxy server certificate that Kibana receives when making an outbound SSL/TLS connection to the proxy server. Valid values are `full`, `certificate`, and `none`.
Use `full` to perform hostname verification, `certificate` to skip hostname verification, and `none` to skip verification. Default: `full`. <>.
`xpack.actions.rejectUnauthorized` {ess-icon}::
-Deprecated. Use <> instead. Set to `false` to bypass certificate validation for actions. Default: `true`.
+deprecated:[8.0.0] Use <> instead. Set to `false` to bypass certificate validation for actions. Default: `true`.
+
As an alternative to setting `xpack.actions.rejectUnauthorized`, you can use the setting
`xpack.actions.customHostSettings` to set SSL options for specific servers.
@@ -206,9 +208,8 @@ For example, `20m`, `24h`, `7d`, `1w`. Default: `60s`.
Specifies the maximum number of times an action can be attempted to run. Can be minimum 1 and maximum 10.
`xpack.actions.run.connectorTypeOverrides` {ess-icon}::
-Overrides the configs under `xpack.actions.run` for the connector type with the given ID. List the connector type identifier and its settings in an array of objects.
+Overrides the configs under `xpack.actions.run` for the connector type with the given ID. List the connector type identifier and its settings in an array of objects. For example:
+
-For example:
[source,yaml]
--
xpack.actions.run:
@@ -220,7 +221,7 @@ xpack.actions.run:
[float]
[[alert-settings]]
-==== Alerting settings
+=== Alerting settings
`xpack.alerting.maxEphemeralActionsPerAlert` {ess-icon}::
deprecated:[8.8.0]
@@ -257,9 +258,8 @@ Specifies the default timeout for tasks associated with all types of rules. The
For example, `20m`, `24h`, `7d`, `1w`. Default: `5m`.
`xpack.alerting.rules.run.ruleTypeOverrides` {ess-icon}::
-Overrides the configs under `xpack.alerting.rules.run` for the rule type with the given ID. List the rule identifier and its settings in an array of objects.
+Overrides the configs under `xpack.alerting.rules.run` for the rule type with the given ID. List the rule identifier and its settings in an array of objects. For example:
+
-For example:
[source,yaml]
--
xpack.alerting.rules.run:
@@ -270,9 +270,8 @@ xpack.alerting.rules.run:
--
`xpack.alerting.rules.run.actions.connectorTypeOverrides` {ess-icon}::
-Overrides the configs under `xpack.alerting.rules.run.actions` for the connector type with the given ID. List the connector type identifier and its settings in an array of objects.
+Overrides the configs under `xpack.alerting.rules.run.actions` for the connector type with the given ID. List the connector type identifier and its settings in an array of objects. For example:
+
-For example:
[source,yaml]
--
xpack.alerting.rules.run:
diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc
index ca8eaf10583af..efa58a34d4853 100644
--- a/docs/setup/settings.asciidoc
+++ b/docs/setup/settings.asciidoc
@@ -619,7 +619,7 @@ Set this value to false to disable the Upgrade Assistant UI. *Default: true*
Set this value to change the {kib} interface language.
Valid locales are: `en`, `zh-CN`, `ja-JP`. *Default: `en`*
-include::{kib-repo-dir}/settings/alert-action-settings.asciidoc[]
+include::{kib-repo-dir}/settings/alert-action-settings.asciidoc[leveloffset=+1]
include::{kib-repo-dir}/settings/apm-settings.asciidoc[]
include::{kib-repo-dir}/settings/banners-settings.asciidoc[]
include::{kib-repo-dir}/settings/cases-settings.asciidoc[leveloffset=+1]
diff --git a/examples/search_examples/server/fibonacci_strategy.ts b/examples/search_examples/server/fibonacci_strategy.ts
index 8317834dcbb3d..8a33872cdb7e7 100644
--- a/examples/search_examples/server/fibonacci_strategy.ts
+++ b/examples/search_examples/server/fibonacci_strategy.ts
@@ -8,6 +8,7 @@
import { v4 as uuidv4 } from 'uuid';
import { ISearchStrategy } from '@kbn/data-plugin/server';
+import { of } from 'rxjs';
import { FibonacciRequest, FibonacciResponse } from '../common/types';
export const fibonacciStrategyProvider = (): ISearchStrategy<
@@ -40,10 +41,7 @@ export const fibonacciStrategyProvider = (): ISearchStrategy<
const took = Date.now() - started;
const values = sequence.slice(0, loaded);
- // Usually we'd do something like "of()" but for some reason it breaks in tests with the error
- // "You provided an invalid object where a stream was expected." which is why we have to cast
- // down below as well
- return [{ id, loaded, total, isRunning, isPartial, rawResponse: { took, values } }];
+ return of({ id, loaded, total, isRunning, isPartial, rawResponse: { took, values } });
},
cancel: async (id: string) => {
responseMap.delete(id);
diff --git a/fleet_packages.json b/fleet_packages.json
index 34d807ac46b9c..c80f68372a72c 100644
--- a/fleet_packages.json
+++ b/fleet_packages.json
@@ -52,6 +52,6 @@
},
{
"name": "security_detection_engine",
- "version": "8.7.2"
+ "version": "8.8.1"
}
]
\ No newline at end of file
diff --git a/package.json b/package.json
index 168a93700492c..7946fc7b4701b 100644
--- a/package.json
+++ b/package.json
@@ -841,7 +841,7 @@
"minimatch": "^3.1.2",
"moment": "^2.29.4",
"moment-duration-format": "^2.3.2",
- "moment-timezone": "^0.5.34",
+ "moment-timezone": "^0.5.43",
"monaco-editor": "^0.24.0",
"monaco-yaml": "3.2.1",
"mustache": "^2.3.2",
diff --git a/packages/core/root/core-root-server-internal/src/bootstrap.test.mocks.ts b/packages/core/root/core-root-server-internal/src/bootstrap.test.mocks.ts
new file mode 100644
index 0000000000000..07277d565f694
--- /dev/null
+++ b/packages/core/root/core-root-server-internal/src/bootstrap.test.mocks.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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { Env } from '@kbn/config';
+import { rawConfigServiceMock, configServiceMock } from '@kbn/config-mocks';
+
+export const mockConfigService = configServiceMock.create();
+export const mockRawConfigService = rawConfigServiceMock.create();
+export const mockRawConfigServiceConstructor = jest.fn(() => mockRawConfigService);
+jest.doMock('@kbn/config', () => ({
+ ConfigService: jest.fn(() => mockConfigService),
+ Env,
+ RawConfigService: jest.fn(mockRawConfigServiceConstructor),
+}));
+
+jest.doMock('./root', () => ({
+ Root: jest.fn(() => ({
+ shutdown: jest.fn(),
+ })),
+}));
diff --git a/packages/core/root/core-root-server-internal/src/bootstrap.test.ts b/packages/core/root/core-root-server-internal/src/bootstrap.test.ts
new file mode 100644
index 0000000000000..1bd413314aa98
--- /dev/null
+++ b/packages/core/root/core-root-server-internal/src/bootstrap.test.ts
@@ -0,0 +1,61 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { of } from 'rxjs';
+import type { CliArgs } from '@kbn/config';
+
+import { mockRawConfigService, mockRawConfigServiceConstructor } from './bootstrap.test.mocks';
+
+jest.mock('@kbn/core-logging-server-internal');
+
+import { bootstrap } from './bootstrap';
+
+const bootstrapCfg = {
+ configs: ['config/kibana.yml'],
+ cliArgs: {} as unknown as CliArgs,
+ applyConfigOverrides: () => ({}),
+};
+
+describe('bootstrap', () => {
+ describe('serverless', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ test('should load additional serverless files for a valid project', async () => {
+ mockRawConfigService.getConfig$.mockReturnValue(of({ serverless: 'es' }));
+ await bootstrap(bootstrapCfg);
+ expect(mockRawConfigServiceConstructor).toHaveBeenCalledTimes(2);
+ expect(mockRawConfigServiceConstructor).toHaveBeenNthCalledWith(
+ 1,
+ bootstrapCfg.configs,
+ bootstrapCfg.applyConfigOverrides
+ );
+ expect(mockRawConfigServiceConstructor).toHaveBeenNthCalledWith(
+ 2,
+ [
+ expect.stringContaining('config/serverless.yml'),
+ expect.stringContaining('config/serverless.es.yml'),
+ ...bootstrapCfg.configs,
+ ],
+ bootstrapCfg.applyConfigOverrides
+ );
+ });
+
+ test('should skip loading the serverless files for an invalid project', async () => {
+ mockRawConfigService.getConfig$.mockReturnValue(of({ serverless: 'not-valid' }));
+ await bootstrap(bootstrapCfg);
+ expect(mockRawConfigServiceConstructor).toHaveBeenCalledTimes(1);
+ expect(mockRawConfigServiceConstructor).toHaveBeenNthCalledWith(
+ 1,
+ bootstrapCfg.configs,
+ bootstrapCfg.applyConfigOverrides
+ );
+ });
+ });
+});
diff --git a/packages/core/root/core-root-server-internal/src/bootstrap.ts b/packages/core/root/core-root-server-internal/src/bootstrap.ts
index 3b5a340a7b79c..3f55b6493a6bd 100644
--- a/packages/core/root/core-root-server-internal/src/bootstrap.ts
+++ b/packages/core/root/core-root-server-internal/src/bootstrap.ts
@@ -7,9 +7,14 @@
*/
import chalk from 'chalk';
+import { firstValueFrom } from 'rxjs';
import { getPackages } from '@kbn/repo-packages';
import { CliArgs, Env, RawConfigService } from '@kbn/config';
import { CriticalError } from '@kbn/core-base-server-internal';
+import { resolve } from 'path';
+import { getConfigDirectory } from '@kbn/utils';
+import { statSync } from 'fs';
+import { VALID_SERVERLESS_PROJECT_TYPES } from './root/serverless_config';
import { Root } from './root';
import { MIGRATION_EXCEPTION_CODE } from './constants';
@@ -38,15 +43,40 @@ export async function bootstrap({ configs, cliArgs, applyConfigOverrides }: Boot
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { REPO_ROOT } = require('@kbn/repo-info');
- const env = Env.createDefault(REPO_ROOT, {
+ let env = Env.createDefault(REPO_ROOT, {
configs,
cliArgs,
repoPackages: getPackages(REPO_ROOT),
});
- const rawConfigService = new RawConfigService(env.configs, applyConfigOverrides);
+ let rawConfigService = new RawConfigService(env.configs, applyConfigOverrides);
rawConfigService.loadConfig();
+ // Hack to load the extra serverless config files if `serverless: {projectType}` is found in it.
+ const rawConfig = await firstValueFrom(rawConfigService.getConfig$());
+ const serverlessProjectType = rawConfig?.serverless;
+ if (
+ typeof serverlessProjectType === 'string' &&
+ VALID_SERVERLESS_PROJECT_TYPES.includes(serverlessProjectType)
+ ) {
+ const extendedConfigs = [
+ ...['serverless.yml', `serverless.${serverlessProjectType}.yml`]
+ .map((name) => resolve(getConfigDirectory(), name))
+ .filter(configFileExists),
+ ...configs,
+ ];
+
+ env = Env.createDefault(REPO_ROOT, {
+ configs: extendedConfigs,
+ cliArgs: { ...cliArgs, serverless: true },
+ repoPackages: getPackages(REPO_ROOT),
+ });
+
+ rawConfigService.stop();
+ rawConfigService = new RawConfigService(env.configs, applyConfigOverrides);
+ rawConfigService.loadConfig();
+ }
+
const root = new Root(rawConfigService, env, onRootShutdown);
process.on('SIGHUP', () => reloadConfiguration());
@@ -128,3 +158,15 @@ function onRootShutdown(reason?: any) {
process.exit(0);
}
+
+function configFileExists(path: string) {
+ try {
+ return statSync(path).isFile();
+ } catch (err) {
+ if (err.code === 'ENOENT') {
+ return false;
+ }
+
+ throw err;
+ }
+}
diff --git a/packages/core/root/core-root-server-internal/src/register_service_config.ts b/packages/core/root/core-root-server-internal/src/register_service_config.ts
index a22ea56f25ee9..f646f9e538ae8 100644
--- a/packages/core/root/core-root-server-internal/src/register_service_config.ts
+++ b/packages/core/root/core-root-server-internal/src/register_service_config.ts
@@ -28,6 +28,7 @@ import { uiSettingsConfig } from '@kbn/core-ui-settings-server-internal';
import { config as pluginsConfig } from '@kbn/core-plugins-server-internal';
import { elasticApmConfig } from './root/elastic_config';
+import { serverlessConfig } from './root/serverless_config';
const rootConfigPath = '';
@@ -49,6 +50,7 @@ export function registerServiceConfig(configService: ConfigService) {
pluginsConfig,
savedObjectsConfig,
savedObjectsMigrationConfig,
+ serverlessConfig,
statusConfig,
uiSettingsConfig,
];
diff --git a/packages/core/root/core-root-server-internal/src/root/serverless_config.ts b/packages/core/root/core-root-server-internal/src/root/serverless_config.ts
new file mode 100644
index 0000000000000..351065f7d83df
--- /dev/null
+++ b/packages/core/root/core-root-server-internal/src/root/serverless_config.ts
@@ -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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { schema, TypeOf, Type } from '@kbn/config-schema';
+import { ServiceConfigDescriptor } from '@kbn/core-base-server-internal';
+
+// Config validation for how to run Kibana in Serverless mode.
+// Clients need to specify the project type to run in.
+// Going for a simple `serverless` string because it serves as
+// a direct replacement to the legacy --serverless CLI flag.
+// If we even decide to extend this further, and converting it into an object,
+// BWC can be ensured by adding the object definition as another alternative to `schema.oneOf`.
+
+export const VALID_SERVERLESS_PROJECT_TYPES = ['es', 'oblt', 'security'];
+
+const serverlessConfigSchema = schema.maybe(
+ schema.oneOf(
+ VALID_SERVERLESS_PROJECT_TYPES.map((projectName) => schema.literal(projectName)) as [
+ Type // This cast is needed because it's different to Type[] :sight:
+ ]
+ )
+);
+
+export type ServerlessConfigType = TypeOf;
+
+export const serverlessConfig: ServiceConfigDescriptor = {
+ path: 'serverless',
+ schema: serverlessConfigSchema,
+};
diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts
index 29ac5472e37dd..89b3610e622b9 100644
--- a/packages/kbn-doc-links/src/get_doc_links.ts
+++ b/packages/kbn-doc-links/src/get_doc_links.ts
@@ -126,6 +126,7 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => {
apiKeys: `${KIBANA_DOCS}api-keys.html`,
behavioralAnalytics: `${ENTERPRISE_SEARCH_DOCS}analytics-overview.html`,
behavioralAnalyticsEvents: `${ENTERPRISE_SEARCH_DOCS}analytics-events.html`,
+ buildConnector: `{$ENTERPRISE_SEARCH_DOCS}build-connector.html`,
bulkApi: `${ELASTICSEARCH_DOCS}docs-bulk.html`,
configuration: `${ENTERPRISE_SEARCH_DOCS}configuration.html`,
connectors: `${ENTERPRISE_SEARCH_DOCS}connectors.html`,
diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts
index efe5e95f238d0..5e2e8e7bf1304 100644
--- a/packages/kbn-doc-links/src/types.ts
+++ b/packages/kbn-doc-links/src/types.ts
@@ -111,6 +111,7 @@ export interface DocLinks {
readonly apiKeys: string;
readonly behavioralAnalytics: string;
readonly behavioralAnalyticsEvents: string;
+ readonly buildConnector: string;
readonly bulkApi: string;
readonly configuration: string;
readonly connectors: string;
diff --git a/packages/kbn-es-query/src/kuery/node_types/node_builder.test.ts b/packages/kbn-es-query/src/kuery/node_types/node_builder.test.ts
index 46e21245bf333..d414788afec1e 100644
--- a/packages/kbn-es-query/src/kuery/node_types/node_builder.test.ts
+++ b/packages/kbn-es-query/src/kuery/node_types/node_builder.test.ts
@@ -55,6 +55,25 @@ describe('nodeBuilder', () => {
});
describe('and method', () => {
+ test('no clauses', () => {
+ const node = nodeBuilder.and([]);
+ const query = toElasticsearchQuery(node);
+ expect(node).toMatchInlineSnapshot(`
+ Object {
+ "arguments": Array [],
+ "function": "and",
+ "type": "function",
+ }
+ `);
+ expect(query).toMatchInlineSnapshot(`
+ Object {
+ "bool": Object {
+ "filter": Array [],
+ },
+ }
+ `);
+ });
+
test('single clause', () => {
const nodes = [nodeBuilder.is('foo', 'bar')];
const query = toElasticsearchQuery(nodeBuilder.and(nodes));
@@ -166,6 +185,26 @@ describe('nodeBuilder', () => {
});
describe('or method', () => {
+ test('no clauses', () => {
+ const node = nodeBuilder.or([]);
+ const query = toElasticsearchQuery(node);
+ expect(node).toMatchInlineSnapshot(`
+ Object {
+ "arguments": Array [],
+ "function": "or",
+ "type": "function",
+ }
+ `);
+ expect(query).toMatchInlineSnapshot(`
+ Object {
+ "bool": Object {
+ "minimum_should_match": 1,
+ "should": Array [],
+ },
+ }
+ `);
+ });
+
test('single clause', () => {
const nodes = [nodeBuilder.is('foo', 'bar')];
const query = toElasticsearchQuery(nodeBuilder.or(nodes));
diff --git a/packages/kbn-es-query/src/kuery/node_types/node_builder.ts b/packages/kbn-es-query/src/kuery/node_types/node_builder.ts
index 948985c965378..80fcd9607c7ab 100644
--- a/packages/kbn-es-query/src/kuery/node_types/node_builder.ts
+++ b/packages/kbn-es-query/src/kuery/node_types/node_builder.ts
@@ -10,23 +10,23 @@ import type { RangeFilterParams } from '../../filters';
import { KueryNode, nodeTypes } from '../types';
export const nodeBuilder = {
- is: (fieldName: string, value: string | KueryNode) => {
+ is: (fieldName: string, value: string | KueryNode): KueryNode => {
return nodeTypes.function.buildNodeWithArgumentNodes('is', [
nodeTypes.literal.buildNode(fieldName),
typeof value === 'string' ? nodeTypes.literal.buildNode(value) : value,
]);
},
or: (nodes: KueryNode[]): KueryNode => {
- return nodes.length > 1 ? nodeTypes.function.buildNode('or', nodes) : nodes[0];
+ return nodes.length === 1 ? nodes[0] : nodeTypes.function.buildNode('or', nodes);
},
and: (nodes: KueryNode[]): KueryNode => {
- return nodes.length > 1 ? nodeTypes.function.buildNode('and', nodes) : nodes[0];
+ return nodes.length === 1 ? nodes[0] : nodeTypes.function.buildNode('and', nodes);
},
range: (
fieldName: string,
operator: keyof Pick,
value: number | string
- ) => {
+ ): KueryNode => {
return nodeTypes.function.buildNodeWithArgumentNodes('range', [
nodeTypes.literal.buildNode(fieldName),
operator,
diff --git a/packages/kbn-securitysolution-grouping/src/components/accordion_panel/helpers.ts b/packages/kbn-securitysolution-grouping/src/components/accordion_panel/helpers.ts
index 5529d5bf521c0..14d7c44daf1f1 100644
--- a/packages/kbn-securitysolution-grouping/src/components/accordion_panel/helpers.ts
+++ b/packages/kbn-securitysolution-grouping/src/components/accordion_panel/helpers.ts
@@ -30,3 +30,22 @@ export const createGroupFilter = (selectedGroup: string, query?: string) =>
},
]
: [];
+
+export const getNullGroupFilter = (selectedGroup: string) => [
+ {
+ meta: {
+ disabled: false,
+ negate: true,
+ alias: null,
+ key: selectedGroup,
+ field: selectedGroup,
+ value: 'exists',
+ type: 'exists',
+ },
+ query: {
+ exists: {
+ field: selectedGroup,
+ },
+ },
+ },
+];
diff --git a/packages/kbn-securitysolution-grouping/src/components/accordion_panel/index.tsx b/packages/kbn-securitysolution-grouping/src/components/accordion_panel/index.tsx
index c1d55495cf785..f40fc79eb8c80 100644
--- a/packages/kbn-securitysolution-grouping/src/components/accordion_panel/index.tsx
+++ b/packages/kbn-securitysolution-grouping/src/components/accordion_panel/index.tsx
@@ -6,12 +6,12 @@
* Side Public License, v 1.
*/
-import { EuiAccordion, EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui';
+import { EuiAccordion, EuiFlexGroup, EuiFlexItem, EuiTitle, EuiIconTip } from '@elastic/eui';
import type { Filter } from '@kbn/es-query';
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import { firstNonNullValue } from '../../helpers';
import type { RawBucket } from '../types';
-import { createGroupFilter } from './helpers';
+import { createGroupFilter, getNullGroupFilter } from './helpers';
interface GroupPanelProps {
customAccordionButtonClassName?: string;
@@ -22,20 +22,35 @@ interface GroupPanelProps {
groupPanelRenderer?: JSX.Element;
groupingLevel?: number;
isLoading: boolean;
+ isNullGroup?: boolean;
+ nullGroupMessage?: string;
onGroupClose: () => void;
onToggleGroup?: (isOpen: boolean, groupBucket: RawBucket) => void;
renderChildComponent: (groupFilter: Filter[]) => React.ReactElement;
selectedGroup: string;
}
-const DefaultGroupPanelRenderer = ({ title }: { title: string }) => (
+const DefaultGroupPanelRenderer = ({
+ isNullGroup,
+ title,
+ nullGroupMessage,
+}: {
+ isNullGroup: boolean;
+ title: string;
+ nullGroupMessage?: string;
+}) => (
-
+
{title}
+ {isNullGroup && nullGroupMessage && (
+
+
+
+ )}
);
@@ -49,10 +64,12 @@ const GroupPanelComponent = ({
groupPanelRenderer,
groupingLevel = 0,
isLoading,
+ isNullGroup = false,
onGroupClose,
onToggleGroup,
renderChildComponent,
selectedGroup,
+ nullGroupMessage,
}: GroupPanelProps) => {
const lastForceState = useRef(forceState);
useEffect(() => {
@@ -68,8 +85,11 @@ const GroupPanelComponent = ({
const groupFieldValue = useMemo(() => firstNonNullValue(groupBucket.key), [groupBucket.key]);
const groupFilters = useMemo(
- () => createGroupFilter(selectedGroup, groupFieldValue),
- [groupFieldValue, selectedGroup]
+ () =>
+ isNullGroup
+ ? getNullGroupFilter(selectedGroup)
+ : createGroupFilter(selectedGroup, groupFieldValue),
+ [groupFieldValue, isNullGroup, selectedGroup]
);
const onToggle = useCallback(
@@ -86,7 +106,13 @@ const GroupPanelComponent = ({
buttonClassName={customAccordionButtonClassName}
buttonContent={
- {groupPanelRenderer ?? }
+ {groupPanelRenderer ?? (
+
+ )}
}
buttonElement="div"
diff --git a/packages/kbn-securitysolution-grouping/src/components/grouping.mock.tsx b/packages/kbn-securitysolution-grouping/src/components/grouping.mock.tsx
index 0647c14485fad..412644910db99 100644
--- a/packages/kbn-securitysolution-grouping/src/components/grouping.mock.tsx
+++ b/packages/kbn-securitysolution-grouping/src/components/grouping.mock.tsx
@@ -8,10 +8,8 @@
import React from 'react';
import { EuiContextMenuItem } from '@elastic/eui';
-export const rule1Name = 'Rule 1 name';
-const rule1Desc = 'Rule 1 description';
-export const rule2Name = 'Rule 2 name';
-const rule2Desc = 'Rule 2 description';
+export const host1Name = 'nice-host';
+export const host2Name = 'cool-host';
export const mockGroupingProps = {
activePage: 0,
@@ -24,13 +22,13 @@ export const mockGroupingProps = {
sum_other_doc_count: 0,
buckets: [
{
- key: [rule1Name, rule1Desc],
- key_as_string: `${rule1Name}|${rule1Desc}`,
+ key: [host1Name],
+ key_as_string: `${host1Name}`,
doc_count: 1,
hostsCountAggregation: {
value: 1,
},
- ruleTags: {
+ hostTags: {
doc_count_error_upper_bound: 0,
sum_other_doc_count: 0,
buckets: [],
@@ -56,13 +54,13 @@ export const mockGroupingProps = {
},
},
{
- key: [rule2Name, rule2Desc],
- key_as_string: `${rule2Name}|${rule2Desc}`,
+ key: [host2Name],
+ key_as_string: `${host2Name}`,
doc_count: 1,
hostsCountAggregation: {
value: 1,
},
- ruleTags: {
+ hostTags: {
doc_count_error_upper_bound: 0,
sum_other_doc_count: 0,
buckets: [],
@@ -87,10 +85,43 @@ export const mockGroupingProps = {
value: 1,
},
},
+ {
+ key: ['-'],
+ key_as_string: `-`,
+ isNullGroup: true,
+ doc_count: 11,
+ hostsCountAggregation: {
+ value: 11,
+ },
+ hostTags: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [],
+ },
+ alertsCount: {
+ value: 11,
+ },
+ severitiesSubAggregation: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'low',
+ doc_count: 11,
+ },
+ ],
+ },
+ countSeveritySubAggregation: {
+ value: 11,
+ },
+ usersCountAggregation: {
+ value: 11,
+ },
+ },
],
},
unitsCount: {
- value: 2,
+ value: 3,
},
},
groupingId: 'test-grouping-id',
@@ -98,7 +129,7 @@ export const mockGroupingProps = {
itemsPerPage: 25,
renderChildComponent: () => {'child component'}
,
onGroupClose: () => {},
- selectedGroup: 'kibana.alert.rule.name',
+ selectedGroup: 'host.name',
takeActionItems: () => [
{}}>
{'Mark as acknowledged'}
diff --git a/packages/kbn-securitysolution-grouping/src/components/grouping.stories.tsx b/packages/kbn-securitysolution-grouping/src/components/grouping.stories.tsx
index b961402ee3a7c..ed08d8541d0e8 100644
--- a/packages/kbn-securitysolution-grouping/src/components/grouping.stories.tsx
+++ b/packages/kbn-securitysolution-grouping/src/components/grouping.stories.tsx
@@ -23,6 +23,6 @@ export default {
},
};
-export const Emtpy: Story = () => {
+export const Empty: Story = () => {
return ;
};
diff --git a/packages/kbn-securitysolution-grouping/src/components/grouping.test.tsx b/packages/kbn-securitysolution-grouping/src/components/grouping.test.tsx
index 2376614ab444c..151b83e45cfd5 100644
--- a/packages/kbn-securitysolution-grouping/src/components/grouping.test.tsx
+++ b/packages/kbn-securitysolution-grouping/src/components/grouping.test.tsx
@@ -10,11 +10,11 @@ import { fireEvent, render, within } from '@testing-library/react';
import React from 'react';
import { I18nProvider } from '@kbn/i18n-react';
import { Grouping } from './grouping';
-import { createGroupFilter } from './accordion_panel/helpers';
+import { createGroupFilter, getNullGroupFilter } from './accordion_panel/helpers';
import { METRIC_TYPE } from '@kbn/analytics';
import { getTelemetryEvent } from '../telemetry/const';
-import { mockGroupingProps, rule1Name, rule2Name } from './grouping.mock';
+import { mockGroupingProps, host1Name, host2Name } from './grouping.mock';
const renderChildComponent = jest.fn();
const takeActionItems = jest.fn();
@@ -37,9 +37,9 @@ describe('grouping container', () => {
);
- expect(getByTestId('unit-count').textContent).toBe('2 events');
- expect(getByTestId('group-count').textContent).toBe('2 groups');
- expect(getAllByTestId('grouping-accordion').length).toBe(2);
+ expect(getByTestId('unit-count').textContent).toBe('14 events');
+ expect(getByTestId('group-count').textContent).toBe('3 groups');
+ expect(getAllByTestId('grouping-accordion').length).toBe(3);
expect(queryByTestId('empty-results-panel')).not.toBeInTheDocument();
});
@@ -79,12 +79,12 @@ describe('grouping container', () => {
fireEvent.click(group1);
expect(renderChildComponent).toHaveBeenNthCalledWith(
1,
- createGroupFilter(testProps.selectedGroup, rule1Name)
+ createGroupFilter(testProps.selectedGroup, host1Name)
);
fireEvent.click(group2);
expect(renderChildComponent).toHaveBeenNthCalledWith(
2,
- createGroupFilter(testProps.selectedGroup, rule2Name)
+ createGroupFilter(testProps.selectedGroup, host2Name)
);
});
@@ -116,4 +116,24 @@ describe('grouping container', () => {
})
);
});
+
+ it('Renders a null group and passes the correct filter to take actions and child component', () => {
+ takeActionItems.mockReturnValue([]);
+ const { getAllByTestId, getByTestId } = render(
+
+
+
+ );
+ expect(getByTestId('null-group-icon')).toBeInTheDocument();
+
+ let lastGroup = getAllByTestId('grouping-accordion').at(-1);
+ fireEvent.click(within(lastGroup!).getByTestId('take-action-button'));
+
+ expect(takeActionItems).toHaveBeenCalledWith(getNullGroupFilter('host.name'), 2);
+
+ lastGroup = getAllByTestId('grouping-accordion').at(-1);
+ fireEvent.click(within(lastGroup!).getByTestId('group-panel-toggle'));
+
+ expect(renderChildComponent).toHaveBeenCalledWith(getNullGroupFilter('host.name'));
+ });
});
diff --git a/packages/kbn-securitysolution-grouping/src/components/grouping.tsx b/packages/kbn-securitysolution-grouping/src/components/grouping.tsx
index 625beda320d04..8182d204f81dd 100644
--- a/packages/kbn-securitysolution-grouping/src/components/grouping.tsx
+++ b/packages/kbn-securitysolution-grouping/src/components/grouping.tsx
@@ -17,12 +17,12 @@ import type { Filter } from '@kbn/es-query';
import React, { useMemo, useState } from 'react';
import { METRIC_TYPE, UiCounterMetricType } from '@kbn/analytics';
import { defaultUnit, firstNonNullValue } from '../helpers';
-import { createGroupFilter } from './accordion_panel/helpers';
+import { createGroupFilter, getNullGroupFilter } from './accordion_panel/helpers';
import { GroupPanel } from './accordion_panel';
import { GroupStats } from './accordion_panel/group_stats';
import { EmptyGroupingComponent } from './empty_results_panel';
import { countCss, groupingContainerCss, groupingContainerCssLevel } from './styles';
-import { GROUPS_UNIT } from './translations';
+import { GROUPS_UNIT, NULL_GROUP } from './translations';
import type { GroupingAggregation, GroupPanelRenderer } from './types';
import { GroupStatsRenderer, OnGroupToggle } from './types';
import { getTelemetryEvent } from '../telemetry/const';
@@ -78,13 +78,20 @@ const GroupingComponent = ({
const [trigger, setTrigger] = useState>(
{}
);
+ const [nullCount, setNullCount] = useState({ unit: 0, group: 0 });
- const unitCount = data?.unitsCount?.value ?? 0;
+ const unitCount = useMemo(
+ () => (data?.unitsCount?.value ?? 0) + nullCount.unit,
+ [data?.unitsCount?.value, nullCount.unit]
+ );
const unitCountText = useMemo(() => {
return `${unitCount.toLocaleString()} ${unit && unit(unitCount)}`;
}, [unitCount, unit]);
- const groupCount = data?.groupsCount?.value ?? 0;
+ const groupCount = useMemo(
+ () => (data?.groupsCount?.value ?? 0) + nullCount.group,
+ [data?.groupsCount?.value, nullCount.group]
+ );
const groupCountText = useMemo(
() => `${groupCount.toLocaleString()} ${GROUPS_UNIT(groupCount)}`,
[groupCount]
@@ -95,15 +102,28 @@ const GroupingComponent = ({
data?.groupByFields?.buckets?.map((groupBucket, groupNumber) => {
const group = firstNonNullValue(groupBucket.key);
const groupKey = `group-${groupNumber}-${group}`;
+ const isNullGroup = groupBucket.isNullGroup ?? false;
+ const nullGroupMessage = isNullGroup
+ ? NULL_GROUP(selectedGroup, unit(groupBucket.doc_count))
+ : undefined;
+ if (isNullGroup) {
+ setNullCount({ unit: groupBucket.doc_count, group: 1 });
+ }
return (
({
forceState={(trigger[groupKey] && trigger[groupKey].state) ?? 'closed'}
groupBucket={groupBucket}
groupPanelRenderer={
- groupPanelRenderer && groupPanelRenderer(selectedGroup, groupBucket)
+ groupPanelRenderer &&
+ groupPanelRenderer(selectedGroup, groupBucket, nullGroupMessage)
}
isLoading={isLoading}
onToggleGroup={(isOpen) => {
@@ -157,8 +178,10 @@ const GroupingComponent = ({
takeActionItems,
tracker,
trigger,
+ unit,
]
);
+
const pageCount = useMemo(
() => (groupCount ? Math.ceil(groupCount / itemsPerPage) : 1),
[groupCount, itemsPerPage]
diff --git a/packages/kbn-securitysolution-grouping/src/components/translations.ts b/packages/kbn-securitysolution-grouping/src/components/translations.ts
index f2cb8a172dbdc..2209d61d1b34f 100644
--- a/packages/kbn-securitysolution-grouping/src/components/translations.ts
+++ b/packages/kbn-securitysolution-grouping/src/components/translations.ts
@@ -54,3 +54,10 @@ export const DEFAULT_UNIT = (totalCount: number) =>
values: { totalCount },
defaultMessage: `{totalCount, plural, =1 {event} other {events}}`,
});
+
+export const NULL_GROUP = (selectedGroup: string, unit: string) =>
+ i18n.translate('grouping.nullGroup.title', {
+ values: { selectedGroup, unit },
+ defaultMessage:
+ 'The selected group by field, {selectedGroup}, is missing a value for this group of {unit}.',
+ });
diff --git a/packages/kbn-securitysolution-grouping/src/components/types.ts b/packages/kbn-securitysolution-grouping/src/components/types.ts
index cf5f55f5c27f3..3ed5cc43ff877 100644
--- a/packages/kbn-securitysolution-grouping/src/components/types.ts
+++ b/packages/kbn-securitysolution-grouping/src/components/types.ts
@@ -12,16 +12,19 @@ export interface GenericBuckets {
key_as_string?: string; // contains, for example, formatted dates
doc_count: number;
}
-
export const NONE_GROUP_KEY = 'none';
export type RawBucket = GenericBuckets & T;
+export interface GroupingBucket {
+ isNullGroup?: boolean;
+}
+
/** Defines the shape of the aggregation returned by Elasticsearch */
// TODO: write developer docs for these fields
export interface RootAggregation {
groupByFields?: {
- buckets?: Array>;
+ buckets?: Array & GroupingBucket>;
};
groupsCount?: {
value?: number | null;
@@ -60,7 +63,8 @@ export type GroupStatsRenderer = (
export type GroupPanelRenderer = (
selectedGroup: string,
- fieldBucket: RawBucket
+ fieldBucket: RawBucket,
+ nullGroupMessage?: string
) => JSX.Element | undefined;
export type OnGroupToggle = (params: {
diff --git a/packages/kbn-securitysolution-grouping/src/containers/query/helpers.ts b/packages/kbn-securitysolution-grouping/src/containers/query/helpers.ts
new file mode 100644
index 0000000000000..0de0e66fe3df0
--- /dev/null
+++ b/packages/kbn-securitysolution-grouping/src/containers/query/helpers.ts
@@ -0,0 +1,37 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { ES_FIELD_TYPES } from '@kbn/field-types';
+/**
+ * Returns a tuple of values according to the `esType` param, these values are meant to be applied in the _missing_
+ * property of the query aggregation of the grouping, to look up for missing values in the response buckets.
+ * These values do not need to be anything in particular, the only requirement is they have to be 2 different values that validate against the field type.
+ */
+export function getFieldTypeMissingValues(esType: string[]): [number, number] | [string, string] {
+ const knownType: ES_FIELD_TYPES = esType[0] as ES_FIELD_TYPES;
+ switch (knownType) {
+ case ES_FIELD_TYPES.BYTE:
+ case ES_FIELD_TYPES.DOUBLE:
+ case ES_FIELD_TYPES.INTEGER:
+ case ES_FIELD_TYPES.LONG:
+ case ES_FIELD_TYPES.FLOAT:
+ case ES_FIELD_TYPES.HALF_FLOAT:
+ case ES_FIELD_TYPES.SCALED_FLOAT:
+ case ES_FIELD_TYPES.SHORT:
+ case ES_FIELD_TYPES.UNSIGNED_LONG:
+ case ES_FIELD_TYPES.DATE:
+ case ES_FIELD_TYPES.DATE_NANOS:
+ return [0, 1];
+ case ES_FIELD_TYPES.IP:
+ return ['0.0.0.0', '::'];
+ default:
+ return ['-', '--'];
+ }
+}
+
+export const getEmptyValue = () => '—';
diff --git a/packages/kbn-securitysolution-grouping/src/containers/query/index.test.ts b/packages/kbn-securitysolution-grouping/src/containers/query/index.test.ts
index c3f81c4071c58..c3ab014119db7 100644
--- a/packages/kbn-securitysolution-grouping/src/containers/query/index.test.ts
+++ b/packages/kbn-securitysolution-grouping/src/containers/query/index.test.ts
@@ -7,12 +7,13 @@
*/
import type { GroupingQueryArgs } from './types';
-import { getGroupingQuery, MAX_QUERY_SIZE } from '.';
+import { getGroupingQuery, parseGroupingQuery } from '.';
+import { getEmptyValue } from './helpers';
const testProps: GroupingQueryArgs = {
additionalFilters: [],
from: '2022-12-28T15:35:32.871Z',
- groupByFields: ['host.name'],
+ groupByField: 'host.name',
statsAggregations: [
{
alertsCount: {
@@ -53,6 +54,7 @@ const testProps: GroupingQueryArgs = {
pageNumber: 0,
rootAggregations: [],
runtimeMappings: {},
+ selectedGroupEsTypes: ['keyword'],
size: 25,
to: '2023-02-23T06:59:59.999Z',
};
@@ -60,12 +62,13 @@ describe('group selector', () => {
beforeEach(() => {
jest.clearAllMocks();
});
- it('Sets terms query when single stackBy field requested', () => {
+ it('Sets multi terms query with missing argument for 2 default values', () => {
const result = getGroupingQuery(testProps);
- expect(result.aggs.groupByFields.multi_terms).toBeUndefined();
- expect(result.aggs.groupByFields.terms).toEqual({
- field: 'host.name',
- size: MAX_QUERY_SIZE,
+ result.aggs.groupByFields?.multi_terms?.terms.forEach((term, i) => {
+ expect(term).toEqual({
+ field: 'host.name',
+ missing: i === 0 ? '-' : '--',
+ });
});
expect(result.aggs.groupByFields.aggs).toEqual({
bucket_truncate: { bucket_sort: { from: 0, size: 25 } },
@@ -79,7 +82,7 @@ describe('group selector', () => {
expect(result.aggs.groupsNumber).toBeUndefined();
expect(result.query.bool.filter.length).toEqual(1);
});
- it('Sets terms query when single stackBy field requested additionalAggregationsRoot', () => {
+ it('Sets additional rootAggregations', () => {
const result = getGroupingQuery({
...testProps,
rootAggregations: [
@@ -105,17 +108,6 @@ describe('group selector', () => {
});
expect(result.aggs.groupsNumber).toEqual({ cardinality: { field: 'host.name' } });
});
- it('Sets terms query when multiple stackBy fields requested', () => {
- const result = getGroupingQuery({
- ...testProps,
- groupByFields: ['kibana.alert.rule.name', 'kibana.alert.rule.description'],
- });
- expect(result.aggs.groupByFields.terms).toBeUndefined();
- expect(result.aggs.groupByFields.multi_terms).toEqual({
- terms: [{ field: 'kibana.alert.rule.name' }, { field: 'kibana.alert.rule.description' }],
- size: MAX_QUERY_SIZE,
- });
- });
it('Additional filters get added to the query', () => {
const result = getGroupingQuery({
...testProps,
@@ -144,4 +136,61 @@ describe('group selector', () => {
});
expect(result.query.bool.filter.length).toEqual(2);
});
+ it('Uses 0/1 for number fields', () => {
+ const result = getGroupingQuery({ ...testProps, selectedGroupEsTypes: ['long'] });
+ result.aggs.groupByFields?.multi_terms?.terms.forEach((term, i) => {
+ expect(term).toEqual({
+ field: 'host.name',
+ missing: i === 0 ? 0 : 1,
+ });
+ });
+ });
+ it('Uses 0.0.0.0/:: for ip fields', () => {
+ const result = getGroupingQuery({ ...testProps, selectedGroupEsTypes: ['ip'] });
+ result.aggs.groupByFields?.multi_terms?.terms.forEach((term, i) => {
+ expect(term).toEqual({
+ field: 'host.name',
+ missing: i === 0 ? '0.0.0.0' : '::',
+ });
+ });
+ });
+
+ it('parseGroupingQuery finds and flags the null group', () => {
+ const data = [
+ {
+ key: ['20.80.64.28', '20.80.64.28'],
+ key_as_string: '20.80.64.28|20.80.64.28',
+ doc_count: 75,
+ },
+ {
+ key: ['0.0.0.0', '0.0.0.0'],
+ key_as_string: '0.0.0.0|0.0.0.0',
+ doc_count: 75,
+ },
+ {
+ key: ['0.0.0.0', '::'],
+ key_as_string: '0.0.0.0|::',
+ doc_count: 75,
+ },
+ ];
+ const result = parseGroupingQuery(data);
+ expect(result).toEqual([
+ {
+ key: ['20.80.64.28'],
+ key_as_string: '20.80.64.28',
+ doc_count: 75,
+ },
+ {
+ key: ['0.0.0.0'],
+ key_as_string: '0.0.0.0',
+ doc_count: 75,
+ },
+ {
+ key: [getEmptyValue()],
+ key_as_string: getEmptyValue(),
+ isNullGroup: true,
+ doc_count: 75,
+ },
+ ]);
+ });
});
diff --git a/packages/kbn-securitysolution-grouping/src/containers/query/index.ts b/packages/kbn-securitysolution-grouping/src/containers/query/index.ts
index 23699c1ccf94a..e4440bc50e249 100644
--- a/packages/kbn-securitysolution-grouping/src/containers/query/index.ts
+++ b/packages/kbn-securitysolution-grouping/src/containers/query/index.ts
@@ -6,6 +6,9 @@
* Side Public License, v 1.
*/
+import { getEmptyValue, getFieldTypeMissingValues } from './helpers';
+import { GroupingBucket } from '../..';
+import { RawBucket } from '../../..';
import type { GroupingQueryArgs, GroupingQuery } from './types';
/** The maximum number of groups to render */
export const DEFAULT_GROUP_BY_FIELD_SIZE = 10;
@@ -24,6 +27,7 @@ export const MAX_QUERY_SIZE = 10000;
* @param rootAggregations Top level aggregations to get the groups number or overall groups metrics.
* Array of {@link NamedAggregation}
* @param runtimeMappings mappings of runtime fields [see runtimeMappings]{@link GroupingQueryArgs.runtimeMappings}
+ * @param selectedGroupEsTypes array of selected group types
* @param size number of grouping results per page
* @param sort add one or more sorts on specific fields
* @param statsAggregations group level aggregations which correspond to {@link GroupStatsRenderer} configuration
@@ -31,14 +35,16 @@ export const MAX_QUERY_SIZE = 10000;
*
* @returns query dsl {@link GroupingQuery}
*/
+
export const getGroupingQuery = ({
additionalFilters = [],
from,
- groupByFields,
+ groupByField,
+ pageNumber,
rootAggregations,
runtimeMappings,
+ selectedGroupEsTypes,
size = DEFAULT_GROUP_BY_FIELD_SIZE,
- pageNumber,
sort,
statsAggregations,
to,
@@ -46,21 +52,25 @@ export const getGroupingQuery = ({
size: 0,
aggs: {
groupByFields: {
- ...(groupByFields.length > 1
- ? {
- multi_terms: {
- terms: groupByFields.map((groupByField) => ({
- field: groupByField,
- })),
- size: MAX_QUERY_SIZE,
- },
- }
- : {
- terms: {
- field: groupByFields[0],
- size: MAX_QUERY_SIZE,
- },
- }),
+ multi_terms: {
+ terms: [
+ // by looking up multiple missing values, we can ensure we're not overwriting an existing group with the default value
+ {
+ field: groupByField,
+ // the AggregationsMultiTermLookup type is wrong in the elasticsearch node package
+ // when this issues is resolved, we can remove these ts expect errors
+ // https://github.com/elastic/elasticsearch/issues/95628
+ // @ts-expect-error
+ missing: getFieldTypeMissingValues(selectedGroupEsTypes)[0],
+ },
+ {
+ field: groupByField,
+ // @ts-expect-error
+ missing: getFieldTypeMissingValues(selectedGroupEsTypes)[1],
+ },
+ ],
+ size: MAX_QUERY_SIZE,
+ },
aggs: {
bucket_truncate: {
bucket_sort: {
@@ -96,3 +106,32 @@ export const getGroupingQuery = ({
runtime_mappings: runtimeMappings,
_source: false,
});
+
+/**
+ * Parses the grouping query response to add the isNullGroup
+ * flag to the buckets and to format the bucket keys
+ * @param buckets buckets returned from the grouping query
+ */
+export const parseGroupingQuery = (
+ buckets: Array>
+): Array & GroupingBucket> =>
+ buckets.map((group) => {
+ if (!Array.isArray(group.key)) {
+ return group;
+ }
+ const emptyValue = getEmptyValue();
+ // If the keys are different means that the `missing` values of the multi_terms aggregation have been applied, we use the default empty string.
+ // If the keys are equal means the `missing` values have not been applied, they are stored values.
+ return group.key[0] === group.key[1]
+ ? {
+ ...group,
+ key: [group.key[0]],
+ key_as_string: group.key[0],
+ }
+ : {
+ ...group,
+ key: [emptyValue],
+ key_as_string: emptyValue,
+ isNullGroup: true,
+ };
+ });
diff --git a/packages/kbn-securitysolution-grouping/src/containers/query/types.ts b/packages/kbn-securitysolution-grouping/src/containers/query/types.ts
index 5a8b2f822fb5c..4a370094d0d5c 100644
--- a/packages/kbn-securitysolution-grouping/src/containers/query/types.ts
+++ b/packages/kbn-securitysolution-grouping/src/containers/query/types.ts
@@ -23,11 +23,12 @@ export type NamedAggregation = Record;
statsAggregations?: NamedAggregation[];
@@ -37,8 +38,7 @@ export interface GroupingQueryArgs {
export interface MainAggregation extends NamedAggregation {
groupByFields: {
aggs: NamedAggregation;
- multi_terms?: estypes.AggregationsAggregationContainer['multi_terms'];
- terms?: estypes.AggregationsAggregationContainer['terms'];
+ multi_terms: estypes.AggregationsAggregationContainer['multi_terms'];
};
}
diff --git a/packages/kbn-securitysolution-grouping/tsconfig.json b/packages/kbn-securitysolution-grouping/tsconfig.json
index 621ba68957cf1..efecb457f96de 100644
--- a/packages/kbn-securitysolution-grouping/tsconfig.json
+++ b/packages/kbn-securitysolution-grouping/tsconfig.json
@@ -25,6 +25,7 @@
"@kbn/kibana-react-plugin",
"@kbn/shared-svg",
"@kbn/ui-theme",
- "@kbn/analytics"
+ "@kbn/analytics",
+ "@kbn/field-types"
]
}
diff --git a/src/cli/serve/integration_tests/serverless_config_flag.test.ts b/src/cli/serve/integration_tests/serverless_config_flag.test.ts
new file mode 100644
index 0000000000000..6c67ba6261eb4
--- /dev/null
+++ b/src/cli/serve/integration_tests/serverless_config_flag.test.ts
@@ -0,0 +1,88 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { spawn, spawnSync } from 'child_process';
+import { readFileSync } from 'fs';
+import { resolve } from 'path';
+import { filter, firstValueFrom, from, take, concatMap } from 'rxjs';
+
+import { REPO_ROOT } from '@kbn/repo-info';
+import { getConfigDirectory } from '@kbn/utils';
+
+describe('cli serverless project type', () => {
+ it(
+ 'exits with statusCode 1 and logs an error when serverless project type is invalid',
+ () => {
+ const { error, status, stdout } = spawnSync(
+ process.execPath,
+ ['scripts/kibana', '--serverless=non-existing-project-type'],
+ {
+ cwd: REPO_ROOT,
+ }
+ );
+ expect(error).toBe(undefined);
+
+ expect(stdout.toString('utf8')).toContain(
+ 'FATAL CLI ERROR Error: invalid --serverless value, must be one of es, oblt, security'
+ );
+
+ expect(status).toBe(1);
+ },
+ 20 * 1000
+ );
+
+ // Skipping this one because on CI it fails to read the config file
+ it.skip.each(['es', 'oblt', 'security'])(
+ 'writes the serverless project type %s in config/serverless.recent.yml',
+ async (mode) => {
+ // Making sure `--serverless` translates into the `serverless` config entry, and validates against the accepted values
+ const child = spawn(process.execPath, ['scripts/kibana', `--serverless=${mode}`], {
+ cwd: REPO_ROOT,
+ });
+
+ // Wait for 5 lines in the logs
+ await firstValueFrom(from(child.stdout).pipe(take(5)));
+
+ expect(
+ readFileSync(resolve(getConfigDirectory(), 'serverless.recent.yml'), 'utf-8')
+ ).toContain(`serverless: ${mode}\n`);
+
+ child.kill('SIGKILL');
+ }
+ );
+
+ it.each(['es', 'oblt', 'security'])(
+ 'Kibana does not crash when running project type %s',
+ async (mode) => {
+ const child = spawn(process.execPath, ['scripts/kibana', `--serverless=${mode}`], {
+ cwd: REPO_ROOT,
+ });
+
+ // Wait until Kibana starts listening to the port
+ let leftover = '';
+ const found = await firstValueFrom(
+ from(child.stdout).pipe(
+ concatMap((chunk: Buffer) => {
+ const data = leftover + chunk.toString('utf-8');
+ const msgs = data.split('\n');
+ leftover = msgs.pop() ?? '';
+ return msgs;
+ }),
+ filter(
+ (msg) =>
+ msg.includes('http server running at http://localhost:5601') || msg.includes('FATAL')
+ )
+ )
+ );
+
+ child.kill('SIGKILL');
+
+ expect(found).not.toContain('FATAL');
+ }
+ );
+});
diff --git a/src/cli/serve/serve.js b/src/cli/serve/serve.js
index 9facf94408235..c1b9f04fd8d81 100644
--- a/src/cli/serve/serve.js
+++ b/src/cli/serve/serve.js
@@ -8,7 +8,7 @@
import { set as lodashSet } from '@kbn/safer-lodash-set';
import _ from 'lodash';
-import { statSync } from 'fs';
+import { statSync, existsSync, readFileSync, writeFileSync } from 'fs';
import { resolve } from 'path';
import url from 'url';
@@ -22,14 +22,14 @@ const VALID_SERVERLESS_PROJECT_MODE = ['es', 'oblt', 'security'];
/**
* @param {Record} opts
- * @returns {ServerlessProjectMode | null}
+ * @returns {ServerlessProjectMode | true | null}
*/
function getServerlessProjectMode(opts) {
if (!opts.serverless) {
return null;
}
- if (VALID_SERVERLESS_PROJECT_MODE.includes(opts.serverless)) {
+ if (VALID_SERVERLESS_PROJECT_MODE.includes(opts.serverless) || opts.serverless === true) {
return opts.serverless;
}
@@ -94,16 +94,6 @@ function configFileExists(name) {
}
}
-/**
- * @returns {boolean} Whether the distribution can run in Serverless mode
- */
-function isServerlessCapableDistribution() {
- // For now, checking if the `serverless.yml` config file exists should be enough
- // We could also check the following as well, but I don't think it's necessary:
- // VALID_SERVERLESS_PROJECT_MODE.some((projectType) => configFileExists(`serverless.${projectType}.yml`))
- return configFileExists('serverless.yml');
-}
-
/**
* @param {string} name
* @param {string[]} configs
@@ -115,6 +105,48 @@ function maybeAddConfig(name, configs, method) {
}
}
+/**
+ * @param {string} file
+ * @param {'es' | 'security' | 'oblt' | true} projectType
+ * @param {boolean} isDevMode
+ * @param {string[]} configs
+ * @param {'push' | 'unshift'} method
+ */
+function maybeSetRecentConfig(file, projectType, isDevMode, configs, method) {
+ const path = resolve(getConfigDirectory(), file);
+
+ function writeMode(selectedProjectType) {
+ writeFileSync(
+ path,
+ `${
+ isDevMode ? 'xpack.serverless.plugin.developer.projectSwitcher.enabled: true\n' : ''
+ }serverless: ${selectedProjectType}\n`
+ );
+ }
+
+ try {
+ if (projectType === true) {
+ if (!existsSync(path)) {
+ writeMode('es');
+ }
+ } else {
+ const data = readFileSync(path, 'utf-8');
+ const match = data.match(/serverless: (\w+)\n/);
+ if (!match || match[1] !== projectType) {
+ writeMode(projectType);
+ }
+ }
+
+ configs[method](path);
+ } catch (err) {
+ if (err.code === 'ENOENT') {
+ return;
+ }
+
+ throw err;
+ }
+}
+
/**
* @returns {string[]}
*/
@@ -251,13 +283,14 @@ export default function (program) {
.option(
'--run-examples',
'Adds plugin paths for all the Kibana example plugins and runs with no base path'
+ )
+ .option(
+ '--serverless [oblt|security|es]',
+ 'Start Kibana in a specific serverless project mode. ' +
+ 'If no mode is provided, it starts Kibana in the most recent serverless project mode (default is es)'
);
}
- if (isServerlessCapableDistribution()) {
- command.option('--serverless ', 'Start Kibana in a serverless project mode');
- }
-
if (DEV_MODE_SUPPORTED) {
command
.option('--dev', 'Run the server with development mode defaults')
@@ -282,10 +315,8 @@ export default function (program) {
const configs = [getConfigPath(), ...getEnvConfigs(), ...(opts.config || [])];
const serverlessMode = getServerlessProjectMode(opts);
- // we "unshift" .serverless. config so that it only overrides defaults
if (serverlessMode) {
- maybeAddConfig(`serverless.yml`, configs, 'push');
- maybeAddConfig(`serverless.${serverlessMode}.yml`, configs, 'unshift');
+ maybeSetRecentConfig('serverless.recent.yml', serverlessMode, opts.dev, configs, 'push');
}
// .dev. configs are "pushed" so that they override all other config files
@@ -293,7 +324,7 @@ export default function (program) {
maybeAddConfig('kibana.dev.yml', configs, 'push');
if (serverlessMode) {
maybeAddConfig(`serverless.dev.yml`, configs, 'push');
- maybeAddConfig(`serverless.${serverlessMode}.dev.yml`, configs, 'push');
+ maybeAddConfig('serverless.recent.dev.yml', configs, 'push');
}
}
@@ -315,7 +346,6 @@ export default function (program) {
oss: !!opts.oss,
cache: !!opts.cache,
dist: !!opts.dist,
- serverless: !!opts.serverless,
};
// In development mode, the main process uses the @kbn/dev-cli-mode
diff --git a/src/core/server/integration_tests/saved_objects/jest.integration.config.js b/src/core/server/integration_tests/saved_objects/jest.integration.config.js
index e0713af701ea3..abafabc152d17 100644
--- a/src/core/server/integration_tests/saved_objects/jest.integration.config.js
+++ b/src/core/server/integration_tests/saved_objects/jest.integration.config.js
@@ -7,6 +7,7 @@
*/
module.exports = {
+ bail: true, // only report 1 issue
// TODO replace the line below with
// preset: '@kbn/test/jest_integration_node
// to do so, we must fix all integration tests first
diff --git a/src/core/server/integration_tests/saved_objects/service/lib/repository.test.ts b/src/core/server/integration_tests/saved_objects/service/lib/repository.test.ts
index 205d59f0536fe..e2d002331f773 100644
--- a/src/core/server/integration_tests/saved_objects/service/lib/repository.test.ts
+++ b/src/core/server/integration_tests/saved_objects/service/lib/repository.test.ts
@@ -46,8 +46,10 @@ describe('SavedObjectsRepository', () => {
});
afterAll(async () => {
- await esServer.stop();
- await root.shutdown();
+ if (root) {
+ await esServer.stop();
+ await root.shutdown();
+ }
});
describe('#incrementCounter', () => {
diff --git a/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy.test.ts b/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy.test.ts
index 255658d7fbe7a..db24bf5a10765 100644
--- a/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy.test.ts
+++ b/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy.test.ts
@@ -139,9 +139,11 @@ describe('404s from proxies', () => {
});
afterAll(async () => {
- await root.shutdown();
- await hapiServer.stop({ timeout: 1000 });
- await esServer.stop();
+ if (root) {
+ await root.shutdown();
+ await hapiServer.stop({ timeout: 1000 });
+ await esServer.stop();
+ }
});
describe('requests when a proxy relays request/responses with the correct product header', () => {
diff --git a/src/plugins/controls/common/options_list/mocks.tsx b/src/plugins/controls/common/options_list/mocks.tsx
index 936a620ec288c..d0e2977a9b439 100644
--- a/src/plugins/controls/common/options_list/mocks.tsx
+++ b/src/plugins/controls/common/options_list/mocks.tsx
@@ -17,13 +17,13 @@ const mockOptionsListComponentState = {
searchString: { value: '', valid: true },
field: undefined,
totalCardinality: 0,
- availableOptions: {
- woof: { doc_count: 100 },
- bark: { doc_count: 75 },
- meow: { doc_count: 50 },
- quack: { doc_count: 25 },
- moo: { doc_count: 5 },
- },
+ availableOptions: [
+ { value: 'woof', docCount: 100 },
+ { value: 'bark', docCount: 75 },
+ { value: 'meow', docCount: 50 },
+ { value: 'quack', docCount: 25 },
+ { value: 'moo', docCount: 5 },
+ ],
invalidSelections: [],
allowExpensiveQueries: true,
popoverOpen: false,
diff --git a/src/plugins/controls/common/options_list/types.ts b/src/plugins/controls/common/options_list/types.ts
index 510dac280fe76..8437eb0382b6e 100644
--- a/src/plugins/controls/common/options_list/types.ts
+++ b/src/plugins/controls/common/options_list/types.ts
@@ -28,9 +28,7 @@ export interface OptionsListEmbeddableInput extends DataControlInput {
placeholder?: string;
}
-export interface OptionsListSuggestions {
- [key: string]: { doc_count: number };
-}
+export type OptionsListSuggestions = Array<{ value: string; docCount?: number }>;
/**
* The Options list response is returned from the serverside Options List route.
diff --git a/src/plugins/controls/public/__stories__/controls.stories.tsx b/src/plugins/controls/public/__stories__/controls.stories.tsx
index 4326ce056d118..a0ba30622e150 100644
--- a/src/plugins/controls/public/__stories__/controls.stories.tsx
+++ b/src/plugins/controls/public/__stories__/controls.stories.tsx
@@ -35,7 +35,11 @@ import { injectStorybookDataView } from '../services/data_views/data_views.story
import { replaceOptionsListMethod } from '../services/options_list/options_list.story';
import { populateStorybookControlFactories } from './storybook_control_factories';
import { replaceValueSuggestionMethod } from '../services/unified_search/unified_search.story';
-import { OptionsListResponse, OptionsListRequest } from '../../common/options_list/types';
+import {
+ OptionsListResponse,
+ OptionsListRequest,
+ OptionsListSuggestions,
+} from '../../common/options_list/types';
export default {
title: 'Controls',
@@ -56,9 +60,9 @@ const storybookStubOptionsListRequest = async (
r({
suggestions: getFlightSearchOptions(request.field.name, request.searchString).reduce(
(o, current, index) => {
- return { ...o, [current]: { doc_count: index } };
+ return [...o, { value: current, docCount: index }];
},
- {}
+ [] as OptionsListSuggestions
),
totalCardinality: 100,
}),
diff --git a/src/plugins/controls/public/options_list/components/options_list_popover.test.tsx b/src/plugins/controls/public/options_list/components/options_list_popover.test.tsx
index ffdb1045cad88..e2fa74dfbf2f1 100644
--- a/src/plugins/controls/public/options_list/components/options_list_popover.test.tsx
+++ b/src/plugins/controls/public/options_list/components/options_list_popover.test.tsx
@@ -70,7 +70,7 @@ describe('Options list popover', () => {
});
test('no available options', async () => {
- const popover = await mountComponent({ componentState: { availableOptions: {} } });
+ const popover = await mountComponent({ componentState: { availableOptions: [] } });
const availableOptionsDiv = findTestSubject(popover, 'optionsList-control-available-options');
const noOptionsDiv = findTestSubject(
availableOptionsDiv,
@@ -127,9 +127,7 @@ describe('Options list popover', () => {
selectedOptions: ['bark', 'woof'],
},
componentState: {
- availableOptions: {
- bark: { doc_count: 75 },
- },
+ availableOptions: [{ value: 'bark', docCount: 75 }],
validSelections: ['bark'],
invalidSelections: ['woof'],
},
@@ -154,9 +152,7 @@ describe('Options list popover', () => {
const popover = await mountComponent({
explicitInput: { selectedOptions: ['bark', 'woof', 'meow'] },
componentState: {
- availableOptions: {
- bark: { doc_count: 75 },
- },
+ availableOptions: [{ value: 'bark', docCount: 75 }],
validSelections: ['bark'],
invalidSelections: ['woof', 'meow'],
},
@@ -219,7 +215,7 @@ describe('Options list popover', () => {
test('if existsSelected = false and no suggestions, then "Exists" does not show up', async () => {
const popover = await mountComponent({
- componentState: { availableOptions: {} },
+ componentState: { availableOptions: [] },
explicitInput: { existsSelected: false },
});
const existsOption = findTestSubject(popover, 'optionsList-control-selection-exists');
diff --git a/src/plugins/controls/public/options_list/components/options_list_popover_suggestions.tsx b/src/plugins/controls/public/options_list/components/options_list_popover_suggestions.tsx
index 8d727bed55e20..e5c14e5ea70fc 100644
--- a/src/plugins/controls/public/options_list/components/options_list_popover_suggestions.tsx
+++ b/src/plugins/controls/public/options_list/components/options_list_popover_suggestions.tsx
@@ -48,7 +48,7 @@ export const OptionsListPopoverSuggestions = ({
const canLoadMoreSuggestions = useMemo(
() =>
totalCardinality
- ? Object.keys(availableOptions ?? {}).length <
+ ? (availableOptions ?? []).length <
Math.min(totalCardinality, MAX_OPTIONS_LIST_REQUEST_SIZE)
: false,
[availableOptions, totalCardinality]
@@ -61,7 +61,7 @@ export const OptionsListPopoverSuggestions = ({
[invalidSelections]
);
const suggestions = useMemo(() => {
- return showOnlySelected ? selectedOptions : Object.keys(availableOptions ?? {});
+ return showOnlySelected ? selectedOptions : availableOptions ?? [];
}, [availableOptions, selectedOptions, showOnlySelected]);
const existsSelectableOption = useMemo(() => {
@@ -79,19 +79,23 @@ export const OptionsListPopoverSuggestions = ({
const [selectableOptions, setSelectableOptions] = useState([]); // will be set in following useEffect
useEffect(() => {
/* This useEffect makes selectableOptions responsive to search, show only selected, and clear selections */
- const options: EuiSelectableOption[] = (suggestions ?? []).map((key) => {
+ const options: EuiSelectableOption[] = (suggestions ?? []).map((suggestion) => {
+ if (typeof suggestion === 'string') {
+ // this means that `showOnlySelected` is true, and doc count is not known when this is the case
+ suggestion = { value: suggestion };
+ }
return {
- key,
- label: key,
- checked: selectedOptionsSet?.has(key) ? 'on' : undefined,
- 'data-test-subj': `optionsList-control-selection-${key}`,
+ key: suggestion.value,
+ label: suggestion.value,
+ checked: selectedOptionsSet?.has(suggestion.value) ? 'on' : undefined,
+ 'data-test-subj': `optionsList-control-selection-${suggestion.value}`,
className:
- showOnlySelected && invalidSelectionsSet.has(key)
+ showOnlySelected && invalidSelectionsSet.has(suggestion.value)
? 'optionsList__selectionInvalid'
: 'optionsList__validSuggestion',
append:
- !showOnlySelected && availableOptions?.[key] ? (
-
+ !showOnlySelected && suggestion?.docCount ? (
+
) : undefined,
};
});
diff --git a/src/plugins/controls/public/options_list/embeddable/options_list_embeddable.tsx b/src/plugins/controls/public/options_list/embeddable/options_list_embeddable.tsx
index e2503b4e530e8..08d5f1150baf5 100644
--- a/src/plugins/controls/public/options_list/embeddable/options_list_embeddable.tsx
+++ b/src/plugins/controls/public/options_list/embeddable/options_list_embeddable.tsx
@@ -371,7 +371,7 @@ export class OptionsListEmbeddable extends Embeddable {
this.dispatch.updateQueryResults({
- availableOptions: {},
+ availableOptions: [],
});
this.dispatch.setLoading(false);
});
diff --git a/src/plugins/controls/public/services/options_list/options_list.story.ts b/src/plugins/controls/public/services/options_list/options_list.story.ts
index 6d3305f97b9aa..cf674887a0ba0 100644
--- a/src/plugins/controls/public/services/options_list/options_list.story.ts
+++ b/src/plugins/controls/public/services/options_list/options_list.story.ts
@@ -18,7 +18,7 @@ let optionsListRequestMethod = async (request: OptionsListRequest, abortSignal:
setTimeout(
() =>
r({
- suggestions: {},
+ suggestions: [],
totalCardinality: 100,
}),
120
diff --git a/src/plugins/controls/server/options_list/options_list_cheap_suggestion_queries.test.ts b/src/plugins/controls/server/options_list/options_list_cheap_suggestion_queries.test.ts
index 31783a1267aca..0476788791f69 100644
--- a/src/plugins/controls/server/options_list/options_list_cheap_suggestion_queries.test.ts
+++ b/src/plugins/controls/server/options_list/options_list_cheap_suggestion_queries.test.ts
@@ -388,17 +388,20 @@ describe('options list cheap queries', () => {
expect(
suggestionAggBuilder.parse(rawSearchResponseMock, optionsListRequestBodyMock).suggestions
).toMatchInlineSnapshot(`
- Object {
- "cool1": Object {
- "doc_count": 5,
+ Array [
+ Object {
+ "docCount": 5,
+ "value": "cool1",
},
- "cool2": Object {
- "doc_count": 15,
+ Object {
+ "docCount": 15,
+ "value": "cool2",
},
- "cool3": Object {
- "doc_count": 10,
+ Object {
+ "docCount": 10,
+ "value": "cool3",
},
- }
+ ]
`);
});
@@ -421,14 +424,16 @@ describe('options list cheap queries', () => {
expect(
suggestionAggBuilder.parse(rawSearchResponseMock, optionsListRequestBodyMock).suggestions
).toMatchInlineSnapshot(`
- Object {
- "false": Object {
- "doc_count": 55,
+ Array [
+ Object {
+ "docCount": 55,
+ "value": "false",
},
- "true": Object {
- "doc_count": 155,
+ Object {
+ "docCount": 155,
+ "value": "true",
},
- }
+ ]
`);
});
@@ -455,17 +460,20 @@ describe('options list cheap queries', () => {
expect(
suggestionAggBuilder.parse(rawSearchResponseMock, optionsListRequestBodyMock).suggestions
).toMatchInlineSnapshot(`
- Object {
- "cool1": Object {
- "doc_count": 5,
+ Array [
+ Object {
+ "docCount": 5,
+ "value": "cool1",
},
- "cool2": Object {
- "doc_count": 15,
+ Object {
+ "docCount": 15,
+ "value": "cool2",
},
- "cool3": Object {
- "doc_count": 10,
+ Object {
+ "docCount": 10,
+ "value": "cool3",
},
- }
+ ]
`);
});
@@ -490,17 +498,20 @@ describe('options list cheap queries', () => {
expect(
suggestionAggBuilder.parse(rawSearchResponseMock, optionsListRequestBodyMock).suggestions
).toMatchInlineSnapshot(`
- Object {
- "cool1": Object {
- "doc_count": 5,
+ Array [
+ Object {
+ "docCount": 5,
+ "value": "cool1",
},
- "cool2": Object {
- "doc_count": 15,
+ Object {
+ "docCount": 15,
+ "value": "cool2",
},
- "cool3": Object {
- "doc_count": 10,
+ Object {
+ "docCount": 10,
+ "value": "cool3",
},
- }
+ ]
`);
});
});
@@ -552,55 +563,50 @@ describe('options list cheap queries', () => {
rawSearchResponseMock,
optionsListRequestBodyMock
).suggestions;
- /** first, verify that the sorting worked as expected */
- expect(Object.keys(parsed)).toMatchInlineSnapshot(`
- Array [
- "52:ae76:5947:5e2a:551:fe6a:712a:c72",
- "111.52.174.2",
- "196.162.13.39",
- "f7a9:640b:b5a0:1219:8d75:ed94:3c3e:2e63",
- "23.216.241.120",
- "28c7:c9a4:42fd:16b0:4de5:e41e:28d9:9172",
- "21.35.91.62",
- "21.35.91.61",
- "203.88.33.151",
- "1ec:aa98:b0a6:d07c:590:18a0:8a33:2eb8",
- ]
- `);
- /** then, make sure the object is structured properly */
+
expect(parsed).toMatchInlineSnapshot(`
- Object {
- "111.52.174.2": Object {
- "doc_count": 11,
+ Array [
+ Object {
+ "docCount": 12,
+ "value": "52:ae76:5947:5e2a:551:fe6a:712a:c72",
},
- "196.162.13.39": Object {
- "doc_count": 10,
+ Object {
+ "docCount": 11,
+ "value": "111.52.174.2",
},
- "1ec:aa98:b0a6:d07c:590:18a0:8a33:2eb8": Object {
- "doc_count": 6,
+ Object {
+ "docCount": 10,
+ "value": "196.162.13.39",
},
- "203.88.33.151": Object {
- "doc_count": 7,
+ Object {
+ "docCount": 10,
+ "value": "f7a9:640b:b5a0:1219:8d75:ed94:3c3e:2e63",
},
- "21.35.91.61": Object {
- "doc_count": 8,
+ Object {
+ "docCount": 9,
+ "value": "23.216.241.120",
},
- "21.35.91.62": Object {
- "doc_count": 8,
+ Object {
+ "docCount": 9,
+ "value": "28c7:c9a4:42fd:16b0:4de5:e41e:28d9:9172",
},
- "23.216.241.120": Object {
- "doc_count": 9,
+ Object {
+ "docCount": 8,
+ "value": "21.35.91.62",
},
- "28c7:c9a4:42fd:16b0:4de5:e41e:28d9:9172": Object {
- "doc_count": 9,
+ Object {
+ "docCount": 8,
+ "value": "21.35.91.61",
},
- "52:ae76:5947:5e2a:551:fe6a:712a:c72": Object {
- "doc_count": 12,
+ Object {
+ "docCount": 7,
+ "value": "203.88.33.151",
},
- "f7a9:640b:b5a0:1219:8d75:ed94:3c3e:2e63": Object {
- "doc_count": 10,
+ Object {
+ "docCount": 6,
+ "value": "1ec:aa98:b0a6:d07c:590:18a0:8a33:2eb8",
},
- }
+ ]
`);
});
});
diff --git a/src/plugins/controls/server/options_list/options_list_cheap_suggestion_queries.ts b/src/plugins/controls/server/options_list/options_list_cheap_suggestion_queries.ts
index 3a302cf62d04b..3b69b2818b909 100644
--- a/src/plugins/controls/server/options_list/options_list_cheap_suggestion_queries.ts
+++ b/src/plugins/controls/server/options_list/options_list_cheap_suggestion_queries.ts
@@ -51,11 +51,11 @@ const cheapSuggestionAggSubtypes: { [key: string]: OptionsListSuggestionAggregat
},
}),
parse: (rawEsResult) => ({
- suggestions: get(rawEsResult, 'aggregations.suggestions.buckets').reduce(
- (suggestions: OptionsListSuggestions, suggestion: EsBucket) => {
- return { ...suggestions, [suggestion.key]: { doc_count: suggestion.doc_count } };
+ suggestions: get(rawEsResult, 'aggregations.suggestions.buckets')?.reduce(
+ (acc: OptionsListSuggestions, suggestion: EsBucket) => {
+ return [...acc, { value: suggestion.key, docCount: suggestion.doc_count }];
},
- {}
+ []
),
}),
},
@@ -75,13 +75,10 @@ const cheapSuggestionAggSubtypes: { [key: string]: OptionsListSuggestionAggregat
}),
parse: (rawEsResult) => ({
suggestions: get(rawEsResult, 'aggregations.suggestions.buckets')?.reduce(
- (suggestions: OptionsListSuggestions, suggestion: EsBucket & { key_as_string: string }) => {
- return {
- ...suggestions,
- [suggestion.key_as_string]: { doc_count: suggestion.doc_count },
- };
+ (acc: OptionsListSuggestions, suggestion: EsBucket & { key_as_string: string }) => {
+ return [...acc, { value: suggestion.key_as_string, docCount: suggestion.doc_count }];
},
- {}
+ []
),
}),
},
@@ -134,7 +131,7 @@ const cheapSuggestionAggSubtypes: { [key: string]: OptionsListSuggestionAggregat
if (!Boolean(rawEsResult.aggregations?.suggestions)) {
// if this is happens, that means there is an invalid search that snuck through to the server side code;
// so, might as well early return with no suggestions
- return { suggestions: {} };
+ return { suggestions: [] };
}
const buckets: EsBucket[] = [];
@@ -153,9 +150,9 @@ const cheapSuggestionAggSubtypes: { [key: string]: OptionsListSuggestionAggregat
return {
suggestions: sortedSuggestions
.slice(0, 10) // only return top 10 results
- .reduce((suggestions, suggestion: EsBucket) => {
- return { ...suggestions, [suggestion.key]: { doc_count: suggestion.doc_count } };
- }, {}),
+ .reduce((acc: OptionsListSuggestions, suggestion: EsBucket) => {
+ return [...acc, { value: suggestion.key, docCount: suggestion.doc_count }];
+ }, []),
};
},
},
@@ -190,11 +187,11 @@ const cheapSuggestionAggSubtypes: { [key: string]: OptionsListSuggestionAggregat
};
},
parse: (rawEsResult) => ({
- suggestions: get(rawEsResult, 'aggregations.nestedSuggestions.suggestions.buckets').reduce(
- (suggestions: OptionsListSuggestions, suggestion: EsBucket) => {
- return { ...suggestions, [suggestion.key]: { doc_count: suggestion.doc_count } };
+ suggestions: get(rawEsResult, 'aggregations.nestedSuggestions.suggestions.buckets')?.reduce(
+ (acc: OptionsListSuggestions, suggestion: EsBucket) => {
+ return [...acc, { value: suggestion.key, docCount: suggestion.doc_count }];
},
- {}
+ []
),
}),
},
diff --git a/src/plugins/controls/server/options_list/options_list_expensive_suggestion_queries.test.ts b/src/plugins/controls/server/options_list/options_list_expensive_suggestion_queries.test.ts
index 7026359e10ee4..5638cbc347366 100644
--- a/src/plugins/controls/server/options_list/options_list_expensive_suggestion_queries.test.ts
+++ b/src/plugins/controls/server/options_list/options_list_expensive_suggestion_queries.test.ts
@@ -466,17 +466,20 @@ describe('options list expensive queries', () => {
expect(suggestionAggBuilder.parse(rawSearchResponseMock, optionsListRequestBodyMock))
.toMatchInlineSnapshot(`
Object {
- "suggestions": Object {
- "cool1": Object {
- "doc_count": 5,
+ "suggestions": Array [
+ Object {
+ "docCount": 5,
+ "value": "cool1",
},
- "cool2": Object {
- "doc_count": 15,
+ Object {
+ "docCount": 15,
+ "value": "cool2",
},
- "cool3": Object {
- "doc_count": 10,
+ Object {
+ "docCount": 10,
+ "value": "cool3",
},
- },
+ ],
"totalCardinality": 3,
}
`);
@@ -503,14 +506,16 @@ describe('options list expensive queries', () => {
expect(suggestionAggBuilder.parse(rawSearchResponseMock, optionsListRequestBodyMock))
.toMatchInlineSnapshot(`
Object {
- "suggestions": Object {
- "false": Object {
- "doc_count": 55,
+ "suggestions": Array [
+ Object {
+ "docCount": 55,
+ "value": "false",
},
- "true": Object {
- "doc_count": 155,
+ Object {
+ "docCount": 155,
+ "value": "true",
},
- },
+ ],
"totalCardinality": 2,
}
`);
@@ -546,17 +551,20 @@ describe('options list expensive queries', () => {
expect(suggestionAggBuilder.parse(rawSearchResponseMock, optionsListRequestBodyMock))
.toMatchInlineSnapshot(`
Object {
- "suggestions": Object {
- "cool1": Object {
- "doc_count": 5,
+ "suggestions": Array [
+ Object {
+ "docCount": 5,
+ "value": "cool1",
},
- "cool2": Object {
- "doc_count": 15,
+ Object {
+ "docCount": 15,
+ "value": "cool2",
},
- "cool3": Object {
- "doc_count": 10,
+ Object {
+ "docCount": 10,
+ "value": "cool3",
},
- },
+ ],
"totalCardinality": 3,
}
`);
@@ -621,55 +629,50 @@ describe('options list expensive queries', () => {
rawSearchResponseMock,
optionsListRequestBodyMock
).suggestions;
- /** first, verify that the sorting worked as expected */
- expect(Object.keys(parsed)).toMatchInlineSnapshot(`
- Array [
- "52:ae76:5947:5e2a:551:fe6a:712a:c72",
- "111.52.174.2",
- "196.162.13.39",
- "f7a9:640b:b5a0:1219:8d75:ed94:3c3e:2e63",
- "23.216.241.120",
- "28c7:c9a4:42fd:16b0:4de5:e41e:28d9:9172",
- "21.35.91.62",
- "21.35.91.61",
- "203.88.33.151",
- "1ec:aa98:b0a6:d07c:590:18a0:8a33:2eb8",
- ]
- `);
- /** then, make sure the object is structured properly */
+
expect(parsed).toMatchInlineSnapshot(`
- Object {
- "111.52.174.2": Object {
- "doc_count": 11,
+ Array [
+ Object {
+ "docCount": 12,
+ "value": "52:ae76:5947:5e2a:551:fe6a:712a:c72",
},
- "196.162.13.39": Object {
- "doc_count": 10,
+ Object {
+ "docCount": 11,
+ "value": "111.52.174.2",
},
- "1ec:aa98:b0a6:d07c:590:18a0:8a33:2eb8": Object {
- "doc_count": 6,
+ Object {
+ "docCount": 10,
+ "value": "196.162.13.39",
},
- "203.88.33.151": Object {
- "doc_count": 7,
+ Object {
+ "docCount": 10,
+ "value": "f7a9:640b:b5a0:1219:8d75:ed94:3c3e:2e63",
},
- "21.35.91.61": Object {
- "doc_count": 8,
+ Object {
+ "docCount": 9,
+ "value": "23.216.241.120",
},
- "21.35.91.62": Object {
- "doc_count": 8,
+ Object {
+ "docCount": 9,
+ "value": "28c7:c9a4:42fd:16b0:4de5:e41e:28d9:9172",
},
- "23.216.241.120": Object {
- "doc_count": 9,
+ Object {
+ "docCount": 8,
+ "value": "21.35.91.62",
},
- "28c7:c9a4:42fd:16b0:4de5:e41e:28d9:9172": Object {
- "doc_count": 9,
+ Object {
+ "docCount": 8,
+ "value": "21.35.91.61",
},
- "52:ae76:5947:5e2a:551:fe6a:712a:c72": Object {
- "doc_count": 12,
+ Object {
+ "docCount": 7,
+ "value": "203.88.33.151",
},
- "f7a9:640b:b5a0:1219:8d75:ed94:3c3e:2e63": Object {
- "doc_count": 10,
+ Object {
+ "docCount": 6,
+ "value": "1ec:aa98:b0a6:d07c:590:18a0:8a33:2eb8",
},
- }
+ ]
`);
});
});
diff --git a/src/plugins/controls/server/options_list/options_list_expensive_suggestion_queries.ts b/src/plugins/controls/server/options_list/options_list_expensive_suggestion_queries.ts
index 63347f8d436d3..a1114191d1fa8 100644
--- a/src/plugins/controls/server/options_list/options_list_expensive_suggestion_queries.ts
+++ b/src/plugins/controls/server/options_list/options_list_expensive_suggestion_queries.ts
@@ -93,9 +93,9 @@ const expensiveSuggestionAggSubtypes: { [key: string]: OptionsListSuggestionAggr
const suggestions = get(rawEsResult, `${basePath}.suggestions.buckets`)?.reduce(
(acc: OptionsListSuggestions, suggestion: EsBucket) => {
- return { ...acc, [suggestion.key]: { doc_count: suggestion.doc_count } };
+ return [...acc, { value: suggestion.key, docCount: suggestion.doc_count }];
},
- {}
+ []
);
return {
suggestions,
@@ -120,14 +120,11 @@ const expensiveSuggestionAggSubtypes: { [key: string]: OptionsListSuggestionAggr
parse: (rawEsResult) => {
const suggestions = get(rawEsResult, 'aggregations.suggestions.buckets')?.reduce(
(acc: OptionsListSuggestions, suggestion: EsBucket & { key_as_string: string }) => {
- return {
- ...acc,
- [suggestion.key_as_string]: { doc_count: suggestion.doc_count },
- };
+ return [...acc, { value: suggestion.key_as_string, docCount: suggestion.doc_count }];
},
- {}
+ []
);
- return { suggestions, totalCardinality: Object.keys(suggestions).length }; // cardinality is only ever 0, 1, or 2 so safe to use length here
+ return { suggestions, totalCardinality: suggestions.length }; // cardinality is only ever 0, 1, or 2 so safe to use length here
},
},
@@ -185,7 +182,7 @@ const expensiveSuggestionAggSubtypes: { [key: string]: OptionsListSuggestionAggr
if (!Boolean(rawEsResult.aggregations?.suggestions)) {
// if this is happens, that means there is an invalid search that snuck through to the server side code;
// so, might as well early return with no suggestions
- return { suggestions: {}, totalCardinality: 0 };
+ return { suggestions: [], totalCardinality: 0 };
}
const buckets: EsBucket[] = [];
getIpBuckets(rawEsResult, buckets, 'ipv4'); // modifies buckets array directly, i.e. "by reference"
@@ -200,11 +197,11 @@ const expensiveSuggestionAggSubtypes: { [key: string]: OptionsListSuggestionAggr
(bucketA: EsBucket, bucketB: EsBucket) => bucketB.doc_count - bucketA.doc_count
);
- const suggestions: OptionsListSuggestions = sortedSuggestions
+ const suggestions = sortedSuggestions
.slice(0, request.size)
.reduce((acc: OptionsListSuggestions, suggestion: EsBucket) => {
- return { ...acc, [suggestion.key]: { doc_count: suggestion.doc_count } };
- }, {});
+ return [...acc, { value: suggestion.key, docCount: suggestion.doc_count }];
+ }, []);
const totalCardinality =
(get(rawEsResult, `aggregations.suggestions.buckets.ipv4.unique_terms.value`) ?? 0) +
(get(rawEsResult, `aggregations.suggestions.buckets.ipv6.unique_terms.value`) ?? 0);
diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts
index 947b062b3a551..529d91a284e4e 100644
--- a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts
+++ b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts
@@ -6,6 +6,8 @@
* Side Public License, v 1.
*/
+import { Observable } from 'rxjs';
+
import {
ContactCardEmbeddable,
ContactCardEmbeddableFactory,
@@ -26,7 +28,7 @@ import { createDashboard } from './create_dashboard';
import { getSampleDashboardPanel } from '../../../mocks';
import { pluginServices } from '../../../services/plugin_services';
import { DashboardCreationOptions } from '../dashboard_container_factory';
-import { Observable } from 'rxjs';
+import { DEFAULT_DASHBOARD_INPUT } from '../../../dashboard_constants';
const embeddableId = 'create-dat-dashboard';
@@ -54,18 +56,28 @@ test('throws error when provided validation function returns invalid', async ()
test('pulls state from dashboard saved object when given a saved object id', async () => {
pluginServices.getServices().dashboardSavedObject.loadDashboardStateFromSavedObject = jest
.fn()
- .mockResolvedValue({ dashboardInput: { description: 'wow would you look at that? Wow.' } });
+ .mockResolvedValue({
+ dashboardInput: {
+ ...DEFAULT_DASHBOARD_INPUT,
+ description: `wow would you look at that? Wow.`,
+ },
+ });
const dashboard = await createDashboard(embeddableId, {}, 0, 'wow-such-id');
expect(
pluginServices.getServices().dashboardSavedObject.loadDashboardStateFromSavedObject
).toHaveBeenCalledWith({ id: 'wow-such-id' });
- expect(dashboard.getState().explicitInput.description).toBe('wow would you look at that? Wow.');
+ expect(dashboard.getState().explicitInput.description).toBe(`wow would you look at that? Wow.`);
});
test('pulls state from session storage which overrides state from saved object', async () => {
pluginServices.getServices().dashboardSavedObject.loadDashboardStateFromSavedObject = jest
.fn()
- .mockResolvedValue({ dashboardInput: { description: 'wow this description is okay' } });
+ .mockResolvedValue({
+ dashboardInput: {
+ ...DEFAULT_DASHBOARD_INPUT,
+ description: 'wow this description is okay',
+ },
+ });
pluginServices.getServices().dashboardSessionStorage.getState = jest
.fn()
.mockReturnValue({ description: 'wow this description marginally better' });
@@ -83,7 +95,12 @@ test('pulls state from session storage which overrides state from saved object',
test('pulls state from creation options initial input which overrides all other state sources', async () => {
pluginServices.getServices().dashboardSavedObject.loadDashboardStateFromSavedObject = jest
.fn()
- .mockResolvedValue({ dashboardInput: { description: 'wow this description is okay' } });
+ .mockResolvedValue({
+ dashboardInput: {
+ ...DEFAULT_DASHBOARD_INPUT,
+ description: 'wow this description is okay',
+ },
+ });
pluginServices.getServices().dashboardSessionStorage.getState = jest
.fn()
.mockReturnValue({ description: 'wow this description marginally better' });
@@ -213,6 +230,7 @@ test('creates new embeddable with incoming embeddable if id does not match exist
},
},
});
+
// flush promises
await new Promise((r) => setTimeout(r, 1));
expect(mockContactCardFactory.create).toHaveBeenCalledWith(
diff --git a/src/plugins/data/server/search/session/session_service.test.ts b/src/plugins/data/server/search/session/session_service.test.ts
index 38a4f027765df..3f79049fa9666 100644
--- a/src/plugins/data/server/search/session/session_service.test.ts
+++ b/src/plugins/data/server/search/session/session_service.test.ts
@@ -575,7 +575,11 @@ describe('SearchSessionService', () => {
const [[findOptions]] = savedObjectsClient.find.mock.calls;
expect(findOptions).toMatchInlineSnapshot(`
Object {
- "filter": undefined,
+ "filter": Object {
+ "arguments": Array [],
+ "function": "and",
+ "type": "function",
+ },
"page": 0,
"perPage": 5,
"type": "search-session",
diff --git a/src/plugins/data_views/server/routes/fields_for.test.ts b/src/plugins/data_views/server/routes/fields_for.test.ts
new file mode 100644
index 0000000000000..57d1465f8a9ee
--- /dev/null
+++ b/src/plugins/data_views/server/routes/fields_for.test.ts
@@ -0,0 +1,52 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { parseMetaFields } from './fields_for';
+
+describe('_fields_for_wildcard', () => {
+ describe('parseMetaFields', () => {
+ it('should throw if receiving a string of comma-separated values', () => {
+ const value = '_source,_id';
+ expect(() => parseMetaFields(value)).toThrowErrorMatchingInlineSnapshot(
+ `"metaFields should be an array of field names, a JSON-stringified array of field names, or a single field name"`
+ );
+ });
+
+ it('should parse a stringified list of values', () => {
+ const value = JSON.stringify(['_source', '_id']);
+ const fields = parseMetaFields(value);
+ expect(fields).toMatchInlineSnapshot(`
+ Array [
+ "_source",
+ "_id",
+ ]
+ `);
+ });
+
+ it('should wrap a single value in an array', () => {
+ const value = '_source';
+ const fields = parseMetaFields(value);
+ expect(fields).toMatchInlineSnapshot(`
+ Array [
+ "_source",
+ ]
+ `);
+ });
+
+ it('should return the array if already an array', () => {
+ const value = ['_source', '_id'];
+ const fields = parseMetaFields(value);
+ expect(fields).toMatchInlineSnapshot(`
+ Array [
+ "_source",
+ "_id",
+ ]
+ `);
+ });
+ });
+});
diff --git a/src/plugins/data_views/server/routes/fields_for.ts b/src/plugins/data_views/server/routes/fields_for.ts
index 415077258966d..8719fd5ce7549 100644
--- a/src/plugins/data_views/server/routes/fields_for.ts
+++ b/src/plugins/data_views/server/routes/fields_for.ts
@@ -17,14 +17,24 @@ import {
import { IndexPatternsFetcher } from '../fetcher';
import type { DataViewsServerPluginStart, DataViewsServerPluginStartDependencies } from '../types';
-const parseMetaFields = (metaFields: string | string[]) => {
- let parsedFields: string[] = [];
- if (typeof metaFields === 'string') {
- parsedFields = JSON.parse(metaFields);
- } else {
- parsedFields = metaFields;
+/**
+ * Accepts one of the following:
+ * 1. An array of field names
+ * 2. A JSON-stringified array of field names
+ * 3. A single field name (not comma-separated)
+ * @returns an array of field names
+ * @param metaFields
+ */
+export const parseMetaFields = (metaFields: string | string[]): string[] => {
+ if (Array.isArray(metaFields)) return metaFields;
+ try {
+ return JSON.parse(metaFields);
+ } catch (e) {
+ if (!metaFields.includes(',')) return [metaFields];
+ throw new Error(
+ 'metaFields should be an array of field names, a JSON-stringified array of field names, or a single field name'
+ );
}
- return parsedFields;
};
const path = '/api/index_patterns/_fields_for_wildcard';
@@ -32,7 +42,7 @@ const path = '/api/index_patterns/_fields_for_wildcard';
type IBody = { index_filter?: estypes.QueryDslQueryContainer } | undefined;
interface IQuery {
pattern: string;
- meta_fields: string[];
+ meta_fields: string | string[];
type?: string;
rollup_index?: string;
allow_no_index?: boolean;
diff --git a/src/plugins/discover/public/application/main/hooks/use_test_based_query_language.test.ts b/src/plugins/discover/public/application/main/hooks/use_test_based_query_language.test.ts
index 7ac2fb6ef7916..d512477d9f53e 100644
--- a/src/plugins/discover/public/application/main/hooks/use_test_based_query_language.test.ts
+++ b/src/plugins/discover/public/application/main/hooks/use_test_based_query_language.test.ts
@@ -14,21 +14,24 @@ import { useTextBasedQueryLanguage } from './use_text_based_query_language';
import { BehaviorSubject } from 'rxjs';
import { FetchStatus } from '../../types';
import { DataDocuments$, RecordRawType } from '../services/discover_data_state_container';
+import { DiscoverAppState } from '../services/discover_app_state_container';
import { DataTableRecord } from '../../../types';
import { AggregateQuery, Query } from '@kbn/es-query';
import { dataViewMock } from '../../../__mocks__/data_view';
import { DataViewListItem } from '@kbn/data-views-plugin/common';
import { savedSearchMock } from '../../../__mocks__/saved_search';
import { getDiscoverStateMock } from '../../../__mocks__/discover_state.mock';
+import { VIEW_MODE } from '@kbn/saved-search-plugin/common';
function getHookProps(
query: AggregateQuery | Query | undefined,
- dataViewsService?: DataViewsContract
+ dataViewsService?: DataViewsContract,
+ appState?: Partial
) {
const replaceUrlState = jest.fn();
const stateContainer = getDiscoverStateMock({ isTimeBased: true });
stateContainer.appState.replaceUrlState = replaceUrlState;
- stateContainer.appState.update({ columns: [] });
+ stateContainer.appState.update({ columns: [], ...appState });
stateContainer.internalState.transitions.setSavedDataViews([dataViewMock as DataViewListItem]);
const msgLoading = {
@@ -83,6 +86,20 @@ describe('useTextBasedQueryLanguage', () => {
});
});
});
+ test('should change viewMode to DOCUMENT_LEVEL if it was AGGREGATED_LEVEL', async () => {
+ const props = getHookProps(query, undefined, {
+ viewMode: VIEW_MODE.AGGREGATED_LEVEL,
+ });
+ const { replaceUrlState } = props;
+
+ renderHook(() => useTextBasedQueryLanguage(props));
+
+ await waitFor(() => expect(replaceUrlState).toHaveBeenCalledTimes(1));
+ expect(replaceUrlState).toHaveBeenCalledWith({
+ index: 'the-data-view-id',
+ viewMode: VIEW_MODE.DOCUMENT_LEVEL,
+ });
+ });
test('changing a text based query with different result columns should change state when loading and finished', async () => {
const props = getHookProps(query);
const { documents$, replaceUrlState } = props;
diff --git a/src/plugins/discover/public/application/main/hooks/use_text_based_query_language.ts b/src/plugins/discover/public/application/main/hooks/use_text_based_query_language.ts
index 5e2eb33efdbe9..ff40121274858 100644
--- a/src/plugins/discover/public/application/main/hooks/use_text_based_query_language.ts
+++ b/src/plugins/discover/public/application/main/hooks/use_text_based_query_language.ts
@@ -14,9 +14,10 @@ import {
} from '@kbn/es-query';
import { useCallback, useEffect, useRef } from 'react';
import type { DataViewsContract } from '@kbn/data-views-plugin/public';
-import { SavedSearch } from '@kbn/saved-search-plugin/public';
+import { SavedSearch, VIEW_MODE } from '@kbn/saved-search-plugin/public';
import type { DiscoverStateContainer } from '../services/discover_state';
import type { DataDocuments$ } from '../services/discover_data_state_container';
+import { getValidViewMode } from '../utils/get_valid_view_mode';
import { FetchStatus } from '../../types';
const MAX_NUM_OF_COLUMNS = 50;
@@ -57,7 +58,7 @@ export function useTextBasedQueryLanguage({
if (!query || next.fetchStatus === FetchStatus.ERROR) {
return;
}
- const { columns: stateColumns, index } = stateContainer.appState.getState();
+ const { columns: stateColumns, index, viewMode } = stateContainer.appState.getState();
let nextColumns: string[] = [];
const isTextBasedQueryLang =
recordRawType === 'plain' && isOfAggregateQueryType(query) && 'sql' in query;
@@ -115,6 +116,9 @@ export function useTextBasedQueryLanguage({
const nextState = {
...(addDataViewToState && { index: dataViewObj.id }),
...(addColumnsToState && { columns: nextColumns }),
+ ...(viewMode === VIEW_MODE.AGGREGATED_LEVEL && {
+ viewMode: getValidViewMode({ viewMode, isTextBasedQueryMode: true }),
+ }),
};
stateContainer.appState.replaceUrlState(nextState);
} else {
diff --git a/src/plugins/discover/public/application/main/utils/get_raw_record_type.ts b/src/plugins/discover/public/application/main/utils/get_raw_record_type.ts
index 595d54420e7ad..36d9544b9e851 100644
--- a/src/plugins/discover/public/application/main/utils/get_raw_record_type.ts
+++ b/src/plugins/discover/public/application/main/utils/get_raw_record_type.ts
@@ -25,7 +25,3 @@ export function getRawRecordType(query?: Query | AggregateQuery) {
return RecordRawType.DOCUMENT;
}
-
-export function isPlainRecord(query?: Query | AggregateQuery): query is AggregateQuery {
- return getRawRecordType(query) === RecordRawType.PLAIN;
-}
diff --git a/src/plugins/discover/public/application/main/utils/get_state_defaults.test.ts b/src/plugins/discover/public/application/main/utils/get_state_defaults.test.ts
index aed900821c747..a994adc340f21 100644
--- a/src/plugins/discover/public/application/main/utils/get_state_defaults.test.ts
+++ b/src/plugins/discover/public/application/main/utils/get_state_defaults.test.ts
@@ -8,8 +8,9 @@
import { getStateDefaults } from './get_state_defaults';
import { createSearchSourceMock } from '@kbn/data-plugin/public/mocks';
+import { VIEW_MODE } from '@kbn/saved-search-plugin/common';
import { dataViewWithTimefieldMock } from '../../../__mocks__/data_view_with_timefield';
-import { savedSearchMock } from '../../../__mocks__/saved_search';
+import { savedSearchMock, savedSearchMockWithSQL } from '../../../__mocks__/saved_search';
import { dataViewMock } from '../../../__mocks__/data_view';
import { discoverServiceMock } from '../../../__mocks__/services';
@@ -75,4 +76,42 @@ describe('getStateDefaults', () => {
}
`);
});
+
+ test('should set view mode correctly', () => {
+ const actualForUndefinedViewMode = getStateDefaults({
+ services: discoverServiceMock,
+ savedSearch: {
+ ...savedSearchMockWithSQL,
+ viewMode: undefined,
+ },
+ });
+ expect(actualForUndefinedViewMode.viewMode).toBeUndefined();
+
+ const actualForTextBasedWithInvalidViewMode = getStateDefaults({
+ services: discoverServiceMock,
+ savedSearch: {
+ ...savedSearchMockWithSQL,
+ viewMode: VIEW_MODE.AGGREGATED_LEVEL,
+ },
+ });
+ expect(actualForTextBasedWithInvalidViewMode.viewMode).toBe(VIEW_MODE.DOCUMENT_LEVEL);
+
+ const actualForTextBasedWithValidViewMode = getStateDefaults({
+ services: discoverServiceMock,
+ savedSearch: {
+ ...savedSearchMockWithSQL,
+ viewMode: VIEW_MODE.DOCUMENT_LEVEL,
+ },
+ });
+ expect(actualForTextBasedWithValidViewMode.viewMode).toBe(VIEW_MODE.DOCUMENT_LEVEL);
+
+ const actualForWithValidViewMode = getStateDefaults({
+ services: discoverServiceMock,
+ savedSearch: {
+ ...savedSearchMock,
+ viewMode: VIEW_MODE.AGGREGATED_LEVEL,
+ },
+ });
+ expect(actualForWithValidViewMode.viewMode).toBe(VIEW_MODE.AGGREGATED_LEVEL);
+ });
});
diff --git a/src/plugins/discover/public/application/main/utils/get_state_defaults.ts b/src/plugins/discover/public/application/main/utils/get_state_defaults.ts
index 8acdc9ce6728b..1ec3be8c3ec1a 100644
--- a/src/plugins/discover/public/application/main/utils/get_state_defaults.ts
+++ b/src/plugins/discover/public/application/main/utils/get_state_defaults.ts
@@ -19,6 +19,8 @@ import {
SEARCH_FIELDS_FROM_SOURCE,
SORT_DEFAULT_ORDER_SETTING,
} from '../../../../common';
+import { isTextBasedQuery } from './is_text_based_query';
+import { getValidViewMode } from './get_valid_view_mode';
function getDefaultColumns(savedSearch: SavedSearch, uiSettings: IUiSettingsClient) {
if (savedSearch.columns && savedSearch.columns.length > 0) {
@@ -81,7 +83,10 @@ export function getStateDefaults({
defaultState.rowHeight = savedSearch.rowHeight;
}
if (savedSearch.viewMode) {
- defaultState.viewMode = savedSearch.viewMode;
+ defaultState.viewMode = getValidViewMode({
+ viewMode: savedSearch.viewMode,
+ isTextBasedQueryMode: isTextBasedQuery(query),
+ });
}
if (savedSearch.hideAggregatedPreview) {
defaultState.hideAggregatedPreview = savedSearch.hideAggregatedPreview;
diff --git a/src/plugins/discover/public/application/main/utils/get_valid_view_mode.test.ts b/src/plugins/discover/public/application/main/utils/get_valid_view_mode.test.ts
new file mode 100644
index 0000000000000..33431ba09b7c5
--- /dev/null
+++ b/src/plugins/discover/public/application/main/utils/get_valid_view_mode.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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { VIEW_MODE } from '@kbn/saved-search-plugin/common';
+import { getValidViewMode } from './get_valid_view_mode';
+
+describe('getValidViewMode', () => {
+ test('should work correctly for regular mode', () => {
+ expect(
+ getValidViewMode({
+ viewMode: undefined,
+ isTextBasedQueryMode: false,
+ })
+ ).toBeUndefined();
+
+ expect(
+ getValidViewMode({
+ viewMode: VIEW_MODE.DOCUMENT_LEVEL,
+ isTextBasedQueryMode: false,
+ })
+ ).toBe(VIEW_MODE.DOCUMENT_LEVEL);
+
+ expect(
+ getValidViewMode({
+ viewMode: VIEW_MODE.AGGREGATED_LEVEL,
+ isTextBasedQueryMode: false,
+ })
+ ).toBe(VIEW_MODE.AGGREGATED_LEVEL);
+ });
+
+ test('should work correctly for text-based mode', () => {
+ expect(
+ getValidViewMode({
+ viewMode: undefined,
+ isTextBasedQueryMode: true,
+ })
+ ).toBeUndefined();
+
+ expect(
+ getValidViewMode({
+ viewMode: VIEW_MODE.DOCUMENT_LEVEL,
+ isTextBasedQueryMode: true,
+ })
+ ).toBe(VIEW_MODE.DOCUMENT_LEVEL);
+
+ expect(
+ getValidViewMode({
+ viewMode: VIEW_MODE.AGGREGATED_LEVEL,
+ isTextBasedQueryMode: true,
+ })
+ ).toBe(VIEW_MODE.DOCUMENT_LEVEL);
+ });
+});
diff --git a/src/plugins/discover/public/application/main/utils/get_valid_view_mode.ts b/src/plugins/discover/public/application/main/utils/get_valid_view_mode.ts
new file mode 100644
index 0000000000000..4d6d660c28eef
--- /dev/null
+++ b/src/plugins/discover/public/application/main/utils/get_valid_view_mode.ts
@@ -0,0 +1,29 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { VIEW_MODE } from '@kbn/saved-search-plugin/public';
+
+/**
+ * Returns a valid view mode
+ * @param viewMode
+ * @param isTextBasedQueryMode
+ */
+export const getValidViewMode = ({
+ viewMode,
+ isTextBasedQueryMode,
+}: {
+ viewMode?: VIEW_MODE;
+ isTextBasedQueryMode: boolean;
+}): VIEW_MODE | undefined => {
+ if (viewMode === VIEW_MODE.AGGREGATED_LEVEL && isTextBasedQueryMode) {
+ // only this mode is supported for text-based languages
+ return VIEW_MODE.DOCUMENT_LEVEL;
+ }
+
+ return viewMode;
+};
diff --git a/src/plugins/discover/public/application/main/utils/is_text_based_query.test.ts b/src/plugins/discover/public/application/main/utils/is_text_based_query.test.ts
new file mode 100644
index 0000000000000..53e85216ba0bf
--- /dev/null
+++ b/src/plugins/discover/public/application/main/utils/is_text_based_query.test.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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { isTextBasedQuery } from './is_text_based_query';
+
+describe('isTextBasedQuery', () => {
+ it('should work correctly', () => {
+ expect(isTextBasedQuery({ query: '', language: 'lucene' })).toEqual(false);
+ expect(isTextBasedQuery({ sql: 'SELECT * from foo' })).toEqual(true);
+ expect(isTextBasedQuery()).toEqual(false);
+ });
+});
diff --git a/src/plugins/discover/public/application/main/utils/is_text_based_query.ts b/src/plugins/discover/public/application/main/utils/is_text_based_query.ts
new file mode 100644
index 0000000000000..bf86af9304231
--- /dev/null
+++ b/src/plugins/discover/public/application/main/utils/is_text_based_query.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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+import type { AggregateQuery, Query } from '@kbn/es-query';
+import { RecordRawType } from '../services/discover_data_state_container';
+import { getRawRecordType } from './get_raw_record_type';
+
+/**
+ * Checks if the query is of AggregateQuery type
+ * @param query
+ */
+export function isTextBasedQuery(query?: Query | AggregateQuery): query is AggregateQuery {
+ return getRawRecordType(query) === RecordRawType.PLAIN;
+}
diff --git a/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx b/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx
index 0e315d3b348e5..1317916588184 100644
--- a/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx
+++ b/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx
@@ -37,7 +37,6 @@ import { SavedSearch } from '@kbn/saved-search-plugin/public';
import { METRIC_TYPE } from '@kbn/analytics';
import { VIEW_MODE } from '../../common/constants';
import { getSortForEmbeddable, SortPair } from '../utils/sorting';
-import { RecordRawType } from '../application/main/services/discover_data_state_container';
import { buildDataTableRecord } from '../utils/build_data_record';
import { DataTableRecord, EsHitRecord } from '../types';
import { ISearchEmbeddable, SearchInput, SearchOutput } from './types';
@@ -59,7 +58,8 @@ import { DiscoverGridSettings } from '../components/discover_grid/types';
import { DocTableProps } from '../components/doc_table/doc_table_wrapper';
import { updateSearchSource } from './utils/update_search_source';
import { FieldStatisticsTable } from '../application/main/components/field_stats_table';
-import { getRawRecordType } from '../application/main/utils/get_raw_record_type';
+import { isTextBasedQuery } from '../application/main/utils/is_text_based_query';
+import { getValidViewMode } from '../application/main/utils/get_valid_view_mode';
import { fetchSql } from '../application/main/utils/fetch_sql';
import { ADHOC_DATA_VIEW_RENDER_EVENT } from '../constants';
@@ -169,6 +169,11 @@ export class SavedSearchEmbeddable
return true;
}
+ private isTextBasedSearch = (savedSearch: SavedSearch): boolean => {
+ const query = savedSearch.searchSource.getField('query');
+ return isTextBasedQuery(query);
+ };
+
private fetch = async () => {
const searchSessionId = this.input.searchSessionId;
const useNewFieldsApi = !this.services.uiSettings.get(SEARCH_FIELDS_FROM_SOURCE, false);
@@ -229,8 +234,7 @@ export class SavedSearchEmbeddable
const query = this.savedSearch.searchSource.getField('query');
const dataView = this.savedSearch.searchSource.getField('index')!;
- const recordRawType = getRawRecordType(query);
- const useSql = recordRawType === RecordRawType.PLAIN;
+ const useSql = this.isTextBasedSearch(this.savedSearch);
try {
// Request SQL data
@@ -515,9 +519,14 @@ export class SavedSearchEmbeddable
return;
}
+ const viewMode = getValidViewMode({
+ viewMode: this.savedSearch.viewMode,
+ isTextBasedQueryMode: this.isTextBasedSearch(this.savedSearch),
+ });
+
if (
this.services.uiSettings.get(SHOW_FIELD_STATISTICS) === true &&
- this.savedSearch.viewMode === VIEW_MODE.AGGREGATED_LEVEL &&
+ viewMode === VIEW_MODE.AGGREGATED_LEVEL &&
searchProps.services &&
searchProps.dataView &&
Array.isArray(searchProps.columns)
diff --git a/src/plugins/unified_field_list/public/components/error_boundary/error_boundary.tsx b/src/plugins/unified_field_list/public/components/error_boundary/error_boundary.tsx
new file mode 100644
index 0000000000000..3fe938b3cbdf5
--- /dev/null
+++ b/src/plugins/unified_field_list/public/components/error_boundary/error_boundary.tsx
@@ -0,0 +1,31 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React from 'react';
+
+/**
+ * Renders nothing instead of a component which triggered an exception.
+ */
+export class ErrorBoundary extends React.Component<{}, { hasError: boolean }> {
+ constructor(props: {}) {
+ super(props);
+ this.state = { hasError: false };
+ }
+
+ static getDerivedStateFromError() {
+ return { hasError: true };
+ }
+
+ render() {
+ if (this.state.hasError) {
+ return null;
+ }
+
+ return this.props.children;
+ }
+}
diff --git a/src/plugins/unified_field_list/public/components/error_boundary/index.tsx b/src/plugins/unified_field_list/public/components/error_boundary/index.tsx
new file mode 100644
index 0000000000000..4e2cd318ddcf4
--- /dev/null
+++ b/src/plugins/unified_field_list/public/components/error_boundary/index.tsx
@@ -0,0 +1,9 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+export { ErrorBoundary } from './error_boundary';
diff --git a/src/plugins/unified_field_list/public/components/field_popover/field_popover.tsx b/src/plugins/unified_field_list/public/components/field_popover/field_popover.tsx
index 59c6b9621f8c3..42806637b8cda 100644
--- a/src/plugins/unified_field_list/public/components/field_popover/field_popover.tsx
+++ b/src/plugins/unified_field_list/public/components/field_popover/field_popover.tsx
@@ -22,8 +22,24 @@ export const FieldPopover: React.FC = ({
renderContent,
...otherPopoverProps
}) => {
- const header = (isOpen && renderHeader?.()) || null;
- const content = (isOpen && renderContent?.()) || null;
+ let header = null;
+ let content = null;
+
+ if (isOpen) {
+ try {
+ header = renderHeader?.() || null;
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.error(error);
+ }
+
+ try {
+ content = renderContent?.() || null;
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.error(error);
+ }
+ }
return (
= (props) => {
+const FieldPopoverFooterComponent: React.FC = (props) => {
const [visualizeButton, setVisualizeButton] = useState(null);
const [categorizeButton, setCategorizeButton] = useState(null);
useEffect(() => {
- getFieldVisualizeButton(props).then(setVisualizeButton);
- getFieldCategorizeButton(props).then(setCategorizeButton);
+ getFieldVisualizeButton(props)
+ .then(setVisualizeButton)
+ .catch((error) => {
+ // eslint-disable-next-line no-console
+ console.error(error);
+ });
+ getFieldCategorizeButton(props)
+ .then(setCategorizeButton)
+ .catch((error) => {
+ // eslint-disable-next-line no-console
+ console.error(error);
+ });
}, [props]);
return visualizeButton || categorizeButton ? (
@@ -30,3 +41,11 @@ export const FieldPopoverFooter: React.FC = (props) =>
) : null;
};
+
+export const FieldPopoverFooter: React.FC = (props) => {
+ return (
+
+
+
+ );
+};
diff --git a/src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx b/src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx
index ae5857c4feafa..028ceddc080c9 100755
--- a/src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx
+++ b/src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx
@@ -47,6 +47,7 @@ import {
getDefaultColor,
} from './field_top_values';
import { FieldSummaryMessage } from './field_summary_message';
+import { ErrorBoundary } from '../error_boundary';
export interface FieldStatsState {
isLoading: boolean;
@@ -540,25 +541,6 @@ const FieldStatsComponent: React.FC = ({
return null;
};
-class ErrorBoundary extends React.Component<{}, { hasError: boolean }> {
- constructor(props: FieldStatsProps) {
- super(props);
- this.state = { hasError: false };
- }
-
- static getDerivedStateFromError() {
- return { hasError: true };
- }
-
- render() {
- if (this.state.hasError) {
- return null;
- }
-
- return this.props.children;
- }
-}
-
/**
* Component which fetches and renders stats for a data view field
* @param props
diff --git a/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx b/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx
index f082d283f9ece..07268726359ba 100644
--- a/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx
+++ b/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx
@@ -88,6 +88,7 @@ export class EditInLensAction implements Action {
searchQuery,
isEmbeddable: true,
description: vis.description || embeddable.getOutput().description,
+ panelTimeRange: embeddable.getInput()?.timeRange,
};
if (navigateToLensConfig) {
if (this.currentAppId) {
diff --git a/src/plugins/visualizations/public/visualize_app/components/visualize_editor.tsx b/src/plugins/visualizations/public/visualize_app/components/visualize_editor.tsx
index 221cdcc9d8e10..70c0c111ce581 100644
--- a/src/plugins/visualizations/public/visualize_app/components/visualize_editor.tsx
+++ b/src/plugins/visualizations/public/visualize_app/components/visualize_editor.tsx
@@ -24,23 +24,46 @@ import { VisualizeServices } from '../types';
import { VisualizeEditorCommon } from './visualize_editor_common';
import { VisualizeAppProps } from '../app';
import { VisualizeConstants } from '../../../common/constants';
+import type { VisualizeInput } from '../..';
export const VisualizeEditor = ({ onAppLeave }: VisualizeAppProps) => {
const { id: visualizationIdFromUrl } = useParams<{ id: string }>();
const [originatingApp, setOriginatingApp] = useState();
const [originatingPath, setOriginatingPath] = useState();
const [embeddableIdValue, setEmbeddableId] = useState();
+ const [embeddableInput, setEmbeddableInput] = useState();
const { services } = useKibana();
const [eventEmitter] = useState(new EventEmitter());
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(!visualizationIdFromUrl);
const isChromeVisible = useChromeVisibility(services.chrome);
+ useEffect(() => {
+ const { stateTransferService, data } = services;
+ const {
+ originatingApp: value,
+ searchSessionId,
+ embeddableId,
+ originatingPath: pathValue,
+ valueInput: valueInputValue,
+ } = stateTransferService.getIncomingEditorState(VisualizeConstants.APP_ID) || {};
+
+ if (searchSessionId) {
+ data.search.session.continue(searchSessionId);
+ } else {
+ data.search.session.start();
+ }
+ setEmbeddableInput(valueInputValue);
+ setEmbeddableId(embeddableId);
+ setOriginatingApp(value);
+ setOriginatingPath(pathValue);
+ }, [services]);
const { savedVisInstance, visEditorRef, visEditorController } = useSavedVisInstance(
services,
eventEmitter,
isChromeVisible,
originatingApp,
- visualizationIdFromUrl
+ visualizationIdFromUrl,
+ embeddableInput
);
const editorName = savedVisInstance?.vis.type.title.toLowerCase().replace(' ', '_') || '';
@@ -66,26 +89,6 @@ export const VisualizeEditor = ({ onAppLeave }: VisualizeAppProps) => {
useLinkedSearchUpdates(services, eventEmitter, appState, savedVisInstance);
useDataViewUpdates(services, eventEmitter, appState, savedVisInstance);
- useEffect(() => {
- const { stateTransferService, data } = services;
- const {
- originatingApp: value,
- searchSessionId,
- embeddableId,
- originatingPath: pathValue,
- } = stateTransferService.getIncomingEditorState(VisualizeConstants.APP_ID) || {};
-
- if (searchSessionId) {
- data.search.session.continue(searchSessionId);
- } else {
- data.search.session.start();
- }
-
- setEmbeddableId(embeddableId);
- setOriginatingApp(value);
- setOriginatingPath(pathValue);
- }, [services]);
-
useEffect(() => {
// clean up all registered listeners if any is left
return () => {
diff --git a/src/plugins/visualizations/public/visualize_app/types.ts b/src/plugins/visualizations/public/visualize_app/types.ts
index aa16be35277f3..b1e0b9dfd2abd 100644
--- a/src/plugins/visualizations/public/visualize_app/types.ts
+++ b/src/plugins/visualizations/public/visualize_app/types.ts
@@ -122,6 +122,7 @@ export interface VisInstance {
embeddableHandler: VisualizeEmbeddableContract;
panelTitle?: string;
panelDescription?: string;
+ panelTimeRange?: TimeRange;
}
export type SavedVisInstance = VisInstance;
diff --git a/src/plugins/visualizations/public/visualize_app/utils/get_top_nav_config.tsx b/src/plugins/visualizations/public/visualize_app/utils/get_top_nav_config.tsx
index 0fd0ab1117d86..39a840b41dc3d 100644
--- a/src/plugins/visualizations/public/visualize_app/utils/get_top_nav_config.tsx
+++ b/src/plugins/visualizations/public/visualize_app/utils/get_top_nav_config.tsx
@@ -315,6 +315,7 @@ export const getTopNavConfig = (
title: visInstance?.panelTitle || vis.title,
visTypeTitle: vis.type.title,
description: visInstance?.panelDescription || vis.description,
+ panelTimeRange: visInstance?.panelTimeRange,
isEmbeddable: Boolean(originatingApp),
};
if (navigateToLensConfig) {
diff --git a/src/plugins/visualizations/public/visualize_app/utils/get_visualization_instance.test.ts b/src/plugins/visualizations/public/visualize_app/utils/get_visualization_instance.test.ts
index 985f194acaf83..d4b7dbd5213e5 100644
--- a/src/plugins/visualizations/public/visualize_app/utils/get_visualization_instance.test.ts
+++ b/src/plugins/visualizations/public/visualize_app/utils/get_visualization_instance.test.ts
@@ -170,6 +170,10 @@ describe('getVisualizationInstanceInput', () => {
id: 'test-id',
description: 'description',
title: 'title',
+ timeRange: {
+ from: 'now-7d/d',
+ to: 'now',
+ },
savedVis: {
title: '',
description: '',
@@ -196,8 +200,15 @@ describe('getVisualizationInstanceInput', () => {
},
},
} as unknown as VisualizeInput;
- const { savedVis, savedSearch, vis, embeddableHandler, panelDescription, panelTitle } =
- await getVisualizationInstanceFromInput(mockServices, input);
+ const {
+ savedVis,
+ savedSearch,
+ vis,
+ embeddableHandler,
+ panelDescription,
+ panelTitle,
+ panelTimeRange,
+ } = await getVisualizationInstanceFromInput(mockServices, input);
expect(getSavedVisualization).toHaveBeenCalled();
expect(createVisAsync).toHaveBeenCalledWith(serializedVisMock.type, input.savedVis);
@@ -216,5 +227,9 @@ describe('getVisualizationInstanceInput', () => {
expect(savedSearch).toBeUndefined();
expect(panelDescription).toBe('description');
expect(panelTitle).toBe('title');
+ expect(panelTimeRange).toStrictEqual({
+ from: 'now-7d/d',
+ to: 'now',
+ });
});
});
diff --git a/src/plugins/visualizations/public/visualize_app/utils/get_visualization_instance.ts b/src/plugins/visualizations/public/visualize_app/utils/get_visualization_instance.ts
index f872183d1b288..e4c484ecd9c31 100644
--- a/src/plugins/visualizations/public/visualize_app/utils/get_visualization_instance.ts
+++ b/src/plugins/visualizations/public/visualize_app/utils/get_visualization_instance.ts
@@ -121,6 +121,7 @@ export const getVisualizationInstanceFromInput = async (
savedSearch,
panelTitle: input?.title ?? '',
panelDescription: input?.description ?? '',
+ panelTimeRange: input?.timeRange ?? undefined,
};
};
diff --git a/src/plugins/visualizations/public/visualize_app/utils/use/use_saved_vis_instance.test.ts b/src/plugins/visualizations/public/visualize_app/utils/use/use_saved_vis_instance.test.ts
index 93f9e2ae0b0a4..4b6b87bd04f1f 100644
--- a/src/plugins/visualizations/public/visualize_app/utils/use/use_saved_vis_instance.test.ts
+++ b/src/plugins/visualizations/public/visualize_app/utils/use/use_saved_vis_instance.test.ts
@@ -147,6 +147,46 @@ describe('useSavedVisInstance', () => {
expect(result.current.savedVisInstance).toBeDefined();
});
+ test('should pass the input timeRange if it exists', async () => {
+ const embeddableInput = {
+ timeRange: {
+ from: 'now-7d/d',
+ to: 'now',
+ },
+ id: 'panel1',
+ };
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useSavedVisInstance(
+ mockServices,
+ eventEmitter,
+ true,
+ undefined,
+ savedVisId,
+ embeddableInput
+ )
+ );
+
+ result.current.visEditorRef.current = document.createElement('div');
+ expect(mockGetVisualizationInstance).toHaveBeenCalledWith(mockServices, savedVisId);
+ expect(mockGetVisualizationInstance.mock.calls.length).toBe(1);
+
+ await waitForNextUpdate();
+ expect(mockServices.chrome.setBreadcrumbs).toHaveBeenCalledWith('Test Vis');
+ expect(mockServices.chrome.docTitle.change).toHaveBeenCalledWith('Test Vis');
+ expect(getEditBreadcrumbs).toHaveBeenCalledWith(
+ { originatingAppName: undefined, redirectToOrigin: undefined },
+ 'Test Vis'
+ );
+ expect(getCreateBreadcrumbs).not.toHaveBeenCalled();
+ expect(mockEmbeddableHandlerRender).not.toHaveBeenCalled();
+ expect(result.current.visEditorController).toBeDefined();
+ expect(result.current.savedVisInstance).toBeDefined();
+ expect(result.current.savedVisInstance?.panelTimeRange).toStrictEqual({
+ from: 'now-7d/d',
+ to: 'now',
+ });
+ });
+
test('should destroy the editor and the savedVis on unmount if chrome exists', async () => {
const { result, unmount, waitForNextUpdate } = renderHook(() =>
useSavedVisInstance(mockServices, eventEmitter, true, undefined, savedVisId)
diff --git a/src/plugins/visualizations/public/visualize_app/utils/use/use_saved_vis_instance.ts b/src/plugins/visualizations/public/visualize_app/utils/use/use_saved_vis_instance.ts
index 27cd03a8cc8ae..f84b7928dda39 100644
--- a/src/plugins/visualizations/public/visualize_app/utils/use/use_saved_vis_instance.ts
+++ b/src/plugins/visualizations/public/visualize_app/utils/use/use_saved_vis_instance.ts
@@ -17,6 +17,7 @@ import { SavedVisInstance, VisualizeServices, IEditorController } from '../../ty
import { VisualizeConstants } from '../../../../common/constants';
import { getTypes } from '../../../services';
import { redirectToSavedObjectPage } from '../utils';
+import type { VisualizeInput } from '../../..';
/**
* This effect is responsible for instantiating a saved vis or creating a new one
@@ -27,13 +28,13 @@ export const useSavedVisInstance = (
eventEmitter: EventEmitter,
isChromeVisible: boolean | undefined,
originatingApp: string | undefined,
- visualizationIdFromUrl: string | undefined
+ visualizationIdFromUrl: string | undefined,
+ embeddableInput?: VisualizeInput
) => {
const [state, setState] = useState<{
savedVisInstance?: SavedVisInstance;
visEditorController?: IEditorController;
}>({});
-
const visEditorRef = useRef(null);
const visId = useRef('');
@@ -82,6 +83,9 @@ export const useSavedVisInstance = (
savedVisInstance = await getVisualizationInstance(services, visualizationIdFromUrl);
}
+ if (embeddableInput && embeddableInput.timeRange) {
+ savedVisInstance.panelTimeRange = embeddableInput.timeRange;
+ }
const { embeddableHandler, savedVis, vis } = savedVisInstance;
const originatingAppName = originatingApp
@@ -166,6 +170,7 @@ export const useSavedVisInstance = (
visualizationIdFromUrl,
state.savedVisInstance,
state.visEditorController,
+ embeddableInput,
]);
useEffect(() => {
diff --git a/test/analytics/tests/instrumented_events/from_the_browser/viewport_resize.ts b/test/analytics/tests/instrumented_events/from_the_browser/viewport_resize.ts
index f5e084810a375..8523b9f5603e7 100644
--- a/test/analytics/tests/instrumented_events/from_the_browser/viewport_resize.ts
+++ b/test/analytics/tests/instrumented_events/from_the_browser/viewport_resize.ts
@@ -14,7 +14,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const browser = getService('browser');
const { common } = getPageObjects(['common']);
- describe('Event "viewport_resize"', () => {
+ // Failing: See https://github.com/elastic/kibana/issues/155166
+ describe.skip('Event "viewport_resize"', () => {
beforeEach(async () => {
// Navigating to `home` with the Welcome prompt because some runs were flaky
// as we handle the Welcome screen only if the login prompt pops up.
diff --git a/test/api_integration/apis/data_views/fields_for_wildcard_route/params.js b/test/api_integration/apis/data_views/fields_for_wildcard_route/params.js
index b91c307cc1743..595d12f38c501 100644
--- a/test/api_integration/apis/data_views/fields_for_wildcard_route/params.js
+++ b/test/api_integration/apis/data_views/fields_for_wildcard_route/params.js
@@ -49,6 +49,15 @@ export default function ({ getService }) {
})
.expect(200));
+ it('accepts single meta_fields query param', () =>
+ supertest
+ .get('/api/index_patterns/_fields_for_wildcard')
+ .query({
+ pattern: '*',
+ meta_fields: ['_id'],
+ })
+ .expect(200));
+
it('rejects a comma-separated list of meta_fields', () =>
supertest
.get('/api/index_patterns/_fields_for_wildcard')
diff --git a/test/functional/apps/console/_context_menu.ts b/test/functional/apps/console/_context_menu.ts
index 8114d4d05097e..b5698d33c9af2 100644
--- a/test/functional/apps/console/_context_menu.ts
+++ b/test/functional/apps/console/_context_menu.ts
@@ -16,7 +16,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const browser = getService('browser');
const toasts = getService('toasts');
- describe('console context menu', function testContextMenu() {
+ // Failing: See https://github.com/elastic/kibana/issues/143230
+ describe.skip('console context menu', function testContextMenu() {
before(async () => {
await PageObjects.common.navigateToApp('console');
// Ensure that the text area can be interacted with
diff --git a/x-pack/packages/ml/trained_models_utils/src/constants/trained_models.ts b/x-pack/packages/ml/trained_models_utils/src/constants/trained_models.ts
index 237a9f23f90d8..3376af46daa2b 100644
--- a/x-pack/packages/ml/trained_models_utils/src/constants/trained_models.ts
+++ b/x-pack/packages/ml/trained_models_utils/src/constants/trained_models.ts
@@ -29,6 +29,7 @@ export const SUPPORTED_PYTORCH_TASKS = {
TEXT_CLASSIFICATION: 'text_classification',
TEXT_EMBEDDING: 'text_embedding',
FILL_MASK: 'fill_mask',
+ // Not supported yet by the Trained Models UI
TEXT_EXPANSION: 'text_expansion',
} as const;
export type SupportedPytorchTasksType =
@@ -39,4 +40,34 @@ export const BUILT_IN_MODEL_TYPE = i18n.translate(
{ defaultMessage: 'built-in' }
);
+export const CURATED_MODEL_TYPE = i18n.translate(
+ 'xpack.ml.trainedModels.modelsList.curatedModelLabel',
+ { defaultMessage: 'curated' }
+);
+
export const BUILT_IN_MODEL_TAG = 'prepackaged';
+
+export const CURATED_MODEL_TAG = 'curated';
+
+export const CURATED_MODEL_DEFINITIONS = {
+ '.elser_model_1_SNAPSHOT': {
+ config: {
+ input: {
+ field_names: ['text_field'],
+ },
+ },
+ description: i18n.translate('xpack.ml.trainedModels.modelsList.elserDescription', {
+ defaultMessage: 'Elastic Learned Sparse EncodeR',
+ }),
+ },
+} as const;
+
+export const MODEL_STATE = {
+ ...DEPLOYMENT_STATE,
+ DOWNLOADING: i18n.translate('xpack.ml.trainedModels.modelsList.downloadingStateLabel', {
+ defaultMessage: 'downloading',
+ }),
+ DOWNLOADED: i18n.translate('xpack.ml.trainedModels.modelsList.downloadedStateLabel', {
+ defaultMessage: 'downloaded',
+ }),
+} as const;
diff --git a/x-pack/plugins/alerting/common/index.ts b/x-pack/plugins/alerting/common/index.ts
index 1fa0806effdef..d71b69bcb51e7 100644
--- a/x-pack/plugins/alerting/common/index.ts
+++ b/x-pack/plugins/alerting/common/index.ts
@@ -66,4 +66,4 @@ export const INTERNAL_ALERTING_API_GET_ACTIVE_MAINTENANCE_WINDOWS_PATH =
export const ALERTS_FEATURE_ID = 'alerts';
export const MONITORING_HISTORY_LIMIT = 200;
-export const ENABLE_MAINTENANCE_WINDOWS = false;
+export const ENABLE_MAINTENANCE_WINDOWS = true;
diff --git a/x-pack/plugins/alerting/public/application/maintenance_windows.tsx b/x-pack/plugins/alerting/public/application/maintenance_windows.tsx
index 4005ee739a12b..58cc533d78a28 100644
--- a/x-pack/plugins/alerting/public/application/maintenance_windows.tsx
+++ b/x-pack/plugins/alerting/public/application/maintenance_windows.tsx
@@ -7,7 +7,7 @@
import React, { Suspense } from 'react';
import ReactDOM from 'react-dom';
-import { Redirect, Router, Switch } from 'react-router-dom';
+import { Router, Switch } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { Route } from '@kbn/shared-ux-router';
import { CoreStart } from '@kbn/core/public';
@@ -18,6 +18,7 @@ import { ManagementAppMountParams } from '@kbn/management-plugin/public';
import { EuiLoadingSpinner } from '@elastic/eui';
import { AlertingPluginStart } from '../plugin';
import { paths } from '../config';
+import { useLicense } from '../hooks/use_license';
const MaintenanceWindowsLazy: React.FC = React.lazy(() => import('../pages/maintenance_windows'));
const MaintenanceWindowsCreateLazy: React.FC = React.lazy(
@@ -28,27 +29,39 @@ const MaintenanceWindowsEditLazy: React.FC = React.lazy(
);
const App = React.memo(() => {
+ const { isAtLeastPlatinum } = useLicense();
+ const hasLicense = isAtLeastPlatinum();
+
return (
- <>
-
-
- }>
-
-
-
-
+
+ {hasLicense ? (
+
}>
-
+ ) : null}
+ {hasLicense ? (
+
}>
-
-
- >
+ ) : null}
+
+ }>
+
+
+
+
);
});
App.displayName = 'App';
diff --git a/x-pack/plugins/alerting/public/hooks/use_license.test.tsx b/x-pack/plugins/alerting/public/hooks/use_license.test.tsx
new file mode 100644
index 0000000000000..0611a6ba86aec
--- /dev/null
+++ b/x-pack/plugins/alerting/public/hooks/use_license.test.tsx
@@ -0,0 +1,55 @@
+/*
+ * 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 { licensingMock } from '@kbn/licensing-plugin/public/mocks';
+import { renderHook } from '@testing-library/react-hooks';
+import { useLicense } from './use_license';
+import { AppMockRenderer, createAppMockRenderer } from '../lib/test_utils';
+
+let appMockRenderer: AppMockRenderer;
+
+describe('useLicense', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe('isAtLeastPlatinum', () => {
+ it('returns true on a valid platinum license', () => {
+ const license = licensingMock.createLicense({
+ license: { type: 'platinum' },
+ });
+ appMockRenderer = createAppMockRenderer({ license });
+
+ const { result } = renderHook(
+ () => {
+ return useLicense();
+ },
+ {
+ wrapper: appMockRenderer.AppWrapper,
+ }
+ );
+
+ expect(result.current.isAtLeastPlatinum()).toBeTruthy();
+ });
+
+ it('returns false on a valid gold license', () => {
+ const license = licensingMock.createLicense({
+ license: { type: 'gold' },
+ });
+ appMockRenderer = createAppMockRenderer({ license });
+
+ const { result } = renderHook(
+ () => {
+ return useLicense();
+ },
+ { wrapper: appMockRenderer.AppWrapper }
+ );
+
+ expect(result.current.isAtLeastPlatinum()).toBeFalsy();
+ });
+ });
+});
diff --git a/x-pack/plugins/alerting/public/hooks/use_license.tsx b/x-pack/plugins/alerting/public/hooks/use_license.tsx
new file mode 100644
index 0000000000000..b9d299776e348
--- /dev/null
+++ b/x-pack/plugins/alerting/public/hooks/use_license.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 type { ILicense, LicenseType } from '@kbn/licensing-plugin/public';
+import { useCallback } from 'react';
+import useObservable from 'react-use/lib/useObservable';
+import { Observable } from 'rxjs';
+import { useKibana } from '../utils/kibana_react';
+
+interface UseLicenseReturnValue {
+ isAtLeastPlatinum: () => boolean;
+}
+
+export const useLicense = (): UseLicenseReturnValue => {
+ const { licensing } = useKibana().services;
+ const license = useObservable(licensing?.license$ ?? new Observable(), null);
+
+ const isAtLeast = useCallback(
+ (level: LicenseType): boolean => {
+ return !!license && license.isAvailable && license.isActive && license.hasAtLeast(level);
+ },
+ [license]
+ );
+
+ const isAtLeastPlatinum = useCallback(() => isAtLeast('platinum'), [isAtLeast]);
+
+ return {
+ isAtLeastPlatinum,
+ };
+};
diff --git a/x-pack/plugins/alerting/public/lib/test_utils.tsx b/x-pack/plugins/alerting/public/lib/test_utils.tsx
index 2dfd1f37066bf..56485d7c88ad1 100644
--- a/x-pack/plugins/alerting/public/lib/test_utils.tsx
+++ b/x-pack/plugins/alerting/public/lib/test_utils.tsx
@@ -6,7 +6,7 @@
*/
import React from 'react';
-import { of } from 'rxjs';
+import { of, BehaviorSubject } from 'rxjs';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { I18nProvider } from '@kbn/i18n-react';
import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
@@ -14,11 +14,17 @@ import { render as reactRender, RenderOptions, RenderResult } from '@testing-lib
import { CoreStart } from '@kbn/core/public';
import { coreMock } from '@kbn/core/public/mocks';
import { euiDarkVars } from '@kbn/ui-theme';
+import type { ILicense } from '@kbn/licensing-plugin/public';
+import { licensingMock } from '@kbn/licensing-plugin/public/mocks';
/* eslint-disable no-console */
type UiRender = (ui: React.ReactElement, options?: RenderOptions) => RenderResult;
+interface AppMockRendererArgs {
+ license?: ILicense | null;
+}
+
export interface AppMockRenderer {
render: UiRender;
coreStart: CoreStart;
@@ -26,9 +32,11 @@ export interface AppMockRenderer {
AppWrapper: React.FC<{ children: React.ReactElement }>;
}
-export const createAppMockRenderer = (): AppMockRenderer => {
+export const createAppMockRenderer = ({ license }: AppMockRendererArgs = {}): AppMockRenderer => {
const theme$ = of({ eui: euiDarkVars, darkMode: true });
+ const licensingPluginMock = licensingMock.createStart();
+
const queryClient = new QueryClient({
defaultOptions: {
queries: {
@@ -46,7 +54,13 @@ export const createAppMockRenderer = (): AppMockRenderer => {
},
});
const core = coreMock.createStart();
- const services = { ...core };
+ const services = {
+ ...core,
+ licensing:
+ license != null
+ ? { ...licensingPluginMock, license$: new BehaviorSubject(license) }
+ : licensingPluginMock,
+ };
const AppWrapper: React.FC<{ children: React.ReactElement }> = React.memo(({ children }) => (
diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/license_prompt.test.tsx b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/license_prompt.test.tsx
new file mode 100644
index 0000000000000..3a5ecc97ae1dc
--- /dev/null
+++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/license_prompt.test.tsx
@@ -0,0 +1,27 @@
+/*
+ * 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 { AppMockRenderer, createAppMockRenderer } from '../../../lib/test_utils';
+import { LicensePrompt } from './license_prompt';
+
+describe('LicensePrompt', () => {
+ let appMockRenderer: AppMockRenderer;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ appMockRenderer = createAppMockRenderer();
+ });
+
+ test('it renders', () => {
+ const result = appMockRenderer.render();
+
+ expect(result.getByTestId('license-prompt-title')).toBeInTheDocument();
+ expect(result.getByTestId('license-prompt-upgrade')).toBeInTheDocument();
+ expect(result.getByTestId('license-prompt-trial')).toBeInTheDocument();
+ });
+});
diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/license_prompt.tsx b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/license_prompt.tsx
new file mode 100644
index 0000000000000..61de0593e387b
--- /dev/null
+++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/license_prompt.tsx
@@ -0,0 +1,69 @@
+/*
+ * 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 {
+ EuiButton,
+ EuiButtonEmpty,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiPageTemplate,
+ EuiText,
+} from '@elastic/eui';
+import * as i18n from '../translations';
+import { useKibana } from '../../../utils/kibana_react';
+
+const title = {i18n.UPGRADE_TO_PLATINUM}
;
+
+export const LicensePrompt = React.memo(() => {
+ const { application } = useKibana().services;
+
+ return (
+
+
+
+ {i18n.UPGRADE_TO_PLATINUM_SUBTITLE}
+
+
+
+
+
+
+ {i18n.UPGRADE_SUBSCRIPTION}
+
+ ,
+
+
+
+ {i18n.START_TRIAL}
+
+ ,
+
+
+
+
+ }
+ />
+ );
+});
+LicensePrompt.displayName = 'LicensePrompt';
diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/index.tsx b/x-pack/plugins/alerting/public/pages/maintenance_windows/index.tsx
index fa9b54122562d..ac6d0b5534b9a 100644
--- a/x-pack/plugins/alerting/public/pages/maintenance_windows/index.tsx
+++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/index.tsx
@@ -26,9 +26,13 @@ import { MaintenanceWindowsList } from './components/maintenance_windows_list';
import { useFindMaintenanceWindows } from '../../hooks/use_find_maintenance_windows';
import { CenterJustifiedSpinner } from './components/center_justified_spinner';
import { ExperimentalBadge } from './components/page_header';
+import { useLicense } from '../../hooks/use_license';
+import { LicensePrompt } from './components/license_prompt';
export const MaintenanceWindowsPage = React.memo(() => {
const { docLinks } = useKibana().services;
+ const { isAtLeastPlatinum } = useLicense();
+
const { navigateToCreateMaintenanceWindow } = useCreateMaintenanceWindowNavigation();
const { isLoading, maintenanceWindows, refetch } = useFindMaintenanceWindows();
@@ -42,6 +46,7 @@ export const MaintenanceWindowsPage = React.memo(() => {
const refreshData = useCallback(() => refetch(), [refetch]);
const showEmptyPrompt = !isLoading && maintenanceWindows.length === 0;
+ const hasLicense = isAtLeastPlatinum();
if (isLoading) {
return ;
@@ -66,7 +71,7 @@ export const MaintenanceWindowsPage = React.memo(() => {
{i18n.MAINTENANCE_WINDOWS_DESCRIPTION}
- {!showEmptyPrompt ? (
+ {!showEmptyPrompt && hasLicense ? (
{i18n.CREATE_NEW_BUTTON}
@@ -74,7 +79,9 @@ export const MaintenanceWindowsPage = React.memo(() => {
) : null}
- {showEmptyPrompt ? (
+ {!hasLicense ? (
+
+ ) : showEmptyPrompt ? (
) : (
<>
diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts b/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts
index 65f24411a2a1a..7e0c2fa484f7a 100644
--- a/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts
+++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/translations.ts
@@ -568,3 +568,31 @@ export const EXPERIMENTAL_DESCRIPTION = i18n.translate(
export const UPCOMING = i18n.translate('xpack.alerting.maintenanceWindows.upcoming', {
defaultMessage: 'Upcoming',
});
+
+export const UPGRADE_TO_PLATINUM = i18n.translate(
+ 'xpack.alerting.maintenanceWindows.licenseCallout.updgradeToPlatinumTitle',
+ {
+ defaultMessage: 'Maintenance Windows are a subscription feature.',
+ }
+);
+
+export const UPGRADE_TO_PLATINUM_SUBTITLE = i18n.translate(
+ 'xpack.alerting.maintenanceWindows.licenseCallout.upgradeToPlatinumSubtitle',
+ {
+ defaultMessage: 'Select an option to unlock it.',
+ }
+);
+
+export const UPGRADE_SUBSCRIPTION = i18n.translate(
+ 'xpack.alerting.maintenanceWindows.licenseCallout.upgradeSubscription',
+ {
+ defaultMessage: 'Upgrade subscription',
+ }
+);
+
+export const START_TRIAL = i18n.translate(
+ 'xpack.alerting.maintenanceWindows.licenseCallout.startTrial',
+ {
+ defaultMessage: 'Start trial',
+ }
+);
diff --git a/x-pack/plugins/alerting/server/lib/convert_rule_ids_to_kuery_node.test.ts b/x-pack/plugins/alerting/server/lib/convert_rule_ids_to_kuery_node.test.ts
index 0c17c68d6162b..258133dab6118 100644
--- a/x-pack/plugins/alerting/server/lib/convert_rule_ids_to_kuery_node.test.ts
+++ b/x-pack/plugins/alerting/server/lib/convert_rule_ids_to_kuery_node.test.ts
@@ -61,6 +61,10 @@ describe('convertRuleIdsToKueryNode', () => {
});
test('should convert empty ids array correctly', () => {
- expect(convertRuleIdsToKueryNode([])).toEqual(undefined);
+ expect(convertRuleIdsToKueryNode([])).toEqual({
+ arguments: [],
+ function: 'or',
+ type: 'function',
+ });
});
});
diff --git a/x-pack/plugins/apm/public/components/app/mobile/transaction_overview/transaction_overview_tabs/stats_list/get_columns.tsx b/x-pack/plugins/apm/public/components/app/mobile/transaction_overview/transaction_overview_tabs/stats_list/get_columns.tsx
index 18ac252011357..9bea439931bed 100644
--- a/x-pack/plugins/apm/public/components/app/mobile/transaction_overview/transaction_overview_tabs/stats_list/get_columns.tsx
+++ b/x-pack/plugins/apm/public/components/app/mobile/transaction_overview/transaction_overview_tabs/stats_list/get_columns.tsx
@@ -33,13 +33,11 @@ type MobileDetailedStatisticsByField =
APIReturnType<'GET /internal/apm/mobile-services/{serviceName}/detailed_statistics'>;
export function getColumns({
- agentName,
detailedStatisticsLoading,
detailedStatistics,
comparisonEnabled,
offset,
}: {
- agentName?: string;
detailedStatisticsLoading: boolean;
detailedStatistics: MobileDetailedStatisticsByField;
comparisonEnabled?: boolean;
diff --git a/x-pack/plugins/apm/server/routes/mobile/get_mobile_main_statistics_by_field.ts b/x-pack/plugins/apm/server/routes/mobile/get_mobile_main_statistics_by_field.ts
index a5783997e391b..af345d52322bf 100644
--- a/x-pack/plugins/apm/server/routes/mobile/get_mobile_main_statistics_by_field.ts
+++ b/x-pack/plugins/apm/server/routes/mobile/get_mobile_main_statistics_by_field.ts
@@ -40,10 +40,18 @@ export interface MobileMainStatisticsResponse {
name: string | number;
latency: number | null;
throughput: number;
- crashRate?: number;
+ crashRate: number;
}>;
}
+type MergedQueriesResponse = Array<{
+ name: string | number;
+ latency: number | null;
+ throughput: number;
+ sessions: number;
+ crashes?: number;
+}>;
+
export async function getMobileMainStatisticsByField({
kuery,
apmEventClient,
@@ -55,7 +63,7 @@ export async function getMobileMainStatisticsByField({
}: Props) {
async function getMobileTransactionEventStatistics() {
const response = await apmEventClient.search(
- `get_mobile_main_statistics_by_field`,
+ `get_mobile_transaction_events_main_statistics_by_field`,
{
apm: {
sources: [
@@ -90,6 +98,11 @@ export async function getMobileMainStatisticsByField({
field: TRANSACTION_DURATION,
},
},
+ sessions: {
+ cardinality: {
+ field: SESSION_ID,
+ },
+ },
},
},
},
@@ -110,66 +123,59 @@ export async function getMobileMainStatisticsByField({
end,
value: bucket.doc_count,
}),
+ sessions: bucket.sessions.value,
};
}) ?? []
);
}
async function getMobileErrorEventStatistics() {
- const response = await apmEventClient.search(
- `get_mobile_transaction_events_main_statistics_by_field`,
- {
- apm: {
- sources: [
- {
- documentType: ApmDocumentType.ErrorEvent,
- rollupInterval: RollupInterval.None,
- },
- ],
+ const response = await apmEventClient.search(`get_mobile_crashes`, {
+ apm: {
+ sources: [
+ {
+ documentType: ApmDocumentType.ErrorEvent,
+ rollupInterval: RollupInterval.None,
+ },
+ ],
+ },
+ body: {
+ track_total_hits: false,
+ size: 0,
+ query: {
+ bool: {
+ filter: [
+ ...termQuery(SERVICE_NAME, serviceName),
+ ...rangeQuery(start, end),
+ ...environmentQuery(environment),
+ ...kqlQuery(kuery),
+ ],
+ },
},
- body: {
- track_total_hits: false,
- size: 0,
- query: {
- bool: {
- filter: [
- ...termQuery(SERVICE_NAME, serviceName),
- ...rangeQuery(start, end),
- ...environmentQuery(environment),
- ...kqlQuery(kuery),
- ],
+ aggs: {
+ main_statistics: {
+ terms: {
+ field,
+ size: 1000,
},
- },
- aggs: {
- main_statistics: {
- terms: {
- field,
- size: 1000,
- },
- aggs: {
- sessions: {
- cardinality: {
- field: SESSION_ID,
- },
- },
- crashes: {
- filter: {
- term: {
- [ERROR_TYPE]: 'crash',
- },
+ aggs: {
+ crashes: {
+ filter: {
+ term: {
+ [ERROR_TYPE]: 'crash',
},
},
},
},
},
},
- }
- );
+ },
+ });
return (
response.aggregations?.main_statistics.buckets.map((bucket) => {
return {
name: bucket.key,
- crashRate: bucket.crashes.doc_count / bucket.sessions.value ?? 0,
+ crashes: bucket.crashes.doc_count ?? 0,
};
}) ?? []
);
@@ -180,7 +186,19 @@ export async function getMobileMainStatisticsByField({
getMobileErrorEventStatistics(),
]);
- const mainStatistics = merge(transactioEventStatistics, errorEventStatistics);
+ const mainStatisticsMerged: MergedQueriesResponse = merge(
+ transactioEventStatistics,
+ errorEventStatistics
+ );
+
+ const mainStatistics = mainStatisticsMerged.map((item) => {
+ return {
+ name: item.name,
+ latency: item.latency,
+ throughput: item.throughput,
+ crashRate: item.crashes ? item.crashes / item.sessions : 0,
+ };
+ });
return { mainStatistics };
}
diff --git a/x-pack/plugins/cases/public/components/files/file_type.test.tsx b/x-pack/plugins/cases/public/components/files/file_type.test.tsx
index 8d4fd4c0eabde..8d2c782ee0aae 100644
--- a/x-pack/plugins/cases/public/components/files/file_type.test.tsx
+++ b/x-pack/plugins/cases/public/components/files/file_type.test.tsx
@@ -27,6 +27,7 @@ describe('getFileType', () => {
icon: 'document',
displayName: 'File Attachment Type',
getAttachmentViewObject: expect.any(Function),
+ getAttachmentRemovalObject: expect.any(Function),
});
});
@@ -184,4 +185,11 @@ describe('getFileType', () => {
);
});
});
+
+ describe('getFileAttachmentRemovalObject', () => {
+ it('event renders the right message', async () => {
+ // @ts-ignore
+ expect(fileType.getAttachmentRemovalObject().event).toBe('removed file');
+ });
+ });
});
diff --git a/x-pack/plugins/cases/public/components/files/file_type.tsx b/x-pack/plugins/cases/public/components/files/file_type.tsx
index 271bf3008e70e..f8b434d984861 100644
--- a/x-pack/plugins/cases/public/components/files/file_type.tsx
+++ b/x-pack/plugins/cases/public/components/files/file_type.tsx
@@ -103,4 +103,5 @@ export const getFileType = (): ExternalReferenceAttachmentType => ({
icon: 'document',
displayName: 'File Attachment Type',
getAttachmentViewObject: getFileAttachmentViewObject,
+ getAttachmentRemovalObject: () => ({ event: i18n.REMOVED_FILE }),
});
diff --git a/x-pack/plugins/cases/public/components/files/translations.tsx b/x-pack/plugins/cases/public/components/files/translations.tsx
index 4023c5b18cea8..2fd8ed28c6aa4 100644
--- a/x-pack/plugins/cases/public/components/files/translations.tsx
+++ b/x-pack/plugins/cases/public/components/files/translations.tsx
@@ -113,3 +113,7 @@ export const DELETE = i18n.translate('xpack.cases.caseView.files.delete', {
export const DELETE_FILE_TITLE = i18n.translate('xpack.cases.caseView.files.deleteThisFile', {
defaultMessage: 'Delete this file?',
});
+
+export const REMOVED_FILE = i18n.translate('xpack.cases.caseView.files.removedFile', {
+ defaultMessage: 'removed file',
+});
diff --git a/x-pack/plugins/cases/public/components/user_actions/comment/registered_attachments.tsx b/x-pack/plugins/cases/public/components/user_actions/comment/registered_attachments.tsx
index 9142a71ed7184..71457cb49e089 100644
--- a/x-pack/plugins/cases/public/components/user_actions/comment/registered_attachments.tsx
+++ b/x-pack/plugins/cases/public/components/user_actions/comment/registered_attachments.tsx
@@ -50,10 +50,10 @@ type BuilderArgs = Pick<
/**
* Provides a render function for attachment type
*/
-const getAttachmentRenderer = memoize((attachmentViewObject: AttachmentViewObject) => {
+const getAttachmentRenderer = memoize(() => {
let AttachmentElement: React.ReactElement;
- const renderCallback = (props: object) => {
+ const renderCallback = (attachmentViewObject: AttachmentViewObject, props: object) => {
if (!attachmentViewObject.children) return;
if (!AttachmentElement) {
@@ -120,7 +120,7 @@ export const createRegisteredAttachmentUserActionBuilder = <
const attachmentViewObject = attachmentType.getAttachmentViewObject(props);
- const renderer = getAttachmentRenderer(attachmentViewObject);
+ const renderer = getAttachmentRenderer();
const actions = attachmentViewObject.getActions?.(props) ?? [];
const [primaryActions, nonPrimaryActions] = partition(actions, 'isPrimary');
const visiblePrimaryActions = primaryActions.slice(0, 2);
@@ -164,7 +164,7 @@ export const createRegisteredAttachmentUserActionBuilder = <
/>
),
- children: renderer(props),
+ children: renderer(attachmentViewObject, props),
},
];
},
diff --git a/x-pack/plugins/cloud_defend/public/components/control_general_view/index.tsx b/x-pack/plugins/cloud_defend/public/components/control_general_view/index.tsx
index 0a253b8c68a73..0c126de3b39ec 100644
--- a/x-pack/plugins/cloud_defend/public/components/control_general_view/index.tsx
+++ b/x-pack/plugins/cloud_defend/public/components/control_general_view/index.tsx
@@ -338,8 +338,9 @@ export const ControlGeneralView = ({ policy, onChange, show }: ViewDeps) => {
{selectors.map((selector, i) => {
- const usedByResponse = !!responses.find((response) =>
- response.match.includes(selector.name)
+ const usedByResponse = !!responses.find(
+ (response) =>
+ response.match.includes(selector.name) || response?.exclude?.includes(selector.name)
);
return (
diff --git a/x-pack/plugins/cloud_defend/public/components/control_general_view/translations.ts b/x-pack/plugins/cloud_defend/public/components/control_general_view/translations.ts
index d80ef8030ea6a..1cb3d92150b6b 100644
--- a/x-pack/plugins/cloud_defend/public/components/control_general_view/translations.ts
+++ b/x-pack/plugins/cloud_defend/public/components/control_general_view/translations.ts
@@ -156,6 +156,10 @@ export const errorValueRequired = i18n.translate('xpack.cloudDefend.errorValueRe
defaultMessage: 'At least one value is required.',
});
+export const errorActionRequired = i18n.translate('xpack.cloudDefend.errorActionRequired', {
+ defaultMessage: 'At least one action is required.',
+});
+
export const getSelectorIconTooltip = (type: SelectorType) => {
switch (type) {
case 'process':
diff --git a/x-pack/plugins/cloud_defend/public/components/control_general_view_response/index.test.tsx b/x-pack/plugins/cloud_defend/public/components/control_general_view_response/index.test.tsx
index 24bfcbadc124f..099ce4a1c2a2e 100644
--- a/x-pack/plugins/cloud_defend/public/components/control_general_view_response/index.test.tsx
+++ b/x-pack/plugins/cloud_defend/public/components/control_general_view_response/index.test.tsx
@@ -206,4 +206,19 @@ describe('', () => {
expect(onDuplicate.mock.calls).toHaveLength(1);
expect(onDuplicate.mock.calls[0][0]).toEqual(mockResponse);
});
+
+ it('shows an error if no actions specified', async () => {
+ const { getByTestId, getByText, rerender } = render();
+
+ const checkBox = getByTestId('cloud-defend-chkalertaction');
+ if (checkBox) {
+ userEvent.click(checkBox);
+ }
+
+ const updatedResponse = onChange.mock.calls[0][0];
+ rerender();
+
+ expect(getByText(i18n.errorActionRequired)).toBeTruthy();
+ expect(updatedResponse.hasErrors).toBeTruthy();
+ });
});
diff --git a/x-pack/plugins/cloud_defend/public/components/control_general_view_response/index.tsx b/x-pack/plugins/cloud_defend/public/components/control_general_view_response/index.tsx
index 88fed27c61455..af037c70bd71a 100644
--- a/x-pack/plugins/cloud_defend/public/components/control_general_view_response/index.tsx
+++ b/x-pack/plugins/cloud_defend/public/components/control_general_view_response/index.tsx
@@ -29,7 +29,12 @@ import {
} from '@elastic/eui';
import { useStyles } from './styles';
import { useStyles as useSelectorStyles } from '../control_general_view_selector/styles';
-import { ControlGeneralViewResponseDeps, ResponseAction } from '../../types';
+import {
+ ControlGeneralViewResponseDeps,
+ ResponseAction,
+ Response,
+ ControlFormErrorMap,
+} from '../../types';
import * as i18n from '../control_general_view/translations';
import { getSelectorTypeIcon } from '../../common/utils';
@@ -59,6 +64,21 @@ export const ControlGeneralViewResponse = ({
const [accordionState, setAccordionState] = useState<'open' | 'closed'>(
responses.length - 1 === index ? 'open' : 'closed'
);
+ const onResponseChange = useCallback(
+ (resp: Response, i: number) => {
+ const hasMatch = resp.match.length > 0;
+ const hasActions = resp.actions.length > 0;
+
+ if (!hasMatch || !hasActions) {
+ resp.hasErrors = true;
+ } else {
+ delete resp.hasErrors;
+ }
+
+ onChange(resp, i);
+ },
+ [onChange]
+ );
const onTogglePopover = useCallback(() => {
setPopoverOpen(!isPopoverOpen);
@@ -81,15 +101,10 @@ export const ControlGeneralViewResponse = ({
const onChangeMatches = useCallback(
(options) => {
response.match = options.map((option: EuiComboBoxOptionOption) => option.value);
- if (response.match.length === 0) {
- response.hasErrors = true;
- } else {
- delete response.hasErrors; // keeps it out of the yaml.
- }
- onChange(response, index);
+ onResponseChange(response, index);
},
- [index, onChange, response]
+ [index, onResponseChange, response]
);
const onChangeExcludes = useCallback(
@@ -100,9 +115,9 @@ export const ControlGeneralViewResponse = ({
delete response.exclude;
}
- onChange(response, index);
+ onResponseChange(response, index);
},
- [index, onChange, response]
+ [index, onResponseChange, response]
);
const selectorOptions = useMemo(() => {
@@ -142,8 +157,8 @@ export const ControlGeneralViewResponse = ({
const onShowExclude = useCallback(() => {
const updatedResponse = { ...response };
updatedResponse.exclude = [];
- onChange(updatedResponse, index);
- }, [index, onChange, response]);
+ onResponseChange(updatedResponse, index);
+ }, [index, onResponseChange, response]);
const logSelected = response.actions.includes('log');
const alertSelected = response.actions.includes('alert');
@@ -170,20 +185,24 @@ export const ControlGeneralViewResponse = ({
updatedResponse.actions.splice(actionIndex, 1);
}
- onChange(updatedResponse, index);
+ onResponseChange(updatedResponse, index);
},
- [index, onChange, response]
+ [index, onResponseChange, response]
);
const errors = useMemo(() => {
- const errs: string[] = [];
+ const errs: ControlFormErrorMap = {};
if (response.match.length === 0) {
- errs.push(i18n.errorValueRequired);
+ errs.match = [i18n.errorValueRequired];
+ }
+
+ if (response.actions.length === 0) {
+ errs.actions = [i18n.errorActionRequired];
}
return errs;
- }, [response.match.length]);
+ }, [response.actions.length, response.match.length]);
const onToggleAccordion = useCallback((isOpen: boolean) => {
setAccordionState(isOpen ? 'open' : 'closed');
@@ -205,6 +224,8 @@ export const ControlGeneralViewResponse = ({
};
}, [accordionState, response.match]);
+ const errorList = useMemo(() => Object.values(errors), [errors]);
+
return (
}
>
- 0}>
- 0}>
+ 0}>
+
)}
-
+
{
// validate responses
responses.forEach((response) => {
// for now we force 'alert' action if 'block' action added.
- if (response.actions.includes('block') && !response.actions.includes('alert')) {
+ if (
+ response.actions &&
+ response.actions.includes('block') &&
+ !response.actions.includes('alert')
+ ) {
errors.push(i18n.errorAlertActionRequired);
}
});
diff --git a/x-pack/plugins/cloud_integrations/cloud_chat/common/constants.ts b/x-pack/plugins/cloud_integrations/cloud_chat/common/constants.ts
index 33dd954b5756f..b196959a582d3 100755
--- a/x-pack/plugins/cloud_integrations/cloud_chat/common/constants.ts
+++ b/x-pack/plugins/cloud_integrations/cloud_chat/common/constants.ts
@@ -6,4 +6,4 @@
*/
export const GET_CHAT_USER_DATA_ROUTE_PATH = '/internal/cloud/chat_user';
-export const DEFAULT_TRIAL_BUFFER = 60;
+export const DEFAULT_TRIAL_BUFFER = 90;
diff --git a/x-pack/plugins/enterprise_search/common/connectors/native_connectors.ts b/x-pack/plugins/enterprise_search/common/connectors/native_connectors.ts
index bccfe15835a63..a130d1260d5c4 100644
--- a/x-pack/plugins/enterprise_search/common/connectors/native_connectors.ts
+++ b/x-pack/plugins/enterprise_search/common/connectors/native_connectors.ts
@@ -7,7 +7,7 @@
import { i18n } from '@kbn/i18n';
-import { DisplayType, FeatureName, NativeConnector } from '../types/connectors';
+import { DisplayType, FeatureName, FieldType, NativeConnector } from '../types/connectors';
export const NATIVE_CONNECTOR_DEFINITIONS: Record = {
mongodb: {
@@ -27,7 +27,7 @@ export const NATIVE_CONNECTOR_DEFINITIONS: Record ({
{`http.cors.allow-origin: "*"
-http.cors.enabled: true
+http.cors.enabled: true
http.cors.allow-credentials: true
http.cors.allow-methods: OPTIONS, POST
http.cors.allow-headers: X-Requested-With, X-Auth-Token, Content-Type, Content-Length, Authorization, Access-Control-Allow-Headers, Accept`}
@@ -272,7 +272,7 @@ export const AnalyticsCollectionIntegrateView: React.FC {
+ const { http } = mockHttpValues;
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+ describe('convertConnector', () => {
+ it('calls correct api', async () => {
+ const promise = Promise.resolve('result');
+ http.put.mockReturnValue(promise);
+ const result = convertConnector({ connectorId: 'connectorId1' });
+ await nextTick();
+ expect(http.put).toHaveBeenCalledWith(
+ '/internal/enterprise_search/connectors/connectorId1/native',
+ { body: JSON.stringify({ is_native: false }) }
+ );
+ await expect(result).resolves.toEqual('result');
+ });
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/convert_connector_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/convert_connector_api_logic.ts
new file mode 100644
index 0000000000000..ba998c9dbce9d
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/convert_connector_api_logic.ts
@@ -0,0 +1,32 @@
+/*
+ * 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 { createApiLogic } from '../../../shared/api_logic/create_api_logic';
+import { HttpLogic } from '../../../shared/http';
+
+export interface ConvertConnectorApiLogicArgs {
+ connectorId: string;
+}
+
+export interface ConvertConnectorApiLogicResponse {
+ updated: boolean;
+}
+
+export const convertConnector = async ({
+ connectorId,
+}: ConvertConnectorApiLogicArgs): Promise => {
+ const route = `/internal/enterprise_search/connectors/${connectorId}/native`;
+
+ return await HttpLogic.values.http.put<{ updated: boolean }>(route, {
+ body: JSON.stringify({ is_native: false }),
+ });
+};
+
+export const ConvertConnectorApiLogic = createApiLogic(
+ ['convert_connector_api_logic'],
+ convertConnector
+);
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/text_expansion/create_text_expansion_model_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/text_expansion/create_text_expansion_model_api_logic.ts
new file mode 100644
index 0000000000000..315815a02ef08
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/text_expansion/create_text_expansion_model_api_logic.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 { ELSER_MODEL_ID } from '../../../../../../common/ml_inference_pipeline';
+import { Actions, createApiLogic } from '../../../../shared/api_logic/create_api_logic';
+import { HttpLogic } from '../../../../shared/http';
+
+export type CreateTextExpansionModelArgs = undefined;
+
+export interface CreateTextExpansionModelResponse {
+ deploymentState: string;
+ modelId: string;
+}
+
+export const createTextExpansionModel = async (): Promise => {
+ const route = `/internal/enterprise_search/ml/models/${ELSER_MODEL_ID}`;
+ return await HttpLogic.values.http.post(route, {
+ body: undefined,
+ });
+};
+
+export const CreateTextExpansionModelApiLogic = createApiLogic(
+ ['create_text_expansion_model_api_logic'],
+ createTextExpansionModel
+);
+
+export type CreateTextExpansionModelApiLogicActions = Actions<
+ CreateTextExpansionModelArgs,
+ CreateTextExpansionModelResponse
+>;
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/text_expansion/fetch_text_expansion_model_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/text_expansion/fetch_text_expansion_model_api_logic.ts
new file mode 100644
index 0000000000000..dd9430dfb07cc
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/text_expansion/fetch_text_expansion_model_api_logic.ts
@@ -0,0 +1,32 @@
+/*
+ * 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 { ELSER_MODEL_ID } from '../../../../../../common/ml_inference_pipeline';
+import { Actions, createApiLogic } from '../../../../shared/api_logic/create_api_logic';
+import { HttpLogic } from '../../../../shared/http';
+
+export type FetchTextExpansionModelArgs = undefined;
+
+export interface FetchTextExpansionModelResponse {
+ deploymentState: string;
+ modelId: string;
+}
+
+export const fetchTextExpansionModelStatus = async () => {
+ return await HttpLogic.values.http.get(
+ `/internal/enterprise_search/ml/models/${ELSER_MODEL_ID}`
+ );
+};
+
+export const FetchTextExpansionModelApiLogic = createApiLogic(
+ ['fetch_text_expansion_model_api_logic'],
+ fetchTextExpansionModelStatus
+);
+
+export type FetchTextExpansionModelApiLogicActions = Actions<
+ FetchTextExpansionModelArgs,
+ FetchTextExpansionModelResponse
+>;
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_api_integration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_api_integration.tsx
index 2fe691e262b64..0d47f02e7475b 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_api_integration.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_api_integration.tsx
@@ -19,7 +19,7 @@ import { EngineApiLogic } from './engine_api_logic';
import { elasticsearchUrl } from './search_application_api';
-const SearchUISnippet = (esUrl: string, engineName: string, apiKey: string) => `6
+const SearchUISnippet = (esUrl: string, engineName: string, apiKey: string) => `
import EnginesAPIConnector from "@elastic/search-ui-engines-connector";
const connector = new EnginesAPIConnector({
@@ -74,7 +74,7 @@ export const EngineApiIntegrationStage: React.FC = () => {
{i18n.translate('xpack.enterpriseSearch.content.engine.api.step3.intro', {
defaultMessage:
- 'Learn how to integrate with your search application with the language clients maintained by Elastic to help build your search experience.',
+ 'Use the following code snippets to connect to your search application.',
})}
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_connect.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_connect.tsx
index a6c29285f4f66..018470da69410 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_connect.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_connect.tsx
@@ -26,6 +26,8 @@ import { EngineViewLogic } from '../engine_view_logic';
import { SearchApplicationAPI } from './search_application_api';
+import '../search_application_layout.scss';
+
const pageTitle = i18n.translate(
'xpack.enterpriseSearch.content.searchApplications.connect.pageTitle',
{
@@ -68,6 +70,8 @@ export const EngineConnect: React.FC = () => {
pageViewTelemetry={EngineViewTabs.CONNECT}
isLoading={isLoadingEngine}
pageHeader={{
+ bottomBorder: false,
+ className: 'searchApplicationHeaderBackgroundColor',
pageTitle,
rightSideItems: [],
}}
@@ -84,6 +88,8 @@ export const EngineConnect: React.FC = () => {
pageViewTelemetry={EngineViewTabs.CONNECT}
isLoading={isLoadingEngine}
pageHeader={{
+ bottomBorder: false,
+ className: 'searchApplicationHeaderBackgroundColor',
pageTitle,
rightSideItems: [],
tabs: [
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_search_preview/engine_search_preview.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_search_preview/engine_search_preview.tsx
index 5cd4895c3aa35..25818607f102f 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_search_preview/engine_search_preview.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_search_preview/engine_search_preview.tsx
@@ -72,6 +72,7 @@ import {
ResultsView,
Sorting,
} from './search_ui_components';
+import '../search_application_layout.scss';
class InternalEngineTransporter implements Transporter {
constructor(
@@ -313,6 +314,8 @@ export const EngineSearchPreview: React.FC = () => {
pageViewTelemetry={EngineViewTabs.PREVIEW}
isLoading={isLoadingEngine}
pageHeader={{
+ bottomBorder: false,
+ className: 'searchApplicationHeaderBackgroundColor',
pageTitle: (
{
pageChrome={[engineName]}
pageViewTelemetry={tabId}
pageHeader={{
+ bottomBorder: false,
pageTitle: engineName,
rightSideItems: [],
}}
@@ -91,6 +92,7 @@ export const EngineView: React.FC = () => {
pageChrome={[engineName]}
pageViewTelemetry={tabId}
pageHeader={{
+ bottomBorder: false,
pageTitle: engineName,
rightSideItems: [],
}}
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/search_application_content.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/search_application_content.tsx
index 4bf79b31a1491..4fa39f2fa0cf2 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/search_application_content.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/search_application_content.tsx
@@ -30,6 +30,7 @@ import { EngineIndices } from './engine_indices';
import { EngineIndicesLogic } from './engine_indices_logic';
import { EngineSchema } from './engine_schema';
import { EngineViewLogic } from './engine_view_logic';
+import './search_application_layout.scss';
const pageTitle = i18n.translate(
'xpack.enterpriseSearch.content.searchApplications.content.pageTitle',
@@ -78,6 +79,8 @@ export const SearchApplicationContent = () => {
pageViewTelemetry={EngineViewTabs.CONTENT}
isLoading={isLoadingEngine}
pageHeader={{
+ bottomBorder: false,
+ className: 'searchApplicationHeaderBackgroundColor',
pageTitle,
rightSideItems: [],
}}
@@ -103,6 +106,7 @@ export const SearchApplicationContent = () => {
pageViewTelemetry={EngineViewTabs.CONTENT}
isLoading={isLoadingEngine}
pageHeader={{
+ bottomBorder: false,
breadcrumbs: [
{
color: 'primary',
@@ -119,6 +123,7 @@ export const SearchApplicationContent = () => {
),
},
],
+ className: 'searchApplicationHeaderBackgroundColor',
pageTitle,
rightSideItems: [
{
expect(popover).toHaveLength(1);
expect(popover.text()).toMatch(
- 'This functionality is in technical preview and may be changed or removed completely in a future release.'
+ 'This functionality may be changed or removed completely in a future release.'
);
});
});
@@ -177,7 +177,7 @@ describe('CreateEngineButton', () => {
expect(popover).toHaveLength(1);
expect(popover.text()).toMatch(
- 'This functionality is in technical preview and may be changed or removed completely in a future release.'
+ 'This functionality may be changed or removed completely in a future release.'
);
});
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_list.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_list.tsx
index 95d160798d1fe..c47e35fb3fe9e 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_list.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_list.tsx
@@ -97,7 +97,7 @@ export const CreateEngineButton: React.FC = ({ disabled
@@ -170,7 +170,7 @@ export const EnginesList: React.FC = ({ createEngineFlyoutOpen }) =>
description: (
{
setLocalConfigEntry({ ...configEntry, value: event.target.value });
}}
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_form.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_form.tsx
index 2c40eb0beafa4..47909e53d8655 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_form.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_form.tsx
@@ -51,9 +51,11 @@ export const ConnectorConfigurationForm = () => {
depends_on: dependencies,
key,
display,
+ is_valid: isValid,
label,
sensitive,
tooltip,
+ validation_errors: validationErrors,
} = configEntry;
const helpText = defaultValue
? i18n.translate(
@@ -89,7 +91,14 @@ export const ConnectorConfigurationForm = () => {
<>
{topSpacing}
-
+
@@ -99,7 +108,14 @@ export const ConnectorConfigurationForm = () => {
}
return (
-
+
);
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.test.ts
index f87d73b882ecd..2211f64645c35 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.test.ts
@@ -8,7 +8,7 @@
import { LogicMounter } from '../../../../__mocks__/kea_logic';
import { connectorIndex } from '../../../__mocks__/view_index.mock';
-import { ConnectorStatus, DisplayType } from '../../../../../../common/types/connectors';
+import { ConnectorStatus, DisplayType, FieldType } from '../../../../../../common/types/connectors';
import { ConnectorConfigurationApiLogic } from '../../../api/connector/update_connector_configuration_api_logic';
import { CachedFetchIndexApiLogic } from '../../../api/index/cached_fetch_index_api_logic';
@@ -61,6 +61,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: false,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
value: 'oldBar',
},
@@ -80,6 +81,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: false,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
value: 'oldBar',
},
@@ -89,6 +91,7 @@ describe('ConnectorConfigurationLogic', () => {
default_value: '',
depends_on: [],
display: DisplayType.TEXTBOX,
+ is_valid: true,
key: 'foo',
label: 'newBar',
options: [],
@@ -96,7 +99,9 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: false,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
+ validation_errors: [],
value: 'oldBar',
},
],
@@ -114,6 +119,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: false,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
value: 'fourthBar',
},
@@ -131,6 +137,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: false,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
value: 'fourthBar',
},
@@ -140,6 +147,7 @@ describe('ConnectorConfigurationLogic', () => {
default_value: '',
depends_on: [],
display: DisplayType.TEXTBOX,
+ is_valid: true,
key: 'foo',
label: 'thirdBar',
options: [],
@@ -147,7 +155,9 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: false,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
+ validation_errors: [],
value: 'fourthBar',
},
],
@@ -166,6 +176,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: false,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
value: 'foofoo',
},
@@ -179,6 +190,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: true,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
value: 'fourthBar',
},
@@ -192,6 +204,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: true,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: ['advanced'],
value: 'I am restricted',
},
@@ -205,6 +218,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: true,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
value: 'I should appear (one dependency)',
},
@@ -221,6 +235,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: true,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
value: 'I should appear (multiple dependencies)',
},
@@ -234,6 +249,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: true,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
value: 'I should hide (one dependency)',
},
@@ -250,6 +266,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: true,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
value: 'I should hide (multiple dependencies)',
},
@@ -265,6 +282,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: false,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
value: 'foofoo',
},
@@ -278,6 +296,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: true,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
value: 'fourthBar',
},
@@ -291,6 +310,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: true,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: ['advanced'],
value: 'I am restricted',
},
@@ -304,6 +324,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: true,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
value: 'I should appear (one dependency)',
},
@@ -320,6 +341,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: true,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
value: 'I should appear (multiple dependencies)',
},
@@ -333,6 +355,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: true,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
value: 'I should hide (one dependency)',
},
@@ -349,6 +372,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: true,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
value: 'I should hide (multiple dependencies)',
},
@@ -357,6 +381,7 @@ describe('ConnectorConfigurationLogic', () => {
default_value: '',
depends_on: [],
display: DisplayType.TEXTBOX,
+ is_valid: true,
key: 'bar',
label: 'foo',
options: [],
@@ -364,7 +389,9 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: false,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
+ validation_errors: [],
value: 'fafa',
});
expect(ConnectorConfigurationLogic.values).toEqual({
@@ -380,6 +407,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: false,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
value: 'foofoo',
},
@@ -393,6 +421,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: true,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
value: 'fourthBar',
},
@@ -406,6 +435,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: true,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: ['advanced'],
value: 'I am restricted',
},
@@ -419,6 +449,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: true,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
value: 'I should appear (one dependency)',
},
@@ -435,6 +466,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: true,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
value: 'I should appear (multiple dependencies)',
},
@@ -448,6 +480,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: true,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
value: 'I should hide (one dependency)',
},
@@ -464,6 +497,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: true,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
value: 'I should hide (multiple dependencies)',
},
@@ -473,6 +507,7 @@ describe('ConnectorConfigurationLogic', () => {
default_value: '',
depends_on: [],
display: DisplayType.TEXTBOX,
+ is_valid: true,
key: 'bar',
label: 'foo',
options: [],
@@ -480,13 +515,16 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: false,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
+ validation_errors: [],
value: 'foofoo',
},
{
default_value: '',
depends_on: [],
display: DisplayType.TEXTBOX,
+ is_valid: true,
key: 'password',
label: 'thirdBar',
options: [],
@@ -494,13 +532,16 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: true,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
+ validation_errors: [],
value: 'fourthBar',
},
{
default_value: '',
depends_on: [{ field: 'bar', value: 'foofoo' }],
display: DisplayType.TEXTBOX,
+ is_valid: true,
key: 'shownDependent1',
label: 'Shown Dependent',
options: [],
@@ -508,7 +549,9 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: true,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
+ validation_errors: [],
value: 'I should appear (one dependency)',
},
{
@@ -518,6 +561,7 @@ describe('ConnectorConfigurationLogic', () => {
{ field: 'password', value: 'fourthBar' },
],
display: DisplayType.TEXTBOX,
+ is_valid: true,
key: 'shownDependent2',
label: 'Shown Dependent 1',
options: [],
@@ -525,7 +569,9 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: true,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
+ validation_errors: [],
value: 'I should appear (multiple dependencies)',
},
],
@@ -540,6 +586,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: false,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
value: 'fafa',
},
@@ -553,6 +600,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: true,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
value: 'fourthBar',
},
@@ -566,6 +614,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: true,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: ['advanced'],
value: 'I am restricted',
},
@@ -579,6 +628,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: true,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
value: 'I should appear (one dependency)',
},
@@ -595,6 +645,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: true,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
value: 'I should appear (multiple dependencies)',
},
@@ -608,6 +659,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: true,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
value: 'I should hide (one dependency)',
},
@@ -624,6 +676,7 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: true,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
value: 'I should hide (multiple dependencies)',
},
@@ -633,6 +686,7 @@ describe('ConnectorConfigurationLogic', () => {
default_value: '',
depends_on: [],
display: DisplayType.TEXTBOX,
+ is_valid: true,
key: 'bar',
label: 'foo',
options: [],
@@ -640,13 +694,16 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: false,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
+ validation_errors: [],
value: 'fafa',
},
{
default_value: '',
depends_on: [],
display: DisplayType.TEXTBOX,
+ is_valid: true,
key: 'password',
label: 'thirdBar',
options: [],
@@ -654,13 +711,16 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: true,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
+ validation_errors: [],
value: 'fourthBar',
},
{
default_value: '',
depends_on: [{ field: 'bar', value: 'fafa' }],
display: DisplayType.TEXTBOX,
+ is_valid: true,
key: 'hiddenDependent1',
label: 'Shown Dependent 2',
options: [],
@@ -668,7 +728,9 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: true,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
+ validation_errors: [],
value: 'I should hide (one dependency)',
},
{
@@ -678,6 +740,7 @@ describe('ConnectorConfigurationLogic', () => {
{ field: 'password', value: 'fourthBar' },
],
display: DisplayType.TEXTBOX,
+ is_valid: true,
key: 'hiddenDependent2',
label: 'Shown Dependent',
options: [],
@@ -685,7 +748,9 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: true,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
+ validation_errors: [],
value: 'I should hide (multiple dependencies)',
},
],
@@ -703,6 +768,7 @@ describe('ConnectorConfigurationLogic', () => {
default_value: '',
depends_on: [],
display: DisplayType.TEXTBOX,
+ is_valid: true,
key: 'foo',
label: 'bar',
options: [],
@@ -710,7 +776,9 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: false,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
+ validation_errors: [],
value: 'barbar',
},
],
@@ -740,6 +808,7 @@ describe('ConnectorConfigurationLogic', () => {
default_value: '',
depends_on: [],
display: DisplayType.TEXTBOX,
+ is_valid: true,
key: 'foo',
label: 'bar',
options: [],
@@ -747,7 +816,9 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: false,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
+ validation_errors: [],
value: 'barbar',
},
],
@@ -762,6 +833,7 @@ describe('ConnectorConfigurationLogic', () => {
default_value: '',
depends_on: [],
display: DisplayType.TEXTBOX,
+ is_valid: true,
key: 'foo',
label: 'bar',
options: [],
@@ -769,7 +841,9 @@ describe('ConnectorConfigurationLogic', () => {
required: false,
sensitive: false,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
+ validation_errors: [],
value: 'barbar',
},
],
@@ -786,13 +860,16 @@ describe('ConnectorConfigurationLogic', () => {
default_value: '',
depends_on: [],
display: DisplayType.TEXTBOX,
+ is_valid: true,
label: 'bar',
options: [],
order: 1,
required: false,
sensitive: true,
tooltip: '',
+ type: FieldType.STRING,
ui_restrictions: [],
+ validation_errors: [],
value: 'Barbara',
},
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.ts
index 861ab90079229..065fe42d5bb0b 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.ts
@@ -7,12 +7,15 @@
import { kea, MakeLogicType } from 'kea';
+import { i18n } from '@kbn/i18n';
+
import {
ConnectorConfiguration,
ConnectorStatus,
Dependency,
DependencyLookup,
DisplayType,
+ FieldType,
SelectOption,
} from '../../../../../../common/types/connectors';
import { isNotNullish } from '../../../../../../common/utils/is_not_nullish';
@@ -59,6 +62,7 @@ export interface ConfigEntry {
default_value: string | number | boolean | null;
depends_on: Dependency[];
display: DisplayType;
+ is_valid: boolean;
key: string;
label: string;
options: SelectOption[];
@@ -66,7 +70,9 @@ export interface ConfigEntry {
required: boolean;
sensitive: boolean;
tooltip: string;
+ type: FieldType;
ui_restrictions: string[];
+ validation_errors: string[];
value: string | number | boolean | null;
}
@@ -86,7 +92,9 @@ function sortAndFilterConnectorConfiguration(config: ConnectorConfiguration): Co
.map(
(key) =>
({
+ is_valid: true,
key,
+ validation_errors: [],
...config[key],
} as ConfigEntry)
)
@@ -119,13 +127,65 @@ function sortAndFilterConnectorConfiguration(config: ConnectorConfiguration): Co
);
}
+function validateConnectorConfiguration(config: ConfigEntry[]): ConfigEntry[] {
+ return config.map((configEntry) => {
+ const label = configEntry.label;
+
+ configEntry.validation_errors = [];
+
+ if (configEntry.type === FieldType.INTEGER && !validIntInput(configEntry.value)) {
+ configEntry.validation_errors.push(
+ i18n.translate(
+ 'xpack.enterpriseSearch.content.indices.configurationConnector.config.invalidInteger',
+ {
+ defaultMessage: '{label} must be an integer.',
+ values: { label },
+ }
+ )
+ );
+ }
+
+ configEntry.is_valid = configEntry.validation_errors.length <= 0;
+
+ return configEntry;
+ });
+}
+
+function validIntInput(value: string | number | boolean | null): boolean {
+ // reject non integers (including x.0 floats), but don't validate if empty
+ return (value !== null || value !== '') &&
+ (isNaN(Number(value)) ||
+ !Number.isSafeInteger(value) ||
+ ensureStringType(value).indexOf('.') >= 0)
+ ? false
+ : true;
+}
+
+function ensureCorrectTyping(
+ type: FieldType,
+ value: string | number | boolean | null
+): string | number | boolean | null {
+ switch (type) {
+ case FieldType.INTEGER:
+ return validIntInput(value) ? ensureIntType(value) : value;
+ case FieldType.BOOLEAN:
+ return ensureBooleanType(value);
+ default:
+ return ensureStringType(value);
+ }
+}
+
export function ensureStringType(value: string | number | boolean | null): string {
- return String(value);
+ return value !== null ? String(value) : '';
}
-export function ensureNumberType(value: string | number | boolean | null): number {
- const numberValue = Number(value);
- return isNaN(numberValue) ? 0 : numberValue;
+export function ensureIntType(value: string | number | boolean | null): number | null {
+ // int is null-safe to prevent empty values from becoming zeroes
+ if (value === null || value === '') {
+ return null;
+ }
+
+ return parseInt(String(value), 10);
}
export function ensureBooleanType(value: string | number | boolean | null): boolean {
@@ -265,6 +325,7 @@ export const ConnectorConfigurationLogic = kea<
required,
sensitive,
tooltip,
+ type,
// eslint-disable-next-line @typescript-eslint/naming-convention
ui_restrictions,
value,
@@ -281,8 +342,9 @@ export const ConnectorConfigurationLogic = kea<
required,
sensitive,
tooltip,
+ type,
ui_restrictions,
- value,
+ value: ensureCorrectTyping(type, value),
},
}),
setLocalConfigState: (_, { configState }) => configState,
@@ -303,7 +365,10 @@ export const ConnectorConfigurationLogic = kea<
],
localConfigView: [
() => [selectors.localConfigState],
- (configState) => sortAndFilterConnectorConfiguration(configState),
+ (configState) => {
+ const config = sortAndFilterConnectorConfiguration(configState);
+ return validateConnectorConfiguration(config);
+ },
],
}),
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/convert_connector.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/convert_connector.tsx
new file mode 100644
index 0000000000000..450565088e842
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/convert_connector.tsx
@@ -0,0 +1,115 @@
+/*
+ * 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 { useActions, useValues } from 'kea';
+
+import {
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiIcon,
+ EuiTitle,
+ EuiSpacer,
+ EuiText,
+ EuiLink,
+ EuiButton,
+ EuiConfirmModal,
+} from '@elastic/eui';
+
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n-react';
+
+import { CANCEL_BUTTON_LABEL } from '../../../../../shared/constants';
+
+import { docLinks } from '../../../../../shared/doc_links';
+
+import { ConvertConnectorLogic } from './convert_connector_logic';
+
+export const ConvertConnector: React.FC = () => {
+ const { convertConnector, hideModal, showModal } = useActions(ConvertConnectorLogic);
+ const { isLoading, isModalVisible } = useValues(ConvertConnectorLogic);
+
+ return (
+ <>
+ {isModalVisible && (
+ hideModal()}
+ onConfirm={() => convertConnector()}
+ title={i18n.translate(
+ 'xpack.enterpriseSearch.content.engine.indices.convertInfexConfirm.title',
+ { defaultMessage: 'Sure you want to convert your connector?' }
+ )}
+ buttonColor="danger"
+ cancelButtonText={CANCEL_BUTTON_LABEL}
+ confirmButtonText={i18n.translate(
+ 'xpack.enterpriseSearch.content.engine.indices.convertIndexConfirm.text',
+ {
+ defaultMessage: 'Yes',
+ }
+ )}
+ isLoading={isLoading}
+ defaultFocusedButton="confirm"
+ maxWidth
+ >
+
+
+ {i18n.translate(
+ 'xpack.enterpriseSearch.content.engine.indices.convertIndexConfirm.description',
+ {
+ defaultMessage:
+ "Once you convert a native connector to a self-managed connector client this can't be undone.",
+ }
+ )}
+
+
+
+ )}
+
+
+
+
+
+
+
+ {i18n.translate(
+ 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.convertConnector.title',
+ {
+ defaultMessage: 'Customize your connector',
+ }
+ )}
+
+
+
+
+
+
+
+ {i18n.translate(
+ 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.convertConnector.linkTitle',
+ { defaultMessage: 'connector client' }
+ )}
+
+ ),
+ }}
+ />
+
+ showModal()}>
+ {i18n.translate(
+ 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.convertConnector.buttonTitle',
+ { defaultMessage: 'Convert connector' }
+ )}
+
+
+ >
+ );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/convert_connector_logic.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/convert_connector_logic.tsx
new file mode 100644
index 0000000000000..841f8cde106a2
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/convert_connector_logic.tsx
@@ -0,0 +1,82 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.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 { kea, MakeLogicType } from 'kea';
+
+import { Status } from '../../../../../../../common/types/api';
+import { Actions } from '../../../../../shared/api_logic/create_api_logic';
+import {
+ ConvertConnectorApiLogic,
+ ConvertConnectorApiLogicArgs,
+ ConvertConnectorApiLogicResponse,
+} from '../../../../api/connector/convert_connector_api_logic';
+import { IndexViewActions, IndexViewLogic } from '../../index_view_logic';
+
+interface ConvertConnectorValues {
+ connectorId: typeof IndexViewLogic.values.connectorId;
+ isLoading: boolean;
+ isModalVisible: boolean;
+ status: Status;
+}
+
+type ConvertConnectorActions = Pick<
+ Actions,
+ 'apiError' | 'apiSuccess' | 'makeRequest'
+> & {
+ convertConnector(): void;
+ fetchIndex(): IndexViewActions['fetchIndex'];
+ hideModal(): void;
+ showModal(): void;
+};
+
+export const ConvertConnectorLogic = kea<
+ MakeLogicType
+>({
+ actions: {
+ convertConnector: () => true,
+ deleteDomain: () => true,
+ hideModal: () => true,
+ showModal: () => true,
+ },
+ connect: {
+ actions: [
+ ConvertConnectorApiLogic,
+ ['apiError', 'apiSuccess', 'makeRequest'],
+ IndexViewLogic,
+ ['fetchIndex'],
+ ],
+ values: [ConvertConnectorApiLogic, ['status'], IndexViewLogic, ['connectorId']],
+ },
+ listeners: ({ actions, values }) => ({
+ convertConnector: () => {
+ if (values.connectorId) {
+ actions.makeRequest({ connectorId: values.connectorId });
+ }
+ },
+ }),
+ path: ['enterprise_search', 'convert_connector_modal'],
+ reducers: {
+ isModalVisible: [
+ false,
+ {
+ apiError: () => false,
+ apiSuccess: () => false,
+ hideModal: () => false,
+ showModal: () => true,
+ },
+ ],
+ },
+ selectors: ({ selectors }) => ({
+ isLoading: [() => [selectors.status], (status: Status) => status === Status.LOADING],
+ }),
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration.tsx
index c4c4af581ef5a..960bd61264ddf 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration.tsx
@@ -31,6 +31,7 @@ import { IndexViewLogic } from '../../index_view_logic';
import { ConnectorNameAndDescription } from '../connector_name_and_description/connector_name_and_description';
import { NATIVE_CONNECTORS } from '../constants';
+import { ConvertConnector } from './convert_connector';
import { NativeConnectorAdvancedConfiguration } from './native_connector_advanced_configuration';
import { NativeConnectorConfigurationConfig } from './native_connector_configuration_config';
import { ResearchConfiguration } from './research_configuration';
@@ -203,6 +204,11 @@ export const NativeConnectorConfiguration: React.FC = () => {
+
+
+
+
+
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout.test.tsx
new file mode 100644
index 0000000000000..c0d9a714ea2f0
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout.test.tsx
@@ -0,0 +1,121 @@
+/*
+ * 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 { setMockValues } from '../../../../../__mocks__/kea_logic';
+
+import React from 'react';
+
+import { shallow } from 'enzyme';
+
+import { EuiButton } from '@elastic/eui';
+
+import {
+ TextExpansionCallOut,
+ DeployModel,
+ ModelDeploymentInProgress,
+ ModelDeployed,
+ TextExpansionDismissButton,
+} from './text_expansion_callout';
+
+jest.mock('./text_expansion_callout_data', () => ({
+ useTextExpansionCallOutData: jest.fn(() => ({
+ dismiss: jest.fn(),
+ isCreateButtonDisabled: false,
+ isDismissable: false,
+ show: true,
+ })),
+}));
+
+const DEFAULT_VALUES = {
+ isCreateButtonDisabled: false,
+ isModelDownloadInProgress: false,
+ isModelDownloaded: false,
+};
+
+describe('TextExpansionCallOut', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ setMockValues(DEFAULT_VALUES);
+ });
+ it('renders panel with deployment instructions if the model is not deployed', () => {
+ const wrapper = shallow();
+ expect(wrapper.find(DeployModel).length).toBe(1);
+ });
+ it('renders panel with deployment in progress status if the model is being deployed', () => {
+ setMockValues({
+ ...DEFAULT_VALUES,
+ isModelDownloadInProgress: true,
+ });
+
+ const wrapper = shallow();
+ expect(wrapper.find(ModelDeploymentInProgress).length).toBe(1);
+ });
+ it('renders panel with deployment in progress status if the model has been deployed', () => {
+ setMockValues({
+ ...DEFAULT_VALUES,
+ isModelDownloaded: true,
+ });
+
+ const wrapper = shallow();
+ expect(wrapper.find(ModelDeployed).length).toBe(1);
+ });
+
+ describe('DeployModel', () => {
+ it('renders deploy button', () => {
+ const wrapper = shallow(
+ {}} isCreateButtonDisabled={false} isDismissable={false} />
+ );
+ expect(wrapper.find(EuiButton).length).toBe(1);
+ const button = wrapper.find(EuiButton);
+ expect(button.prop('disabled')).toBe(false);
+ });
+ it('renders disabled deploy button if it is set to disabled', () => {
+ const wrapper = shallow(
+ {}} isCreateButtonDisabled isDismissable={false} />
+ );
+ expect(wrapper.find(EuiButton).length).toBe(1);
+ const button = wrapper.find(EuiButton);
+ expect(button.prop('disabled')).toBe(true);
+ });
+ it('renders dismiss button if it is set to dismissable', () => {
+ const wrapper = shallow(
+ {}} isCreateButtonDisabled={false} isDismissable />
+ );
+ expect(wrapper.find(TextExpansionDismissButton).length).toBe(1);
+ });
+ it('does not render dismiss button if it is set to non-dismissable', () => {
+ const wrapper = shallow(
+ {}} isCreateButtonDisabled={false} isDismissable={false} />
+ );
+ expect(wrapper.find(TextExpansionDismissButton).length).toBe(0);
+ });
+ });
+
+ describe('ModelDeploymentInProgress', () => {
+ it('renders dismiss button if it is set to dismissable', () => {
+ const wrapper = shallow( {}} isDismissable />);
+ expect(wrapper.find(TextExpansionDismissButton).length).toBe(1);
+ });
+ it('does not render dismiss button if it is set to non-dismissable', () => {
+ const wrapper = shallow(
+ {}} isDismissable={false} />
+ );
+ expect(wrapper.find(TextExpansionDismissButton).length).toBe(0);
+ });
+ });
+
+ describe('ModelDeployed', () => {
+ it('renders dismiss button if it is set to dismissable', () => {
+ const wrapper = shallow( {}} isDismissable />);
+ expect(wrapper.find(TextExpansionDismissButton).length).toBe(1);
+ });
+ it('does not render dismiss button if it is set to non-dismissable', () => {
+ const wrapper = shallow( {}} isDismissable={false} />);
+ expect(wrapper.find(TextExpansionDismissButton).length).toBe(0);
+ });
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout.tsx
index a6784afecabd0..53e3e9c492a59 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout.tsx
@@ -5,15 +5,17 @@
* 2.0.
*/
-import React, { useCallback, useEffect, useMemo, useState } from 'react';
+import React from 'react';
-import { useValues } from 'kea';
+import { useActions, useValues } from 'kea';
import {
EuiBadge,
+ EuiButton,
EuiButtonIcon,
EuiFlexGroup,
EuiFlexItem,
+ EuiIcon,
EuiLink,
EuiPanel,
EuiText,
@@ -24,113 +26,217 @@ import { FormattedMessage, FormattedHTMLMessage } from '@kbn/i18n-react';
import { docLinks } from '../../../../../shared/doc_links';
-import { MLInferenceLogic } from './ml_inference_logic';
+import { useTextExpansionCallOutData } from './text_expansion_callout_data';
+import { TextExpansionCalloutLogic } from './text_expansion_callout_logic';
export interface TextExpansionCallOutState {
dismiss: () => void;
- dismissable: boolean;
+ isCreateButtonDisabled: boolean;
+ isDismissable: boolean;
show: boolean;
}
export interface TextExpansionCallOutProps {
- dismissable?: boolean;
+ isDismissable?: boolean;
}
-export const TEXT_EXPANSION_CALL_OUT_DISMISSED_KEY =
- 'enterprise-search-text-expansion-callout-dismissed';
-
-export const useTextExpansionCallOutData = ({
- dismissable = false,
-}: TextExpansionCallOutProps): TextExpansionCallOutState => {
- const { supportedMLModels } = useValues(MLInferenceLogic);
-
- const doesNotHaveTextExpansionModel = useMemo(() => {
- return !supportedMLModels.some((m) => m.inference_config?.text_expansion);
- }, [supportedMLModels]);
-
- const [show, setShow] = useState(() => {
- if (!dismissable) return true;
-
- try {
- return localStorage.getItem(TEXT_EXPANSION_CALL_OUT_DISMISSED_KEY) !== 'true';
- } catch {
- return true;
- }
- });
-
- useEffect(() => {
- try {
- localStorage.setItem(TEXT_EXPANSION_CALL_OUT_DISMISSED_KEY, JSON.stringify(!show));
- } catch {
- return;
- }
- }, [show]);
-
- const dismiss = useCallback(() => {
- setShow(false);
- }, []);
-
- return { dismiss, dismissable, show: doesNotHaveTextExpansionModel && show };
+export const TextExpansionDismissButton = ({
+ dismiss,
+}: Pick) => {
+ return (
+
+ );
};
-export const TextExpansionCallOut: React.FC = (props) => {
- const { dismiss, dismissable, show } = useTextExpansionCallOutData(props);
-
- if (!show) return null;
+export const DeployModel = ({
+ dismiss,
+ isCreateButtonDisabled,
+ isDismissable,
+}: Pick) => {
+ const { createTextExpansionModel } = useActions(TextExpansionCalloutLogic);
return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {isDismissable && (
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+ createTextExpansionModel(undefined)}
+ >
+ {i18n.translate(
+ 'xpack.enterpriseSearch.content.indices.pipelines.textExpansionCallOut.deployButton.label',
+ {
+ defaultMessage: 'Deploy',
+ }
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export const ModelDeploymentInProgress = ({
+ dismiss,
+ isDismissable,
+}: Pick) => (
+
+
+
-
-
-
+
-
-
-
+
- {dismissable && (
+ {isDismissable && (
-
+
)}
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+);
+
+export const ModelDeployed = ({
+ dismiss,
+ isDismissable,
+}: Pick) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {isDismissable && (
+
+
+
+ )}
-
-
+
+
+
+
+
+
+
+
+);
+
+export const TextExpansionCallOut: React.FC = (props) => {
+ const { dismiss, isDismissable, show } = useTextExpansionCallOutData(props);
+ const { isCreateButtonDisabled, isModelDownloadInProgress, isModelDownloaded } =
+ useValues(TextExpansionCalloutLogic);
+
+ if (!show) return null;
+
+ if (!!isModelDownloadInProgress) {
+ return ;
+ } else if (!!isModelDownloaded) {
+ return ;
+ }
+
+ return (
+
);
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout_data.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout_data.tsx
new file mode 100644
index 0000000000000..35f8f105c1e1e
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout_data.tsx
@@ -0,0 +1,55 @@
+/*
+ * 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 { useCallback, useEffect, useState } from 'react';
+
+import { useValues } from 'kea';
+
+import { TextExpansionCallOutProps, TextExpansionCallOutState } from './text_expansion_callout';
+import { TextExpansionCalloutLogic } from './text_expansion_callout_logic';
+
+export const TEXT_EXPANSION_CALL_OUT_DISMISSED_KEY =
+ 'enterprise-search-text-expansion-callout-dismissed';
+
+const isDismissed = () => localStorage.getItem(TEXT_EXPANSION_CALL_OUT_DISMISSED_KEY) === 'true';
+
+export const useTextExpansionCallOutData = ({
+ isDismissable = false,
+}: TextExpansionCallOutProps): TextExpansionCallOutState => {
+ const { isCreateButtonDisabled } = useValues(TextExpansionCalloutLogic);
+
+ const [show, setShow] = useState(() => {
+ if (!isDismissable) return true;
+
+ try {
+ return !isDismissed();
+ } catch {
+ return true;
+ }
+ });
+
+ useEffect(() => {
+ try {
+ if (!isDismissed()) {
+ localStorage.setItem(TEXT_EXPANSION_CALL_OUT_DISMISSED_KEY, JSON.stringify(!show));
+ }
+ } catch {
+ return;
+ }
+ }, [show]);
+
+ const dismiss = useCallback(() => {
+ setShow(false);
+ }, []);
+
+ return {
+ dismiss,
+ isCreateButtonDisabled,
+ isDismissable,
+ show,
+ };
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout_logic.test.ts
new file mode 100644
index 0000000000000..23afb770bead4
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout_logic.test.ts
@@ -0,0 +1,291 @@
+/*
+ * 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 { LogicMounter } from '../../../../../__mocks__/kea_logic';
+
+import { HttpResponse } from '@kbn/core/public';
+
+import { ErrorResponse, Status } from '../../../../../../../common/types/api';
+import { MlModelDeploymentState } from '../../../../../../../common/types/ml';
+import { CreateTextExpansionModelApiLogic } from '../../../../api/ml_models/text_expansion/create_text_expansion_model_api_logic';
+import { FetchTextExpansionModelApiLogic } from '../../../../api/ml_models/text_expansion/fetch_text_expansion_model_api_logic';
+
+import {
+ TextExpansionCalloutLogic,
+ TextExpansionCalloutValues,
+} from './text_expansion_callout_logic';
+
+const DEFAULT_VALUES: TextExpansionCalloutValues = {
+ createTextExpansionModelStatus: Status.IDLE,
+ createdTextExpansionModel: undefined,
+ isCreateButtonDisabled: false,
+ isModelDownloadInProgress: false,
+ isModelDownloaded: false,
+ isPollingTextExpansionModelActive: false,
+ textExpansionModel: undefined,
+ textExpansionModelPollTimeoutId: null,
+};
+
+jest.useFakeTimers();
+
+describe('TextExpansionCalloutLogic', () => {
+ const { mount } = new LogicMounter(TextExpansionCalloutLogic);
+ const { mount: mountCreateTextExpansionModelApiLogic } = new LogicMounter(
+ CreateTextExpansionModelApiLogic
+ );
+ const { mount: mountFetchTextExpansionModelApiLogic } = new LogicMounter(
+ FetchTextExpansionModelApiLogic
+ );
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mountCreateTextExpansionModelApiLogic();
+ mountFetchTextExpansionModelApiLogic();
+ mount();
+ });
+
+ it('has expected default values', () => {
+ expect(TextExpansionCalloutLogic.values).toEqual(DEFAULT_VALUES);
+ });
+
+ describe('listeners', () => {
+ describe('createTextExpansionModelPollingTimeout', () => {
+ const duration = 5000;
+ it('sets polling timeout', () => {
+ jest.spyOn(global, 'setTimeout');
+ jest.spyOn(TextExpansionCalloutLogic.actions, 'setTextExpansionModelPollingId');
+
+ TextExpansionCalloutLogic.actions.createTextExpansionModelPollingTimeout(duration);
+
+ expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), duration);
+ expect(TextExpansionCalloutLogic.actions.setTextExpansionModelPollingId).toHaveBeenCalled();
+ });
+ it('clears polling timeout if it is set', () => {
+ mount({
+ ...DEFAULT_VALUES,
+ textExpansionModelPollTimeoutId: 'timeout-id',
+ });
+
+ jest.spyOn(global, 'clearTimeout');
+
+ TextExpansionCalloutLogic.actions.createTextExpansionModelPollingTimeout(duration);
+
+ expect(clearTimeout).toHaveBeenCalledWith('timeout-id');
+ });
+ });
+
+ describe('createTextExpansionModelSuccess', () => {
+ it('sets createdTextExpansionModel', () => {
+ jest.spyOn(TextExpansionCalloutLogic.actions, 'fetchTextExpansionModel');
+ jest.spyOn(TextExpansionCalloutLogic.actions, 'startPollingTextExpansionModel');
+
+ TextExpansionCalloutLogic.actions.createTextExpansionModelSuccess({
+ deploymentState: MlModelDeploymentState.Downloading,
+ modelId: 'mock-model-id',
+ });
+
+ expect(TextExpansionCalloutLogic.actions.fetchTextExpansionModel).toHaveBeenCalled();
+ expect(TextExpansionCalloutLogic.actions.startPollingTextExpansionModel).toHaveBeenCalled();
+ });
+ });
+
+ describe('fetchTextExpansionModelSuccess', () => {
+ const data = {
+ deploymentState: MlModelDeploymentState.Downloading,
+ modelId: 'mock-model-id',
+ };
+
+ it('starts polling when the model is downloading and polling is not active', () => {
+ mount({
+ ...DEFAULT_VALUES,
+ });
+ jest.spyOn(TextExpansionCalloutLogic.actions, 'startPollingTextExpansionModel');
+
+ TextExpansionCalloutLogic.actions.fetchTextExpansionModelSuccess(data);
+
+ expect(TextExpansionCalloutLogic.actions.startPollingTextExpansionModel).toHaveBeenCalled();
+ });
+ it('sets polling timeout when the model is downloading and polling is active', () => {
+ mount({
+ ...DEFAULT_VALUES,
+ textExpansionModelPollTimeoutId: 'timeout-id',
+ });
+ jest.spyOn(TextExpansionCalloutLogic.actions, 'createTextExpansionModelPollingTimeout');
+
+ TextExpansionCalloutLogic.actions.fetchTextExpansionModelSuccess(data);
+
+ expect(
+ TextExpansionCalloutLogic.actions.createTextExpansionModelPollingTimeout
+ ).toHaveBeenCalled();
+ });
+ it('stops polling when the model is downloaded and polling is active', () => {
+ mount({
+ ...DEFAULT_VALUES,
+ textExpansionModelPollTimeoutId: 'timeout-id',
+ });
+ jest.spyOn(TextExpansionCalloutLogic.actions, 'stopPollingTextExpansionModel');
+
+ TextExpansionCalloutLogic.actions.fetchTextExpansionModelSuccess({
+ deploymentState: MlModelDeploymentState.Downloaded,
+ modelId: 'mock-model-id',
+ });
+
+ expect(TextExpansionCalloutLogic.actions.stopPollingTextExpansionModel).toHaveBeenCalled();
+ });
+ });
+
+ describe('fetchTextExpansionModelError', () => {
+ it('stops polling if it is active', () => {
+ mount({
+ ...DEFAULT_VALUES,
+ textExpansionModelPollTimeoutId: 'timeout-id',
+ });
+ jest.spyOn(TextExpansionCalloutLogic.actions, 'createTextExpansionModelPollingTimeout');
+
+ TextExpansionCalloutLogic.actions.fetchTextExpansionModelError({
+ body: {
+ error: '',
+ message: 'some error',
+ statusCode: 500,
+ },
+ } as HttpResponse);
+
+ expect(
+ TextExpansionCalloutLogic.actions.createTextExpansionModelPollingTimeout
+ ).toHaveBeenCalled();
+ });
+ });
+
+ describe('startPollingTextExpansionModel', () => {
+ it('sets polling timeout', () => {
+ jest.spyOn(TextExpansionCalloutLogic.actions, 'createTextExpansionModelPollingTimeout');
+
+ TextExpansionCalloutLogic.actions.startPollingTextExpansionModel();
+
+ expect(
+ TextExpansionCalloutLogic.actions.createTextExpansionModelPollingTimeout
+ ).toHaveBeenCalled();
+ });
+ it('clears polling timeout if it is set', () => {
+ mount({
+ ...DEFAULT_VALUES,
+ textExpansionModelPollTimeoutId: 'timeout-id',
+ });
+
+ jest.spyOn(global, 'clearTimeout');
+
+ TextExpansionCalloutLogic.actions.startPollingTextExpansionModel();
+
+ expect(clearTimeout).toHaveBeenCalledWith('timeout-id');
+ });
+ });
+
+ describe('stopPollingTextExpansionModel', () => {
+ it('clears polling timeout and poll timeout ID if it is set', () => {
+ mount({
+ ...DEFAULT_VALUES,
+ textExpansionModelPollTimeoutId: 'timeout-id',
+ });
+
+ jest.spyOn(global, 'clearTimeout');
+ jest.spyOn(TextExpansionCalloutLogic.actions, 'clearTextExpansionModelPollingId');
+
+ TextExpansionCalloutLogic.actions.stopPollingTextExpansionModel();
+
+ expect(clearTimeout).toHaveBeenCalledWith('timeout-id');
+ expect(
+ TextExpansionCalloutLogic.actions.clearTextExpansionModelPollingId
+ ).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('reducers', () => {
+ describe('textExpansionModelPollTimeoutId', () => {
+ it('gets cleared on clearTextExpansionModelPollingId', () => {
+ TextExpansionCalloutLogic.actions.clearTextExpansionModelPollingId();
+
+ expect(TextExpansionCalloutLogic.values.textExpansionModelPollTimeoutId).toBe(null);
+ });
+ it('gets set on setTextExpansionModelPollingId', () => {
+ const timeout = setTimeout(() => {}, 500);
+ TextExpansionCalloutLogic.actions.setTextExpansionModelPollingId(timeout);
+
+ expect(TextExpansionCalloutLogic.values.textExpansionModelPollTimeoutId).toEqual(timeout);
+ });
+ });
+ });
+
+ describe('selectors', () => {
+ describe('isCreateButtonDisabled', () => {
+ it('is set to false if the fetch model API is idle', () => {
+ CreateTextExpansionModelApiLogic.actions.apiReset();
+ expect(TextExpansionCalloutLogic.values.isCreateButtonDisabled).toBe(false);
+ });
+ it('is set to true if the fetch model API is not idle', () => {
+ CreateTextExpansionModelApiLogic.actions.apiSuccess({
+ deploymentState: MlModelDeploymentState.Downloading,
+ modelId: 'mock-model-id',
+ });
+ expect(TextExpansionCalloutLogic.values.isCreateButtonDisabled).toBe(true);
+ });
+ });
+
+ describe('isModelDownloadInProgress', () => {
+ it('is set to true if the model is downloading', () => {
+ FetchTextExpansionModelApiLogic.actions.apiSuccess({
+ deploymentState: MlModelDeploymentState.Downloading,
+ modelId: 'mock-model-id',
+ });
+ expect(TextExpansionCalloutLogic.values.isModelDownloadInProgress).toBe(true);
+ });
+ it('is set to false if the model is downloading', () => {
+ FetchTextExpansionModelApiLogic.actions.apiSuccess({
+ deploymentState: MlModelDeploymentState.Started,
+ modelId: 'mock-model-id',
+ });
+ expect(TextExpansionCalloutLogic.values.isModelDownloadInProgress).toBe(false);
+ });
+ });
+
+ describe('isModelDownloaded', () => {
+ it('is set to true if the model is downloaded', () => {
+ FetchTextExpansionModelApiLogic.actions.apiSuccess({
+ deploymentState: MlModelDeploymentState.Downloaded,
+ modelId: 'mock-model-id',
+ });
+ expect(TextExpansionCalloutLogic.values.isModelDownloaded).toBe(true);
+ });
+ it('is set to false if the model is not downloaded', () => {
+ FetchTextExpansionModelApiLogic.actions.apiSuccess({
+ deploymentState: MlModelDeploymentState.NotDeployed,
+ modelId: 'mock-model-id',
+ });
+ expect(TextExpansionCalloutLogic.values.isModelDownloaded).toBe(false);
+ });
+ });
+
+ describe('isPollingTextExpansionModelActive', () => {
+ it('is set to false if polling is not active', () => {
+ mount({
+ ...DEFAULT_VALUES,
+ textExpansionModelPollTimeoutId: null,
+ });
+
+ expect(TextExpansionCalloutLogic.values.isPollingTextExpansionModelActive).toBe(false);
+ });
+ it('is set to true if polling is active', () => {
+ mount({
+ ...DEFAULT_VALUES,
+ textExpansionModelPollTimeoutId: 'timeout-id',
+ });
+
+ expect(TextExpansionCalloutLogic.values.isPollingTextExpansionModelActive).toBe(true);
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout_logic.ts
new file mode 100644
index 0000000000000..140cfb265f6d8
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout_logic.ts
@@ -0,0 +1,178 @@
+/*
+ * 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 { kea, MakeLogicType } from 'kea';
+
+import { Status } from '../../../../../../../common/types/api';
+import { MlModelDeploymentState } from '../../../../../../../common/types/ml';
+import {
+ CreateTextExpansionModelApiLogic,
+ CreateTextExpansionModelApiLogicActions,
+ CreateTextExpansionModelResponse,
+} from '../../../../api/ml_models/text_expansion/create_text_expansion_model_api_logic';
+import {
+ FetchTextExpansionModelApiLogic,
+ FetchTextExpansionModelApiLogicActions,
+ FetchTextExpansionModelResponse,
+} from '../../../../api/ml_models/text_expansion/fetch_text_expansion_model_api_logic';
+
+const FETCH_TEXT_EXPANSION_MODEL_POLLING_DURATION = 5000; // 5 seconds
+const FETCH_TEXT_EXPANSION_MODEL_POLLING_DURATION_ON_FAILURE = 30000; // 30 seconds
+
+interface TextExpansionCalloutActions {
+ clearTextExpansionModelPollingId: () => void;
+ createTextExpansionModel: CreateTextExpansionModelApiLogicActions['makeRequest'];
+ createTextExpansionModelPollingTimeout: (duration: number) => { duration: number };
+ createTextExpansionModelSuccess: CreateTextExpansionModelApiLogicActions['apiSuccess'];
+ fetchTextExpansionModel: FetchTextExpansionModelApiLogicActions['makeRequest'];
+ fetchTextExpansionModelError: FetchTextExpansionModelApiLogicActions['apiError'];
+ fetchTextExpansionModelSuccess: FetchTextExpansionModelApiLogicActions['apiSuccess'];
+ setTextExpansionModelPollingId: (pollTimeoutId: ReturnType) => {
+ pollTimeoutId: ReturnType;
+ };
+ startPollingTextExpansionModel: () => void;
+ stopPollingTextExpansionModel: () => void;
+ textExpansionModel: FetchTextExpansionModelApiLogicActions['apiSuccess'];
+}
+
+export interface TextExpansionCalloutValues {
+ createTextExpansionModelStatus: Status;
+ createdTextExpansionModel: CreateTextExpansionModelResponse | undefined;
+ isCreateButtonDisabled: boolean;
+ isModelDownloadInProgress: boolean;
+ isModelDownloaded: boolean;
+ isPollingTextExpansionModelActive: boolean;
+ textExpansionModel: FetchTextExpansionModelResponse | undefined;
+ textExpansionModelPollTimeoutId: null | ReturnType;
+}
+
+export const TextExpansionCalloutLogic = kea<
+ MakeLogicType
+>({
+ actions: {
+ clearTextExpansionModelPollingId: true,
+ createTextExpansionModelPollingTimeout: (duration) => ({ duration }),
+ setTextExpansionModelPollingId: (pollTimeoutId: ReturnType) => ({
+ pollTimeoutId,
+ }),
+ startPollingTextExpansionModel: true,
+ stopPollingTextExpansionModel: true,
+ },
+ connect: {
+ actions: [
+ CreateTextExpansionModelApiLogic,
+ ['makeRequest as createTextExpansionModel', 'apiSuccess as createTextExpansionModelSuccess'],
+ FetchTextExpansionModelApiLogic,
+ [
+ 'makeRequest as fetchTextExpansionModel',
+ 'apiSuccess as fetchTextExpansionModelSuccess',
+ 'apiError as fetchTextExpansionModelError',
+ ],
+ ],
+ values: [
+ CreateTextExpansionModelApiLogic,
+ ['data as createdTextExpansionModel', 'status as createTextExpansionModelStatus'], // error as ...
+ FetchTextExpansionModelApiLogic,
+ ['data as textExpansionModel'],
+ ],
+ },
+ events: ({ actions, values }) => ({
+ afterMount: () => {
+ actions.fetchTextExpansionModel(undefined);
+ },
+ beforeUnmount: () => {
+ if (values.textExpansionModelPollTimeoutId !== null) {
+ actions.stopPollingTextExpansionModel();
+ }
+ },
+ }),
+ listeners: ({ actions, values }) => ({
+ createTextExpansionModelPollingTimeout: ({ duration }) => {
+ if (values.textExpansionModelPollTimeoutId !== null) {
+ clearTimeout(values.textExpansionModelPollTimeoutId);
+ }
+ const timeoutId = setTimeout(() => {
+ actions.fetchTextExpansionModel(undefined);
+ }, duration);
+ actions.setTextExpansionModelPollingId(timeoutId);
+ },
+ createTextExpansionModelSuccess: () => {
+ actions.fetchTextExpansionModel(undefined);
+ actions.startPollingTextExpansionModel();
+ },
+ fetchTextExpansionModelError: () => {
+ if (values.isPollingTextExpansionModelActive) {
+ actions.createTextExpansionModelPollingTimeout(
+ FETCH_TEXT_EXPANSION_MODEL_POLLING_DURATION_ON_FAILURE
+ );
+ }
+ },
+ fetchTextExpansionModelSuccess: (data) => {
+ if (data?.deploymentState === MlModelDeploymentState.Downloading) {
+ if (!values.isPollingTextExpansionModelActive) {
+ actions.startPollingTextExpansionModel();
+ } else {
+ actions.createTextExpansionModelPollingTimeout(
+ FETCH_TEXT_EXPANSION_MODEL_POLLING_DURATION
+ );
+ }
+ } else if (
+ data?.deploymentState === MlModelDeploymentState.Downloaded &&
+ values.isPollingTextExpansionModelActive
+ ) {
+ actions.stopPollingTextExpansionModel();
+ }
+ },
+ startPollingTextExpansionModel: () => {
+ if (values.textExpansionModelPollTimeoutId !== null) {
+ clearTimeout(values.textExpansionModelPollTimeoutId);
+ }
+ actions.createTextExpansionModelPollingTimeout(FETCH_TEXT_EXPANSION_MODEL_POLLING_DURATION);
+ },
+ stopPollingTextExpansionModel: () => {
+ if (values.textExpansionModelPollTimeoutId !== null) {
+ clearTimeout(values.textExpansionModelPollTimeoutId);
+ actions.clearTextExpansionModelPollingId();
+ }
+ },
+ }),
+ path: ['enterprise_search', 'content', 'text_expansion_callout_logic'],
+ reducers: {
+ textExpansionModelPollTimeoutId: [
+ null,
+ {
+ clearTextExpansionModelPollingId: () => null,
+ setTextExpansionModelPollingId: (_, { pollTimeoutId }) => pollTimeoutId,
+ },
+ ],
+ },
+ selectors: ({ selectors }) => ({
+ isCreateButtonDisabled: [
+ () => [selectors.createTextExpansionModelStatus],
+ (status: Status) => status !== Status.IDLE,
+ ],
+ isModelDownloadInProgress: [
+ () => [selectors.textExpansionModel],
+ (data: FetchTextExpansionModelResponse) =>
+ data?.deploymentState === MlModelDeploymentState.Downloading,
+ ],
+ isModelDownloaded: [
+ () => [selectors.textExpansionModel],
+ (data: FetchTextExpansionModelResponse) =>
+ data?.deploymentState === MlModelDeploymentState.Downloaded ||
+ // TODO: add button for starting model, then remove these states
+ data?.deploymentState === MlModelDeploymentState.Starting ||
+ data?.deploymentState === MlModelDeploymentState.Started ||
+ data?.deploymentState === MlModelDeploymentState.FullyAllocated,
+ ],
+ isPollingTextExpansionModelActive: [
+ () => [selectors.textExpansionModelPollTimeoutId],
+ (pollingTimeoutId: TextExpansionCalloutValues['textExpansionModelPollTimeoutId']) =>
+ pollingTimeoutId !== null,
+ ],
+ }),
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference_pipeline_processors_card.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference_pipeline_processors_card.tsx
index 4a6a6235982fd..6d3ad46a985c0 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference_pipeline_processors_card.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference_pipeline_processors_card.tsx
@@ -12,6 +12,8 @@ import { useActions, useValues } from 'kea';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { InferencePipeline } from '../../../../../../common/types/pipelines';
+import { KibanaLogic } from '../../../../shared/kibana/kibana_logic';
+import { LicensingLogic } from '../../../../shared/licensing';
import { IndexNameLogic } from '../index_name_logic';
import { InferencePipelineCard } from './inference_pipeline_card';
@@ -20,6 +22,8 @@ import { TextExpansionCallOut } from './ml_inference/text_expansion_callout';
import { PipelinesLogic } from './pipelines_logic';
export const MlInferencePipelineProcessorsCard: React.FC = () => {
+ const { capabilities, isCloud } = useValues(KibanaLogic);
+ const { hasPlatinumLicense } = useValues(LicensingLogic);
const { indexName } = useValues(IndexNameLogic);
const { mlInferencePipelineProcessors: inferencePipelines } = useValues(PipelinesLogic);
const { fetchMlInferenceProcessors, openAddMlInferencePipelineModal } =
@@ -28,9 +32,12 @@ export const MlInferencePipelineProcessorsCard: React.FC = () => {
fetchMlInferenceProcessors({ indexName });
}, [indexName]);
+ const hasMLPermissions = capabilities?.ml?.canGetTrainedModels ?? false;
+ const isGated = !isCloud && !hasPlatinumLicense;
+
return (
-
+ {hasMLPermissions && !isGated && }
openAddMlInferencePipelineModal()} />
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts b/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts
index cb6663eebf6ab..b690f2eb6b315 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts
@@ -36,6 +36,7 @@ class DocLinks {
public appSearchWebCrawlerReference: string;
public behavioralAnalytics: string;
public behavioralAnalyticsEvents: string;
+ public buildConnector: string;
public bulkApi: string;
public clientsGoIndex: string;
public clientsGuide: string;
@@ -164,6 +165,7 @@ class DocLinks {
this.appSearchWebCrawlerReference = '';
this.behavioralAnalytics = '';
this.behavioralAnalyticsEvents = '';
+ this.buildConnector = '';
this.bulkApi = '';
this.clientsGoIndex = '';
this.clientsGuide = '';
@@ -294,6 +296,7 @@ class DocLinks {
this.appSearchWebCrawlerReference = docLinks.links.appSearch.webCrawlerReference;
this.behavioralAnalytics = docLinks.links.enterpriseSearch.behavioralAnalytics;
this.behavioralAnalyticsEvents = docLinks.links.enterpriseSearch.behavioralAnalyticsEvents;
+ this.buildConnector = docLinks.links.enterpriseSearch.buildConnector;
this.bulkApi = docLinks.links.enterpriseSearch.bulkApi;
this.clientsGoIndex = docLinks.links.clients.goIndex;
this.clientsGuide = docLinks.links.clients.guide;
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx
index dcb343a2beec4..cab7c58d5f099 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx
@@ -287,7 +287,7 @@ describe('useEnterpriseSearchEngineNav', () => {
{
href: `/app/enterprise_search/content/engines/${engineName}/preview`,
id: 'enterpriseSearchEnginePreview',
- name: 'Preview',
+ name: 'Search Preview',
},
{
href: `/app/enterprise_search/content/engines/${engineName}/content`,
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx
index 7fbc354ff725f..86eb362c1e2ae 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx
@@ -196,7 +196,7 @@ export const useEnterpriseSearchEngineNav = (engineName?: string, isEmptyState?:
{
id: 'enterpriseSearchEnginePreview',
name: i18n.translate('xpack.enterpriseSearch.nav.engine.previewTitle', {
- defaultMessage: 'Preview',
+ defaultMessage: 'Search Preview',
}),
...generateNavLink({
shouldNotCreateHref: true,
diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/put_update_native.ts b/x-pack/plugins/enterprise_search/server/lib/connectors/put_update_native.ts
new file mode 100644
index 0000000000000..6b3039974f8a4
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/server/lib/connectors/put_update_native.ts
@@ -0,0 +1,27 @@
+/*
+ * 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 { IScopedClusterClient } from '@kbn/core-elasticsearch-server';
+
+import { CONNECTORS_INDEX } from '../..';
+import { Connector } from '../../../common/types/connectors';
+
+export const putUpdateNative = async (
+ client: IScopedClusterClient,
+ connectorId: string,
+ isNative: boolean
+) => {
+ const result = await client.asCurrentUser.update({
+ doc: {
+ is_native: isNative,
+ },
+ id: connectorId,
+ index: CONNECTORS_INDEX,
+ });
+
+ return result;
+};
diff --git a/x-pack/plugins/enterprise_search/server/lib/ml/get_ml_model_deployment_status.test.ts b/x-pack/plugins/enterprise_search/server/lib/ml/get_ml_model_deployment_status.test.ts
new file mode 100644
index 0000000000000..e2944ad7a0e91
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/server/lib/ml/get_ml_model_deployment_status.test.ts
@@ -0,0 +1,272 @@
+/*
+ * 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 { MlTrainedModels } from '@kbn/ml-plugin/server';
+
+import { MlModelDeploymentState } from '../../../common/types/ml';
+import { ElasticsearchResponseError } from '../../utils/identify_exceptions';
+
+import { getMlModelDeploymentStatus } from './get_ml_model_deployment_status';
+
+describe('getMlModelDeploymentStatus', () => {
+ const mockTrainedModelsProvider = {
+ getTrainedModels: jest.fn(),
+ getTrainedModelsStats: jest.fn(),
+ };
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should error when there is no trained model provider', () => {
+ expect(() => getMlModelDeploymentStatus('mockModelName', undefined)).rejects.toThrowError(
+ 'Machine Learning is not enabled'
+ );
+ });
+
+ it('should return not deployed status if no model is found', async () => {
+ const mockGetReturn = {
+ count: 0,
+ trained_model_configs: [],
+ };
+
+ mockTrainedModelsProvider.getTrainedModels.mockImplementation(() =>
+ Promise.resolve(mockGetReturn)
+ );
+
+ const deployedStatus = await getMlModelDeploymentStatus(
+ 'mockModelName',
+ mockTrainedModelsProvider as unknown as MlTrainedModels
+ );
+
+ expect(deployedStatus.deploymentState).toEqual(MlModelDeploymentState.NotDeployed);
+ expect(deployedStatus.modelId).toEqual('mockModelName');
+ });
+
+ it('should return not deployed status if no model is found when getTrainedModels has a 404', async () => {
+ const mockErrorRejection: ElasticsearchResponseError = {
+ meta: {
+ body: {
+ error: {
+ type: 'resource_not_found_exception',
+ },
+ },
+ statusCode: 404,
+ },
+ name: 'ResponseError',
+ };
+
+ mockTrainedModelsProvider.getTrainedModels.mockImplementation(() =>
+ Promise.reject(mockErrorRejection)
+ );
+
+ const deployedStatus = await getMlModelDeploymentStatus(
+ 'mockModelName',
+ mockTrainedModelsProvider as unknown as MlTrainedModels
+ );
+
+ expect(deployedStatus.deploymentState).toEqual(MlModelDeploymentState.NotDeployed);
+ expect(deployedStatus.modelId).toEqual('mockModelName');
+ });
+
+ it('should return downloading if the model is downloading', async () => {
+ const mockGetReturn = {
+ count: 1,
+ trained_model_configs: [
+ {
+ fully_defined: false,
+ model_id: 'mockModelName',
+ },
+ ],
+ };
+
+ mockTrainedModelsProvider.getTrainedModels.mockImplementation(() =>
+ Promise.resolve(mockGetReturn)
+ );
+
+ const deployedStatus = await getMlModelDeploymentStatus(
+ 'mockModelName',
+ mockTrainedModelsProvider as unknown as MlTrainedModels
+ );
+
+ expect(deployedStatus.deploymentState).toEqual(MlModelDeploymentState.Downloading);
+ expect(deployedStatus.modelId).toEqual('mockModelName');
+ });
+
+ it('should return downloaded if the model is downloaded but not deployed', async () => {
+ const mockGetReturn = {
+ count: 1,
+ trained_model_configs: [
+ {
+ fully_defined: true,
+ model_id: 'mockModelName',
+ },
+ ],
+ };
+
+ const mockStatsReturn = {
+ count: 0,
+ trained_model_stats: [],
+ };
+
+ mockTrainedModelsProvider.getTrainedModels.mockImplementation(() =>
+ Promise.resolve(mockGetReturn)
+ );
+ mockTrainedModelsProvider.getTrainedModelsStats.mockImplementation(() =>
+ Promise.resolve(mockStatsReturn)
+ );
+
+ const deployedStatus = await getMlModelDeploymentStatus(
+ 'mockModelName',
+ mockTrainedModelsProvider as unknown as MlTrainedModels
+ );
+
+ expect(deployedStatus.deploymentState).toEqual(MlModelDeploymentState.Downloaded);
+ expect(deployedStatus.modelId).toEqual('mockModelName');
+ });
+
+ it('should return starting if the model is starting deployment', async () => {
+ const mockGetReturn = {
+ count: 1,
+ trained_model_configs: [
+ {
+ fully_defined: true,
+ model_id: 'mockModelName',
+ },
+ ],
+ };
+
+ const mockStatsReturn = {
+ count: 1,
+ trained_model_stats: [
+ {
+ deployment_stats: {
+ allocation_status: {
+ allocation_count: 0,
+ state: 'starting',
+ target_allocation_count: 3,
+ },
+ start_time: 123456,
+ },
+ model_id: 'mockModelName',
+ },
+ ],
+ };
+
+ mockTrainedModelsProvider.getTrainedModels.mockImplementation(() =>
+ Promise.resolve(mockGetReturn)
+ );
+ mockTrainedModelsProvider.getTrainedModelsStats.mockImplementation(() =>
+ Promise.resolve(mockStatsReturn)
+ );
+
+ const deployedStatus = await getMlModelDeploymentStatus(
+ 'mockModelName',
+ mockTrainedModelsProvider as unknown as MlTrainedModels
+ );
+
+ expect(deployedStatus.deploymentState).toEqual(MlModelDeploymentState.Starting);
+ expect(deployedStatus.modelId).toEqual('mockModelName');
+ expect(deployedStatus.nodeAllocationCount).toEqual(0);
+ expect(deployedStatus.startTime).toEqual(123456);
+ expect(deployedStatus.targetAllocationCount).toEqual(3);
+ });
+
+ it('should return started if the model has been started', async () => {
+ const mockGetReturn = {
+ count: 1,
+ trained_model_configs: [
+ {
+ fully_defined: true,
+ model_id: 'mockModelName',
+ },
+ ],
+ };
+
+ const mockStatsReturn = {
+ count: 1,
+ trained_model_stats: [
+ {
+ deployment_stats: {
+ allocation_status: {
+ allocation_count: 1,
+ state: 'started',
+ target_allocation_count: 3,
+ },
+ start_time: 123456,
+ },
+ model_id: 'mockModelName',
+ },
+ ],
+ };
+
+ mockTrainedModelsProvider.getTrainedModels.mockImplementation(() =>
+ Promise.resolve(mockGetReturn)
+ );
+ mockTrainedModelsProvider.getTrainedModelsStats.mockImplementation(() =>
+ Promise.resolve(mockStatsReturn)
+ );
+
+ const deployedStatus = await getMlModelDeploymentStatus(
+ 'mockModelName',
+ mockTrainedModelsProvider as unknown as MlTrainedModels
+ );
+
+ expect(deployedStatus.deploymentState).toEqual(MlModelDeploymentState.Started);
+ expect(deployedStatus.modelId).toEqual('mockModelName');
+ expect(deployedStatus.nodeAllocationCount).toEqual(1);
+ expect(deployedStatus.startTime).toEqual(123456);
+ expect(deployedStatus.targetAllocationCount).toEqual(3);
+ });
+
+ it('should return fully allocated if the model is fully allocated', async () => {
+ const mockGetReturn = {
+ count: 1,
+ trained_model_configs: [
+ {
+ fully_defined: true,
+ model_id: 'mockModelName',
+ },
+ ],
+ };
+
+ const mockStatsReturn = {
+ count: 1,
+ trained_model_stats: [
+ {
+ deployment_stats: {
+ allocation_status: {
+ allocation_count: 3,
+ state: 'fully_allocated',
+ target_allocation_count: 3,
+ },
+ start_time: 123456,
+ },
+ model_id: 'mockModelName',
+ },
+ ],
+ };
+
+ mockTrainedModelsProvider.getTrainedModels.mockImplementation(() =>
+ Promise.resolve(mockGetReturn)
+ );
+ mockTrainedModelsProvider.getTrainedModelsStats.mockImplementation(() =>
+ Promise.resolve(mockStatsReturn)
+ );
+
+ const deployedStatus = await getMlModelDeploymentStatus(
+ 'mockModelName',
+ mockTrainedModelsProvider as unknown as MlTrainedModels
+ );
+
+ expect(deployedStatus.deploymentState).toEqual(MlModelDeploymentState.FullyAllocated);
+ expect(deployedStatus.modelId).toEqual('mockModelName');
+ expect(deployedStatus.nodeAllocationCount).toEqual(3);
+ expect(deployedStatus.startTime).toEqual(123456);
+ expect(deployedStatus.targetAllocationCount).toEqual(3);
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/server/lib/ml/get_ml_model_deployment_status.ts b/x-pack/plugins/enterprise_search/server/lib/ml/get_ml_model_deployment_status.ts
new file mode 100644
index 0000000000000..f44aa1b649bba
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/server/lib/ml/get_ml_model_deployment_status.ts
@@ -0,0 +1,119 @@
+/*
+ * 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 {
+ MlGetTrainedModelsStatsRequest,
+ MlGetTrainedModelsRequest,
+} from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
+import { MlTrainedModels } from '@kbn/ml-plugin/server';
+
+import {
+ MlModelDeploymentStatus,
+ MlModelDeploymentState,
+ MlTrainedModelConfigWithDefined,
+} from '../../../common/types/ml';
+
+import { isNotFoundExceptionError } from './ml_model_deployment_common';
+
+export const getMlModelDeploymentStatus = async (
+ modelName: string,
+ trainedModelsProvider: MlTrainedModels | undefined
+): Promise => {
+ if (!trainedModelsProvider) {
+ throw new Error('Machine Learning is not enabled');
+ }
+
+ // TODO: the ts-expect-error below should be removed once the correct typings are
+ // available in Kibana
+ const modelDetailsRequest: MlGetTrainedModelsRequest = {
+ // @ts-expect-error @elastic-elasticsearch getTrainedModels types incorrect
+ include: 'definition_status',
+ model_id: modelName,
+ };
+
+ // get the model details to see if we're downloaded...
+ try {
+ const modelDetailsResponse = await trainedModelsProvider.getTrainedModels(modelDetailsRequest);
+ if (!modelDetailsResponse || modelDetailsResponse.count === 0) {
+ // no model? return no status
+ return getDefaultStatusReturn(MlModelDeploymentState.NotDeployed, modelName);
+ }
+
+ // TODO - we can remove this cast to the extension once the new types are available
+ // in kibana that includes the fully_defined field
+ const firstTrainedModelConfig = modelDetailsResponse.trained_model_configs
+ ? (modelDetailsResponse.trained_model_configs[0] as MlTrainedModelConfigWithDefined)
+ : (undefined as unknown as MlTrainedModelConfigWithDefined);
+
+ // are we downloaded?
+ if (!firstTrainedModelConfig || !firstTrainedModelConfig.fully_defined) {
+ // we're still downloading...
+ return getDefaultStatusReturn(MlModelDeploymentState.Downloading, modelName);
+ }
+ } catch (error) {
+ if (!isNotFoundExceptionError(error)) {
+ throw error;
+ }
+ // not found? return a default
+ return getDefaultStatusReturn(MlModelDeploymentState.NotDeployed, modelName);
+ }
+
+ const modelRequest: MlGetTrainedModelsStatsRequest = {
+ model_id: modelName,
+ };
+
+ const modelStatsResponse = await trainedModelsProvider.getTrainedModelsStats(modelRequest);
+ if (
+ !modelStatsResponse.trained_model_stats ||
+ modelStatsResponse.trained_model_stats.length < 1 ||
+ modelStatsResponse.trained_model_stats[0]?.deployment_stats === undefined
+ ) {
+ // if we're here - we're downloaded, but not deployed if we can't find the stats
+ return getDefaultStatusReturn(MlModelDeploymentState.Downloaded, modelName);
+ }
+
+ const modelDeployment = modelStatsResponse.trained_model_stats[0].deployment_stats;
+
+ return {
+ deploymentState: getMlModelDeploymentStateForStatus(modelDeployment?.allocation_status.state),
+ modelId: modelName,
+ nodeAllocationCount: modelDeployment?.allocation_status.allocation_count || 0,
+ startTime: modelDeployment?.start_time || 0,
+ targetAllocationCount: modelDeployment?.allocation_status.target_allocation_count || 0,
+ };
+};
+
+function getDefaultStatusReturn(
+ status: MlModelDeploymentState,
+ modelName: string
+): MlModelDeploymentStatus {
+ return {
+ deploymentState: status,
+ modelId: modelName,
+ nodeAllocationCount: 0,
+ startTime: 0,
+ targetAllocationCount: 0,
+ };
+}
+
+function getMlModelDeploymentStateForStatus(state?: string): MlModelDeploymentState {
+ if (!state) {
+ return MlModelDeploymentState.NotDeployed;
+ }
+
+ switch (state) {
+ case 'starting':
+ return MlModelDeploymentState.Starting;
+ case 'started':
+ return MlModelDeploymentState.Started;
+ case 'fully_allocated':
+ return MlModelDeploymentState.FullyAllocated;
+ }
+
+ // unknown state? return default
+ return MlModelDeploymentState.NotDeployed;
+}
diff --git a/x-pack/plugins/enterprise_search/server/lib/ml/ml_model_deployment_common.ts b/x-pack/plugins/enterprise_search/server/lib/ml/ml_model_deployment_common.ts
new file mode 100644
index 0000000000000..9465a94301443
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/server/lib/ml/ml_model_deployment_common.ts
@@ -0,0 +1,40 @@
+/*
+ * 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 {
+ ElasticsearchResponseError,
+ isNotFoundException,
+ isResourceNotFoundException,
+} from '../../utils/identify_exceptions';
+
+export const acceptableModelNames = ['.elser_model_1', '.elser_model_1_SNAPSHOT'];
+
+export function isNotFoundExceptionError(error: unknown): boolean {
+ return (
+ isResourceNotFoundException(error as ElasticsearchResponseError) ||
+ isNotFoundException(error as ElasticsearchResponseError) ||
+ // @ts-expect-error error types incorrect
+ error?.statusCode === 404
+ );
+}
+
+export function throwIfNotAcceptableModelName(modelName: string) {
+ if (!acceptableModelNames.includes(modelName)) {
+ const notFoundError: ElasticsearchResponseError = {
+ meta: {
+ body: {
+ error: {
+ type: 'resource_not_found_exception',
+ },
+ },
+ statusCode: 404,
+ },
+ name: 'ResponseError',
+ };
+ throw notFoundError;
+ }
+}
diff --git a/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_deployment.test.ts b/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_deployment.test.ts
new file mode 100644
index 0000000000000..ae11a89ed5ac0
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_deployment.test.ts
@@ -0,0 +1,117 @@
+/*
+ * 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 { MlTrainedModels } from '@kbn/ml-plugin/server';
+
+import { MlModelDeploymentState } from '../../../common/types/ml';
+
+import { ElasticsearchResponseError } from '../../utils/identify_exceptions';
+
+import * as mockGetStatus from './get_ml_model_deployment_status';
+import { startMlModelDeployment } from './start_ml_model_deployment';
+
+describe('startMlModelDeployment', () => {
+ const productionModelName = '.elser_model_1';
+ const snapshotModelName = '.elser_model_1_SNAPSHOT';
+ const mockTrainedModelsProvider = {
+ getTrainedModels: jest.fn(),
+ getTrainedModelsStats: jest.fn(),
+ startTrainedModelDeployment: jest.fn(),
+ };
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should error when there is no trained model provider', () => {
+ expect(() => startMlModelDeployment(productionModelName, undefined)).rejects.toThrowError(
+ 'Machine Learning is not enabled'
+ );
+ });
+
+ it('should return not found if we are using an unknown model name', async () => {
+ try {
+ await startMlModelDeployment(
+ 'unknownModelName',
+ mockTrainedModelsProvider as unknown as MlTrainedModels
+ );
+ } catch (e) {
+ const asResponseError = e as unknown as ElasticsearchResponseError;
+ expect(asResponseError.meta?.statusCode).toEqual(404);
+ expect(asResponseError.name).toEqual('ResponseError');
+ }
+ });
+
+ it('should return the deployment state if not "downloaded"', async () => {
+ jest.spyOn(mockGetStatus, 'getMlModelDeploymentStatus').mockReturnValueOnce(
+ Promise.resolve({
+ deploymentState: MlModelDeploymentState.Starting,
+ modelId: productionModelName,
+ nodeAllocationCount: 0,
+ startTime: 123456,
+ targetAllocationCount: 3,
+ })
+ );
+
+ const response = await startMlModelDeployment(
+ productionModelName,
+ mockTrainedModelsProvider as unknown as MlTrainedModels
+ );
+
+ expect(response.deploymentState).toEqual(MlModelDeploymentState.Starting);
+ });
+
+ it('should return the deployment state if not "downloaded" for snapshot model', async () => {
+ jest.spyOn(mockGetStatus, 'getMlModelDeploymentStatus').mockReturnValueOnce(
+ Promise.resolve({
+ deploymentState: MlModelDeploymentState.Starting,
+ modelId: snapshotModelName,
+ nodeAllocationCount: 0,
+ startTime: 123456,
+ targetAllocationCount: 3,
+ })
+ );
+
+ const response = await startMlModelDeployment(
+ snapshotModelName,
+ mockTrainedModelsProvider as unknown as MlTrainedModels
+ );
+
+ expect(response.deploymentState).toEqual(MlModelDeploymentState.Starting);
+ });
+
+ it('should deploy model if it is downloaded', async () => {
+ jest
+ .spyOn(mockGetStatus, 'getMlModelDeploymentStatus')
+ .mockReturnValueOnce(
+ Promise.resolve({
+ deploymentState: MlModelDeploymentState.Downloaded,
+ modelId: productionModelName,
+ nodeAllocationCount: 0,
+ startTime: 123456,
+ targetAllocationCount: 3,
+ })
+ )
+ .mockReturnValueOnce(
+ Promise.resolve({
+ deploymentState: MlModelDeploymentState.Starting,
+ modelId: productionModelName,
+ nodeAllocationCount: 0,
+ startTime: 123456,
+ targetAllocationCount: 3,
+ })
+ );
+ mockTrainedModelsProvider.startTrainedModelDeployment.mockImplementation(async () => {});
+
+ const response = await startMlModelDeployment(
+ productionModelName,
+ mockTrainedModelsProvider as unknown as MlTrainedModels
+ );
+ expect(response.deploymentState).toEqual(MlModelDeploymentState.Starting);
+ expect(mockTrainedModelsProvider.startTrainedModelDeployment).toBeCalledTimes(1);
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_deployment.ts b/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_deployment.ts
new file mode 100644
index 0000000000000..75bac93c2d200
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_deployment.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 { MlStartTrainedModelDeploymentRequest } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
+
+import { MlTrainedModels } from '@kbn/ml-plugin/server';
+
+import { MlModelDeploymentStatus, MlModelDeploymentState } from '../../../common/types/ml';
+
+import { getMlModelDeploymentStatus } from './get_ml_model_deployment_status';
+import {
+ isNotFoundExceptionError,
+ throwIfNotAcceptableModelName,
+} from './ml_model_deployment_common';
+
+export const startMlModelDeployment = async (
+ modelName: string,
+ trainedModelsProvider: MlTrainedModels | undefined
+): Promise => {
+ if (!trainedModelsProvider) {
+ throw new Error('Machine Learning is not enabled');
+ }
+
+ // before anything else, check our model name
+ // to ensure we only allow those names we want
+ throwIfNotAcceptableModelName(modelName);
+
+ try {
+ // try and get the deployment status of the model first
+ // and see if it's already deployed or deploying...
+ const deploymentStatus = await getMlModelDeploymentStatus(modelName, trainedModelsProvider);
+ const deploymentState = deploymentStatus?.deploymentState || MlModelDeploymentState.NotDeployed;
+
+ // if we're not just "downloaded", return the current status
+ if (deploymentState !== MlModelDeploymentState.Downloaded) {
+ return deploymentStatus;
+ }
+ } catch (error) {
+ // don't rethrow the not found here - if it's not found there's
+ // a good chance it's not started downloading yet
+ if (!isNotFoundExceptionError(error)) {
+ throw error;
+ }
+ }
+
+ // we're downloaded already, but not deployed yet - let's deploy it
+ const startRequest: MlStartTrainedModelDeploymentRequest = {
+ model_id: modelName,
+ wait_for: 'started',
+ };
+
+ await trainedModelsProvider.startTrainedModelDeployment(startRequest);
+ return await getMlModelDeploymentStatus(modelName, trainedModelsProvider);
+};
diff --git a/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_download.test.ts b/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_download.test.ts
new file mode 100644
index 0000000000000..3c4cfa2f66ee9
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_download.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 { MlTrainedModels } from '@kbn/ml-plugin/server';
+
+import { MlModelDeploymentState } from '../../../common/types/ml';
+
+import { ElasticsearchResponseError } from '../../utils/identify_exceptions';
+
+import * as mockGetStatus from './get_ml_model_deployment_status';
+import { startMlModelDownload } from './start_ml_model_download';
+
+describe('startMlModelDownload', () => {
+ const knownModelName = '.elser_model_1_SNAPSHOT';
+ const mockTrainedModelsProvider = {
+ getTrainedModels: jest.fn(),
+ getTrainedModelsStats: jest.fn(),
+ putTrainedModel: jest.fn(),
+ };
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should error when there is no trained model provider', () => {
+ expect(() => startMlModelDownload(knownModelName, undefined)).rejects.toThrowError(
+ 'Machine Learning is not enabled'
+ );
+ });
+
+ it('should return not found if we are using an unknown model name', async () => {
+ try {
+ await startMlModelDownload(
+ 'unknownModelName',
+ mockTrainedModelsProvider as unknown as MlTrainedModels
+ );
+ } catch (e) {
+ const asResponseError = e as unknown as ElasticsearchResponseError;
+ expect(asResponseError.meta?.statusCode).toEqual(404);
+ expect(asResponseError.name).toEqual('ResponseError');
+ }
+ });
+
+ it('should return the deployment state if already deployed or downloading', async () => {
+ jest.spyOn(mockGetStatus, 'getMlModelDeploymentStatus').mockReturnValueOnce(
+ Promise.resolve({
+ deploymentState: MlModelDeploymentState.Starting,
+ modelId: knownModelName,
+ nodeAllocationCount: 0,
+ startTime: 123456,
+ targetAllocationCount: 3,
+ })
+ );
+
+ const response = await startMlModelDownload(
+ knownModelName,
+ mockTrainedModelsProvider as unknown as MlTrainedModels
+ );
+
+ expect(response.deploymentState).toEqual(MlModelDeploymentState.Starting);
+ });
+
+ it('should start a download and sync if not downloaded yet', async () => {
+ jest
+ .spyOn(mockGetStatus, 'getMlModelDeploymentStatus')
+ .mockReturnValueOnce(
+ Promise.resolve({
+ deploymentState: MlModelDeploymentState.NotDeployed,
+ modelId: knownModelName,
+ nodeAllocationCount: 0,
+ startTime: 123456,
+ targetAllocationCount: 3,
+ })
+ )
+ .mockReturnValueOnce(
+ Promise.resolve({
+ deploymentState: MlModelDeploymentState.Downloading,
+ modelId: knownModelName,
+ nodeAllocationCount: 0,
+ startTime: 123456,
+ targetAllocationCount: 3,
+ })
+ );
+
+ mockTrainedModelsProvider.putTrainedModel.mockImplementation(async () => {});
+
+ const response = await startMlModelDownload(
+ knownModelName,
+ mockTrainedModelsProvider as unknown as MlTrainedModels
+ );
+ expect(response.deploymentState).toEqual(MlModelDeploymentState.Downloading);
+ expect(mockTrainedModelsProvider.putTrainedModel).toBeCalledTimes(1);
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_download.ts b/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_download.ts
new file mode 100644
index 0000000000000..c5abfcf1f1035
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_download.ts
@@ -0,0 +1,67 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { MlPutTrainedModelRequest } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
+import { MlTrainedModels } from '@kbn/ml-plugin/server';
+
+import { MlModelDeploymentState, MlModelDeploymentStatus } from '../../../common/types/ml';
+
+import { getMlModelDeploymentStatus } from './get_ml_model_deployment_status';
+import {
+ isNotFoundExceptionError,
+ throwIfNotAcceptableModelName,
+} from './ml_model_deployment_common';
+
+export const startMlModelDownload = async (
+ modelName: string,
+ trainedModelsProvider: MlTrainedModels | undefined
+): Promise => {
+ if (!trainedModelsProvider) {
+ throw new Error('Machine Learning is not enabled');
+ }
+
+ // before anything else, check our model name
+ // to ensure we only allow those names we want
+ throwIfNotAcceptableModelName(modelName);
+
+ try {
+ // try and get the deployment status of the model first
+ // and see if it's already deployed or deploying...
+ const deploymentStatus = await getMlModelDeploymentStatus(modelName, trainedModelsProvider);
+ const deploymentState = deploymentStatus?.deploymentState || MlModelDeploymentState.NotDeployed;
+
+ // if we're downloading or already started / starting / done
+ // return the status
+ if (deploymentState !== MlModelDeploymentState.NotDeployed) {
+ return deploymentStatus;
+ }
+ } catch (error) {
+ // don't rethrow the not found here -
+ // if it's not found there's a good chance it's not started
+ // downloading yet
+ if (!isNotFoundExceptionError(error)) {
+ throw error;
+ }
+ }
+
+ // we're not downloaded yet - let's initiate that...
+ const putRequest: MlPutTrainedModelRequest = {
+ // @ts-expect-error @elastic-elasticsearch inference_config can be optional
+ body: {
+ input: {
+ field_names: ['text_field'],
+ },
+ },
+ model_id: modelName,
+ };
+
+ // this will also sync our saved objects for us
+ await trainedModelsProvider.putTrainedModel(putRequest);
+
+ // and return our status
+ return await getMlModelDeploymentStatus(modelName, trainedModelsProvider);
+};
diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts
index 32e7cb79a2597..f0c0df50f7155 100644
--- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts
@@ -22,6 +22,7 @@ import { fetchSyncJobsByConnectorId } from '../../lib/connectors/fetch_sync_jobs
import { cancelSyncs } from '../../lib/connectors/post_cancel_syncs';
import { updateFiltering } from '../../lib/connectors/put_update_filtering';
import { updateFilteringDraft } from '../../lib/connectors/put_update_filtering_draft';
+import { putUpdateNative } from '../../lib/connectors/put_update_native';
import { startConnectorSync } from '../../lib/connectors/start_sync';
import { updateConnectorConfiguration } from '../../lib/connectors/update_connector_configuration';
import { updateConnectorNameAndDescription } from '../../lib/connectors/update_connector_name_and_description';
@@ -383,4 +384,24 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) {
return result ? response.ok({ body: result }) : response.conflict();
})
);
+ router.put(
+ {
+ path: '/internal/enterprise_search/connectors/{connectorId}/native',
+ validate: {
+ body: schema.object({
+ is_native: schema.boolean(),
+ }),
+ params: schema.object({
+ connectorId: schema.string(),
+ }),
+ },
+ },
+ elasticsearchErrorHandler(log, async (context, request, response) => {
+ const { client } = (await context.core).elasticsearch;
+ const connectorId = decodeURIComponent(request.params.connectorId);
+ const { is_native } = request.body;
+ const result = await putUpdateNative(client, connectorId, is_native);
+ return result ? response.ok({ body: result }) : response.conflict();
+ })
+ );
}
diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.test.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.test.ts
index 92977df6021d6..a2ea06d622152 100644
--- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.test.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.test.ts
@@ -56,7 +56,23 @@ jest.mock('../../lib/indices/pipelines/ml_inference/get_ml_inference_errors', ()
jest.mock('../../lib/pipelines/ml_inference/get_ml_inference_pipelines', () => ({
getMlInferencePipelines: jest.fn(),
}));
+jest.mock('../../lib/ml/get_ml_model_deployment_status', () => ({
+ getMlModelDeploymentStatus: jest.fn(),
+}));
+jest.mock('../../lib/ml/start_ml_model_deployment', () => ({
+ startMlModelDeployment: jest.fn(),
+}));
+jest.mock('../../lib/ml/start_ml_model_download', () => ({
+ startMlModelDownload: jest.fn(),
+}));
+jest.mock('@kbn/ml-plugin/server/saved_objects/service', () => ({
+ mlSavedObjectServiceFactory: jest.fn(),
+}));
+jest.mock('@kbn/ml-plugin/server/lib/ml_client/ml_client', () => ({
+ getMlClient: jest.fn(),
+}));
+import { MlModelDeploymentState } from '../../../common/types/ml';
import { indexOrAliasExists } from '../../lib/indices/exists_index';
import { getMlInferenceErrors } from '../../lib/indices/pipelines/ml_inference/get_ml_inference_errors';
import { fetchMlInferencePipelineHistory } from '../../lib/indices/pipelines/ml_inference/get_ml_inference_pipeline_history';
@@ -65,6 +81,9 @@ import { preparePipelineAndIndexForMlInference } from '../../lib/indices/pipelin
import { deleteMlInferencePipeline } from '../../lib/indices/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline';
import { detachMlInferencePipeline } from '../../lib/indices/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline';
import { fetchMlInferencePipelineProcessors } from '../../lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors';
+import { getMlModelDeploymentStatus } from '../../lib/ml/get_ml_model_deployment_status';
+import { startMlModelDeployment } from '../../lib/ml/start_ml_model_deployment';
+import { startMlModelDownload } from '../../lib/ml/start_ml_model_download';
import { getMlInferencePipelines } from '../../lib/pipelines/ml_inference/get_ml_inference_pipelines';
import { ElasticsearchResponseError } from '../../utils/identify_exceptions';
@@ -166,6 +185,12 @@ describe('Enterprise Search Managed Indices', () => {
mockTrainedModelsProvider = {
getTrainedModels: jest.fn(),
getTrainedModelsStats: jest.fn(),
+ startTrainedModelDeployment: jest.fn(),
+ stopTrainedModelDeployment: jest.fn(),
+ inferTrainedModel: jest.fn(),
+ deleteTrainedModel: jest.fn(),
+ updateTrainedModelDeployment: jest.fn(),
+ putTrainedModel: jest.fn(),
} as MlTrainedModels;
mockMl = {
@@ -1060,6 +1085,12 @@ describe('Enterprise Search Managed Indices', () => {
mockTrainedModelsProvider = {
getTrainedModels: jest.fn(),
getTrainedModelsStats: jest.fn(),
+ startTrainedModelDeployment: jest.fn(),
+ stopTrainedModelDeployment: jest.fn(),
+ inferTrainedModel: jest.fn(),
+ deleteTrainedModel: jest.fn(),
+ updateTrainedModelDeployment: jest.fn(),
+ putTrainedModel: jest.fn(),
} as MlTrainedModels;
mockMl = {
@@ -1101,4 +1132,195 @@ describe('Enterprise Search Managed Indices', () => {
});
});
});
+
+ describe('POST /internal/enterprise_search/ml/models/{modelName}', () => {
+ let mockMl: SharedServices;
+ let mockTrainedModelsProvider: MlTrainedModels;
+
+ beforeEach(() => {
+ const context = {
+ core: Promise.resolve(mockCore),
+ } as unknown as jest.Mocked;
+
+ mockRouter = new MockRouter({
+ context,
+ method: 'post',
+ path: '/internal/enterprise_search/ml/models/{modelName}',
+ });
+
+ mockTrainedModelsProvider = {
+ getTrainedModels: jest.fn(),
+ getTrainedModelsStats: jest.fn(),
+ putTrainedModel: jest.fn(),
+ } as unknown as MlTrainedModels;
+
+ mockMl = {
+ trainedModelsProvider: () => Promise.resolve(mockTrainedModelsProvider),
+ } as unknown as jest.Mocked;
+
+ registerIndexRoutes({
+ ...mockDependencies,
+ ml: mockMl,
+ router: mockRouter.router,
+ });
+ });
+ const modelName = '.elser_model_1_SNAPSHOT';
+
+ it('fails validation without modelName', () => {
+ const request = {
+ params: {},
+ };
+ mockRouter.shouldThrow(request);
+ });
+
+ it('downloads the model', async () => {
+ const request = {
+ params: { modelName },
+ };
+
+ const mockResponse = {
+ deploymentState: MlModelDeploymentState.Downloading,
+ modelId: modelName,
+ nodeAllocationCount: 0,
+ startTime: 0,
+ targetAllocationCount: 0,
+ };
+
+ (startMlModelDownload as jest.Mock).mockResolvedValueOnce(mockResponse);
+
+ await mockRouter.callRoute(request);
+
+ expect(mockRouter.response.ok).toHaveBeenCalledWith({
+ body: mockResponse,
+ headers: { 'content-type': 'application/json' },
+ });
+ });
+ });
+
+ describe('POST /internal/enterprise_search/ml/models/{modelName}/deploy', () => {
+ let mockMl: SharedServices;
+ let mockTrainedModelsProvider: MlTrainedModels;
+
+ beforeEach(() => {
+ const context = {
+ core: Promise.resolve(mockCore),
+ } as unknown as jest.Mocked;
+
+ mockRouter = new MockRouter({
+ context,
+ method: 'post',
+ path: '/internal/enterprise_search/ml/models/{modelName}/deploy',
+ });
+
+ mockTrainedModelsProvider = {
+ getTrainedModels: jest.fn(),
+ getTrainedModelsStats: jest.fn(),
+ startTrainedModelDeployment: jest.fn(),
+ } as unknown as MlTrainedModels;
+
+ mockMl = {
+ trainedModelsProvider: () => Promise.resolve(mockTrainedModelsProvider),
+ } as unknown as jest.Mocked;
+
+ registerIndexRoutes({
+ ...mockDependencies,
+ ml: mockMl,
+ router: mockRouter.router,
+ });
+ });
+ const modelName = '.elser_model_1_SNAPSHOT';
+
+ it('fails validation without modelName', () => {
+ const request = {
+ params: {},
+ };
+ mockRouter.shouldThrow(request);
+ });
+
+ it('deploys the model', async () => {
+ const request = {
+ params: { modelName },
+ };
+
+ const mockResponse = {
+ deploymentState: MlModelDeploymentState.Starting,
+ modelId: modelName,
+ nodeAllocationCount: 0,
+ startTime: 123456,
+ targetAllocationCount: 3,
+ };
+
+ (startMlModelDeployment as jest.Mock).mockResolvedValueOnce(mockResponse);
+
+ await mockRouter.callRoute(request);
+
+ expect(mockRouter.response.ok).toHaveBeenCalledWith({
+ body: mockResponse,
+ headers: { 'content-type': 'application/json' },
+ });
+ });
+ });
+
+ describe('GET /internal/enterprise_search/ml/models/{modelName}', () => {
+ let mockMl: SharedServices;
+ let mockTrainedModelsProvider: MlTrainedModels;
+
+ beforeEach(() => {
+ const context = {
+ core: Promise.resolve(mockCore),
+ } as unknown as jest.Mocked;
+
+ mockRouter = new MockRouter({
+ context,
+ method: 'get',
+ path: '/internal/enterprise_search/ml/models/{modelName}',
+ });
+
+ mockTrainedModelsProvider = {
+ getTrainedModels: jest.fn(),
+ getTrainedModelsStats: jest.fn(),
+ } as unknown as MlTrainedModels;
+
+ mockMl = {
+ trainedModelsProvider: () => Promise.resolve(mockTrainedModelsProvider),
+ } as unknown as jest.Mocked;
+
+ registerIndexRoutes({
+ ...mockDependencies,
+ ml: mockMl,
+ router: mockRouter.router,
+ });
+ });
+ const modelName = '.elser_model_1_SNAPSHOT';
+
+ it('fails validation without modelName', () => {
+ const request = {
+ params: {},
+ };
+ mockRouter.shouldThrow(request);
+ });
+
+ it('deploys or downloads the model', async () => {
+ const request = {
+ params: { modelName },
+ };
+
+ const mockResponse = {
+ deploymentState: MlModelDeploymentState.Starting,
+ modelId: modelName,
+ nodeAllocationCount: 0,
+ startTime: 123456,
+ targetAllocationCount: 3,
+ };
+
+ (getMlModelDeploymentStatus as jest.Mock).mockResolvedValueOnce(mockResponse);
+
+ await mockRouter.callRoute(request);
+
+ expect(mockRouter.response.ok).toHaveBeenCalledWith({
+ body: mockResponse,
+ headers: { 'content-type': 'application/json' },
+ });
+ });
+ });
});
diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts
index 5c6e70273ae3f..373513f7111c8 100644
--- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts
@@ -37,6 +37,9 @@ import { preparePipelineAndIndexForMlInference } from '../../lib/indices/pipelin
import { deleteMlInferencePipeline } from '../../lib/indices/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline';
import { detachMlInferencePipeline } from '../../lib/indices/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline';
import { fetchMlInferencePipelineProcessors } from '../../lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors';
+import { getMlModelDeploymentStatus } from '../../lib/ml/get_ml_model_deployment_status';
+import { startMlModelDeployment } from '../../lib/ml/start_ml_model_deployment';
+import { startMlModelDownload } from '../../lib/ml/start_ml_model_download';
import { createIndexPipelineDefinitions } from '../../lib/pipelines/create_pipeline_definitions';
import { deleteIndexPipelines } from '../../lib/pipelines/delete_pipelines';
import { getCustomPipelines } from '../../lib/pipelines/get_custom_pipelines';
@@ -989,4 +992,127 @@ export function registerIndexRoutes({
}
})
);
+
+ router.post(
+ {
+ path: '/internal/enterprise_search/ml/models/{modelName}',
+ validate: {
+ params: schema.object({
+ modelName: schema.string(),
+ }),
+ },
+ },
+ elasticsearchErrorHandler(log, async (context, request, response) => {
+ const modelName = decodeURIComponent(request.params.modelName);
+ const {
+ savedObjects: { client: savedObjectsClient },
+ } = await context.core;
+ const trainedModelsProvider = ml
+ ? await ml.trainedModelsProvider(request, savedObjectsClient)
+ : undefined;
+
+ try {
+ const deployResult = await startMlModelDownload(modelName, trainedModelsProvider);
+
+ return response.ok({
+ body: deployResult,
+ headers: { 'content-type': 'application/json' },
+ });
+ } catch (error) {
+ if (isResourceNotFoundException(error)) {
+ // return specific message if model doesn't exist
+ return createError({
+ errorCode: ErrorCode.RESOURCE_NOT_FOUND,
+ message: error.meta?.body?.error?.reason,
+ response,
+ statusCode: 404,
+ });
+ }
+ // otherwise, let the default handler wrap it
+ throw error;
+ }
+ })
+ );
+
+ router.post(
+ {
+ path: '/internal/enterprise_search/ml/models/{modelName}/deploy',
+ validate: {
+ params: schema.object({
+ modelName: schema.string(),
+ }),
+ },
+ },
+ elasticsearchErrorHandler(log, async (context, request, response) => {
+ const modelName = decodeURIComponent(request.params.modelName);
+ const {
+ savedObjects: { client: savedObjectsClient },
+ } = await context.core;
+ const trainedModelsProvider = ml
+ ? await ml.trainedModelsProvider(request, savedObjectsClient)
+ : undefined;
+
+ try {
+ const deployResult = await startMlModelDeployment(modelName, trainedModelsProvider);
+
+ return response.ok({
+ body: deployResult,
+ headers: { 'content-type': 'application/json' },
+ });
+ } catch (error) {
+ if (isResourceNotFoundException(error)) {
+ // return specific message if model doesn't exist
+ return createError({
+ errorCode: ErrorCode.RESOURCE_NOT_FOUND,
+ message: error.meta?.body?.error?.reason,
+ response,
+ statusCode: 404,
+ });
+ }
+ // otherwise, let the default handler wrap it
+ throw error;
+ }
+ })
+ );
+
+ router.get(
+ {
+ path: '/internal/enterprise_search/ml/models/{modelName}',
+ validate: {
+ params: schema.object({
+ modelName: schema.string(),
+ }),
+ },
+ },
+ elasticsearchErrorHandler(log, async (context, request, response) => {
+ const modelName = decodeURIComponent(request.params.modelName);
+ const {
+ savedObjects: { client: savedObjectsClient },
+ } = await context.core;
+ const trainedModelsProvider = ml
+ ? await ml.trainedModelsProvider(request, savedObjectsClient)
+ : undefined;
+
+ try {
+ const getStatusResult = await getMlModelDeploymentStatus(modelName, trainedModelsProvider);
+
+ return response.ok({
+ body: getStatusResult,
+ headers: { 'content-type': 'application/json' },
+ });
+ } catch (error) {
+ if (isResourceNotFoundException(error)) {
+ // return specific message if model doesn't exist
+ return createError({
+ errorCode: ErrorCode.RESOURCE_NOT_FOUND,
+ message: error.meta?.body?.error?.reason,
+ response,
+ statusCode: 404,
+ });
+ }
+ // otherwise, let the default handler wrap it
+ throw error;
+ }
+ })
+ );
}
diff --git a/x-pack/plugins/fleet/common/errors.ts b/x-pack/plugins/fleet/common/errors.ts
index 8e22b971be6ad..75c789e30e9ce 100644
--- a/x-pack/plugins/fleet/common/errors.ts
+++ b/x-pack/plugins/fleet/common/errors.ts
@@ -17,3 +17,5 @@ export class FleetError extends Error {
}
export class PackagePolicyValidationError extends FleetError {}
+
+export class MessageSigningError extends FleetError {}
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/action_menu.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/action_menu.test.tsx
new file mode 100644
index 0000000000000..c88b2c0aedcad
--- /dev/null
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/action_menu.test.tsx
@@ -0,0 +1,168 @@
+/*
+ * 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 { fireEvent } from '@testing-library/dom';
+
+import { createFleetTestRendererMock } from '../../../../../../mock';
+import type { Agent, AgentPolicy } from '../../../../types';
+import { ExperimentalFeaturesService } from '../../../../services';
+import { useAuthz } from '../../../../../../hooks/use_authz';
+
+import { AgentDetailsActionMenu } from './actions_menu';
+
+jest.mock('../../../../../../hooks/use_fleet_status', () => ({
+ FleetStatusProvider: (props: any) => {
+ return props.children;
+ },
+}));
+
+jest.mock('../../../../../../services/experimental_features');
+jest.mock('../../../../../../hooks/use_authz');
+
+const mockedExperimentalFeaturesService = jest.mocked(ExperimentalFeaturesService);
+const mockedUseAuthz = jest.mocked(useAuthz);
+
+function renderActions({ agent, agentPolicy }: { agent: Agent; agentPolicy?: AgentPolicy }) {
+ const renderer = createFleetTestRendererMock();
+
+ const utils = renderer.render(
+
+ );
+
+ fireEvent.click(utils.getByRole('button'));
+
+ return { utils };
+}
+
+describe('AgentDetailsActionMenu', () => {
+ beforeEach(() => {
+ mockedExperimentalFeaturesService.get.mockReturnValue({
+ diagnosticFileUploadEnabled: true,
+ } as any);
+ mockedUseAuthz.mockReturnValue({
+ fleet: {
+ all: true,
+ },
+ } as any);
+ });
+
+ describe('Request Diagnotics action', () => {
+ function renderAndGetDiagnosticsButton({
+ agent,
+ agentPolicy,
+ }: {
+ agent: Agent;
+ agentPolicy?: AgentPolicy;
+ }) {
+ const { utils } = renderActions({
+ agent,
+ agentPolicy,
+ });
+
+ return utils.queryByTestId('requestAgentDiagnosticsBtn');
+ }
+
+ it('should not render action if feature is disabled', async () => {
+ mockedExperimentalFeaturesService.get.mockReturnValue({
+ diagnosticFileUploadEnabled: false,
+ } as any);
+ const res = renderAndGetDiagnosticsButton({
+ agent: {} as Agent,
+ agentPolicy: {} as AgentPolicy,
+ });
+ expect(res).toBe(null);
+ });
+
+ it('should render an active action button if agent version >= 8.7', async () => {
+ const res = renderAndGetDiagnosticsButton({
+ agent: {
+ status: 'online',
+ local_metadata: { elastic: { agent: { version: '8.8.0' } } },
+ } as any,
+ agentPolicy: {} as AgentPolicy,
+ });
+
+ expect(res).not.toBe(null);
+ expect(res).toBeEnabled();
+ });
+
+ it('should render an active action button if agent version >= 8.7 and policy is_managed', async () => {
+ const res = renderAndGetDiagnosticsButton({
+ agent: {
+ status: 'online',
+ local_metadata: { elastic: { agent: { version: '8.8.0' } } },
+ } as any,
+ agentPolicy: {
+ is_managed: true,
+ } as AgentPolicy,
+ });
+
+ expect(res).not.toBe(null);
+ expect(res).toBeEnabled();
+ });
+
+ it('should render a disabled action button if agent version < 8.7', async () => {
+ const res = renderAndGetDiagnosticsButton({
+ agent: {
+ status: 'online',
+ local_metadata: { elastic: { agent: { version: '8.6.0' } } },
+ } as any,
+ agentPolicy: {
+ is_managed: true,
+ } as AgentPolicy,
+ });
+
+ expect(res).not.toBe(null);
+ expect(res).not.toBeEnabled();
+ });
+ });
+
+ describe('View agent JSON action', () => {
+ function renderAndGetViewJSONButton({
+ agent,
+ agentPolicy,
+ }: {
+ agent: Agent;
+ agentPolicy?: AgentPolicy;
+ }) {
+ const { utils } = renderActions({
+ agent,
+ agentPolicy,
+ });
+
+ return utils.queryByTestId('viewAgentDetailsJsonBtn');
+ }
+
+ it('should render an active button', async () => {
+ const res = renderAndGetViewJSONButton({
+ agent: {} as any,
+ agentPolicy: {} as AgentPolicy,
+ });
+
+ expect(res).not.toBe(null);
+ expect(res).toBeEnabled();
+ });
+
+ it('should render an active button for managed agent policy', async () => {
+ const res = renderAndGetViewJSONButton({
+ agent: {} as any,
+ agentPolicy: {
+ is_managed: true,
+ } as AgentPolicy,
+ });
+
+ expect(res).not.toBe(null);
+ expect(res).toBeEnabled();
+ });
+ });
+});
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx
index d4710e71de9c2..80651ad5a4ab3 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx
@@ -61,53 +61,60 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{
}
}, [onCancelReassign, setIsReassignFlyoutOpen]);
- const menuItems = [
- {
- setIsReassignFlyoutOpen(true);
- }}
- disabled={!agent.active}
- key="reassignPolicy"
- >
-
- ,
- {
- setIsUnenrollModalOpen(true);
- }}
- key="unenrollAgent"
- >
- {isUnenrolling ? (
+ const menuItems = [];
+
+ if (!agentPolicy?.is_managed) {
+ menuItems.push(
+ {
+ setIsReassignFlyoutOpen(true);
+ }}
+ disabled={!agent.active && !agentPolicy}
+ key="reassignPolicy"
+ >
- ) : (
+ ,
+ {
+ setIsUnenrollModalOpen(true);
+ }}
+ key="unenrollAgent"
+ >
+ {isUnenrolling ? (
+
+ ) : (
+
+ )}
+ ,
+ {
+ setIsUpgradeModalOpen(true);
+ }}
+ key="upgradeAgent"
+ >
- )}
- ,
- {
- setIsUpgradeModalOpen(true);
- }}
- key="upgradeAgent"
- >
-
- ,
+
+ );
+ }
+
+ menuItems.push(
{
@@ -115,13 +122,14 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{
setIsAgentDetailsJsonFlyoutOpen(!isAgentDetailsJsonFlyoutOpen);
}}
key="agentDetailsJson"
+ data-test-subj="viewAgentDetailsJsonBtn"
>
- ,
- ];
+
+ );
if (diagnosticFileUploadEnabled) {
menuItems.push(
@@ -131,6 +139,7 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{
onClick={() => {
setIsRequestDiagnosticsModalOpen(true);
}}
+ data-test-subj="requestAgentDiagnosticsBtn"
key="requestDiagnostics"
>
{
<>
- {!isAgentPolicyLoading && !agentPolicyData?.item?.is_managed && (
+ {!isAgentPolicyLoading && (
({
+ FleetStatusProvider: (props: any) => {
+ return props.children;
+ },
+}));
+
+jest.mock('../../../../../../services/experimental_features');
+jest.mock('../../../../../../hooks/use_authz');
+
+const mockedExperimentalFeaturesService = jest.mocked(ExperimentalFeaturesService);
+const mockedUseAuthz = jest.mocked(useAuthz);
+
+function renderTableRowActions({
+ agent,
+ agentPolicy,
+}: {
+ agent: Agent;
+ agentPolicy?: AgentPolicy;
+}) {
+ const renderer = createFleetTestRendererMock();
+
+ const utils = renderer.render(
+
+ );
+
+ fireEvent.click(utils.getByTestId('agentActionsBtn'));
+
+ return { utils };
+}
+describe('TableRowActions', () => {
+ beforeEach(() => {
+ mockedExperimentalFeaturesService.get.mockReturnValue({
+ diagnosticFileUploadEnabled: true,
+ } as any);
+ mockedUseAuthz.mockReturnValue({
+ fleet: {
+ all: true,
+ },
+ } as any);
+ });
+
+ describe('Request Diagnotics action', () => {
+ function renderAndGetDiagnosticsButton({
+ agent,
+ agentPolicy,
+ }: {
+ agent: Agent;
+ agentPolicy?: AgentPolicy;
+ }) {
+ const { utils } = renderTableRowActions({
+ agent,
+ agentPolicy,
+ });
+
+ return utils.queryByTestId('requestAgentDiagnosticsBtn');
+ }
+
+ it('should not render action if feature is disabled', async () => {
+ mockedExperimentalFeaturesService.get.mockReturnValue({
+ diagnosticFileUploadEnabled: false,
+ } as any);
+ const res = renderAndGetDiagnosticsButton({
+ agent: {} as Agent,
+ agentPolicy: {} as AgentPolicy,
+ });
+ expect(res).toBe(null);
+ });
+
+ it('should render an active action button if agent version >= 8.7', async () => {
+ const res = renderAndGetDiagnosticsButton({
+ agent: {
+ status: 'online',
+ local_metadata: { elastic: { agent: { version: '8.8.0' } } },
+ } as any,
+ agentPolicy: {} as AgentPolicy,
+ });
+
+ expect(res).not.toBe(null);
+ expect(res).toBeEnabled();
+ });
+
+ it('should render an active action button if agent version >= 8.7 and policy is_managed', async () => {
+ const res = renderAndGetDiagnosticsButton({
+ agent: {
+ status: 'online',
+ local_metadata: { elastic: { agent: { version: '8.8.0' } } },
+ } as any,
+ agentPolicy: {
+ is_managed: true,
+ } as AgentPolicy,
+ });
+
+ expect(res).not.toBe(null);
+ expect(res).toBeEnabled();
+ });
+
+ it('should render a disabled action button if agent version < 8.7', async () => {
+ const res = renderAndGetDiagnosticsButton({
+ agent: {
+ status: 'online',
+ local_metadata: { elastic: { agent: { version: '8.6.0' } } },
+ } as any,
+ agentPolicy: {
+ is_managed: true,
+ } as AgentPolicy,
+ });
+
+ expect(res).not.toBe(null);
+ expect(res).not.toBeEnabled();
+ });
+ });
+});
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.tsx
index 243eabadcf3b7..e3e56ed2696af 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.tsx
@@ -80,6 +80,7 @@ export const TableRowActions: React.FunctionComponent<{
/>
,
{
@@ -99,6 +100,7 @@ export const TableRowActions: React.FunctionComponent<{
)}
,
{
@@ -111,24 +113,27 @@ export const TableRowActions: React.FunctionComponent<{
/>
);
+ }
- if (diagnosticFileUploadEnabled) {
- menuItems.push(
- {
- onRequestDiagnosticsClick();
- }}
- >
-
-
- );
- }
+ if (diagnosticFileUploadEnabled) {
+ menuItems.push(
+ {
+ onRequestDiagnosticsClick();
+ }}
+ >
+
+
+ );
}
+
return (
{
appContextService.stop();
});
- it('POST /message_signing_service/rotate_key_pair?acknowledge=true succeeds with an 200 with `acknowledge=true`', async () => {
- (appContextService.getMessageSigningService()?.rotateKeyPair as jest.Mock).mockReturnValue(
- true
- );
-
- await rotateKeyPairHandler(
- coreMock.createCustomRequestHandlerContext(context),
- request,
- response
- );
- expect(response.ok).toHaveBeenCalledWith({
- body: {
- message: 'Key pair rotated successfully.',
- },
+ it(`POST /message_signing_service/rotate_key_pair?acknowledge=true fails with an 500 with "acknowledge=true" when no messaging service`, async () => {
+ appContextService.start({
+ ...createAppContextStartContractMock(),
+ // @ts-expect-error
+ messageSigningService: undefined,
});
- });
-
- it(`POST /message_signing_service/rotate_key_pair?acknowledge=true fails with an 500 with "acknowledge=true" when rotateKeyPair doesn't succeed`, async () => {
- (appContextService.getMessageSigningService()?.rotateKeyPair as jest.Mock).mockReturnValue(
- false
- );
await rotateKeyPairHandler(
coreMock.createCustomRequestHandlerContext(context),
@@ -74,26 +59,49 @@ describe('FleetMessageSigningServiceHandler', () => {
expect(response.customError).toHaveBeenCalledWith({
statusCode: 500,
body: {
- message: 'Failed to rotate key pair!',
+ message: 'Failed to rotate key pair. Message signing service is unavailable!',
},
});
});
- it(`POST /message_signing_service/rotate_key_pair?acknowledge=true fails with an 500 with "acknowledge=true" when no messaging service`, async () => {
- (appContextService.getMessageSigningService()?.rotateKeyPair as jest.Mock).mockReturnValue(
- undefined
- );
-
+ it('POST /message_signing_service/rotate_key_pair?acknowledge=true succeeds with `acknowledge=true`', async () => {
await rotateKeyPairHandler(
coreMock.createCustomRequestHandlerContext(context),
request,
response
);
- expect(response.customError).toHaveBeenCalledWith({
- statusCode: 500,
+ expect(response.ok).toHaveBeenCalledWith({
body: {
- message: 'Failed to rotate key pair!',
+ message: 'Key pair rotated successfully.',
},
});
});
+
+ it.each([
+ 'foo',
+ Error('do not show this').message,
+ Error(JSON.stringify({ not: 'even this' })).message,
+ ])(
+ 'POST /message_signing_service/rotate_key_pair?acknowledge=true throws only a generic 500 error if rotate fails with error `%s`',
+ async (error) => {
+ // specific error
+ (appContextService.getMessageSigningService()?.rotateKeyPair as jest.Mock).mockRejectedValue(
+ Error(error)
+ );
+
+ await rotateKeyPairHandler(
+ coreMock.createCustomRequestHandlerContext(context),
+ request,
+ response
+ );
+
+ // API shows generic error
+ expect(response.customError).toHaveBeenCalledWith({
+ statusCode: 500,
+ body: {
+ message: 'Failed to rotate key pair!',
+ },
+ });
+ }
+ );
});
diff --git a/x-pack/plugins/fleet/server/routes/message_signing_service/handlers.ts b/x-pack/plugins/fleet/server/routes/message_signing_service/handlers.ts
index 18a00ca86fdd8..7e2acdb5171a8 100644
--- a/x-pack/plugins/fleet/server/routes/message_signing_service/handlers.ts
+++ b/x-pack/plugins/fleet/server/routes/message_signing_service/handlers.ts
@@ -18,25 +18,29 @@ export const rotateKeyPairHandler: FleetRequestHandler<
TypeOf,
undefined
> = async (_, __, response) => {
+ const logger = appContextService.getLogger();
+ const messageSigningService = appContextService.getMessageSigningService();
+ if (!messageSigningService) {
+ const errorMessage = 'Failed to rotate key pair. Message signing service is unavailable!';
+ logger.error(errorMessage);
+ return response.customError({
+ statusCode: 500,
+ body: {
+ message: errorMessage,
+ },
+ });
+ }
+
try {
- const rotateKeyPairResponse = await appContextService
- .getMessageSigningService()
- ?.rotateKeyPair();
+ await messageSigningService.rotateKeyPair();
- if (!rotateKeyPairResponse) {
- return response.customError({
- statusCode: 500,
- body: {
- message: 'Failed to rotate key pair!',
- },
- });
- }
return response.ok({
body: {
message: 'Key pair rotated successfully.',
},
});
} catch (error) {
- return defaultFleetErrorHandler({ error, response });
+ logger.error(error);
+ return defaultFleetErrorHandler({ error: new Error('Failed to rotate key pair!'), response });
}
};
diff --git a/x-pack/plugins/fleet/server/services/security/message_signing_service.test.ts b/x-pack/plugins/fleet/server/services/security/message_signing_service.test.ts
index 6503f2f08d566..cb4288cd31488 100644
--- a/x-pack/plugins/fleet/server/services/security/message_signing_service.test.ts
+++ b/x-pack/plugins/fleet/server/services/security/message_signing_service.test.ts
@@ -14,7 +14,7 @@ import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/s
import { MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE } from '../../constants';
import { createAppContextStartContractMock } from '../../mocks';
-import { appContextService } from '../app_context';
+import { appContextService } from '..';
import {
type MessageSigningServiceInterface,
@@ -108,7 +108,7 @@ describe('MessageSigningService', () => {
it('can correctly rotate existing key pair', async () => {
mockCreatePointInTimeFinderAsInternalUserOnce([keyPairObj]);
- const rotateKeyPairResponse = await messageSigningService.rotateKeyPair();
+ await messageSigningService.rotateKeyPair();
expect(soClientMock.delete).toBeCalledWith(
MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE,
@@ -119,8 +119,60 @@ describe('MessageSigningService', () => {
public_key: expect.any(String),
passphrase: expect.any(String),
});
+ });
+
+ it('does not generate key pair on rotation if no key pair exists', async () => {
+ // no previous key pair
+ mockCreatePointInTimeFinderAsInternalUserOnce([]);
+
+ const response = messageSigningService.rotateKeyPair();
+ await expect(response).rejects.toThrowError(
+ 'Error rotating key pair: Error fetching current key pair: No current key pair found!'
+ );
+ });
+
+ it('throws `getCurrentKeyPairObj` error if any on rotate', async () => {
+ mockCreatePointInTimeFinderAsInternalUserOnce([keyPairObj]);
+ // mock delete to throw
+ jest
+ .spyOn(messageSigningService, 'getCurrentKeyPairObj' as any)
+ .mockRejectedValue(Error('foo'));
+
+ const response = messageSigningService.rotateKeyPair();
+ await expect(response).rejects.toThrowError(
+ 'Error rotating key pair: Error fetching current key pair: foo'
+ );
+ });
+
+ it('throws `soClient` `delete` error if any on rotate', async () => {
+ mockCreatePointInTimeFinderAsInternalUserOnce([keyPairObj]);
+ // mock delete to throw
+ soClientMock.delete.mockRejectedValue(Error('foo'));
+
+ const response = messageSigningService.rotateKeyPair();
+ await expect(response).rejects.toThrowError(
+ 'Error rotating key pair: Error deleting current key pair: foo'
+ );
+ });
+
+ it('throws `soClient` `create` error if any on rotate', async () => {
+ mockCreatePointInTimeFinderAsInternalUserOnce([keyPairObj]);
+ // mock delete to throw
+ soClientMock.create.mockRejectedValue(Error('foo'));
+
+ const response = messageSigningService.rotateKeyPair();
+ await expect(response).rejects.toThrowError(
+ 'Error rotating key pair: Error creating key pair: foo'
+ );
+ });
+
+ it('throws `generateKeyPair` error if any on rotate', async () => {
+ mockCreatePointInTimeFinderAsInternalUserOnce([keyPairObj]);
+ // mock delete to throw
+ messageSigningService.generateKeyPair = jest.fn().mockRejectedValue(Error('foo'));
- expect(rotateKeyPairResponse).toEqual(true);
+ const response = messageSigningService.rotateKeyPair();
+ await expect(response).rejects.toThrowError('Error rotating key pair: foo');
});
it('does not generate key pair if one exists', async () => {
diff --git a/x-pack/plugins/fleet/server/services/security/message_signing_service.ts b/x-pack/plugins/fleet/server/services/security/message_signing_service.ts
index fc5583dec01d0..6fcf92c0aebe8 100644
--- a/x-pack/plugins/fleet/server/services/security/message_signing_service.ts
+++ b/x-pack/plugins/fleet/server/services/security/message_signing_service.ts
@@ -15,6 +15,8 @@ import type {
import type { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin/server';
import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server';
+import { MessageSigningError } from '../../../common/errors';
+
import { MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE } from '../../constants';
import { appContextService } from '../app_context';
@@ -30,7 +32,7 @@ export interface MessageSigningServiceInterface {
generateKeyPair(
providedPassphrase?: string
): Promise<{ privateKey: string; publicKey: string; passphrase: string }>;
- rotateKeyPair(): Promise;
+ rotateKeyPair(): Promise;
sign(message: Buffer | Record): Promise<{ data: Buffer; signature: string }>;
getPublicKey(): Promise;
}
@@ -40,15 +42,13 @@ export class MessageSigningService implements MessageSigningServiceInterface {
constructor(private esoClient: EncryptedSavedObjectsClient) {}
- public get isEncryptionAvailable(): boolean {
+ public get isEncryptionAvailable(): MessageSigningServiceInterface['isEncryptionAvailable'] {
return appContextService.getEncryptedSavedObjectsSetup()?.canEncrypt ?? false;
}
- public async generateKeyPair(providedPassphrase?: string): Promise<{
- privateKey: string;
- publicKey: string;
- passphrase: string;
- }> {
+ public async generateKeyPair(
+ providedPassphrase?: string
+ ): ReturnType {
const existingKeyPair = await this.checkForExistingKeyPair();
if (existingKeyPair) {
return existingKeyPair;
@@ -82,21 +82,25 @@ export class MessageSigningService implements MessageSigningServiceInterface {
}
: { ...keypairSoObject, passphrase_plain: passphrase };
- await this.soClient.create>(
- MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE,
- keypairSoObject
- );
+ try {
+ await this.soClient.create>(
+ MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE,
+ keypairSoObject
+ );
- return {
- privateKey,
- publicKey,
- passphrase,
- };
+ return {
+ privateKey,
+ publicKey,
+ passphrase,
+ };
+ } catch (error) {
+ throw new MessageSigningError(`Error creating key pair: ${error.message}`, error);
+ }
}
public async sign(
message: Buffer | Record
- ): Promise<{ data: Buffer; signature: string }> {
+ ): ReturnType {
const { privateKey: serializedPrivateKey, passphrase } = await this.generateKeyPair();
const msgBuffer = Buffer.isBuffer(message)
@@ -125,7 +129,7 @@ export class MessageSigningService implements MessageSigningServiceInterface {
};
}
- public async getPublicKey(): Promise {
+ public async getPublicKey(): ReturnType {
const { publicKey } = await this.generateKeyPair();
if (!publicKey) {
@@ -135,26 +139,34 @@ export class MessageSigningService implements MessageSigningServiceInterface {
return publicKey;
}
- public async rotateKeyPair(): Promise {
- const isRemoved = await this.removeKeyPair();
- if (isRemoved) {
+ public async rotateKeyPair(): ReturnType {
+ try {
+ await this.removeKeyPair();
await this.generateKeyPair();
- return true;
+ } catch (error) {
+ throw new MessageSigningError(`Error rotating key pair: ${error.message}`, error);
}
- return false;
- // TODO: Apply changes to all policies
}
- private async removeKeyPair(): Promise {
- const currentKeyPair = await this.getCurrentKeyPairObj();
- if (currentKeyPair) {
+ private async removeKeyPair(): Promise {
+ let currentKeyPair: Awaited>;
+ try {
+ currentKeyPair = await this.getCurrentKeyPairObj();
+ if (!currentKeyPair) {
+ throw new MessageSigningError('No current key pair found!');
+ }
+ } catch (error) {
+ throw new MessageSigningError(`Error fetching current key pair: ${error.message}`, error);
+ }
+
+ try {
await this.soClient.delete(MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE, currentKeyPair.id);
- return true;
+ } catch (error) {
+ throw new MessageSigningError(`Error deleting current key pair: ${error.message}`, error);
}
- return false;
}
- private get soClient() {
+ private get soClient(): SavedObjectsClientContract {
if (this._soClient) {
return this._soClient;
}
diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx
index ebba2083fdf32..e7f4b22a4717b 100644
--- a/x-pack/plugins/lens/public/app_plugin/app.tsx
+++ b/x-pack/plugins/lens/public/app_plugin/app.tsx
@@ -8,6 +8,7 @@
import './app.scss';
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
import { i18n } from '@kbn/i18n';
+import type { TimeRange } from '@kbn/es-query';
import { EuiBreadcrumb, EuiConfirmModal } from '@elastic/eui';
import { useExecutionContext, useKibana } from '@kbn/kibana-react-plugin/public';
import { OnSaveProps } from '@kbn/saved-objects-plugin/public';
@@ -52,6 +53,7 @@ export type SaveProps = Omit
onTitleDuplicate?: OnSaveProps['onTitleDuplicate'];
newDescription?: string;
newTags?: string[];
+ panelTimeRange?: TimeRange;
};
export function App({
diff --git a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx
index 50d70bb58ae75..9128f64783298 100644
--- a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx
+++ b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx
@@ -670,6 +670,7 @@ export const LensTopNavMenu = ({
isTitleDuplicateConfirmed: false,
returnToOrigin: true,
newDescription: contextFromEmbeddable ? initialContext.description : '',
+ panelTimeRange: contextFromEmbeddable ? initialContext.panelTimeRange : undefined,
},
{
saveToLibrary:
diff --git a/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx b/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx
index de5748b8c6550..0f5f1c88dbd3f 100644
--- a/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx
+++ b/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx
@@ -292,11 +292,17 @@ export const runSaveLensVisualization = async (
}
}
try {
- const newInput = (await attributeService.wrapAttributes(
+ let newInput = (await attributeService.wrapAttributes(
docToSave,
options.saveToLibrary,
originalInput
)) as LensEmbeddableInput;
+ if (saveProps.panelTimeRange) {
+ newInput = {
+ ...newInput,
+ timeRange: saveProps.panelTimeRange,
+ };
+ }
if (saveProps.returnToOrigin && redirectToOrigin) {
// disabling the validation on app leave because the document has been saved.
diff --git a/x-pack/plugins/lens/public/app_plugin/types.ts b/x-pack/plugins/lens/public/app_plugin/types.ts
index 193ba130e02ef..0db73da116132 100644
--- a/x-pack/plugins/lens/public/app_plugin/types.ts
+++ b/x-pack/plugins/lens/public/app_plugin/types.ts
@@ -9,6 +9,7 @@ import type { History } from 'history';
import type { OnSaveProps } from '@kbn/saved-objects-plugin/public';
import { Observable } from 'rxjs';
import { SpacesApi } from '@kbn/spaces-plugin/public';
+import type { TimeRange } from '@kbn/es-query';
import type {
ApplicationStart,
AppMountParameters,
@@ -94,6 +95,7 @@ export type RunSave = (
onTitleDuplicate?: OnSaveProps['onTitleDuplicate'];
newDescription?: string;
newTags?: string[];
+ panelTimeRange?: TimeRange;
},
options: {
saveToLibrary: boolean;
diff --git a/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/dimension_editor.tsx
index 4f7842ee125c4..b54bac6d0121e 100644
--- a/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/dimension_editor.tsx
+++ b/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/dimension_editor.tsx
@@ -784,6 +784,13 @@ export function DimensionEditor(props: DimensionEditorProps) {
incompleteColumn={
layer.incompleteColumns ? layer.incompleteColumns[referenceId] : undefined
}
+ onResetIncomplete={() => {
+ updateLayer({
+ ...layer,
+ // clean up the incomplete column data for the referenced id
+ incompleteColumns: { ...layer.incompleteColumns, [referenceId]: null },
+ });
+ }}
onDeleteColumn={() => {
updateLayer(
deleteColumn({
diff --git a/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/reference_editor.test.tsx b/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/reference_editor.test.tsx
index cb50049e3fbec..74a43f3e7db21 100644
--- a/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/reference_editor.test.tsx
+++ b/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/reference_editor.test.tsx
@@ -65,6 +65,7 @@ describe('reference editor', () => {
onChooseField: jest.fn(),
onChooseFunction: jest.fn(),
onDeleteColumn: jest.fn(),
+ onResetIncomplete: jest.fn(),
columnId: 'ref',
paramEditorUpdater,
selectionStyle: 'full' as const,
diff --git a/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/reference_editor.tsx b/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/reference_editor.tsx
index fa918b07eb29d..53fe225d4d926 100644
--- a/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/reference_editor.tsx
+++ b/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/reference_editor.tsx
@@ -92,6 +92,7 @@ export interface ReferenceEditorProps {
) => void;
onChooseField: (choice: FieldChoiceWithOperationType) => void;
onDeleteColumn: () => void;
+ onResetIncomplete: () => void;
onChooseFunction: (operationType: string, field?: IndexPatternField) => void;
// Services
@@ -116,6 +117,7 @@ export const ReferenceEditor = (props: ReferenceEditorProps) => {
functionLabel,
onChooseField,
onDeleteColumn,
+ onResetIncomplete,
onChooseFunction,
fieldLabel,
operationDefinitionMap,
@@ -261,6 +263,10 @@ export const ReferenceEditor = (props: ReferenceEditorProps) => {
}
const operationType = choices[0].value!;
+ // When it has an incomplete state, make sure to clear it up before updating
+ if (incompleteColumn) {
+ onResetIncomplete();
+ }
if (column?.operationType === operationType) {
return;
}
diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/index.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/index.tsx
index 32d26cff1b12f..e676075309fb0 100644
--- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/index.tsx
+++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/index.tsx
@@ -800,6 +800,7 @@ The top values of a specified field ranked by the chosen metric.
}}
column={currentColumn.params.orderAgg}
incompleteColumn={incompleteColumn}
+ onResetIncomplete={() => setIncompleteColumn(undefined)}
onDeleteColumn={() => {
throw new Error('Should not be called');
}}
diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts
index 1904be8c1541c..6d24931032274 100644
--- a/x-pack/plugins/lens/public/types.ts
+++ b/x-pack/plugins/lens/public/types.ts
@@ -246,6 +246,7 @@ export type VisualizeEditorContext = {
searchFilters?: Filter[];
title?: string;
description?: string;
+ panelTimeRange?: TimeRange;
visTypeTitle?: string;
isEmbeddable?: boolean;
} & NavigateToLensContext;
diff --git a/x-pack/plugins/lens/readme.md b/x-pack/plugins/lens/readme.md
index b3768ba0b2540..334ab844c4308 100644
--- a/x-pack/plugins/lens/readme.md
+++ b/x-pack/plugins/lens/readme.md
@@ -229,7 +229,8 @@ Run all tests from the `x-pack` root directory
- Run `node scripts/functional_tests_server`
- Run `node ../scripts/functional_test_runner.js --config ./test/functional/apps/lens/group1/config.ts`
- Run `node ../scripts/functional_test_runner.js --config ./test/functional/apps/lens/group2/config.ts`
- - Run `node ../scripts/functional_test_runner.js --config ./test/functional/apps/lens/group3/config.ts`
+ - ...
+ - Run `node ../scripts/functional_test_runner.js --config ./test/functional/apps/lens/group6/config.ts`
- API Functional tests:
- Run `node scripts/functional_tests_server`
- Run `node ../scripts/functional_test_runner.js --config ./test/api_integration/config.ts --grep=Lens`
diff --git a/x-pack/plugins/license_management/__jest__/__snapshots__/license_page_header.test.js.snap b/x-pack/plugins/license_management/__jest__/__snapshots__/license_page_header.test.js.snap
index 51f214ca7f592..533367de22376 100644
--- a/x-pack/plugins/license_management/__jest__/__snapshots__/license_page_header.test.js.snap
+++ b/x-pack/plugins/license_management/__jest__/__snapshots__/license_page_header.test.js.snap
@@ -93,7 +93,7 @@ Array [
>
Your license will expire on
- October 12, 2099 7:00 PM EST
+ October 12, 2099 8:00 PM EDT
diff --git a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts
index d37f02a079a2d..da37b796cdf55 100644
--- a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts
+++ b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts
@@ -408,7 +408,7 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource
if (!geoField) {
throw new Error(
i18n.translate('xpack.maps.source.esSource.noGeoFieldErrorMessage', {
- defaultMessage: `Data view "{indexPatternLabel}"" no longer contains the geo field "{geoField}"`,
+ defaultMessage: `Data view "{indexPatternLabel}" no longer contains the geo field "{geoField}"`,
values: { indexPatternLabel: indexPattern.getName(), geoField: this.getGeoFieldName() },
})
);
diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx
index 834023182f45b..e2377ebd2e886 100644
--- a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx
+++ b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx
@@ -794,7 +794,6 @@ export class MapEmbeddable
}
const hiddenLayerIds = getHiddenLayerIds(this._savedMap.getStore().getState());
-
if (!_.isEqual(this.input.hiddenLayers, hiddenLayerIds)) {
this.updateInput({
hiddenLayers: hiddenLayerIds,
@@ -802,14 +801,7 @@ export class MapEmbeddable
}
const isLoading = isMapLoading(this._savedMap.getStore().getState());
- const firstLayerWithError = getLayerList(this._savedMap.getStore().getState()).find((layer) => {
- return layer.hasErrors();
- });
- const output = this.getOutput();
- if (
- output.loading !== isLoading ||
- firstLayerWithError?.getErrors() !== output.error?.message
- ) {
+ if (this.getOutput().loading !== isLoading) {
/**
* Maps emit rendered when the data is loaded, as we don't have feedback from the maps rendering library atm.
* This means that the DASHBOARD_LOADED_EVENT event might be fired while a map is still rendering in some cases.
@@ -817,13 +809,10 @@ export class MapEmbeddable
*/
this.updateOutput({
loading: isLoading,
- rendered: !isLoading && firstLayerWithError === undefined,
- error: firstLayerWithError
- ? {
- name: 'EmbeddableError',
- message: firstLayerWithError.getErrors(),
- }
- : undefined,
+ rendered: !isLoading,
+ // do not surface layer errors as output.error
+ // output.error blocks entire embeddable display and prevents map from displaying
+ // layer errors are better surfaced in legend while still keeping the map usable
});
}
}
diff --git a/x-pack/plugins/ml/public/application/management/jobs_list/components/insufficient_license_page.tsx b/x-pack/plugins/ml/public/application/management/jobs_list/components/insufficient_license_page.tsx
index a6a9e11198822..9c68f6d4f7aec 100644
--- a/x-pack/plugins/ml/public/application/management/jobs_list/components/insufficient_license_page.tsx
+++ b/x-pack/plugins/ml/public/application/management/jobs_list/components/insufficient_license_page.tsx
@@ -10,7 +10,14 @@ import React, { FC } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { CoreStart } from '@kbn/core/public';
-import { EuiEmptyPrompt, EuiLink, EuiPageContent_Deprecated as EuiPageContent } from '@elastic/eui';
+import {
+ EuiButton,
+ EuiButtonEmpty,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiPageTemplate,
+ EuiText,
+} from '@elastic/eui';
interface Props {
basePath: CoreStart['http']['basePath'];
@@ -18,41 +25,63 @@ interface Props {
export const InsufficientLicensePage: FC = ({ basePath }) => (
<>
-
+
+
+ }
data-test-subj="mlPageInsufficientLicense"
- >
-
-
-
- }
- body={
-
-
-
-
- ),
- }}
- />
-
- }
- />
-
+ body={
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ,
+
+
+
+
+
+ ,
+
+
+
+
+ }
+ />
>
);
diff --git a/x-pack/plugins/ml/public/application/model_management/model_actions.tsx b/x-pack/plugins/ml/public/application/model_management/model_actions.tsx
index 8fdb50f7b40f3..84957ac045e51 100644
--- a/x-pack/plugins/ml/public/application/model_management/model_actions.tsx
+++ b/x-pack/plugins/ml/public/application/model_management/model_actions.tsx
@@ -15,6 +15,10 @@ import {
DEPLOYMENT_STATE,
TRAINED_MODEL_TYPE,
} from '@kbn/ml-trained-models-utils';
+import {
+ CURATED_MODEL_TAG,
+ MODEL_STATE,
+} from '@kbn/ml-trained-models-utils/src/constants/trained_models';
import { useTrainedModelsApiService } from '../services/ml_api_service/trained_models';
import { getUserConfirmationProvider } from './force_stop_dialog';
import { useToastNotificationService } from '../services/toast_notification_service';
@@ -154,7 +158,7 @@ export function useModelActions({
type: 'icon',
isPrimary: true,
enabled: (item) => {
- return canStartStopTrainedModels && !isLoading;
+ return canStartStopTrainedModels && !isLoading && item.state !== MODEL_STATE.DOWNLOADING;
},
available: (item) => item.model_type === TRAINED_MODEL_TYPE.PYTORCH,
onClick: async (item) => {
@@ -317,6 +321,50 @@ export function useModelActions({
}
},
},
+ {
+ name: i18n.translate('xpack.ml.inference.modelsList.downloadModelActionLabel', {
+ defaultMessage: 'Download model',
+ }),
+ description: i18n.translate('xpack.ml.inference.modelsList.downloadModelActionLabel', {
+ defaultMessage: 'Download model',
+ }),
+ 'data-test-subj': 'mlModelsTableRowDownloadModelAction',
+ icon: 'download',
+ type: 'icon',
+ isPrimary: true,
+ available: (item) => item.tags.includes(CURATED_MODEL_TAG),
+ enabled: (item) => !item.state && !isLoading,
+ onClick: async (item) => {
+ try {
+ onLoading(true);
+ await trainedModelsApiService.putTrainedModelConfig(
+ item.model_id,
+ item.putModelConfig!
+ );
+ displaySuccessToast(
+ i18n.translate('xpack.ml.trainedModels.modelsList.downloadSuccess', {
+ defaultMessage: '"{modelId}" model download has been started successfully.',
+ values: {
+ modelId: item.model_id,
+ },
+ })
+ );
+ // Need to fetch model state updates
+ await fetchModels();
+ } catch (e) {
+ displayErrorToast(
+ e,
+ i18n.translate('xpack.ml.trainedModels.modelsList.downloadFailed', {
+ defaultMessage: 'Failed to download "{modelId}"',
+ values: {
+ modelId: item.model_id,
+ },
+ })
+ );
+ onLoading(false);
+ }
+ },
+ },
{
name: (model) => {
const enabled = !isPopulatedObject(model.pipelines);
@@ -350,7 +398,8 @@ export function useModelActions({
onClick: (model) => {
onModelsDeleteRequest([model.model_id]);
},
- available: (item) => canDeleteTrainedModels && !isBuiltInModel(item),
+ available: (item) =>
+ canDeleteTrainedModels && !isBuiltInModel(item) && !item.putModelConfig,
enabled: (item) => {
// TODO check for permissions to delete ingest pipelines.
// ATM undefined means pipelines fetch failed server-side.
diff --git a/x-pack/plugins/ml/public/application/model_management/models_list.tsx b/x-pack/plugins/ml/public/application/model_management/models_list.tsx
index 5887dc05c5369..391136f3f2ada 100644
--- a/x-pack/plugins/ml/public/application/model_management/models_list.tsx
+++ b/x-pack/plugins/ml/public/application/model_management/models_list.tsx
@@ -33,6 +33,12 @@ import {
DEPLOYMENT_STATE,
} from '@kbn/ml-trained-models-utils';
import { isDefined } from '@kbn/ml-is-defined';
+import {
+ CURATED_MODEL_DEFINITIONS,
+ CURATED_MODEL_TAG,
+ CURATED_MODEL_TYPE,
+ MODEL_STATE,
+} from '@kbn/ml-trained-models-utils/src/constants/trained_models';
import { useModelActions } from './model_actions';
import { ModelsTableToConfigMapping } from '.';
import { ModelsBarStats, StatsBar } from '../components/stats_bar';
@@ -62,6 +68,8 @@ export type ModelItem = TrainedModelConfigResponse & {
stats?: Stats & { deployment_stats: TrainedModelDeploymentStatsResponse[] };
pipelines?: ModelPipelines['pipelines'] | null;
deployment_ids: string[];
+ putModelConfig?: object;
+ state: string;
};
export type ModelItemFull = Required;
@@ -134,6 +142,35 @@ export const ModelsList: FC = ({
[]
);
+ const isCuratedModel = useCallback(
+ (item: ModelItem) => item.tags.includes(CURATED_MODEL_TAG),
+ []
+ );
+
+ /**
+ * Checks if the model download complete.
+ */
+ const isDownloadComplete = useCallback(
+ async (modelId: string): Promise => {
+ try {
+ const response = await trainedModelsApiService.getTrainedModels(modelId, {
+ include: 'definition_status',
+ });
+ // @ts-ignore
+ return !!response[0]?.fully_defined;
+ } catch (error) {
+ displayErrorToast(
+ error,
+ i18n.translate('xpack.ml.trainedModels.modelsList.downloadStatusCheckErrorMessage', {
+ defaultMessage: 'Failed to check download status',
+ })
+ );
+ }
+ return false;
+ },
+ [trainedModelsApiService, displayErrorToast]
+ );
+
/**
* Fetches trained models.
*/
@@ -158,6 +195,7 @@ export const ModelsList: FC = ({
model.model_type,
...Object.keys(model.inference_config),
...(isBuiltInModel(model as ModelItem) ? [BUILT_IN_MODEL_TYPE] : []),
+ ...(isCuratedModel(model as ModelItem) ? [CURATED_MODEL_TYPE] : []),
],
}
: {}),
@@ -239,7 +277,26 @@ export const ModelsList: FC = ({
model.deployment_ids = modelStats
.map((v) => v.deployment_stats?.deployment_id)
.filter(isDefined);
+ model.state = model.stats.deployment_stats?.some(
+ (v) => v.state === DEPLOYMENT_STATE.STARTED
+ )
+ ? DEPLOYMENT_STATE.STARTED
+ : '';
});
+
+ const curatedModels = models.filter((model) =>
+ CURATED_MODEL_DEFINITIONS.hasOwnProperty(model.model_id)
+ );
+ if (curatedModels.length > 0) {
+ for (const model of curatedModels) {
+ if (model.state === MODEL_STATE.STARTED) {
+ // no need to check for the download status if the model has been deployed
+ continue;
+ }
+ const isDownloaded = await isDownloadComplete(model.model_id);
+ model.state = isDownloaded ? MODEL_STATE.DOWNLOADED : MODEL_STATE.DOWNLOADING;
+ }
+ }
}
return true;
@@ -310,21 +367,26 @@ export const ModelsList: FC = ({
align: 'left',
width: '40px',
isExpander: true,
- render: (item: ModelItem) => (
-
- ),
+ render: (item: ModelItem) => {
+ if (!item.stats) {
+ return null;
+ }
+ return (
+
+ );
+ },
'data-test-subj': 'mlModelsTableRowDetailsToggle',
},
{
@@ -368,17 +430,13 @@ export const ModelsList: FC = ({
'data-test-subj': 'mlModelsTableColumnType',
},
{
+ field: 'state',
name: i18n.translate('xpack.ml.trainedModels.modelsList.stateHeader', {
defaultMessage: 'State',
}),
align: 'left',
truncateText: false,
- render: (model: ModelItem) => {
- const state = model.stats?.deployment_stats?.some(
- (v) => v.state === DEPLOYMENT_STATE.STARTED
- )
- ? DEPLOYMENT_STATE.STARTED
- : '';
+ render: (state: string) => {
return state ? {state} : null;
},
'data-test-subj': 'mlModelsTableColumnDeploymentState',
@@ -473,7 +531,10 @@ export const ModelsList: FC = ({
return '';
},
- selectable: (item) => !isPopulatedObject(item.pipelines) && !isBuiltInModel(item),
+ selectable: (item) =>
+ !isPopulatedObject(item.pipelines) &&
+ !isBuiltInModel(item) &&
+ !(isCuratedModel(item) && !item.state),
onSelectionChange: (selectedItems) => {
setSelectedModels(selectedItems);
},
@@ -510,6 +571,22 @@ export const ModelsList: FC = ({
: {}),
};
+ const resultItems = useMemo(() => {
+ const idSet = new Set(items.map((i) => i.model_id));
+ const notDownloaded: ModelItem[] = Object.entries(CURATED_MODEL_DEFINITIONS)
+ .filter(([modelId]) => !idSet.has(modelId))
+ .map(([modelId, modelDefinition]) => {
+ return {
+ model_id: modelId,
+ type: [CURATED_MODEL_TYPE],
+ tags: [CURATED_MODEL_TAG],
+ putModelConfig: modelDefinition.config,
+ description: modelDefinition.description,
+ } as ModelItem;
+ });
+ return [...items, ...notDownloaded];
+ }, [items]);
+
return (
<>
@@ -531,7 +608,7 @@ export const ModelsList: FC = ({
isExpandable={true}
itemIdToExpandedRowMap={itemIdToExpandedRowMap}
isSelectable={false}
- items={items}
+ items={resultItems}
itemId={ModelsTableToConfigMapping.id}
loading={isLoading}
search={search}
diff --git a/x-pack/plugins/ml/public/application/model_management/test_models/utils.ts b/x-pack/plugins/ml/public/application/model_management/test_models/utils.ts
index bb0dc6e9973e8..f97015773c908 100644
--- a/x-pack/plugins/ml/public/application/model_management/test_models/utils.ts
+++ b/x-pack/plugins/ml/public/application/model_management/test_models/utils.ts
@@ -13,7 +13,9 @@ import {
} from '@kbn/ml-trained-models-utils';
import type { ModelItem } from '../models_list';
-const PYTORCH_TYPES = Object.values(SUPPORTED_PYTORCH_TASKS);
+const PYTORCH_TYPES = Object.values(SUPPORTED_PYTORCH_TASKS).filter(
+ (taskType) => taskType !== SUPPORTED_PYTORCH_TASKS.TEXT_EXPANSION
+);
export function isTestable(modelItem: ModelItem, checkForState = false) {
if (
diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/trained_models.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/trained_models.ts
index 2975982eccad0..858fabcfb8ff5 100644
--- a/x-pack/plugins/ml/public/application/services/ml_api_service/trained_models.ts
+++ b/x-pack/plugins/ml/public/application/services/ml_api_service/trained_models.ts
@@ -196,6 +196,14 @@ export function trainedModelsApiProvider(httpService: HttpService) {
query: { type, node, showClosedJobs },
});
},
+
+ putTrainedModelConfig(modelId: string, config: object) {
+ return httpService.http({
+ path: `${apiBasePath}/trained_models/${modelId}`,
+ method: 'PUT',
+ body: JSON.stringify(config),
+ });
+ },
};
}
diff --git a/x-pack/plugins/ml/server/lib/ml_client/types.ts b/x-pack/plugins/ml/server/lib/ml_client/types.ts
index bba5e9b49a0ac..88d1475b23ff7 100644
--- a/x-pack/plugins/ml/server/lib/ml_client/types.ts
+++ b/x-pack/plugins/ml/server/lib/ml_client/types.ts
@@ -13,12 +13,15 @@ export interface UpdateTrainedModelDeploymentRequest {
model_id: string;
number_of_allocations: number;
}
+export interface UpdateTrainedModelDeploymentResponse {
+ acknowledge: boolean;
+}
export interface MlClient extends OrigMlClient {
anomalySearch: ReturnType['anomalySearch'];
updateTrainedModelDeployment: (
payload: UpdateTrainedModelDeploymentRequest
- ) => Promise<{ acknowledge: boolean }>;
+ ) => Promise;
}
export type MlClientParams =
diff --git a/x-pack/plugins/ml/server/shared_services/providers/trained_models.ts b/x-pack/plugins/ml/server/shared_services/providers/trained_models.ts
index 808b1a2f0d4a4..ca2b08702ce72 100644
--- a/x-pack/plugins/ml/server/shared_services/providers/trained_models.ts
+++ b/x-pack/plugins/ml/server/shared_services/providers/trained_models.ts
@@ -7,7 +7,10 @@
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { KibanaRequest, SavedObjectsClientContract } from '@kbn/core/server';
-import { UpdateTrainedModelDeploymentRequest } from '../../lib/ml_client/types';
+import type {
+ UpdateTrainedModelDeploymentRequest,
+ UpdateTrainedModelDeploymentResponse,
+} from '../../lib/ml_client/types';
import type { GetGuards } from '../shared_services';
export interface TrainedModelsProvider {
@@ -21,6 +24,24 @@ export interface TrainedModelsProvider {
getTrainedModelsStats(
params: estypes.MlGetTrainedModelsStatsRequest
): Promise;
+ startTrainedModelDeployment(
+ params: estypes.MlStartTrainedModelDeploymentRequest
+ ): Promise;
+ stopTrainedModelDeployment(
+ params: estypes.MlStopTrainedModelDeploymentRequest
+ ): Promise;
+ inferTrainedModel(
+ params: estypes.MlInferTrainedModelRequest
+ ): Promise;
+ deleteTrainedModel(
+ params: estypes.MlDeleteTrainedModelRequest
+ ): Promise;
+ updateTrainedModelDeployment(
+ params: UpdateTrainedModelDeploymentRequest
+ ): Promise;
+ putTrainedModel(
+ params: estypes.MlPutTrainedModelRequest
+ ): Promise;
};
}
@@ -80,11 +101,19 @@ export function getTrainedModelsProvider(getGuards: GetGuards): TrainedModelsPro
async updateTrainedModelDeployment(params: UpdateTrainedModelDeploymentRequest) {
return await guards
.isFullLicense()
- .hasMlCapabilities(['canStartStopTrainedModels'])
+ .hasMlCapabilities(['canCreateTrainedModels'])
.ok(async ({ mlClient }) => {
return mlClient.updateTrainedModelDeployment(params);
});
},
+ async putTrainedModel(params: estypes.MlPutTrainedModelRequest) {
+ return await guards
+ .isFullLicense()
+ .hasMlCapabilities(['canCreateTrainedModels'])
+ .ok(async ({ mlClient }) => {
+ return mlClient.putTrainedModel(params);
+ });
+ },
};
},
};
diff --git a/x-pack/plugins/observability/public/components/slo/feedback_button/feedback_button.tsx b/x-pack/plugins/observability/public/components/slo/feedback_button/feedback_button.tsx
index f72e4b008f390..dbd45bf74dee4 100644
--- a/x-pack/plugins/observability/public/components/slo/feedback_button/feedback_button.tsx
+++ b/x-pack/plugins/observability/public/components/slo/feedback_button/feedback_button.tsx
@@ -5,16 +5,21 @@
* 2.0.
*/
+import React from 'react';
import { EuiButton } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import React from 'react';
const SLO_FEEDBACK_LINK = 'https://ela.st/slo-feedback';
-export function FeedbackButton() {
+interface Props {
+ disabled?: boolean;
+}
+
+export function FeedbackButton({ disabled }: Props) {
return (
ruleId ? `${RULES_PAGE_LINK}/${encodeURI(ruleId)}` : RULES_PAGE_LINK,
slos: SLOS_PAGE_LINK,
+ slosWelcome: `${SLOS_PAGE_LINK}/welcome`,
sloCreate: `${SLOS_PAGE_LINK}/create`,
sloEdit: (sloId: string) => `${SLOS_PAGE_LINK}/edit/${encodeURI(sloId)}`,
sloDetails: (sloId: string) => `${SLOS_PAGE_LINK}/${encodeURI(sloId)}`,
diff --git a/x-pack/plugins/observability/public/hooks/slo/__storybook_mocks__/use_fetch_slo_list.ts b/x-pack/plugins/observability/public/hooks/slo/__storybook_mocks__/use_fetch_slo_list.ts
index 3c573cb6754d5..2faa0887b82ea 100644
--- a/x-pack/plugins/observability/public/hooks/slo/__storybook_mocks__/use_fetch_slo_list.ts
+++ b/x-pack/plugins/observability/public/hooks/slo/__storybook_mocks__/use_fetch_slo_list.ts
@@ -12,6 +12,7 @@ export const useFetchSloList = (): UseFetchSloListResponse => {
return {
isInitialLoading: false,
isLoading: false,
+ isRefetching: false,
isError: false,
isSuccess: true,
sloList,
diff --git a/x-pack/plugins/observability/public/hooks/slo/use_clone_slo.ts b/x-pack/plugins/observability/public/hooks/slo/use_clone_slo.ts
index d2fcc25eb2c34..00fddd8aebf14 100644
--- a/x-pack/plugins/observability/public/hooks/slo/use_clone_slo.ts
+++ b/x-pack/plugins/observability/public/hooks/slo/use_clone_slo.ts
@@ -7,15 +7,19 @@
import { v1 as uuidv1 } from 'uuid';
import { useMutation, useQueryClient } from '@tanstack/react-query';
+import { i18n } from '@kbn/i18n';
import type { CreateSLOInput, CreateSLOResponse, FindSLOResponse } from '@kbn/slo-schema';
import { useKibana } from '../../utils/kibana_react';
export function useCloneSlo() {
- const { http } = useKibana().services;
+ const {
+ http,
+ notifications: { toasts },
+ } = useKibana().services;
const queryClient = useQueryClient();
- const cloneSlo = useMutation<
+ return useMutation<
CreateSLOResponse,
string,
{ slo: CreateSLOInput; idToCopyFrom?: string },
@@ -41,7 +45,10 @@ export function useCloneSlo() {
const optimisticUpdate = {
...data,
- results: [...(data?.results || []), { ...sloUsedToClone, name: slo.name, id: uuidv1() }],
+ results: [
+ ...(data?.results || []),
+ { ...sloUsedToClone, name: slo.name, id: uuidv1(), summary: undefined },
+ ],
total: data?.total && data.total + 1,
};
@@ -50,20 +57,31 @@ export function useCloneSlo() {
queryClient.setQueryData(queryKey, optimisticUpdate);
}
+ toasts.addSuccess(
+ i18n.translate('xpack.observability.slo.clone.successNotification', {
+ defaultMessage: 'Successfully created {name}',
+ values: { name: slo.name },
+ })
+ );
+
// Return a context object with the snapshotted value
return { previousSloList: data };
},
// If the mutation fails, use the context returned from onMutate to roll back
- onError: (_err, _slo, context) => {
+ onError: (_err, { slo }, context) => {
if (context?.previousSloList) {
queryClient.setQueryData(['fetchSloList'], context.previousSloList);
}
+ toasts.addDanger(
+ i18n.translate('xpack.observability.slo.clone.errorNotification', {
+ defaultMessage: 'Failed to clone {name}',
+ values: { name: slo.name },
+ })
+ );
},
onSuccess: () => {
queryClient.invalidateQueries(['fetchSloList']);
},
}
);
-
- return cloneSlo;
}
diff --git a/x-pack/plugins/observability/public/hooks/slo/use_create_slo.ts b/x-pack/plugins/observability/public/hooks/slo/use_create_slo.ts
index d9b000fee0cd2..3b4bdf4ce61bd 100644
--- a/x-pack/plugins/observability/public/hooks/slo/use_create_slo.ts
+++ b/x-pack/plugins/observability/public/hooks/slo/use_create_slo.ts
@@ -5,27 +5,70 @@
* 2.0.
*/
+import { v1 as uuidv1 } from 'uuid';
import { useMutation, useQueryClient } from '@tanstack/react-query';
-import type { CreateSLOInput, CreateSLOResponse } from '@kbn/slo-schema';
+import { i18n } from '@kbn/i18n';
+import type { CreateSLOInput, CreateSLOResponse, FindSLOResponse } from '@kbn/slo-schema';
import { useKibana } from '../../utils/kibana_react';
export function useCreateSlo() {
- const { http } = useKibana().services;
+ const {
+ http,
+ notifications: { toasts },
+ } = useKibana().services;
const queryClient = useQueryClient();
- const createSlo = useMutation(
+ return useMutation(
({ slo }: { slo: CreateSLOInput }) => {
const body = JSON.stringify(slo);
return http.post(`/api/observability/slos`, { body });
},
{
mutationKey: ['createSlo'],
- onSuccess: () => {
+ onSuccess: (_data, { slo: { name } }) => {
+ toasts.addSuccess(
+ i18n.translate('xpack.observability.slo.create.successNotification', {
+ defaultMessage: 'Successfully created {name}',
+ values: { name },
+ })
+ );
queryClient.invalidateQueries(['fetchSloList']);
},
+ onError: (error, { slo: { name } }) => {
+ toasts.addError(new Error(String(error)), {
+ title: i18n.translate('xpack.observability.slo.create.errorNotification', {
+ defaultMessage: 'Something went wrong while creating {name}',
+ values: { name },
+ }),
+ });
+ },
+ onMutate: async ({ slo }) => {
+ // Cancel any outgoing refetches (so they don't overwrite our optimistic update)
+ await queryClient.cancelQueries(['fetchSloList']);
+
+ const latestFetchSloListRequest = (
+ queryClient.getQueriesData(['fetchSloList']) || []
+ ).at(0);
+
+ const [queryKey, data] = latestFetchSloListRequest || [];
+
+ const newItem = { ...slo, id: uuidv1() };
+
+ const optimisticUpdate = {
+ ...data,
+ results: [...(data?.results || []), { ...newItem }],
+ total: data?.total ? data.total + 1 : 1,
+ };
+
+ // Optimistically update to the new value
+ if (queryKey) {
+ queryClient.setQueryData(queryKey, optimisticUpdate);
+ }
+
+ // Return a context object with the snapshotted value
+ return { previousSloList: data };
+ },
}
);
-
- return createSlo;
}
diff --git a/x-pack/plugins/observability/public/hooks/slo/use_delete_slo.ts b/x-pack/plugins/observability/public/hooks/slo/use_delete_slo.ts
index f0af48585a26a..2878b3e158684 100644
--- a/x-pack/plugins/observability/public/hooks/slo/use_delete_slo.ts
+++ b/x-pack/plugins/observability/public/hooks/slo/use_delete_slo.ts
@@ -6,21 +6,24 @@
*/
import { useMutation, useQueryClient } from '@tanstack/react-query';
+import { i18n } from '@kbn/i18n';
import { FindSLOResponse } from '@kbn/slo-schema';
-
import { useKibana } from '../../utils/kibana_react';
-export function useDeleteSlo(sloId: string) {
- const { http } = useKibana().services;
+export function useDeleteSlo() {
+ const {
+ http,
+ notifications: { toasts },
+ } = useKibana().services;
const queryClient = useQueryClient();
const deleteSlo = useMutation<
string,
string,
- { id: string },
+ { id: string; name: string },
{ previousSloList: FindSLOResponse | undefined }
>(
- ['deleteSlo', sloId],
+ ['deleteSlo'],
({ id }) => {
try {
return http.delete(`/api/observability/slos/${id}`);
@@ -50,16 +53,30 @@ export function useDeleteSlo(sloId: string) {
queryClient.setQueryData(queryKey, optimisticUpdate);
}
+ toasts.addSuccess(
+ i18n.translate('xpack.observability.slo.slo.delete.successNotification', {
+ defaultMessage: 'Deleted {name}',
+ values: { name: slo.name },
+ })
+ );
+
// Return a context object with the snapshotted value
return { previousSloList: data };
},
// If the mutation fails, use the context returned from onMutate to roll back
- onError: (_err, _slo, context) => {
+ onError: (_err, slo, context) => {
if (context?.previousSloList) {
queryClient.setQueryData(['fetchSloList'], context.previousSloList);
}
+
+ toasts.addDanger(
+ i18n.translate('xpack.observability.slo.slo.delete.errorNotification', {
+ defaultMessage: 'Failed to delete {name}',
+ values: { name: slo.name },
+ })
+ );
},
- onSuccess: () => {
+ onSuccess: (_success, slo) => {
queryClient.invalidateQueries(['fetchSloList']);
},
}
diff --git a/x-pack/plugins/observability/public/hooks/slo/use_fetch_rules_for_slo.ts b/x-pack/plugins/observability/public/hooks/slo/use_fetch_rules_for_slo.ts
index 153e2fb95c966..39ea6fdf994e9 100644
--- a/x-pack/plugins/observability/public/hooks/slo/use_fetch_rules_for_slo.ts
+++ b/x-pack/plugins/observability/public/hooks/slo/use_fetch_rules_for_slo.ts
@@ -77,8 +77,9 @@ export function useFetchRulesForSlo({ sloIds }: Params): UseFetchRulesForSloResp
// ignore error for retrieving slos
}
},
- enabled: Boolean(sloIds),
+ enabled: Boolean(sloIds?.length),
refetchOnWindowFocus: false,
+ keepPreviousData: true,
}
);
diff --git a/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts b/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts
index fa9c2761e94d8..086ec0d8e77a9 100644
--- a/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts
+++ b/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts
@@ -28,6 +28,7 @@ interface SLOListParams {
export interface UseFetchSloListResponse {
isInitialLoading: boolean;
isLoading: boolean;
+ isRefetching: boolean;
isSuccess: boolean;
isError: boolean;
sloList: FindSLOResponse | undefined;
@@ -96,14 +97,19 @@ export function useFetchSloList({
queryClient.invalidateQueries(['fetchActiveAlerts'], {
exact: false,
});
+
+ queryClient.invalidateQueries(['fetchRulesForSlo'], {
+ exact: false,
+ });
},
}
);
return {
sloList: data,
- isLoading: isLoading || isRefetching,
isInitialLoading,
+ isLoading,
+ isRefetching,
isSuccess,
isError,
refetch,
diff --git a/x-pack/plugins/observability/public/hooks/slo/use_update_slo.ts b/x-pack/plugins/observability/public/hooks/slo/use_update_slo.ts
index 46d3d2eddea76..5fc25f4192756 100644
--- a/x-pack/plugins/observability/public/hooks/slo/use_update_slo.ts
+++ b/x-pack/plugins/observability/public/hooks/slo/use_update_slo.ts
@@ -6,27 +6,43 @@
*/
import { useMutation, useQueryClient } from '@tanstack/react-query';
+import { i18n } from '@kbn/i18n';
import type { UpdateSLOInput, UpdateSLOResponse } from '@kbn/slo-schema';
import { useKibana } from '../../utils/kibana_react';
export function useUpdateSlo() {
- const { http } = useKibana().services;
+ const {
+ http,
+ notifications: { toasts },
+ } = useKibana().services;
const queryClient = useQueryClient();
- const updateSlo = useMutation(
+ return useMutation(
({ sloId, slo }: { sloId: string; slo: UpdateSLOInput }) => {
const body = JSON.stringify(slo);
return http.put(`/api/observability/slos/${sloId}`, { body });
},
{
mutationKey: ['updateSlo'],
- onSuccess: () => {
+ onSuccess: (_data, { slo: { name } }) => {
+ toasts.addSuccess(
+ i18n.translate('xpack.observability.slo.update.successNotification', {
+ defaultMessage: 'Successfully updated {name}',
+ values: { name },
+ })
+ );
queryClient.invalidateQueries(['fetchSloList']);
queryClient.invalidateQueries(['fetchHistoricalSummary']);
},
+ onError: (error, { slo: { name } }) => {
+ toasts.addError(new Error(String(error)), {
+ title: i18n.translate('xpack.observability.slo.update.errorNotification', {
+ defaultMessage: 'Something went wrong when updating {name}',
+ values: { name },
+ }),
+ });
+ },
}
);
-
- return updateSlo;
}
diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/header_control.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/header_control.tsx
index 99ce1ba03f9b6..80ae36cad8439 100644
--- a/x-pack/plugins/observability/public/pages/slo_details/components/header_control.tsx
+++ b/x-pack/plugins/observability/public/pages/slo_details/components/header_control.tsx
@@ -5,19 +5,25 @@
* 2.0.
*/
-import { EuiButton, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui';
-import React, { useState } from 'react';
+import React, { useCallback, useState } from 'react';
import { i18n } from '@kbn/i18n';
+import { EuiButton, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui';
import { SLOWithSummaryResponse } from '@kbn/slo-schema';
+import { useCapabilities } from '../../../hooks/slo/use_capabilities';
+import { useKibana } from '../../../utils/kibana_react';
+import { useCloneSlo } from '../../../hooks/slo/use_clone_slo';
+import { useDeleteSlo } from '../../../hooks/slo/use_delete_slo';
import { isApmIndicatorType } from '../../../utils/slo/indicator';
import { convertSliApmParamsToApmAppDeeplinkUrl } from '../../../utils/slo/convert_sli_apm_params_to_apm_app_deeplink_url';
import { SLO_BURN_RATE_RULE_ID } from '../../../../common/constants';
import { sloFeatureId } from '../../../../common';
import { paths } from '../../../config/paths';
-import { useKibana } from '../../../utils/kibana_react';
-import { ObservabilityAppServices } from '../../../application/types';
-import { useCapabilities } from '../../../hooks/slo/use_capabilities';
+import {
+ transformSloResponseToCreateSloInput,
+ transformValuesToCreateSLOInput,
+} from '../../slo_edit/helpers/process_slo_form_values';
+import { SloDeleteConfirmationModal } from '../../slos/components/slo_delete_confirmation_modal';
export interface Props {
slo: SLOWithSummaryResponse | undefined;
@@ -29,17 +35,22 @@ export function HeaderControl({ isLoading, slo }: Props) {
application: { navigateToUrl },
http: { basePath },
triggersActionsUi: { getAddRuleFlyout: AddRuleFlyout },
- } = useKibana().services;
+ } = useKibana().services;
const { hasWriteCapabilities } = useCapabilities();
+
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const [isRuleFlyoutVisible, setRuleFlyoutVisibility] = useState(false);
+ const [isDeleteConfirmationModalOpen, setDeleteConfirmationModalOpen] = useState(false);
+
+ const { mutate: cloneSlo } = useCloneSlo();
+ const { mutate: deleteSlo } = useDeleteSlo();
const handleActionsClick = () => setIsPopoverOpen((value) => !value);
const closePopover = () => setIsPopoverOpen(false);
const handleEdit = () => {
if (slo) {
- navigateToUrl(basePath.prepend(paths.observability.sloEdit(slo.id)));
+ navigate(basePath.prepend(paths.observability.sloEdit(slo.id)));
}
};
@@ -81,10 +92,45 @@ export function HeaderControl({ isLoading, slo }: Props) {
transactionType,
});
- navigateToUrl(basePath.prepend(url));
+ navigate(basePath.prepend(url));
+ }
+ };
+
+ const handleClone = async () => {
+ if (slo) {
+ setIsPopoverOpen(false);
+
+ const newSlo = transformValuesToCreateSLOInput(
+ transformSloResponseToCreateSloInput({ ...slo, name: `[Copy] ${slo.name}` })!
+ );
+
+ cloneSlo({ slo: newSlo, idToCopyFrom: slo.id });
+
+ navigate(basePath.prepend(paths.observability.slos));
+ }
+ };
+
+ const handleDelete = () => {
+ setDeleteConfirmationModalOpen(true);
+ setIsPopoverOpen(false);
+ };
+
+ const handleDeleteCancel = () => {
+ setDeleteConfirmationModalOpen(false);
+ };
+
+ const handleDeleteConfirm = async () => {
+ if (slo) {
+ deleteSlo({ id: slo.id, name: slo.name });
+ navigate(basePath.prepend(paths.observability.slos));
}
};
+ const navigate = useCallback(
+ (url: string) => setTimeout(() => navigateToUrl(url)),
+ [navigateToUrl]
+ );
+
return (
<>
@@ -123,6 +170,7 @@ export function HeaderControl({ isLoading, slo }: Props) {
@@ -136,6 +184,7 @@ export function HeaderControl({ isLoading, slo }: Props) {
@@ -143,24 +192,50 @@ export function HeaderControl({ isLoading, slo }: Props) {
defaultMessage: 'Manage rules',
})}
,
- ].concat(
- !!slo && isApmIndicatorType(slo.indicator.type)
- ? [
-
- {i18n.translate(
- 'xpack.observability.slos.sloDetails.headerControl.exploreInApm',
- {
- defaultMessage: 'Explore in APM',
- }
- )}
- ,
- ]
- : []
- )}
+ ]
+ .concat(
+ !!slo && isApmIndicatorType(slo.indicator.type) ? (
+
+ {i18n.translate(
+ 'xpack.observability.slos.sloDetails.headerControl.exploreInApm',
+ {
+ defaultMessage: 'Service details',
+ }
+ )}
+
+ ) : (
+ []
+ )
+ )
+ .concat(
+
+ {i18n.translate('xpack.observability.slo.slo.item.actions.clone', {
+ defaultMessage: 'Clone',
+ })}
+ ,
+
+ {i18n.translate('xpack.observability.slo.slo.item.actions.delete', {
+ defaultMessage: 'Delete',
+ })}
+
+ )}
/>
@@ -173,6 +248,14 @@ export function HeaderControl({ isLoading, slo }: Props) {
initialValues={{ name: `${slo.name} burn rate`, params: { sloId: slo.id } }}
/>
) : null}
+
+ {slo && isDeleteConfirmationModalOpen ? (
+
+ ) : null}
>
);
}
diff --git a/x-pack/plugins/observability/public/pages/slo_details/slo_details.test.tsx b/x-pack/plugins/observability/public/pages/slo_details/slo_details.test.tsx
index 4028b80203591..0e05f61bf195b 100644
--- a/x-pack/plugins/observability/public/pages/slo_details/slo_details.test.tsx
+++ b/x-pack/plugins/observability/public/pages/slo_details/slo_details.test.tsx
@@ -6,19 +6,21 @@
*/
import React from 'react';
-import { fireEvent, screen } from '@testing-library/react';
+import { fireEvent, screen, waitFor } from '@testing-library/react';
import { useKibana } from '../../utils/kibana_react';
import { useParams } from 'react-router-dom';
import { useLicense } from '../../hooks/use_license';
+import { useCapabilities } from '../../hooks/slo/use_capabilities';
import { useFetchSloDetails } from '../../hooks/slo/use_fetch_slo_details';
+import { useFetchHistoricalSummary } from '../../hooks/slo/use_fetch_historical_summary';
+import { useFetchActiveAlerts } from '../../hooks/slo/use_fetch_active_alerts';
+import { useCloneSlo } from '../../hooks/slo/use_clone_slo';
+import { useDeleteSlo } from '../../hooks/slo/use_delete_slo';
import { render } from '../../utils/test_helper';
import { SloDetailsPage } from './slo_details';
import { buildSlo } from '../../data/slo/slo';
import { paths } from '../../config/paths';
-import { useFetchHistoricalSummary } from '../../hooks/slo/use_fetch_historical_summary';
-import { useCapabilities } from '../../hooks/slo/use_capabilities';
-import { useFetchActiveAlerts } from '../../hooks/slo/use_fetch_active_alerts';
import {
HEALTHY_STEP_DOWN_ROLLING_SLO,
historicalSummaryData,
@@ -34,21 +36,26 @@ jest.mock('react-router-dom', () => ({
jest.mock('../../utils/kibana_react');
jest.mock('../../hooks/use_breadcrumbs');
jest.mock('../../hooks/use_license');
+jest.mock('../../hooks/slo/use_capabilities');
jest.mock('../../hooks/slo/use_fetch_active_alerts');
jest.mock('../../hooks/slo/use_fetch_slo_details');
jest.mock('../../hooks/slo/use_fetch_historical_summary');
-jest.mock('../../hooks/slo/use_capabilities');
+jest.mock('../../hooks/slo/use_clone_slo');
+jest.mock('../../hooks/slo/use_delete_slo');
const useKibanaMock = useKibana as jest.Mock;
const useParamsMock = useParams as jest.Mock;
const useLicenseMock = useLicense as jest.Mock;
+const useCapabilitiesMock = useCapabilities as jest.Mock;
const useFetchActiveAlertsMock = useFetchActiveAlerts as jest.Mock;
const useFetchSloDetailsMock = useFetchSloDetails as jest.Mock;
const useFetchHistoricalSummaryMock = useFetchHistoricalSummary as jest.Mock;
-const useCapabilitiesMock = useCapabilities as jest.Mock;
+const useCloneSloMock = useCloneSlo as jest.Mock;
+const useDeleteSloMock = useDeleteSlo as jest.Mock;
const mockNavigate = jest.fn();
-const mockBasePathPrepend = jest.fn();
+const mockClone = jest.fn();
+const mockDelete = jest.fn();
const mockKibana = () => {
useKibanaMock.mockReturnValue({
@@ -57,7 +64,14 @@ const mockKibana = () => {
charts: chartPluginMock.createStartContract(),
http: {
basePath: {
- prepend: mockBasePathPrepend,
+ prepend: (url: string) => url,
+ },
+ },
+ notifications: {
+ toasts: {
+ addSuccess: jest.fn(),
+ addDanger: jest.fn(),
+ addError: jest.fn(),
},
},
triggersActionsUi: {
@@ -86,6 +100,8 @@ describe('SLO Details Page', () => {
sloHistoricalSummaryResponse: historicalSummaryData,
});
useFetchActiveAlertsMock.mockReturnValue({ isLoading: false, data: {} });
+ useCloneSloMock.mockReturnValue({ mutate: mockClone });
+ useDeleteSloMock.mockReturnValue({ mutate: mockDelete });
});
describe('when the incorrect license is found', () => {
@@ -97,7 +113,7 @@ describe('SLO Details Page', () => {
render();
- expect(mockNavigate).toBeCalledWith(mockBasePathPrepend(paths.observability.slos));
+ expect(mockNavigate).toBeCalledWith(paths.observability.slos);
});
});
@@ -182,6 +198,79 @@ describe('SLO Details Page', () => {
expect(screen.queryByTestId('sloDetailsHeaderControlPopoverCreateRule')).toBeTruthy();
});
+ it("renders a 'Manage rules' button under actions menu", async () => {
+ const slo = buildSlo();
+ useParamsMock.mockReturnValue(slo.id);
+ useFetchSloDetailsMock.mockReturnValue({ isLoading: false, slo });
+ useLicenseMock.mockReturnValue({ hasAtLeast: () => true });
+
+ render();
+
+ fireEvent.click(screen.getByTestId('o11yHeaderControlActionsButton'));
+ expect(screen.queryByTestId('sloDetailsHeaderControlPopoverManageRules')).toBeTruthy();
+ });
+
+ it("renders a 'Clone' button under actions menu", async () => {
+ const slo = buildSlo();
+ useParamsMock.mockReturnValue(slo.id);
+ useFetchSloDetailsMock.mockReturnValue({ isLoading: false, slo });
+ useLicenseMock.mockReturnValue({ hasAtLeast: () => true });
+
+ render();
+
+ fireEvent.click(screen.getByTestId('o11yHeaderControlActionsButton'));
+
+ const button = screen.queryByTestId('sloDetailsHeaderControlPopoverClone');
+
+ expect(button).toBeTruthy();
+
+ fireEvent.click(button!);
+
+ const { id, createdAt, enabled, revision, summary, updatedAt, ...newSlo } = slo;
+
+ expect(mockClone).toBeCalledWith({
+ idToCopyFrom: slo.id,
+ slo: {
+ ...newSlo,
+ name: `[Copy] ${newSlo.name}`,
+ },
+ });
+
+ await waitFor(() => {
+ expect(mockNavigate).toBeCalledWith(paths.observability.slos);
+ });
+ });
+
+ it("renders a 'Delete' button under actions menu", async () => {
+ const slo = buildSlo();
+ useParamsMock.mockReturnValue(slo.id);
+ useFetchSloDetailsMock.mockReturnValue({ isLoading: false, slo });
+ useLicenseMock.mockReturnValue({ hasAtLeast: () => true });
+
+ render();
+
+ fireEvent.click(screen.getByTestId('o11yHeaderControlActionsButton'));
+
+ const button = screen.queryByTestId('sloDetailsHeaderControlPopoverDelete');
+
+ expect(button).toBeTruthy();
+
+ fireEvent.click(button!);
+
+ const deleteModalConfirmButton = screen.queryByTestId('confirmModalConfirmButton');
+
+ fireEvent.click(deleteModalConfirmButton!);
+
+ expect(mockDelete).toBeCalledWith({
+ id: slo.id,
+ name: slo.name,
+ });
+
+ await waitFor(() => {
+ expect(mockNavigate).toBeCalledWith(paths.observability.slos);
+ });
+ });
+
it('renders the Overview tab by default', async () => {
const slo = buildSlo();
useParamsMock.mockReturnValue(slo.id);
diff --git a/x-pack/plugins/observability/public/pages/slo_details/slo_details.tsx b/x-pack/plugins/observability/public/pages/slo_details/slo_details.tsx
index 48401ca29f94e..29a7f573e3c14 100644
--- a/x-pack/plugins/observability/public/pages/slo_details/slo_details.tsx
+++ b/x-pack/plugins/observability/public/pages/slo_details/slo_details.tsx
@@ -7,6 +7,7 @@
import React, { useState } from 'react';
import { useParams } from 'react-router-dom';
+import { useIsMutating } from '@tanstack/react-query';
import { EuiBreadcrumbProps } from '@elastic/eui/src/components/breadcrumbs/breadcrumb';
import { EuiLoadingSpinner } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
@@ -44,6 +45,8 @@ export function SloDetailsPage() {
const { isLoading, slo } = useFetchSloDetails({ sloId, shouldRefetch: isAutoRefreshing });
+ const isCloningOrDeleting = Boolean(useIsMutating());
+
useBreadcrumbs(getBreadcrumbs(basePath, slo));
const isSloNotFound = !isLoading && slo === undefined;
@@ -55,6 +58,8 @@ export function SloDetailsPage() {
navigateToUrl(basePath.prepend(paths.observability.slos));
}
+ const isPerformingAction = isLoading || isCloningOrDeleting;
+
const handleToggleAutoRefresh = () => {
setIsAutoRefreshing(!isAutoRefreshing);
};
@@ -62,15 +67,15 @@ export function SloDetailsPage() {
return (
,
+ pageTitle: ,
rightSideItems: [
- ,
+ ,
,
- ,
+ ,
],
bottomBorder: false,
}}
diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx
index 195bc7933a74d..9331c648bbd8c 100644
--- a/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx
+++ b/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import React, { useEffect, useState } from 'react';
+import React, { useCallback, useEffect, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { useLocation, useHistory } from 'react-router-dom';
import {
@@ -51,7 +51,6 @@ export function SloEditForm({ slo }: Props) {
const {
application: { navigateToUrl },
http: { basePath },
- notifications: { toasts },
triggersActionsUi: { getAddRuleFlyout: AddRuleFlyout },
} = useKibana().services;
@@ -121,64 +120,39 @@ export function SloEditForm({ slo }: Props) {
const values = getValues();
if (isEditMode) {
- try {
- const processedValues = transformValuesToUpdateSLOInput(values);
+ const processedValues = transformValuesToUpdateSLOInput(values);
+ if (isCreateRuleCheckboxChecked) {
await updateSlo({ sloId: slo.id, slo: processedValues });
-
- toasts.addSuccess(
- i18n.translate('xpack.observability.slo.sloEdit.update.success', {
- defaultMessage: 'Successfully updated {name}',
- values: { name: getValues().name },
- })
+ navigate(
+ basePath.prepend(
+ `${paths.observability.sloEdit(slo.id)}?${CREATE_RULE_SEARCH_PARAM}=true`
+ )
);
-
- if (isCreateRuleCheckboxChecked) {
- navigateToUrl(
- basePath.prepend(
- `${paths.observability.sloEdit(slo.id)}?${CREATE_RULE_SEARCH_PARAM}=true`
- )
- );
- } else {
- navigateToUrl(basePath.prepend(paths.observability.slos));
- }
- } catch (error) {
- toasts.addError(new Error(error), {
- title: i18n.translate('xpack.observability.slo.sloEdit.creation.error', {
- defaultMessage: 'Something went wrong',
- }),
- });
+ } else {
+ updateSlo({ sloId: slo.id, slo: processedValues });
+ navigate(basePath.prepend(paths.observability.slos));
}
} else {
- try {
- const processedValues = transformValuesToCreateSLOInput(values);
+ const processedValues = transformValuesToCreateSLOInput(values);
+ if (isCreateRuleCheckboxChecked) {
const { id } = await createSlo({ slo: processedValues });
-
- toasts.addSuccess(
- i18n.translate('xpack.observability.slo.sloEdit.creation.success', {
- defaultMessage: 'Successfully created {name}',
- values: { name: getValues().name },
- })
+ navigate(
+ basePath.prepend(`${paths.observability.sloEdit(id)}?${CREATE_RULE_SEARCH_PARAM}=true`)
);
-
- if (isCreateRuleCheckboxChecked) {
- navigateToUrl(
- basePath.prepend(`${paths.observability.sloEdit(id)}?${CREATE_RULE_SEARCH_PARAM}=true`)
- );
- } else {
- navigateToUrl(basePath.prepend(paths.observability.slos));
- }
- } catch (error) {
- toasts.addError(new Error(error), {
- title: i18n.translate('xpack.observability.slo.sloEdit.creation.error', {
- defaultMessage: 'Something went wrong',
- }),
- });
+ } else {
+ createSlo({ slo: processedValues });
+ navigate(basePath.prepend(paths.observability.slos));
}
}
};
+ const navigate = useCallback(
+ (url: string) => setTimeout(() => navigateToUrl(url)),
+ [navigateToUrl]
+ );
+
const handleChangeCheckbox = () => {
setIsCreateRuleCheckboxChecked(!isCreateRuleCheckboxChecked);
};
diff --git a/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.test.tsx b/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.test.tsx
index 73b386e54ca16..da327bc22b492 100644
--- a/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.test.tsx
+++ b/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.test.tsx
@@ -539,46 +539,6 @@ describe('SLO Edit Page', () => {
});
describe('when submitting has completed successfully', () => {
- it('renders a success toast', async () => {
- const slo = buildSlo();
-
- jest.spyOn(Router, 'useParams').mockReturnValue({ sloId: '123' });
- jest
- .spyOn(Router, 'useLocation')
- .mockReturnValue({ pathname: 'foo', search: '', state: '', hash: '' });
-
- useFetchSloMock.mockReturnValue({ isLoading: false, slo });
-
- useFetchIndicesMock.mockReturnValue({
- isLoading: false,
- indices: [{ name: 'some-index' }],
- });
-
- useCreateSloMock.mockReturnValue({
- mutateAsync: jest.fn().mockResolvedValue('success'),
- isLoading: false,
- isSuccess: false,
- isError: false,
- });
-
- useUpdateSloMock.mockReturnValue({
- mutateAsync: jest.fn().mockResolvedValue('success'),
- isLoading: false,
- isSuccess: false,
- isError: false,
- });
-
- render();
-
- expect(screen.queryByTestId('sloFormSubmitButton')).toBeEnabled();
-
- await waitFor(() => {
- fireEvent.click(screen.getByTestId('sloFormSubmitButton'));
- });
-
- expect(mockAddSuccess).toBeCalled();
- });
-
it('navigates to the SLO List page when checkbox to create new rule is not checked', async () => {
const slo = buildSlo();
@@ -615,8 +575,9 @@ describe('SLO Edit Page', () => {
await waitFor(() => {
fireEvent.click(screen.getByTestId('sloFormSubmitButton'));
});
-
- expect(mockNavigate).toBeCalledWith(mockBasePathPrepend(paths.observability.slos));
+ await waitFor(() => {
+ expect(mockNavigate).toBeCalledWith(mockBasePathPrepend(paths.observability.slos));
+ });
});
it('navigates to the SLO Edit page when checkbox to create new rule is checked', async () => {
@@ -657,9 +618,11 @@ describe('SLO Edit Page', () => {
fireEvent.click(screen.getByTestId('sloFormSubmitButton'));
});
- expect(mockNavigate).toBeCalledWith(
- mockBasePathPrepend(`${paths.observability.sloEdit(slo.id)}?create-rule=true`)
- );
+ await waitFor(() => {
+ expect(mockNavigate).toBeCalledWith(
+ mockBasePathPrepend(`${paths.observability.sloEdit(slo.id)}?create-rule=true`)
+ );
+ });
});
it('opens the Add Rule Flyout when visiting an existing SLO with search params set', async () => {
@@ -698,47 +661,5 @@ describe('SLO Edit Page', () => {
});
});
});
-
- describe('when submitting has not completed successfully', () => {
- it('renders an error toast', async () => {
- const slo = buildSlo();
-
- jest.spyOn(Router, 'useParams').mockReturnValue({ sloId: '123' });
- jest
- .spyOn(Router, 'useLocation')
- .mockReturnValue({ pathname: 'foo', search: '', state: '', hash: '' });
-
- useFetchSloMock.mockReturnValue({ isLoading: false, slo });
-
- useFetchIndicesMock.mockReturnValue({
- isLoading: false,
- indices: [{ name: 'some-index' }],
- });
-
- useCreateSloMock.mockReturnValue({
- mutateAsync: jest.fn().mockRejectedValue('argh, I died'),
- isLoading: false,
- isSuccess: false,
- isError: false,
- });
-
- useUpdateSloMock.mockReturnValue({
- mutateAsync: jest.fn().mockRejectedValue('argh, I died'),
- isLoading: false,
- isSuccess: false,
- isError: false,
- });
-
- render();
-
- expect(screen.queryByTestId('sloFormSubmitButton')).toBeEnabled();
-
- await waitFor(() => {
- fireEvent.click(screen.getByTestId('sloFormSubmitButton'));
- });
-
- expect(mockAddError).toBeCalled();
- });
- });
});
});
diff --git a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx
index 89d5eadf4d9ad..380d1a898bf0c 100644
--- a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx
+++ b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx
@@ -6,7 +6,7 @@
*/
import React from 'react';
-import { EuiFlexGroup } from '@elastic/eui';
+import { EuiFlexGroup, EuiSkeletonRectangle } from '@elastic/eui';
import { SLOWithSummaryResponse } from '@kbn/slo-schema';
import { Rule } from '@kbn/triggers-actions-ui-plugin/public';
@@ -14,25 +14,54 @@ import { SloIndicatorTypeBadge } from './slo_indicator_type_badge';
import { SloStatusBadge } from '../../../../components/slo/slo_status_badge';
import { SloActiveAlertsBadge } from '../../../../components/slo/slo_status_badge/slo_active_alerts_badge';
import { SloTimeWindowBadge } from './slo_time_window_badge';
+import { SloRulesBadge } from './slo_rules_badge';
import type { ActiveAlerts } from '../../../../hooks/slo/use_fetch_active_alerts';
import type { SloRule } from '../../../../hooks/slo/use_fetch_rules_for_slo';
-import { SloRulesBadge } from './slo_rules_badge';
export interface Props {
activeAlerts?: ActiveAlerts;
+ isLoading: boolean;
rules: Array> | undefined;
slo: SLOWithSummaryResponse;
onClickRuleBadge: () => void;
}
-export function SloBadges({ activeAlerts, rules, slo, onClickRuleBadge }: Props) {
+export function SloBadges({ activeAlerts, isLoading, rules, slo, onClickRuleBadge }: Props) {
return (
-
-
-
-
-
+ {isLoading ? (
+ <>
+
+
+
+ >
+ ) : (
+ <>
+
+
+
+
+
+ >
+ )}
);
}
diff --git a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_indicator_type_badge.tsx b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_indicator_type_badge.tsx
index 87f005e2fa5c0..ad73af3d73bfa 100644
--- a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_indicator_type_badge.tsx
+++ b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_indicator_type_badge.tsx
@@ -63,7 +63,7 @@ export function SloIndicatorTypeBadge({ slo }: Props) {
@@ -73,7 +73,7 @@ export function SloIndicatorTypeBadge({ slo }: Props) {
onClickAriaLabel={i18n.translate(
'xpack.observability.slo.indicatorTypeBadge.exploreInApm',
{
- defaultMessage: 'Explore {service} in APM',
+ defaultMessage: 'View {service} details',
values: { service: slo.indicator.params.service },
}
)}
diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_delete_confirmation_modal.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_delete_confirmation_modal.tsx
index 4a292bef8c14b..e132661aa16ad 100644
--- a/x-pack/plugins/observability/public/pages/slos/components/slo_delete_confirmation_modal.tsx
+++ b/x-pack/plugins/observability/public/pages/slos/components/slo_delete_confirmation_modal.tsx
@@ -5,91 +5,49 @@
* 2.0.
*/
+import React from 'react';
import { EuiConfirmModal } from '@elastic/eui';
-import React, { useState } from 'react';
import { i18n } from '@kbn/i18n';
import type { SLOWithSummaryResponse } from '@kbn/slo-schema';
-import { useKibana } from '../../../utils/kibana_react';
-import { useDeleteSlo } from '../../../hooks/slo/use_delete_slo';
export interface SloDeleteConfirmationModalProps {
slo: SLOWithSummaryResponse;
onCancel: () => void;
+ onConfirm: () => void;
}
export function SloDeleteConfirmationModal({
- slo: { id, name },
+ slo: { name },
onCancel,
+ onConfirm,
}: SloDeleteConfirmationModalProps) {
- const {
- notifications: { toasts },
- } = useKibana().services;
-
- const [isVisible, setIsVisible] = useState(true);
-
- const { mutate: deleteSlo, isSuccess, isError } = useDeleteSlo(id);
-
- if (isSuccess) {
- toasts.addSuccess(getDeleteSuccesfulMessage(name));
- }
-
- if (isError) {
- toasts.addDanger(getDeleteFailMessage(name));
- }
-
- const handleConfirm = () => {
- setIsVisible(false);
- deleteSlo({ id });
- };
-
- return isVisible ? (
+ return (
{i18n.translate('xpack.observability.slo.slo.deleteConfirmationModal.descriptionText', {
defaultMessage: "You can't recover {name} after deleting.",
values: { name },
})}
- ) : null;
-}
-
-const getTitle = () =>
- i18n.translate('xpack.observability.slo.slo.deleteConfirmationModal.title', {
- defaultMessage: 'Are you sure?',
- });
-
-const getCancelButtonText = () =>
- i18n.translate('xpack.observability.slo.slo.deleteConfirmationModal.cancelButtonLabel', {
- defaultMessage: 'Cancel',
- });
-
-const getConfirmButtonText = (name: string) =>
- i18n.translate('xpack.observability.slo.slo.deleteConfirmationModal.deleteButtonLabel', {
- defaultMessage: 'Delete {name}',
- values: { name },
- });
-
-const getDeleteSuccesfulMessage = (name: string) =>
- i18n.translate(
- 'xpack.observability.slo.slo.deleteConfirmationModal.successNotification.descriptionText',
- {
- defaultMessage: 'Deleted {name}',
- values: { name },
- }
- );
-
-const getDeleteFailMessage = (name: string) =>
- i18n.translate(
- 'xpack.observability.slo.slo.deleteConfirmationModal.errorNotification.descriptionText',
- {
- defaultMessage: 'Failed to delete {name}',
- values: { name },
- }
);
+}
diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx
index e71a2cd98a0e6..0efab27b0ebdf 100644
--- a/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx
+++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx
@@ -29,7 +29,7 @@ export function SloList({ autoRefresh }: Props) {
const [sort, setSort] = useState('creationTime');
const [indicatorTypeFilter, setIndicatorTypeFilter] = useState([]);
- const { isLoading, isError, sloList, refetch } = useFetchSloList({
+ const { isInitialLoading, isLoading, isRefetching, isError, sloList, refetch } = useFetchSloList({
page: activePage + 1,
name: query,
sortBy: sort,
@@ -69,7 +69,15 @@ export function SloList({ autoRefresh }: Props) {
-
+
{results.length ? (
diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx
index 049481486eb9e..debc9a2136717 100644
--- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx
+++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx
@@ -6,7 +6,7 @@
*/
import React, { useState } from 'react';
-import { useIsMutating, useQueryClient } from '@tanstack/react-query';
+import { useQueryClient } from '@tanstack/react-query';
import {
EuiButtonIcon,
EuiContextMenuItem,
@@ -45,6 +45,7 @@ export interface SloListItemProps {
historicalSummary?: HistoricalSummaryResponse[];
historicalSummaryLoading: boolean;
activeAlerts?: ActiveAlerts;
+ onConfirmDelete: (slo: SLOWithSummaryResponse) => void;
}
export function SloListItem({
@@ -53,6 +54,7 @@ export function SloListItem({
historicalSummary = [],
historicalSummaryLoading,
activeAlerts,
+ onConfirmDelete,
}: SloListItemProps) {
const {
application: { navigateToUrl },
@@ -65,7 +67,6 @@ export function SloListItem({
const filteredRuleTypes = useGetFilteredRuleTypes();
const { mutate: cloneSlo } = useCloneSlo();
- const isDeletingSlo = Boolean(useIsMutating(['deleteSlo', slo.id]));
const [isActionsPopoverOpen, setIsActionsPopoverOpen] = useState(false);
const [isAddRuleFlyoutOpen, setIsAddRuleFlyoutOpen] = useState(false);
@@ -114,21 +115,17 @@ export function SloListItem({
setIsActionsPopoverOpen(false);
};
+ const handleDeleteConfirm = () => {
+ setDeleteConfirmationModalOpen(false);
+ onConfirmDelete(slo);
+ };
+
const handleDeleteCancel = () => {
setDeleteConfirmationModalOpen(false);
};
return (
-
+
{/* CONTENT */}
@@ -137,13 +134,18 @@ export function SloListItem({
-
- {slo.name}
-
+ {slo.summary ? (
+
+ {slo.name}
+
+ ) : (
+ {slo.name}
+ )}
-
+ {slo.summary ? (
+
+ ) : null}
@@ -175,12 +179,12 @@ export function SloListItem({
onClick={handleClickActions}
/>
}
- panelPaddingSize="none"
+ panelPaddingSize="m"
closePopover={handleClickActions}
isOpen={isActionsPopoverOpen}
>
{i18n.translate('xpack.observability.slo.slo.item.actions.createRule', {
- defaultMessage: 'Create new Alert rule',
+ defaultMessage: 'Create new alert rule',
})}
,
+
) : null}
);
diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.tsx
index 2f6f71612788e..e70f8139fe9f5 100644
--- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.tsx
+++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.tsx
@@ -11,6 +11,7 @@ import type { SLOWithSummaryResponse } from '@kbn/slo-schema';
import { useFetchActiveAlerts } from '../../../hooks/slo/use_fetch_active_alerts';
import { useFetchRulesForSlo } from '../../../hooks/slo/use_fetch_rules_for_slo';
import { useFetchHistoricalSummary } from '../../../hooks/slo/use_fetch_historical_summary';
+import { useDeleteSlo } from '../../../hooks/slo/use_delete_slo';
import { SloListItem } from './slo_list_item';
import { SloListEmpty } from './slo_list_empty';
import { SloListError } from './slo_list_error';
@@ -29,6 +30,8 @@ export function SloListItems({ sloList, loading, error }: Props) {
const { isLoading: historicalSummaryLoading, sloHistoricalSummaryResponse } =
useFetchHistoricalSummary({ sloIds });
+ const { mutate: deleteSlo } = useDeleteSlo();
+
if (!loading && !error && sloList.length === 0) {
return ;
}
@@ -36,6 +39,10 @@ export function SloListItems({ sloList, loading, error }: Props) {
return ;
}
+ const handleDelete = (slo: SLOWithSummaryResponse) => {
+ deleteSlo({ id: slo.id, name: slo.name });
+ };
+
return (
{sloList.map((slo) => (
@@ -46,6 +53,7 @@ export function SloListItems({ sloList, loading, error }: Props) {
historicalSummary={sloHistoricalSummaryResponse?.[slo.id]}
historicalSummaryLoading={historicalSummaryLoading}
slo={slo}
+ onConfirmDelete={handleDelete}
/>
))}
diff --git a/x-pack/plugins/observability/public/pages/slos/slos.test.tsx b/x-pack/plugins/observability/public/pages/slos/slos.test.tsx
index a3e01f0b00458..15d450b1e0689 100644
--- a/x-pack/plugins/observability/public/pages/slos/slos.test.tsx
+++ b/x-pack/plugins/observability/public/pages/slos/slos.test.tsx
@@ -6,7 +6,7 @@
*/
import React from 'react';
-import { screen, act } from '@testing-library/react';
+import { screen, act, waitFor } from '@testing-library/react';
import { chartPluginMock } from '@kbn/charts-plugin/public/mocks';
import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl';
@@ -23,6 +23,7 @@ import { SlosPage } from './slos';
import { emptySloList, sloList } from '../../data/slo/slo';
import { historicalSummaryData } from '../../data/slo/historical_summary_data';
import { useCapabilities } from '../../hooks/slo/use_capabilities';
+import { paths } from '../../config/paths';
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
@@ -69,7 +70,7 @@ const mockKibana = () => {
charts: chartPluginMock.createSetupContract(),
http: {
basePath: {
- prepend: jest.fn(),
+ prepend: (url: string) => url,
},
},
notifications: {
@@ -98,17 +99,22 @@ describe('SLOs Page', () => {
});
describe('when the incorrect license is found', () => {
- it('renders the welcome prompt with subscription buttons', async () => {
+ beforeEach(() => {
useFetchSloListMock.mockReturnValue({ isLoading: false, sloList: emptySloList });
useLicenseMock.mockReturnValue({ hasAtLeast: () => false });
-
+ useFetchHistoricalSummaryMock.mockReturnValue({
+ isLoading: false,
+ sloHistoricalSummaryResponse: {},
+ });
+ });
+ it('navigates to the SLOs Welcome Page', async () => {
await act(async () => {
render();
});
- expect(screen.queryByTestId('slosPageWelcomePrompt')).toBeTruthy();
- expect(screen.queryByTestId('slosPageWelcomePromptSignupForCloudButton')).toBeTruthy();
- expect(screen.queryByTestId('slosPageWelcomePromptSignupForLicenseButton')).toBeTruthy();
+ await waitFor(() => {
+ expect(mockNavigate).toBeCalledWith(paths.observability.slosWelcome);
+ });
});
});
@@ -117,14 +123,20 @@ describe('SLOs Page', () => {
useLicenseMock.mockReturnValue({ hasAtLeast: () => true });
});
- it('renders the SLOs Welcome Prompt when the API has finished loading and there are no results', async () => {
+ it('navigates to the SLOs Welcome Page when the API has finished loading and there are no results', async () => {
useFetchSloListMock.mockReturnValue({ isLoading: false, sloList: emptySloList });
+ useFetchHistoricalSummaryMock.mockReturnValue({
+ isLoading: false,
+ sloHistoricalSummaryResponse: {},
+ });
await act(async () => {
render();
});
- expect(screen.queryByTestId('slosPageWelcomePrompt')).toBeTruthy();
+ await waitFor(() => {
+ expect(mockNavigate).toBeCalledWith(paths.observability.slosWelcome);
+ });
});
it('should have a create new SLO button', async () => {
@@ -198,7 +210,9 @@ describe('SLOs Page', () => {
button.click();
- expect(mockNavigate).toBeCalled();
+ expect(mockNavigate).toBeCalledWith(
+ `${paths.observability.sloEdit(sloList.results.at(0)?.id || '')}`
+ );
});
it('allows creating a new rule for an SLO', async () => {
@@ -250,7 +264,10 @@ describe('SLOs Page', () => {
screen.getByTestId('confirmModalConfirmButton').click();
- expect(mockDeleteSlo).toBeCalledWith({ id: sloList.results.at(0)?.id });
+ expect(mockDeleteSlo).toBeCalledWith({
+ id: sloList.results.at(0)?.id,
+ name: sloList.results.at(0)?.name,
+ });
});
it('allows cloning an SLO', async () => {
diff --git a/x-pack/plugins/observability/public/pages/slos/slos.tsx b/x-pack/plugins/observability/public/pages/slos/slos.tsx
index b81998d8452de..9b7df97e1e13c 100644
--- a/x-pack/plugins/observability/public/pages/slos/slos.tsx
+++ b/x-pack/plugins/observability/public/pages/slos/slos.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import React, { useState } from 'react';
+import React, { useEffect, useState } from 'react';
import { EuiButton } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
@@ -16,12 +16,11 @@ import { useBreadcrumbs } from '../../hooks/use_breadcrumbs';
import { useCapabilities } from '../../hooks/slo/use_capabilities';
import { useFetchSloList } from '../../hooks/slo/use_fetch_slo_list';
import { SloList } from './components/slo_list';
-import { SloListWelcomePrompt } from './components/slo_list_welcome_prompt';
import { AutoRefreshButton } from './components/auto_refresh_button';
-import { paths } from '../../config/paths';
-import type { ObservabilityAppServices } from '../../application/types';
import { HeaderTitle } from './components/header_title';
import { FeedbackButton } from '../../components/slo/feedback_button/feedback_button';
+import { paths } from '../../config/paths';
+import type { ObservabilityAppServices } from '../../application/types';
export function SlosPage() {
const {
@@ -55,14 +54,16 @@ export function SlosPage() {
setIsAutoRefreshing(!isAutoRefreshing);
};
+ useEffect(() => {
+ if ((!isLoading && total === 0) || hasAtLeast('platinum') === false) {
+ navigateToUrl(basePath.prepend(paths.observability.slosWelcome));
+ }
+ }, [basePath, hasAtLeast, isLoading, navigateToUrl, total]);
+
if (isInitialLoading) {
return null;
}
- if ((!isLoading && total === 0) || !hasAtLeast('platinum')) {
- return ;
- }
-
return (
{
+ useKibanaMock.mockReturnValue({
+ services: {
+ application: { navigateToUrl: mockNavigate },
+ http: {
+ basePath: {
+ prepend: (url: string) => url,
+ },
+ },
+ },
+ });
+};
+
+describe('SLOs Welcome Page', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockKibana();
+ useCapabilitiesMock.mockReturnValue({ hasWriteCapabilities: true, hasReadCapabilities: true });
+ });
+
+ describe('when the incorrect license is found', () => {
+ it('renders the welcome message with subscription buttons', async () => {
+ useFetchSloListMock.mockReturnValue({ isLoading: false, sloList: emptySloList });
+ useLicenseMock.mockReturnValue({ hasAtLeast: () => false });
+
+ render();
+
+ expect(screen.queryByTestId('slosPageWelcomePrompt')).toBeTruthy();
+ expect(screen.queryByTestId('slosPageWelcomePromptSignupForCloudButton')).toBeTruthy();
+ expect(screen.queryByTestId('slosPageWelcomePromptSignupForLicenseButton')).toBeTruthy();
+ });
+ });
+
+ describe('when the correct license is found', () => {
+ beforeEach(() => {
+ useLicenseMock.mockReturnValue({ hasAtLeast: () => true });
+ });
+
+ describe('when loading is done and no results are found', () => {
+ beforeEach(() => {
+ useFetchSloListMock.mockReturnValue({ isLoading: false, emptySloList });
+ });
+
+ it('should display the welcome message with a Create new SLO button which should navigate to the SLO Creation page', async () => {
+ render();
+ expect(screen.queryByTestId('slosPageWelcomePrompt')).toBeTruthy();
+
+ const createNewSloButton = screen.queryByTestId('o11ySloListWelcomePromptCreateSloButton');
+ expect(createNewSloButton).toBeTruthy();
+ createNewSloButton?.click();
+
+ await waitFor(() => {
+ expect(mockNavigate).toBeCalledWith(paths.observability.sloCreate);
+ });
+ });
+ });
+
+ describe('when loading is done and results are found', () => {
+ beforeEach(() => {
+ useFetchSloListMock.mockReturnValue({ isLoading: false, sloList });
+ });
+
+ it('should navigate to the SLO List page', async () => {
+ render();
+ await waitFor(() => {
+ expect(mockNavigate).toBeCalledWith(paths.observability.slos);
+ });
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_welcome_prompt.tsx b/x-pack/plugins/observability/public/pages/slos_welcome/slos_welcome.tsx
similarity index 87%
rename from x-pack/plugins/observability/public/pages/slos/components/slo_list_welcome_prompt.tsx
rename to x-pack/plugins/observability/public/pages/slos_welcome/slos_welcome.tsx
index 478c0d85cc613..50a062413951b 100644
--- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_welcome_prompt.tsx
+++ b/x-pack/plugins/observability/public/pages/slos_welcome/slos_welcome.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import React from 'react';
+import React, { useEffect } from 'react';
import {
EuiPageTemplate,
EuiButton,
@@ -18,13 +18,14 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { useKibana } from '../../../utils/kibana_react';
-import { useLicense } from '../../../hooks/use_license';
-import { usePluginContext } from '../../../hooks/use_plugin_context';
-import { paths } from '../../../config/paths';
+import { useKibana } from '../../utils/kibana_react';
+import { useLicense } from '../../hooks/use_license';
+import { usePluginContext } from '../../hooks/use_plugin_context';
+import { useFetchSloList } from '../../hooks/slo/use_fetch_slo_list';
+import { paths } from '../../config/paths';
import illustration from './assets/illustration.svg';
-export function SloListWelcomePrompt() {
+export function SlosWelcomePage() {
const {
application: { navigateToUrl },
http: { basePath },
@@ -32,14 +33,24 @@ export function SloListWelcomePrompt() {
const { ObservabilityPageTemplate } = usePluginContext();
const { hasAtLeast } = useLicense();
-
const hasRightLicense = hasAtLeast('platinum');
+ const { isLoading, sloList } = useFetchSloList();
+ const { total } = sloList || { total: 0 };
+
const handleClickCreateSlo = () => {
navigateToUrl(basePath.prepend(paths.observability.sloCreate));
};
- return (
+ const hasSlosAndHasPermissions = total > 0 && hasAtLeast('platinum') === true;
+
+ useEffect(() => {
+ if (hasSlosAndHasPermissions) {
+ navigateToUrl(basePath.prepend(paths.observability.slos));
+ }
+ }, [basePath, hasSlosAndHasPermissions, navigateToUrl]);
+
+ return hasSlosAndHasPermissions || isLoading ? null : (
{i18n.translate('xpack.observability.slo.sloList.welcomePrompt.learnMoreLink', {
diff --git a/x-pack/plugins/observability/public/routes/index.tsx b/x-pack/plugins/observability/public/routes/index.tsx
index f6fe307195309..165a7e6767569 100644
--- a/x-pack/plugins/observability/public/routes/index.tsx
+++ b/x-pack/plugins/observability/public/routes/index.tsx
@@ -17,6 +17,7 @@ import { OverviewPage } from '../pages/overview/overview';
import { RulesPage } from '../pages/rules/rules';
import { RuleDetailsPage } from '../pages/rule_details';
import { SlosPage } from '../pages/slos/slos';
+import { SlosWelcomePage } from '../pages/slos_welcome/slos_welcome';
import { SloDetailsPage } from '../pages/slo_details/slo_details';
import { SloEditPage } from '../pages/slo_edit/slo_edit';
import { casesPath } from '../../common';
@@ -135,6 +136,13 @@ export const routes = {
params: {},
exact: true,
},
+ '/slos/welcome': {
+ handler: () => {
+ return ;
+ },
+ params: {},
+ exact: true,
+ },
'/slos/edit/:sloId': {
handler: () => {
return ;
diff --git a/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_wrapper.ts b/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_wrapper.ts
index cb4d9d734bd02..a69e3b657aeb2 100644
--- a/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_wrapper.ts
+++ b/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_wrapper.ts
@@ -195,7 +195,7 @@ export const createPersistenceRuleTypeWrapper: CreatePersistenceRuleTypeWrapper
start: Date.parse(alert[TIMESTAMP]),
end: Date.parse(alert[TIMESTAMP]),
}),
- alerts: [alert],
+ alerts: [formatAlert?.(alert) ?? alert],
})
);
@@ -387,7 +387,7 @@ export const createPersistenceRuleTypeWrapper: CreatePersistenceRuleTypeWrapper
start: Date.parse(alert[TIMESTAMP]),
end: Date.parse(alert[TIMESTAMP]),
}),
- alerts: [alert],
+ alerts: [formatAlert?.(alert) ?? alert],
})
);
diff --git a/x-pack/plugins/security_solution/common/utils/expand_dotted.test.ts b/x-pack/plugins/security_solution/common/utils/expand_dotted.test.ts
index 018220e400937..d3f227274e7f1 100644
--- a/x-pack/plugins/security_solution/common/utils/expand_dotted.test.ts
+++ b/x-pack/plugins/security_solution/common/utils/expand_dotted.test.ts
@@ -70,6 +70,18 @@ describe('Expand Dotted', () => {
});
});
+ it('overwrites earlier fields when later fields conflict', () => {
+ const simpleDottedObj = {
+ 'kibana.test.1': 'the spice must flow',
+ 'kibana.test': 2,
+ };
+ expect(expandDottedObject(simpleDottedObj)).toEqual({
+ kibana: {
+ test: 2,
+ },
+ });
+ });
+
it('expands non dotted field without changing it other than reference', () => {
const simpleDottedObj = {
test: { value: '123' },
diff --git a/x-pack/plugins/security_solution/common/utils/expand_dotted.ts b/x-pack/plugins/security_solution/common/utils/expand_dotted.ts
index f90f589486ff5..4aa56b021244b 100644
--- a/x-pack/plugins/security_solution/common/utils/expand_dotted.ts
+++ b/x-pack/plugins/security_solution/common/utils/expand_dotted.ts
@@ -5,16 +5,7 @@
* 2.0.
*/
-import { merge } from '@kbn/std';
-
-const expandDottedField = (dottedFieldName: string, val: unknown): object => {
- const parts = dottedFieldName.split('.');
- if (parts.length === 1) {
- return { [parts[0]]: val };
- } else {
- return { [parts[0]]: expandDottedField(parts.slice(1).join('.'), val) };
- }
-};
+import { setWith } from 'lodash';
/*
* Expands an object with "dotted" fields to a nested object with unflattened fields.
@@ -48,8 +39,9 @@ export const expandDottedObject = (dottedObj: object) => {
if (Array.isArray(dottedObj)) {
return dottedObj;
}
- return Object.entries(dottedObj).reduce(
- (acc, [key, val]) => merge(acc, expandDottedField(key, val)),
- {}
- );
+ const returnObj = {};
+ Object.entries(dottedObj).forEach(([key, value]) => {
+ setWith(returnObj, key, value, Object);
+ });
+ return returnObj;
};
diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/alerts_details.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/alerts_details.cy.ts
index ac52c58a37928..c995c48ad2222 100644
--- a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/alerts_details.cy.ts
+++ b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/alerts_details.cy.ts
@@ -21,7 +21,7 @@ import { cleanKibana } from '../../tasks/common';
import { waitForAlertsToPopulate } from '../../tasks/create_new_rule';
import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver';
import { login, visit, visitWithoutDateRange } from '../../tasks/login';
-import { getUnmappedRule, getNewRule } from '../../objects/rule';
+import { getUnmappedRule } from '../../objects/rule';
import { ALERTS_URL } from '../../urls/navigation';
import { tablePageSelector } from '../../screens/table_pagination';
import { ALERTS_COUNT } from '../../screens/alerts';
@@ -90,13 +90,13 @@ describe('Alert details flyout', () => {
});
describe('Url state management', { testIsolation: false }, () => {
- const testRule = getNewRule();
before(() => {
cleanKibana();
+ esArchiverLoad('query_alert');
login();
- createRule(testRule);
visit(ALERTS_URL);
waitForAlertsToPopulate();
+ expandFirstAlert();
});
it('should store the flyout state in the url when it is opened', () => {
@@ -118,7 +118,7 @@ describe('Alert details flyout', () => {
cy.reload();
cy.get(OVERVIEW_RULE).should('be.visible');
cy.get(OVERVIEW_RULE).then((field) => {
- expect(field).to.contain(testRule.name);
+ expect(field).to.contain('Endpoint Security');
});
});
@@ -140,5 +140,15 @@ describe('Alert details flyout', () => {
cy.get(OVERVIEW_RULE).should('be.visible');
});
});
+
+ it('should have the `kibana.alert.url` field set', () => {
+ expandFirstAlert();
+ openTable();
+ filterBy('kibana.alert.url');
+ cy.get('[data-test-subj="formatted-field-kibana.alert.url"]').should(
+ 'have.text',
+ 'http://localhost:5601/app/security/alerts/redirect/eabbdefc23da981f2b74ab58b82622a97bb9878caa11bc914e2adfacc94780f1?index=.alerts-security.alerts-default×tamp=2023-04-27T11:03:57.906Z'
+ );
+ });
});
});
diff --git a/x-pack/plugins/security_solution/public/common/hooks/flyout/use_init_flyout_url_param.ts b/x-pack/plugins/security_solution/public/common/hooks/flyout/use_init_flyout_url_param.ts
index 875a87e46e4af..a67b605841917 100644
--- a/x-pack/plugins/security_solution/public/common/hooks/flyout/use_init_flyout_url_param.ts
+++ b/x-pack/plugins/security_solution/public/common/hooks/flyout/use_init_flyout_url_param.ts
@@ -10,10 +10,10 @@ import { useCallback, useEffect, useState } from 'react';
import { useDispatch } from 'react-redux';
import {
- dataTableActions,
dataTableSelectors,
- tableDefaults,
TableId,
+ tableDefaults,
+ dataTableActions,
} from '@kbn/securitysolution-data-table';
import { useInitializeUrlParam } from '../../utils/global_query_string';
import { URL_PARAM_KEY } from '../use_url_state';
@@ -39,16 +39,28 @@ export const useInitFlyoutFromUrlParam = () => {
}, []);
const loadExpandedDetailFromUrl = useCallback(() => {
- const { initialized, isLoading, totalCount } = dataTableCurrent;
+ const { initialized, isLoading, totalCount, additionalFilters } = dataTableCurrent;
const isTableLoaded = initialized && !isLoading && totalCount > 0;
- if (urlDetails && isTableLoaded) {
- updateHasLoadedUrlDetails(true);
- dispatch(
- dataTableActions.toggleDetailPanel({
- id: TableId.alertsOnAlertsPage,
- ...urlDetails,
- })
- );
+ if (urlDetails) {
+ if (!additionalFilters.showBuildingBlockAlerts) {
+ // We want to show building block alerts when loading the flyout in case the alert is a building block alert
+ dispatch(
+ dataTableActions.updateShowBuildingBlockAlertsFilter({
+ id: TableId.alertsOnAlertsPage,
+ showBuildingBlockAlerts: true,
+ })
+ );
+ }
+
+ if (isTableLoaded) {
+ updateHasLoadedUrlDetails(true);
+ dispatch(
+ dataTableActions.toggleDetailPanel({
+ id: TableId.alertsOnAlertsPage,
+ ...urlDetails,
+ })
+ );
+ }
}
}, [dataTableCurrent, dispatch, urlDetails]);
diff --git a/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts b/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts
index e21a5a519fc51..e6d5d67daf2bf 100644
--- a/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts
@@ -97,6 +97,15 @@ export const getBrowserFieldPath = (field: string, browserFields: BrowserFields)
return [splitFields[0], 'fields', field];
};
+export const getFieldEsTypes = (field: string, browserFields: BrowserFields): string[] => {
+ const pathBrowserField = getBrowserFieldPath(field, browserFields);
+ const browserField = get(pathBrowserField, browserFields);
+ if (browserField != null) {
+ return browserField.esTypes;
+ }
+ return [];
+};
+
export const checkIfFieldTypeIsDate = (field: string, browserFields: BrowserFields) => {
const pathBrowserField = getBrowserFieldPath(field, browserFields);
const browserField = get(pathBrowserField, browserFields);
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.test.tsx
index 4a57d7cac8e73..dae31cb56a79b 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.test.tsx
@@ -155,15 +155,9 @@ describe('GroupedAlertsTable', () => {
if (i.skip) {
return mockQueryResponse;
}
- if (i.query.aggs.groupByFields.multi_terms != null) {
- return {
- ...mockQueryResponse,
- data: groupingSearchResponse.ruleName,
- };
- }
return {
...mockQueryResponse,
- data: i.query.aggs.groupByFields.terms.field != null ? groupingSearchResponse.hostName : {},
+ data: groupingSearchResponse,
};
});
});
@@ -214,6 +208,68 @@ describe('GroupedAlertsTable', () => {
expect(within(level0).getByTestId('alerts-table')).toBeInTheDocument();
});
+ it('Query gets passed correctly', () => {
+ jest
+ .spyOn(window.localStorage, 'getItem')
+ .mockReturnValue(getMockStorageState(['kibana.alert.rule.name']));
+
+ render(
+
+
+
+ );
+ expect(mockUseQueryAlerts).toHaveBeenLastCalledWith({
+ indexName: 'test',
+ query: {
+ _source: false,
+ aggs: {
+ groupByFields: {
+ aggs: {
+ bucket_truncate: {
+ bucket_sort: { from: 0, size: 25, sort: [{ unitsCount: { order: 'desc' } }] },
+ },
+ countSeveritySubAggregation: { cardinality: { field: 'kibana.alert.severity' } },
+ hostsCountAggregation: { cardinality: { field: 'host.name' } },
+ description: { terms: { field: 'kibana.alert.rule.description', size: 1 } },
+ ruleTags: { terms: { field: 'kibana.alert.rule.tags' } },
+ severitiesSubAggregation: { terms: { field: 'kibana.alert.severity' } },
+ unitsCount: { cardinality: { field: 'kibana.alert.uuid' } },
+ usersCountAggregation: { cardinality: { field: 'user.name' } },
+ },
+ multi_terms: {
+ size: 10000,
+ terms: [
+ { field: 'kibana.alert.rule.name', missing: '-' },
+ { field: 'kibana.alert.rule.name', missing: '--' },
+ ],
+ },
+ },
+ groupsCount: { cardinality: { field: 'kibana.alert.rule.name' } },
+ unitsCount: { value_count: { field: 'kibana.alert.rule.name' } },
+ },
+ query: {
+ bool: {
+ filter: [
+ { bool: { filter: [], must: [], must_not: [], should: [] } },
+ {
+ range: {
+ '@timestamp': {
+ gte: '2020-07-07T08:20:18.966Z',
+ lte: '2020-07-08T08:20:18.966Z',
+ },
+ },
+ },
+ ],
+ },
+ },
+ runtime_mappings: {},
+ size: 0,
+ },
+ queryName: 'securitySolutionUI fetchAlerts grouping',
+ skip: false,
+ });
+ });
+
it('renders grouping table in second accordion level when 2 groups are selected', async () => {
jest
.spyOn(window.localStorage, 'getItem')
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx
index 99ae3f4ff3433..968e415f38214 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx
@@ -132,10 +132,6 @@ const GroupedAlertsTableComponent: React.FC = (props)
setPageIndex((curr) => curr.map(() => DEFAULT_PAGE_INDEX));
}, []);
- useEffect(() => {
- resetAllPagination();
- }, [resetAllPagination, selectedGroups]);
-
const setPageVar = useCallback(
(newNumber: number, groupingLevel: number, pageType: 'index' | 'size') => {
if (pageType === 'index') {
@@ -158,23 +154,31 @@ const GroupedAlertsTableComponent: React.FC = (props)
[setStoragePageSize]
);
- const nonGroupingFilters = useRef({
+ const paginationResetTriggers = useRef({
defaultFilters: props.defaultFilters,
globalFilters: props.globalFilters,
globalQuery: props.globalQuery,
+ selectedGroups,
});
useEffect(() => {
- const nonGrouping = {
+ const triggers = {
defaultFilters: props.defaultFilters,
globalFilters: props.globalFilters,
globalQuery: props.globalQuery,
+ selectedGroups,
};
- if (!isEqual(nonGroupingFilters.current, nonGrouping)) {
+ if (!isEqual(paginationResetTriggers.current, triggers)) {
resetAllPagination();
- nonGroupingFilters.current = nonGrouping;
+ paginationResetTriggers.current = triggers;
}
- }, [props.defaultFilters, props.globalFilters, props.globalQuery, resetAllPagination]);
+ }, [
+ props.defaultFilters,
+ props.globalFilters,
+ props.globalQuery,
+ resetAllPagination,
+ selectedGroups,
+ ]);
const getLevel = useCallback(
(level: number, selectedGroup: string, parentGroupingFilter?: string) => {
@@ -184,6 +188,7 @@ const GroupedAlertsTableComponent: React.FC = (props)
return getLevel(
level + 1,
selectedGroups[level + 1],
+ // stringify because if the filter is passed as an object, it will cause unnecessary re-rendering
JSON.stringify([
...groupingFilters,
...(parentGroupingFilter ? JSON.parse(parentGroupingFilter) : []),
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_sub_grouping.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_sub_grouping.tsx
index cf8a8128cb166..6fefc30e556cc 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_sub_grouping.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_sub_grouping.tsx
@@ -15,7 +15,8 @@ import { getEsQueryConfig } from '@kbn/data-plugin/common';
import type { DynamicGroupingProps } from '@kbn/securitysolution-grouping/src';
import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/types';
import type { TableIdLiteral } from '@kbn/securitysolution-data-table';
-import { combineQueries } from '../../../common/lib/kuery';
+import { parseGroupingQuery } from '@kbn/securitysolution-grouping/src';
+import { combineQueries, getFieldEsTypes } from '../../../common/lib/kuery';
import { SourcererScopeName } from '../../../common/store/sourcerer/model';
import type { AlertsGroupingAggregation } from './grouping_settings/types';
import type { Status } from '../../../../common/detection_engine/schemas/common';
@@ -140,17 +141,32 @@ export const GroupedSubLevelComponent: React.FC = ({
}
}, [defaultFilters, globalFilters, globalQuery, parentGroupingFilter]);
+ const selectedGroupEsTypes = useMemo(
+ () => getFieldEsTypes(selectedGroup, browserFields),
+ [selectedGroup, browserFields]
+ );
+
const queryGroups = useMemo(() => {
return getAlertsGroupingQuery({
additionalFilters,
selectedGroup,
+ selectedGroupEsTypes,
from,
runtimeMappings,
to,
pageSize,
pageIndex,
});
- }, [additionalFilters, from, pageIndex, pageSize, runtimeMappings, selectedGroup, to]);
+ }, [
+ additionalFilters,
+ from,
+ pageIndex,
+ pageSize,
+ runtimeMappings,
+ selectedGroup,
+ selectedGroupEsTypes,
+ to,
+ ]);
const emptyGlobalQuery = useMemo(() => getGlobalQuery([]), [getGlobalQuery]);
@@ -177,6 +193,11 @@ export const GroupedSubLevelComponent: React.FC = ({
skip: isNoneGroup([selectedGroup]),
});
+ const buckets = useMemo(
+ () => parseGroupingQuery(alertsGroupsData?.aggregations?.groupByFields?.buckets ?? []),
+ [alertsGroupsData]
+ );
+
useEffect(() => {
if (!isNoneGroup([selectedGroup])) {
setAlertsQuery(queryGroups);
@@ -225,7 +246,13 @@ export const GroupedSubLevelComponent: React.FC = ({
() =>
getGrouping({
activePage: pageIndex,
- data: alertsGroupsData?.aggregations,
+ data: {
+ ...alertsGroupsData?.aggregations,
+ groupByFields: {
+ ...alertsGroupsData?.aggregations?.groupByFields,
+ buckets,
+ },
+ },
groupingLevel,
inspectButton: inspect,
isLoading: loading || isLoadingGroups,
@@ -238,20 +265,21 @@ export const GroupedSubLevelComponent: React.FC = ({
takeActionItems: getTakeActionItems,
}),
[
- alertsGroupsData?.aggregations,
getGrouping,
- getTakeActionItems,
+ pageIndex,
+ alertsGroupsData,
+ buckets,
groupingLevel,
inspect,
- isLoadingGroups,
loading,
- pageIndex,
+ isLoadingGroups,
pageSize,
renderChildComponent,
onGroupClose,
selectedGroup,
- setPageIndex,
+ getTakeActionItems,
setPageSize,
+ setPageIndex,
]
);
};
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_panel_renderers.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_panel_renderers.test.tsx
index ff70bfe8e47ce..2038bddea952e 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_panel_renderers.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_panel_renderers.test.tsx
@@ -5,51 +5,74 @@
* 2.0.
*/
-import { shallow } from 'enzyme';
-
import { renderGroupPanel } from '.';
+import { render } from '@testing-library/react';
describe('renderGroupPanel', () => {
it('renders correctly when the field renderer exists', () => {
- const wrapperRuleName = shallow(
- renderGroupPanel('kibana.alert.rule.name', {
- key: ['Rule name test', 'Some description'],
- doc_count: 10,
- })!
+ let { getByTestId } = render(
+ renderGroupPanel(
+ 'kibana.alert.rule.name',
+ {
+ key: ['Rule name test', 'Some description'],
+ doc_count: 10,
+ },
+ 'This is a null group!'
+ )!
);
- expect(wrapperRuleName.find('[data-test-subj="rule-name-group-renderer"]')).toBeTruthy();
- const wrapperHostName = shallow(
- renderGroupPanel('host.name', {
- key: 'Host',
- doc_count: 2,
- })!
+ expect(getByTestId('rule-name-group-renderer')).toBeInTheDocument();
+ const result1 = render(
+ renderGroupPanel(
+ 'host.name',
+ {
+ key: 'Host',
+ doc_count: 2,
+ },
+ 'This is a null group!'
+ )!
);
+ getByTestId = result1.getByTestId;
+
+ expect(getByTestId('host-name-group-renderer')).toBeInTheDocument();
- expect(wrapperHostName.find('[data-test-subj="host-name-group-renderer"]')).toBeTruthy();
- const wrapperUserName = shallow(
- renderGroupPanel('user.name', {
- key: 'User test',
- doc_count: 1,
- })!
+ const result2 = render(
+ renderGroupPanel(
+ 'user.name',
+ {
+ key: 'User test',
+ doc_count: 1,
+ },
+ 'This is a null group!'
+ )!
);
+ getByTestId = result2.getByTestId;
- expect(wrapperUserName.find('[data-test-subj="host-name-group-renderer"]')).toBeTruthy();
- const wrapperSourceIp = shallow(
- renderGroupPanel('source.ip', {
- key: 'sourceIp',
- doc_count: 23,
- })!
+ expect(getByTestId('host-name-group-renderer')).toBeInTheDocument();
+ const result3 = render(
+ renderGroupPanel(
+ 'source.ip',
+ {
+ key: 'sourceIp',
+ doc_count: 23,
+ },
+ 'This is a null group!'
+ )!
);
+ getByTestId = result3.getByTestId;
- expect(wrapperSourceIp.find('[data-test-subj="source-ip-group-renderer"]')).toBeTruthy();
+ expect(getByTestId('source-ip-group-renderer')).toBeInTheDocument();
});
it('returns undefined when the renderer does not exist', () => {
- const wrapper = renderGroupPanel('process.name', {
- key: 'process',
- doc_count: 10,
- });
+ const wrapper = renderGroupPanel(
+ 'process.name',
+ {
+ key: 'process',
+ doc_count: 10,
+ },
+ 'This is a null group!'
+ );
expect(wrapper).toBeUndefined();
});
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_panel_renderers.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_panel_renderers.tsx
index ffea45007fc6d..2781b78247cf6 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_panel_renderers.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_panel_renderers.tsx
@@ -11,6 +11,7 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
+ EuiIconTip,
EuiText,
EuiTextColor,
EuiTitle,
@@ -18,32 +19,35 @@ import {
import { euiThemeVars } from '@kbn/ui-theme';
import { isArray } from 'lodash/fp';
import React from 'react';
-import type { RawBucket } from '@kbn/securitysolution-grouping';
+import type { GroupPanelRenderer } from '@kbn/securitysolution-grouping/src';
import type { AlertsGroupingAggregation } from './types';
import { firstNonNullValue } from '../../../../../common/endpoint/models/ecs_safety_helpers';
import type { GenericBuckets } from '../../../../../common/search_strategy';
import { PopoverItems } from '../../../../common/components/popover_items';
import { COLUMN_TAGS } from '../../../pages/detection_engine/rules/translations';
-export const renderGroupPanel = (
- selectedGroup: string,
- bucket: RawBucket
+export const renderGroupPanel: GroupPanelRenderer = (
+ selectedGroup,
+ bucket,
+ nullGroupMessage
) => {
switch (selectedGroup) {
case 'kibana.alert.rule.name':
return isArray(bucket.key) ? (
) : undefined;
case 'host.name':
- return ;
+ return ;
case 'user.name':
- return ;
+ return ;
case 'source.ip':
- return ;
+ return ;
}
};
@@ -89,66 +93,87 @@ const RuleNameGroupContent = React.memo<{
});
RuleNameGroupContent.displayName = 'RuleNameGroup';
-const HostNameGroupContent = React.memo<{ hostName: string | string[] }>(({ hostName }) => (
-
- (
+ ({ hostName, nullGroupMessage }) => (
+
-
-
-
-
-
- {hostName}
-
-
-
-));
-HostNameGroupContent.displayName = 'HostNameGroupContent';
-
-const UserNameGroupContent = React.memo<{ userName: string | string[] }>(({ userName }) => {
- const userNameValue = firstNonNullValue(userName) ?? '-';
- return (
-
-
-
+
+
-
+
- {userName}
+ {hostName}
+ {nullGroupMessage && (
+
+
+
+ )}
- );
-});
+ )
+);
+HostNameGroupContent.displayName = 'HostNameGroupContent';
+
+const UserNameGroupContent = React.memo<{ userName: string | string[]; nullGroupMessage?: string }>(
+ ({ userName, nullGroupMessage }) => {
+ const userNameValue = firstNonNullValue(userName) ?? '-';
+ return (
+
+
+
+
+
+
+
+ {userName}
+
+
+ {nullGroupMessage && (
+
+
+
+ )}
+
+ );
+ }
+);
UserNameGroupContent.displayName = 'UserNameGroupContent';
-const SourceIpGroupContent = React.memo<{ sourceIp: string | string[] }>(({ sourceIp }) => (
-
-
-
-
-
-
- {sourceIp}
-
-
-
-));
+const SourceIpGroupContent = React.memo<{ sourceIp: string | string[]; nullGroupMessage?: string }>(
+ ({ sourceIp, nullGroupMessage }) => (
+
+
+
+
+
+
+ {sourceIp}
+
+
+ {nullGroupMessage && (
+
+
+
+ )}
+
+ )
+);
SourceIpGroupContent.displayName = 'SourceIpGroupContent';
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/mock.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/mock.ts
index 9e0b4e63715aa..c9717c2a1ad2b 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/mock.ts
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/mock.ts
@@ -8,1729 +8,1339 @@
import { mockAlertSearchResponse } from '../../../../common/components/alerts_treemap/lib/mocks/mock_alert_search_response';
export const groupingSearchResponse = {
- ruleName: {
- ...mockAlertSearchResponse,
- hits: {
- total: {
- value: 6048,
- relation: 'eq',
- },
- max_score: null,
- hits: [],
- },
- aggregations: {
- groupsCount: {
- value: 32,
- },
- groupByFields: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: ['critical hosts [Duplicate]', 'f'],
- key_as_string: 'critical hosts [Duplicate]|f',
- doc_count: 300,
- hostsCountAggregation: {
- value: 30,
- },
- ruleTags: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'cool',
- doc_count: 300,
- },
- {
- key: 'rule',
- doc_count: 300,
- },
- ],
- },
- unitsCount: {
- value: 300,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 300,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: ['critical hosts [Duplicate] [Duplicate]', 'f'],
- key_as_string: 'critical hosts [Duplicate] [Duplicate]|f',
- doc_count: 300,
- hostsCountAggregation: {
- value: 30,
- },
- ruleTags: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'cool',
- doc_count: 300,
- },
- {
- key: 'rule',
- doc_count: 300,
- },
- ],
- },
- unitsCount: {
- value: 300,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 300,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: ['high hosts [Duplicate]', 'f'],
- key_as_string: 'high hosts [Duplicate]|f',
- doc_count: 300,
- hostsCountAggregation: {
- value: 30,
- },
- ruleTags: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'cool',
- doc_count: 300,
- },
- {
- key: 'rule',
- doc_count: 300,
- },
- ],
- },
- unitsCount: {
- value: 300,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'high',
- doc_count: 300,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: ['high hosts [Duplicate] [Duplicate]', 'f'],
- key_as_string: 'high hosts [Duplicate] [Duplicate]|f',
- doc_count: 300,
- hostsCountAggregation: {
- value: 30,
- },
- ruleTags: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'cool',
- doc_count: 300,
- },
- {
- key: 'rule',
- doc_count: 300,
- },
- ],
- },
- unitsCount: {
- value: 300,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'high',
- doc_count: 300,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: ['low hosts [Duplicate]', 'f'],
- key_as_string: 'low hosts [Duplicate]|f',
- doc_count: 300,
- hostsCountAggregation: {
- value: 30,
- },
- ruleTags: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'cool',
- doc_count: 300,
- },
- {
- key: 'rule',
- doc_count: 300,
- },
- ],
- },
- unitsCount: {
- value: 300,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'low',
- doc_count: 300,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: ['low hosts [Duplicate] [Duplicate]', 'f'],
- key_as_string: 'low hosts [Duplicate] [Duplicate]|f',
- doc_count: 300,
- hostsCountAggregation: {
- value: 30,
- },
- ruleTags: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'cool',
- doc_count: 300,
- },
- {
- key: 'rule',
- doc_count: 300,
- },
- ],
- },
- unitsCount: {
- value: 300,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'low',
- doc_count: 300,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: ['medium hosts [Duplicate]', 'f'],
- key_as_string: 'medium hosts [Duplicate]|f',
- doc_count: 300,
- hostsCountAggregation: {
- value: 30,
- },
- ruleTags: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'cool',
- doc_count: 300,
- },
- {
- key: 'rule',
- doc_count: 300,
- },
- ],
- },
- unitsCount: {
- value: 300,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'medium',
- doc_count: 300,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: ['medium hosts [Duplicate] [Duplicate]', 'f'],
- key_as_string: 'medium hosts [Duplicate] [Duplicate]|f',
- doc_count: 300,
- hostsCountAggregation: {
- value: 30,
- },
- ruleTags: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'cool',
- doc_count: 300,
- },
- {
- key: 'rule',
- doc_count: 300,
- },
- ],
- },
- unitsCount: {
- value: 300,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'medium',
- doc_count: 300,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: ['critical users [Duplicate]', 'f'],
- key_as_string: 'critical users [Duplicate]|f',
- doc_count: 273,
- hostsCountAggregation: {
- value: 10,
- },
- ruleTags: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'cool',
- doc_count: 273,
- },
- {
- key: 'rule',
- doc_count: 273,
- },
- ],
- },
- unitsCount: {
- value: 273,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 273,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 91,
- },
- },
- {
- key: ['critical users [Duplicate] [Duplicate]', 'f'],
- key_as_string: 'critical users [Duplicate] [Duplicate]|f',
- doc_count: 273,
- hostsCountAggregation: {
- value: 10,
- },
- ruleTags: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'cool',
- doc_count: 273,
- },
- {
- key: 'rule',
- doc_count: 273,
- },
- ],
- },
- unitsCount: {
- value: 273,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 273,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 91,
- },
- },
- {
- key: ['high users [Duplicate]', 'f'],
- key_as_string: 'high users [Duplicate]|f',
- doc_count: 273,
- hostsCountAggregation: {
- value: 10,
- },
- ruleTags: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'cool',
- doc_count: 273,
- },
- {
- key: 'rule',
- doc_count: 273,
- },
- ],
- },
- unitsCount: {
- value: 273,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'high',
- doc_count: 273,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 91,
- },
- },
- {
- key: ['high users [Duplicate] [Duplicate]', 'f'],
- key_as_string: 'high users [Duplicate] [Duplicate]|f',
- doc_count: 273,
- hostsCountAggregation: {
- value: 10,
- },
- ruleTags: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'cool',
- doc_count: 273,
- },
- {
- key: 'rule',
- doc_count: 273,
- },
- ],
- },
- unitsCount: {
- value: 273,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'high',
- doc_count: 273,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 91,
- },
- },
- {
- key: ['low users [Duplicate]', 'f'],
- key_as_string: 'low users [Duplicate]|f',
- doc_count: 273,
- hostsCountAggregation: {
- value: 10,
- },
- ruleTags: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'cool',
- doc_count: 273,
- },
- {
- key: 'rule',
- doc_count: 273,
- },
- ],
- },
- unitsCount: {
- value: 273,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'low',
- doc_count: 273,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 91,
- },
- },
- {
- key: ['low users [Duplicate] [Duplicate]', 'f'],
- key_as_string: 'low users [Duplicate] [Duplicate]|f',
- doc_count: 273,
- hostsCountAggregation: {
- value: 10,
- },
- ruleTags: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'cool',
- doc_count: 273,
- },
- {
- key: 'rule',
- doc_count: 273,
- },
- ],
- },
- unitsCount: {
- value: 273,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'low',
- doc_count: 273,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 91,
- },
- },
- {
- key: ['medium users [Duplicate]', 'f'],
- key_as_string: 'medium users [Duplicate]|f',
- doc_count: 273,
- hostsCountAggregation: {
- value: 10,
- },
- ruleTags: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'cool',
- doc_count: 273,
- },
- {
- key: 'rule',
- doc_count: 273,
- },
- ],
- },
- unitsCount: {
- value: 273,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'medium',
- doc_count: 273,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 91,
- },
- },
- {
- key: ['medium users [Duplicate] [Duplicate]', 'f'],
- key_as_string: 'medium users [Duplicate] [Duplicate]|f',
- doc_count: 273,
- hostsCountAggregation: {
- value: 10,
- },
- ruleTags: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'cool',
- doc_count: 273,
- },
- {
- key: 'rule',
- doc_count: 273,
- },
- ],
- },
- unitsCount: {
- value: 273,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'medium',
- doc_count: 273,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 91,
- },
- },
- {
- key: ['critical hosts', 'f'],
- key_as_string: 'critical hosts|f',
- doc_count: 100,
- hostsCountAggregation: {
- value: 30,
- },
- ruleTags: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'cool',
- doc_count: 100,
- },
- {
- key: 'rule',
- doc_count: 100,
- },
- ],
- },
- unitsCount: {
- value: 100,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 100,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: ['critical hosts [Duplicate] [Duplicate] [Duplicate]', 'f'],
- key_as_string: 'critical hosts [Duplicate] [Duplicate] [Duplicate]|f',
- doc_count: 100,
- hostsCountAggregation: {
- value: 30,
- },
- ruleTags: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'cool',
- doc_count: 100,
- },
- {
- key: 'rule',
- doc_count: 100,
- },
- ],
- },
- unitsCount: {
- value: 100,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 100,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: ['high hosts', 'f'],
- key_as_string: 'high hosts|f',
- doc_count: 100,
- hostsCountAggregation: {
- value: 30,
- },
- ruleTags: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'cool',
- doc_count: 100,
- },
- {
- key: 'rule',
- doc_count: 100,
- },
- ],
- },
- unitsCount: {
- value: 100,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'high',
- doc_count: 100,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: ['high hosts [Duplicate] [Duplicate] [Duplicate]', 'f'],
- key_as_string: 'high hosts [Duplicate] [Duplicate] [Duplicate]|f',
- doc_count: 100,
- hostsCountAggregation: {
- value: 30,
- },
- ruleTags: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'cool',
- doc_count: 100,
- },
- {
- key: 'rule',
- doc_count: 100,
- },
- ],
- },
- unitsCount: {
- value: 100,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'high',
- doc_count: 100,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: ['low hosts ', 'f'],
- key_as_string: 'low hosts |f',
- doc_count: 100,
- hostsCountAggregation: {
- value: 30,
- },
- ruleTags: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'cool',
- doc_count: 100,
- },
- {
- key: 'rule',
- doc_count: 100,
- },
- ],
- },
- unitsCount: {
- value: 100,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'low',
- doc_count: 100,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: ['low hosts [Duplicate] [Duplicate] [Duplicate]', 'f'],
- key_as_string: 'low hosts [Duplicate] [Duplicate] [Duplicate]|f',
- doc_count: 100,
- hostsCountAggregation: {
- value: 30,
- },
- ruleTags: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'cool',
- doc_count: 100,
- },
- {
- key: 'rule',
- doc_count: 100,
- },
- ],
- },
- unitsCount: {
- value: 100,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'low',
- doc_count: 100,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: ['medium hosts', 'f'],
- key_as_string: 'medium hosts|f',
- doc_count: 100,
- hostsCountAggregation: {
- value: 30,
- },
- ruleTags: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'cool',
- doc_count: 100,
- },
- {
- key: 'rule',
- doc_count: 100,
- },
- ],
- },
- unitsCount: {
- value: 100,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'medium',
- doc_count: 100,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: ['medium hosts [Duplicate] [Duplicate] [Duplicate]', 'f'],
- key_as_string: 'medium hosts [Duplicate] [Duplicate] [Duplicate]|f',
- doc_count: 100,
- hostsCountAggregation: {
- value: 30,
- },
- ruleTags: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'cool',
- doc_count: 100,
- },
- {
- key: 'rule',
- doc_count: 100,
- },
- ],
- },
- unitsCount: {
- value: 100,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'medium',
- doc_count: 100,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: ['critical users [Duplicate] [Duplicate] [Duplicate]', 'f'],
- key_as_string: 'critical users [Duplicate] [Duplicate] [Duplicate]|f',
- doc_count: 91,
- hostsCountAggregation: {
- value: 10,
- },
- ruleTags: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'cool',
- doc_count: 91,
- },
- {
- key: 'rule',
- doc_count: 91,
- },
- ],
- },
- unitsCount: {
- value: 91,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 91,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 91,
- },
- },
- ],
- },
- unitsCount: {
- value: 6048,
- },
+ ...mockAlertSearchResponse,
+ hits: {
+ total: {
+ value: 6048,
+ relation: 'eq',
},
+ max_score: null,
+ hits: [],
},
- hostName: {
- ...mockAlertSearchResponse,
- hits: {
- total: {
- value: 900,
- relation: 'eq',
- },
- max_score: null,
- hits: [],
+ aggregations: {
+ groupsCount: {
+ value: 32,
+ },
+ groupByFields: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: ['critical hosts [Duplicate]', 'critical hosts [Duplicate]'],
+ key_as_string: 'critical hosts [Duplicate]|critical hosts [Duplicate]',
+ doc_count: 300,
+ hostsCountAggregation: {
+ value: 30,
+ },
+ ruleTags: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'cool',
+ doc_count: 300,
+ },
+ {
+ key: 'rule',
+ doc_count: 300,
+ },
+ ],
+ },
+ description: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'f',
+ doc_count: 1,
+ },
+ ],
+ },
+ unitsCount: {
+ value: 300,
+ },
+ severitiesSubAggregation: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'critical',
+ doc_count: 300,
+ },
+ ],
+ },
+ countSeveritySubAggregation: {
+ value: 1,
+ },
+ usersCountAggregation: {
+ value: 0,
+ },
+ },
+ {
+ key: ['critical hosts [Duplicate] [Duplicate]', 'critical hosts [Duplicate] [Duplicate]'],
+ key_as_string:
+ 'critical hosts [Duplicate] [Duplicate]|critical hosts [Duplicate] [Duplicate]',
+ doc_count: 300,
+ hostsCountAggregation: {
+ value: 30,
+ },
+ ruleTags: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'cool',
+ doc_count: 300,
+ },
+ {
+ key: 'rule',
+ doc_count: 300,
+ },
+ ],
+ },
+ description: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'f',
+ doc_count: 1,
+ },
+ ],
+ },
+ unitsCount: {
+ value: 300,
+ },
+ severitiesSubAggregation: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'critical',
+ doc_count: 300,
+ },
+ ],
+ },
+ countSeveritySubAggregation: {
+ value: 1,
+ },
+ usersCountAggregation: {
+ value: 0,
+ },
+ },
+ {
+ key: ['high hosts [Duplicate]', 'high hosts [Duplicate]'],
+ key_as_string: 'high hosts [Duplicate]|high hosts [Duplicate]',
+ doc_count: 300,
+ hostsCountAggregation: {
+ value: 30,
+ },
+ ruleTags: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'cool',
+ doc_count: 300,
+ },
+ {
+ key: 'rule',
+ doc_count: 300,
+ },
+ ],
+ },
+ description: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'f',
+ doc_count: 1,
+ },
+ ],
+ },
+ unitsCount: {
+ value: 300,
+ },
+ severitiesSubAggregation: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'high',
+ doc_count: 300,
+ },
+ ],
+ },
+ countSeveritySubAggregation: {
+ value: 1,
+ },
+ usersCountAggregation: {
+ value: 0,
+ },
+ },
+ {
+ key: ['high hosts [Duplicate] [Duplicate]', 'high hosts [Duplicate] [Duplicate]'],
+ key_as_string: 'high hosts [Duplicate] [Duplicate]|high hosts [Duplicate] [Duplicate]',
+ doc_count: 300,
+ hostsCountAggregation: {
+ value: 30,
+ },
+ ruleTags: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'cool',
+ doc_count: 300,
+ },
+ {
+ key: 'rule',
+ doc_count: 300,
+ },
+ ],
+ },
+ description: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'f',
+ doc_count: 1,
+ },
+ ],
+ },
+ unitsCount: {
+ value: 300,
+ },
+ severitiesSubAggregation: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'high',
+ doc_count: 300,
+ },
+ ],
+ },
+ countSeveritySubAggregation: {
+ value: 1,
+ },
+ usersCountAggregation: {
+ value: 0,
+ },
+ },
+ {
+ key: ['low hosts [Duplicate]', 'low hosts [Duplicate]'],
+ key_as_string: 'low hosts [Duplicate]|low hosts [Duplicate]',
+ doc_count: 300,
+ hostsCountAggregation: {
+ value: 30,
+ },
+ ruleTags: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'cool',
+ doc_count: 300,
+ },
+ {
+ key: 'rule',
+ doc_count: 300,
+ },
+ ],
+ },
+ description: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'f',
+ doc_count: 1,
+ },
+ ],
+ },
+ unitsCount: {
+ value: 300,
+ },
+ severitiesSubAggregation: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'low',
+ doc_count: 300,
+ },
+ ],
+ },
+ countSeveritySubAggregation: {
+ value: 1,
+ },
+ usersCountAggregation: {
+ value: 0,
+ },
+ },
+ {
+ key: ['low hosts [Duplicate] [Duplicate]', 'low hosts [Duplicate] [Duplicate]'],
+ key_as_string: 'low hosts [Duplicate] [Duplicate]|low hosts [Duplicate] [Duplicate]',
+ doc_count: 300,
+ hostsCountAggregation: {
+ value: 30,
+ },
+ ruleTags: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'cool',
+ doc_count: 300,
+ },
+ {
+ key: 'rule',
+ doc_count: 300,
+ },
+ ],
+ },
+ description: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'f',
+ doc_count: 1,
+ },
+ ],
+ },
+ unitsCount: {
+ value: 300,
+ },
+ severitiesSubAggregation: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'low',
+ doc_count: 300,
+ },
+ ],
+ },
+ countSeveritySubAggregation: {
+ value: 1,
+ },
+ usersCountAggregation: {
+ value: 0,
+ },
+ },
+ {
+ key: ['medium hosts [Duplicate]', 'medium hosts [Duplicate]'],
+ key_as_string: 'medium hosts [Duplicate]|medium hosts [Duplicate]',
+ doc_count: 300,
+ hostsCountAggregation: {
+ value: 30,
+ },
+ ruleTags: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'cool',
+ doc_count: 300,
+ },
+ {
+ key: 'rule',
+ doc_count: 300,
+ },
+ ],
+ },
+ description: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'f',
+ doc_count: 1,
+ },
+ ],
+ },
+ unitsCount: {
+ value: 300,
+ },
+ severitiesSubAggregation: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'medium',
+ doc_count: 300,
+ },
+ ],
+ },
+ countSeveritySubAggregation: {
+ value: 1,
+ },
+ usersCountAggregation: {
+ value: 0,
+ },
+ },
+ {
+ key: ['medium hosts [Duplicate] [Duplicate]', 'medium hosts [Duplicate] [Duplicate]'],
+ key_as_string:
+ 'medium hosts [Duplicate] [Duplicate]|medium hosts [Duplicate] [Duplicate]',
+ doc_count: 300,
+ hostsCountAggregation: {
+ value: 30,
+ },
+ ruleTags: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'cool',
+ doc_count: 300,
+ },
+ {
+ key: 'rule',
+ doc_count: 300,
+ },
+ ],
+ },
+ description: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'f',
+ doc_count: 1,
+ },
+ ],
+ },
+ unitsCount: {
+ value: 300,
+ },
+ severitiesSubAggregation: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'medium',
+ doc_count: 300,
+ },
+ ],
+ },
+ countSeveritySubAggregation: {
+ value: 1,
+ },
+ usersCountAggregation: {
+ value: 0,
+ },
+ },
+ {
+ key: ['critical users [Duplicate]', 'critical users [Duplicate]'],
+ key_as_string: 'critical users [Duplicate]|critical users [Duplicate]',
+ doc_count: 273,
+ hostsCountAggregation: {
+ value: 10,
+ },
+ ruleTags: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'cool',
+ doc_count: 273,
+ },
+ {
+ key: 'rule',
+ doc_count: 273,
+ },
+ ],
+ },
+ description: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'f',
+ doc_count: 1,
+ },
+ ],
+ },
+ unitsCount: {
+ value: 273,
+ },
+ severitiesSubAggregation: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'critical',
+ doc_count: 273,
+ },
+ ],
+ },
+ countSeveritySubAggregation: {
+ value: 1,
+ },
+ usersCountAggregation: {
+ value: 91,
+ },
+ },
+ {
+ key: [
+ 'critical users [Duplicate] [Duplicate]',
+ 'critical users [Duplicate] [Duplicate]',
+ ],
+ key_as_string:
+ 'critical users [Duplicate] [Duplicate]|critical users [Duplicate] [Duplicate]',
+ doc_count: 273,
+ hostsCountAggregation: {
+ value: 10,
+ },
+ ruleTags: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'cool',
+ doc_count: 273,
+ },
+ {
+ key: 'rule',
+ doc_count: 273,
+ },
+ ],
+ },
+ description: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'f',
+ doc_count: 1,
+ },
+ ],
+ },
+ unitsCount: {
+ value: 273,
+ },
+ severitiesSubAggregation: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'critical',
+ doc_count: 273,
+ },
+ ],
+ },
+ countSeveritySubAggregation: {
+ value: 1,
+ },
+ usersCountAggregation: {
+ value: 91,
+ },
+ },
+ {
+ key: ['high users [Duplicate]', 'high users [Duplicate]'],
+ key_as_string: 'high users [Duplicate]|high users [Duplicate]',
+ doc_count: 273,
+ hostsCountAggregation: {
+ value: 10,
+ },
+ ruleTags: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'cool',
+ doc_count: 273,
+ },
+ {
+ key: 'rule',
+ doc_count: 273,
+ },
+ ],
+ },
+ description: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'f',
+ doc_count: 1,
+ },
+ ],
+ },
+ unitsCount: {
+ value: 273,
+ },
+ severitiesSubAggregation: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'high',
+ doc_count: 273,
+ },
+ ],
+ },
+ countSeveritySubAggregation: {
+ value: 1,
+ },
+ usersCountAggregation: {
+ value: 91,
+ },
+ },
+ {
+ key: ['high users [Duplicate] [Duplicate]', 'high users [Duplicate] [Duplicate]'],
+ key_as_string: 'high users [Duplicate] [Duplicate]|high users [Duplicate] [Duplicate]',
+ doc_count: 273,
+ hostsCountAggregation: {
+ value: 10,
+ },
+ ruleTags: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'cool',
+ doc_count: 273,
+ },
+ {
+ key: 'rule',
+ doc_count: 273,
+ },
+ ],
+ },
+ description: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'f',
+ doc_count: 1,
+ },
+ ],
+ },
+ unitsCount: {
+ value: 273,
+ },
+ severitiesSubAggregation: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'high',
+ doc_count: 273,
+ },
+ ],
+ },
+ countSeveritySubAggregation: {
+ value: 1,
+ },
+ usersCountAggregation: {
+ value: 91,
+ },
+ },
+ {
+ key: ['low users [Duplicate]', 'low users [Duplicate]'],
+ key_as_string: 'low users [Duplicate]|low users [Duplicate]',
+ doc_count: 273,
+ hostsCountAggregation: {
+ value: 10,
+ },
+ ruleTags: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'cool',
+ doc_count: 273,
+ },
+ {
+ key: 'rule',
+ doc_count: 273,
+ },
+ ],
+ },
+ description: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'f',
+ doc_count: 1,
+ },
+ ],
+ },
+ unitsCount: {
+ value: 273,
+ },
+ severitiesSubAggregation: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'low',
+ doc_count: 273,
+ },
+ ],
+ },
+ countSeveritySubAggregation: {
+ value: 1,
+ },
+ usersCountAggregation: {
+ value: 91,
+ },
+ },
+ {
+ key: ['low users [Duplicate] [Duplicate]', 'low users [Duplicate] [Duplicate]'],
+ key_as_string: 'low users [Duplicate] [Duplicate]|low users [Duplicate] [Duplicate]',
+ doc_count: 273,
+ hostsCountAggregation: {
+ value: 10,
+ },
+ ruleTags: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'cool',
+ doc_count: 273,
+ },
+ {
+ key: 'rule',
+ doc_count: 273,
+ },
+ ],
+ },
+ description: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'f',
+ doc_count: 1,
+ },
+ ],
+ },
+ unitsCount: {
+ value: 273,
+ },
+ severitiesSubAggregation: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'low',
+ doc_count: 273,
+ },
+ ],
+ },
+ countSeveritySubAggregation: {
+ value: 1,
+ },
+ usersCountAggregation: {
+ value: 91,
+ },
+ },
+ {
+ key: ['medium users [Duplicate]', 'medium users [Duplicate]'],
+ key_as_string: 'medium users [Duplicate]|medium users [Duplicate]',
+ doc_count: 273,
+ hostsCountAggregation: {
+ value: 10,
+ },
+ ruleTags: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'cool',
+ doc_count: 273,
+ },
+ {
+ key: 'rule',
+ doc_count: 273,
+ },
+ ],
+ },
+ description: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'f',
+ doc_count: 1,
+ },
+ ],
+ },
+ unitsCount: {
+ value: 273,
+ },
+ severitiesSubAggregation: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'medium',
+ doc_count: 273,
+ },
+ ],
+ },
+ countSeveritySubAggregation: {
+ value: 1,
+ },
+ usersCountAggregation: {
+ value: 91,
+ },
+ },
+ {
+ key: ['medium users [Duplicate] [Duplicate]', 'medium users [Duplicate] [Duplicate]'],
+ key_as_string:
+ 'medium users [Duplicate] [Duplicate]|medium users [Duplicate] [Duplicate]',
+ doc_count: 273,
+ hostsCountAggregation: {
+ value: 10,
+ },
+ ruleTags: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'cool',
+ doc_count: 273,
+ },
+ {
+ key: 'rule',
+ doc_count: 273,
+ },
+ ],
+ },
+ description: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'f',
+ doc_count: 1,
+ },
+ ],
+ },
+ unitsCount: {
+ value: 273,
+ },
+ severitiesSubAggregation: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'medium',
+ doc_count: 273,
+ },
+ ],
+ },
+ countSeveritySubAggregation: {
+ value: 1,
+ },
+ usersCountAggregation: {
+ value: 91,
+ },
+ },
+ {
+ key: ['critical hosts', 'critical hosts'],
+ key_as_string: 'critical hosts|critical hosts',
+ doc_count: 100,
+ hostsCountAggregation: {
+ value: 30,
+ },
+ ruleTags: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'cool',
+ doc_count: 100,
+ },
+ {
+ key: 'rule',
+ doc_count: 100,
+ },
+ ],
+ },
+ description: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'f',
+ doc_count: 1,
+ },
+ ],
+ },
+ unitsCount: {
+ value: 100,
+ },
+ severitiesSubAggregation: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'critical',
+ doc_count: 100,
+ },
+ ],
+ },
+ countSeveritySubAggregation: {
+ value: 1,
+ },
+ usersCountAggregation: {
+ value: 0,
+ },
+ },
+ {
+ key: [
+ 'critical hosts [Duplicate] [Duplicate] [Duplicate]',
+ 'critical hosts [Duplicate] [Duplicate] [Duplicate]',
+ ],
+ key_as_string:
+ 'critical hosts [Duplicate] [Duplicate] [Duplicate]|critical hosts [Duplicate] [Duplicate] [Duplicate]',
+ doc_count: 100,
+ hostsCountAggregation: {
+ value: 30,
+ },
+ ruleTags: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'cool',
+ doc_count: 100,
+ },
+ {
+ key: 'rule',
+ doc_count: 100,
+ },
+ ],
+ },
+ description: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'f',
+ doc_count: 1,
+ },
+ ],
+ },
+ unitsCount: {
+ value: 100,
+ },
+ severitiesSubAggregation: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'critical',
+ doc_count: 100,
+ },
+ ],
+ },
+ countSeveritySubAggregation: {
+ value: 1,
+ },
+ usersCountAggregation: {
+ value: 0,
+ },
+ },
+ {
+ key: ['high hosts', 'high hosts'],
+ key_as_string: 'high hosts|high hosts',
+ doc_count: 100,
+ hostsCountAggregation: {
+ value: 30,
+ },
+ ruleTags: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'cool',
+ doc_count: 100,
+ },
+ {
+ key: 'rule',
+ doc_count: 100,
+ },
+ ],
+ },
+ description: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'f',
+ doc_count: 1,
+ },
+ ],
+ },
+ unitsCount: {
+ value: 100,
+ },
+ severitiesSubAggregation: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'high',
+ doc_count: 100,
+ },
+ ],
+ },
+ countSeveritySubAggregation: {
+ value: 1,
+ },
+ usersCountAggregation: {
+ value: 0,
+ },
+ },
+ {
+ key: [
+ 'high hosts [Duplicate] [Duplicate] [Duplicate]',
+ 'high hosts [Duplicate] [Duplicate] [Duplicate]',
+ ],
+ key_as_string:
+ 'high hosts [Duplicate] [Duplicate] [Duplicate]|high hosts [Duplicate] [Duplicate] [Duplicate]',
+ doc_count: 100,
+ hostsCountAggregation: {
+ value: 30,
+ },
+ ruleTags: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'cool',
+ doc_count: 100,
+ },
+ {
+ key: 'rule',
+ doc_count: 100,
+ },
+ ],
+ },
+ description: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'f',
+ doc_count: 1,
+ },
+ ],
+ },
+ unitsCount: {
+ value: 100,
+ },
+ severitiesSubAggregation: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'high',
+ doc_count: 100,
+ },
+ ],
+ },
+ countSeveritySubAggregation: {
+ value: 1,
+ },
+ usersCountAggregation: {
+ value: 0,
+ },
+ },
+ {
+ key: ['low hosts ', 'low hosts '],
+ key_as_string: 'low hosts |low hosts ',
+ doc_count: 100,
+ hostsCountAggregation: {
+ value: 30,
+ },
+ ruleTags: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'cool',
+ doc_count: 100,
+ },
+ {
+ key: 'rule',
+ doc_count: 100,
+ },
+ ],
+ },
+ description: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'f',
+ doc_count: 1,
+ },
+ ],
+ },
+ unitsCount: {
+ value: 100,
+ },
+ severitiesSubAggregation: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'low',
+ doc_count: 100,
+ },
+ ],
+ },
+ countSeveritySubAggregation: {
+ value: 1,
+ },
+ usersCountAggregation: {
+ value: 0,
+ },
+ },
+ {
+ key: [
+ 'low hosts [Duplicate] [Duplicate] [Duplicate]',
+ 'low hosts [Duplicate] [Duplicate] [Duplicate]',
+ ],
+ key_as_string:
+ 'low hosts [Duplicate] [Duplicate] [Duplicate]|low hosts [Duplicate] [Duplicate] [Duplicate]',
+ doc_count: 100,
+ hostsCountAggregation: {
+ value: 30,
+ },
+ ruleTags: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'cool',
+ doc_count: 100,
+ },
+ {
+ key: 'rule',
+ doc_count: 100,
+ },
+ ],
+ },
+ description: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'f',
+ doc_count: 1,
+ },
+ ],
+ },
+ unitsCount: {
+ value: 100,
+ },
+ severitiesSubAggregation: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'low',
+ doc_count: 100,
+ },
+ ],
+ },
+ countSeveritySubAggregation: {
+ value: 1,
+ },
+ usersCountAggregation: {
+ value: 0,
+ },
+ },
+ {
+ key: ['medium hosts', 'medium hosts'],
+ key_as_string: 'medium hosts|medium hosts',
+ doc_count: 100,
+ hostsCountAggregation: {
+ value: 30,
+ },
+ ruleTags: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'cool',
+ doc_count: 100,
+ },
+ {
+ key: 'rule',
+ doc_count: 100,
+ },
+ ],
+ },
+ description: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'f',
+ doc_count: 1,
+ },
+ ],
+ },
+ unitsCount: {
+ value: 100,
+ },
+ severitiesSubAggregation: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'medium',
+ doc_count: 100,
+ },
+ ],
+ },
+ countSeveritySubAggregation: {
+ value: 1,
+ },
+ usersCountAggregation: {
+ value: 0,
+ },
+ },
+ {
+ key: [
+ 'medium hosts [Duplicate] [Duplicate] [Duplicate]',
+ 'medium hosts [Duplicate] [Duplicate] [Duplicate]',
+ ],
+ key_as_string:
+ 'medium hosts [Duplicate] [Duplicate] [Duplicate]|medium hosts [Duplicate] [Duplicate] [Duplicate]',
+ doc_count: 100,
+ hostsCountAggregation: {
+ value: 30,
+ },
+ ruleTags: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'cool',
+ doc_count: 100,
+ },
+ {
+ key: 'rule',
+ doc_count: 100,
+ },
+ ],
+ },
+ description: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'f',
+ doc_count: 1,
+ },
+ ],
+ },
+ unitsCount: {
+ value: 100,
+ },
+ severitiesSubAggregation: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'medium',
+ doc_count: 100,
+ },
+ ],
+ },
+ countSeveritySubAggregation: {
+ value: 1,
+ },
+ usersCountAggregation: {
+ value: 0,
+ },
+ },
+ {
+ key: [
+ 'critical users [Duplicate] [Duplicate] [Duplicate]',
+ 'critical users [Duplicate] [Duplicate] [Duplicate]',
+ ],
+ key_as_string:
+ 'critical users [Duplicate] [Duplicate] [Duplicate]|critical users [Duplicate] [Duplicate] [Duplicate]',
+ doc_count: 91,
+ hostsCountAggregation: {
+ value: 10,
+ },
+ ruleTags: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'cool',
+ doc_count: 91,
+ },
+ {
+ key: 'rule',
+ doc_count: 91,
+ },
+ ],
+ },
+ description: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'f',
+ doc_count: 1,
+ },
+ ],
+ },
+ unitsCount: {
+ value: 91,
+ },
+ severitiesSubAggregation: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'critical',
+ doc_count: 91,
+ },
+ ],
+ },
+ countSeveritySubAggregation: {
+ value: 1,
+ },
+ usersCountAggregation: {
+ value: 91,
+ },
+ },
+ ],
+ },
+ description: {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'f',
+ doc_count: 1,
+ },
+ ],
},
- aggregations: {
- groupsCount: {
- value: 40,
- },
- groupByFields: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'Host-f0m6ngo8fo',
- doc_count: 75,
- rulesCountAggregation: {
- value: 3,
- },
- unitsCount: {
- value: 75,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 75,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 25,
- },
- },
- {
- key: 'Host-4aijlqggv8',
- doc_count: 63,
- rulesCountAggregation: {
- value: 3,
- },
- unitsCount: {
- value: 63,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 63,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 21,
- },
- },
- {
- key: 'Host-e50lhbdm91',
- doc_count: 51,
- rulesCountAggregation: {
- value: 3,
- },
- unitsCount: {
- value: 51,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 51,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 17,
- },
- },
- {
- key: 'sqp',
- doc_count: 42,
- rulesCountAggregation: {
- value: 3,
- },
- unitsCount: {
- value: 42,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 42,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: 'sUl',
- doc_count: 33,
- rulesCountAggregation: {
- value: 3,
- },
- unitsCount: {
- value: 33,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 33,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: 'vLJ',
- doc_count: 30,
- rulesCountAggregation: {
- value: 3,
- },
- unitsCount: {
- value: 30,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 30,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: 'Host-n28uwmsqmd',
- doc_count: 27,
- rulesCountAggregation: {
- value: 3,
- },
- unitsCount: {
- value: 27,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 27,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 9,
- },
- },
- {
- key: 'JaE',
- doc_count: 27,
- rulesCountAggregation: {
- value: 3,
- },
- unitsCount: {
- value: 27,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 27,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: 'CUA',
- doc_count: 24,
- rulesCountAggregation: {
- value: 3,
- },
- unitsCount: {
- value: 24,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 24,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: 'FWT',
- doc_count: 24,
- rulesCountAggregation: {
- value: 3,
- },
- unitsCount: {
- value: 24,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 24,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: 'ZqT',
- doc_count: 24,
- rulesCountAggregation: {
- value: 3,
- },
- unitsCount: {
- value: 24,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 24,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: 'mmn',
- doc_count: 24,
- rulesCountAggregation: {
- value: 3,
- },
- unitsCount: {
- value: 24,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 24,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: 'xRS',
- doc_count: 24,
- rulesCountAggregation: {
- value: 3,
- },
- unitsCount: {
- value: 24,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 24,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: 'HiC',
- doc_count: 21,
- rulesCountAggregation: {
- value: 3,
- },
- unitsCount: {
- value: 21,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 21,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: 'Host-d7zbfvl3zz',
- doc_count: 21,
- rulesCountAggregation: {
- value: 3,
- },
- unitsCount: {
- value: 21,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 21,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 7,
- },
- },
- {
- key: 'Nnc',
- doc_count: 21,
- rulesCountAggregation: {
- value: 3,
- },
- unitsCount: {
- value: 21,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 21,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: 'OqH',
- doc_count: 21,
- rulesCountAggregation: {
- value: 3,
- },
- unitsCount: {
- value: 21,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 21,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: 'Vaw',
- doc_count: 21,
- rulesCountAggregation: {
- value: 3,
- },
- unitsCount: {
- value: 21,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 21,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: 'XPg',
- doc_count: 21,
- rulesCountAggregation: {
- value: 3,
- },
- unitsCount: {
- value: 21,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 21,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: 'qBS',
- doc_count: 21,
- rulesCountAggregation: {
- value: 3,
- },
- unitsCount: {
- value: 21,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 21,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: 'rwt',
- doc_count: 21,
- rulesCountAggregation: {
- value: 3,
- },
- unitsCount: {
- value: 21,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 21,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: 'xVJ',
- doc_count: 21,
- rulesCountAggregation: {
- value: 3,
- },
- unitsCount: {
- value: 21,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 21,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: 'Bxg',
- doc_count: 18,
- rulesCountAggregation: {
- value: 3,
- },
- unitsCount: {
- value: 18,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 18,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: 'efP',
- doc_count: 18,
- rulesCountAggregation: {
- value: 3,
- },
- unitsCount: {
- value: 18,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 18,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- {
- key: 'qcb',
- doc_count: 18,
- rulesCountAggregation: {
- value: 3,
- },
- unitsCount: {
- value: 18,
- },
- severitiesSubAggregation: {
- doc_count_error_upper_bound: 0,
- sum_other_doc_count: 0,
- buckets: [
- {
- key: 'critical',
- doc_count: 18,
- },
- ],
- },
- countSeveritySubAggregation: {
- value: 1,
- },
- usersCountAggregation: {
- value: 0,
- },
- },
- ],
- },
- unitsCount: {
- value: 900,
- },
+ unitsCount: {
+ value: 6048,
},
},
};
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/query_builder.test.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/query_builder.test.ts
index b4f8568da2c60..0d92882d4c53a 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/query_builder.test.ts
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/query_builder.test.ts
@@ -15,6 +15,7 @@ describe('getAlertsGroupingQuery', () => {
pageIndex: 0,
pageSize: 25,
runtimeMappings: {},
+ selectedGroupEsTypes: ['keyword'],
selectedGroup: 'kibana.alert.rule.name',
additionalFilters: [
{
@@ -60,11 +61,23 @@ describe('getAlertsGroupingQuery', () => {
field: 'kibana.alert.uuid',
},
},
+ description: {
+ terms: {
+ field: 'kibana.alert.rule.description',
+ size: 1,
+ },
+ },
bucket_truncate: {
bucket_sort: {
from: 0,
size: 25,
- sort: undefined,
+ sort: [
+ {
+ unitsCount: {
+ order: 'desc',
+ },
+ },
+ ],
},
},
countSeveritySubAggregation: {
@@ -98,9 +111,11 @@ describe('getAlertsGroupingQuery', () => {
terms: [
{
field: 'kibana.alert.rule.name',
+ missing: '-',
},
{
- field: 'kibana.alert.rule.description',
+ field: 'kibana.alert.rule.name',
+ missing: '--',
},
],
},
@@ -152,6 +167,7 @@ describe('getAlertsGroupingQuery', () => {
pageIndex: 0,
pageSize: 25,
runtimeMappings: {},
+ selectedGroupEsTypes: ['keyword'],
selectedGroup: 'process.name',
additionalFilters: [
{
@@ -201,7 +217,13 @@ describe('getAlertsGroupingQuery', () => {
bucket_sort: {
from: 0,
size: 25,
- sort: undefined,
+ sort: [
+ {
+ unitsCount: {
+ order: 'desc',
+ },
+ },
+ ],
},
},
rulesCountAggregation: {
@@ -210,9 +232,18 @@ describe('getAlertsGroupingQuery', () => {
},
},
},
- terms: {
- field: 'process.name',
+ multi_terms: {
size: 10000,
+ terms: [
+ {
+ field: 'process.name',
+ missing: '-',
+ },
+ {
+ field: 'process.name',
+ missing: '--',
+ },
+ ],
},
},
},
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/query_builder.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/query_builder.ts
index 921a8d3e3d43f..cde272af8d498 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/query_builder.ts
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/query_builder.ts
@@ -10,14 +10,6 @@ import type { BoolQuery } from '@kbn/es-query';
import type { NamedAggregation } from '@kbn/securitysolution-grouping';
import { isNoneGroup, getGroupingQuery } from '@kbn/securitysolution-grouping';
-const getGroupFields = (groupValue: string) => {
- if (groupValue === 'kibana.alert.rule.name') {
- return [groupValue, 'kibana.alert.rule.description'];
- } else {
- return [groupValue];
- }
-};
-
interface AlertsGroupingQueryParams {
additionalFilters: Array<{
bool: BoolQuery;
@@ -27,6 +19,7 @@ interface AlertsGroupingQueryParams {
pageSize: number;
runtimeMappings: MappingRuntimeFields;
selectedGroup: string;
+ selectedGroupEsTypes: string[];
to: string;
}
@@ -37,12 +30,13 @@ export const getAlertsGroupingQuery = ({
pageSize,
runtimeMappings,
selectedGroup,
+ selectedGroupEsTypes,
to,
}: AlertsGroupingQueryParams) =>
getGroupingQuery({
additionalFilters,
from,
- groupByFields: !isNoneGroup([selectedGroup]) ? getGroupFields(selectedGroup) : [],
+ groupByField: selectedGroup,
statsAggregations: !isNoneGroup([selectedGroup])
? getAggregationsByGroupField(selectedGroup)
: [],
@@ -56,7 +50,9 @@ export const getAlertsGroupingQuery = ({
: []),
],
runtimeMappings,
+ selectedGroupEsTypes,
size: pageSize,
+ sort: [{ unitsCount: { order: 'desc' } }],
to,
});
@@ -74,6 +70,14 @@ const getAggregationsByGroupField = (field: string): NamedAggregation[] => {
case 'kibana.alert.rule.name':
aggMetrics.push(
...[
+ {
+ description: {
+ terms: {
+ field: 'kibana.alert.rule.description',
+ size: 1,
+ },
+ },
+ },
{
countSeveritySubAggregation: {
cardinality: {
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/types.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/types.ts
index 72d3d95b86789..d271551f83460 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/types.ts
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/types.ts
@@ -12,6 +12,9 @@ export interface AlertsGroupingAggregation {
unitsCount?: {
value?: NumberOrNull;
};
+ description?: {
+ buckets?: GenericBuckets[];
+ };
severitiesSubAggregation?: {
buckets?: GenericBuckets[];
};
diff --git a/x-pack/plugins/security_solution/public/detections/pages/alerts/alert_details_redirect.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/alerts/alert_details_redirect.test.tsx
new file mode 100644
index 0000000000000..60ed09ab4d002
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detections/pages/alerts/alert_details_redirect.test.tsx
@@ -0,0 +1,132 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import React from 'react';
+import { render } from '@testing-library/react';
+import { Router } from 'react-router-dom';
+import { AlertDetailsRedirect } from './alert_details_redirect';
+import {
+ createSecuritySolutionStorageMock,
+ mockGlobalState,
+ SUB_PLUGINS_REDUCER,
+ TestProviders,
+} from '../../../common/mock';
+import { createStore } from '../../../common/store';
+import { kibanaObservable } from '@kbn/timelines-plugin/public/mock';
+import {
+ ALERTS_PATH,
+ ALERT_DETAILS_REDIRECT_PATH,
+ DEFAULT_ALERTS_INDEX,
+} from '../../../../common/constants';
+import { mockHistory } from '../../../common/utils/route/mocks';
+
+jest.mock('../../../common/lib/kibana');
+
+const testAlertId = 'test-alert-id';
+jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'),
+ useParams: () => ({
+ alertId: testAlertId,
+ }),
+}));
+
+const testIndex = '.someTestIndex';
+const testTimestamp = '2023-04-20T12:00:00.000Z';
+const mockPathname = `${ALERT_DETAILS_REDIRECT_PATH}/${testAlertId}`;
+
+describe('AlertDetailsRedirect', () => {
+ const { storage } = createSecuritySolutionStorageMock();
+ const store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
+ afterEach(() => {
+ mockHistory.replace.mockClear();
+ });
+
+ describe('with index and timestamp query parameters set', () => {
+ it('redirects to the expected path with the correct query parameters', () => {
+ const testSearch = `?index=${testIndex}×tamp=${testTimestamp}`;
+ const historyMock = {
+ ...mockHistory,
+ location: {
+ hash: '',
+ pathname: mockPathname,
+ search: testSearch,
+ state: '',
+ },
+ };
+ render(
+
+
+
+
+
+ );
+
+ expect(historyMock.replace).toHaveBeenCalledWith({
+ hash: '',
+ pathname: ALERTS_PATH,
+ search: `?query=(language:kuery,query:'_id: ${testAlertId}')&timerange=(global:(linkTo:!(timeline,socTrends),timerange:(from:'${testTimestamp}',kind:absolute,to:'2023-04-20T12:05:00.000Z')),timeline:(linkTo:!(global,socTrends),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now/d,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now/d)))&eventFlyout=(panelView:eventDetail,params:(eventId:${testAlertId},indexName:${testIndex}))`,
+ state: undefined,
+ });
+ });
+ });
+
+ describe('with only index query parameter set', () => {
+ it('redirects to the expected path with the default global timestamp settings', () => {
+ const testSearch = `?index=${testIndex}`;
+ const historyMock = {
+ ...mockHistory,
+ location: {
+ hash: '',
+ pathname: mockPathname,
+ search: testSearch,
+ state: '',
+ },
+ };
+ render(
+
+
+
+
+
+ );
+
+ expect(historyMock.replace).toHaveBeenCalledWith({
+ hash: '',
+ pathname: ALERTS_PATH,
+ search: `?query=(language:kuery,query:'_id: ${testAlertId}')&timerange=(global:(linkTo:!(timeline,socTrends),timerange:(from:'2020-07-07T08:20:18.966Z',kind:absolute,to:'2020-07-08T08:25:18.966Z')),timeline:(linkTo:!(global,socTrends),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now/d,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now/d)))&eventFlyout=(panelView:eventDetail,params:(eventId:${testAlertId},indexName:${testIndex}))`,
+ state: undefined,
+ });
+ });
+ });
+
+ describe('with no query parameters set', () => {
+ it('redirects to the expected path with the proper default alerts index and default global timestamp setting', () => {
+ const historyMock = {
+ ...mockHistory,
+ location: {
+ hash: '',
+ pathname: mockPathname,
+ search: '',
+ state: '',
+ },
+ };
+ render(
+
+
+
+
+
+ );
+
+ expect(historyMock.replace).toHaveBeenCalledWith({
+ hash: '',
+ pathname: ALERTS_PATH,
+ search: `?query=(language:kuery,query:'_id: ${testAlertId}')&timerange=(global:(linkTo:!(timeline,socTrends),timerange:(from:'2020-07-07T08:20:18.966Z',kind:absolute,to:'2020-07-08T08:25:18.966Z')),timeline:(linkTo:!(global,socTrends),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now/d,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now/d)))&eventFlyout=(panelView:eventDetail,params:(eventId:${testAlertId},indexName:.internal${DEFAULT_ALERTS_INDEX}-default))`,
+ state: undefined,
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/detections/pages/alerts/alert_details_redirect.tsx b/x-pack/plugins/security_solution/public/detections/pages/alerts/alert_details_redirect.tsx
index c44fcfdd7e509..ec8bf7c1526e3 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/alerts/alert_details_redirect.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/alerts/alert_details_redirect.tsx
@@ -32,9 +32,9 @@ export const AlertDetailsRedirect = () => {
// Default to the existing global timerange if we don't get this query param for whatever reason
const fromTime = timestamp ?? globalTimerange.from;
- // Add 1 millisecond to the alert timestamp as the alert table is non-inclusive of the end time
- // So we have to extend slightly beyond the range of the timestamp of the given alert
- const toTime = moment(timestamp ?? globalTimerange.to).add('1', 'millisecond');
+ // Add 5 minutes to the alert timestamp as the alert table is non-inclusive of the end time
+ // This also provides padding time if the user clears the `_id` filter after redirect to see other alerts
+ const toTime = moment(timestamp ?? globalTimerange.to).add('5', 'minutes');
const timerange = encode({
global: {
diff --git a/x-pack/plugins/synthetics/common/constants/ui.ts b/x-pack/plugins/synthetics/common/constants/ui.ts
index 40c1d26c58cbc..d014b8b8ea6ff 100644
--- a/x-pack/plugins/synthetics/common/constants/ui.ts
+++ b/x-pack/plugins/synthetics/common/constants/ui.ts
@@ -105,3 +105,7 @@ export const FILTER_FIELDS = {
};
export const SYNTHETICS_INDEX_PATTERN = 'synthetics-*';
+
+export const LICENSE_NOT_ACTIVE_ERROR = 'License not active';
+export const LICENSE_MISSING_ERROR = 'Missing license information';
+export const LICENSE_NOT_SUPPORTED_ERROR = 'License not supported';
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/monitor_details_panel.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/monitor_details_panel.tsx
index c14e516fb7f67..5cddb55074c24 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/monitor_details_panel.tsx
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/monitor_details_panel.tsx
@@ -65,6 +65,8 @@ export const MonitorDetailsPanel = ({
return ;
}
+ const url = latestPing?.url?.full ?? (monitor as unknown as MonitorFields)[ConfigKey.URLS];
+
return (
{URL_LABEL}
-
- {latestPing?.url?.full ?? (monitor as unknown as MonitorFields)[ConfigKey.URLS]}
-
+ {url ? (
+
+ {url}
+
+ ) : (
+
+ {UN_AVAILABLE_LABEL}
+
+ )}
{LAST_RUN_LABEL}
@@ -260,3 +264,7 @@ const PROJECT_ID_LABEL = i18n.translate('xpack.synthetics.monitorList.projectIdH
const MONITOR_ID_ITEM_TEXT = i18n.translate('xpack.synthetics.monitorList.monitorIdItemText', {
defaultMessage: 'Monitor ID',
});
+
+const UN_AVAILABLE_LABEL = i18n.translate('xpack.synthetics.monitorList.unAvailable', {
+ defaultMessage: '(unavailable)',
+});
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/actions_popover.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/actions_popover.tsx
index 019a8c3972d65..90822b63dccfa 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/actions_popover.tsx
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/actions_popover.tsx
@@ -235,7 +235,12 @@ export function ActionsPopover({
},
},
{
- name: monitor.isStatusAlertEnabled ? disableAlertLabel : enableMonitorAlertLabel,
+ name: (
+
+ {monitor.isStatusAlertEnabled ? disableAlertLabel : enableMonitorAlertLabel}
+
+ ),
+ disabled: !canEditSynthetics,
icon: alertLoading ? (
) : monitor.isStatusAlertEnabled ? (
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/metric_item_icon.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/metric_item_icon.tsx
index a1fdf1e734d5c..7534d94ebe1b2 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/metric_item_icon.tsx
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/metric_item_icon.tsx
@@ -27,7 +27,7 @@ import { useRef } from 'react';
import { selectErrorPopoverState, toggleErrorPopoverOpen } from '../../../../state';
import { useErrorDetailsLink } from '../../../common/links/error_details_link';
import { MonitorOverviewItem, OverviewPing } from '../../../../../../../common/runtime_types';
-import { manualTestRunSelector } from '../../../../state/manual_test_runs';
+import { manualTestRunSelector, isTestRunning } from '../../../../state/manual_test_runs';
import { useFormatTestRunAt } from '../../../../utils/monitor_test_result/test_time_formats';
const Container = styled.div`
@@ -62,7 +62,7 @@ export const MetricItemIcon = ({
dispatch(toggleErrorPopoverOpen(configIdByLocation));
};
- const inProgress = testNowRun?.status === 'in-progress' || testNowRun?.status === 'loading';
+ const inProgress = isTestRunning(testNowRun);
const errorLink = useErrorDetailsLink({
configId: monitor.configId,
@@ -75,9 +75,11 @@ export const MetricItemIcon = ({
if (inProgress) {
return (
-
-
-
+
+
+
+
+
);
}
@@ -164,6 +166,10 @@ const ERROR_DETAILS = i18n.translate('xpack.synthetics.errorDetails.label', {
defaultMessage: 'Error details',
});
+const TEST_IN_PROGRESS = i18n.translate('xpack.synthetics.inProgress.label', {
+ defaultMessage: 'Manual test run is in progress.',
+});
+
const StyledIcon = euiStyled.div<{ boxShadow: string }>`
box-sizing: border-box;
display: flex;
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_enablement.ts b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_enablement.ts
index fe726d0cbe3d2..057d15218ac2e 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_enablement.ts
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_enablement.ts
@@ -15,10 +15,10 @@ export function useEnablement() {
const { loading, error, enablement } = useSelector(selectSyntheticsEnablement);
useEffect(() => {
- if (!enablement && !loading) {
+ if (!enablement && !loading && !error) {
dispatch(getSyntheticsEnablement());
}
- }, [dispatch, enablement, loading]);
+ }, [dispatch, enablement, error, loading]);
return {
enablement: {
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_synthetics_priviliges.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_synthetics_priviliges.tsx
index b1f1d57a33fd7..26f77151010e9 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_synthetics_priviliges.tsx
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_synthetics_priviliges.tsx
@@ -12,13 +12,20 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
+ EuiButton,
EuiMarkdownFormat,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { css } from '@emotion/react';
import { i18n } from '@kbn/i18n';
import { selectOverviewStatus } from '../state/overview_status';
-import { SYNTHETICS_INDEX_PATTERN } from '../../../../common/constants';
+import {
+ LICENSE_MISSING_ERROR,
+ LICENSE_NOT_ACTIVE_ERROR,
+ LICENSE_NOT_SUPPORTED_ERROR,
+ SYNTHETICS_INDEX_PATTERN,
+} from '../../../../common/constants';
+import { useSyntheticsSettingsContext } from '../contexts';
export const useSyntheticsPrivileges = () => {
const { error } = useSelector(selectOverviewStatus);
@@ -36,6 +43,24 @@ export const useSyntheticsPrivileges = () => {
);
}
+ if (
+ error?.body?.message &&
+ [LICENSE_NOT_ACTIVE_ERROR, LICENSE_MISSING_ERROR, LICENSE_NOT_SUPPORTED_ERROR].includes(
+ error?.body?.message
+ )
+ ) {
+ return (
+
+
+
+
+
+ );
+ }
};
const Unprivileged = ({ unprivilegedIndices }: { unprivilegedIndices: string[] }) => (
@@ -77,3 +102,40 @@ const Unprivileged = ({ unprivilegedIndices }: { unprivilegedIndices: string[] }
}
/>
);
+
+const LicenseExpired = () => {
+ const { basePath } = useSyntheticsSettingsContext();
+ return (
+
+
+
+ }
+ body={
+
+
+
+ }
+ actions={[
+
+ {i18n.translate('xpack.synthetics.invalidLicense.licenseManagementLink', {
+ defaultMessage: 'Manage your license',
+ })}
+ ,
+ ]}
+ />
+ );
+};
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/manual_test_runs/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/manual_test_runs/index.ts
index 9f1d5a3296f00..936efc6ada6ca 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/state/manual_test_runs/index.ts
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/manual_test_runs/index.ts
@@ -31,6 +31,9 @@ export enum TestRunStatus {
COMPLETED = 'completed',
}
+export const isTestRunning = (testRun?: ManualTestRun) =>
+ testRun?.status === TestRunStatus.IN_PROGRESS || testRun?.status === TestRunStatus.LOADING;
+
export interface ManualTestRun {
configId: string;
name: string;
diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/lib/domains/license.ts b/x-pack/plugins/synthetics/server/legacy_uptime/lib/domains/license.ts
index d921766529e8f..6a55b0459c632 100644
--- a/x-pack/plugins/synthetics/server/legacy_uptime/lib/domains/license.ts
+++ b/x-pack/plugins/synthetics/server/legacy_uptime/lib/domains/license.ts
@@ -6,6 +6,11 @@
*/
import { ILicense } from '@kbn/licensing-plugin/server';
+import {
+ LICENSE_MISSING_ERROR,
+ LICENSE_NOT_ACTIVE_ERROR,
+ LICENSE_NOT_SUPPORTED_ERROR,
+} from '../../../../common/constants';
export interface UMLicenseStatusResponse {
statusCode: number;
@@ -18,19 +23,19 @@ export type UMLicenseCheck = (
export const licenseCheck: UMLicenseCheck = (license) => {
if (license === undefined) {
return {
- message: 'Missing license information',
+ message: LICENSE_MISSING_ERROR,
statusCode: 400,
};
}
if (!license.hasAtLeast('basic')) {
return {
- message: 'License not supported',
+ message: LICENSE_NOT_SUPPORTED_ERROR,
statusCode: 401,
};
}
if (license.isActive === false) {
return {
- message: 'License not active',
+ message: LICENSE_NOT_ACTIVE_ERROR,
statusCode: 403,
};
}
diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts
index 3e420bf478dec..69fc7ce0ee6dc 100644
--- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts
+++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts
@@ -108,6 +108,10 @@ export class SyntheticsService {
}
public async setupIndexTemplates() {
+ if (process.env.CI && !this.config?.manifestUrl) {
+ // skip installation on CI
+ return;
+ }
if (this.indexTemplateExists) {
// if already installed, don't need to reinstall
return;
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx
index a983b91e27e40..1c756b242c79f 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx
@@ -278,7 +278,9 @@ export const StepDetailsForm: FC = React.memo(
// Reset retention policy settings when the user disables the whole option
useEffect(() => {
if (!isRetentionPolicyEnabled) {
- setRetentionPolicyDateField(isRetentionPolicyAvailable ? dateFieldNames[0] : '');
+ setRetentionPolicyDateField(
+ isRetentionPolicyAvailable ? dataViewAvailableTimeFields[0] : ''
+ );
setRetentionPolicyMaxAge('');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -713,7 +715,7 @@ export const StepDetailsForm: FC = React.memo(
)}
>
({ text }))}
+ options={dataViewAvailableTimeFields.map((text: string) => ({ text }))}
value={retentionPolicyDateField}
onChange={(e) => setRetentionPolicyDateField(e.target.value)}
data-test-subj="transformRetentionPolicyDateFieldSelect"
diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json
index 285e5b8a1284f..dbb73e6708986 100644
--- a/x-pack/plugins/translations/translations/fr-FR.json
+++ b/x-pack/plugins/translations/translations/fr-FR.json
@@ -21021,7 +21021,6 @@
"xpack.ml.jobsList.stopDatafeedsConfirmModal.stopButtonLabel": "Arrêter {jobsCount, plural, one {le flux de données} other {les flux de données}}",
"xpack.ml.jobsList.stopDatafeedsModal.stopDatafeedsTitle": "Arrêter le flux de données pour {jobsCount, plural, one {{jobId}} other {# tâches}} ?",
"xpack.ml.managedJobsWarningCallout": "{jobsCount, plural, one {Cette tâche} other {Au moins l'une de ces tâches}} est préconfigurée par Elastic. Le fait de {jobsCount, plural, one {la} other {les}} {action} peut avoir un impact sur d'autres éléments du produit.",
- "xpack.ml.management.jobsList.insufficientLicenseDescription": "Pour utiliser ces fonctionnalités de Machine Learning, vous devez {link}.",
"xpack.ml.management.jobsSpacesList.updateSpaces.error": "Erreur lors de la mise à jour de {id}",
"xpack.ml.management.syncSavedObjectsFlyout.datafeedsAdded.title": "Objets enregistrés sans flux de données ({count})",
"xpack.ml.management.syncSavedObjectsFlyout.datafeedsRemoved.title": "Objets enregistrés avec des ID de flux de données sans correspondance ({count})",
@@ -22498,7 +22497,6 @@
"xpack.ml.management.jobsList.accessDeniedTitle": "Accès refusé",
"xpack.ml.management.jobsList.analyticsDocsLabel": "Documents de tâches d'analyse",
"xpack.ml.management.jobsList.anomalyDetectionDocsLabel": "Documents de tâches de détection des anomalies",
- "xpack.ml.management.jobsList.insufficientLicenseDescription.link": "démarrer un essai ou mettre à niveau votre abonnement",
"xpack.ml.management.jobsList.insufficientLicenseLabel": "Mettre à niveau pour bénéficier des fonctionnalités d'abonnement",
"xpack.ml.management.jobsList.jobsListTagline": "Visualisez, exportez et importez des éléments d'analyse de Machine Learning et de détection des anomalies.",
"xpack.ml.management.jobsList.jobsListTitle": "Machine Learning",
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 8c87e175bea63..f2bc416d70cca 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -21012,7 +21012,6 @@
"xpack.ml.jobsList.startDatafeedsModal.startManagedDatafeedsDescription": "{jobsCount, plural, other {これらのジョブのうちの少なくとも1個のジョブ}}はElasticによってあらかじめ構成されています。終了日を指定して{jobsCount, plural, other {それらを}}開始すると、製品の他の部分に影響する可能性があります。",
"xpack.ml.jobsList.stopDatafeedsConfirmModal.stopButtonLabel": "{jobsCount, plural, other {データフィード}}を終了",
"xpack.ml.managedJobsWarningCallout": "{jobsCount, plural, other {これらのジョブのうちの少なくとも1個のジョブ}}はElasticによってあらかじめ構成されています。{jobsCount, plural, other {それらを}}{action}すると、製品の他の部分に影響する可能性があります。",
- "xpack.ml.management.jobsList.insufficientLicenseDescription": "これらの機械学習機能を使用するには、{link}する必要があります。",
"xpack.ml.management.jobsSpacesList.updateSpaces.error": "{id}の更新エラー",
"xpack.ml.management.syncSavedObjectsFlyout.datafeedsAdded.title": "データフィードがない選択されたオブジェクト({count})",
"xpack.ml.management.syncSavedObjectsFlyout.datafeedsRemoved.title": "データフィードIDが一致しない保存されたオブジェクト({count})",
@@ -22484,7 +22483,6 @@
"xpack.ml.management.jobsList.accessDeniedTitle": "アクセスが拒否されました",
"xpack.ml.management.jobsList.analyticsDocsLabel": "分析ジョブドキュメント",
"xpack.ml.management.jobsList.anomalyDetectionDocsLabel": "異常検知ジョブドキュメント",
- "xpack.ml.management.jobsList.insufficientLicenseDescription.link": "試用版を開始するか、サブスクリプションをアップグレード",
"xpack.ml.management.jobsList.insufficientLicenseLabel": "サブスクリプション機能のアップグレード",
"xpack.ml.management.jobsList.jobsListTagline": "機械学習分析と異常検知項目を表示、エクスポート、インポートします。",
"xpack.ml.management.jobsList.jobsListTitle": "機械学習",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 23ecf9913249e..6fdc03569d231 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -21020,7 +21020,6 @@
"xpack.ml.jobsList.stopDatafeedsConfirmModal.stopButtonLabel": "停止{jobsCount, plural, other {数据馈送}}",
"xpack.ml.jobsList.stopDatafeedsModal.stopDatafeedsTitle": "停止 {jobsCount, plural, one {{jobId}} other {# 个作业}}的数据馈送?",
"xpack.ml.managedJobsWarningCallout": "{jobsCount, plural, one {此作业} other {至少一个此类作业}}由 Elastic 预配置;{action} {jobsCount, plural, one {此作业} other {这些作业}}可能会影响该产品的其他部分。",
- "xpack.ml.management.jobsList.insufficientLicenseDescription": "要使用这些 Machine Learning 功能,必须{link}。",
"xpack.ml.management.jobsSpacesList.updateSpaces.error": "更新 {id} 时出错",
"xpack.ml.management.syncSavedObjectsFlyout.datafeedsAdded.title": "缺失数据馈送的已保存对象 ({count})",
"xpack.ml.management.syncSavedObjectsFlyout.datafeedsRemoved.title": "具有不匹配数据馈送 ID 的已保存对象 ({count})",
@@ -22497,7 +22496,6 @@
"xpack.ml.management.jobsList.accessDeniedTitle": "访问被拒绝",
"xpack.ml.management.jobsList.analyticsDocsLabel": "分析作业文档",
"xpack.ml.management.jobsList.anomalyDetectionDocsLabel": "异常检测作业文档",
- "xpack.ml.management.jobsList.insufficientLicenseDescription.link": "开始试用或升级您的订阅",
"xpack.ml.management.jobsList.insufficientLicenseLabel": "升级以使用订阅功能",
"xpack.ml.management.jobsList.jobsListTagline": "查看、导出和导入 Machine Learning 分析和异常检测项目。",
"xpack.ml.management.jobsList.jobsListTitle": "Machine Learning",
diff --git a/x-pack/test/accessibility/apps/ingest_node_pipelines.ts b/x-pack/test/accessibility/apps/ingest_node_pipelines.ts
index 4bbd9cde06d2d..0f57acd9ed773 100644
--- a/x-pack/test/accessibility/apps/ingest_node_pipelines.ts
+++ b/x-pack/test/accessibility/apps/ingest_node_pipelines.ts
@@ -14,7 +14,8 @@ export default function ({ getService, getPageObjects }: any) {
const log = getService('log');
const a11y = getService('a11y'); /* this is the wrapping service around axe */
- describe('Ingest Pipelines Accessibility', async () => {
+ // Failing: See https://github.com/elastic/kibana/issues/155928
+ describe.skip('Ingest Pipelines Accessibility', async () => {
before(async () => {
await putSamplePipeline(esClient);
await common.navigateToApp('ingestPipelines');
diff --git a/x-pack/test/api_integration/config.ts b/x-pack/test/api_integration/config.ts
index 5766a9efdf982..e43c76d42adfa 100644
--- a/x-pack/test/api_integration/config.ts
+++ b/x-pack/test/api_integration/config.ts
@@ -30,9 +30,6 @@ export async function getApiIntegrationConfig({ readConfigFile }: FtrConfigProvi
'--xpack.ruleRegistry.write.enabled=true',
'--xpack.ruleRegistry.write.enabled=true',
'--xpack.ruleRegistry.write.cache.enabled=false',
- '--xpack.uptime.service.password=test',
- '--xpack.uptime.service.username=localKibanaIntegrationTestsUser',
- '--xpack.uptime.service.devUrl=mockDevUrl',
'--monitoring_collection.opentelemetry.metrics.prometheus.enabled=true',
],
},
diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/no_public_base_url/push.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/no_public_base_url/push.ts
index f0b07da160674..062a697ebb22e 100644
--- a/x-pack/test/cases_api_integration/security_and_spaces/tests/no_public_base_url/push.ts
+++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/no_public_base_url/push.ts
@@ -7,6 +7,7 @@
import expect from '@kbn/expect';
import { RecordingServiceNowSimulator } from '@kbn/actions-simulators-plugin/server/servicenow_simulation';
+import { arraysToEqual } from '../../../common/lib/validation';
import {
postCommentUserReq,
postCommentAlertReq,
@@ -32,8 +33,7 @@ export default ({ getService }: FtrProviderContext): void => {
const supertest = getService('supertest');
const es = getService('es');
- // Failing: See https://github.com/elastic/kibana/issues/154640
- describe.skip('push_case', () => {
+ describe('push_case', () => {
describe('incident recorder server', () => {
const actionsRemover = new ActionsRemover(supertest);
let serviceNowSimulatorURL: string = '';
@@ -114,6 +114,14 @@ export default ({ getService }: FtrProviderContext): void => {
Boolean(request.work_notes)
);
+ const allWorkNotes = allCommentRequests.map((request) => request.work_notes);
+ const expectedNotes = [
+ 'This is a cool comment\n\nAdded by elastic.',
+ 'Isolated host host-name with comment: comment text\n\nAdded by elastic.',
+ 'Released host host-name with comment: comment text\n\nAdded by elastic.',
+ 'Elastic Alerts attached to the case: 3',
+ ];
+
/**
* For each of these comments a request is made:
* postCommentUserReq, postCommentActionsReq, postCommentActionsReleaseReq, and a comment with the
@@ -122,21 +130,9 @@ export default ({ getService }: FtrProviderContext): void => {
*/
expect(allCommentRequests.length).be(4);
- // User comment: postCommentUserReq
- expect(allCommentRequests[0].work_notes).eql('This is a cool comment\n\nAdded by elastic.');
-
- // Isolate host comment: postCommentActionsReq
- expect(allCommentRequests[1].work_notes).eql(
- 'Isolated host host-name with comment: comment text\n\nAdded by elastic.'
- );
-
- // Unisolate host comment: postCommentActionsReleaseReq
- expect(allCommentRequests[2].work_notes).eql(
- 'Released host host-name with comment: comment text\n\nAdded by elastic.'
- );
-
- // Total alerts
- expect(allCommentRequests[3].work_notes).eql('Elastic Alerts attached to the case: 3');
+ // since we're using a bulk create we can't guarantee the ordering so we'll check that the values exist but not
+ // there specific order in the results
+ expect(arraysToEqual(allWorkNotes, expectedNotes)).to.be(true);
});
});
});
diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/cases/push_case.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/cases/push_case.ts
index a9d4382fc08bc..608153386ffe5 100644
--- a/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/cases/push_case.ts
+++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/cases/push_case.ts
@@ -60,6 +60,7 @@ import {
secOnlyRead,
superUser,
} from '../../../../common/lib/authentication/users';
+import { arraysToEqual } from '../../../../common/lib/validation';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext): void => {
@@ -217,6 +218,14 @@ export default ({ getService }: FtrProviderContext): void => {
Boolean(request.work_notes)
);
+ const allWorkNotes = allCommentRequests.map((request) => request.work_notes);
+ const expectedNotes = [
+ 'This is a cool comment\n\nAdded by elastic.',
+ 'Isolated host host-name with comment: comment text\n\nAdded by elastic.',
+ 'Released host host-name with comment: comment text\n\nAdded by elastic.',
+ `Elastic Alerts attached to the case: 3\n\nFor more details, view the alerts in Kibana\nAlerts URL: https://localhost:5601/app/management/insightsAndAlerting/cases/${patchedCase.id}/?tabId=alerts`,
+ ];
+
/**
* For each of these comments a request is made:
* postCommentUserReq, postCommentActionsReq, postCommentActionsReleaseReq, and a comment with the
@@ -225,23 +234,9 @@ export default ({ getService }: FtrProviderContext): void => {
*/
expect(allCommentRequests.length).be(4);
- // User comment: postCommentUserReq
- expect(allCommentRequests[0].work_notes).eql('This is a cool comment\n\nAdded by elastic.');
-
- // Isolate host comment: postCommentActionsReq
- expect(allCommentRequests[1].work_notes).eql(
- 'Isolated host host-name with comment: comment text\n\nAdded by elastic.'
- );
-
- // Unisolate host comment: postCommentActionsReleaseReq
- expect(allCommentRequests[2].work_notes).eql(
- 'Released host host-name with comment: comment text\n\nAdded by elastic.'
- );
-
- // Total alerts
- expect(allCommentRequests[3].work_notes).eql(
- `Elastic Alerts attached to the case: 3\n\nFor more details, view the alerts in Kibana\nAlerts URL: https://localhost:5601/app/management/insightsAndAlerting/cases/${patchedCase.id}/?tabId=alerts`
- );
+ // since we're using a bulk create we can't guarantee the ordering so we'll check that the values exist but not
+ // there specific order in the results
+ expect(arraysToEqual(allWorkNotes, expectedNotes)).to.be(true);
});
it('should format the totalAlerts with spaceId correctly', async () => {
diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/threat_match.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/threat_match.ts
index c381c216bbf71..912fa62e8447d 100644
--- a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/threat_match.ts
+++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/threat_match.ts
@@ -145,7 +145,8 @@ export default ({ getService }: FtrProviderContext) => {
/**
* Specific api integration tests for threat matching rule type
*/
- describe('Threat match type rules', () => {
+ // FLAKY: https://github.com/elastic/kibana/issues/155304
+ describe.skip('Threat match type rules', () => {
before(async () => {
// await deleteSignalsIndex(supertest, log);
// await deleteAllAlerts(supertest, log);
diff --git a/x-pack/test/examples/search_examples/partial_results_example.ts b/x-pack/test/examples/search_examples/partial_results_example.ts
index f7d199463e256..269b2e79ab38f 100644
--- a/x-pack/test/examples/search_examples/partial_results_example.ts
+++ b/x-pack/test/examples/search_examples/partial_results_example.ts
@@ -14,8 +14,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const PageObjects = getPageObjects(['common']);
const retry = getService('retry');
- // FLAKY: https://github.com/elastic/kibana/issues/116038
- describe.skip('Partial results example', () => {
+ describe('Partial results example', () => {
before(async () => {
await PageObjects.common.navigateToApp('searchExamples');
await testSubjects.click('/search');
diff --git a/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts b/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts
index aa798a0cd7d98..2c275a723c6ff 100644
--- a/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts
+++ b/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts
@@ -39,7 +39,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await PageObjects.timePicker.setDefaultAbsoluteRange();
}
- describe('discover feature controls security', () => {
+ // Failing: See https://github.com/elastic/kibana/issues/153796
+ describe.skip('discover feature controls security', () => {
before(async () => {
await kibanaServer.importExport.load(
'x-pack/test/functional/fixtures/kbn_archiver/discover/feature_controls/security'
diff --git a/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts b/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts
index baae09f47c530..479d5972a8673 100644
--- a/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts
+++ b/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts
@@ -26,7 +26,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const es = getService('es');
const security = getService('security');
- describe('Ingest Pipelines', function () {
+ // Failing: See https://github.com/elastic/kibana/issues/156014
+ // Failing: See https://github.com/elastic/kibana/issues/118593
+ // Failing: See https://github.com/elastic/kibana/issues/156015
+ describe.skip('Ingest Pipelines', function () {
this.tags('smoke');
before(async () => {
await security.testUser.setRoles(['ingest_pipelines_user']);
diff --git a/x-pack/test/functional/apps/lens/group1/index.ts b/x-pack/test/functional/apps/lens/group1/index.ts
index a129a66c519d9..67d4e8023e4fc 100644
--- a/x-pack/test/functional/apps/lens/group1/index.ts
+++ b/x-pack/test/functional/apps/lens/group1/index.ts
@@ -37,7 +37,7 @@ export default ({ getService, loadTestFile, getPageObjects }: FtrProviderContext
};
let indexPatternString: string;
before(async () => {
- await log.debug('Starting lens before method');
+ log.debug('Starting lens before method');
await browser.setWindowSize(1280, 1200);
await kibanaServer.savedObjects.cleanStandardList();
try {
@@ -74,16 +74,9 @@ export default ({ getService, loadTestFile, getPageObjects }: FtrProviderContext
if (config.get('esTestCluster.ccs')) {
loadTestFile(require.resolve('./smokescreen'));
} else {
- loadTestFile(require.resolve('./smokescreen'));
- loadTestFile(require.resolve('./ad_hoc_data_view'));
- loadTestFile(require.resolve('./partition'));
- loadTestFile(require.resolve('./persistent_context'));
- loadTestFile(require.resolve('./table_dashboard'));
- loadTestFile(require.resolve('./table'));
- loadTestFile(require.resolve('./text_based_languages'));
- loadTestFile(require.resolve('./fields_list'));
- loadTestFile(require.resolve('./layer_actions'));
- loadTestFile(require.resolve('./field_formatters'));
+ // total run time ~16 min
+ loadTestFile(require.resolve('./smokescreen')); // 12m 12s
+ loadTestFile(require.resolve('./ad_hoc_data_view')); // 3m 40s
}
});
};
diff --git a/x-pack/test/functional/apps/lens/group1/field_formatters.ts b/x-pack/test/functional/apps/lens/group2/field_formatters.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group1/field_formatters.ts
rename to x-pack/test/functional/apps/lens/group2/field_formatters.ts
diff --git a/x-pack/test/functional/apps/lens/group1/fields_list.ts b/x-pack/test/functional/apps/lens/group2/fields_list.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group1/fields_list.ts
rename to x-pack/test/functional/apps/lens/group2/fields_list.ts
diff --git a/x-pack/test/functional/apps/lens/group2/index.ts b/x-pack/test/functional/apps/lens/group2/index.ts
index 277b415a9ab49..18302745362ab 100644
--- a/x-pack/test/functional/apps/lens/group2/index.ts
+++ b/x-pack/test/functional/apps/lens/group2/index.ts
@@ -14,22 +14,14 @@ export default ({ getService, loadTestFile, getPageObjects }: FtrProviderContext
const esArchiver = getService('esArchiver');
const kibanaServer = getService('kibanaServer');
const PageObjects = getPageObjects(['timePicker']);
- const config = getService('config');
- let remoteEsArchiver;
describe('lens app - group 2', () => {
const esArchive = 'x-pack/test/functional/es_archives/logstash_functional';
const localIndexPatternString = 'logstash-*';
- const remoteIndexPatternString = 'ftr-remote:logstash-*';
const localFixtures = {
lensBasic: 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json',
lensDefault: 'x-pack/test/functional/fixtures/kbn_archiver/lens/default',
};
-
- const remoteFixtures = {
- lensBasic: 'x-pack/test/functional/fixtures/kbn_archiver/lens/ccs/lens_basic.json',
- lensDefault: 'x-pack/test/functional/fixtures/kbn_archiver/lens/ccs/default',
- };
let esNode: EsArchiver;
let fixtureDirs: {
lensBasic: string;
@@ -37,21 +29,12 @@ export default ({ getService, loadTestFile, getPageObjects }: FtrProviderContext
};
let indexPatternString: string;
before(async () => {
- await log.debug('Starting lens before method');
+ log.debug('Starting lens before method');
await browser.setWindowSize(1280, 1200);
await kibanaServer.savedObjects.cleanStandardList();
- try {
- config.get('esTestCluster.ccs');
- remoteEsArchiver = getService('remoteEsArchiver' as 'esArchiver');
- esNode = remoteEsArchiver;
- fixtureDirs = remoteFixtures;
- indexPatternString = remoteIndexPatternString;
- } catch (error) {
- esNode = esArchiver;
- fixtureDirs = localFixtures;
- indexPatternString = localIndexPatternString;
- }
-
+ esNode = esArchiver;
+ fixtureDirs = localFixtures;
+ indexPatternString = localIndexPatternString;
await esNode.load(esArchive);
// changing the timepicker default here saves us from having to set it in Discover (~8s)
await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings();
@@ -64,21 +47,21 @@ export default ({ getService, loadTestFile, getPageObjects }: FtrProviderContext
});
after(async () => {
- await esArchiver.unload(esArchive);
+ await esNode.unload(esArchive);
await PageObjects.timePicker.resetDefaultAbsoluteRangeViaUiSettings();
await kibanaServer.importExport.unload(fixtureDirs.lensBasic);
await kibanaServer.importExport.unload(fixtureDirs.lensDefault);
await kibanaServer.savedObjects.cleanStandardList();
});
- loadTestFile(require.resolve('./add_to_dashboard'));
- loadTestFile(require.resolve('./runtime_fields'));
- loadTestFile(require.resolve('./dashboard'));
- loadTestFile(require.resolve('./terms'));
- loadTestFile(require.resolve('./epoch_millis'));
- loadTestFile(require.resolve('./show_underlying_data'));
- loadTestFile(require.resolve('./show_underlying_data_dashboard'));
- loadTestFile(require.resolve('./share'));
- loadTestFile(require.resolve('./tsdb'));
+ // total run time ~ 16m 20s
+ loadTestFile(require.resolve('./partition')); // 1m 40s
+ loadTestFile(require.resolve('./persistent_context')); // 1m
+ loadTestFile(require.resolve('./table_dashboard')); // 3m 10s
+ loadTestFile(require.resolve('./table')); // 1m 40s
+ loadTestFile(require.resolve('./text_based_languages')); // 3m 40s
+ loadTestFile(require.resolve('./fields_list')); // 2m 7s
+ loadTestFile(require.resolve('./layer_actions')); // 1m 45s
+ loadTestFile(require.resolve('./field_formatters')); // 1m 30s
});
};
diff --git a/x-pack/test/functional/apps/lens/group1/layer_actions.ts b/x-pack/test/functional/apps/lens/group2/layer_actions.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group1/layer_actions.ts
rename to x-pack/test/functional/apps/lens/group2/layer_actions.ts
diff --git a/x-pack/test/functional/apps/lens/group1/partition.ts b/x-pack/test/functional/apps/lens/group2/partition.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group1/partition.ts
rename to x-pack/test/functional/apps/lens/group2/partition.ts
diff --git a/x-pack/test/functional/apps/lens/group1/persistent_context.ts b/x-pack/test/functional/apps/lens/group2/persistent_context.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group1/persistent_context.ts
rename to x-pack/test/functional/apps/lens/group2/persistent_context.ts
diff --git a/x-pack/test/functional/apps/lens/group1/table.ts b/x-pack/test/functional/apps/lens/group2/table.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group1/table.ts
rename to x-pack/test/functional/apps/lens/group2/table.ts
diff --git a/x-pack/test/functional/apps/lens/group1/table_dashboard.ts b/x-pack/test/functional/apps/lens/group2/table_dashboard.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group1/table_dashboard.ts
rename to x-pack/test/functional/apps/lens/group2/table_dashboard.ts
diff --git a/x-pack/test/functional/apps/lens/group1/text_based_languages.ts b/x-pack/test/functional/apps/lens/group2/text_based_languages.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group1/text_based_languages.ts
rename to x-pack/test/functional/apps/lens/group2/text_based_languages.ts
diff --git a/x-pack/test/functional/apps/lens/group2/add_to_dashboard.ts b/x-pack/test/functional/apps/lens/group3/add_to_dashboard.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group2/add_to_dashboard.ts
rename to x-pack/test/functional/apps/lens/group3/add_to_dashboard.ts
diff --git a/x-pack/test/functional/apps/lens/group2/epoch_millis.ts b/x-pack/test/functional/apps/lens/group3/epoch_millis.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group2/epoch_millis.ts
rename to x-pack/test/functional/apps/lens/group3/epoch_millis.ts
diff --git a/x-pack/test/functional/apps/lens/group3/index.ts b/x-pack/test/functional/apps/lens/group3/index.ts
index 315d905f300e9..aa2112f248f71 100644
--- a/x-pack/test/functional/apps/lens/group3/index.ts
+++ b/x-pack/test/functional/apps/lens/group3/index.ts
@@ -71,25 +71,10 @@ export default ({ getService, loadTestFile, getPageObjects }: FtrProviderContext
await kibanaServer.savedObjects.cleanStandardList();
});
- loadTestFile(require.resolve('./colors'));
- loadTestFile(require.resolve('./chart_data'));
- loadTestFile(require.resolve('./time_shift'));
- loadTestFile(require.resolve('./drag_and_drop'));
- loadTestFile(require.resolve('./disable_auto_apply'));
- loadTestFile(require.resolve('./geo_field'));
- loadTestFile(require.resolve('./formula'));
- loadTestFile(require.resolve('./heatmap'));
- loadTestFile(require.resolve('./gauge'));
- loadTestFile(require.resolve('./metric'));
- loadTestFile(require.resolve('./legacy_metric'));
- loadTestFile(require.resolve('./reference_lines'));
- loadTestFile(require.resolve('./annotations'));
- loadTestFile(require.resolve('./inspector'));
- loadTestFile(require.resolve('./error_handling'));
- loadTestFile(require.resolve('./lens_tagging'));
- loadTestFile(require.resolve('./lens_reporting'));
- // keep these two last in the group in this order because they are messing with the default saved objects
- loadTestFile(require.resolve('./rollup'));
- loadTestFile(require.resolve('./no_data'));
+ // total run time ~16 min
+ loadTestFile(require.resolve('./add_to_dashboard')); // 12m 50s
+ loadTestFile(require.resolve('./runtime_fields')); // 1m
+ loadTestFile(require.resolve('./terms')); // 1m 35s
+ loadTestFile(require.resolve('./epoch_millis')); // 30s
});
};
diff --git a/x-pack/test/functional/apps/lens/group2/runtime_fields.ts b/x-pack/test/functional/apps/lens/group3/runtime_fields.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group2/runtime_fields.ts
rename to x-pack/test/functional/apps/lens/group3/runtime_fields.ts
diff --git a/x-pack/test/functional/apps/lens/group2/terms.ts b/x-pack/test/functional/apps/lens/group3/terms.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group2/terms.ts
rename to x-pack/test/functional/apps/lens/group3/terms.ts
diff --git a/x-pack/test/functional/apps/lens/group3/chart_data.ts b/x-pack/test/functional/apps/lens/group4/chart_data.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group3/chart_data.ts
rename to x-pack/test/functional/apps/lens/group4/chart_data.ts
diff --git a/x-pack/test/functional/apps/lens/group3/colors.ts b/x-pack/test/functional/apps/lens/group4/colors.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group3/colors.ts
rename to x-pack/test/functional/apps/lens/group4/colors.ts
diff --git a/x-pack/test/functional/apps/lens/group4/config.ts b/x-pack/test/functional/apps/lens/group4/config.ts
new file mode 100644
index 0000000000000..d927f93adeffd
--- /dev/null
+++ b/x-pack/test/functional/apps/lens/group4/config.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 { FtrConfigProviderContext } from '@kbn/test';
+
+export default async function ({ readConfigFile }: FtrConfigProviderContext) {
+ const functionalConfig = await readConfigFile(require.resolve('../../../config.base.js'));
+
+ return {
+ ...functionalConfig.getAll(),
+ testFiles: [require.resolve('.')],
+ };
+}
diff --git a/x-pack/test/functional/apps/lens/group2/dashboard.ts b/x-pack/test/functional/apps/lens/group4/dashboard.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group2/dashboard.ts
rename to x-pack/test/functional/apps/lens/group4/dashboard.ts
diff --git a/x-pack/test/functional/apps/lens/group4/index.ts b/x-pack/test/functional/apps/lens/group4/index.ts
new file mode 100644
index 0000000000000..13cfa3fe421e1
--- /dev/null
+++ b/x-pack/test/functional/apps/lens/group4/index.ts
@@ -0,0 +1,85 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { EsArchiver } from '@kbn/es-archiver';
+import { FtrProviderContext } from '../../../ftr_provider_context';
+
+export default ({ getService, loadTestFile, getPageObjects }: FtrProviderContext) => {
+ const browser = getService('browser');
+ const log = getService('log');
+ const esArchiver = getService('esArchiver');
+ const kibanaServer = getService('kibanaServer');
+ const PageObjects = getPageObjects(['timePicker']);
+ const config = getService('config');
+ let remoteEsArchiver;
+
+ describe('lens app - group 4', () => {
+ const esArchive = 'x-pack/test/functional/es_archives/logstash_functional';
+ const localIndexPatternString = 'logstash-*';
+ const remoteIndexPatternString = 'ftr-remote:logstash-*';
+ const localFixtures = {
+ lensBasic: 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json',
+ lensDefault: 'x-pack/test/functional/fixtures/kbn_archiver/lens/default',
+ };
+
+ const remoteFixtures = {
+ lensBasic: 'x-pack/test/functional/fixtures/kbn_archiver/lens/ccs/lens_basic.json',
+ lensDefault: 'x-pack/test/functional/fixtures/kbn_archiver/lens/ccs/default',
+ };
+ let esNode: EsArchiver;
+ let fixtureDirs: {
+ lensBasic: string;
+ lensDefault: string;
+ };
+ let indexPatternString: string;
+ before(async () => {
+ await log.debug('Starting lens before method');
+ await browser.setWindowSize(1280, 1200);
+ await kibanaServer.savedObjects.cleanStandardList();
+ try {
+ config.get('esTestCluster.ccs');
+ remoteEsArchiver = getService('remoteEsArchiver' as 'esArchiver');
+ esNode = remoteEsArchiver;
+ fixtureDirs = remoteFixtures;
+ indexPatternString = remoteIndexPatternString;
+ } catch (error) {
+ esNode = esArchiver;
+ fixtureDirs = localFixtures;
+ indexPatternString = localIndexPatternString;
+ }
+
+ await esNode.load(esArchive);
+ // changing the timepicker default here saves us from having to set it in Discover (~8s)
+ await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings();
+ await kibanaServer.uiSettings.update({
+ defaultIndex: indexPatternString,
+ 'dateFormat:tz': 'UTC',
+ });
+ await kibanaServer.importExport.load(fixtureDirs.lensBasic);
+ await kibanaServer.importExport.load(fixtureDirs.lensDefault);
+ });
+
+ after(async () => {
+ await esArchiver.unload(esArchive);
+ await PageObjects.timePicker.resetDefaultAbsoluteRangeViaUiSettings();
+ await kibanaServer.importExport.unload(fixtureDirs.lensBasic);
+ await kibanaServer.importExport.unload(fixtureDirs.lensDefault);
+ await kibanaServer.savedObjects.cleanStandardList();
+ });
+
+ // total run time ~16m 30s
+ loadTestFile(require.resolve('./colors')); // 1m 2s
+ loadTestFile(require.resolve('./chart_data')); // 1m 10s
+ loadTestFile(require.resolve('./time_shift')); // 1m
+ loadTestFile(require.resolve('./dashboard')); // 6m 45s
+ loadTestFile(require.resolve('./show_underlying_data')); // 2m
+ loadTestFile(require.resolve('./show_underlying_data_dashboard')); // 2m 10s
+ loadTestFile(require.resolve('./share')); // 1m 20s
+ // keep it last in the group
+ loadTestFile(require.resolve('./tsdb')); // 1m
+ });
+};
diff --git a/x-pack/test/functional/apps/lens/group2/share.ts b/x-pack/test/functional/apps/lens/group4/share.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group2/share.ts
rename to x-pack/test/functional/apps/lens/group4/share.ts
diff --git a/x-pack/test/functional/apps/lens/group2/show_underlying_data.ts b/x-pack/test/functional/apps/lens/group4/show_underlying_data.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group2/show_underlying_data.ts
rename to x-pack/test/functional/apps/lens/group4/show_underlying_data.ts
diff --git a/x-pack/test/functional/apps/lens/group2/show_underlying_data_dashboard.ts b/x-pack/test/functional/apps/lens/group4/show_underlying_data_dashboard.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group2/show_underlying_data_dashboard.ts
rename to x-pack/test/functional/apps/lens/group4/show_underlying_data_dashboard.ts
diff --git a/x-pack/test/functional/apps/lens/group3/time_shift.ts b/x-pack/test/functional/apps/lens/group4/time_shift.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group3/time_shift.ts
rename to x-pack/test/functional/apps/lens/group4/time_shift.ts
diff --git a/x-pack/test/functional/apps/lens/group2/tsdb.ts b/x-pack/test/functional/apps/lens/group4/tsdb.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group2/tsdb.ts
rename to x-pack/test/functional/apps/lens/group4/tsdb.ts
diff --git a/x-pack/test/functional/apps/lens/group5/config.ts b/x-pack/test/functional/apps/lens/group5/config.ts
new file mode 100644
index 0000000000000..d927f93adeffd
--- /dev/null
+++ b/x-pack/test/functional/apps/lens/group5/config.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 { FtrConfigProviderContext } from '@kbn/test';
+
+export default async function ({ readConfigFile }: FtrConfigProviderContext) {
+ const functionalConfig = await readConfigFile(require.resolve('../../../config.base.js'));
+
+ return {
+ ...functionalConfig.getAll(),
+ testFiles: [require.resolve('.')],
+ };
+}
diff --git a/x-pack/test/functional/apps/lens/group3/drag_and_drop.ts b/x-pack/test/functional/apps/lens/group5/drag_and_drop.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group3/drag_and_drop.ts
rename to x-pack/test/functional/apps/lens/group5/drag_and_drop.ts
diff --git a/x-pack/test/functional/apps/lens/group3/formula.ts b/x-pack/test/functional/apps/lens/group5/formula.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group3/formula.ts
rename to x-pack/test/functional/apps/lens/group5/formula.ts
diff --git a/x-pack/test/functional/apps/lens/group3/gauge.ts b/x-pack/test/functional/apps/lens/group5/gauge.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group3/gauge.ts
rename to x-pack/test/functional/apps/lens/group5/gauge.ts
diff --git a/x-pack/test/functional/apps/lens/group3/geo_field.ts b/x-pack/test/functional/apps/lens/group5/geo_field.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group3/geo_field.ts
rename to x-pack/test/functional/apps/lens/group5/geo_field.ts
diff --git a/x-pack/test/functional/apps/lens/group3/heatmap.ts b/x-pack/test/functional/apps/lens/group5/heatmap.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group3/heatmap.ts
rename to x-pack/test/functional/apps/lens/group5/heatmap.ts
diff --git a/x-pack/test/functional/apps/lens/group5/index.ts b/x-pack/test/functional/apps/lens/group5/index.ts
new file mode 100644
index 0000000000000..5dabfd4a71499
--- /dev/null
+++ b/x-pack/test/functional/apps/lens/group5/index.ts
@@ -0,0 +1,81 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { EsArchiver } from '@kbn/es-archiver';
+import { FtrProviderContext } from '../../../ftr_provider_context';
+
+export default ({ getService, loadTestFile, getPageObjects }: FtrProviderContext) => {
+ const browser = getService('browser');
+ const log = getService('log');
+ const esArchiver = getService('esArchiver');
+ const kibanaServer = getService('kibanaServer');
+ const PageObjects = getPageObjects(['timePicker']);
+ const config = getService('config');
+ let remoteEsArchiver;
+
+ describe('lens app - group 5', () => {
+ const esArchive = 'x-pack/test/functional/es_archives/logstash_functional';
+ const localIndexPatternString = 'logstash-*';
+ const remoteIndexPatternString = 'ftr-remote:logstash-*';
+ const localFixtures = {
+ lensBasic: 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json',
+ lensDefault: 'x-pack/test/functional/fixtures/kbn_archiver/lens/default',
+ };
+
+ const remoteFixtures = {
+ lensBasic: 'x-pack/test/functional/fixtures/kbn_archiver/lens/ccs/lens_basic.json',
+ lensDefault: 'x-pack/test/functional/fixtures/kbn_archiver/lens/ccs/default',
+ };
+ let esNode: EsArchiver;
+ let fixtureDirs: {
+ lensBasic: string;
+ lensDefault: string;
+ };
+ let indexPatternString: string;
+ before(async () => {
+ log.debug('Starting lens before method');
+ await browser.setWindowSize(1280, 1200);
+ await kibanaServer.savedObjects.cleanStandardList();
+ try {
+ config.get('esTestCluster.ccs');
+ remoteEsArchiver = getService('remoteEsArchiver' as 'esArchiver');
+ esNode = remoteEsArchiver;
+ fixtureDirs = remoteFixtures;
+ indexPatternString = remoteIndexPatternString;
+ } catch (error) {
+ esNode = esArchiver;
+ fixtureDirs = localFixtures;
+ indexPatternString = localIndexPatternString;
+ }
+
+ await esNode.load(esArchive);
+ // changing the timepicker default here saves us from having to set it in Discover (~8s)
+ await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings();
+ await kibanaServer.uiSettings.update({
+ defaultIndex: indexPatternString,
+ 'dateFormat:tz': 'UTC',
+ });
+ await kibanaServer.importExport.load(fixtureDirs.lensBasic);
+ await kibanaServer.importExport.load(fixtureDirs.lensDefault);
+ });
+
+ after(async () => {
+ await esArchiver.unload(esArchive);
+ await PageObjects.timePicker.resetDefaultAbsoluteRangeViaUiSettings();
+ await kibanaServer.importExport.unload(fixtureDirs.lensBasic);
+ await kibanaServer.importExport.unload(fixtureDirs.lensDefault);
+ await kibanaServer.savedObjects.cleanStandardList();
+ });
+
+ // total run time ~ 16m
+ loadTestFile(require.resolve('./drag_and_drop')); // 7m 40s
+ loadTestFile(require.resolve('./geo_field')); // 26s
+ loadTestFile(require.resolve('./formula')); // 5m 52s
+ loadTestFile(require.resolve('./heatmap')); // 51s
+ loadTestFile(require.resolve('./gauge')); // 1m 17s
+ });
+};
diff --git a/x-pack/test/functional/apps/lens/group3/annotations.ts b/x-pack/test/functional/apps/lens/group6/annotations.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group3/annotations.ts
rename to x-pack/test/functional/apps/lens/group6/annotations.ts
diff --git a/x-pack/test/functional/apps/lens/group6/config.ts b/x-pack/test/functional/apps/lens/group6/config.ts
new file mode 100644
index 0000000000000..d927f93adeffd
--- /dev/null
+++ b/x-pack/test/functional/apps/lens/group6/config.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 { FtrConfigProviderContext } from '@kbn/test';
+
+export default async function ({ readConfigFile }: FtrConfigProviderContext) {
+ const functionalConfig = await readConfigFile(require.resolve('../../../config.base.js'));
+
+ return {
+ ...functionalConfig.getAll(),
+ testFiles: [require.resolve('.')],
+ };
+}
diff --git a/x-pack/test/functional/apps/lens/group3/disable_auto_apply.ts b/x-pack/test/functional/apps/lens/group6/disable_auto_apply.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group3/disable_auto_apply.ts
rename to x-pack/test/functional/apps/lens/group6/disable_auto_apply.ts
diff --git a/x-pack/test/functional/apps/lens/group3/error_handling.ts b/x-pack/test/functional/apps/lens/group6/error_handling.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group3/error_handling.ts
rename to x-pack/test/functional/apps/lens/group6/error_handling.ts
diff --git a/x-pack/test/functional/apps/lens/group6/index.ts b/x-pack/test/functional/apps/lens/group6/index.ts
new file mode 100644
index 0000000000000..bc59c2878805e
--- /dev/null
+++ b/x-pack/test/functional/apps/lens/group6/index.ts
@@ -0,0 +1,88 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { EsArchiver } from '@kbn/es-archiver';
+import { FtrProviderContext } from '../../../ftr_provider_context';
+
+export default ({ getService, loadTestFile, getPageObjects }: FtrProviderContext) => {
+ const browser = getService('browser');
+ const log = getService('log');
+ const esArchiver = getService('esArchiver');
+ const kibanaServer = getService('kibanaServer');
+ const PageObjects = getPageObjects(['timePicker']);
+ const config = getService('config');
+ let remoteEsArchiver;
+
+ describe('lens app - group 6', () => {
+ const esArchive = 'x-pack/test/functional/es_archives/logstash_functional';
+ const localIndexPatternString = 'logstash-*';
+ const remoteIndexPatternString = 'ftr-remote:logstash-*';
+ const localFixtures = {
+ lensBasic: 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json',
+ lensDefault: 'x-pack/test/functional/fixtures/kbn_archiver/lens/default',
+ };
+
+ const remoteFixtures = {
+ lensBasic: 'x-pack/test/functional/fixtures/kbn_archiver/lens/ccs/lens_basic.json',
+ lensDefault: 'x-pack/test/functional/fixtures/kbn_archiver/lens/ccs/default',
+ };
+ let esNode: EsArchiver;
+ let fixtureDirs: {
+ lensBasic: string;
+ lensDefault: string;
+ };
+ let indexPatternString: string;
+ before(async () => {
+ log.debug('Starting lens before method');
+ await browser.setWindowSize(1280, 1200);
+ await kibanaServer.savedObjects.cleanStandardList();
+ try {
+ config.get('esTestCluster.ccs');
+ remoteEsArchiver = getService('remoteEsArchiver' as 'esArchiver');
+ esNode = remoteEsArchiver;
+ fixtureDirs = remoteFixtures;
+ indexPatternString = remoteIndexPatternString;
+ } catch (error) {
+ esNode = esArchiver;
+ fixtureDirs = localFixtures;
+ indexPatternString = localIndexPatternString;
+ }
+
+ await esNode.load(esArchive);
+ // changing the timepicker default here saves us from having to set it in Discover (~8s)
+ await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings();
+ await kibanaServer.uiSettings.update({
+ defaultIndex: indexPatternString,
+ 'dateFormat:tz': 'UTC',
+ });
+ await kibanaServer.importExport.load(fixtureDirs.lensBasic);
+ await kibanaServer.importExport.load(fixtureDirs.lensDefault);
+ });
+
+ after(async () => {
+ await esArchiver.unload(esArchive);
+ await PageObjects.timePicker.resetDefaultAbsoluteRangeViaUiSettings();
+ await kibanaServer.importExport.unload(fixtureDirs.lensBasic);
+ await kibanaServer.importExport.unload(fixtureDirs.lensDefault);
+ await kibanaServer.savedObjects.cleanStandardList();
+ });
+
+ // total run time ~16m
+ loadTestFile(require.resolve('./metric')); // 4m 7s
+ loadTestFile(require.resolve('./legacy_metric')); // 29s
+ loadTestFile(require.resolve('./disable_auto_apply')); // 1m 6s
+ loadTestFile(require.resolve('./reference_lines')); // 1m
+ loadTestFile(require.resolve('./annotations')); // 1m
+ loadTestFile(require.resolve('./inspector')); // 1m 19s
+ loadTestFile(require.resolve('./error_handling')); // 1m 8s
+ loadTestFile(require.resolve('./lens_tagging')); // 1m 9s
+ loadTestFile(require.resolve('./lens_reporting')); // 3m
+ // keep these two last in the group in this order because they are messing with the default saved objects
+ loadTestFile(require.resolve('./rollup')); // 1m 30s
+ loadTestFile(require.resolve('./no_data')); // 36s
+ });
+};
diff --git a/x-pack/test/functional/apps/lens/group3/inspector.ts b/x-pack/test/functional/apps/lens/group6/inspector.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group3/inspector.ts
rename to x-pack/test/functional/apps/lens/group6/inspector.ts
diff --git a/x-pack/test/functional/apps/lens/group3/legacy_metric.ts b/x-pack/test/functional/apps/lens/group6/legacy_metric.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group3/legacy_metric.ts
rename to x-pack/test/functional/apps/lens/group6/legacy_metric.ts
diff --git a/x-pack/test/functional/apps/lens/group3/lens_reporting.ts b/x-pack/test/functional/apps/lens/group6/lens_reporting.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group3/lens_reporting.ts
rename to x-pack/test/functional/apps/lens/group6/lens_reporting.ts
diff --git a/x-pack/test/functional/apps/lens/group3/lens_tagging.ts b/x-pack/test/functional/apps/lens/group6/lens_tagging.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group3/lens_tagging.ts
rename to x-pack/test/functional/apps/lens/group6/lens_tagging.ts
diff --git a/x-pack/test/functional/apps/lens/group3/metric.ts b/x-pack/test/functional/apps/lens/group6/metric.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group3/metric.ts
rename to x-pack/test/functional/apps/lens/group6/metric.ts
diff --git a/x-pack/test/functional/apps/lens/group3/no_data.ts b/x-pack/test/functional/apps/lens/group6/no_data.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group3/no_data.ts
rename to x-pack/test/functional/apps/lens/group6/no_data.ts
diff --git a/x-pack/test/functional/apps/lens/group3/reference_lines.ts b/x-pack/test/functional/apps/lens/group6/reference_lines.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group3/reference_lines.ts
rename to x-pack/test/functional/apps/lens/group6/reference_lines.ts
diff --git a/x-pack/test/functional/apps/lens/group3/rollup.ts b/x-pack/test/functional/apps/lens/group6/rollup.ts
similarity index 100%
rename from x-pack/test/functional/apps/lens/group3/rollup.ts
rename to x-pack/test/functional/apps/lens/group6/rollup.ts
diff --git a/x-pack/test/functional/apps/lens/open_in_lens/tsvb/dashboard.ts b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/dashboard.ts
index ef625b7116eea..579134c3ce287 100644
--- a/x-pack/test/functional/apps/lens/open_in_lens/tsvb/dashboard.ts
+++ b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/dashboard.ts
@@ -17,7 +17,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
'dashboard',
'canvas',
]);
-
+ const dashboardCustomizePanel = getService('dashboardCustomizePanel');
+ const dashboardBadgeActions = getService('dashboardBadgeActions');
+ const dashboardPanelActions = getService('dashboardPanelActions');
const testSubjects = getService('testSubjects');
const retry = getService('retry');
const panelActions = getService('dashboardPanelActions');
@@ -42,6 +44,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await dashboard.waitForRenderComplete();
const originalEmbeddableCount = await canvas.getEmbeddableCount();
+ await dashboardPanelActions.customizePanel();
+ await dashboardCustomizePanel.clickToggleShowCustomTimeRange();
+ await dashboardCustomizePanel.clickToggleQuickMenuButton();
+ await dashboardCustomizePanel.clickCommonlyUsedTimeRange('Last_30 days');
+ await dashboardCustomizePanel.clickSaveButton();
+ await dashboard.waitForRenderComplete();
+ await dashboardBadgeActions.expectExistsTimeRangeBadgeAction();
await panelActions.openContextMenu();
await panelActions.clickEdit();
@@ -59,6 +68,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
});
const titles = await dashboard.getPanelTitles();
expect(titles[0]).to.be('My TSVB to Lens viz 1 (converted)');
+ await dashboardBadgeActions.expectExistsTimeRangeBadgeAction();
await panelActions.removePanel();
});
@@ -72,6 +82,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await dashboard.waitForRenderComplete();
const originalEmbeddableCount = await canvas.getEmbeddableCount();
+ await dashboardPanelActions.customizePanel();
+ await dashboardCustomizePanel.clickToggleShowCustomTimeRange();
+ await dashboardCustomizePanel.clickToggleQuickMenuButton();
+ await dashboardCustomizePanel.clickCommonlyUsedTimeRange('Last_30 days');
+ await dashboardCustomizePanel.clickSaveButton();
+ await dashboard.waitForRenderComplete();
+ await dashboardBadgeActions.expectExistsTimeRangeBadgeAction();
await panelActions.openContextMenu();
await panelActions.clickEdit();
@@ -95,7 +112,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
expect(descendants.length).to.equal(0);
const titles = await dashboard.getPanelTitles();
expect(titles[0]).to.be('My TSVB to Lens viz 2 (converted)');
-
+ await dashboardBadgeActions.expectExistsTimeRangeBadgeAction();
await panelActions.removePanel();
});
});
diff --git a/x-pack/test/functional/apps/transform/edit_clone/cloning.ts b/x-pack/test/functional/apps/transform/edit_clone/cloning.ts
index 6b3bbac3f7b3d..152ea8c9caa66 100644
--- a/x-pack/test/functional/apps/transform/edit_clone/cloning.ts
+++ b/x-pack/test/functional/apps/transform/edit_clone/cloning.ts
@@ -35,12 +35,19 @@ function getTransformConfig(): TransformPivotConfig {
source: { index: ['ft_ecommerce'] },
pivot: {
group_by: { category: { terms: { field: 'category.keyword' } } },
- aggregations: { 'products.base_price.avg': { avg: { field: 'products.base_price' } } },
+ aggregations: {
+ 'products.base_price.avg': { avg: { field: 'products.base_price' } },
+ 'order_date.max': {
+ max: {
+ field: 'order_date',
+ },
+ },
+ },
},
description:
'ecommerce batch transform with avg(products.base_price) grouped by terms(category)',
frequency: '3s',
- retention_policy: { time: { field: 'order_date', max_age: '1d' } },
+ retention_policy: { time: { field: 'order_date.max', max_age: '1d' } },
settings: {
max_page_search_size: 250,
num_failure_retries: 0,
@@ -75,11 +82,16 @@ function getTransformConfigWithRuntimeMappings(): TransformPivotConfig {
'rt_total_charge.avg': { avg: { field: 'rt_total_charge' } },
'rt_total_charge.min': { min: { field: 'rt_total_charge' } },
'rt_total_charge.max': { max: { field: 'rt_total_charge' } },
+ max_order_date: {
+ max: {
+ field: 'order_date',
+ },
+ },
},
},
description: 'ecommerce batch transform grouped by terms(rt_gender_lower)',
frequency: '3s',
- retention_policy: { time: { field: 'order_date', max_age: '3d' } },
+ retention_policy: { time: { field: 'max_order_date', max_age: '3d' } },
settings: {
max_page_search_size: 250,
num_failure_retries: 5,
@@ -155,11 +167,16 @@ function getTransformConfigWithBoolFilterAgg(): TransformPivotConfig {
},
},
},
+ max_order_date: {
+ max: {
+ field: 'order_date',
+ },
+ },
},
},
description: 'ecommerce batch transform with filter aggregations',
frequency: '3s',
- retention_policy: { time: { field: 'order_date', max_age: '3d' } },
+ retention_policy: { time: { field: 'max_order_date', max_age: '3d' } },
settings: {
max_page_search_size: 250,
num_failure_retries: 5,
@@ -253,7 +270,7 @@ export default function ({ getService }: FtrProviderContext) {
],
},
retentionPolicySwitchEnabled: true,
- retentionPolicyField: 'order_date',
+ retentionPolicyField: 'order_date.max',
retentionPolicyMaxAge: '1d',
numFailureRetries: getNumFailureRetriesStr(
transformConfigWithPivot.settings?.num_failure_retries
@@ -288,7 +305,7 @@ export default function ({ getService }: FtrProviderContext) {
values: [`female`, `male`],
},
retentionPolicySwitchEnabled: true,
- retentionPolicyField: 'order_date',
+ retentionPolicyField: 'max_order_date',
retentionPolicyMaxAge: '3d',
numFailureRetries: getNumFailureRetriesStr(
transformConfigWithRuntimeMapping.settings?.num_failure_retries
@@ -341,7 +358,7 @@ export default function ({ getService }: FtrProviderContext) {
],
},
retentionPolicySwitchEnabled: true,
- retentionPolicyField: 'order_date',
+ retentionPolicyField: 'max_order_date',
retentionPolicyMaxAge: '3d',
numFailureRetries: getNumFailureRetriesStr(
transformConfigWithBoolFilterAgg.settings?.num_failure_retries
diff --git a/x-pack/test/security_solution_cypress/es_archives/query_alert/data.json b/x-pack/test/security_solution_cypress/es_archives/query_alert/data.json
new file mode 100644
index 0000000000000..551f3a376033d
--- /dev/null
+++ b/x-pack/test/security_solution_cypress/es_archives/query_alert/data.json
@@ -0,0 +1,419 @@
+{
+ "type": "doc",
+ "value": {
+ "id": "eabbdefc23da981f2b74ab58b82622a97bb9878caa11bc914e2adfacc94780f1",
+ "index": ".internal.alerts-security.alerts-default-000001",
+ "source": {
+ "@timestamp": "2023-04-27T11:03:57.906Z",
+ "Endpoint": {
+ "capabilities": [
+ "isolation",
+ "kill_process",
+ "suspend_process",
+ "running_processes",
+ "get_file",
+ "execute"
+ ],
+ "configuration": {
+ "isolation": true
+ },
+ "policy": {
+ "applied": {
+ "endpoint_policy_version": 3,
+ "id": "C2A9093E-E289-4C0A-AA44-8C32A414FA7A",
+ "name": "With Eventing",
+ "status": "success",
+ "version": 5
+ }
+ },
+ "state": {
+ "isolation": true
+ },
+ "status": "enrolled"
+ },
+ "agent": {
+ "id": "b563ce99-e373-4a1f-a5fe-97e956140aeb",
+ "type": "endpoint",
+ "version": "8.8.0"
+ },
+ "data_stream": {
+ "dataset": "endpoint.alerts",
+ "namespace": "default",
+ "type": "logs"
+ },
+ "dll": [
+ {
+ "Ext": {
+ "compile_time": 1534424710,
+ "malware_classification": {
+ "identifier": "Whitelisted",
+ "score": 0,
+ "threshold": 0,
+ "version": "3.0.0"
+ },
+ "mapped_address": 5362483200,
+ "mapped_size": 0
+ },
+ "code_signature": {
+ "subject_name": "Cybereason Inc",
+ "trusted": true
+ },
+ "hash": {
+ "md5": "1f2d082566b0fc5f2c238a5180db7451",
+ "sha1": "ca85243c0af6a6471bdaa560685c51eefd6dbc0d",
+ "sha256": "8ad40c90a611d36eb8f9eb24fa04f7dbca713db383ff55a03aa0f382e92061a2"
+ },
+ "path": "C:\\Program Files\\Cybereason ActiveProbe\\AmSvc.exe",
+ "pe": {
+ "architecture": "x64"
+ }
+ }
+ ],
+ "ecs": {
+ "version": "1.4.0"
+ },
+ "elastic": {
+ "agent": {
+ "id": "b563ce99-e373-4a1f-a5fe-97e956140aeb"
+ }
+ },
+ "event.action": "creation",
+ "event.agent_id_status": "auth_metadata_missing",
+ "event.category": "malware",
+ "event.code": "malicious_file",
+ "event.dataset": "endpoint",
+ "event.id": "b28993d4-8b8a-4f0f-9f54-84a89bad66ae",
+ "event.ingested": "2023-04-27T10:58:03Z",
+ "event.kind": "signal",
+ "event.module": "endpoint",
+ "event.sequence": 5826,
+ "event.type": "creation",
+ "file": {
+ "Ext": {
+ "code_signature": [
+ {
+ "subject_name": "bad signer",
+ "trusted": false
+ }
+ ],
+ "malware_classification": {
+ "identifier": "endpointpe",
+ "score": 1,
+ "threshold": 0.66,
+ "version": "3.0.33"
+ },
+ "quarantine_message": "fake quarantine message",
+ "quarantine_result": true,
+ "temp_file_path": "C:/temp/fake_malware.exe"
+ },
+ "accessed": 1682752652103,
+ "created": 1682752652103,
+ "hash": {
+ "md5": "fake file md5",
+ "sha1": "fake file sha1",
+ "sha256": "fake file sha256"
+ },
+ "mtime": 1682752652103,
+ "name": "fake_malware.exe",
+ "owner": "SYSTEM",
+ "path": "C:/fake_malware.exe",
+ "size": 3456
+ },
+ "host": {
+ "architecture": "wtnozeqvub",
+ "hostname": "Host-fwarau82er",
+ "id": "4260adf9-5e63-445d-92c6-e03359bcd342",
+ "ip": [
+ "10.249.37.72",
+ "10.150.39.243",
+ "10.186.17.170"
+ ],
+ "mac": [
+ "f5-f-97-dc-20-67",
+ "b5-56-ca-98-81-ca",
+ "22-86-39-4c-87-33"
+ ],
+ "name": "Host-fwarau82er",
+ "os": {
+ "Ext": {
+ "variant": "Darwin"
+ },
+ "family": "Darwin",
+ "full": "macOS Monterey",
+ "name": "macOS",
+ "platform": "macOS",
+ "version": "12.6.1"
+ }
+ },
+ "kibana.alert.ancestors": [
+ {
+ "depth": 0,
+ "id": "vT9cwocBh3b8EMpD8lsi",
+ "index": ".ds-logs-endpoint.alerts-default-2023.04.27-000001",
+ "type": "event"
+ }
+ ],
+ "kibana.alert.depth": 1,
+ "kibana.alert.last_detected": "2023-04-27T11:03:57.993Z",
+ "kibana.alert.original_event.action": "creation",
+ "kibana.alert.original_event.agent_id_status": "auth_metadata_missing",
+ "kibana.alert.original_event.category": "malware",
+ "kibana.alert.original_event.code": "malicious_file",
+ "kibana.alert.original_event.dataset": "endpoint",
+ "kibana.alert.original_event.id": "b28993d4-8b8a-4f0f-9f54-84a89bad66ae",
+ "kibana.alert.original_event.ingested": "2023-04-27T10:58:03Z",
+ "kibana.alert.original_event.kind": "alert",
+ "kibana.alert.original_event.module": "endpoint",
+ "kibana.alert.original_event.sequence": 5826,
+ "kibana.alert.original_event.type": "creation",
+ "kibana.alert.original_time": "2023-04-29T07:17:32.103Z",
+ "kibana.alert.reason": "malware event with process malware writer, file fake_malware.exe, on Host-fwarau82er created medium alert Endpoint Security.",
+ "kibana.alert.risk_score": 47,
+ "kibana.alert.rule.actions": [
+ ],
+ "kibana.alert.rule.author": [
+ "Elastic"
+ ],
+ "kibana.alert.rule.category": "Custom Query Rule",
+ "kibana.alert.rule.consumer": "siem",
+ "kibana.alert.rule.created_at": "2023-04-27T10:58:27.546Z",
+ "kibana.alert.rule.created_by": "elastic",
+ "kibana.alert.rule.description": "Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.",
+ "kibana.alert.rule.enabled": true,
+ "kibana.alert.rule.exceptions_list": [
+ {
+ "id": "endpoint_list",
+ "list_id": "endpoint_list",
+ "namespace_type": "agnostic",
+ "type": "endpoint"
+ }
+ ],
+ "kibana.alert.rule.execution.uuid": "ebf843ff-e0e1-47f8-9ed2-cc8066afbcef",
+ "kibana.alert.rule.false_positives": [
+ ],
+ "kibana.alert.rule.from": "now-10m",
+ "kibana.alert.rule.immutable": true,
+ "kibana.alert.rule.indices": [
+ "logs-endpoint.alerts-*"
+ ],
+ "kibana.alert.rule.interval": "5m",
+ "kibana.alert.rule.license": "Elastic License v2",
+ "kibana.alert.rule.max_signals": 10000,
+ "kibana.alert.rule.name": "Endpoint Security",
+ "kibana.alert.rule.parameters": {
+ "author": [
+ "Elastic"
+ ],
+ "description": "Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.",
+ "exceptions_list": [
+ {
+ "id": "endpoint_list",
+ "list_id": "endpoint_list",
+ "namespace_type": "agnostic",
+ "type": "endpoint"
+ }
+ ],
+ "false_positives": [
+ ],
+ "from": "now-10m",
+ "immutable": true,
+ "index": [
+ "logs-endpoint.alerts-*"
+ ],
+ "language": "kuery",
+ "license": "Elastic License v2",
+ "max_signals": 10000,
+ "query": "event.kind:alert and event.module:(endpoint and not endgame)\n",
+ "references": [
+ ],
+ "related_integrations": [
+ {
+ "package": "endpoint",
+ "version": "^8.2.0"
+ }
+ ],
+ "required_fields": [
+ {
+ "ecs": true,
+ "name": "event.kind",
+ "type": "keyword"
+ },
+ {
+ "ecs": true,
+ "name": "event.module",
+ "type": "keyword"
+ }
+ ],
+ "risk_score": 47,
+ "risk_score_mapping": [
+ {
+ "field": "event.risk_score",
+ "operator": "equals",
+ "value": ""
+ }
+ ],
+ "rule_id": "9a1a2dae-0b5f-4c3d-8305-a268d404c306",
+ "rule_name_override": "message",
+ "setup": "",
+ "severity": "medium",
+ "severity_mapping": [
+ {
+ "field": "event.severity",
+ "operator": "equals",
+ "severity": "low",
+ "value": "21"
+ },
+ {
+ "field": "event.severity",
+ "operator": "equals",
+ "severity": "medium",
+ "value": "47"
+ },
+ {
+ "field": "event.severity",
+ "operator": "equals",
+ "severity": "high",
+ "value": "73"
+ },
+ {
+ "field": "event.severity",
+ "operator": "equals",
+ "severity": "critical",
+ "value": "99"
+ }
+ ],
+ "threat": [
+ ],
+ "timestamp_override": "event.ingested",
+ "to": "now",
+ "type": "query",
+ "version": 101
+ },
+ "kibana.alert.rule.producer": "siem",
+ "kibana.alert.rule.references": [
+ ],
+ "kibana.alert.rule.revision": 0,
+ "kibana.alert.rule.risk_score": 47,
+ "kibana.alert.rule.risk_score_mapping": [
+ {
+ "field": "event.risk_score",
+ "operator": "equals",
+ "value": ""
+ }
+ ],
+ "kibana.alert.rule.rule_id": "9a1a2dae-0b5f-4c3d-8305-a268d404c306",
+ "kibana.alert.rule.rule_name_override": "message",
+ "kibana.alert.rule.rule_type_id": "siem.queryRule",
+ "kibana.alert.rule.severity": "medium",
+ "kibana.alert.rule.severity_mapping": [
+ {
+ "field": "event.severity",
+ "operator": "equals",
+ "severity": "low",
+ "value": "21"
+ },
+ {
+ "field": "event.severity",
+ "operator": "equals",
+ "severity": "medium",
+ "value": "47"
+ },
+ {
+ "field": "event.severity",
+ "operator": "equals",
+ "severity": "high",
+ "value": "73"
+ },
+ {
+ "field": "event.severity",
+ "operator": "equals",
+ "severity": "critical",
+ "value": "99"
+ }
+ ],
+ "kibana.alert.rule.tags": [
+ "Elastic",
+ "Endpoint Security"
+ ],
+ "kibana.alert.rule.threat": [
+ ],
+ "kibana.alert.rule.timestamp_override": "event.ingested",
+ "kibana.alert.rule.to": "now",
+ "kibana.alert.rule.type": "query",
+ "kibana.alert.rule.updated_at": "2023-04-27T10:58:27.546Z",
+ "kibana.alert.rule.updated_by": "elastic",
+ "kibana.alert.rule.uuid": "7015a3e2-e4ea-11ed-8c11-49608884878f",
+ "kibana.alert.rule.version": 101,
+ "kibana.alert.severity": "medium",
+ "kibana.alert.start": "2023-04-27T11:03:57.993Z",
+ "kibana.alert.status": "active",
+ "kibana.alert.url": "http://localhost:5601/app/security/alerts/redirect/eabbdefc23da981f2b74ab58b82622a97bb9878caa11bc914e2adfacc94780f1?index=.alerts-security.alerts-default×tamp=2023-04-27T11:03:57.906Z",
+ "kibana.alert.uuid": "eabbdefc23da981f2b74ab58b82622a97bb9878caa11bc914e2adfacc94780f1",
+ "kibana.alert.workflow_status": "open",
+ "kibana.space_ids": [
+ "default"
+ ],
+ "kibana.version": "8.8.0",
+ "process": {
+ "Ext": {
+ "ancestry": [
+ "qa5jgw1wr7",
+ "5k1hclygc6"
+ ],
+ "code_signature": [
+ {
+ "subject_name": "bad signer",
+ "trusted": false
+ }
+ ],
+ "token": {
+ "domain": "NT AUTHORITY",
+ "integrity_level": 16384,
+ "integrity_level_name": "system",
+ "privileges": [
+ {
+ "description": "Replace a process level token",
+ "enabled": false,
+ "name": "SeAssignPrimaryTokenPrivilege"
+ }
+ ],
+ "sid": "S-1-5-18",
+ "type": "tokenPrimary",
+ "user": "SYSTEM"
+ },
+ "user": "SYSTEM"
+ },
+ "entity_id": "nqh8ts6ves",
+ "entry_leader": {
+ "entity_id": "jnm38bel0w",
+ "name": "fake entry",
+ "pid": 791
+ },
+ "executable": "C:/malware.exe",
+ "group_leader": {
+ "entity_id": "jnm38bel0w",
+ "name": "fake leader",
+ "pid": 848
+ },
+ "hash": {
+ "md5": "fake md5",
+ "sha1": "fake sha1",
+ "sha256": "fake sha256"
+ },
+ "name": "malware writer",
+ "parent": {
+ "entity_id": "qa5jgw1wr7",
+ "pid": 1
+ },
+ "pid": 2,
+ "session_leader": {
+ "entity_id": "jnm38bel0w",
+ "name": "fake session",
+ "pid": 909
+ },
+ "start": 1682752652103,
+ "uptime": 0
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/x-pack/test/security_solution_cypress/es_archives/query_alert/mappings.json b/x-pack/test/security_solution_cypress/es_archives/query_alert/mappings.json
new file mode 100644
index 0000000000000..f346eee132104
--- /dev/null
+++ b/x-pack/test/security_solution_cypress/es_archives/query_alert/mappings.json
@@ -0,0 +1,7904 @@
+{
+ "type": "index",
+ "value": {
+ "aliases": {
+ ".alerts-security.alerts-default": {
+ "is_write_index": true
+ },
+ ".siem-signals-default": {
+ "is_write_index": false
+ }
+ },
+ "index": ".internal.alerts-security.alerts-default-000001",
+ "mappings": {
+ "_meta": {
+ "kibana": {
+ "version": "8.8.0"
+ },
+ "managed": true,
+ "namespace": "default"
+ },
+ "dynamic": "false",
+ "properties": {
+ "@timestamp": {
+ "type": "date"
+ },
+ "agent": {
+ "properties": {
+ "build": {
+ "properties": {
+ "original": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "ephemeral_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "client": {
+ "properties": {
+ "address": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "as": {
+ "properties": {
+ "number": {
+ "type": "long"
+ },
+ "organization": {
+ "properties": {
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "bytes": {
+ "type": "long"
+ },
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "geo": {
+ "properties": {
+ "city_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "continent_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "continent_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "location": {
+ "type": "geo_point"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "postal_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "timezone": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "ip": {
+ "type": "ip"
+ },
+ "mac": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "nat": {
+ "properties": {
+ "ip": {
+ "type": "ip"
+ },
+ "port": {
+ "type": "long"
+ }
+ }
+ },
+ "packets": {
+ "type": "long"
+ },
+ "port": {
+ "type": "long"
+ },
+ "registered_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "subdomain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "top_level_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "user": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "email": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full_name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "group": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "roles": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "cloud": {
+ "properties": {
+ "account": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "availability_zone": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "instance": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "machine": {
+ "properties": {
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "origin": {
+ "properties": {
+ "account": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "availability_zone": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "instance": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "machine": {
+ "properties": {
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "project": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "provider": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "service": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "project": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "provider": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "service": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "target": {
+ "properties": {
+ "account": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "availability_zone": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "instance": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "machine": {
+ "properties": {
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "project": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "provider": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "service": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "container": {
+ "properties": {
+ "cpu": {
+ "properties": {
+ "usage": {
+ "scaling_factor": 1000,
+ "type": "scaled_float"
+ }
+ }
+ },
+ "disk": {
+ "properties": {
+ "read": {
+ "properties": {
+ "bytes": {
+ "type": "long"
+ }
+ }
+ },
+ "write": {
+ "properties": {
+ "bytes": {
+ "type": "long"
+ }
+ }
+ }
+ }
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "image": {
+ "properties": {
+ "hash": {
+ "properties": {
+ "all": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "tag": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "labels": {
+ "type": "object"
+ },
+ "memory": {
+ "properties": {
+ "usage": {
+ "scaling_factor": 1000,
+ "type": "scaled_float"
+ }
+ }
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "network": {
+ "properties": {
+ "egress": {
+ "properties": {
+ "bytes": {
+ "type": "long"
+ }
+ }
+ },
+ "ingress": {
+ "properties": {
+ "bytes": {
+ "type": "long"
+ }
+ }
+ }
+ }
+ },
+ "runtime": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "destination": {
+ "properties": {
+ "address": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "as": {
+ "properties": {
+ "number": {
+ "type": "long"
+ },
+ "organization": {
+ "properties": {
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "bytes": {
+ "type": "long"
+ },
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "geo": {
+ "properties": {
+ "city_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "continent_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "continent_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "location": {
+ "type": "geo_point"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "postal_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "timezone": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "ip": {
+ "type": "ip"
+ },
+ "mac": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "nat": {
+ "properties": {
+ "ip": {
+ "type": "ip"
+ },
+ "port": {
+ "type": "long"
+ }
+ }
+ },
+ "packets": {
+ "type": "long"
+ },
+ "port": {
+ "type": "long"
+ },
+ "registered_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "subdomain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "top_level_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "user": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "email": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full_name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "group": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "roles": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "device": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "manufacturer": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "model": {
+ "properties": {
+ "identifier": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "dll": {
+ "properties": {
+ "code_signature": {
+ "properties": {
+ "digest_algorithm": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "exists": {
+ "type": "boolean"
+ },
+ "signing_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "status": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "subject_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "team_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "timestamp": {
+ "type": "date"
+ },
+ "trusted": {
+ "type": "boolean"
+ },
+ "valid": {
+ "type": "boolean"
+ }
+ }
+ },
+ "hash": {
+ "properties": {
+ "md5": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha1": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha256": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha384": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha512": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ssdeep": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "tlsh": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "path": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "pe": {
+ "properties": {
+ "architecture": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "company": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "description": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "file_version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "imphash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "original_file_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "pehash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "product": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "dns": {
+ "properties": {
+ "answers": {
+ "properties": {
+ "class": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "data": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ttl": {
+ "type": "long"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "header_flags": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "op_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "question": {
+ "properties": {
+ "class": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "registered_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "subdomain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "top_level_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "resolved_ip": {
+ "type": "ip"
+ },
+ "response_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "ecs": {
+ "properties": {
+ "version": {
+ "type": "keyword"
+ }
+ }
+ },
+ "email": {
+ "properties": {
+ "attachments": {
+ "properties": {
+ "file": {
+ "properties": {
+ "extension": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "hash": {
+ "properties": {
+ "md5": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha1": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha256": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha384": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha512": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ssdeep": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "tlsh": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "mime_type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "size": {
+ "type": "long"
+ }
+ }
+ }
+ },
+ "type": "nested"
+ },
+ "bcc": {
+ "properties": {
+ "address": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "cc": {
+ "properties": {
+ "address": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "content_type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "delivery_timestamp": {
+ "type": "date"
+ },
+ "direction": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "from": {
+ "properties": {
+ "address": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "local_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "message_id": {
+ "type": "wildcard"
+ },
+ "origination_timestamp": {
+ "type": "date"
+ },
+ "reply_to": {
+ "properties": {
+ "address": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "sender": {
+ "properties": {
+ "address": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "subject": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "to": {
+ "properties": {
+ "address": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "x_mailer": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "error": {
+ "properties": {
+ "code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "message": {
+ "type": "match_only_text"
+ },
+ "stack_trace": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "type": "wildcard"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "event": {
+ "properties": {
+ "action": {
+ "type": "keyword"
+ },
+ "agent_id_status": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "category": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "created": {
+ "type": "date"
+ },
+ "dataset": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "duration": {
+ "type": "long"
+ },
+ "end": {
+ "type": "date"
+ },
+ "hash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ingested": {
+ "type": "date"
+ },
+ "kind": {
+ "type": "keyword"
+ },
+ "module": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "original": {
+ "type": "keyword"
+ },
+ "outcome": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "provider": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "reason": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "reference": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "risk_score": {
+ "type": "float"
+ },
+ "risk_score_norm": {
+ "type": "float"
+ },
+ "sequence": {
+ "type": "long"
+ },
+ "severity": {
+ "type": "long"
+ },
+ "start": {
+ "type": "date"
+ },
+ "timezone": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "url": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "faas": {
+ "properties": {
+ "coldstart": {
+ "type": "boolean"
+ },
+ "execution": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "trigger": {
+ "properties": {
+ "request_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ },
+ "type": "nested"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "file": {
+ "properties": {
+ "accessed": {
+ "type": "date"
+ },
+ "attributes": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "code_signature": {
+ "properties": {
+ "digest_algorithm": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "exists": {
+ "type": "boolean"
+ },
+ "signing_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "status": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "subject_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "team_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "timestamp": {
+ "type": "date"
+ },
+ "trusted": {
+ "type": "boolean"
+ },
+ "valid": {
+ "type": "boolean"
+ }
+ }
+ },
+ "created": {
+ "type": "date"
+ },
+ "ctime": {
+ "type": "date"
+ },
+ "device": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "directory": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "drive_letter": {
+ "ignore_above": 1,
+ "type": "keyword"
+ },
+ "elf": {
+ "properties": {
+ "architecture": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "byte_order": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "cpu_type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "creation_date": {
+ "type": "date"
+ },
+ "exports": {
+ "type": "flattened"
+ },
+ "header": {
+ "properties": {
+ "abi_version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "class": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "data": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "entrypoint": {
+ "type": "long"
+ },
+ "object_version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "os_abi": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "imports": {
+ "type": "flattened"
+ },
+ "sections": {
+ "properties": {
+ "chi2": {
+ "type": "long"
+ },
+ "entropy": {
+ "type": "long"
+ },
+ "flags": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "physical_offset": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "physical_size": {
+ "type": "long"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "virtual_address": {
+ "type": "long"
+ },
+ "virtual_size": {
+ "type": "long"
+ }
+ },
+ "type": "nested"
+ },
+ "segments": {
+ "properties": {
+ "sections": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ },
+ "type": "nested"
+ },
+ "shared_libraries": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "telfhash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "extension": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "fork_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "gid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "group": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "hash": {
+ "properties": {
+ "md5": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha1": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha256": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha384": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha512": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ssdeep": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "tlsh": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "inode": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "mime_type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "mode": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "mtime": {
+ "type": "date"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "owner": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "path": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "pe": {
+ "properties": {
+ "architecture": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "company": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "description": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "file_version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "imphash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "original_file_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "pehash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "product": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "size": {
+ "type": "long"
+ },
+ "target_path": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "uid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "x509": {
+ "properties": {
+ "alternative_names": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "issuer": {
+ "properties": {
+ "common_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "distinguished_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "locality": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "organization": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "organizational_unit": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "state_or_province": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "not_after": {
+ "type": "date"
+ },
+ "not_before": {
+ "type": "date"
+ },
+ "public_key_algorithm": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "public_key_curve": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "public_key_exponent": {
+ "type": "long"
+ },
+ "public_key_size": {
+ "type": "long"
+ },
+ "serial_number": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "signature_algorithm": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "subject": {
+ "properties": {
+ "common_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "distinguished_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "locality": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "organization": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "organizational_unit": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "state_or_province": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "version_number": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "group": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "host": {
+ "properties": {
+ "architecture": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "boot": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "cpu": {
+ "properties": {
+ "usage": {
+ "scaling_factor": 1000,
+ "type": "scaled_float"
+ }
+ }
+ },
+ "disk": {
+ "properties": {
+ "read": {
+ "properties": {
+ "bytes": {
+ "type": "long"
+ }
+ }
+ },
+ "write": {
+ "properties": {
+ "bytes": {
+ "type": "long"
+ }
+ }
+ }
+ }
+ },
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "geo": {
+ "properties": {
+ "city_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "continent_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "continent_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "location": {
+ "type": "geo_point"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "postal_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "timezone": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hostname": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ip": {
+ "type": "ip"
+ },
+ "mac": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "network": {
+ "properties": {
+ "egress": {
+ "properties": {
+ "bytes": {
+ "type": "long"
+ },
+ "packets": {
+ "type": "long"
+ }
+ }
+ },
+ "ingress": {
+ "properties": {
+ "bytes": {
+ "type": "long"
+ },
+ "packets": {
+ "type": "long"
+ }
+ }
+ }
+ }
+ },
+ "os": {
+ "properties": {
+ "family": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "kernel": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "platform": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "pid_ns_ino": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "risk": {
+ "properties": {
+ "calculated_level": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "calculated_score": {
+ "type": "float"
+ },
+ "calculated_score_norm": {
+ "type": "float"
+ },
+ "static_level": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "static_score": {
+ "type": "float"
+ },
+ "static_score_norm": {
+ "type": "float"
+ }
+ }
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "uptime": {
+ "type": "long"
+ }
+ }
+ },
+ "http": {
+ "properties": {
+ "request": {
+ "properties": {
+ "body": {
+ "properties": {
+ "bytes": {
+ "type": "long"
+ },
+ "content": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "type": "wildcard"
+ }
+ }
+ },
+ "bytes": {
+ "type": "long"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "method": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "mime_type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "referrer": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "response": {
+ "properties": {
+ "body": {
+ "properties": {
+ "bytes": {
+ "type": "long"
+ },
+ "content": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "type": "wildcard"
+ }
+ }
+ },
+ "bytes": {
+ "type": "long"
+ },
+ "mime_type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "status_code": {
+ "type": "long"
+ }
+ }
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "kibana": {
+ "properties": {
+ "alert": {
+ "properties": {
+ "action_group": {
+ "type": "keyword"
+ },
+ "ancestors": {
+ "properties": {
+ "depth": {
+ "type": "long"
+ },
+ "id": {
+ "type": "keyword"
+ },
+ "index": {
+ "type": "keyword"
+ },
+ "rule": {
+ "type": "keyword"
+ },
+ "type": {
+ "type": "keyword"
+ }
+ }
+ },
+ "building_block_type": {
+ "type": "keyword"
+ },
+ "case_ids": {
+ "type": "keyword"
+ },
+ "depth": {
+ "type": "long"
+ },
+ "duration": {
+ "properties": {
+ "us": {
+ "type": "long"
+ }
+ }
+ },
+ "end": {
+ "type": "date"
+ },
+ "flapping": {
+ "type": "boolean"
+ },
+ "flapping_history": {
+ "type": "boolean"
+ },
+ "group": {
+ "properties": {
+ "id": {
+ "type": "keyword"
+ },
+ "index": {
+ "type": "integer"
+ }
+ }
+ },
+ "instance": {
+ "properties": {
+ "id": {
+ "type": "keyword"
+ }
+ }
+ },
+ "last_detected": {
+ "type": "date"
+ },
+ "maintenance_window_ids": {
+ "type": "keyword"
+ },
+ "new_terms": {
+ "type": "keyword"
+ },
+ "original_event": {
+ "properties": {
+ "action": {
+ "type": "keyword"
+ },
+ "agent_id_status": {
+ "type": "keyword"
+ },
+ "category": {
+ "type": "keyword"
+ },
+ "code": {
+ "type": "keyword"
+ },
+ "created": {
+ "type": "date"
+ },
+ "dataset": {
+ "type": "keyword"
+ },
+ "duration": {
+ "type": "keyword"
+ },
+ "end": {
+ "type": "date"
+ },
+ "hash": {
+ "type": "keyword"
+ },
+ "id": {
+ "type": "keyword"
+ },
+ "ingested": {
+ "type": "date"
+ },
+ "kind": {
+ "type": "keyword"
+ },
+ "module": {
+ "type": "keyword"
+ },
+ "original": {
+ "type": "keyword"
+ },
+ "outcome": {
+ "type": "keyword"
+ },
+ "provider": {
+ "type": "keyword"
+ },
+ "reason": {
+ "type": "keyword"
+ },
+ "reference": {
+ "type": "keyword"
+ },
+ "risk_score": {
+ "type": "float"
+ },
+ "risk_score_norm": {
+ "type": "float"
+ },
+ "sequence": {
+ "type": "long"
+ },
+ "severity": {
+ "type": "long"
+ },
+ "start": {
+ "type": "date"
+ },
+ "timezone": {
+ "type": "keyword"
+ },
+ "type": {
+ "type": "keyword"
+ },
+ "url": {
+ "type": "keyword"
+ }
+ }
+ },
+ "original_time": {
+ "type": "date"
+ },
+ "reason": {
+ "type": "keyword"
+ },
+ "risk_score": {
+ "type": "float"
+ },
+ "rule": {
+ "properties": {
+ "author": {
+ "type": "keyword"
+ },
+ "building_block_type": {
+ "type": "keyword"
+ },
+ "category": {
+ "type": "keyword"
+ },
+ "consumer": {
+ "type": "keyword"
+ },
+ "created_at": {
+ "type": "date"
+ },
+ "created_by": {
+ "type": "keyword"
+ },
+ "description": {
+ "type": "keyword"
+ },
+ "enabled": {
+ "type": "keyword"
+ },
+ "exceptions_list": {
+ "type": "object"
+ },
+ "execution": {
+ "properties": {
+ "uuid": {
+ "type": "keyword"
+ }
+ }
+ },
+ "false_positives": {
+ "type": "keyword"
+ },
+ "from": {
+ "type": "keyword"
+ },
+ "immutable": {
+ "type": "keyword"
+ },
+ "interval": {
+ "type": "keyword"
+ },
+ "license": {
+ "type": "keyword"
+ },
+ "max_signals": {
+ "type": "long"
+ },
+ "name": {
+ "type": "keyword"
+ },
+ "note": {
+ "type": "keyword"
+ },
+ "parameters": {
+ "ignore_above": 4096,
+ "type": "flattened"
+ },
+ "producer": {
+ "type": "keyword"
+ },
+ "references": {
+ "type": "keyword"
+ },
+ "revision": {
+ "type": "long"
+ },
+ "rule_id": {
+ "type": "keyword"
+ },
+ "rule_name_override": {
+ "type": "keyword"
+ },
+ "rule_type_id": {
+ "type": "keyword"
+ },
+ "tags": {
+ "type": "keyword"
+ },
+ "threat": {
+ "properties": {
+ "framework": {
+ "type": "keyword"
+ },
+ "tactic": {
+ "properties": {
+ "id": {
+ "type": "keyword"
+ },
+ "name": {
+ "type": "keyword"
+ },
+ "reference": {
+ "type": "keyword"
+ }
+ }
+ },
+ "technique": {
+ "properties": {
+ "id": {
+ "type": "keyword"
+ },
+ "name": {
+ "type": "keyword"
+ },
+ "reference": {
+ "type": "keyword"
+ },
+ "subtechnique": {
+ "properties": {
+ "id": {
+ "type": "keyword"
+ },
+ "name": {
+ "type": "keyword"
+ },
+ "reference": {
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "timeline_id": {
+ "type": "keyword"
+ },
+ "timeline_title": {
+ "type": "keyword"
+ },
+ "timestamp_override": {
+ "type": "keyword"
+ },
+ "to": {
+ "type": "keyword"
+ },
+ "type": {
+ "type": "keyword"
+ },
+ "updated_at": {
+ "type": "date"
+ },
+ "updated_by": {
+ "type": "keyword"
+ },
+ "uuid": {
+ "type": "keyword"
+ },
+ "version": {
+ "type": "keyword"
+ }
+ }
+ },
+ "severity": {
+ "type": "keyword"
+ },
+ "start": {
+ "type": "date"
+ },
+ "status": {
+ "type": "keyword"
+ },
+ "suppression": {
+ "properties": {
+ "docs_count": {
+ "type": "long"
+ },
+ "end": {
+ "type": "date"
+ },
+ "start": {
+ "type": "date"
+ },
+ "terms": {
+ "properties": {
+ "field": {
+ "type": "keyword"
+ },
+ "value": {
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "system_status": {
+ "type": "keyword"
+ },
+ "threshold_result": {
+ "properties": {
+ "cardinality": {
+ "properties": {
+ "field": {
+ "type": "keyword"
+ },
+ "value": {
+ "type": "long"
+ }
+ }
+ },
+ "count": {
+ "type": "long"
+ },
+ "from": {
+ "type": "date"
+ },
+ "terms": {
+ "properties": {
+ "field": {
+ "type": "keyword"
+ },
+ "value": {
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "time_range": {
+ "format": "epoch_millis||strict_date_optional_time",
+ "type": "date_range"
+ },
+ "url": {
+ "ignore_above": 2048,
+ "index": false,
+ "type": "keyword"
+ },
+ "uuid": {
+ "type": "keyword"
+ },
+ "workflow_reason": {
+ "type": "keyword"
+ },
+ "workflow_status": {
+ "type": "keyword"
+ },
+ "workflow_user": {
+ "type": "keyword"
+ }
+ }
+ },
+ "space_ids": {
+ "type": "keyword"
+ },
+ "version": {
+ "type": "version"
+ }
+ }
+ },
+ "labels": {
+ "type": "object"
+ },
+ "log": {
+ "properties": {
+ "file": {
+ "properties": {
+ "path": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "level": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "logger": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "origin": {
+ "properties": {
+ "file": {
+ "properties": {
+ "line": {
+ "type": "long"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "function": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "syslog": {
+ "properties": {
+ "appname": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "facility": {
+ "properties": {
+ "code": {
+ "type": "long"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hostname": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "msgid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "priority": {
+ "type": "long"
+ },
+ "procid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "severity": {
+ "properties": {
+ "code": {
+ "type": "long"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "structured_data": {
+ "type": "flattened"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "message": {
+ "type": "match_only_text"
+ },
+ "network": {
+ "properties": {
+ "application": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "bytes": {
+ "type": "long"
+ },
+ "community_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "direction": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "forwarded_ip": {
+ "type": "ip"
+ },
+ "iana_number": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "inner": {
+ "properties": {
+ "vlan": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "packets": {
+ "type": "long"
+ },
+ "protocol": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "transport": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "vlan": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "observer": {
+ "properties": {
+ "egress": {
+ "properties": {
+ "interface": {
+ "properties": {
+ "alias": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "vlan": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "zone": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "geo": {
+ "properties": {
+ "city_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "continent_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "continent_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "location": {
+ "type": "geo_point"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "postal_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "timezone": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hostname": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ingress": {
+ "properties": {
+ "interface": {
+ "properties": {
+ "alias": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "vlan": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "zone": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "ip": {
+ "type": "ip"
+ },
+ "mac": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "os": {
+ "properties": {
+ "family": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "kernel": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "platform": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "product": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "serial_number": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "vendor": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "orchestrator": {
+ "properties": {
+ "api_version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "cluster": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "url": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "namespace": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "organization": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "resource": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ip": {
+ "type": "ip"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "parent": {
+ "properties": {
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "organization": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "package": {
+ "properties": {
+ "architecture": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "build_version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "checksum": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "description": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "install_scope": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "installed": {
+ "type": "date"
+ },
+ "license": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "path": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "reference": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "size": {
+ "type": "long"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "process": {
+ "properties": {
+ "args": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "args_count": {
+ "type": "long"
+ },
+ "code_signature": {
+ "properties": {
+ "digest_algorithm": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "exists": {
+ "type": "boolean"
+ },
+ "signing_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "status": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "subject_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "team_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "timestamp": {
+ "type": "date"
+ },
+ "trusted": {
+ "type": "boolean"
+ },
+ "valid": {
+ "type": "boolean"
+ }
+ }
+ },
+ "command_line": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "type": "wildcard"
+ },
+ "elf": {
+ "properties": {
+ "architecture": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "byte_order": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "cpu_type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "creation_date": {
+ "type": "date"
+ },
+ "exports": {
+ "type": "flattened"
+ },
+ "header": {
+ "properties": {
+ "abi_version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "class": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "data": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "entrypoint": {
+ "type": "long"
+ },
+ "object_version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "os_abi": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "imports": {
+ "type": "flattened"
+ },
+ "sections": {
+ "properties": {
+ "chi2": {
+ "type": "long"
+ },
+ "entropy": {
+ "type": "long"
+ },
+ "flags": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "physical_offset": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "physical_size": {
+ "type": "long"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "virtual_address": {
+ "type": "long"
+ },
+ "virtual_size": {
+ "type": "long"
+ }
+ },
+ "type": "nested"
+ },
+ "segments": {
+ "properties": {
+ "sections": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ },
+ "type": "nested"
+ },
+ "shared_libraries": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "telfhash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "end": {
+ "type": "date"
+ },
+ "entity_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "entry_leader": {
+ "properties": {
+ "args": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "args_count": {
+ "type": "long"
+ },
+ "attested_groups": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "attested_user": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "command_line": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "type": "wildcard"
+ },
+ "entity_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "entry_meta": {
+ "properties": {
+ "source": {
+ "properties": {
+ "ip": {
+ "type": "ip"
+ }
+ }
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "executable": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "group": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "interactive": {
+ "type": "boolean"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "parent": {
+ "properties": {
+ "entity_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "pid": {
+ "type": "long"
+ },
+ "session_leader": {
+ "properties": {
+ "entity_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "pid": {
+ "type": "long"
+ },
+ "start": {
+ "type": "date"
+ }
+ }
+ },
+ "start": {
+ "type": "date"
+ }
+ }
+ },
+ "pid": {
+ "type": "long"
+ },
+ "real_group": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "real_user": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "same_as_process": {
+ "type": "boolean"
+ },
+ "saved_group": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "saved_user": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "start": {
+ "type": "date"
+ },
+ "supplemental_groups": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "tty": {
+ "properties": {
+ "char_device": {
+ "properties": {
+ "major": {
+ "type": "long"
+ },
+ "minor": {
+ "type": "long"
+ }
+ }
+ }
+ }
+ },
+ "user": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "working_directory": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "env_vars": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "executable": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "exit_code": {
+ "type": "long"
+ },
+ "group_leader": {
+ "properties": {
+ "args": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "args_count": {
+ "type": "long"
+ },
+ "command_line": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "type": "wildcard"
+ },
+ "entity_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "executable": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "group": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "interactive": {
+ "type": "boolean"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "pid": {
+ "type": "long"
+ },
+ "real_group": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "real_user": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "same_as_process": {
+ "type": "boolean"
+ },
+ "saved_group": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "saved_user": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "start": {
+ "type": "date"
+ },
+ "supplemental_groups": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "tty": {
+ "properties": {
+ "char_device": {
+ "properties": {
+ "major": {
+ "type": "long"
+ },
+ "minor": {
+ "type": "long"
+ }
+ }
+ }
+ }
+ },
+ "user": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "working_directory": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hash": {
+ "properties": {
+ "md5": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha1": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha256": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha384": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha512": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ssdeep": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "tlsh": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "interactive": {
+ "type": "boolean"
+ },
+ "io": {
+ "properties": {
+ "bytes_skipped": {
+ "properties": {
+ "length": {
+ "type": "long"
+ },
+ "offset": {
+ "type": "long"
+ }
+ }
+ },
+ "max_bytes_per_process_exceeded": {
+ "type": "boolean"
+ },
+ "text": {
+ "type": "wildcard"
+ },
+ "total_bytes_captured": {
+ "type": "long"
+ },
+ "total_bytes_skipped": {
+ "type": "long"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "parent": {
+ "properties": {
+ "args": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "args_count": {
+ "type": "long"
+ },
+ "code_signature": {
+ "properties": {
+ "digest_algorithm": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "exists": {
+ "type": "boolean"
+ },
+ "signing_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "status": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "subject_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "team_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "timestamp": {
+ "type": "date"
+ },
+ "trusted": {
+ "type": "boolean"
+ },
+ "valid": {
+ "type": "boolean"
+ }
+ }
+ },
+ "command_line": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "type": "wildcard"
+ },
+ "elf": {
+ "properties": {
+ "architecture": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "byte_order": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "cpu_type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "creation_date": {
+ "type": "date"
+ },
+ "exports": {
+ "type": "flattened"
+ },
+ "header": {
+ "properties": {
+ "abi_version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "class": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "data": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "entrypoint": {
+ "type": "long"
+ },
+ "object_version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "os_abi": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "imports": {
+ "type": "flattened"
+ },
+ "sections": {
+ "properties": {
+ "chi2": {
+ "type": "long"
+ },
+ "entropy": {
+ "type": "long"
+ },
+ "flags": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "physical_offset": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "physical_size": {
+ "type": "long"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "virtual_address": {
+ "type": "long"
+ },
+ "virtual_size": {
+ "type": "long"
+ }
+ },
+ "type": "nested"
+ },
+ "segments": {
+ "properties": {
+ "sections": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ },
+ "type": "nested"
+ },
+ "shared_libraries": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "telfhash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "end": {
+ "type": "date"
+ },
+ "entity_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "executable": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "exit_code": {
+ "type": "long"
+ },
+ "group": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "group_leader": {
+ "properties": {
+ "entity_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "pid": {
+ "type": "long"
+ },
+ "start": {
+ "type": "date"
+ }
+ }
+ },
+ "hash": {
+ "properties": {
+ "md5": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha1": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha256": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha384": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha512": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ssdeep": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "tlsh": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "interactive": {
+ "type": "boolean"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "pe": {
+ "properties": {
+ "architecture": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "company": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "description": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "file_version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "imphash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "original_file_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "pehash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "product": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "pgid": {
+ "type": "long"
+ },
+ "pid": {
+ "type": "long"
+ },
+ "real_group": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "real_user": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "saved_group": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "saved_user": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "start": {
+ "type": "date"
+ },
+ "supplemental_groups": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "thread": {
+ "properties": {
+ "id": {
+ "type": "long"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "title": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "tty": {
+ "properties": {
+ "char_device": {
+ "properties": {
+ "major": {
+ "type": "long"
+ },
+ "minor": {
+ "type": "long"
+ }
+ }
+ }
+ }
+ },
+ "uptime": {
+ "type": "long"
+ },
+ "user": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "working_directory": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "pe": {
+ "properties": {
+ "architecture": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "company": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "description": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "file_version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "imphash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "original_file_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "pehash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "product": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "pgid": {
+ "type": "long"
+ },
+ "pid": {
+ "type": "long"
+ },
+ "previous": {
+ "properties": {
+ "args": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "args_count": {
+ "type": "long"
+ },
+ "executable": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "real_group": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "real_user": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "saved_group": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "saved_user": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "session_leader": {
+ "properties": {
+ "args": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "args_count": {
+ "type": "long"
+ },
+ "command_line": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "type": "wildcard"
+ },
+ "entity_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "executable": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "group": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "interactive": {
+ "type": "boolean"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "parent": {
+ "properties": {
+ "entity_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "pid": {
+ "type": "long"
+ },
+ "session_leader": {
+ "properties": {
+ "entity_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "pid": {
+ "type": "long"
+ },
+ "start": {
+ "type": "date"
+ }
+ }
+ },
+ "start": {
+ "type": "date"
+ }
+ }
+ },
+ "pid": {
+ "type": "long"
+ },
+ "real_group": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "real_user": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "same_as_process": {
+ "type": "boolean"
+ },
+ "saved_group": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "saved_user": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "start": {
+ "type": "date"
+ },
+ "supplemental_groups": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "tty": {
+ "properties": {
+ "char_device": {
+ "properties": {
+ "major": {
+ "type": "long"
+ },
+ "minor": {
+ "type": "long"
+ }
+ }
+ }
+ }
+ },
+ "user": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "working_directory": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "start": {
+ "type": "date"
+ },
+ "supplemental_groups": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "thread": {
+ "properties": {
+ "id": {
+ "type": "long"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "title": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "tty": {
+ "properties": {
+ "char_device": {
+ "properties": {
+ "major": {
+ "type": "long"
+ },
+ "minor": {
+ "type": "long"
+ }
+ }
+ },
+ "columns": {
+ "type": "long"
+ },
+ "rows": {
+ "type": "long"
+ }
+ }
+ },
+ "uptime": {
+ "type": "long"
+ },
+ "user": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "working_directory": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "registry": {
+ "properties": {
+ "data": {
+ "properties": {
+ "bytes": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "strings": {
+ "type": "wildcard"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hive": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "key": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "path": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "value": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "related": {
+ "properties": {
+ "hash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "hosts": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ip": {
+ "type": "ip"
+ },
+ "user": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "rule": {
+ "properties": {
+ "author": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "category": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "description": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "license": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "reference": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ruleset": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "uuid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "server": {
+ "properties": {
+ "address": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "as": {
+ "properties": {
+ "number": {
+ "type": "long"
+ },
+ "organization": {
+ "properties": {
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "bytes": {
+ "type": "long"
+ },
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "geo": {
+ "properties": {
+ "city_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "continent_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "continent_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "location": {
+ "type": "geo_point"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "postal_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "timezone": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "ip": {
+ "type": "ip"
+ },
+ "mac": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "nat": {
+ "properties": {
+ "ip": {
+ "type": "ip"
+ },
+ "port": {
+ "type": "long"
+ }
+ }
+ },
+ "packets": {
+ "type": "long"
+ },
+ "port": {
+ "type": "long"
+ },
+ "registered_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "subdomain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "top_level_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "user": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "email": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full_name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "group": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "roles": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "service": {
+ "properties": {
+ "address": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "environment": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ephemeral_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "node": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "role": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "roles": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "origin": {
+ "properties": {
+ "address": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "environment": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ephemeral_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "node": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "role": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "roles": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "state": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "state": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "target": {
+ "properties": {
+ "address": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "environment": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ephemeral_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "node": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "role": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "roles": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "state": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "signal": {
+ "properties": {
+ "ancestors": {
+ "properties": {
+ "depth": {
+ "path": "kibana.alert.ancestors.depth",
+ "type": "alias"
+ },
+ "id": {
+ "path": "kibana.alert.ancestors.id",
+ "type": "alias"
+ },
+ "index": {
+ "path": "kibana.alert.ancestors.index",
+ "type": "alias"
+ },
+ "type": {
+ "path": "kibana.alert.ancestors.type",
+ "type": "alias"
+ }
+ }
+ },
+ "depth": {
+ "path": "kibana.alert.depth",
+ "type": "alias"
+ },
+ "group": {
+ "properties": {
+ "id": {
+ "path": "kibana.alert.group.id",
+ "type": "alias"
+ },
+ "index": {
+ "path": "kibana.alert.group.index",
+ "type": "alias"
+ }
+ }
+ },
+ "original_event": {
+ "properties": {
+ "action": {
+ "path": "kibana.alert.original_event.action",
+ "type": "alias"
+ },
+ "category": {
+ "path": "kibana.alert.original_event.category",
+ "type": "alias"
+ },
+ "code": {
+ "path": "kibana.alert.original_event.code",
+ "type": "alias"
+ },
+ "created": {
+ "path": "kibana.alert.original_event.created",
+ "type": "alias"
+ },
+ "dataset": {
+ "path": "kibana.alert.original_event.dataset",
+ "type": "alias"
+ },
+ "duration": {
+ "path": "kibana.alert.original_event.duration",
+ "type": "alias"
+ },
+ "end": {
+ "path": "kibana.alert.original_event.end",
+ "type": "alias"
+ },
+ "hash": {
+ "path": "kibana.alert.original_event.hash",
+ "type": "alias"
+ },
+ "id": {
+ "path": "kibana.alert.original_event.id",
+ "type": "alias"
+ },
+ "kind": {
+ "path": "kibana.alert.original_event.kind",
+ "type": "alias"
+ },
+ "module": {
+ "path": "kibana.alert.original_event.module",
+ "type": "alias"
+ },
+ "outcome": {
+ "path": "kibana.alert.original_event.outcome",
+ "type": "alias"
+ },
+ "provider": {
+ "path": "kibana.alert.original_event.provider",
+ "type": "alias"
+ },
+ "reason": {
+ "path": "kibana.alert.original_event.reason",
+ "type": "alias"
+ },
+ "risk_score": {
+ "path": "kibana.alert.original_event.risk_score",
+ "type": "alias"
+ },
+ "risk_score_norm": {
+ "path": "kibana.alert.original_event.risk_score_norm",
+ "type": "alias"
+ },
+ "sequence": {
+ "path": "kibana.alert.original_event.sequence",
+ "type": "alias"
+ },
+ "severity": {
+ "path": "kibana.alert.original_event.severity",
+ "type": "alias"
+ },
+ "start": {
+ "path": "kibana.alert.original_event.start",
+ "type": "alias"
+ },
+ "timezone": {
+ "path": "kibana.alert.original_event.timezone",
+ "type": "alias"
+ },
+ "type": {
+ "path": "kibana.alert.original_event.type",
+ "type": "alias"
+ }
+ }
+ },
+ "original_time": {
+ "path": "kibana.alert.original_time",
+ "type": "alias"
+ },
+ "reason": {
+ "path": "kibana.alert.reason",
+ "type": "alias"
+ },
+ "rule": {
+ "properties": {
+ "author": {
+ "path": "kibana.alert.rule.author",
+ "type": "alias"
+ },
+ "building_block_type": {
+ "path": "kibana.alert.building_block_type",
+ "type": "alias"
+ },
+ "created_at": {
+ "path": "kibana.alert.rule.created_at",
+ "type": "alias"
+ },
+ "created_by": {
+ "path": "kibana.alert.rule.created_by",
+ "type": "alias"
+ },
+ "description": {
+ "path": "kibana.alert.rule.description",
+ "type": "alias"
+ },
+ "enabled": {
+ "path": "kibana.alert.rule.enabled",
+ "type": "alias"
+ },
+ "false_positives": {
+ "path": "kibana.alert.rule.false_positives",
+ "type": "alias"
+ },
+ "from": {
+ "path": "kibana.alert.rule.from",
+ "type": "alias"
+ },
+ "id": {
+ "path": "kibana.alert.rule.uuid",
+ "type": "alias"
+ },
+ "immutable": {
+ "path": "kibana.alert.rule.immutable",
+ "type": "alias"
+ },
+ "interval": {
+ "path": "kibana.alert.rule.interval",
+ "type": "alias"
+ },
+ "license": {
+ "path": "kibana.alert.rule.license",
+ "type": "alias"
+ },
+ "max_signals": {
+ "path": "kibana.alert.rule.max_signals",
+ "type": "alias"
+ },
+ "name": {
+ "path": "kibana.alert.rule.name",
+ "type": "alias"
+ },
+ "note": {
+ "path": "kibana.alert.rule.note",
+ "type": "alias"
+ },
+ "references": {
+ "path": "kibana.alert.rule.references",
+ "type": "alias"
+ },
+ "risk_score": {
+ "path": "kibana.alert.risk_score",
+ "type": "alias"
+ },
+ "rule_id": {
+ "path": "kibana.alert.rule.rule_id",
+ "type": "alias"
+ },
+ "rule_name_override": {
+ "path": "kibana.alert.rule.rule_name_override",
+ "type": "alias"
+ },
+ "severity": {
+ "path": "kibana.alert.severity",
+ "type": "alias"
+ },
+ "tags": {
+ "path": "kibana.alert.rule.tags",
+ "type": "alias"
+ },
+ "threat": {
+ "properties": {
+ "framework": {
+ "path": "kibana.alert.rule.threat.framework",
+ "type": "alias"
+ },
+ "tactic": {
+ "properties": {
+ "id": {
+ "path": "kibana.alert.rule.threat.tactic.id",
+ "type": "alias"
+ },
+ "name": {
+ "path": "kibana.alert.rule.threat.tactic.name",
+ "type": "alias"
+ },
+ "reference": {
+ "path": "kibana.alert.rule.threat.tactic.reference",
+ "type": "alias"
+ }
+ }
+ },
+ "technique": {
+ "properties": {
+ "id": {
+ "path": "kibana.alert.rule.threat.technique.id",
+ "type": "alias"
+ },
+ "name": {
+ "path": "kibana.alert.rule.threat.technique.name",
+ "type": "alias"
+ },
+ "reference": {
+ "path": "kibana.alert.rule.threat.technique.reference",
+ "type": "alias"
+ },
+ "subtechnique": {
+ "properties": {
+ "id": {
+ "path": "kibana.alert.rule.threat.technique.subtechnique.id",
+ "type": "alias"
+ },
+ "name": {
+ "path": "kibana.alert.rule.threat.technique.subtechnique.name",
+ "type": "alias"
+ },
+ "reference": {
+ "path": "kibana.alert.rule.threat.technique.subtechnique.reference",
+ "type": "alias"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "timeline_id": {
+ "path": "kibana.alert.rule.timeline_id",
+ "type": "alias"
+ },
+ "timeline_title": {
+ "path": "kibana.alert.rule.timeline_title",
+ "type": "alias"
+ },
+ "timestamp_override": {
+ "path": "kibana.alert.rule.timestamp_override",
+ "type": "alias"
+ },
+ "to": {
+ "path": "kibana.alert.rule.to",
+ "type": "alias"
+ },
+ "type": {
+ "path": "kibana.alert.rule.type",
+ "type": "alias"
+ },
+ "updated_at": {
+ "path": "kibana.alert.rule.updated_at",
+ "type": "alias"
+ },
+ "updated_by": {
+ "path": "kibana.alert.rule.updated_by",
+ "type": "alias"
+ },
+ "version": {
+ "path": "kibana.alert.rule.version",
+ "type": "alias"
+ }
+ }
+ },
+ "status": {
+ "path": "kibana.alert.workflow_status",
+ "type": "alias"
+ },
+ "threshold_result": {
+ "properties": {
+ "cardinality": {
+ "properties": {
+ "field": {
+ "path": "kibana.alert.threshold_result.cardinality.field",
+ "type": "alias"
+ },
+ "value": {
+ "path": "kibana.alert.threshold_result.cardinality.value",
+ "type": "alias"
+ }
+ }
+ },
+ "count": {
+ "path": "kibana.alert.threshold_result.count",
+ "type": "alias"
+ },
+ "from": {
+ "path": "kibana.alert.threshold_result.from",
+ "type": "alias"
+ },
+ "terms": {
+ "properties": {
+ "field": {
+ "path": "kibana.alert.threshold_result.terms.field",
+ "type": "alias"
+ },
+ "value": {
+ "path": "kibana.alert.threshold_result.terms.value",
+ "type": "alias"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "source": {
+ "properties": {
+ "address": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "as": {
+ "properties": {
+ "number": {
+ "type": "long"
+ },
+ "organization": {
+ "properties": {
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "bytes": {
+ "type": "long"
+ },
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "geo": {
+ "properties": {
+ "city_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "continent_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "continent_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "location": {
+ "type": "geo_point"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "postal_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "timezone": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "ip": {
+ "type": "ip"
+ },
+ "mac": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "nat": {
+ "properties": {
+ "ip": {
+ "type": "ip"
+ },
+ "port": {
+ "type": "long"
+ }
+ }
+ },
+ "packets": {
+ "type": "long"
+ },
+ "port": {
+ "type": "long"
+ },
+ "registered_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "subdomain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "top_level_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "user": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "email": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full_name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "group": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "roles": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "span": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "tags": {
+ "type": "keyword"
+ },
+ "threat": {
+ "properties": {
+ "enrichments": {
+ "properties": {
+ "indicator": {
+ "properties": {
+ "as": {
+ "properties": {
+ "number": {
+ "type": "long"
+ },
+ "organization": {
+ "properties": {
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "confidence": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "description": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "email": {
+ "properties": {
+ "address": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "file": {
+ "properties": {
+ "accessed": {
+ "type": "date"
+ },
+ "attributes": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "code_signature": {
+ "properties": {
+ "digest_algorithm": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "exists": {
+ "type": "boolean"
+ },
+ "signing_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "status": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "subject_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "team_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "timestamp": {
+ "type": "date"
+ },
+ "trusted": {
+ "type": "boolean"
+ },
+ "valid": {
+ "type": "boolean"
+ }
+ }
+ },
+ "created": {
+ "type": "date"
+ },
+ "ctime": {
+ "type": "date"
+ },
+ "device": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "directory": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "drive_letter": {
+ "ignore_above": 1,
+ "type": "keyword"
+ },
+ "elf": {
+ "properties": {
+ "architecture": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "byte_order": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "cpu_type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "creation_date": {
+ "type": "date"
+ },
+ "exports": {
+ "type": "flattened"
+ },
+ "header": {
+ "properties": {
+ "abi_version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "class": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "data": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "entrypoint": {
+ "type": "long"
+ },
+ "object_version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "os_abi": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "imports": {
+ "type": "flattened"
+ },
+ "sections": {
+ "properties": {
+ "chi2": {
+ "type": "long"
+ },
+ "entropy": {
+ "type": "long"
+ },
+ "flags": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "physical_offset": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "physical_size": {
+ "type": "long"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "virtual_address": {
+ "type": "long"
+ },
+ "virtual_size": {
+ "type": "long"
+ }
+ },
+ "type": "nested"
+ },
+ "segments": {
+ "properties": {
+ "sections": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ },
+ "type": "nested"
+ },
+ "shared_libraries": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "telfhash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "extension": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "fork_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "gid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "group": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "hash": {
+ "properties": {
+ "md5": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha1": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha256": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha384": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha512": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ssdeep": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "tlsh": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "inode": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "mime_type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "mode": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "mtime": {
+ "type": "date"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "owner": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "path": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "pe": {
+ "properties": {
+ "architecture": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "company": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "description": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "file_version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "imphash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "original_file_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "pehash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "product": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "size": {
+ "type": "long"
+ },
+ "target_path": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "uid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "x509": {
+ "properties": {
+ "alternative_names": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "issuer": {
+ "properties": {
+ "common_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "distinguished_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "locality": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "organization": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "organizational_unit": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "state_or_province": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "not_after": {
+ "type": "date"
+ },
+ "not_before": {
+ "type": "date"
+ },
+ "public_key_algorithm": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "public_key_curve": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "public_key_exponent": {
+ "type": "long"
+ },
+ "public_key_size": {
+ "type": "long"
+ },
+ "serial_number": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "signature_algorithm": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "subject": {
+ "properties": {
+ "common_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "distinguished_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "locality": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "organization": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "organizational_unit": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "state_or_province": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "version_number": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "first_seen": {
+ "type": "date"
+ },
+ "geo": {
+ "properties": {
+ "city_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "continent_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "continent_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "location": {
+ "type": "geo_point"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "postal_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "timezone": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "ip": {
+ "type": "ip"
+ },
+ "last_seen": {
+ "type": "date"
+ },
+ "marking": {
+ "properties": {
+ "tlp": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "tlp_version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "modified_at": {
+ "type": "date"
+ },
+ "port": {
+ "type": "long"
+ },
+ "provider": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "reference": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "registry": {
+ "properties": {
+ "data": {
+ "properties": {
+ "bytes": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "strings": {
+ "type": "wildcard"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hive": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "key": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "path": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "value": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "scanner_stats": {
+ "type": "long"
+ },
+ "sightings": {
+ "type": "long"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "url": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "extension": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "fragment": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "type": "wildcard"
+ },
+ "original": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "type": "wildcard"
+ },
+ "password": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "path": {
+ "type": "wildcard"
+ },
+ "port": {
+ "type": "long"
+ },
+ "query": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "registered_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "scheme": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "subdomain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "top_level_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "username": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "x509": {
+ "properties": {
+ "alternative_names": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "issuer": {
+ "properties": {
+ "common_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "distinguished_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "locality": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "organization": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "organizational_unit": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "state_or_province": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "not_after": {
+ "type": "date"
+ },
+ "not_before": {
+ "type": "date"
+ },
+ "public_key_algorithm": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "public_key_curve": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "public_key_exponent": {
+ "type": "long"
+ },
+ "public_key_size": {
+ "type": "long"
+ },
+ "serial_number": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "signature_algorithm": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "subject": {
+ "properties": {
+ "common_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "distinguished_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "locality": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "organization": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "organizational_unit": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "state_or_province": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "version_number": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "matched": {
+ "properties": {
+ "atomic": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "field": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "index": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "occurred": {
+ "type": "date"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ },
+ "type": "nested"
+ },
+ "feed": {
+ "properties": {
+ "dashboard_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "description": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "reference": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "framework": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "group": {
+ "properties": {
+ "alias": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "reference": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "indicator": {
+ "properties": {
+ "as": {
+ "properties": {
+ "number": {
+ "type": "long"
+ },
+ "organization": {
+ "properties": {
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "confidence": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "description": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "email": {
+ "properties": {
+ "address": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "file": {
+ "properties": {
+ "accessed": {
+ "type": "date"
+ },
+ "attributes": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "code_signature": {
+ "properties": {
+ "digest_algorithm": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "exists": {
+ "type": "boolean"
+ },
+ "signing_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "status": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "subject_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "team_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "timestamp": {
+ "type": "date"
+ },
+ "trusted": {
+ "type": "boolean"
+ },
+ "valid": {
+ "type": "boolean"
+ }
+ }
+ },
+ "created": {
+ "type": "date"
+ },
+ "ctime": {
+ "type": "date"
+ },
+ "device": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "directory": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "drive_letter": {
+ "ignore_above": 1,
+ "type": "keyword"
+ },
+ "elf": {
+ "properties": {
+ "architecture": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "byte_order": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "cpu_type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "creation_date": {
+ "type": "date"
+ },
+ "exports": {
+ "type": "flattened"
+ },
+ "header": {
+ "properties": {
+ "abi_version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "class": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "data": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "entrypoint": {
+ "type": "long"
+ },
+ "object_version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "os_abi": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "imports": {
+ "type": "flattened"
+ },
+ "sections": {
+ "properties": {
+ "chi2": {
+ "type": "long"
+ },
+ "entropy": {
+ "type": "long"
+ },
+ "flags": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "physical_offset": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "physical_size": {
+ "type": "long"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "virtual_address": {
+ "type": "long"
+ },
+ "virtual_size": {
+ "type": "long"
+ }
+ },
+ "type": "nested"
+ },
+ "segments": {
+ "properties": {
+ "sections": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ },
+ "type": "nested"
+ },
+ "shared_libraries": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "telfhash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "extension": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "fork_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "gid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "group": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "hash": {
+ "properties": {
+ "md5": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha1": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha256": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha384": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha512": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ssdeep": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "tlsh": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "inode": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "mime_type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "mode": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "mtime": {
+ "type": "date"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "owner": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "path": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "pe": {
+ "properties": {
+ "architecture": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "company": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "description": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "file_version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "imphash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "original_file_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "pehash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "product": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "size": {
+ "type": "long"
+ },
+ "target_path": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "uid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "x509": {
+ "properties": {
+ "alternative_names": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "issuer": {
+ "properties": {
+ "common_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "distinguished_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "locality": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "organization": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "organizational_unit": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "state_or_province": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "not_after": {
+ "type": "date"
+ },
+ "not_before": {
+ "type": "date"
+ },
+ "public_key_algorithm": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "public_key_curve": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "public_key_exponent": {
+ "type": "long"
+ },
+ "public_key_size": {
+ "type": "long"
+ },
+ "serial_number": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "signature_algorithm": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "subject": {
+ "properties": {
+ "common_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "distinguished_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "locality": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "organization": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "organizational_unit": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "state_or_province": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "version_number": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "first_seen": {
+ "type": "date"
+ },
+ "geo": {
+ "properties": {
+ "city_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "continent_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "continent_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "location": {
+ "type": "geo_point"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "postal_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "timezone": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "ip": {
+ "type": "ip"
+ },
+ "last_seen": {
+ "type": "date"
+ },
+ "marking": {
+ "properties": {
+ "tlp": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "tlp_version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "modified_at": {
+ "type": "date"
+ },
+ "port": {
+ "type": "long"
+ },
+ "provider": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "reference": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "registry": {
+ "properties": {
+ "data": {
+ "properties": {
+ "bytes": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "strings": {
+ "type": "wildcard"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hive": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "key": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "path": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "value": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "scanner_stats": {
+ "type": "long"
+ },
+ "sightings": {
+ "type": "long"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "url": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "extension": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "fragment": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "type": "wildcard"
+ },
+ "original": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "type": "wildcard"
+ },
+ "password": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "path": {
+ "type": "wildcard"
+ },
+ "port": {
+ "type": "long"
+ },
+ "query": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "registered_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "scheme": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "subdomain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "top_level_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "username": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "x509": {
+ "properties": {
+ "alternative_names": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "issuer": {
+ "properties": {
+ "common_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "distinguished_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "locality": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "organization": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "organizational_unit": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "state_or_province": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "not_after": {
+ "type": "date"
+ },
+ "not_before": {
+ "type": "date"
+ },
+ "public_key_algorithm": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "public_key_curve": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "public_key_exponent": {
+ "type": "long"
+ },
+ "public_key_size": {
+ "type": "long"
+ },
+ "serial_number": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "signature_algorithm": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "subject": {
+ "properties": {
+ "common_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "distinguished_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "locality": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "organization": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "organizational_unit": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "state_or_province": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "version_number": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "software": {
+ "properties": {
+ "alias": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "platforms": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "reference": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "tactic": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "reference": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "technique": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "reference": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "subtechnique": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "reference": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "tls": {
+ "properties": {
+ "cipher": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "client": {
+ "properties": {
+ "certificate": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "certificate_chain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "hash": {
+ "properties": {
+ "md5": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha1": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha256": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "issuer": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ja3": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "not_after": {
+ "type": "date"
+ },
+ "not_before": {
+ "type": "date"
+ },
+ "server_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "subject": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "supported_ciphers": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "x509": {
+ "properties": {
+ "alternative_names": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "issuer": {
+ "properties": {
+ "common_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "distinguished_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "locality": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "organization": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "organizational_unit": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "state_or_province": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "not_after": {
+ "type": "date"
+ },
+ "not_before": {
+ "type": "date"
+ },
+ "public_key_algorithm": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "public_key_curve": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "public_key_exponent": {
+ "type": "long"
+ },
+ "public_key_size": {
+ "type": "long"
+ },
+ "serial_number": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "signature_algorithm": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "subject": {
+ "properties": {
+ "common_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "distinguished_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "locality": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "organization": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "organizational_unit": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "state_or_province": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "version_number": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "curve": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "established": {
+ "type": "boolean"
+ },
+ "next_protocol": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "resumed": {
+ "type": "boolean"
+ },
+ "server": {
+ "properties": {
+ "certificate": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "certificate_chain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "hash": {
+ "properties": {
+ "md5": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha1": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha256": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "issuer": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ja3s": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "not_after": {
+ "type": "date"
+ },
+ "not_before": {
+ "type": "date"
+ },
+ "subject": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "x509": {
+ "properties": {
+ "alternative_names": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "issuer": {
+ "properties": {
+ "common_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "distinguished_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "locality": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "organization": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "organizational_unit": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "state_or_province": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "not_after": {
+ "type": "date"
+ },
+ "not_before": {
+ "type": "date"
+ },
+ "public_key_algorithm": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "public_key_curve": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "public_key_exponent": {
+ "type": "long"
+ },
+ "public_key_size": {
+ "type": "long"
+ },
+ "serial_number": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "signature_algorithm": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "subject": {
+ "properties": {
+ "common_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "distinguished_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "locality": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "organization": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "organizational_unit": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "state_or_province": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "version_number": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version_protocol": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "trace": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "transaction": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "url": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "extension": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "fragment": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "type": "wildcard"
+ },
+ "original": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "type": "wildcard"
+ },
+ "password": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "path": {
+ "type": "wildcard"
+ },
+ "port": {
+ "type": "long"
+ },
+ "query": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "registered_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "scheme": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "subdomain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "top_level_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "username": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "user": {
+ "properties": {
+ "changes": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "email": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full_name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "group": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "roles": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "effective": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "email": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full_name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "group": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "roles": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "email": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full_name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "group": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "risk": {
+ "properties": {
+ "calculated_level": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "calculated_score": {
+ "type": "float"
+ },
+ "calculated_score_norm": {
+ "type": "float"
+ },
+ "static_level": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "static_score": {
+ "type": "float"
+ },
+ "static_score_norm": {
+ "type": "float"
+ }
+ }
+ },
+ "roles": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "target": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "email": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full_name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "group": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "hash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "roles": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "user_agent": {
+ "properties": {
+ "device": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "original": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "os": {
+ "properties": {
+ "family": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "kernel": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "platform": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "vulnerability": {
+ "properties": {
+ "category": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "classification": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "description": {
+ "fields": {
+ "text": {
+ "type": "match_only_text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "enumeration": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "reference": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "report_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "scanner": {
+ "properties": {
+ "vendor": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "score": {
+ "properties": {
+ "base": {
+ "type": "float"
+ },
+ "environmental": {
+ "type": "float"
+ },
+ "temporal": {
+ "type": "float"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "severity": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "settings": {
+ "index": {
+ "auto_expand_replicas": "0-1",
+ "hidden": "true",
+ "lifecycle": {
+ "name": ".alerts-ilm-policy",
+ "rollover_alias": ".alerts-security.alerts-default"
+ },
+ "mapping": {
+ "total_fields": {
+ "limit": "2500"
+ }
+ },
+ "number_of_replicas": "0",
+ "number_of_shards": "1"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 83f9b163487d8..f2f58c3bea369 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -21295,14 +21295,14 @@ moment-duration-format@^2.3.2:
resolved "https://registry.yarnpkg.com/moment-duration-format/-/moment-duration-format-2.3.2.tgz#5fa2b19b941b8d277122ff3f87a12895ec0d6212"
integrity sha512-cBMXjSW+fjOb4tyaVHuaVE/A5TqkukDWiOfxxAjY+PEqmmBQlLwn+8OzwPiG3brouXKY5Un4pBjAeB6UToXHaQ==
-moment-timezone@^0.5.34:
- version "0.5.34"
- resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.34.tgz#a75938f7476b88f155d3504a9343f7519d9a405c"
- integrity sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg==
+moment-timezone@^0.5.43:
+ version "0.5.43"
+ resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.43.tgz#3dd7f3d0c67f78c23cd1906b9b2137a09b3c4790"
+ integrity sha512-72j3aNyuIsDxdF1i7CEgV2FfxM1r6aaqJyLB2vwb33mXYyoyLly+F1zbWqhA3/bVIoJ4szlUoMbUnVdid32NUQ==
dependencies:
- moment ">= 2.9.0"
+ moment "^2.29.4"
-"moment@>= 2.9.0", moment@>=1.6.0, moment@>=2.14.0, moment@^2.10.6, moment@^2.29.4:
+moment@>=1.6.0, moment@>=2.14.0, moment@^2.10.6, moment@^2.29.4:
version "2.29.4"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108"
integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==