diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc
index 99660ff865526..784b952cccfe4 100644
--- a/docs/CHANGELOG.asciidoc
+++ b/docs/CHANGELOG.asciidoc
@@ -57,8 +57,6 @@ Review important information about the {kib} 7.x releases.
[[release-notes-7.13.1]]
== {kib} 7.13.1
-coming::[7.13.1]
-
The 7.13.1 release includes the following bug fixes.
[float]
diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.correctiveactions.md b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.correctiveactions.md
index e362bc4e0329c..447823a5c3491 100644
--- a/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.correctiveactions.md
+++ b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.correctiveactions.md
@@ -15,6 +15,6 @@ correctiveActions: {
[key: string]: any;
};
};
- manualSteps?: string[];
+ manualSteps: string[];
};
```
diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.md b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.md
index 6e46ce0b8611f..7592b8486d950 100644
--- a/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.md
+++ b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.md
@@ -14,7 +14,7 @@ export interface DeprecationsDetails
| Property | Type | Description |
| --- | --- | --- |
-| [correctiveActions](./kibana-plugin-core-server.deprecationsdetails.correctiveactions.md) | {
api?: {
path: string;
method: 'POST' | 'PUT';
body?: {
[key: string]: any;
};
};
manualSteps?: string[];
}
| |
+| [correctiveActions](./kibana-plugin-core-server.deprecationsdetails.correctiveactions.md) | {
api?: {
path: string;
method: 'POST' | 'PUT';
body?: {
[key: string]: any;
};
};
manualSteps: string[];
}
| |
| [deprecationType](./kibana-plugin-core-server.deprecationsdetails.deprecationtype.md) | 'config' | 'feature'
| (optional) Used to identify between different deprecation types. Example use case: in Upgrade Assistant, we may want to allow the user to sort by deprecation type or show each type in a separate tab.Feel free to add new types if necessary. Predefined types are necessary to reduce having similar definitions with different keywords across kibana deprecations. |
| [documentationUrl](./kibana-plugin-core-server.deprecationsdetails.documentationurl.md) | string
| |
| [level](./kibana-plugin-core-server.deprecationsdetails.level.md) | 'warning' | 'critical' | 'fetch_error'
| levels: - warning: will not break deployment upon upgrade - critical: needs to be addressed before upgrade. - fetch\_error: Deprecations service failed to grab the deprecation details for the domain. |
diff --git a/packages/kbn-config/src/config_service.test.ts b/packages/kbn-config/src/config_service.test.ts
index c2d4f15b6d915..b1b622381abb1 100644
--- a/packages/kbn-config/src/config_service.test.ts
+++ b/packages/kbn-config/src/config_service.test.ts
@@ -418,8 +418,14 @@ test('logs deprecation warning during validation', async () => {
const configService = new ConfigService(rawConfig, defaultEnv, logger);
mockApplyDeprecations.mockImplementationOnce((config, deprecations, createAddDeprecation) => {
const addDeprecation = createAddDeprecation!('');
- addDeprecation({ message: 'some deprecation message' });
- addDeprecation({ message: 'another deprecation message' });
+ addDeprecation({
+ message: 'some deprecation message',
+ correctiveActions: { manualSteps: ['do X'] },
+ });
+ addDeprecation({
+ message: 'another deprecation message',
+ correctiveActions: { manualSteps: ['do Y'] },
+ });
return { config, changedPaths: mockedChangedPaths };
});
@@ -444,13 +450,24 @@ test('does not log warnings for silent deprecations during validation', async ()
mockApplyDeprecations
.mockImplementationOnce((config, deprecations, createAddDeprecation) => {
const addDeprecation = createAddDeprecation!('');
- addDeprecation({ message: 'some deprecation message', silent: true });
- addDeprecation({ message: 'another deprecation message' });
+ addDeprecation({
+ message: 'some deprecation message',
+ correctiveActions: { manualSteps: ['do X'] },
+ silent: true,
+ });
+ addDeprecation({
+ message: 'another deprecation message',
+ correctiveActions: { manualSteps: ['do Y'] },
+ });
return { config, changedPaths: mockedChangedPaths };
})
.mockImplementationOnce((config, deprecations, createAddDeprecation) => {
const addDeprecation = createAddDeprecation!('');
- addDeprecation({ message: 'I am silent', silent: true });
+ addDeprecation({
+ message: 'I am silent',
+ silent: true,
+ correctiveActions: { manualSteps: ['do Z'] },
+ });
return { config, changedPaths: mockedChangedPaths };
});
@@ -519,7 +536,11 @@ describe('getHandledDeprecatedConfigs', () => {
mockApplyDeprecations.mockImplementationOnce((config, deprecations, createAddDeprecation) => {
deprecations.forEach((deprecation) => {
const addDeprecation = createAddDeprecation!(deprecation.path);
- addDeprecation({ message: `some deprecation message`, documentationUrl: 'some-url' });
+ addDeprecation({
+ message: `some deprecation message`,
+ documentationUrl: 'some-url',
+ correctiveActions: { manualSteps: ['do X'] },
+ });
});
return { config, changedPaths: mockedChangedPaths };
});
@@ -532,6 +553,11 @@ describe('getHandledDeprecatedConfigs', () => {
"base",
Array [
Object {
+ "correctiveActions": Object {
+ "manualSteps": Array [
+ "do X",
+ ],
+ },
"documentationUrl": "some-url",
"message": "some deprecation message",
},
diff --git a/packages/kbn-config/src/deprecation/types.ts b/packages/kbn-config/src/deprecation/types.ts
index 0522365ad76c1..1791dac060e2b 100644
--- a/packages/kbn-config/src/deprecation/types.ts
+++ b/packages/kbn-config/src/deprecation/types.ts
@@ -25,8 +25,8 @@ export interface DeprecatedConfigDetails {
silent?: boolean;
/* (optional) link to the documentation for more details on the deprecation. */
documentationUrl?: string;
- /* (optional) corrective action needed to fix this deprecation. */
- correctiveActions?: {
+ /* corrective action needed to fix this deprecation. */
+ correctiveActions: {
/**
* Specify a list of manual steps our users need to follow
* to fix the deprecation before upgrade.
diff --git a/src/core/public/deprecations/deprecations_client.test.ts b/src/core/public/deprecations/deprecations_client.test.ts
index 2f52f7b4af195..a998a03772cca 100644
--- a/src/core/public/deprecations/deprecations_client.test.ts
+++ b/src/core/public/deprecations/deprecations_client.test.ts
@@ -90,6 +90,7 @@ describe('DeprecationsClient', () => {
path: 'some-path',
method: 'POST',
},
+ manualSteps: ['manual-step'],
},
};
@@ -104,7 +105,9 @@ describe('DeprecationsClient', () => {
domainId: 'testPluginId-1',
message: 'some-message',
level: 'warning',
- correctiveActions: {},
+ correctiveActions: {
+ manualSteps: ['manual-step'],
+ },
};
const isResolvable = deprecationsClient.isDeprecationResolvable(mockDeprecationDetails);
@@ -120,7 +123,9 @@ describe('DeprecationsClient', () => {
domainId: 'testPluginId-1',
message: 'some-message',
level: 'warning',
- correctiveActions: {},
+ correctiveActions: {
+ manualSteps: ['manual-step'],
+ },
};
const result = await deprecationsClient.resolveDeprecation(mockDeprecationDetails);
@@ -144,6 +149,7 @@ describe('DeprecationsClient', () => {
extra_param: 123,
},
},
+ manualSteps: ['manual-step'],
},
};
const result = await deprecationsClient.resolveDeprecation(mockDeprecationDetails);
@@ -176,6 +182,7 @@ describe('DeprecationsClient', () => {
extra_param: 123,
},
},
+ manualSteps: ['manual-step'],
},
};
http.fetch.mockRejectedValue({ body: { message: mockResponse } });
diff --git a/src/core/server/config/deprecation/core_deprecations.test.ts b/src/core/server/config/deprecation/core_deprecations.test.ts
index 26fb96154223c..5abe7da721ed2 100644
--- a/src/core/server/config/deprecation/core_deprecations.test.ts
+++ b/src/core/server/config/deprecation/core_deprecations.test.ts
@@ -24,7 +24,7 @@ describe('core deprecations', () => {
const { messages } = applyCoreDeprecations();
expect(messages).toMatchInlineSnapshot(`
Array [
- "Environment variable CONFIG_PATH is deprecated. It has been replaced with KBN_PATH_CONF pointing to a config folder",
+ "Environment variable \\"CONFIG_PATH\\" is deprecated. It has been replaced with \\"KBN_PATH_CONF\\" pointing to a config folder",
]
`);
});
@@ -425,7 +425,7 @@ describe('core deprecations', () => {
});
expect(messages).toMatchInlineSnapshot(`
Array [
- "\\"logging.events.log\\" has been deprecated and will be removed in 8.0. Moving forward, log levels can be customized on a per-logger basis using the new logging configuration. ",
+ "\\"logging.events.log\\" has been deprecated and will be removed in 8.0. Moving forward, log levels can be customized on a per-logger basis using the new logging configuration.",
]
`);
});
@@ -438,7 +438,7 @@ describe('core deprecations', () => {
});
expect(messages).toMatchInlineSnapshot(`
Array [
- "\\"logging.events.error\\" has been deprecated and will be removed in 8.0. Moving forward, you can use \\"logging.root.level: error\\" in your logging configuration. ",
+ "\\"logging.events.error\\" has been deprecated and will be removed in 8.0. Moving forward, you can use \\"logging.root.level: error\\" in your logging configuration.",
]
`);
});
diff --git a/src/core/server/config/deprecation/core_deprecations.ts b/src/core/server/config/deprecation/core_deprecations.ts
index e270a5c67aa1c..04889f20956b5 100644
--- a/src/core/server/config/deprecation/core_deprecations.ts
+++ b/src/core/server/config/deprecation/core_deprecations.ts
@@ -11,7 +11,10 @@ import { ConfigDeprecationProvider, ConfigDeprecation } from '@kbn/config';
const configPathDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => {
if (process.env?.CONFIG_PATH) {
addDeprecation({
- message: `Environment variable CONFIG_PATH is deprecated. It has been replaced with KBN_PATH_CONF pointing to a config folder`,
+ message: `Environment variable "CONFIG_PATH" is deprecated. It has been replaced with "KBN_PATH_CONF" pointing to a config folder`,
+ correctiveActions: {
+ manualSteps: ['Use "KBN_PATH_CONF" instead of "CONFIG_PATH" to point to a config folder.'],
+ },
});
}
};
@@ -20,6 +23,11 @@ const dataPathDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecati
if (process.env?.DATA_PATH) {
addDeprecation({
message: `Environment variable "DATA_PATH" will be removed. It has been replaced with kibana.yml setting "path.data"`,
+ correctiveActions: {
+ manualSteps: [
+ `Set 'path.data' in the config file or CLI flag with the value of the environment variable "DATA_PATH".`,
+ ],
+ },
});
}
};
@@ -32,6 +40,12 @@ const rewriteBasePathDeprecation: ConfigDeprecation = (settings, fromPath, addDe
'will expect that all requests start with server.basePath rather than expecting you to rewrite ' +
'the requests in your reverse proxy. Set server.rewriteBasePath to false to preserve the ' +
'current behavior and silence this warning.',
+ correctiveActions: {
+ manualSteps: [
+ `Set 'server.rewriteBasePath' in the config file, CLI flag, or environment variable (in Docker only).`,
+ `Set to false to preserve the current behavior and silence this warning.`,
+ ],
+ },
});
}
};
@@ -41,6 +55,11 @@ const rewriteCorsSettings: ConfigDeprecation = (settings, fromPath, addDeprecati
if (typeof corsSettings === 'boolean') {
addDeprecation({
message: '"server.cors" is deprecated and has been replaced by "server.cors.enabled"',
+ correctiveActions: {
+ manualSteps: [
+ `Replace "server.cors: ${corsSettings}" with "server.cors.enabled: ${corsSettings}"`,
+ ],
+ },
});
return {
@@ -72,6 +91,9 @@ const cspRulesDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecati
if (sourceList.find((source) => source.includes(NONCE_STRING))) {
addDeprecation({
message: `csp.rules no longer supports the {nonce} syntax. Replacing with 'self' in ${policy}`,
+ correctiveActions: {
+ manualSteps: [`Replace {nonce} syntax with 'self' in ${policy}`],
+ },
});
sourceList = sourceList.filter((source) => !source.includes(NONCE_STRING));
@@ -87,6 +109,9 @@ const cspRulesDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecati
) {
addDeprecation({
message: `csp.rules must contain the 'self' source. Automatically adding to ${policy}.`,
+ correctiveActions: {
+ manualSteps: [`Add 'self' source to ${policy}.`],
+ },
});
sourceList.push(SELF_STRING);
}
@@ -111,6 +136,12 @@ const mapManifestServiceUrlDeprecation: ConfigDeprecation = (
'of the Elastic Maps Service settings. These settings have moved to the "map.emsTileApiUrl" and ' +
'"map.emsFileApiUrl" settings instead. These settings are for development use only and should not be ' +
'modified for use in production environments.',
+ correctiveActions: {
+ manualSteps: [
+ `Use "map.emsTileApiUrl" and "map.emsFileApiUrl" config instead of "map.manifestServiceUrl".`,
+ `These settings are for development use only and should not be modified for use in production environments.`,
+ ],
+ },
});
}
};
@@ -121,6 +152,11 @@ const serverHostZeroDeprecation: ConfigDeprecation = (settings, fromPath, addDep
message:
'Support for setting server.host to "0" in kibana.yml is deprecated and will be removed in Kibana version 8.0.0. ' +
'Instead use "0.0.0.0" to bind to all interfaces.',
+ correctiveActions: {
+ manualSteps: [
+ `Replace "server.host: 0" to "server.host: 0.0.0.0" in your kibana configurations.`,
+ ],
+ },
});
}
return settings;
@@ -136,12 +172,28 @@ const opsLoggingEventDeprecation: ConfigDeprecation = (settings, fromPath, addDe
'in 8.0. To access ops data moving forward, please enable debug logs for the ' +
'"metrics.ops" context in your logging configuration. For more details, see ' +
'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx',
+ correctiveActions: {
+ manualSteps: [
+ `Remove "logging.events.ops" from your kibana settings.`,
+ `Enable debug logs for the "metrics.ops" context in your logging configuration`,
+ ],
+ },
});
}
};
const requestLoggingEventDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => {
if (settings.logging?.events?.request || settings.logging?.events?.response) {
+ const removeConfigsSteps = [];
+
+ if (settings.logging?.events?.request) {
+ removeConfigsSteps.push(`Remove "logging.events.request" from your kibana configs.`);
+ }
+
+ if (settings.logging?.events?.response) {
+ removeConfigsSteps.push(`Remove "logging.events.response" from your kibana configs.`);
+ }
+
addDeprecation({
documentationUrl:
'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingevents',
@@ -150,6 +202,12 @@ const requestLoggingEventDeprecation: ConfigDeprecation = (settings, fromPath, a
'in 8.0. To access request and/or response data moving forward, please enable debug logs for the ' +
'"http.server.response" context in your logging configuration. For more details, see ' +
'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx',
+ correctiveActions: {
+ manualSteps: [
+ ...removeConfigsSteps,
+ `enable debug logs for the "http.server.response" context in your logging configuration.`,
+ ],
+ },
});
}
};
@@ -164,6 +222,12 @@ const timezoneLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDe
'in 8.0. To set the timezone moving forward, please add a timezone date modifier to the log pattern ' +
'in your logging configuration. For more details, see ' +
'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx',
+ correctiveActions: {
+ manualSteps: [
+ `Remove "logging.timezone" from your kibana configs.`,
+ `To set the timezone add a timezone date modifier to the log pattern in your logging configuration.`,
+ ],
+ },
});
}
};
@@ -178,6 +242,12 @@ const destLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprec
'in 8.0. To set the destination moving forward, you can use the "console" appender ' +
'in your logging configuration or define a custom one. For more details, see ' +
'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx',
+ correctiveActions: {
+ manualSteps: [
+ `Remove "logging.dest" from your kibana configs.`,
+ `To set the destination use the "console" appender in your logging configuration or define a custom one.`,
+ ],
+ },
});
}
};
@@ -190,6 +260,12 @@ const quietLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDepre
message:
'"logging.quiet" has been deprecated and will be removed ' +
'in 8.0. Moving forward, you can use "logging.root.level:error" in your logging configuration. ',
+ correctiveActions: {
+ manualSteps: [
+ `Remove "logging.quiet" from your kibana configs.`,
+ `Use "logging.root.level:error" in your logging configuration.`,
+ ],
+ },
});
}
};
@@ -202,6 +278,12 @@ const silentLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDepr
message:
'"logging.silent" has been deprecated and will be removed ' +
'in 8.0. Moving forward, you can use "logging.root.level:off" in your logging configuration. ',
+ correctiveActions: {
+ manualSteps: [
+ `Remove "logging.silent" from your kibana configs.`,
+ `Use "logging.root.level:off" in your logging configuration.`,
+ ],
+ },
});
}
};
@@ -214,6 +296,12 @@ const verboseLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDep
message:
'"logging.verbose" has been deprecated and will be removed ' +
'in 8.0. Moving forward, you can use "logging.root.level:all" in your logging configuration. ',
+ correctiveActions: {
+ manualSteps: [
+ `Remove "logging.verbose" from your kibana configs.`,
+ `Use "logging.root.level:all" in your logging configuration.`,
+ ],
+ },
});
}
};
@@ -234,6 +322,12 @@ const jsonLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprec
'There is currently no default layout for custom appenders and each one must be declared explicitly. ' +
'For more details, see ' +
'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx',
+ correctiveActions: {
+ manualSteps: [
+ `Remove "logging.json" from your kibana configs.`,
+ `Configure the "appender.layout" property for every custom appender in your logging configuration.`,
+ ],
+ },
});
}
};
@@ -248,6 +342,12 @@ const logRotateDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecat
'Moving forward, you can enable log rotation using the "rolling-file" appender for a logger ' +
'in your logging configuration. For more details, see ' +
'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#rolling-file-appender',
+ correctiveActions: {
+ manualSteps: [
+ `Remove "logging.rotate" from your kibana configs.`,
+ `Enable log rotation using the "rolling-file" appender for a logger in your logging configuration.`,
+ ],
+ },
});
}
};
@@ -259,7 +359,13 @@ const logEventsLogDeprecation: ConfigDeprecation = (settings, fromPath, addDepre
'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingevents',
message:
'"logging.events.log" has been deprecated and will be removed ' +
- 'in 8.0. Moving forward, log levels can be customized on a per-logger basis using the new logging configuration. ',
+ 'in 8.0. Moving forward, log levels can be customized on a per-logger basis using the new logging configuration.',
+ correctiveActions: {
+ manualSteps: [
+ `Remove "logging.events.log" from your kibana configs.`,
+ `Customize log levels can be per-logger using the new logging configuration.`,
+ ],
+ },
});
}
};
@@ -271,7 +377,13 @@ const logEventsErrorDeprecation: ConfigDeprecation = (settings, fromPath, addDep
'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingevents',
message:
'"logging.events.error" has been deprecated and will be removed ' +
- 'in 8.0. Moving forward, you can use "logging.root.level: error" in your logging configuration. ',
+ 'in 8.0. Moving forward, you can use "logging.root.level: error" in your logging configuration.',
+ correctiveActions: {
+ manualSteps: [
+ `Remove "logging.events.error" from your kibana configs.`,
+ `Use "logging.root.level: error" in your logging configuration.`,
+ ],
+ },
});
}
};
@@ -282,6 +394,9 @@ const logFilterDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecat
documentationUrl:
'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingfilter',
message: '"logging.filter" has been deprecated and will be removed in 8.0.',
+ correctiveActions: {
+ manualSteps: [`Remove "logging.filter" from your kibana configs.`],
+ },
});
}
};
diff --git a/src/core/server/deprecations/deprecations_service.ts b/src/core/server/deprecations/deprecations_service.ts
index 205dd964468c1..ede7f859ffd0d 100644
--- a/src/core/server/deprecations/deprecations_service.ts
+++ b/src/core/server/deprecations/deprecations_service.ts
@@ -116,7 +116,7 @@ export interface DeprecationsSetupDeps {
export class DeprecationsService implements CoreService {
private readonly logger: Logger;
- constructor(private readonly coreContext: CoreContext) {
+ constructor(private readonly coreContext: Pick) {
this.logger = coreContext.logger.get('deprecations-service');
}
@@ -154,7 +154,7 @@ export class DeprecationsService implements CoreService [
if (es.username === 'elastic') {
addDeprecation({
message: `Setting [${fromPath}.username] to "elastic" is deprecated. You should use the "kibana_system" user instead.`,
+ correctiveActions: {
+ manualSteps: [`Replace [${fromPath}.username] from "elastic" to "kibana_system".`],
+ },
});
} else if (es.username === 'kibana') {
addDeprecation({
message: `Setting [${fromPath}.username] to "kibana" is deprecated. You should use the "kibana_system" user instead.`,
+ correctiveActions: {
+ manualSteps: [`Replace [${fromPath}.username] from "kibana" to "kibana_system".`],
+ },
});
}
if (es.ssl?.key !== undefined && es.ssl?.certificate === undefined) {
addDeprecation({
message: `Setting [${fromPath}.ssl.key] without [${fromPath}.ssl.certificate] is deprecated. This has no effect, you should use both settings to enable TLS client authentication to Elasticsearch.`,
+ correctiveActions: {
+ manualSteps: [
+ `Set [${fromPath}.ssl.certificate] in your kibana configs to enable TLS client authentication to Elasticsearch.`,
+ ],
+ },
});
} else if (es.ssl?.certificate !== undefined && es.ssl?.key === undefined) {
addDeprecation({
message: `Setting [${fromPath}.ssl.certificate] without [${fromPath}.ssl.key] is deprecated. This has no effect, you should use both settings to enable TLS client authentication to Elasticsearch.`,
+ correctiveActions: {
+ manualSteps: [
+ `Set [${fromPath}.ssl.key] in your kibana configs to enable TLS client authentication to Elasticsearch.`,
+ ],
+ },
});
} else if (es.logQueries === true) {
addDeprecation({
message: `Setting [${fromPath}.logQueries] is deprecated and no longer used. You should set the log level to "debug" for the "elasticsearch.queries" context in "logging.loggers" or use "logging.verbose: true".`,
+ correctiveActions: {
+ manualSteps: [
+ `Remove Setting [${fromPath}.logQueries] from your kibana configs`,
+ `Set the log level to "debug" for the "elasticsearch.queries" context in "logging.loggers".`,
+ ],
+ },
});
}
return;
diff --git a/src/core/server/kibana_config.ts b/src/core/server/kibana_config.ts
index 623ffa86c0e8d..77ee3197b988d 100644
--- a/src/core/server/kibana_config.ts
+++ b/src/core/server/kibana_config.ts
@@ -18,6 +18,12 @@ const deprecations: ConfigDeprecationProvider = () => [
addDeprecation({
message: `"kibana.index" is deprecated. Multitenancy by changing "kibana.index" will not be supported starting in 8.0. See https://ela.st/kbn-remove-legacy-multitenancy for more details`,
documentationUrl: 'https://ela.st/kbn-remove-legacy-multitenancy',
+ correctiveActions: {
+ manualSteps: [
+ `If you rely on this setting to achieve multitenancy you should use Spaces, cross-cluster replication, or cross-cluster search instead.`,
+ `To migrate to Spaces, we encourage using saved object management to export your saved objects from a tenant into the default tenant in a space.`,
+ ],
+ },
});
}
return settings;
diff --git a/src/core/server/saved_objects/saved_objects_config.ts b/src/core/server/saved_objects/saved_objects_config.ts
index 7182df74c597f..c62d322f0bf8d 100644
--- a/src/core/server/saved_objects/saved_objects_config.ts
+++ b/src/core/server/saved_objects/saved_objects_config.ts
@@ -29,6 +29,9 @@ const migrationDeprecations: ConfigDeprecationProvider = () => [
message:
'"migrations.enableV2" is deprecated and will be removed in an upcoming release without any further notice.',
documentationUrl: 'https://ela.st/kbn-so-migration-v2',
+ correctiveActions: {
+ manualSteps: [`Remove "migrations.enableV2" from your kibana configs.`],
+ },
});
}
return settings;
diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md
index 0c35177f51f99..379e4147ae024 100644
--- a/src/core/server/server.api.md
+++ b/src/core/server/server.api.md
@@ -872,7 +872,7 @@ export interface DeprecationsDetails {
[key: string]: any;
};
};
- manualSteps?: string[];
+ manualSteps: string[];
};
deprecationType?: 'config' | 'feature';
// (undocumented)
diff --git a/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts b/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts
index 01cebcb15963b..458b691573e56 100644
--- a/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts
+++ b/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts
@@ -48,7 +48,8 @@ const setup = () => {
};
};
-describe('createStreamingBatchedFunction()', () => {
+// FLAKY: https://github.com/elastic/kibana/issues/101126
+describe.skip('createStreamingBatchedFunction()', () => {
test('returns a function', () => {
const { fetchStreaming } = setup();
const fn = createStreamingBatchedFunction({
diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_spatial_filter.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_spatial_filter.test.ts
index 479cbb140fc70..0ae1513ae5d1b 100644
--- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_spatial_filter.test.ts
+++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_spatial_filter.test.ts
@@ -54,6 +54,61 @@ describe('mapSpatialFilter()', () => {
expect(result).toHaveProperty('type', FILTERS.SPATIAL_FILTER);
});
+ test('should return the key for matching multi field filter', async () => {
+ const filter = {
+ meta: {
+ alias: 'my spatial filter',
+ isMultiIndex: true,
+ type: FILTERS.SPATIAL_FILTER,
+ } as FilterMeta,
+ query: {
+ bool: {
+ should: [
+ {
+ bool: {
+ must: [
+ {
+ exists: {
+ field: 'geo.coordinates',
+ },
+ },
+ {
+ geo_distance: {
+ distance: '1000km',
+ 'geo.coordinates': [120, 30],
+ },
+ },
+ ],
+ },
+ },
+ {
+ bool: {
+ must: [
+ {
+ exists: {
+ field: 'location',
+ },
+ },
+ {
+ geo_distance: {
+ distance: '1000km',
+ location: [120, 30],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ } as Filter;
+ const result = mapSpatialFilter(filter);
+
+ expect(result).toHaveProperty('key', 'query');
+ expect(result).toHaveProperty('value', '');
+ expect(result).toHaveProperty('type', FILTERS.SPATIAL_FILTER);
+ });
+
test('should return undefined for none matching', async (done) => {
const filter = {
meta: {
diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_spatial_filter.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_spatial_filter.ts
index 0703bc055a39b..229257c1a7d81 100644
--- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_spatial_filter.ts
+++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_spatial_filter.ts
@@ -22,5 +22,18 @@ export const mapSpatialFilter = (filter: Filter) => {
value: '',
};
}
+
+ if (
+ filter.meta &&
+ filter.meta.type === FILTERS.SPATIAL_FILTER &&
+ filter.meta.isMultiIndex &&
+ filter.query?.bool?.should
+ ) {
+ return {
+ key: 'query',
+ type: filter.meta.type,
+ value: '',
+ };
+ }
throw filter;
};
diff --git a/test/plugin_functional/plugins/core_plugin_deprecations/server/config.ts b/test/plugin_functional/plugins/core_plugin_deprecations/server/config.ts
index e051c39f68150..650567f761aa3 100644
--- a/test/plugin_functional/plugins/core_plugin_deprecations/server/config.ts
+++ b/test/plugin_functional/plugins/core_plugin_deprecations/server/config.ts
@@ -26,6 +26,11 @@ const configSecretDeprecation: ConfigDeprecation = (settings, fromPath, addDepre
message:
'Kibana plugin functional tests will no longer allow corePluginDeprecations.secret ' +
'config to be set to anything except 42.',
+ correctiveActions: {
+ manualSteps: [
+ `This is an intentional deprecation for testing with no intention for having it fixed!`,
+ ],
+ },
});
}
return settings;
diff --git a/test/plugin_functional/plugins/core_plugin_deprecations/server/plugin.ts b/test/plugin_functional/plugins/core_plugin_deprecations/server/plugin.ts
index 65a2ce02aa0a4..9922e56f44bd9 100644
--- a/test/plugin_functional/plugins/core_plugin_deprecations/server/plugin.ts
+++ b/test/plugin_functional/plugins/core_plugin_deprecations/server/plugin.ts
@@ -29,7 +29,9 @@ async function getDeprecations({
message: `SavedObject test-deprecations-plugin is still being used.`,
documentationUrl: 'another-test-url',
level: 'critical',
- correctiveActions: {},
+ correctiveActions: {
+ manualSteps: ['Step a', 'Step b'],
+ },
});
}
diff --git a/test/plugin_functional/test_suites/core/deprecations.ts b/test/plugin_functional/test_suites/core/deprecations.ts
index 99b1a79fb51e3..38a8b835b118c 100644
--- a/test/plugin_functional/test_suites/core/deprecations.ts
+++ b/test/plugin_functional/test_suites/core/deprecations.ts
@@ -45,7 +45,11 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
level: 'critical',
message:
'Kibana plugin functional tests will no longer allow corePluginDeprecations.secret config to be set to anything except 42.',
- correctiveActions: {},
+ correctiveActions: {
+ manualSteps: [
+ 'This is an intentional deprecation for testing with no intention for having it fixed!',
+ ],
+ },
documentationUrl: 'config-secret-doc-url',
deprecationType: 'config',
domainId: 'corePluginDeprecations',
@@ -64,7 +68,9 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
message: 'SavedObject test-deprecations-plugin is still being used.',
documentationUrl: 'another-test-url',
level: 'critical',
- correctiveActions: {},
+ correctiveActions: {
+ manualSteps: ['Step a', 'Step b'],
+ },
domainId: 'corePluginDeprecations',
},
];
@@ -151,6 +157,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
mockFail: true,
},
},
+ manualSteps: ['Step a', 'Step b'],
},
domainId: 'corePluginDeprecations',
})
@@ -178,6 +185,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
mockFail: true,
},
},
+ manualSteps: ['Step a', 'Step b'],
},
domainId: 'corePluginDeprecations',
})
@@ -213,6 +221,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
path: '/api/core_deprecations_resolve/',
body: { keyId },
},
+ manualSteps: ['Step a', 'Step b'],
},
domainId: 'corePluginDeprecations',
})
diff --git a/x-pack/plugins/actions/server/index.ts b/x-pack/plugins/actions/server/index.ts
index 6a0f06b34d670..692ff6fa0a508 100644
--- a/x-pack/plugins/actions/server/index.ts
+++ b/x-pack/plugins/actions/server/index.ts
@@ -69,7 +69,18 @@ export const config: PluginConfigDescriptor = {
) {
addDeprecation({
message:
- '`xpack.actions.customHostSettings[].tls.rejectUnauthorized` is deprecated. Use `xpack.actions.customHostSettings[].tls.verificationMode` instead, with the setting `verificationMode:full` eql to `rejectUnauthorized:true`, and `verificationMode:none` eql to `rejectUnauthorized:false`.',
+ `"xpack.actions.customHostSettings[].tls.rejectUnauthorized" is deprecated.` +
+ `Use "xpack.actions.customHostSettings[].tls.verificationMode" instead, ` +
+ `with the setting "verificationMode:full" eql to "rejectUnauthorized:true", ` +
+ `and "verificationMode:none" eql to "rejectUnauthorized:false".`,
+ correctiveActions: {
+ manualSteps: [
+ `Remove "xpack.actions.customHostSettings[].tls.rejectUnauthorized" from your kibana configs.`,
+ `Use "xpack.actions.customHostSettings[].tls.verificationMode" ` +
+ `with the setting "verificationMode:full" eql to "rejectUnauthorized:true", ` +
+ `and "verificationMode:none" eql to "rejectUnauthorized:false".`,
+ ],
+ },
});
}
},
@@ -77,7 +88,17 @@ export const config: PluginConfigDescriptor = {
if (!!settings?.xpack?.actions?.rejectUnauthorized) {
addDeprecation({
message:
- '`xpack.actions.rejectUnauthorized` is deprecated. Use `xpack.actions.verificationMode` instead, with the setting `verificationMode:full` eql to `rejectUnauthorized:true`, and `verificationMode:none` eql to `rejectUnauthorized:false`.',
+ `"xpack.actions.rejectUnauthorized" is deprecated. Use "xpack.actions.verificationMode" instead, ` +
+ `with the setting "verificationMode:full" eql to "rejectUnauthorized:true", ` +
+ `and "verificationMode:none" eql to "rejectUnauthorized:false".`,
+ correctiveActions: {
+ manualSteps: [
+ `Remove "xpack.actions.rejectUnauthorized" from your kibana configs.`,
+ `Use "xpack.actions.verificationMode" ` +
+ `with the setting "verificationMode:full" eql to "rejectUnauthorized:true", ` +
+ `and "verificationMode:none" eql to "rejectUnauthorized:false".`,
+ ],
+ },
});
}
},
@@ -85,7 +106,17 @@ export const config: PluginConfigDescriptor = {
if (!!settings?.xpack?.actions?.proxyRejectUnauthorizedCertificates) {
addDeprecation({
message:
- '`xpack.actions.proxyRejectUnauthorizedCertificates` is deprecated. Use `xpack.actions.proxyVerificationMode` instead, with the setting `proxyVerificationMode:full` eql to `rejectUnauthorized:true`, and `proxyVerificationMode:none` eql to `rejectUnauthorized:false`.',
+ `"xpack.actions.proxyRejectUnauthorizedCertificates" is deprecated. Use "xpack.actions.proxyVerificationMode" instead, ` +
+ `with the setting "proxyVerificationMode:full" eql to "rejectUnauthorized:true",` +
+ `and "proxyVerificationMode:none" eql to "rejectUnauthorized:false".`,
+ correctiveActions: {
+ manualSteps: [
+ `Remove "xpack.actions.proxyRejectUnauthorizedCertificates" from your kibana configs.`,
+ `Use "xpack.actions.proxyVerificationMode" ` +
+ `with the setting "proxyVerificationMode:full" eql to "rejectUnauthorized:true",` +
+ `and "proxyVerificationMode:none" eql to "rejectUnauthorized:false".`,
+ ],
+ },
});
}
},
diff --git a/x-pack/plugins/banners/server/config.ts b/x-pack/plugins/banners/server/config.ts
index 5ee01ec2b0b19..37b4c57fc2ce1 100644
--- a/x-pack/plugins/banners/server/config.ts
+++ b/x-pack/plugins/banners/server/config.ts
@@ -45,6 +45,12 @@ export const config: PluginConfigDescriptor = {
if (pluginConfig?.placement === 'header') {
addDeprecation({
message: 'The `header` value for xpack.banners.placement has been replaced by `top`',
+ correctiveActions: {
+ manualSteps: [
+ `Remove "xpack.banners.placement: header" from your kibana configs.`,
+ `Add "xpack.banners.placement: to" to your kibana configs instead.`,
+ ],
+ },
});
return {
set: [{ path: `${fromPath}.placement`, value: 'top' }],
diff --git a/x-pack/plugins/lens/public/index.ts b/x-pack/plugins/lens/public/index.ts
index fcfed9b9f1fc5..0fdd3bf426232 100644
--- a/x-pack/plugins/lens/public/index.ts
+++ b/x-pack/plugins/lens/public/index.ts
@@ -20,6 +20,7 @@ export type {
ValueLabelConfig,
YAxisMode,
XYCurveType,
+ YConfig,
} from './xy_visualization/types';
export type { DataType, OperationMetadata } from './types';
export type {
diff --git a/x-pack/plugins/maps/common/descriptor_types/map_descriptor.ts b/x-pack/plugins/maps/common/descriptor_types/map_descriptor.ts
index 102434ffda161..4092ba49888bc 100644
--- a/x-pack/plugins/maps/common/descriptor_types/map_descriptor.ts
+++ b/x-pack/plugins/maps/common/descriptor_types/map_descriptor.ts
@@ -11,7 +11,7 @@ import { ReactNode } from 'react';
import { GeoJsonProperties } from 'geojson';
import { Geometry } from 'geojson';
import { Query } from '../../../../../src/plugins/data/common';
-import { DRAW_TYPE, ES_GEO_FIELD_TYPE, ES_SPATIAL_RELATIONS } from '../constants';
+import { DRAW_TYPE, ES_SPATIAL_RELATIONS } from '../constants';
export type MapExtent = {
minLon: number;
@@ -70,9 +70,6 @@ export type DrawState = {
actionId: string;
drawType: DRAW_TYPE;
filterLabel?: string; // point radius filter alias
- geoFieldName?: string;
- geoFieldType?: ES_GEO_FIELD_TYPE;
geometryLabel?: string;
- indexPatternId?: string;
relation?: ES_SPATIAL_RELATIONS;
};
diff --git a/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.test.js b/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.test.js
index c2ca952c3e8c9..e6b27e78fd36b 100644
--- a/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.test.js
+++ b/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.test.js
@@ -9,9 +9,7 @@ import {
hitsToGeoJson,
geoPointToGeometry,
geoShapeToGeometry,
- createExtentFilter,
roundCoordinates,
- extractFeaturesFromFilters,
makeESBbox,
scaleBounds,
} from './elasticsearch_geo_utils';
@@ -388,94 +386,6 @@ describe('geoShapeToGeometry', () => {
});
});
-describe('createExtentFilter', () => {
- it('should return elasticsearch geo_bounding_box filter', () => {
- const mapExtent = {
- maxLat: 39,
- maxLon: -83,
- minLat: 35,
- minLon: -89,
- };
- const filter = createExtentFilter(mapExtent, [geoFieldName]);
- expect(filter.geo_bounding_box).toEqual({
- location: {
- top_left: [-89, 39],
- bottom_right: [-83, 35],
- },
- });
- });
-
- it('should clamp longitudes to -180 to 180 and latitudes to -90 to 90', () => {
- const mapExtent = {
- maxLat: 120,
- maxLon: 200,
- minLat: -100,
- minLon: -190,
- };
- const filter = createExtentFilter(mapExtent, [geoFieldName]);
- expect(filter.geo_bounding_box).toEqual({
- location: {
- top_left: [-180, 89],
- bottom_right: [180, -89],
- },
- });
- });
-
- it('should make left longitude greater than right longitude when area crosses 180 meridian east to west', () => {
- const mapExtent = {
- maxLat: 39,
- maxLon: 200,
- minLat: 35,
- minLon: 100,
- };
- const filter = createExtentFilter(mapExtent, [geoFieldName]);
- const leftLon = filter.geo_bounding_box.location.top_left[0];
- const rightLon = filter.geo_bounding_box.location.bottom_right[0];
- expect(leftLon).toBeGreaterThan(rightLon);
- expect(filter.geo_bounding_box).toEqual({
- location: {
- top_left: [100, 39],
- bottom_right: [-160, 35],
- },
- });
- });
-
- it('should make left longitude greater than right longitude when area crosses 180 meridian west to east', () => {
- const mapExtent = {
- maxLat: 39,
- maxLon: -100,
- minLat: 35,
- minLon: -200,
- };
- const filter = createExtentFilter(mapExtent, [geoFieldName]);
- const leftLon = filter.geo_bounding_box.location.top_left[0];
- const rightLon = filter.geo_bounding_box.location.bottom_right[0];
- expect(leftLon).toBeGreaterThan(rightLon);
- expect(filter.geo_bounding_box).toEqual({
- location: {
- top_left: [160, 39],
- bottom_right: [-100, 35],
- },
- });
- });
-
- it('should clamp longitudes to -180 to 180 when longitude wraps globe', () => {
- const mapExtent = {
- maxLat: 39,
- maxLon: 209,
- minLat: 35,
- minLon: -191,
- };
- const filter = createExtentFilter(mapExtent, [geoFieldName]);
- expect(filter.geo_bounding_box).toEqual({
- location: {
- top_left: [-180, 39],
- bottom_right: [180, 35],
- },
- });
- });
-});
-
describe('roundCoordinates', () => {
it('should set coordinates precision', () => {
const coordinates = [
@@ -492,134 +402,6 @@ describe('roundCoordinates', () => {
});
});
-describe('extractFeaturesFromFilters', () => {
- it('should ignore non-spatial filers', () => {
- const phraseFilter = {
- meta: {
- alias: null,
- disabled: false,
- index: '90943e30-9a47-11e8-b64d-95841ca0b247',
- key: 'machine.os',
- negate: false,
- params: {
- query: 'ios',
- },
- type: 'phrase',
- },
- query: {
- match_phrase: {
- 'machine.os': 'ios',
- },
- },
- };
- expect(extractFeaturesFromFilters([phraseFilter])).toEqual([]);
- });
-
- it('should convert geo_distance filter to feature', () => {
- const spatialFilter = {
- geo_distance: {
- distance: '1096km',
- 'geo.coordinates': [-89.87125, 53.49454],
- },
- meta: {
- alias: 'geo.coordinates within 1096km of -89.87125,53.49454',
- disabled: false,
- index: '90943e30-9a47-11e8-b64d-95841ca0b247',
- key: 'geo.coordinates',
- negate: false,
- type: 'spatial_filter',
- value: '',
- },
- };
-
- const features = extractFeaturesFromFilters([spatialFilter]);
- expect(features[0].geometry.coordinates[0][0]).toEqual([-89.87125, 63.35109118642093]);
- expect(features[0].properties).toEqual({
- filter: 'geo.coordinates within 1096km of -89.87125,53.49454',
- });
- });
-
- it('should convert geo_shape filter to feature', () => {
- const spatialFilter = {
- geo_shape: {
- 'geo.coordinates': {
- relation: 'INTERSECTS',
- shape: {
- coordinates: [
- [
- [-101.21639, 48.1413],
- [-101.21639, 41.84905],
- [-90.95149, 41.84905],
- [-90.95149, 48.1413],
- [-101.21639, 48.1413],
- ],
- ],
- type: 'Polygon',
- },
- },
- ignore_unmapped: true,
- },
- meta: {
- alias: 'geo.coordinates in bounds',
- disabled: false,
- index: '90943e30-9a47-11e8-b64d-95841ca0b247',
- key: 'geo.coordinates',
- negate: false,
- type: 'spatial_filter',
- value: '',
- },
- };
-
- expect(extractFeaturesFromFilters([spatialFilter])).toEqual([
- {
- type: 'Feature',
- geometry: {
- type: 'Polygon',
- coordinates: [
- [
- [-101.21639, 48.1413],
- [-101.21639, 41.84905],
- [-90.95149, 41.84905],
- [-90.95149, 48.1413],
- [-101.21639, 48.1413],
- ],
- ],
- },
- properties: {
- filter: 'geo.coordinates in bounds',
- },
- },
- ]);
- });
-
- it('should ignore geo_shape filter with pre-index shape', () => {
- const spatialFilter = {
- geo_shape: {
- 'geo.coordinates': {
- indexed_shape: {
- id: 's5gldXEBkTB2HMwpC8y0',
- index: 'world_countries_v1',
- path: 'coordinates',
- },
- relation: 'INTERSECTS',
- },
- ignore_unmapped: true,
- },
- meta: {
- alias: 'geo.coordinates in multipolygon',
- disabled: false,
- index: '90943e30-9a47-11e8-b64d-95841ca0b247',
- key: 'geo.coordinates',
- negate: false,
- type: 'spatial_filter',
- value: '',
- },
- };
-
- expect(extractFeaturesFromFilters([spatialFilter])).toEqual([]);
- });
-});
-
describe('makeESBbox', () => {
it('Should invert Y-axis', () => {
const bbox = makeESBbox({
diff --git a/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.ts b/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.ts
index e47afce77f779..8033f8d187fd5 100644
--- a/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.ts
+++ b/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.ts
@@ -16,61 +16,13 @@ import { BBox } from '@turf/helpers';
import {
DECIMAL_DEGREES_PRECISION,
ES_GEO_FIELD_TYPE,
- ES_SPATIAL_RELATIONS,
GEO_JSON_TYPE,
POLYGON_COORDINATES_EXTERIOR_INDEX,
LON_INDEX,
LAT_INDEX,
} from '../constants';
-import { getEsSpatialRelationLabel } from '../i18n_getters';
-import { Filter, FilterMeta, FILTERS } from '../../../../../src/plugins/data/common';
import { MapExtent } from '../descriptor_types';
-
-const SPATIAL_FILTER_TYPE = FILTERS.SPATIAL_FILTER;
-
-type Coordinates = Position | Position[] | Position[][] | Position[][][];
-
-// Elasticsearch stores more then just GeoJSON.
-// 1) geometry.type as lower case string
-// 2) circle and envelope types
-interface ESGeometry {
- type: string;
- coordinates: Coordinates;
-}
-
-export interface ESBBox {
- top_left: number[];
- bottom_right: number[];
-}
-
-interface GeoShapeQueryBody {
- shape?: Polygon;
- relation?: ES_SPATIAL_RELATIONS;
- indexed_shape?: PreIndexedShape;
-}
-
-// Index signature explicitly states that anything stored in an object using a string conforms to the structure
-// problem is that Elasticsearch signature also allows for other string keys to conform to other structures, like 'ignore_unmapped'
-// Use intersection type to exclude certain properties from the index signature
-// https://basarat.gitbook.io/typescript/type-system/index-signatures#excluding-certain-properties-from-the-index-signature
-type GeoShapeQuery = { ignore_unmapped: boolean } & { [geoFieldName: string]: GeoShapeQueryBody };
-
-export type GeoFilter = Filter & {
- geo_bounding_box?: {
- [geoFieldName: string]: ESBBox;
- };
- geo_distance?: {
- distance: string;
- [geoFieldName: string]: Position | { lat: number; lon: number } | string;
- };
- geo_shape?: GeoShapeQuery;
-};
-
-export interface PreIndexedShape {
- index: string;
- id: string | number;
- path: string;
-}
+import { Coordinates, ESBBox, ESGeometry } from './types';
function ensureGeoField(type: string) {
const expectedTypes = [ES_GEO_FIELD_TYPE.GEO_POINT, ES_GEO_FIELD_TYPE.GEO_SHAPE];
@@ -349,136 +301,6 @@ export function makeESBbox({ maxLat, maxLon, minLat, minLon }: MapExtent): ESBBo
return esBbox;
}
-export function createExtentFilter(mapExtent: MapExtent, geoFieldNames: string[]): GeoFilter {
- const esBbox = makeESBbox(mapExtent);
- return geoFieldNames.length === 1
- ? {
- geo_bounding_box: {
- [geoFieldNames[0]]: esBbox,
- },
- meta: {
- alias: null,
- disabled: false,
- negate: false,
- key: geoFieldNames[0],
- },
- }
- : {
- query: {
- bool: {
- should: geoFieldNames.map((geoFieldName) => {
- return {
- bool: {
- must: [
- {
- exists: {
- field: geoFieldName,
- },
- },
- {
- geo_bounding_box: {
- [geoFieldName]: esBbox,
- },
- },
- ],
- },
- };
- }),
- },
- },
- meta: {
- alias: null,
- disabled: false,
- negate: false,
- },
- };
-}
-
-export function createSpatialFilterWithGeometry({
- preIndexedShape,
- geometry,
- geometryLabel,
- indexPatternId,
- geoFieldName,
- relation = ES_SPATIAL_RELATIONS.INTERSECTS,
-}: {
- preIndexedShape?: PreIndexedShape | null;
- geometry: Polygon;
- geometryLabel: string;
- indexPatternId: string;
- geoFieldName: string;
- relation: ES_SPATIAL_RELATIONS;
-}): GeoFilter {
- const meta: FilterMeta = {
- type: SPATIAL_FILTER_TYPE,
- negate: false,
- index: indexPatternId,
- key: geoFieldName,
- alias: `${geoFieldName} ${getEsSpatialRelationLabel(relation)} ${geometryLabel}`,
- disabled: false,
- };
-
- const shapeQuery: GeoShapeQueryBody = {
- relation,
- };
- if (preIndexedShape) {
- shapeQuery.indexed_shape = preIndexedShape;
- } else {
- shapeQuery.shape = geometry;
- }
-
- return {
- meta,
- // Currently no way to create an object with exclude property from index signature
- // typescript error for "ignore_unmapped is not assignable to type 'GeoShapeQueryBody'" expected"
- // @ts-expect-error
- geo_shape: {
- ignore_unmapped: true,
- [geoFieldName]: shapeQuery,
- },
- };
-}
-
-export function createDistanceFilterWithMeta({
- alias,
- distanceKm,
- geoFieldName,
- indexPatternId,
- point,
-}: {
- alias: string;
- distanceKm: number;
- geoFieldName: string;
- indexPatternId: string;
- point: Position;
-}): GeoFilter {
- const meta: FilterMeta = {
- type: SPATIAL_FILTER_TYPE,
- negate: false,
- index: indexPatternId,
- key: geoFieldName,
- alias: alias
- ? alias
- : i18n.translate('xpack.maps.es_geo_utils.distanceFilterAlias', {
- defaultMessage: '{geoFieldName} within {distanceKm}km of {pointLabel}',
- values: {
- distanceKm,
- geoFieldName,
- pointLabel: point.join(', '),
- },
- }),
- disabled: false,
- };
-
- return {
- geo_distance: {
- distance: `${distanceKm}km`,
- [geoFieldName]: point,
- },
- meta,
- };
-}
-
export function roundCoordinates(coordinates: Coordinates): void {
for (let i = 0; i < coordinates.length; i++) {
const value = coordinates[i];
@@ -549,44 +371,6 @@ export function clamp(val: number, min: number, max: number): number {
}
}
-export function extractFeaturesFromFilters(filters: GeoFilter[]): Feature[] {
- const features: Feature[] = [];
- filters
- .filter((filter) => {
- return filter.meta.key && filter.meta.type === SPATIAL_FILTER_TYPE;
- })
- .forEach((filter) => {
- const geoFieldName = filter.meta.key!;
- let geometry;
- if (filter.geo_distance && filter.geo_distance[geoFieldName]) {
- const distanceSplit = filter.geo_distance.distance.split('km');
- const distance = parseFloat(distanceSplit[0]);
- const circleFeature = turfCircle(filter.geo_distance[geoFieldName], distance);
- geometry = circleFeature.geometry;
- } else if (
- filter.geo_shape &&
- filter.geo_shape[geoFieldName] &&
- filter.geo_shape[geoFieldName].shape
- ) {
- geometry = filter.geo_shape[geoFieldName].shape;
- } else {
- // do not know how to convert spatial filter to geometry
- // this includes pre-indexed shapes
- return;
- }
-
- features.push({
- type: 'Feature',
- geometry,
- properties: {
- filter: filter.meta.alias,
- },
- });
- });
-
- return features;
-}
-
export function scaleBounds(bounds: MapExtent, scaleFactor: number): MapExtent {
const width = bounds.maxLon - bounds.minLon;
const height = bounds.maxLat - bounds.minLat;
diff --git a/x-pack/plugins/maps/common/elasticsearch_util/index.ts b/x-pack/plugins/maps/common/elasticsearch_util/index.ts
index 24dd56b217401..7073a4201f7a5 100644
--- a/x-pack/plugins/maps/common/elasticsearch_util/index.ts
+++ b/x-pack/plugins/maps/common/elasticsearch_util/index.ts
@@ -8,4 +8,6 @@
export * from './es_agg_utils';
export * from './convert_to_geojson';
export * from './elasticsearch_geo_utils';
+export * from './spatial_filter_utils';
+export * from './types';
export { isTotalHitsGreaterThan, TotalHits } from './total_hits';
diff --git a/x-pack/plugins/maps/common/elasticsearch_util/spatial_filter_utils.test.ts b/x-pack/plugins/maps/common/elasticsearch_util/spatial_filter_utils.test.ts
new file mode 100644
index 0000000000000..d828aca4a1a00
--- /dev/null
+++ b/x-pack/plugins/maps/common/elasticsearch_util/spatial_filter_utils.test.ts
@@ -0,0 +1,534 @@
+/*
+ * 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 { Polygon } from 'geojson';
+import {
+ createDistanceFilterWithMeta,
+ createExtentFilter,
+ createSpatialFilterWithGeometry,
+ extractFeaturesFromFilters,
+} from './spatial_filter_utils';
+
+const geoFieldName = 'location';
+
+describe('createExtentFilter', () => {
+ it('should return elasticsearch geo_bounding_box filter', () => {
+ const mapExtent = {
+ maxLat: 39,
+ maxLon: -83,
+ minLat: 35,
+ minLon: -89,
+ };
+ const filter = createExtentFilter(mapExtent, [geoFieldName]);
+ expect(filter.geo_bounding_box).toEqual({
+ location: {
+ top_left: [-89, 39],
+ bottom_right: [-83, 35],
+ },
+ });
+ });
+
+ it('should clamp longitudes to -180 to 180 and latitudes to -90 to 90', () => {
+ const mapExtent = {
+ maxLat: 120,
+ maxLon: 200,
+ minLat: -100,
+ minLon: -190,
+ };
+ const filter = createExtentFilter(mapExtent, [geoFieldName]);
+ expect(filter.geo_bounding_box).toEqual({
+ location: {
+ top_left: [-180, 89],
+ bottom_right: [180, -89],
+ },
+ });
+ });
+
+ it('should make left longitude greater than right longitude when area crosses 180 meridian east to west', () => {
+ const mapExtent = {
+ maxLat: 39,
+ maxLon: 200,
+ minLat: 35,
+ minLon: 100,
+ };
+ const filter = createExtentFilter(mapExtent, [geoFieldName]);
+ expect(filter.geo_bounding_box).toEqual({
+ location: {
+ top_left: [100, 39],
+ bottom_right: [-160, 35],
+ },
+ });
+ });
+
+ it('should make left longitude greater than right longitude when area crosses 180 meridian west to east', () => {
+ const mapExtent = {
+ maxLat: 39,
+ maxLon: -100,
+ minLat: 35,
+ minLon: -200,
+ };
+ const filter = createExtentFilter(mapExtent, [geoFieldName]);
+ expect(filter.geo_bounding_box).toEqual({
+ location: {
+ top_left: [160, 39],
+ bottom_right: [-100, 35],
+ },
+ });
+ });
+
+ it('should clamp longitudes to -180 to 180 when longitude wraps globe', () => {
+ const mapExtent = {
+ maxLat: 39,
+ maxLon: 209,
+ minLat: 35,
+ minLon: -191,
+ };
+ const filter = createExtentFilter(mapExtent, [geoFieldName]);
+ expect(filter.geo_bounding_box).toEqual({
+ location: {
+ top_left: [-180, 39],
+ bottom_right: [180, 35],
+ },
+ });
+ });
+
+ it('should support multiple geo fields', () => {
+ const mapExtent = {
+ maxLat: 39,
+ maxLon: -83,
+ minLat: 35,
+ minLon: -89,
+ };
+ expect(createExtentFilter(mapExtent, [geoFieldName, 'myOtherLocation'])).toEqual({
+ meta: {
+ alias: null,
+ disabled: false,
+ isMultiIndex: true,
+ negate: false,
+ },
+ query: {
+ bool: {
+ should: [
+ {
+ bool: {
+ must: [
+ {
+ exists: {
+ field: 'location',
+ },
+ },
+ {
+ geo_bounding_box: {
+ location: {
+ top_left: [-89, 39],
+ bottom_right: [-83, 35],
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ bool: {
+ must: [
+ {
+ exists: {
+ field: 'myOtherLocation',
+ },
+ },
+ {
+ geo_bounding_box: {
+ myOtherLocation: {
+ top_left: [-89, 39],
+ bottom_right: [-83, 35],
+ },
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ });
+ });
+});
+
+describe('createSpatialFilterWithGeometry', () => {
+ it('should build filter for single field', () => {
+ const spatialFilter = createSpatialFilterWithGeometry({
+ geometry: {
+ coordinates: [
+ [
+ [-101.21639, 48.1413],
+ [-101.21639, 41.84905],
+ [-90.95149, 41.84905],
+ [-90.95149, 48.1413],
+ [-101.21639, 48.1413],
+ ],
+ ],
+ type: 'Polygon',
+ },
+ geometryLabel: 'myShape',
+ geoFieldNames: ['geo.coordinates'],
+ });
+ expect(spatialFilter).toEqual({
+ meta: {
+ alias: 'intersects myShape',
+ disabled: false,
+ key: 'geo.coordinates',
+ negate: false,
+ type: 'spatial_filter',
+ },
+ geo_shape: {
+ 'geo.coordinates': {
+ relation: 'INTERSECTS',
+ shape: {
+ coordinates: [
+ [
+ [-101.21639, 48.1413],
+ [-101.21639, 41.84905],
+ [-90.95149, 41.84905],
+ [-90.95149, 48.1413],
+ [-101.21639, 48.1413],
+ ],
+ ],
+ type: 'Polygon',
+ },
+ },
+ ignore_unmapped: true,
+ },
+ });
+ });
+
+ it('should build filter for multiple field', () => {
+ const spatialFilter = createSpatialFilterWithGeometry({
+ geometry: {
+ coordinates: [
+ [
+ [-101.21639, 48.1413],
+ [-101.21639, 41.84905],
+ [-90.95149, 41.84905],
+ [-90.95149, 48.1413],
+ [-101.21639, 48.1413],
+ ],
+ ],
+ type: 'Polygon',
+ },
+ geometryLabel: 'myShape',
+ geoFieldNames: ['geo.coordinates', 'location'],
+ });
+ expect(spatialFilter).toEqual({
+ meta: {
+ alias: 'intersects myShape',
+ disabled: false,
+ isMultiIndex: true,
+ key: undefined,
+ negate: false,
+ type: 'spatial_filter',
+ },
+ query: {
+ bool: {
+ should: [
+ {
+ bool: {
+ must: [
+ {
+ exists: {
+ field: 'geo.coordinates',
+ },
+ },
+ {
+ geo_shape: {
+ 'geo.coordinates': {
+ relation: 'INTERSECTS',
+ shape: {
+ coordinates: [
+ [
+ [-101.21639, 48.1413],
+ [-101.21639, 41.84905],
+ [-90.95149, 41.84905],
+ [-90.95149, 48.1413],
+ [-101.21639, 48.1413],
+ ],
+ ],
+ type: 'Polygon',
+ },
+ },
+ ignore_unmapped: true,
+ },
+ },
+ ],
+ },
+ },
+ {
+ bool: {
+ must: [
+ {
+ exists: {
+ field: 'location',
+ },
+ },
+ {
+ geo_shape: {
+ location: {
+ relation: 'INTERSECTS',
+ shape: {
+ coordinates: [
+ [
+ [-101.21639, 48.1413],
+ [-101.21639, 41.84905],
+ [-90.95149, 41.84905],
+ [-90.95149, 48.1413],
+ [-101.21639, 48.1413],
+ ],
+ ],
+ type: 'Polygon',
+ },
+ },
+ ignore_unmapped: true,
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ });
+ });
+});
+
+describe('createDistanceFilterWithMeta', () => {
+ it('should build filter for single field', () => {
+ const spatialFilter = createDistanceFilterWithMeta({
+ point: [120, 30],
+ distanceKm: 1000,
+ geoFieldNames: ['geo.coordinates'],
+ });
+ expect(spatialFilter).toEqual({
+ meta: {
+ alias: 'within 1000km of 120, 30',
+ disabled: false,
+ key: 'geo.coordinates',
+ negate: false,
+ type: 'spatial_filter',
+ },
+ geo_distance: {
+ distance: '1000km',
+ 'geo.coordinates': [120, 30],
+ },
+ });
+ });
+
+ it('should build filter for multiple field', () => {
+ const spatialFilter = createDistanceFilterWithMeta({
+ point: [120, 30],
+ distanceKm: 1000,
+ geoFieldNames: ['geo.coordinates', 'location'],
+ });
+ expect(spatialFilter).toEqual({
+ meta: {
+ alias: 'within 1000km of 120, 30',
+ disabled: false,
+ isMultiIndex: true,
+ key: undefined,
+ negate: false,
+ type: 'spatial_filter',
+ },
+ query: {
+ bool: {
+ should: [
+ {
+ bool: {
+ must: [
+ {
+ exists: {
+ field: 'geo.coordinates',
+ },
+ },
+ {
+ geo_distance: {
+ distance: '1000km',
+ 'geo.coordinates': [120, 30],
+ },
+ },
+ ],
+ },
+ },
+ {
+ bool: {
+ must: [
+ {
+ exists: {
+ field: 'location',
+ },
+ },
+ {
+ geo_distance: {
+ distance: '1000km',
+ location: [120, 30],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ });
+ });
+});
+
+describe('extractFeaturesFromFilters', () => {
+ it('should ignore non-spatial filers', () => {
+ const phraseFilter = {
+ meta: {
+ alias: null,
+ disabled: false,
+ index: '90943e30-9a47-11e8-b64d-95841ca0b247',
+ key: 'machine.os',
+ negate: false,
+ params: {
+ query: 'ios',
+ },
+ type: 'phrase',
+ },
+ query: {
+ match_phrase: {
+ 'machine.os': 'ios',
+ },
+ },
+ };
+ expect(extractFeaturesFromFilters([phraseFilter])).toEqual([]);
+ });
+
+ it('should convert single field geo_distance filter to feature', () => {
+ const spatialFilter = createDistanceFilterWithMeta({
+ point: [-89.87125, 53.49454],
+ distanceKm: 1096,
+ geoFieldNames: ['geo.coordinates', 'location'],
+ });
+
+ const features = extractFeaturesFromFilters([spatialFilter]);
+ expect((features[0].geometry as Polygon).coordinates[0][0]).toEqual([
+ -89.87125,
+ 63.35109118642093,
+ ]);
+ expect(features[0].properties).toEqual({
+ filter: 'within 1096km of -89.87125, 53.49454',
+ });
+ });
+
+ it('should convert multi field geo_distance filter to feature', () => {
+ const spatialFilter = createDistanceFilterWithMeta({
+ point: [-89.87125, 53.49454],
+ distanceKm: 1096,
+ geoFieldNames: ['geo.coordinates', 'location'],
+ });
+
+ const features = extractFeaturesFromFilters([spatialFilter]);
+ expect((features[0].geometry as Polygon).coordinates[0][0]).toEqual([
+ -89.87125,
+ 63.35109118642093,
+ ]);
+ expect(features[0].properties).toEqual({
+ filter: 'within 1096km of -89.87125, 53.49454',
+ });
+ });
+
+ it('should convert single field geo_shape filter to feature', () => {
+ const spatialFilter = createSpatialFilterWithGeometry({
+ geometry: {
+ coordinates: [
+ [
+ [-101.21639, 48.1413],
+ [-101.21639, 41.84905],
+ [-90.95149, 41.84905],
+ [-90.95149, 48.1413],
+ [-101.21639, 48.1413],
+ ],
+ ],
+ type: 'Polygon',
+ },
+ geometryLabel: 'myShape',
+ geoFieldNames: ['geo.coordinates'],
+ });
+ expect(extractFeaturesFromFilters([spatialFilter])).toEqual([
+ {
+ type: 'Feature',
+ geometry: {
+ type: 'Polygon',
+ coordinates: [
+ [
+ [-101.21639, 48.1413],
+ [-101.21639, 41.84905],
+ [-90.95149, 41.84905],
+ [-90.95149, 48.1413],
+ [-101.21639, 48.1413],
+ ],
+ ],
+ } as Polygon,
+ properties: {
+ filter: 'intersects myShape',
+ },
+ },
+ ]);
+ });
+
+ it('should convert multi field geo_shape filter to feature', () => {
+ const spatialFilter = createSpatialFilterWithGeometry({
+ geometry: {
+ coordinates: [
+ [
+ [-101.21639, 48.1413],
+ [-101.21639, 41.84905],
+ [-90.95149, 41.84905],
+ [-90.95149, 48.1413],
+ [-101.21639, 48.1413],
+ ],
+ ],
+ type: 'Polygon',
+ },
+ geometryLabel: 'myShape',
+ geoFieldNames: ['geo.coordinates', 'location'],
+ });
+ expect(extractFeaturesFromFilters([spatialFilter])).toEqual([
+ {
+ type: 'Feature',
+ geometry: {
+ type: 'Polygon',
+ coordinates: [
+ [
+ [-101.21639, 48.1413],
+ [-101.21639, 41.84905],
+ [-90.95149, 41.84905],
+ [-90.95149, 48.1413],
+ [-101.21639, 48.1413],
+ ],
+ ],
+ } as Polygon,
+ properties: {
+ filter: 'intersects myShape',
+ },
+ },
+ ]);
+ });
+
+ it('should ignore geo_shape filter with pre-index shape', () => {
+ const spatialFilter = createSpatialFilterWithGeometry({
+ preIndexedShape: {
+ index: 'world_countries_v1',
+ id: 's5gldXEBkTB2HMwpC8y0',
+ path: 'coordinates',
+ },
+ geometryLabel: 'myShape',
+ geoFieldNames: ['geo.coordinates'],
+ });
+ expect(extractFeaturesFromFilters([spatialFilter])).toEqual([]);
+ });
+});
diff --git a/x-pack/plugins/maps/common/elasticsearch_util/spatial_filter_utils.ts b/x-pack/plugins/maps/common/elasticsearch_util/spatial_filter_utils.ts
new file mode 100644
index 0000000000000..70df9e9646f50
--- /dev/null
+++ b/x-pack/plugins/maps/common/elasticsearch_util/spatial_filter_utils.ts
@@ -0,0 +1,221 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { i18n } from '@kbn/i18n';
+import { Feature, Geometry, Polygon, Position } from 'geojson';
+// @ts-expect-error
+import turfCircle from '@turf/circle';
+import { FilterMeta, FILTERS } from '../../../../../src/plugins/data/common';
+import { MapExtent } from '../descriptor_types';
+import { ES_SPATIAL_RELATIONS } from '../constants';
+import { getEsSpatialRelationLabel } from '../i18n_getters';
+import { GeoFilter, GeoShapeQueryBody, PreIndexedShape } from './types';
+import { makeESBbox } from './elasticsearch_geo_utils';
+
+const SPATIAL_FILTER_TYPE = FILTERS.SPATIAL_FILTER;
+
+// wrapper around boiler plate code for creating bool.should clause with nested bool.must clauses
+// ensuring geoField exists prior to running geoField query
+// This allows for writing a single geo filter that spans multiple indices with different geo fields.
+function createMultiGeoFieldFilter(
+ geoFieldNames: string[],
+ meta: FilterMeta,
+ createGeoFilter: (geoFieldName: string) => Omit
+): GeoFilter {
+ if (geoFieldNames.length === 0) {
+ throw new Error('Unable to create filter, geo fields not provided');
+ }
+
+ if (geoFieldNames.length === 1) {
+ const geoFilter = createGeoFilter(geoFieldNames[0]);
+ return {
+ meta: {
+ ...meta,
+ key: geoFieldNames[0],
+ },
+ ...geoFilter,
+ };
+ }
+
+ return {
+ meta: {
+ ...meta,
+ key: undefined,
+ isMultiIndex: true,
+ },
+ query: {
+ bool: {
+ should: geoFieldNames.map((geoFieldName) => {
+ return {
+ bool: {
+ must: [
+ {
+ exists: {
+ field: geoFieldName,
+ },
+ },
+ createGeoFilter(geoFieldName),
+ ],
+ },
+ };
+ }),
+ },
+ },
+ };
+}
+
+export function createExtentFilter(mapExtent: MapExtent, geoFieldNames: string[]): GeoFilter {
+ const esBbox = makeESBbox(mapExtent);
+ function createGeoFilter(geoFieldName: string) {
+ return {
+ geo_bounding_box: {
+ [geoFieldName]: esBbox,
+ },
+ };
+ }
+
+ const meta: FilterMeta = {
+ alias: null,
+ disabled: false,
+ negate: false,
+ };
+
+ return createMultiGeoFieldFilter(geoFieldNames, meta, createGeoFilter);
+}
+
+export function createSpatialFilterWithGeometry({
+ preIndexedShape,
+ geometry,
+ geometryLabel,
+ geoFieldNames,
+ relation = ES_SPATIAL_RELATIONS.INTERSECTS,
+}: {
+ preIndexedShape?: PreIndexedShape | null;
+ geometry?: Polygon;
+ geometryLabel: string;
+ geoFieldNames: string[];
+ relation?: ES_SPATIAL_RELATIONS;
+}): GeoFilter {
+ const meta: FilterMeta = {
+ type: SPATIAL_FILTER_TYPE,
+ negate: false,
+ key: geoFieldNames.length === 1 ? geoFieldNames[0] : undefined,
+ alias: `${getEsSpatialRelationLabel(relation)} ${geometryLabel}`,
+ disabled: false,
+ };
+
+ function createGeoFilter(geoFieldName: string) {
+ const shapeQuery: GeoShapeQueryBody = {
+ relation,
+ };
+ if (preIndexedShape) {
+ shapeQuery.indexed_shape = preIndexedShape;
+ } else if (geometry) {
+ shapeQuery.shape = geometry;
+ } else {
+ throw new Error('Must supply either preIndexedShape or geometry, you did not supply either');
+ }
+
+ return {
+ geo_shape: {
+ ignore_unmapped: true,
+ [geoFieldName]: shapeQuery,
+ },
+ };
+ }
+
+ // Currently no way to create an object with exclude property from index signature
+ // typescript error for "ignore_unmapped is not assignable to type 'GeoShapeQueryBody'" expected"
+ // @ts-expect-error
+ return createMultiGeoFieldFilter(geoFieldNames, meta, createGeoFilter);
+}
+
+export function createDistanceFilterWithMeta({
+ alias,
+ distanceKm,
+ geoFieldNames,
+ point,
+}: {
+ alias?: string;
+ distanceKm: number;
+ geoFieldNames: string[];
+ point: Position;
+}): GeoFilter {
+ const meta: FilterMeta = {
+ type: SPATIAL_FILTER_TYPE,
+ negate: false,
+ alias: alias
+ ? alias
+ : i18n.translate('xpack.maps.es_geo_utils.distanceFilterAlias', {
+ defaultMessage: 'within {distanceKm}km of {pointLabel}',
+ values: {
+ distanceKm,
+ pointLabel: point.join(', '),
+ },
+ }),
+ disabled: false,
+ };
+
+ function createGeoFilter(geoFieldName: string) {
+ return {
+ geo_distance: {
+ distance: `${distanceKm}km`,
+ [geoFieldName]: point,
+ },
+ };
+ }
+
+ return createMultiGeoFieldFilter(geoFieldNames, meta, createGeoFilter);
+}
+
+function extractGeometryFromFilter(geoFieldName: string, filter: GeoFilter): Geometry | undefined {
+ if (filter.geo_distance && filter.geo_distance[geoFieldName]) {
+ const distanceSplit = filter.geo_distance.distance.split('km');
+ const distance = parseFloat(distanceSplit[0]);
+ const circleFeature = turfCircle(filter.geo_distance[geoFieldName], distance);
+ return circleFeature.geometry;
+ }
+
+ if (filter.geo_shape && filter.geo_shape[geoFieldName] && filter.geo_shape[geoFieldName].shape) {
+ return filter.geo_shape[geoFieldName].shape;
+ }
+}
+
+export function extractFeaturesFromFilters(filters: GeoFilter[]): Feature[] {
+ const features: Feature[] = [];
+ filters
+ .filter((filter) => {
+ return filter.meta.type === SPATIAL_FILTER_TYPE;
+ })
+ .forEach((filter) => {
+ let geometry: Geometry | undefined;
+ if (filter.meta.isMultiIndex) {
+ const geoFieldName = filter?.query?.bool?.should?.[0]?.bool?.must?.[0]?.exists?.field;
+ const spatialClause = filter?.query?.bool?.should?.[0]?.bool?.must?.[1];
+ if (geoFieldName && spatialClause) {
+ geometry = extractGeometryFromFilter(geoFieldName, spatialClause);
+ }
+ } else {
+ const geoFieldName = filter.meta.key;
+ if (geoFieldName) {
+ geometry = extractGeometryFromFilter(geoFieldName, filter);
+ }
+ }
+
+ if (geometry) {
+ features.push({
+ type: 'Feature',
+ geometry,
+ properties: {
+ filter: filter.meta.alias,
+ },
+ });
+ }
+ });
+
+ return features;
+}
diff --git a/x-pack/plugins/maps/common/elasticsearch_util/types.ts b/x-pack/plugins/maps/common/elasticsearch_util/types.ts
new file mode 100644
index 0000000000000..bbb508ce69275
--- /dev/null
+++ b/x-pack/plugins/maps/common/elasticsearch_util/types.ts
@@ -0,0 +1,54 @@
+/*
+ * 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 { Polygon, Position } from 'geojson';
+import { Filter } from '../../../../../src/plugins/data/common';
+import { ES_SPATIAL_RELATIONS } from '../constants';
+
+export type Coordinates = Position | Position[] | Position[][] | Position[][][];
+
+// Elasticsearch stores more then just GeoJSON.
+// 1) geometry.type as lower case string
+// 2) circle and envelope types
+export interface ESGeometry {
+ type: string;
+ coordinates: Coordinates;
+}
+
+export interface ESBBox {
+ top_left: number[];
+ bottom_right: number[];
+}
+
+export interface GeoShapeQueryBody {
+ shape?: Polygon;
+ relation?: ES_SPATIAL_RELATIONS;
+ indexed_shape?: PreIndexedShape;
+}
+
+// Index signature explicitly states that anything stored in an object using a string conforms to the structure
+// problem is that Elasticsearch signature also allows for other string keys to conform to other structures, like 'ignore_unmapped'
+// Use intersection type to exclude certain properties from the index signature
+// https://basarat.gitbook.io/typescript/type-system/index-signatures#excluding-certain-properties-from-the-index-signature
+type GeoShapeQuery = { ignore_unmapped: boolean } & { [geoFieldName: string]: GeoShapeQueryBody };
+
+export type GeoFilter = Filter & {
+ geo_bounding_box?: {
+ [geoFieldName: string]: ESBBox;
+ };
+ geo_distance?: {
+ distance: string;
+ [geoFieldName: string]: Position | { lat: number; lon: number } | string;
+ };
+ geo_shape?: GeoShapeQuery;
+};
+
+export interface PreIndexedShape {
+ index: string;
+ id: string | number;
+ path: string;
+}
diff --git a/x-pack/plugins/maps/public/components/__snapshots__/geometry_filter_form.test.js.snap b/x-pack/plugins/maps/public/components/__snapshots__/geometry_filter_form.test.js.snap
index ccbe4667b78ea..80238bf3a4d17 100644
--- a/x-pack/plugins/maps/public/components/__snapshots__/geometry_filter_form.test.js.snap
+++ b/x-pack/plugins/maps/public/components/__snapshots__/geometry_filter_form.test.js.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should not show "within" relation when filter geometry is not closed 1`] = `
+exports[`render 1`] = `
-
-
-
-
-
-
- Create filter
-
-
-
-`;
-
-exports[`should render error message 1`] = `
-
-
-
-
-
-
-
-
- Simulated error
-
@@ -177,7 +70,7 @@ exports[`should render error message 1`] = `
`;
-exports[`should render relation select when geo field is geo_shape 1`] = `
+exports[`should render error message 1`] = `
-
-
-
- Create filter
-
-
-
-`;
-
-exports[`should render relation select without "within"-relation when geo field is geo_point 1`] = `
-
-
-
-
-
-
-
-
-
-
+
+ Simulated error
+
diff --git a/x-pack/plugins/maps/public/components/distance_filter_form.tsx b/x-pack/plugins/maps/public/components/distance_filter_form.tsx
index 14ae6b11b85c8..b5fdcbc46b932 100644
--- a/x-pack/plugins/maps/public/components/distance_filter_form.tsx
+++ b/x-pack/plugins/maps/public/components/distance_filter_form.tsx
@@ -16,47 +16,28 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { ActionExecutionContext, Action } from 'src/plugins/ui_actions/public';
-import { MultiIndexGeoFieldSelect } from './multi_index_geo_field_select';
-import { GeoFieldWithIndex } from './geo_field_with_index';
import { ActionSelect } from './action_select';
import { ACTION_GLOBAL_APPLY_FILTER } from '../../../../../src/plugins/data/public';
interface Props {
className?: string;
buttonLabel: string;
- geoFields: GeoFieldWithIndex[];
getFilterActions?: () => Promise;
getActionContext?: () => ActionExecutionContext;
- onSubmit: ({
- actionId,
- filterLabel,
- indexPatternId,
- geoFieldName,
- }: {
- actionId: string;
- filterLabel: string;
- indexPatternId: string;
- geoFieldName: string;
- }) => void;
+ onSubmit: ({ actionId, filterLabel }: { actionId: string; filterLabel: string }) => void;
}
interface State {
actionId: string;
- selectedField: GeoFieldWithIndex | undefined;
filterLabel: string;
}
export class DistanceFilterForm extends Component {
state: State = {
actionId: ACTION_GLOBAL_APPLY_FILTER,
- selectedField: this.props.geoFields.length ? this.props.geoFields[0] : undefined,
filterLabel: '',
};
- _onGeoFieldChange = (selectedField: GeoFieldWithIndex | undefined) => {
- this.setState({ selectedField });
- };
-
_onFilterLabelChange = (e: ChangeEvent) => {
this.setState({
filterLabel: e.target.value,
@@ -68,14 +49,9 @@ export class DistanceFilterForm extends Component {
};
_onSubmit = () => {
- if (!this.state.selectedField) {
- return;
- }
this.props.onSubmit({
actionId: this.state.actionId,
filterLabel: this.state.filterLabel,
- indexPatternId: this.state.selectedField.indexPatternId,
- geoFieldName: this.state.selectedField.geoFieldName,
});
};
@@ -95,12 +71,6 @@ export class DistanceFilterForm extends Component {
/>
-
-
{
-
+
{this.props.buttonLabel}
diff --git a/x-pack/plugins/maps/public/components/geo_field_with_index.ts b/x-pack/plugins/maps/public/components/geo_field_with_index.ts
deleted file mode 100644
index 5273bff44f8d7..0000000000000
--- a/x-pack/plugins/maps/public/components/geo_field_with_index.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-/* eslint-disable @typescript-eslint/consistent-type-definitions */
-
-// Maps can contain geo fields from multiple index patterns. GeoFieldWithIndex is used to:
-// 1) Combine the geo field along with associated index pattern state.
-// 2) Package asynchronously looked up state via getIndexPatternService() to avoid
-// PITA of looking up async state in downstream react consumers.
-export type GeoFieldWithIndex = {
- geoFieldName: string;
- geoFieldType: string;
- indexPatternTitle: string;
- indexPatternId: string;
-};
diff --git a/x-pack/plugins/maps/public/components/geometry_filter_form.js b/x-pack/plugins/maps/public/components/geometry_filter_form.js
index 624d3b60fe14b..2e13f63b79883 100644
--- a/x-pack/plugins/maps/public/components/geometry_filter_form.js
+++ b/x-pack/plugins/maps/public/components/geometry_filter_form.js
@@ -18,16 +18,14 @@ import {
EuiFormErrorText,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { ES_GEO_FIELD_TYPE, ES_SPATIAL_RELATIONS } from '../../common/constants';
+import { ES_SPATIAL_RELATIONS } from '../../common/constants';
import { getEsSpatialRelationLabel } from '../../common/i18n_getters';
-import { MultiIndexGeoFieldSelect } from './multi_index_geo_field_select';
import { ActionSelect } from './action_select';
import { ACTION_GLOBAL_APPLY_FILTER } from '../../../../../src/plugins/data/public';
export class GeometryFilterForm extends Component {
static propTypes = {
buttonLabel: PropTypes.string.isRequired,
- geoFields: PropTypes.array.isRequired,
getFilterActions: PropTypes.func,
getActionContext: PropTypes.func,
intitialGeometryLabel: PropTypes.string.isRequired,
@@ -42,15 +40,10 @@ export class GeometryFilterForm extends Component {
state = {
actionId: ACTION_GLOBAL_APPLY_FILTER,
- selectedField: this.props.geoFields.length ? this.props.geoFields[0] : undefined,
geometryLabel: this.props.intitialGeometryLabel,
relation: ES_SPATIAL_RELATIONS.INTERSECTS,
};
- _onGeoFieldChange = (selectedField) => {
- this.setState({ selectedField });
- };
-
_onGeometryLabelChange = (e) => {
this.setState({
geometryLabel: e.target.value,
@@ -71,29 +64,12 @@ export class GeometryFilterForm extends Component {
this.props.onSubmit({
actionId: this.state.actionId,
geometryLabel: this.state.geometryLabel,
- indexPatternId: this.state.selectedField.indexPatternId,
- geoFieldName: this.state.selectedField.geoFieldName,
relation: this.state.relation,
});
};
_renderRelationInput() {
- // relationship only used when filtering geo_shape fields
- if (!this.state.selectedField) {
- return null;
- }
-
- const spatialRelations =
- this.props.isFilterGeometryClosed &&
- this.state.selectedField.geoFieldType !== ES_GEO_FIELD_TYPE.GEO_POINT
- ? Object.values(ES_SPATIAL_RELATIONS)
- : Object.values(ES_SPATIAL_RELATIONS).filter((relation) => {
- // - cannot filter by "within"-relation when filtering geometry is not closed
- // - do not distinguish between intersects/within for filtering for points since they are equivalent
- return relation !== ES_SPATIAL_RELATIONS.WITHIN;
- });
-
- const options = spatialRelations.map((relation) => {
+ const options = Object.values(ES_SPATIAL_RELATIONS).map((relation) => {
return {
value: relation,
text: getEsSpatialRelationLabel(relation),
@@ -137,12 +113,6 @@ export class GeometryFilterForm extends Component {
/>
-
-
{this._renderRelationInput()}
{this.props.buttonLabel}
diff --git a/x-pack/plugins/maps/public/components/geometry_filter_form.test.js b/x-pack/plugins/maps/public/components/geometry_filter_form.test.js
index d981caf944ab9..3ce79782788b0 100644
--- a/x-pack/plugins/maps/public/components/geometry_filter_form.test.js
+++ b/x-pack/plugins/maps/public/components/geometry_filter_form.test.js
@@ -16,76 +16,14 @@ const defaultProps = {
onSubmit: () => {},
};
-test('should render relation select without "within"-relation when geo field is geo_point', async () => {
- const component = shallow(
-
- );
-
- expect(component).toMatchSnapshot();
-});
-
-test('should render relation select when geo field is geo_shape', async () => {
- const component = shallow(
-
- );
-
- expect(component).toMatchSnapshot();
-});
-
-test('should not show "within" relation when filter geometry is not closed', async () => {
- const component = shallow(
-
- );
+test('render', async () => {
+ const component = shallow();
expect(component).toMatchSnapshot();
});
test('should render error message', async () => {
- const component = shallow(
-
- );
+ const component = shallow();
expect(component).toMatchSnapshot();
});
diff --git a/x-pack/plugins/maps/public/components/multi_index_geo_field_select.tsx b/x-pack/plugins/maps/public/components/multi_index_geo_field_select.tsx
deleted file mode 100644
index 564b84ae84300..0000000000000
--- a/x-pack/plugins/maps/public/components/multi_index_geo_field_select.tsx
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { EuiFormRow, EuiSuperSelect, EuiTextColor, EuiText } from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
-import { GeoFieldWithIndex } from './geo_field_with_index';
-
-const OPTION_ID_DELIMITER = '/';
-
-function createOptionId(geoField: GeoFieldWithIndex): string {
- // Namespace field with indexPatterId to avoid collisions between field names
- return `${geoField.indexPatternId}${OPTION_ID_DELIMITER}${geoField.geoFieldName}`;
-}
-
-function splitOptionId(optionId: string) {
- const split = optionId.split(OPTION_ID_DELIMITER);
- return {
- indexPatternId: split[0],
- geoFieldName: split[1],
- };
-}
-
-interface Props {
- fields: GeoFieldWithIndex[];
- onChange: (newSelectedField: GeoFieldWithIndex | undefined) => void;
- selectedField: GeoFieldWithIndex | undefined;
-}
-
-export function MultiIndexGeoFieldSelect({ fields, onChange, selectedField }: Props) {
- function onFieldSelect(selectedOptionId: string) {
- const { indexPatternId, geoFieldName } = splitOptionId(selectedOptionId);
-
- const newSelectedField = fields.find((field) => {
- return field.indexPatternId === indexPatternId && field.geoFieldName === geoFieldName;
- });
- onChange(newSelectedField);
- }
-
- const options = fields.map((geoField: GeoFieldWithIndex) => {
- return {
- inputDisplay: (
-
-
- {geoField.indexPatternTitle}
-
-
- {geoField.geoFieldName}
-
- ),
- value: createOptionId(geoField),
- };
- });
-
- return (
-
-
-
- );
-}
diff --git a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx
index 02374932a4c70..26746d9ad2416 100644
--- a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx
+++ b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx
@@ -20,15 +20,12 @@ import { ToolbarOverlay } from '../toolbar_overlay';
import { EditLayerPanel } from '../edit_layer_panel';
import { AddLayerPanel } from '../add_layer_panel';
import { ExitFullScreenButton } from '../../../../../../src/plugins/kibana_react/public';
-import { getIndexPatternsFromIds } from '../../index_pattern_util';
-import { ES_GEO_FIELD_TYPE, RawValue } from '../../../common/constants';
-import { indexPatterns as indexPatternsUtils } from '../../../../../../src/plugins/data/public';
+import { RawValue } from '../../../common/constants';
import { FLYOUT_STATE } from '../../reducers/ui';
import { MapSettings } from '../../reducers/map';
import { MapSettingsPanel } from '../map_settings_panel';
import { registerLayerWizards } from '../../classes/layers/load_layer_wizards';
import { RenderToolTipContent } from '../../classes/tooltips/tooltip_property';
-import { GeoFieldWithIndex } from '../../components/geo_field_with_index';
import { MapRefreshConfig } from '../../../common/descriptor_types';
import { ILayer } from '../../classes/layers/layer';
@@ -58,7 +55,6 @@ export interface Props {
interface State {
isInitialLoadRenderTimeoutComplete: boolean;
domId: string;
- geoFields: GeoFieldWithIndex[];
showFitToBoundsButton: boolean;
showTimesliderButton: boolean;
}
@@ -66,7 +62,6 @@ interface State {
export class MapContainer extends Component {
private _isMounted: boolean = false;
private _isInitalLoadRenderTimerStarted: boolean = false;
- private _prevIndexPatternIds: string[] = [];
private _refreshTimerId: number | null = null;
private _prevIsPaused: boolean | null = null;
private _prevInterval: number | null = null;
@@ -74,7 +69,6 @@ export class MapContainer extends Component {
state: State = {
isInitialLoadRenderTimeoutComplete: false,
domId: uuid(),
- geoFields: [],
showFitToBoundsButton: false,
showTimesliderButton: false,
};
@@ -95,10 +89,6 @@ export class MapContainer extends Component {
this._isInitalLoadRenderTimerStarted = true;
this._startInitialLoadRenderTimer();
}
-
- if (!!this.props.addFilters) {
- this._loadGeoFields(this.props.indexPatternIds);
- }
}
componentWillUnmount() {
@@ -151,40 +141,6 @@ export class MapContainer extends Component {
}
}
- async _loadGeoFields(nextIndexPatternIds: string[]) {
- if (_.isEqual(nextIndexPatternIds, this._prevIndexPatternIds)) {
- // all ready loaded index pattern ids
- return;
- }
-
- this._prevIndexPatternIds = nextIndexPatternIds;
-
- const geoFields: GeoFieldWithIndex[] = [];
- const indexPatterns = await getIndexPatternsFromIds(nextIndexPatternIds);
- indexPatterns.forEach((indexPattern) => {
- indexPattern.fields.forEach((field) => {
- if (
- indexPattern.id &&
- !indexPatternsUtils.isNestedField(field) &&
- (field.type === ES_GEO_FIELD_TYPE.GEO_POINT || field.type === ES_GEO_FIELD_TYPE.GEO_SHAPE)
- ) {
- geoFields.push({
- geoFieldName: field.name,
- geoFieldType: field.type,
- indexPatternTitle: indexPattern.title,
- indexPatternId: indexPattern.id,
- });
- }
- });
- });
-
- if (!this._isMounted) {
- return;
- }
-
- this.setState({ geoFields });
- }
-
_setRefreshTimer = () => {
const { isPaused, interval } = this.props.refreshConfig;
@@ -289,13 +245,11 @@ export class MapContainer extends Component {
getFilterActions={getFilterActions}
getActionContext={getActionContext}
onSingleValueTrigger={onSingleValueTrigger}
- geoFields={this.state.geoFields}
renderTooltipContent={renderTooltipContent}
/>
{!this.props.settings.hideToolbarOverlay && (
{
_onDraw = async (e: { features: Feature[] }) => {
- if (
- !e.features.length ||
- !this.props.drawState ||
- !this.props.drawState.geoFieldName ||
- !this.props.drawState.indexPatternId
- ) {
+ if (!e.features.length || !this.props.drawState || !this.props.geoFieldNames.length) {
return;
}
@@ -61,8 +57,7 @@ export class DrawFilterControl extends Component {
filter = createDistanceFilterWithMeta({
alias: this.props.drawState.filterLabel ? this.props.drawState.filterLabel : '',
distanceKm,
- geoFieldName: this.props.drawState.geoFieldName,
- indexPatternId: this.props.drawState.indexPatternId,
+ geoFieldNames: this.props.geoFieldNames,
point: [
_.round(circle.properties.center[0], precision),
_.round(circle.properties.center[1], precision),
@@ -78,8 +73,7 @@ export class DrawFilterControl extends Component {
this.props.drawState.drawType === DRAW_TYPE.BOUNDS
? getBoundingBoxGeometry(geometry)
: geometry,
- indexPatternId: this.props.drawState.indexPatternId,
- geoFieldName: this.props.drawState.geoFieldName,
+ geoFieldNames: this.props.geoFieldNames,
geometryLabel: this.props.drawState.geometryLabel ? this.props.drawState.geometryLabel : '',
relation: this.props.drawState.relation
? this.props.drawState.relation
diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_filter_control/index.ts b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_filter_control/index.ts
index 17f4d919fb7e0..58ad530093557 100644
--- a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_filter_control/index.ts
+++ b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_filter_control/index.ts
@@ -10,13 +10,18 @@ import { ThunkDispatch } from 'redux-thunk';
import { connect } from 'react-redux';
import { DrawFilterControl } from './draw_filter_control';
import { updateDrawState } from '../../../../actions';
-import { getDrawState, isDrawingFilter } from '../../../../selectors/map_selectors';
+import {
+ getDrawState,
+ isDrawingFilter,
+ getGeoFieldNames,
+} from '../../../../selectors/map_selectors';
import { MapStoreState } from '../../../../reducers/store';
function mapStateToProps(state: MapStoreState) {
return {
isDrawingFilter: isDrawingFilter(state),
drawState: getDrawState(state),
+ geoFieldNames: getGeoFieldNames(state),
};
}
diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx
index 877de10e11383..9d8bce083c292 100644
--- a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx
+++ b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx
@@ -43,7 +43,6 @@ import {
// @ts-expect-error
} from './utils';
import { ResizeChecker } from '../../../../../../src/plugins/kibana_utils/public';
-import { GeoFieldWithIndex } from '../../components/geo_field_with_index';
import { RenderToolTipContent } from '../../classes/tooltips/tooltip_property';
import { MapExtentState } from '../../actions';
import { TileStatusTracker } from './tile_status_tracker';
@@ -68,7 +67,6 @@ export interface Props {
getFilterActions?: () => Promise;
getActionContext?: () => ActionExecutionContext;
onSingleValueTrigger?: (actionId: string, key: string, value: RawValue) => void;
- geoFields: GeoFieldWithIndex[];
renderTooltipContent?: RenderToolTipContent;
setAreTilesLoaded: (layerId: string, areTilesLoaded: boolean) => void;
}
@@ -432,7 +430,6 @@ export class MBMap extends Component {
getFilterActions={this.props.getFilterActions}
getActionContext={this.props.getActionContext}
onSingleValueTrigger={this.props.onSingleValueTrigger}
- geoFields={this.props.geoFields}
renderTooltipContent={this.props.renderTooltipContent}
/>
) : null;
diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/features_tooltip/feature_geometry_filter_form.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/features_tooltip/feature_geometry_filter_form.tsx
index 04e564943fa39..5f26715c86004 100644
--- a/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/features_tooltip/feature_geometry_filter_form.tsx
+++ b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/features_tooltip/feature_geometry_filter_form.tsx
@@ -21,7 +21,6 @@ import {
import { ES_SPATIAL_RELATIONS, GEO_JSON_TYPE } from '../../../../../common/constants';
// @ts-expect-error
import { GeometryFilterForm } from '../../../../components/geometry_filter_form';
-import { GeoFieldWithIndex } from '../../../../components/geo_field_with_index';
// over estimated and imprecise value to ensure filter has additional room for any meta keys added when filter is mapped.
const META_OVERHEAD = 100;
@@ -29,11 +28,11 @@ const META_OVERHEAD = 100;
interface Props {
onClose: () => void;
geometry: Geometry;
- geoFields: GeoFieldWithIndex[];
addFilters: (filters: Filter[], actionId: string) => Promise;
getFilterActions?: () => Promise;
getActionContext?: () => ActionExecutionContext;
loadPreIndexedShape: () => Promise;
+ geoFieldNames: string[];
}
interface State {
@@ -77,13 +76,9 @@ export class FeatureGeometryFilterForm extends Component {
_createFilter = async ({
geometryLabel,
- indexPatternId,
- geoFieldName,
relation,
}: {
geometryLabel: string;
- indexPatternId: string;
- geoFieldName: string;
relation: ES_SPATIAL_RELATIONS;
}) => {
this.setState({ errorMsg: undefined });
@@ -97,8 +92,7 @@ export class FeatureGeometryFilterForm extends Component {
preIndexedShape,
geometry: this.props.geometry as Polygon,
geometryLabel,
- indexPatternId,
- geoFieldName,
+ geoFieldNames: this.props.geoFieldNames,
relation,
});
@@ -130,7 +124,6 @@ export class FeatureGeometryFilterForm extends Component {
defaultMessage: 'Create filter',
}
)}
- geoFields={this.props.geoFields}
getFilterActions={this.props.getFilterActions}
getActionContext={this.props.getActionContext}
intitialGeometryLabel={this.props.geometry.type.toLowerCase()}
diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/index.ts b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/index.ts
index 28510815d1a2e..2861c80306526 100644
--- a/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/index.ts
+++ b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/index.ts
@@ -20,6 +20,7 @@ import {
getLayerList,
getOpenTooltips,
getHasLockedTooltips,
+ getGeoFieldNames,
isDrawingFilter,
} from '../../../selectors/map_selectors';
import { MapStoreState } from '../../../reducers/store';
@@ -30,6 +31,7 @@ function mapStateToProps(state: MapStoreState) {
hasLockedTooltips: getHasLockedTooltips(state),
isDrawingFilter: isDrawingFilter(state),
openTooltips: getOpenTooltips(state),
+ geoFieldNames: getGeoFieldNames(state),
};
}
diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.test.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.test.tsx
index ac6e3cfcccf4e..a11000a48866f 100644
--- a/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.test.tsx
+++ b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.test.tsx
@@ -77,7 +77,7 @@ const defaultProps = {
layerList: [mockLayer],
isDrawingFilter: false,
addFilters: async () => {},
- geoFields: [],
+ geoFieldNames: [],
openTooltips: [],
hasLockedTooltips: false,
};
diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.tsx
index 09dd9ee4f51d9..e9af9dcc89e07 100644
--- a/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.tsx
+++ b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.tsx
@@ -20,7 +20,6 @@ import { Geometry } from 'geojson';
import { Filter } from 'src/plugins/data/public';
import { ActionExecutionContext, Action } from 'src/plugins/ui_actions/public';
import {
- ES_GEO_FIELD_TYPE,
FEATURE_ID_PROPERTY_NAME,
GEO_JSON_TYPE,
LON_INDEX,
@@ -37,7 +36,6 @@ import { FeatureGeometryFilterForm } from './features_tooltip';
import { EXCLUDE_TOO_MANY_FEATURES_BOX } from '../../../classes/util/mb_filter_expressions';
import { ILayer } from '../../../classes/layers/layer';
import { IVectorLayer } from '../../../classes/layers/vector_layer';
-import { GeoFieldWithIndex } from '../../../components/geo_field_with_index';
import { RenderToolTipContent } from '../../../classes/tooltips/tooltip_property';
function justifyAnchorLocation(
@@ -70,7 +68,7 @@ export interface Props {
closeOnHoverTooltip: () => void;
getActionContext?: () => ActionExecutionContext;
getFilterActions?: () => Promise;
- geoFields: GeoFieldWithIndex[];
+ geoFieldNames: string[];
hasLockedTooltips: boolean;
isDrawingFilter: boolean;
layerList: ILayer[];
@@ -163,8 +161,10 @@ export class TooltipControl extends Component {
const actions = [];
const geometry = this._getFeatureGeometry({ layerId, featureId });
- const geoFieldsForFeature = this._filterGeoFieldsByFeatureGeometry(geometry);
- if (geometry && geoFieldsForFeature.length && this.props.addFilters) {
+ const isPolygon =
+ geometry &&
+ (geometry.type === GEO_JSON_TYPE.POLYGON || geometry.type === GEO_JSON_TYPE.MULTI_POLYGON);
+ if (isPolygon && this.props.geoFieldNames.length && this.props.addFilters) {
actions.push({
label: i18n.translate('xpack.maps.tooltip.action.filterByGeometryLabel', {
defaultMessage: 'Filter by geometry',
@@ -175,8 +175,8 @@ export class TooltipControl extends Component {
onClose={() => {
this.props.closeOnClickTooltip(tooltipId);
}}
- geometry={geometry}
- geoFields={geoFieldsForFeature}
+ geometry={geometry!}
+ geoFieldNames={this.props.geoFieldNames}
addFilters={this.props.addFilters}
getFilterActions={this.props.getFilterActions}
getActionContext={this.props.getActionContext}
@@ -191,29 +191,6 @@ export class TooltipControl extends Component {
return actions;
}
- _filterGeoFieldsByFeatureGeometry(geometry: Geometry | null) {
- if (!geometry) {
- return [];
- }
-
- // line geometry can only create filters for geo_shape fields.
- if (
- geometry.type === GEO_JSON_TYPE.LINE_STRING ||
- geometry.type === GEO_JSON_TYPE.MULTI_LINE_STRING
- ) {
- return this.props.geoFields.filter(({ geoFieldType }) => {
- return geoFieldType === ES_GEO_FIELD_TYPE.GEO_SHAPE;
- });
- }
-
- // TODO support geo distance filters for points
- if (geometry.type === GEO_JSON_TYPE.POINT || geometry.type === GEO_JSON_TYPE.MULTI_POINT) {
- return [];
- }
-
- return this.props.geoFields;
- }
-
_getTooltipFeatures(
mbFeatures: MapboxGeoJSONFeature[],
isLocked: boolean,
diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/__snapshots__/toolbar_overlay.test.tsx.snap b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/__snapshots__/toolbar_overlay.test.tsx.snap
index 168a070b07744..245cf6e5b2a48 100644
--- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/__snapshots__/toolbar_overlay.test.tsx.snap
+++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/__snapshots__/toolbar_overlay.test.tsx.snap
@@ -29,18 +29,7 @@ exports[`Should show all controls 1`] = `
-
+
diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/index.ts b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/index.ts
index d1008edfd572d..7d176c13da049 100644
--- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/index.ts
+++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/index.ts
@@ -5,4 +5,16 @@
* 2.0.
*/
-export { ToolbarOverlay } from './toolbar_overlay';
+import { connect } from 'react-redux';
+import { MapStoreState } from '../../reducers/store';
+import { getGeoFieldNames } from '../../selectors/map_selectors';
+import { ToolbarOverlay } from './toolbar_overlay';
+
+function mapStateToProps(state: MapStoreState) {
+ return {
+ showToolsControl: getGeoFieldNames(state).length !== 0,
+ };
+}
+
+const connected = connect(mapStateToProps)(ToolbarOverlay);
+export { connected as ToolbarOverlay };
diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/toolbar_overlay.test.tsx b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/toolbar_overlay.test.tsx
index 28b5ab9c78f40..9efbd82fe390a 100644
--- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/toolbar_overlay.test.tsx
+++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/toolbar_overlay.test.tsx
@@ -21,22 +21,20 @@ import { ToolbarOverlay } from './toolbar_overlay';
test('Should only show set view control', async () => {
const component = shallow(
-
+
);
expect(component).toMatchSnapshot();
});
test('Should show all controls', async () => {
- const geoFieldWithIndex = {
- geoFieldName: 'myGeoFieldName',
- geoFieldType: 'geo_point',
- indexPatternTitle: 'myIndex',
- indexPatternId: '1',
- };
const component = shallow(
{}}
- geoFields={[geoFieldWithIndex]}
+ showToolsControl={true}
showFitToBoundsButton={true}
showTimesliderButton={true}
/>
diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/toolbar_overlay.tsx b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/toolbar_overlay.tsx
index 41c6c1f7c4a7c..2b35793218969 100644
--- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/toolbar_overlay.tsx
+++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/toolbar_overlay.tsx
@@ -13,11 +13,10 @@ import { SetViewControl } from './set_view_control';
import { ToolsControl } from './tools_control';
import { FitToData } from './fit_to_data';
import { TimesliderToggleButton } from './timeslider_toggle_button';
-import { GeoFieldWithIndex } from '../../components/geo_field_with_index';
export interface Props {
addFilters?: ((filters: Filter[], actionId: string) => Promise) | null;
- geoFields: GeoFieldWithIndex[];
+ showToolsControl: boolean;
getFilterActions?: () => Promise;
getActionContext?: () => ActionExecutionContext;
showFitToBoundsButton: boolean;
@@ -26,10 +25,9 @@ export interface Props {
export function ToolbarOverlay(props: Props) {
const toolsButton =
- props.addFilters && props.geoFields.length ? (
+ props.addFilters && props.showToolsControl ? (
diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/__snapshots__/tools_control.test.tsx.snap b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/__snapshots__/tools_control.test.tsx.snap
index b6d217d690764..aa5c6aa42c77d 100644
--- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/__snapshots__/tools_control.test.tsx.snap
+++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/__snapshots__/tools_control.test.tsx.snap
@@ -56,16 +56,6 @@ exports[`Should render cancel button when drawing 1`] = `
"content": ,
"id": 3,
@@ -187,16 +157,6 @@ exports[`renders 1`] = `
"content": ,
"id": 3,
diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.tsx b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.tsx
index 6779fe945137e..9ea20914c6422 100644
--- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.tsx
+++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.tsx
@@ -22,7 +22,6 @@ import { DRAW_TYPE, ES_GEO_FIELD_TYPE, ES_SPATIAL_RELATIONS } from '../../../../
// @ts-expect-error
import { GeometryFilterForm } from '../../../components/geometry_filter_form';
import { DistanceFilterForm } from '../../../components/distance_filter_form';
-import { GeoFieldWithIndex } from '../../../components/geo_field_with_index';
import { DrawState } from '../../../../common/descriptor_types';
const DRAW_SHAPE_LABEL = i18n.translate('xpack.maps.toolbarOverlay.drawShapeLabel', {
@@ -54,7 +53,6 @@ const DRAW_DISTANCE_LABEL_SHORT = i18n.translate(
export interface Props {
cancelDraw: () => void;
- geoFields: GeoFieldWithIndex[];
initiateDraw: (drawState: DrawState) => void;
isDrawingFilter: boolean;
getFilterActions?: () => Promise;
@@ -98,9 +96,6 @@ export class ToolsControl extends Component {
_initiateBoundsDraw = (options: {
actionId: string;
geometryLabel: string;
- indexPatternId: string;
- geoFieldName: string;
- geoFieldType: ES_GEO_FIELD_TYPE;
relation: ES_SPATIAL_RELATIONS;
}) => {
this.props.initiateDraw({
@@ -110,12 +105,7 @@ export class ToolsControl extends Component {
this._closePopover();
};
- _initiateDistanceDraw = (options: {
- actionId: string;
- filterLabel: string;
- indexPatternId: string;
- geoFieldName: string;
- }) => {
+ _initiateDistanceDraw = (options: { actionId: string; filterLabel: string }) => {
this.props.initiateDraw({
drawType: DRAW_TYPE.DISTANCE,
...options,
@@ -154,7 +144,6 @@ export class ToolsControl extends Component {
{
{
= {
logs: LOGS_FREQUENCY_LABEL,
mem: MEMORY_USAGE_LABEL,
nwk: NETWORK_ACTIVITY_LABEL,
+ cwv: CORE_WEB_VITALS_LABEL,
};
export const ReportToDataTypeMap: Record = {
@@ -105,4 +107,9 @@ export const ReportToDataTypeMap: Record = {
mem: 'infra_metrics',
logs: 'infra_logs',
cpu: 'infra_metrics',
+ cwv: 'ux',
};
+
+export const USE_BREAK_DOWN_COLUMN = 'USE_BREAK_DOWN_COLUMN';
+export const FILTER_RECORDS = 'FILTER_RECORDS';
+export const OPERATION_COLUMN = 'operation';
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/elasticsearch_fieldnames.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/elasticsearch_fieldnames.ts
index 3faf54fff3140..5ecc5b758de84 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/elasticsearch_fieldnames.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/elasticsearch_fieldnames.ts
@@ -119,6 +119,7 @@ export const TRANSACTION_URL = 'url.full';
export const CLIENT_GEO = 'client.geo';
export const USER_AGENT_DEVICE = 'user_agent.device.name';
export const USER_AGENT_OS = 'user_agent.os.name';
+export const USER_AGENT_OS_VERSION = 'user_agent.os.version';
export const TRANSACTION_TIME_TO_FIRST_BYTE = 'transaction.marks.agent.timeToFirstByte';
export const TRANSACTION_DOM_INTERACTIVE = 'transaction.marks.agent.domInteractive';
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/labels.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/labels.ts
index ba820a25f868a..92150a76319f8 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/labels.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/labels.ts
@@ -200,6 +200,14 @@ export const NETWORK_ACTIVITY_LABEL = i18n.translate(
defaultMessage: 'Network activity',
}
);
+
+export const CORE_WEB_VITALS_LABEL = i18n.translate(
+ 'xpack.observability.expView.fieldLabels.coreWebVitals',
+ {
+ defaultMessage: 'Core web vitals',
+ }
+);
+
export const MEMORY_USAGE_LABEL = i18n.translate(
'xpack.observability.expView.fieldLabels.memoryUsage',
{
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts
index c6089b2316784..797ee0c2e0977 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts
@@ -17,6 +17,7 @@ import { getMemoryUsageLensConfig } from './metrics/memory_usage_config';
import { getNetworkActivityLensConfig } from './metrics/network_activity_config';
import { getLogsFrequencyLensConfig } from './logs/logs_frequency_config';
import { IIndexPattern } from '../../../../../../../../src/plugins/data/common/index_patterns';
+import { getCoreWebVitalsConfig } from './rum/core_web_vitals_config';
interface Props {
reportType: keyof typeof ReportViewTypes;
@@ -30,6 +31,8 @@ export const getDefaultConfigs = ({ reportType, seriesId, indexPattern }: Props)
return getPerformanceDistLensConfig({ seriesId, indexPattern });
case 'kpi-trends':
return getKPITrendsLensConfig({ seriesId, indexPattern });
+ case 'core-web-vitals':
+ return getCoreWebVitalsConfig({ seriesId, indexPattern });
case 'uptime-duration':
return getMonitorDurationConfig({ seriesId, indexPattern });
case 'uptime-pings':
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts
index a5fdd4971a86f..6976b55921b09 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts
@@ -342,7 +342,7 @@ describe('Lens Attribute', () => {
orderBy: { columnId: 'y-axis-column', type: 'column' },
orderDirection: 'desc',
otherBucket: true,
- size: 3,
+ size: 10,
},
scale: 'ordinal',
sourceField: 'user_agent.name',
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts
index 3e22c4da6115a..89fc9ca5fcc58 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts
@@ -24,14 +24,15 @@ import {
OperationMetadata,
FieldBasedIndexPatternColumn,
SumIndexPatternColumn,
+ TermsIndexPatternColumn,
} from '../../../../../../lens/public';
import {
buildPhraseFilter,
buildPhrasesFilter,
IndexPattern,
} from '../../../../../../../../src/plugins/data/common';
-import { FieldLabels } from './constants';
-import { DataSeries, UrlFilter, URLReportDefinition } from '../types';
+import { FieldLabels, FILTER_RECORDS, USE_BREAK_DOWN_COLUMN } from './constants';
+import { ColumnFilter, DataSeries, UrlFilter, URLReportDefinition } from '../types';
function getLayerReferenceName(layerId: string) {
return `indexpattern-datasource-layer-${layerId}`;
@@ -53,6 +54,7 @@ export const parseCustomFieldName = (
) => {
let fieldName = sourceField;
let columnType;
+ let columnFilters;
const rdf = reportViewConfig.reportDefinitions ?? [];
@@ -61,17 +63,21 @@ export const parseCustomFieldName = (
if (customField) {
if (selectedDefinitions[fieldName]) {
fieldName = selectedDefinitions[fieldName][0];
- if (customField?.options)
- columnType = customField?.options?.find(({ field }) => field === fieldName)?.columnType;
- } else if (customField.defaultValue) {
- fieldName = customField.defaultValue;
- } else if (customField.options?.[0].field) {
- fieldName = customField.options?.[0].field;
+ if (customField?.options) {
+ const currField = customField?.options?.find(
+ ({ field, id }) => field === fieldName || id === fieldName
+ );
+ columnType = currField?.columnType;
+ columnFilters = currField?.columnFilters;
+ }
+ } else if (customField.options?.[0].field || customField.options?.[0].id) {
+ fieldName = customField.options?.[0].field || customField.options?.[0].id;
columnType = customField.options?.[0].columnType;
+ columnFilters = customField.options?.[0].columnFilters;
}
}
- return { fieldName, columnType };
+ return { fieldName, columnType, columnFilters };
};
export class LensAttributes {
@@ -82,6 +88,7 @@ export class LensAttributes {
seriesType: SeriesType;
reportViewConfig: DataSeries;
reportDefinitions: URLReportDefinition;
+ breakdownSource?: string;
constructor(
indexPattern: IndexPattern,
@@ -89,12 +96,14 @@ export class LensAttributes {
seriesType?: SeriesType,
filters?: UrlFilter[],
operationType?: OperationType,
- reportDefinitions?: URLReportDefinition
+ reportDefinitions?: URLReportDefinition,
+ breakdownSource?: string
) {
this.indexPattern = indexPattern;
this.layers = {};
this.filters = filters ?? [];
this.reportDefinitions = reportDefinitions ?? {};
+ this.breakdownSource = breakdownSource;
if (operationType) {
reportViewConfig.yAxisColumns.forEach((yAxisColumn) => {
@@ -109,10 +118,10 @@ export class LensAttributes {
this.visualization = this.getXyState();
}
- addBreakdown(sourceField: string) {
+ getBreakdownColumn(sourceField: string): TermsIndexPatternColumn {
const fieldMeta = this.indexPattern.getFieldByName(sourceField);
- this.layers.layer1.columns['break-down-column'] = {
+ return {
sourceField,
label: `Top values of ${FieldLabels[sourceField]}`,
dataType: fieldMeta?.type as DataType,
@@ -120,13 +129,22 @@ export class LensAttributes {
scale: 'ordinal',
isBucketed: true,
params: {
- size: 3,
+ size: 10,
orderBy: { type: 'column', columnId: 'y-axis-column' },
orderDirection: 'desc',
otherBucket: true,
missingBucket: false,
},
};
+ }
+
+ addBreakdown(sourceField: string) {
+ const { xAxisColumn } = this.reportViewConfig;
+ if (xAxisColumn?.sourceField === USE_BREAK_DOWN_COLUMN) {
+ // do nothing since this will be used a x axis source
+ return;
+ }
+ this.layers.layer1.columns['break-down-column'] = this.getBreakdownColumn(sourceField);
this.layers.layer1.columnOrder = [
'x-axis-column',
@@ -229,15 +247,27 @@ export class LensAttributes {
getXAxis() {
const { xAxisColumn } = this.reportViewConfig;
+ if (xAxisColumn?.sourceField === USE_BREAK_DOWN_COLUMN) {
+ return this.getBreakdownColumn(this.breakdownSource || this.reportViewConfig.breakdowns[0]);
+ }
+
return this.getColumnBasedOnType(xAxisColumn.sourceField!, undefined, xAxisColumn.label);
}
- getColumnBasedOnType(sourceField: string, operationType?: OperationType, label?: string) {
- const { fieldMeta, columnType, fieldName } = this.getFieldMeta(sourceField);
+ getColumnBasedOnType(
+ sourceField: string,
+ operationType?: OperationType,
+ label?: string,
+ colIndex?: number
+ ) {
+ const { fieldMeta, columnType, fieldName, columnFilters } = this.getFieldMeta(sourceField);
const { type: fieldType } = fieldMeta ?? {};
- if (fieldName === 'Records') {
- return this.getRecordsColumn();
+ if (fieldName === 'Records' || columnType === FILTER_RECORDS) {
+ return this.getRecordsColumn(
+ label,
+ colIndex !== undefined ? columnFilters?.[colIndex] : undefined
+ );
}
if (fieldType === 'date') {
@@ -256,11 +286,11 @@ export class LensAttributes {
}
getFieldMeta(sourceField: string) {
- const { fieldName, columnType } = this.getCustomFieldName(sourceField);
+ const { fieldName, columnType, columnFilters } = this.getCustomFieldName(sourceField);
const fieldMeta = this.indexPattern.getFieldByName(fieldName);
- return { fieldMeta, fieldName, columnType };
+ return { fieldMeta, fieldName, columnType, columnFilters };
}
getMainYAxis() {
@@ -270,7 +300,7 @@ export class LensAttributes {
return this.getRecordsColumn(label);
}
- return this.getColumnBasedOnType(sourceField!, operationType, label);
+ return this.getColumnBasedOnType(sourceField!, operationType, label, 0);
}
getChildYAxises() {
@@ -286,13 +316,14 @@ export class LensAttributes {
lensColumns[`y-axis-column-${i}`] = this.getColumnBasedOnType(
sourceField!,
operationType,
- label
+ label,
+ i
);
}
return lensColumns;
}
- getRecordsColumn(label?: string): CountIndexPatternColumn {
+ getRecordsColumn(label?: string, columnFilter?: ColumnFilter): CountIndexPatternColumn {
return {
dataType: 'number',
isBucketed: false,
@@ -300,6 +331,7 @@ export class LensAttributes {
operationType: 'count',
scale: 'ratio',
sourceField: 'Records',
+ filter: columnFilter,
} as CountIndexPatternColumn;
}
@@ -331,7 +363,9 @@ export class LensAttributes {
layerId: 'layer1',
seriesType: this.seriesType ?? 'line',
palette: this.reportViewConfig.palette,
- yConfig: [{ forAccessor: 'y-axis-column', color: 'green' }],
+ yConfig: this.reportViewConfig.yConfig || [
+ { forAccessor: 'y-axis-column', color: 'green' },
+ ],
xAccessor: 'x-axis-column',
},
],
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/core_web_vitals_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/core_web_vitals_config.ts
new file mode 100644
index 0000000000000..de9ea12be20cf
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/core_web_vitals_config.ts
@@ -0,0 +1,164 @@
+/*
+ * 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 { euiPaletteForStatus } from '@elastic/eui';
+import { ConfigProps, DataSeries } from '../../types';
+import { FieldLabels, FILTER_RECORDS, USE_BREAK_DOWN_COLUMN } from '../constants';
+import { buildPhraseFilter } from '../utils';
+import {
+ CLIENT_GEO_COUNTRY_NAME,
+ CLS_FIELD,
+ FID_FIELD,
+ LCP_FIELD,
+ PROCESSOR_EVENT,
+ SERVICE_NAME,
+ TRANSACTION_TYPE,
+ USER_AGENT_DEVICE,
+ USER_AGENT_NAME,
+ USER_AGENT_OS,
+ USER_AGENT_VERSION,
+ TRANSACTION_URL,
+ USER_AGENT_OS_VERSION,
+ URL_FULL,
+ SERVICE_ENVIRONMENT,
+} from '../constants/elasticsearch_fieldnames';
+
+export function getCoreWebVitalsConfig({ seriesId, indexPattern }: ConfigProps): DataSeries {
+ const statusPallete = euiPaletteForStatus(3);
+
+ return {
+ id: seriesId,
+ defaultSeriesType: 'bar_horizontal_percentage_stacked',
+ reportType: 'kpi-trends',
+ seriesTypes: ['bar_horizontal_percentage_stacked'],
+ xAxisColumn: {
+ sourceField: USE_BREAK_DOWN_COLUMN,
+ },
+ yAxisColumns: [
+ {
+ sourceField: 'core.web.vitals',
+ label: 'Good',
+ },
+ {
+ sourceField: 'core.web.vitals',
+ label: 'Average',
+ },
+ {
+ sourceField: 'core.web.vitals',
+ label: 'Poor',
+ },
+ ],
+ hasOperationType: false,
+ defaultFilters: [
+ {
+ field: TRANSACTION_URL,
+ isNegated: false,
+ },
+ SERVICE_NAME,
+ {
+ field: USER_AGENT_OS,
+ nested: USER_AGENT_OS_VERSION,
+ },
+ CLIENT_GEO_COUNTRY_NAME,
+ USER_AGENT_DEVICE,
+ {
+ field: USER_AGENT_NAME,
+ nested: USER_AGENT_VERSION,
+ },
+ ],
+ breakdowns: [
+ SERVICE_NAME,
+ USER_AGENT_NAME,
+ USER_AGENT_OS,
+ CLIENT_GEO_COUNTRY_NAME,
+ USER_AGENT_DEVICE,
+ URL_FULL,
+ ],
+ filters: [
+ ...buildPhraseFilter(TRANSACTION_TYPE, 'page-load', indexPattern),
+ ...buildPhraseFilter(PROCESSOR_EVENT, 'transaction', indexPattern),
+ ],
+ labels: { ...FieldLabels, [SERVICE_NAME]: 'Web Application' },
+ reportDefinitions: [
+ {
+ field: SERVICE_NAME,
+ required: true,
+ },
+ {
+ field: SERVICE_ENVIRONMENT,
+ },
+ {
+ field: 'core.web.vitals',
+ custom: true,
+ options: [
+ {
+ id: LCP_FIELD,
+ label: 'Largest contentful paint',
+ columnType: FILTER_RECORDS,
+ columnFilters: [
+ {
+ language: 'kuery',
+ query: `${LCP_FIELD} < 2500`,
+ },
+ {
+ language: 'kuery',
+ query: `${LCP_FIELD} > 2500 and ${LCP_FIELD} < 4000`,
+ },
+ {
+ language: 'kuery',
+ query: `${LCP_FIELD} > 4000`,
+ },
+ ],
+ },
+ {
+ label: 'First input delay',
+ id: FID_FIELD,
+ columnType: FILTER_RECORDS,
+ columnFilters: [
+ {
+ language: 'kuery',
+ query: `${FID_FIELD} < 100`,
+ },
+ {
+ language: 'kuery',
+ query: `${FID_FIELD} > 100 and ${FID_FIELD} < 300`,
+ },
+ {
+ language: 'kuery',
+ query: `${FID_FIELD} > 300`,
+ },
+ ],
+ },
+ {
+ label: 'Cumulative layout shift',
+ id: CLS_FIELD,
+ columnType: FILTER_RECORDS,
+ columnFilters: [
+ {
+ language: 'kuery',
+ query: `${CLS_FIELD} < 0.1`,
+ },
+ {
+ language: 'kuery',
+ query: `${CLS_FIELD} > 0.1 and ${CLS_FIELD} < 0.25`,
+ },
+ {
+ language: 'kuery',
+ query: `${CLS_FIELD} > 0.25`,
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ yConfig: [
+ { color: statusPallete[0], forAccessor: 'y-axis-column' },
+ { color: statusPallete[1], forAccessor: 'y-axis-column-1' },
+ { color: statusPallete[2], forAccessor: 'y-axis-column-2' },
+ ],
+ };
+}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/kpi_trends_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/kpi_trends_config.ts
index 029fe5534965e..5e2d3440526b5 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/kpi_trends_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/kpi_trends_config.ts
@@ -6,7 +6,7 @@
*/
import { ConfigProps, DataSeries } from '../../types';
-import { FieldLabels, RECORDS_FIELD } from '../constants';
+import { FieldLabels, OPERATION_COLUMN, RECORDS_FIELD } from '../constants';
import { buildPhraseFilter } from '../utils';
import {
CLIENT_GEO_COUNTRY_NAME,
@@ -85,20 +85,25 @@ export function getKPITrendsLensConfig({ seriesId, indexPattern }: ConfigProps):
{
field: 'business.kpi',
custom: true,
- defaultValue: RECORDS_FIELD,
options: [
- { field: RECORDS_FIELD, label: PAGE_VIEWS_LABEL },
- { label: PAGE_LOAD_TIME_LABEL, field: TRANSACTION_DURATION, columnType: 'operation' },
+ { field: RECORDS_FIELD, id: RECORDS_FIELD, label: PAGE_VIEWS_LABEL },
+ {
+ label: PAGE_LOAD_TIME_LABEL,
+ field: TRANSACTION_DURATION,
+ id: TRANSACTION_DURATION,
+ columnType: OPERATION_COLUMN,
+ },
{
label: BACKEND_TIME_LABEL,
field: TRANSACTION_TIME_TO_FIRST_BYTE,
- columnType: 'operation',
+ id: TRANSACTION_TIME_TO_FIRST_BYTE,
+ columnType: OPERATION_COLUMN,
},
- { label: FCP_LABEL, field: FCP_FIELD, columnType: 'operation' },
- { label: TBT_LABEL, field: TBT_FIELD, columnType: 'operation' },
- { label: LCP_LABEL, field: LCP_FIELD, columnType: 'operation' },
- { label: FID_LABEL, field: FID_FIELD, columnType: 'operation' },
- { label: CLS_LABEL, field: CLS_FIELD, columnType: 'operation' },
+ { label: FCP_LABEL, field: FCP_FIELD, id: FCP_FIELD, columnType: OPERATION_COLUMN },
+ { label: TBT_LABEL, field: TBT_FIELD, id: TBT_FIELD, columnType: OPERATION_COLUMN },
+ { label: LCP_LABEL, field: LCP_FIELD, id: LCP_FIELD, columnType: OPERATION_COLUMN },
+ { label: FID_LABEL, field: FID_FIELD, id: FID_FIELD, columnType: OPERATION_COLUMN },
+ { label: CLS_LABEL, field: CLS_FIELD, id: CLS_FIELD, columnType: OPERATION_COLUMN },
],
},
],
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/performance_dist_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/performance_dist_config.ts
index af8bd00a69553..0dc582c5683dd 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/performance_dist_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/performance_dist_config.ts
@@ -80,15 +80,18 @@ export function getPerformanceDistLensConfig({ seriesId, indexPattern }: ConfigP
{
field: 'performance.metric',
custom: true,
- defaultValue: TRANSACTION_DURATION,
options: [
- { label: PAGE_LOAD_TIME_LABEL, field: TRANSACTION_DURATION },
- { label: BACKEND_TIME_LABEL, field: TRANSACTION_TIME_TO_FIRST_BYTE },
- { label: FCP_LABEL, field: FCP_FIELD },
- { label: TBT_LABEL, field: TBT_FIELD },
- { label: LCP_LABEL, field: LCP_FIELD },
- { label: FID_LABEL, field: FID_FIELD },
- { label: CLS_LABEL, field: CLS_FIELD },
+ { label: PAGE_LOAD_TIME_LABEL, id: TRANSACTION_DURATION, field: TRANSACTION_DURATION },
+ {
+ label: BACKEND_TIME_LABEL,
+ id: TRANSACTION_TIME_TO_FIRST_BYTE,
+ field: TRANSACTION_TIME_TO_FIRST_BYTE,
+ },
+ { label: FCP_LABEL, id: FCP_FIELD, field: FCP_FIELD },
+ { label: TBT_LABEL, id: TBT_FIELD, field: TBT_FIELD },
+ { label: LCP_LABEL, id: LCP_FIELD, field: LCP_FIELD },
+ { label: FID_LABEL, id: FID_FIELD, field: FID_FIELD },
+ { label: CLS_LABEL, id: CLS_FIELD, field: CLS_FIELD },
],
},
],
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts
index dc6b4bd0ec879..ea6f435460401 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts
@@ -67,7 +67,8 @@ export const useLensAttributes = ({
seriesType,
filters,
operationType,
- reportDefinitions
+ reportDefinitions,
+ breakdown
);
if (breakdown) {
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/chart_types.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/chart_types.tsx
index 3943ae3710209..a296d2520db34 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/chart_types.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/chart_types.tsx
@@ -20,9 +20,11 @@ const CHART_TYPE_LABEL = i18n.translate('xpack.observability.expView.chartTypes.
export function SeriesChartTypesSelect({
seriesId,
+ seriesTypes,
defaultChartType,
}: {
seriesId: string;
+ seriesTypes?: SeriesType[];
defaultChartType: SeriesType;
}) {
const { series, setSeries, allSeries } = useUrlStorage(seriesId);
@@ -42,8 +44,18 @@ export function SeriesChartTypesSelect({
onChange={onChange}
value={seriesType}
excludeChartTypes={['bar_percentage_stacked']}
+ includeChartTypes={
+ seriesTypes || [
+ 'bar',
+ 'bar_horizontal',
+ 'line',
+ 'area',
+ 'bar_stacked',
+ 'area_stacked',
+ 'bar_horizontal_percentage_stacked',
+ ]
+ }
label={CHART_TYPE_LABEL}
- includeChartTypes={['bar', 'bar_horizontal', 'line', 'area', 'bar_stacked', 'area_stacked']}
/>
);
}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.tsx
index 162892071dbff..e95cd894df5f2 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.tsx
@@ -16,5 +16,11 @@ export function ReportBreakdowns({
dataViewSeries: DataSeries;
seriesId: string;
}) {
- return ;
+ return (
+
+ );
}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx
index 717309e064ba3..ff8b0f7aa578b 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx
@@ -6,7 +6,7 @@
*/
import React from 'react';
-import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule } from '@elastic/eui';
import styled from 'styled-components';
import { useAppIndexPatternContext } from '../../hooks/use_app_index_pattern';
import { useUrlStorage } from '../../hooks/use_url_storage';
@@ -66,8 +66,9 @@ export function ReportDefinitionCol({
+
{indexPattern &&
- reportDefinitions.map(({ field, custom, options, defaultValue }) => (
+ reportDefinitions.map(({ field, custom, options }) => (
{!custom ? (
) : (
-
+
)}
))}
@@ -95,7 +91,11 @@ export function ReportDefinitionCol({
)}
-
+
);
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/custom_report_field.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/custom_report_field.tsx
index 6b74ad45b2c07..b41f3a603e5da 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/custom_report_field.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/custom_report_field.tsx
@@ -17,7 +17,7 @@ interface Props {
options: ReportDefinition['options'];
}
-export function CustomReportField({ field, seriesId, options: opts, defaultValue }: Props) {
+export function CustomReportField({ field, seriesId, options: opts }: Props) {
const { series, setSeries } = useUrlStorage(seriesId);
const { reportDefinitions: rtd = {} } = series;
@@ -35,11 +35,11 @@ export function CustomReportField({ field, seriesId, options: opts, defaultValue
fullWidth
compressed
prepend={'Metric'}
- options={options.map(({ label, field: fd }) => ({
- value: fd,
+ options={options.map(({ label, field: fd, id }) => ({
+ value: fd || id,
inputDisplay: label,
}))}
- valueOfSelected={reportDefinitions?.[field]?.[0] || defaultValue || options?.[0].field}
+ valueOfSelected={reportDefinitions?.[field]?.[0] || options?.[0].field || options?.[0].id}
onChange={(value) => onChange(value)}
/>
);
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx
index 5e270524b1880..1944bb281598b 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx
@@ -27,6 +27,7 @@ export const ReportTypes: Record
-
+
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.test.tsx
index eff62a60509e3..9d26ec79c31ad 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.test.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.test.tsx
@@ -23,7 +23,13 @@ describe('Breakdowns', function () {
it('should render properly', async function () {
mockUrlStorage({});
- render();
+ render(
+
+ );
screen.getAllByText('Browser family');
});
@@ -31,7 +37,13 @@ describe('Breakdowns', function () {
it('should call set series on change', function () {
const { setSeries } = mockUrlStorage({ breakdown: USER_AGENT_OS });
- render();
+ render(
+
+ );
screen.getAllByText('Operating system');
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx
index 5561779daa8c4..5cf6ac47aa8c7 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.tsx
@@ -8,15 +8,17 @@
import React from 'react';
import { EuiSuperSelect } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { FieldLabels } from '../../configurations/constants';
+import { USE_BREAK_DOWN_COLUMN } from '../../configurations/constants';
import { useUrlStorage } from '../../hooks/use_url_storage';
+import { DataSeries } from '../../types';
interface Props {
seriesId: string;
breakdowns: string[];
+ reportViewConfig: DataSeries;
}
-export function Breakdowns({ seriesId, breakdowns = [] }: Props) {
+export function Breakdowns({ reportViewConfig, seriesId, breakdowns = [] }: Props) {
const { setSeries, series } = useUrlStorage(seriesId);
const selectedBreakdown = series.breakdown;
@@ -36,13 +38,21 @@ export function Breakdowns({ seriesId, breakdowns = [] }: Props) {
}
};
- const items = breakdowns.map((breakdown) => ({ id: breakdown, label: FieldLabels[breakdown] }));
- items.push({
- id: NO_BREAKDOWN,
- label: i18n.translate('xpack.observability.exp.breakDownFilter.noBreakdown', {
- defaultMessage: 'No breakdown',
- }),
- });
+ const hasUseBreakdownColumn = reportViewConfig.xAxisColumn.sourceField === USE_BREAK_DOWN_COLUMN;
+
+ const items = breakdowns.map((breakdown) => ({
+ id: breakdown,
+ label: reportViewConfig.labels[breakdown],
+ }));
+
+ if (!hasUseBreakdownColumn) {
+ items.push({
+ id: NO_BREAKDOWN,
+ label: i18n.translate('xpack.observability.exp.breakDownFilter.noBreakdown', {
+ defaultMessage: 'No breakdown',
+ }),
+ });
+ }
const options = items.map(({ id, label }) => ({
inputDisplay: id === NO_BREAKDOWN ? label : {label},
@@ -50,13 +60,16 @@ export function Breakdowns({ seriesId, breakdowns = [] }: Props) {
dropdownDisplay: label,
}));
+ const valueOfSelected =
+ selectedBreakdown || (hasUseBreakdownColumn ? options[0].value : NO_BREAKDOWN);
+
return (
onOptionChange(value)}
data-test-subj={'seriesBreakdown'}
/>
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts
index 7b9278352925e..c3dd824e441a8 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts
@@ -12,6 +12,7 @@ import {
FieldBasedIndexPatternColumn,
SeriesType,
OperationType,
+ YConfig,
} from '../../../../../lens/public';
import { PersistableFilter } from '../../../../../lens/common';
@@ -21,6 +22,7 @@ import { ExistsFilter } from '../../../../../../../src/plugins/data/common/es_qu
export const ReportViewTypes = {
pld: 'page-load-dist',
kpi: 'kpi-trends',
+ cwv: 'core-web-vitals',
upd: 'uptime-duration',
upp: 'uptime-pings',
svl: 'service-latency',
@@ -37,16 +39,22 @@ export type ReportViewTypeId = keyof typeof ReportViewTypes;
export type ReportViewType = ValueOf;
+export interface ColumnFilter {
+ language: 'kuery';
+ query: string;
+}
+
export interface ReportDefinition {
field: string;
required?: boolean;
custom?: boolean;
- defaultValue?: string;
options?: Array<{
- field: string;
+ id: string;
+ field?: string;
label: string;
description?: string;
- columnType?: 'range' | 'operation';
+ columnType?: 'range' | 'operation' | 'FILTER_RECORDS';
+ columnFilters?: ColumnFilter[];
}>;
}
@@ -66,6 +74,7 @@ export interface DataSeries {
hasOperationType: boolean;
palette?: PaletteOutput;
yTitle?: string;
+ yConfig?: YConfig[];
}
export type URLReportDefinition = Record;
diff --git a/x-pack/plugins/reporting/server/config/index.test.ts b/x-pack/plugins/reporting/server/config/index.test.ts
index 327b03d679cae..b9665759f9f52 100644
--- a/x-pack/plugins/reporting/server/config/index.test.ts
+++ b/x-pack/plugins/reporting/server/config/index.test.ts
@@ -45,7 +45,7 @@ describe('deprecations', () => {
const { messages } = applyReportingDeprecations({ roles: { enabled: true } });
expect(messages).toMatchInlineSnapshot(`
Array [
- "\\"xpack.reporting.roles\\" is deprecated. Granting reporting privilege through a \\"reporting_user\\" role will not be supported starting in 8.0. Please set 'xpack.reporting.roles.enabled' to 'false' and grant reporting privileges to users using Kibana application privileges **Management > Security > Roles**.",
+ "\\"xpack.reporting.roles\\" is deprecated. Granting reporting privilege through a \\"reporting_user\\" role will not be supported starting in 8.0. Please set \\"xpack.reporting.roles.enabled\\" to \\"false\\" and grant reporting privileges to users using Kibana application privileges **Management > Security > Roles**.",
]
`);
});
diff --git a/x-pack/plugins/reporting/server/config/index.ts b/x-pack/plugins/reporting/server/config/index.ts
index 8927bd8ee94d5..d0c743f859b3c 100644
--- a/x-pack/plugins/reporting/server/config/index.ts
+++ b/x-pack/plugins/reporting/server/config/index.ts
@@ -28,6 +28,12 @@ export const config: PluginConfigDescriptor = {
if (reporting?.index) {
addDeprecation({
message: `"${fromPath}.index" is deprecated. Multitenancy by changing "kibana.index" will not be supported starting in 8.0. See https://ela.st/kbn-remove-legacy-multitenancy for more details`,
+ correctiveActions: {
+ manualSteps: [
+ `If you rely on this setting to achieve multitenancy you should use Spaces, cross-cluster replication, or cross-cluster search instead.`,
+ `To migrate to Spaces, we encourage using saved object management to export your saved objects from a tenant into the default tenant in a space.`,
+ ],
+ },
});
}
@@ -35,8 +41,15 @@ export const config: PluginConfigDescriptor = {
addDeprecation({
message:
`"${fromPath}.roles" is deprecated. Granting reporting privilege through a "reporting_user" role will not be supported ` +
- `starting in 8.0. Please set 'xpack.reporting.roles.enabled' to 'false' and grant reporting privileges to users ` +
+ `starting in 8.0. Please set "xpack.reporting.roles.enabled" to "false" and grant reporting privileges to users ` +
`using Kibana application privileges **Management > Security > Roles**.`,
+ correctiveActions: {
+ manualSteps: [
+ `Set 'xpack.reporting.roles.enabled' to 'false' in your kibana configs.`,
+ `Grant reporting privileges to users using Kibana application privileges` +
+ `under **Management > Security > Roles**.`,
+ ],
+ },
});
}
},
diff --git a/x-pack/plugins/security/server/config_deprecations.test.ts b/x-pack/plugins/security/server/config_deprecations.test.ts
index a233d760359e5..d2c75fd2331b9 100644
--- a/x-pack/plugins/security/server/config_deprecations.test.ts
+++ b/x-pack/plugins/security/server/config_deprecations.test.ts
@@ -243,7 +243,7 @@ describe('Config Deprecations', () => {
expect(migrated).toEqual(config);
expect(messages).toMatchInlineSnapshot(`
Array [
- "Defining \`xpack.security.authc.providers\` as an array of provider types is deprecated. Use extended \`object\` format instead.",
+ "Defining \\"xpack.security.authc.providers\\" as an array of provider types is deprecated. Use extended \\"object\\" format instead.",
]
`);
});
@@ -262,7 +262,7 @@ describe('Config Deprecations', () => {
expect(migrated).toEqual(config);
expect(messages).toMatchInlineSnapshot(`
Array [
- "Defining \`xpack.security.authc.providers\` as an array of provider types is deprecated. Use extended \`object\` format instead.",
+ "Defining \\"xpack.security.authc.providers\\" as an array of provider types is deprecated. Use extended \\"object\\" format instead.",
"Enabling both \`basic\` and \`token\` authentication providers in \`xpack.security.authc.providers\` is deprecated. Login page will only use \`token\` provider.",
]
`);
diff --git a/x-pack/plugins/security/server/config_deprecations.ts b/x-pack/plugins/security/server/config_deprecations.ts
index f99f3450417f2..a12f2f738f14c 100644
--- a/x-pack/plugins/security/server/config_deprecations.ts
+++ b/x-pack/plugins/security/server/config_deprecations.ts
@@ -27,7 +27,13 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({
if (Array.isArray(settings?.xpack?.security?.authc?.providers)) {
addDeprecation({
message:
- 'Defining `xpack.security.authc.providers` as an array of provider types is deprecated. Use extended `object` format instead.',
+ `Defining "xpack.security.authc.providers" as an array of provider types is deprecated. ` +
+ `Use extended "object" format instead.`,
+ correctiveActions: {
+ manualSteps: [
+ `Use the extended object format for "xpack.security.authc.providers" in your Kibana configuration.`,
+ ],
+ },
});
}
},
@@ -47,6 +53,11 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({
addDeprecation({
message:
'Enabling both `basic` and `token` authentication providers in `xpack.security.authc.providers` is deprecated. Login page will only use `token` provider.',
+ correctiveActions: {
+ manualSteps: [
+ 'Remove either the `basic` or `token` auth provider in "xpack.security.authc.providers" from your Kibana configuration.',
+ ],
+ },
});
}
},
@@ -59,6 +70,11 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({
addDeprecation({
message:
'`xpack.security.authc.providers.saml..maxRedirectURLSize` is deprecated and is no longer used',
+ correctiveActions: {
+ manualSteps: [
+ `Remove "xpack.security.authc.providers.saml..maxRedirectURLSize" from your Kibana configuration.`,
+ ],
+ },
});
}
},
@@ -68,6 +84,12 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({
message:
'Disabling the security plugin (`xpack.security.enabled`) will not be supported in the next major version (8.0). ' +
'To turn off security features, disable them in Elasticsearch instead.',
+ correctiveActions: {
+ manualSteps: [
+ `Remove "xpack.security.enabled" from your Kibana configuration.`,
+ `To turn off security features, disable them in Elasticsearch instead.`,
+ ],
+ },
});
}
},
diff --git a/x-pack/plugins/spaces/server/config.ts b/x-pack/plugins/spaces/server/config.ts
index 2ad8ed654ebb6..2a2f363d769f7 100644
--- a/x-pack/plugins/spaces/server/config.ts
+++ b/x-pack/plugins/spaces/server/config.ts
@@ -28,6 +28,9 @@ const disabledDeprecation: ConfigDeprecation = (config, fromPath, addDeprecation
if (config.xpack?.spaces?.enabled === false) {
addDeprecation({
message: `Disabling the Spaces plugin (xpack.spaces.enabled) will not be supported in the next major version (8.0)`,
+ correctiveActions: {
+ manualSteps: [`Remove "xpack.spaces.enabled: false" from your Kibana configuration`],
+ },
});
}
};
diff --git a/x-pack/plugins/task_manager/server/index.ts b/x-pack/plugins/task_manager/server/index.ts
index 155b8246905d1..80f0e298a8ac3 100644
--- a/x-pack/plugins/task_manager/server/index.ts
+++ b/x-pack/plugins/task_manager/server/index.ts
@@ -38,11 +38,23 @@ export const config: PluginConfigDescriptor = {
addDeprecation({
documentationUrl: 'https://ela.st/kbn-remove-legacy-multitenancy',
message: `"${fromPath}.index" is deprecated. Multitenancy by changing "kibana.index" will not be supported starting in 8.0. See https://ela.st/kbn-remove-legacy-multitenancy for more details`,
+ correctiveActions: {
+ manualSteps: [
+ `If you rely on this setting to achieve multitenancy you should use Spaces, cross-cluster replication, or cross-cluster search instead.`,
+ `To migrate to Spaces, we encourage using saved object management to export your saved objects from a tenant into the default tenant in a space.`,
+ ],
+ },
});
}
if (taskManager?.max_workers > MAX_WORKERS_LIMIT) {
addDeprecation({
message: `setting "${fromPath}.max_workers" (${taskManager?.max_workers}) greater than ${MAX_WORKERS_LIMIT} is deprecated. Values greater than ${MAX_WORKERS_LIMIT} will not be supported starting in 8.0.`,
+ correctiveActions: {
+ manualSteps: [
+ `Maximum allowed value of "${fromPath}.max_workers" is ${MAX_WORKERS_LIMIT}.` +
+ `Replace "${fromPath}.max_workers: ${taskManager?.max_workers}" with (${MAX_WORKERS_LIMIT}).`,
+ ],
+ },
});
}
},
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 7bc2e87297076..7442b41493c79 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -13312,7 +13312,6 @@
"xpack.maps.emsSource.tooltipsTitle": "ツールチップフィールド",
"xpack.maps.es_geo_utils.convert.invalidGeometryCollectionErrorMessage": "GeometryCollectionを convertESShapeToGeojsonGeometryに渡さないでください",
"xpack.maps.es_geo_utils.convert.unsupportedGeometryTypeErrorMessage": "{geometryType} ジオメトリから Geojson に変換できません。サポートされていません",
- "xpack.maps.es_geo_utils.distanceFilterAlias": "{pointLabel} の {distanceKm}km 以内にある {geoFieldName}",
"xpack.maps.es_geo_utils.unsupportedFieldTypeErrorMessage": "サポートされていないフィールドタイプ、期待値:{expectedTypes}、提供された値:{fieldType}",
"xpack.maps.es_geo_utils.unsupportedGeometryTypeErrorMessage": "サポートされていないジオメトリタイプ、期待値:{expectedTypes}、提供された値:{geometryType}",
"xpack.maps.es_geo_utils.wkt.invalidWKTErrorMessage": "{wkt} を Geojson に変換できません。有効な WKT が必要です。",
@@ -13492,7 +13491,6 @@
"xpack.maps.metricSelect.selectAggregationPlaceholder": "集約を選択",
"xpack.maps.metricSelect.sumDropDownOptionLabel": "合計",
"xpack.maps.metricSelect.termsDropDownOptionLabel": "トップ用語",
- "xpack.maps.multiIndexFieldSelect.fieldLabel": "フィールドのフィルタリング",
"xpack.maps.mvtSource.addFieldLabel": "追加",
"xpack.maps.mvtSource.fieldPlaceholderText": "フィールド名",
"xpack.maps.mvtSource.numberFieldLabel": "数字",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index d725a6750c073..7a8831dc15f84 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -13488,7 +13488,6 @@
"xpack.maps.emsSource.tooltipsTitle": "工具提示字段",
"xpack.maps.es_geo_utils.convert.invalidGeometryCollectionErrorMessage": "不应将 GeometryCollection 传递给 convertESShapeToGeojsonGeometry",
"xpack.maps.es_geo_utils.convert.unsupportedGeometryTypeErrorMessage": "无法将 {geometryType} 几何图形转换成 geojson,不支持",
- "xpack.maps.es_geo_utils.distanceFilterAlias": "{pointLabel} {distanceKm}km 内的 {geoFieldName}",
"xpack.maps.es_geo_utils.unsupportedFieldTypeErrorMessage": "字段类型不受支持,应为 {expectedTypes},而提供的是 {fieldType}",
"xpack.maps.es_geo_utils.unsupportedGeometryTypeErrorMessage": "几何类型不受支持,应为 {expectedTypes},而提供的是 {geometryType}",
"xpack.maps.es_geo_utils.wkt.invalidWKTErrorMessage": "无法将 {wkt} 转换成 geojson。需要有效的 WKT。",
@@ -13669,7 +13668,6 @@
"xpack.maps.metricSelect.selectAggregationPlaceholder": "选择聚合",
"xpack.maps.metricSelect.sumDropDownOptionLabel": "求和",
"xpack.maps.metricSelect.termsDropDownOptionLabel": "热门词",
- "xpack.maps.multiIndexFieldSelect.fieldLabel": "筛选字段",
"xpack.maps.mvtSource.addFieldLabel": "添加",
"xpack.maps.mvtSource.fieldPlaceholderText": "字段名称",
"xpack.maps.mvtSource.numberFieldLabel": "数字",
diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview.test.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview.test.ts
index f3f76c3a6688e..85efaf38f32a7 100644
--- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview.test.ts
+++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview.test.ts
@@ -45,7 +45,7 @@ describe('Overview page', () => {
const kibanaDeprecationsMockResponse: DomainDeprecationDetails[] = [
{
- correctiveActions: {},
+ correctiveActions: { manualSteps: ['test-step'] },
domainId: 'xpack.spaces',
level: 'critical',
message: