diff --git a/.github/paths-labeller.yml b/.github/paths-labeller.yml
index 4a498af6b1c88..3d35cd74e0718 100644
--- a/.github/paths-labeller.yml
+++ b/.github/paths-labeller.yml
@@ -1,13 +1,4 @@
---
- - "Team:AppArch":
- - "src/plugins/bfetch/**/*.*"
- - "src/plugins/dashboard_embeddable_container/**/*.*"
- - "src/plugins/data/**/*.*"
- - "src/plugins/embeddable/**/*.*"
- - "src/plugins/expressions/**/*.*"
- - "src/plugins/inspector/**/*.*"
- - "src/plugins/ui_actions/**/*.*"
- - "src/plugins/visualizations/**/*.*"
- "Feature:Embedding":
- "src/plugins/embeddable/**/*.*"
- "src/plugins/dashboard_embeddable_container/**/*.*"
diff --git a/docs/apm/agent-configuration.asciidoc b/docs/apm/agent-configuration.asciidoc
index 6f147d0e3223a..0d2834c1a400e 100644
--- a/docs/apm/agent-configuration.asciidoc
+++ b/docs/apm/agent-configuration.asciidoc
@@ -31,37 +31,18 @@ Kibana communicates any changed settings to APM Server so that your agents only
[float]
==== Supported configurations
-[float]
-===== `CAPTURE_BODY`
-
-added[7.5.0] Can be `"off"`, `"errors"`, `"transactions"`, or `"all"`. Defaults to `"off"`.
-
-For transactions that are HTTP requests, the Agent can optionally capture the request body, e.g., POST variables.
-Remember, request bodies often contain sensitive values like passwords, credit card numbers, etc.
-If your service handles sensitive data, enable this feature with care.
-Turning on body capturing can also significantly increase the overhead the overhead of the Agent,
-and the Elasticsearch index size.
-
-[float]
-===== `TRANSACTION_MAX_SPANS`
-
-added[7.5.0] A number between `0` and `32000`. Defaults to `500`.
-
-Limit the number of spans that are recorded per transaction.
-This is helpful in cases where a transaction creates a very high amount of spans, e.g., thousands of SQL queries.
-Setting an upper limit will help prevent the Agent and the APM Server from being overloaded.
-
-[float]
-===== `TRANSACTION_SAMPLE_RATE`
-
-added[7.3.0] A sample rate between `0.000` and `1.0`. Default configuration is `1.0` (100% of traces).
-
-Adjusting the sampling rate controls what percent of requests are traced.
-`1.0` means _all_ requests are traced. If you set the `TRANSACTION_SAMPLE_RATE` to a value below `1.0`,
-the agent will randomly sample only a subset of transactions.
-Unsampled transactions only record the name of the transaction, the overall transaction time, and the result.
-
-IMPORTANT: In a distributed trace, the sampling decision is propagated by the initializing Agent.
-This means if you're using multiple agents, only the originating service's sampling rate will be used.
-Be sure to set sensible defaults in _all_ of your agents, especially the
-{apm-rum-ref}/configuration.html#transaction-sample-rate[JavaScript RUM Agent].
+Each Agent has its own list of supported configurations.
+After selecting a Service name and environment in the APM app,
+a list of all available configuration options,
+including descriptions and default values, will be displayed.
+
+Supported configurations are also marked in each Agent's configuration documentation:
+
+[horizontal]
+Go Agent:: {apm-go-ref}/configuration.html[Configuration reference]
+Java Agent:: {apm-java-ref}/configuration.html[Configuration reference]
+.NET Agent:: {apm-dotnet-ref}/configuration.html[Configuration reference]
+Node.js Agent:: {apm-node-ref}/configuration.html[Configuration reference]
+Python Agent:: {apm-py-ref}/configuration.html[Configuration reference]
+Ruby Agent:: {apm-ruby-ref}/configuration.html[Configuration reference]
+Real User Monitoring (RUM) Agent:: {apm-rum-ref}/configuration.html[Configuration reference]
diff --git a/docs/apm/images/apm-agent-configuration.png b/docs/apm/images/apm-agent-configuration.png
index d998b5daedd9b..ded0553219a03 100644
Binary files a/docs/apm/images/apm-agent-configuration.png and b/docs/apm/images/apm-agent-configuration.png differ
diff --git a/docs/images/Dashboard_add_new_visualization.png b/docs/images/Dashboard_add_new_visualization.png
index b871131805ab5..445f1f5dd8df5 100644
Binary files a/docs/images/Dashboard_add_new_visualization.png and b/docs/images/Dashboard_add_new_visualization.png differ
diff --git a/docs/images/Dashboard_add_visualization.png b/docs/images/Dashboard_add_visualization.png
index bc705b66e17d1..179dbc66eb194 100644
Binary files a/docs/images/Dashboard_add_visualization.png and b/docs/images/Dashboard_add_visualization.png differ
diff --git a/docs/epm/index.asciidoc b/docs/ingest_manager/index.asciidoc
similarity index 71%
rename from docs/epm/index.asciidoc
rename to docs/ingest_manager/index.asciidoc
index d2ebe003afd6b..1254f412e14c5 100644
--- a/docs/epm/index.asciidoc
+++ b/docs/ingest_manager/index.asciidoc
@@ -1,8 +1,8 @@
[role="xpack"]
[[epm]]
-== Elastic Package Manager
+== Ingest Manager
-These are the docs for the Elastic Package Manager (EPM).
+These are the docs for the Ingest Manager.
=== Configuration
@@ -39,16 +39,71 @@ curl -X DELETE localhost:5601/api/ingest_manager/epm/packages/iptables-1.0.4
This section is to define terms used across ingest management.
+==== Data Source
+
+A data source is a definition on how to collect data from a service, for example `nginx`. A data source contains
+definitions for one or multiple inputs and each input can contain one or multiple streams.
+
+With the example of the nginx Data Source, it contains to inputs: `logs` and `nginx/metrics`. Logs and metrics are collected
+differently. The `logs` input contains two streams, `access` and `error`, the `nginx/metrics` input contains the stubstatus stream.
+
+
+==== Data Stream
+
+Data Streams are a [new concept](https://github.com/elastic/elasticsearch/issues/53100) in Elasticsearch which simplify
+ingesting data and the setup of Elasticsearch.
+
==== Elastic Agent
+
A single, unified agent that users can deploy to hosts or containers. It controls which data is collected from the host or containers and where the data is sent. It will run Beats, Endpoint or other monitoring programs as needed. It can operate standalone or pull a configuration policy from Fleet.
+
+==== Elastic Package Registry
+
+The Elastic Package Registry (EPR) is a service which runs under [https://epr.elastic.co]. It serves the packages through its API.
+More details about the registry can be found [here](https://github.com/elastic/package-registry).
+
+==== Fleet
+
+Fleet is the part of the Ingest Manager UI in Kibana that handles the part of enrolling Elastic Agents,
+managing agents and sending configurations to the Elastic Agent.
+
+==== Indexing Strategy
+
+Ingest Management + Elastic Agent follow a strict new indexing strategy: `{type}-{dataset}-{namespace}`. An example
+for this is `logs-nginx.access-default`. More details about it can be found in the Index Strategy below. All data of
+the index strategy is sent to Data Streams.
+
+==== Input
+
+An input is the configuration unit in an Agent Config that defines the options on how to collect data from
+an endpoint. This could be username / password which are need to authenticate with a service or a host url
+as an example.
+
+An input is part of a Data Source and contains streams.
+
+==== Integration
+
+An integration is a package with the type integration. An integration package has at least 1 data source
+and usually collects data from / about a service.
+
+
==== Namespace
+
A user-specified string that will be used to part of the index name in Elasticsearch. It helps users identify logs coming from a specific environment (like prod or test), an application, or other identifiers.
+
==== Package
-A package contains all the assets for the Elastic Stack. A more detailed definition of a package can be found under https://github.com/elastic/package-registry.
+A package contains all the assets for the Elastic Stack. A more detailed definition of a
+package can be found under https://github.com/elastic/package-registry.
+
+Besides the assets, a package contains the data source definitions with its inputs and streams.
+
+==== Stream
+A stream is a configuration unit in the Elastic Agent config. A stream is part of an input and defines how the data
+fetched by this input should be processed and which Data Stream to send it to.
== Indexing Strategy
diff --git a/docs/migration/migrate_8_0.asciidoc b/docs/migration/migrate_8_0.asciidoc
index 4dc03222f057e..f3e30eb7e7d77 100644
--- a/docs/migration/migrate_8_0.asciidoc
+++ b/docs/migration/migrate_8_0.asciidoc
@@ -141,4 +141,13 @@ access level.
been deprecated with warnings that have been logged throughout 7.x. Please use Kibana UI to re-generate the
POST URL snippets if you depend on these for automated PDF reports.
+[float]
+=== Configurations starting with `xpack.telemetry` are no longer valid
+
+*Details:*
+The `xpack.` prefix has been removed for all telemetry configurations.
+
+*Impact:*
+For any configurations beginning with `xpack.telemetry`, remove the `xpack` prefix. Use {kibana-ref}/telemetry-settings-kbn.html#telemetry-general-settings[`telemetry.enabled`] instead.
+
// end::notable-breaking-changes[]
diff --git a/docs/settings/telemetry-settings.asciidoc b/docs/settings/telemetry-settings.asciidoc
new file mode 100644
index 0000000000000..ad5f53ad879f8
--- /dev/null
+++ b/docs/settings/telemetry-settings.asciidoc
@@ -0,0 +1,38 @@
+[[telemetry-settings-kbn]]
+=== Telemetry settings in Kibana
+++++
+Telemetry settings
+++++
+
+By default, Usage Collection (also known as Telemetry) is enabled. This
+helps us learn about the {kib} features that our users are most interested in, so we
+can focus our efforts on making them even better.
+
+You can control whether this data is sent from the {kib} servers, or if it should be sent
+from the user's browser, in case a firewall is blocking the connections from the server. Additionally, you can decide to completely disable this feature either in the config file or in {kib} via *Management > Kibana > Advanced Settings > Usage Data*.
+
+See our https://www.elastic.co/legal/privacy-statement[Privacy Statement] to learn more.
+
+[float]
+[[telemetry-general-settings]]
+==== General telemetry settings
+
+`telemetry.enabled`:: *Default: true*.
+Set to `true` to send cluster statistics to Elastic. Reporting your
+cluster statistics helps us improve your user experience. Your data is never
+shared with anyone. Set to `false` to disable statistics reporting from any
+browser connected to the {kib} instance.
+
+`telemetry.sendUsageFrom`:: *Default: 'browser'*.
+Set to `'server'` to report the cluster statistics from the {kib} server.
+If the server fails to connect to our endpoint at https://telemetry.elastic.co/, it assumes
+it is behind a firewall and falls back to `'browser'` to send it from users' browsers
+when they are navigating through {kib}.
+
+`telemetry.optIn`:: *Default: true*.
+Set to `true` to automatically opt into reporting cluster statistics. You can also opt out through
+*Advanced Settings* in {kib}.
+
+`telemetry.allowChangingOptInStatus`:: *Default: true*.
+Set to `true` to allow overwriting the `telemetry.optIn` setting via the {kib} UI.
+Note: When `false`, `telemetry.optIn` must be `true`. To disable telemetry and not allow users to change that parameter, use `telemetry.enabled`.
diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc
index fb58456ecc2cd..41fe8d337c03b 100644
--- a/docs/setup/settings.asciidoc
+++ b/docs/setup/settings.asciidoc
@@ -463,3 +463,4 @@ include::{docdir}/settings/reporting-settings.asciidoc[]
include::secure-settings.asciidoc[]
include::{docdir}/settings/security-settings.asciidoc[]
include::{docdir}/settings/spaces-settings.asciidoc[]
+include::{docdir}/settings/telemetry-settings.asciidoc[]
diff --git a/docs/user/alerting/images/alert-concepts-summary.svg b/docs/user/alerting/images/alert-concepts-summary.svg
index d11023b706418..0d63601c0693d 100644
--- a/docs/user/alerting/images/alert-concepts-summary.svg
+++ b/docs/user/alerting/images/alert-concepts-summary.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/user/alerting/images/alert-instances.svg b/docs/user/alerting/images/alert-instances.svg
index b7b0bd4996053..97f610041f0eb 100644
--- a/docs/user/alerting/images/alert-instances.svg
+++ b/docs/user/alerting/images/alert-instances.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/examples/embeddable_examples/public/hello_world/hello_world_embeddable_factory.ts b/examples/embeddable_examples/public/hello_world/hello_world_embeddable_factory.ts
index 2995c99ac9e58..b81a229ea23e7 100644
--- a/examples/embeddable_examples/public/hello_world/hello_world_embeddable_factory.ts
+++ b/examples/embeddable_examples/public/hello_world/hello_world_embeddable_factory.ts
@@ -21,11 +21,11 @@ import { i18n } from '@kbn/i18n';
import {
IContainer,
EmbeddableInput,
- EmbeddableFactory,
+ EmbeddableFactoryDefinition,
} from '../../../../src/plugins/embeddable/public';
import { HelloWorldEmbeddable, HELLO_WORLD_EMBEDDABLE } from './hello_world_embeddable';
-export class HelloWorldEmbeddableFactory extends EmbeddableFactory {
+export class HelloWorldEmbeddableFactory implements EmbeddableFactoryDefinition {
public readonly type = HELLO_WORLD_EMBEDDABLE;
/**
diff --git a/examples/embeddable_examples/public/list_container/list_container_factory.ts b/examples/embeddable_examples/public/list_container/list_container_factory.ts
index 247cf48b41bde..1fde254110c62 100644
--- a/examples/embeddable_examples/public/list_container/list_container_factory.ts
+++ b/examples/embeddable_examples/public/list_container/list_container_factory.ts
@@ -19,7 +19,7 @@
import { i18n } from '@kbn/i18n';
import {
- EmbeddableFactory,
+ EmbeddableFactoryDefinition,
ContainerInput,
EmbeddableStart,
} from '../../../../src/plugins/embeddable/public';
@@ -29,22 +29,20 @@ interface StartServices {
getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory'];
}
-export class ListContainerFactory extends EmbeddableFactory {
+export class ListContainerFactory implements EmbeddableFactoryDefinition {
public readonly type = LIST_CONTAINER;
public readonly isContainerType = true;
- constructor(private getStartServices: () => Promise) {
- super();
- }
+ constructor(private getStartServices: () => Promise) {}
public async isEditable() {
return true;
}
- public async create(initialInput: ContainerInput) {
+ public create = async (initialInput: ContainerInput) => {
const { getEmbeddableFactory } = await this.getStartServices();
return new ListContainer(initialInput, getEmbeddableFactory);
- }
+ };
public getDisplayName() {
return i18n.translate('embeddableExamples.searchableListContainer.displayName', {
diff --git a/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_embeddable_factory.ts b/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_embeddable_factory.ts
index 9afdeabaee765..d010e25885ce6 100644
--- a/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_embeddable_factory.ts
+++ b/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_embeddable_factory.ts
@@ -18,7 +18,7 @@
*/
import { i18n } from '@kbn/i18n';
-import { IContainer, EmbeddableFactory } from '../../../../src/plugins/embeddable/public';
+import { IContainer, EmbeddableFactoryDefinition } from '../../../../src/plugins/embeddable/public';
import {
MultiTaskTodoEmbeddable,
MULTI_TASK_TODO_EMBEDDABLE,
@@ -26,10 +26,8 @@ import {
MultiTaskTodoOutput,
} from './multi_task_todo_embeddable';
-export class MultiTaskTodoEmbeddableFactory extends EmbeddableFactory<
- MultiTaskTodoInput,
- MultiTaskTodoOutput
-> {
+export class MultiTaskTodoEmbeddableFactory
+ implements EmbeddableFactoryDefinition {
public readonly type = MULTI_TASK_TODO_EMBEDDABLE;
public async isEditable() {
diff --git a/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_factory.ts b/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_factory.ts
index 79859cc015a80..382bb65e769ef 100644
--- a/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_factory.ts
+++ b/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_factory.ts
@@ -18,7 +18,10 @@
*/
import { i18n } from '@kbn/i18n';
-import { EmbeddableFactory, EmbeddableStart } from '../../../../src/plugins/embeddable/public';
+import {
+ EmbeddableFactoryDefinition,
+ EmbeddableStart,
+} from '../../../../src/plugins/embeddable/public';
import {
SEARCHABLE_LIST_CONTAINER,
SearchableListContainer,
@@ -29,22 +32,20 @@ interface StartServices {
getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory'];
}
-export class SearchableListContainerFactory extends EmbeddableFactory {
+export class SearchableListContainerFactory implements EmbeddableFactoryDefinition {
public readonly type = SEARCHABLE_LIST_CONTAINER;
public readonly isContainerType = true;
- constructor(private getStartServices: () => Promise) {
- super();
- }
+ constructor(private getStartServices: () => Promise) {}
public async isEditable() {
return true;
}
- public async create(initialInput: SearchableContainerInput) {
+ public create = async (initialInput: SearchableContainerInput) => {
const { getEmbeddableFactory } = await this.getStartServices();
return new SearchableListContainer(initialInput, getEmbeddableFactory);
- }
+ };
public getDisplayName() {
return i18n.translate('embeddableExamples.searchableListContainer.displayName', {
diff --git a/examples/embeddable_examples/public/todo/todo_embeddable_factory.tsx b/examples/embeddable_examples/public/todo/todo_embeddable_factory.tsx
index d7be436905382..bc577ca36d793 100644
--- a/examples/embeddable_examples/public/todo/todo_embeddable_factory.tsx
+++ b/examples/embeddable_examples/public/todo/todo_embeddable_factory.tsx
@@ -23,7 +23,7 @@ import { OverlayStart } from 'kibana/public';
import { EuiFieldText } from '@elastic/eui';
import { EuiButton } from '@elastic/eui';
import { toMountPoint } from '../../../../src/plugins/kibana_react/public';
-import { IContainer, EmbeddableFactory } from '../../../../src/plugins/embeddable/public';
+import { IContainer, EmbeddableFactoryDefinition } from '../../../../src/plugins/embeddable/public';
import { TodoEmbeddable, TODO_EMBEDDABLE, TodoInput, TodoOutput } from './todo_embeddable';
function TaskInput({ onSave }: { onSave: (task: string) => void }) {
@@ -47,16 +47,11 @@ interface StartServices {
openModal: OverlayStart['openModal'];
}
-export class TodoEmbeddableFactory extends EmbeddableFactory<
- TodoInput,
- TodoOutput,
- TodoEmbeddable
-> {
+export class TodoEmbeddableFactory
+ implements EmbeddableFactoryDefinition {
public readonly type = TODO_EMBEDDABLE;
- constructor(private getStartServices: () => Promise) {
- super();
- }
+ constructor(private getStartServices: () => Promise) {}
public async isEditable() {
return true;
@@ -72,7 +67,7 @@ export class TodoEmbeddableFactory extends EmbeddableFactory<
* used to collect specific embeddable input that the container will not provide, like
* in this case, the task string.
*/
- public async getExplicitInput() {
+ public getExplicitInput = async () => {
const { openModal } = await this.getStartServices();
return new Promise<{ task: string }>(resolve => {
const onSave = (task: string) => resolve({ task });
@@ -87,7 +82,7 @@ export class TodoEmbeddableFactory extends EmbeddableFactory<
)
);
});
- }
+ };
public getDisplayName() {
return i18n.translate('embeddableExamples.todo.displayName', {
diff --git a/examples/embeddable_explorer/public/todo_embeddable_example.tsx b/examples/embeddable_explorer/public/todo_embeddable_example.tsx
index ce92301236c2b..2af6c713593c6 100644
--- a/examples/embeddable_explorer/public/todo_embeddable_example.tsx
+++ b/examples/embeddable_explorer/public/todo_embeddable_example.tsx
@@ -37,9 +37,14 @@ import {
import {
TodoEmbeddable,
TODO_EMBEDDABLE,
- TodoEmbeddableFactory,
+ TodoInput,
} from '../../../examples/embeddable_examples/public/todo';
-import { EmbeddableStart, EmbeddableRoot } from '../../../src/plugins/embeddable/public';
+import {
+ EmbeddableStart,
+ EmbeddableRoot,
+ EmbeddableOutput,
+ ErrorEmbeddable,
+} from '../../../src/plugins/embeddable/public';
interface Props {
getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory'];
@@ -53,7 +58,7 @@ interface State {
}
export class TodoEmbeddableExample extends React.Component {
- private embeddable?: TodoEmbeddable;
+ private embeddable?: TodoEmbeddable | ErrorEmbeddable;
constructor(props: Props) {
super(props);
@@ -62,7 +67,9 @@ export class TodoEmbeddableExample extends React.Component {
}
public componentDidMount() {
- const factory = this.props.getEmbeddableFactory(TODO_EMBEDDABLE) as TodoEmbeddableFactory;
+ const factory = this.props.getEmbeddableFactory(
+ TODO_EMBEDDABLE
+ );
if (factory === undefined) {
throw new Error('Embeddable factory is undefined!');
diff --git a/kibana.d.ts b/kibana.d.ts
index 68538320e05a2..21e3e99abaa90 100644
--- a/kibana.d.ts
+++ b/kibana.d.ts
@@ -37,7 +37,6 @@ import * as LegacyKibanaServer from './src/legacy/server/kbn_server';
*/
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace Legacy {
- export type IndexPatternsService = LegacyKibanaServer.IndexPatternsService;
export type KibanaConfig = LegacyKibanaServer.KibanaConfig;
export type Request = LegacyKibanaServer.Request;
export type ResponseToolkit = LegacyKibanaServer.ResponseToolkit;
diff --git a/package.json b/package.json
index cdc41a2d8a3f0..49b5baecda474 100644
--- a/package.json
+++ b/package.json
@@ -395,7 +395,7 @@
"babel-eslint": "^10.0.3",
"babel-jest": "^24.9.0",
"babel-plugin-istanbul": "^5.2.0",
- "backport": "5.1.2",
+ "backport": "5.1.3",
"chai": "3.5.0",
"chance": "1.0.18",
"cheerio": "0.22.0",
diff --git a/src/core/server/config/deprecation/core_deprecations.ts b/src/core/server/config/deprecation/core_deprecations.ts
index d91e55115d0b1..f0b21adf62ff7 100644
--- a/src/core/server/config/deprecation/core_deprecations.ts
+++ b/src/core/server/config/deprecation/core_deprecations.ts
@@ -128,13 +128,6 @@ export const coreDeprecationProvider: ConfigDeprecationProvider = ({
renameFromRoot('optimize.lazyHost', 'optimize.watchHost'),
renameFromRoot('optimize.lazyPrebuild', 'optimize.watchPrebuild'),
renameFromRoot('optimize.lazyProxyTimeout', 'optimize.watchProxyTimeout'),
- renameFromRoot('xpack.xpack_main.telemetry.config', 'telemetry.config'),
- renameFromRoot('xpack.xpack_main.telemetry.url', 'telemetry.url'),
- renameFromRoot('xpack.xpack_main.telemetry.enabled', 'telemetry.enabled'),
- renameFromRoot('xpack.telemetry.enabled', 'telemetry.enabled'),
- renameFromRoot('xpack.telemetry.config', 'telemetry.config'),
- renameFromRoot('xpack.telemetry.banner', 'telemetry.banner'),
- renameFromRoot('xpack.telemetry.url', 'telemetry.url'),
// Monitoring renames
// TODO: Remove these from here once the monitoring plugin is migrated to NP
renameFromRoot('xpack.monitoring.enabled', 'monitoring.enabled'),
diff --git a/src/core/server/elasticsearch/elasticsearch_config.test.mocks.ts b/src/core/server/elasticsearch/elasticsearch_config.test.mocks.ts
index d908fdbfd2e80..df8566746eaf9 100644
--- a/src/core/server/elasticsearch/elasticsearch_config.test.mocks.ts
+++ b/src/core/server/elasticsearch/elasticsearch_config.test.mocks.ts
@@ -22,7 +22,7 @@ jest.mock('fs', () => ({ readFileSync: mockReadFileSync }));
export const mockReadPkcs12Keystore = jest.fn();
export const mockReadPkcs12Truststore = jest.fn();
-jest.mock('../../utils', () => ({
+jest.mock('../utils', () => ({
readPkcs12Keystore: mockReadPkcs12Keystore,
readPkcs12Truststore: mockReadPkcs12Truststore,
}));
diff --git a/src/core/server/elasticsearch/elasticsearch_config.test.ts b/src/core/server/elasticsearch/elasticsearch_config.test.ts
index 1b4fc5eafec76..de3f57298f461 100644
--- a/src/core/server/elasticsearch/elasticsearch_config.test.ts
+++ b/src/core/server/elasticsearch/elasticsearch_config.test.ts
@@ -227,7 +227,7 @@ describe('throws when config is invalid', () => {
beforeAll(() => {
const realFs = jest.requireActual('fs');
mockReadFileSync.mockImplementation((path: string) => realFs.readFileSync(path));
- const utils = jest.requireActual('../../utils');
+ const utils = jest.requireActual('../utils');
mockReadPkcs12Keystore.mockImplementation((path: string, password?: string) =>
utils.readPkcs12Keystore(path, password)
);
diff --git a/src/core/server/elasticsearch/elasticsearch_config.ts b/src/core/server/elasticsearch/elasticsearch_config.ts
index b2f4e388b337d..d3012e361b3ed 100644
--- a/src/core/server/elasticsearch/elasticsearch_config.ts
+++ b/src/core/server/elasticsearch/elasticsearch_config.ts
@@ -21,7 +21,7 @@ import { schema, TypeOf } from '@kbn/config-schema';
import { Duration } from 'moment';
import { readFileSync } from 'fs';
import { ConfigDeprecationProvider } from 'src/core/server';
-import { readPkcs12Keystore, readPkcs12Truststore } from '../../utils';
+import { readPkcs12Keystore, readPkcs12Truststore } from '../utils';
import { ServiceConfigDescriptor } from '../internal_types';
const hostURISchema = schema.uri({ scheme: ['http', 'https'] });
diff --git a/src/core/server/http/ssl_config.test.mocks.ts b/src/core/server/http/ssl_config.test.mocks.ts
index ab98c3a27920c..50e34c7c8f678 100644
--- a/src/core/server/http/ssl_config.test.mocks.ts
+++ b/src/core/server/http/ssl_config.test.mocks.ts
@@ -24,7 +24,7 @@ jest.mock('fs', () => {
export const mockReadPkcs12Keystore = jest.fn();
export const mockReadPkcs12Truststore = jest.fn();
-jest.mock('../../utils', () => ({
+jest.mock('../utils', () => ({
readPkcs12Keystore: mockReadPkcs12Keystore,
readPkcs12Truststore: mockReadPkcs12Truststore,
}));
diff --git a/src/core/server/http/ssl_config.test.ts b/src/core/server/http/ssl_config.test.ts
index 3980b9c247fa3..5d0bed601f540 100644
--- a/src/core/server/http/ssl_config.test.ts
+++ b/src/core/server/http/ssl_config.test.ts
@@ -45,7 +45,7 @@ describe('#SslConfig', () => {
beforeEach(() => {
const realFs = jest.requireActual('fs');
mockReadFileSync.mockImplementation((path: string) => realFs.readFileSync(path));
- const utils = jest.requireActual('../../utils');
+ const utils = jest.requireActual('../utils');
mockReadPkcs12Keystore.mockImplementation((path: string, password?: string) =>
utils.readPkcs12Keystore(path, password)
);
diff --git a/src/core/server/http/ssl_config.ts b/src/core/server/http/ssl_config.ts
index 0096eeb092565..4eb0c50e72362 100644
--- a/src/core/server/http/ssl_config.ts
+++ b/src/core/server/http/ssl_config.ts
@@ -20,7 +20,7 @@
import { schema, TypeOf } from '@kbn/config-schema';
import crypto from 'crypto';
import { readFileSync } from 'fs';
-import { readPkcs12Keystore, readPkcs12Truststore } from '../../utils';
+import { readPkcs12Keystore, readPkcs12Truststore } from '../utils';
// `crypto` type definitions doesn't currently include `crypto.constants`, see
// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/fa5baf1733f49cf26228a4e509914572c1b74adf/types/node/v6/index.d.ts#L3412
diff --git a/src/core/utils/crypto/__fixtures__/README.md b/src/core/server/utils/crypto/__fixtures__/README.md
similarity index 100%
rename from src/core/utils/crypto/__fixtures__/README.md
rename to src/core/server/utils/crypto/__fixtures__/README.md
diff --git a/src/core/utils/crypto/__fixtures__/index.ts b/src/core/server/utils/crypto/__fixtures__/index.ts
similarity index 100%
rename from src/core/utils/crypto/__fixtures__/index.ts
rename to src/core/server/utils/crypto/__fixtures__/index.ts
diff --git a/src/core/utils/crypto/__fixtures__/no_ca.p12 b/src/core/server/utils/crypto/__fixtures__/no_ca.p12
similarity index 100%
rename from src/core/utils/crypto/__fixtures__/no_ca.p12
rename to src/core/server/utils/crypto/__fixtures__/no_ca.p12
diff --git a/src/core/utils/crypto/__fixtures__/no_cert.p12 b/src/core/server/utils/crypto/__fixtures__/no_cert.p12
similarity index 100%
rename from src/core/utils/crypto/__fixtures__/no_cert.p12
rename to src/core/server/utils/crypto/__fixtures__/no_cert.p12
diff --git a/src/core/utils/crypto/__fixtures__/no_key.p12 b/src/core/server/utils/crypto/__fixtures__/no_key.p12
similarity index 100%
rename from src/core/utils/crypto/__fixtures__/no_key.p12
rename to src/core/server/utils/crypto/__fixtures__/no_key.p12
diff --git a/src/core/utils/crypto/__fixtures__/two_cas.p12 b/src/core/server/utils/crypto/__fixtures__/two_cas.p12
similarity index 100%
rename from src/core/utils/crypto/__fixtures__/two_cas.p12
rename to src/core/server/utils/crypto/__fixtures__/two_cas.p12
diff --git a/src/core/utils/crypto/__fixtures__/two_keys.p12 b/src/core/server/utils/crypto/__fixtures__/two_keys.p12
similarity index 100%
rename from src/core/utils/crypto/__fixtures__/two_keys.p12
rename to src/core/server/utils/crypto/__fixtures__/two_keys.p12
diff --git a/src/core/utils/crypto/index.ts b/src/core/server/utils/crypto/index.ts
similarity index 100%
rename from src/core/utils/crypto/index.ts
rename to src/core/server/utils/crypto/index.ts
diff --git a/src/core/utils/crypto/pkcs12.test.ts b/src/core/server/utils/crypto/pkcs12.test.ts
similarity index 99%
rename from src/core/utils/crypto/pkcs12.test.ts
rename to src/core/server/utils/crypto/pkcs12.test.ts
index c6c28697c4bcc..e9eb72fe395ff 100644
--- a/src/core/utils/crypto/pkcs12.test.ts
+++ b/src/core/server/utils/crypto/pkcs12.test.ts
@@ -29,7 +29,7 @@ import {
import { NO_CA_PATH, NO_CERT_PATH, NO_KEY_PATH, TWO_CAS_PATH, TWO_KEYS_PATH } from './__fixtures__';
import { readFileSync } from 'fs';
-import { readPkcs12Keystore, Pkcs12ReadResult, readPkcs12Truststore } from '.';
+import { readPkcs12Keystore, Pkcs12ReadResult, readPkcs12Truststore } from './index';
const reformatPem = (pem: string) => {
// ensure consistency in line endings when comparing two PEM files
diff --git a/src/core/utils/crypto/pkcs12.ts b/src/core/server/utils/crypto/pkcs12.ts
similarity index 100%
rename from src/core/utils/crypto/pkcs12.ts
rename to src/core/server/utils/crypto/pkcs12.ts
diff --git a/src/core/server/utils/index.ts b/src/core/server/utils/index.ts
index 86924c559e5fa..b01a4c4e04899 100644
--- a/src/core/server/utils/index.ts
+++ b/src/core/server/utils/index.ts
@@ -17,5 +17,6 @@
* under the License.
*/
+export * from './crypto';
export * from './from_root';
export * from './package_json';
diff --git a/src/core/utils/index.ts b/src/core/utils/index.ts
index e35356343cfe2..a6df0992f6cc6 100644
--- a/src/core/utils/index.ts
+++ b/src/core/utils/index.ts
@@ -19,7 +19,6 @@
export * from './assert_never';
export * from './context';
-export * from './crypto';
export * from './deep_freeze';
export * from './get';
export * from './map_to_object';
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx
index a39266ecd8db3..5f4c7da51533f 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx
+++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx
@@ -48,7 +48,6 @@ import {
import {
DASHBOARD_CONTAINER_TYPE,
DashboardContainer,
- DashboardContainerFactory,
DashboardContainerInput,
DashboardPanelState,
} from '../../../../../../plugins/dashboard/public';
@@ -58,6 +57,7 @@ import {
isErrorEmbeddable,
openAddPanelFlyout,
ViewMode,
+ ContainerOutput,
} from '../../../../../../plugins/embeddable/public';
import { NavAction, SavedDashboardPanel } from './types';
@@ -307,83 +307,92 @@ export class DashboardAppController {
let outputSubscription: Subscription | undefined;
const dashboardDom = document.getElementById('dashboardViewport');
- const dashboardFactory = embeddable.getEmbeddableFactory(
- DASHBOARD_CONTAINER_TYPE
- ) as DashboardContainerFactory;
- dashboardFactory
- .create(getDashboardInput())
- .then((container: DashboardContainer | ErrorEmbeddable) => {
- if (!isErrorEmbeddable(container)) {
- dashboardContainer = container;
-
- dashboardContainer.renderEmpty = () => {
- const shouldShowEditHelp = getShouldShowEditHelp();
- const shouldShowViewHelp = getShouldShowViewHelp();
- const isEmptyInReadOnlyMode = shouldShowUnauthorizedEmptyState();
- const isEmptyState = shouldShowEditHelp || shouldShowViewHelp || isEmptyInReadOnlyMode;
- return isEmptyState ? (
-
- ) : null;
- };
-
- updateIndexPatterns(dashboardContainer);
-
- outputSubscription = dashboardContainer.getOutput$().subscribe(() => {
- updateIndexPatterns(dashboardContainer);
- });
-
- inputSubscription = dashboardContainer.getInput$().subscribe(() => {
- let dirty = false;
+ const dashboardFactory = embeddable.getEmbeddableFactory<
+ DashboardContainerInput,
+ ContainerOutput,
+ DashboardContainer
+ >(DASHBOARD_CONTAINER_TYPE);
+
+ if (dashboardFactory) {
+ dashboardFactory
+ .create(getDashboardInput())
+ .then((container: DashboardContainer | ErrorEmbeddable | undefined) => {
+ if (container && !isErrorEmbeddable(container)) {
+ dashboardContainer = container;
+
+ dashboardContainer.renderEmpty = () => {
+ const shouldShowEditHelp = getShouldShowEditHelp();
+ const shouldShowViewHelp = getShouldShowViewHelp();
+ const isEmptyInReadOnlyMode = shouldShowUnauthorizedEmptyState();
+ const isEmptyState =
+ shouldShowEditHelp || shouldShowViewHelp || isEmptyInReadOnlyMode;
+ return isEmptyState ? (
+
+ ) : null;
+ };
- // This has to be first because handleDashboardContainerChanges causes
- // appState.save which will cause refreshDashboardContainer to be called.
-
- if (
- !esFilters.compareFilters(
- container.getInput().filters,
- queryFilter.getFilters(),
- esFilters.COMPARE_ALL_OPTIONS
- )
- ) {
- // Add filters modifies the object passed to it, hence the clone deep.
- queryFilter.addFilters(_.cloneDeep(container.getInput().filters));
+ updateIndexPatterns(dashboardContainer);
- dashboardStateManager.applyFilters($scope.model.query, container.getInput().filters);
- dirty = true;
- }
+ outputSubscription = dashboardContainer.getOutput$().subscribe(() => {
+ updateIndexPatterns(dashboardContainer);
+ });
- dashboardStateManager.handleDashboardContainerChanges(container);
- $scope.$evalAsync(() => {
- if (dirty) {
- updateState();
+ inputSubscription = dashboardContainer.getInput$().subscribe(() => {
+ let dirty = false;
+
+ // This has to be first because handleDashboardContainerChanges causes
+ // appState.save which will cause refreshDashboardContainer to be called.
+
+ if (
+ !esFilters.compareFilters(
+ container.getInput().filters,
+ queryFilter.getFilters(),
+ esFilters.COMPARE_ALL_OPTIONS
+ )
+ ) {
+ // Add filters modifies the object passed to it, hence the clone deep.
+ queryFilter.addFilters(_.cloneDeep(container.getInput().filters));
+
+ dashboardStateManager.applyFilters(
+ $scope.model.query,
+ container.getInput().filters
+ );
+ dirty = true;
}
+
+ dashboardStateManager.handleDashboardContainerChanges(container);
+ $scope.$evalAsync(() => {
+ if (dirty) {
+ updateState();
+ }
+ });
});
- });
- dashboardStateManager.registerChangeListener(() => {
- // we aren't checking dirty state because there are changes the container needs to know about
- // that won't make the dashboard "dirty" - like a view mode change.
- refreshDashboardContainer();
- });
+ dashboardStateManager.registerChangeListener(() => {
+ // we aren't checking dirty state because there are changes the container needs to know about
+ // that won't make the dashboard "dirty" - like a view mode change.
+ refreshDashboardContainer();
+ });
- // This code needs to be replaced with a better mechanism for adding new embeddables of
- // any type from the add panel. Likely this will happen via creating a visualization "inline",
- // without navigating away from the UX.
- if ($routeParams[DashboardConstants.ADD_EMBEDDABLE_TYPE]) {
- const type = $routeParams[DashboardConstants.ADD_EMBEDDABLE_TYPE];
- const id = $routeParams[DashboardConstants.ADD_EMBEDDABLE_ID];
- container.addSavedObjectEmbeddable(type, id);
- removeQueryParam(history, DashboardConstants.ADD_EMBEDDABLE_TYPE);
- removeQueryParam(history, DashboardConstants.ADD_EMBEDDABLE_ID);
+ // This code needs to be replaced with a better mechanism for adding new embeddables of
+ // any type from the add panel. Likely this will happen via creating a visualization "inline",
+ // without navigating away from the UX.
+ if ($routeParams[DashboardConstants.ADD_EMBEDDABLE_TYPE]) {
+ const type = $routeParams[DashboardConstants.ADD_EMBEDDABLE_TYPE];
+ const id = $routeParams[DashboardConstants.ADD_EMBEDDABLE_ID];
+ container.addSavedObjectEmbeddable(type, id);
+ removeQueryParam(history, DashboardConstants.ADD_EMBEDDABLE_TYPE);
+ removeQueryParam(history, DashboardConstants.ADD_EMBEDDABLE_ID);
+ }
}
- }
- if (dashboardDom) {
- container.render(dashboardDom);
- }
- });
+ if (dashboardDom && container) {
+ container.render(dashboardDom);
+ }
+ });
+ }
// Part of the exposed plugin API - do not remove without careful consideration.
this.appStatus = {
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_embeddable_factory.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_embeddable_factory.ts
index 6f3adc1f4fcce..ad61984a52536 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_embeddable_factory.ts
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_embeddable_factory.ts
@@ -22,9 +22,9 @@ import { i18n } from '@kbn/i18n';
import { UiActionsStart } from 'src/plugins/ui_actions/public';
import { getServices } from '../../kibana_services';
import {
- EmbeddableFactory,
- ErrorEmbeddable,
+ EmbeddableFactoryDefinition,
Container,
+ ErrorEmbeddable,
} from '../../../../../../../plugins/embeddable/public';
import { TimeRange } from '../../../../../../../plugins/data/public';
@@ -37,28 +37,23 @@ interface StartServices {
isEditable: () => boolean;
}
-export class SearchEmbeddableFactory extends EmbeddableFactory<
- SearchInput,
- SearchOutput,
- SearchEmbeddable
-> {
+export class SearchEmbeddableFactory
+ implements EmbeddableFactoryDefinition {
public readonly type = SEARCH_EMBEDDABLE_TYPE;
private $injector: auto.IInjectorService | null;
private getInjector: () => Promise | null;
+ public readonly savedObjectMetaData = {
+ name: i18n.translate('kbn.discover.savedSearch.savedObjectName', {
+ defaultMessage: 'Saved search',
+ }),
+ type: 'search',
+ getIconForSavedObject: () => 'search',
+ };
constructor(
private getStartServices: () => Promise,
getInjector: () => Promise
) {
- super({
- savedObjectMetaData: {
- name: i18n.translate('kbn.discover.savedSearch.savedObjectName', {
- defaultMessage: 'Saved search',
- }),
- type: 'search',
- getIconForSavedObject: () => 'search',
- },
- });
this.$injector = null;
this.getInjector = getInjector;
}
@@ -67,9 +62,9 @@ export class SearchEmbeddableFactory extends EmbeddableFactory<
return false;
}
- public async isEditable() {
+ public isEditable = async () => {
return (await this.getStartServices()).isEditable();
- }
+ };
public getDisplayName() {
return i18n.translate('kbn.embeddable.search.displayName', {
@@ -77,11 +72,11 @@ export class SearchEmbeddableFactory extends EmbeddableFactory<
});
}
- public async createFromSavedObject(
+ public createFromSavedObject = async (
savedObjectId: string,
input: Partial & { id: string; timeRange: TimeRange },
parent?: Container
- ): Promise {
+ ): Promise => {
if (!this.$injector) {
this.$injector = await this.getInjector();
}
@@ -115,7 +110,7 @@ export class SearchEmbeddableFactory extends EmbeddableFactory<
console.error(e); // eslint-disable-line no-console
return new ErrorEmbeddable(e, input, parent);
}
- }
+ };
public async create(input: SearchInput) {
return new ErrorEmbeddable('Saved searches can only be created from a saved object', input);
diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts
index 2ed7e3d43168c..addc608efd57d 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts
+++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts
@@ -61,6 +61,7 @@ export interface VisualizeKibanaServices {
I18nContext: I18nStart['Context'];
setActiveUrl: (newUrl: string) => void;
DefaultVisualizationEditor: typeof DefaultEditorController;
+ createVisEmbeddableFromObject: VisualizationsStart['__LEGACY']['createVisEmbeddableFromObject'];
}
let services: VisualizeKibanaServices | null = null;
diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js
index d1bf4411cac2a..7c9ab32ab2f72 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js
+++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js
@@ -43,7 +43,7 @@ import {
import { createSavedSearchesLoader } from '../../../../../../plugins/discover/public';
const getResolvedResults = deps => {
- const { core, data, visualizations } = deps;
+ const { core, data, visualizations, createVisEmbeddableFromObject } = deps;
const results = {};
@@ -60,7 +60,7 @@ const getResolvedResults = deps => {
})
.then(vis => {
results.vis = vis;
- return deps.embeddable.getEmbeddableFactory('visualization').createFromObject(results.vis, {
+ return createVisEmbeddableFromObject(vis, {
timeRange: data.query.timefilter.timefilter.getTime(),
filters: data.query.filterManager.getFilters(),
});
diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts
index 6d32579f5c541..a14c4a44f1c7c 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts
+++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts
@@ -156,6 +156,7 @@ export class VisualizePlugin implements Plugin {
I18nContext: coreStart.i18n.Context,
setActiveUrl,
DefaultVisualizationEditor: DefaultEditorController,
+ createVisEmbeddableFromObject: visualizations.__LEGACY.createVisEmbeddableFromObject,
};
setServices(deps);
diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_group.test.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/agg_group.test.tsx
index 5d02f0a2c759e..483446daed10f 100644
--- a/src/legacy/core_plugins/vis_default_editor/public/components/agg_group.test.tsx
+++ b/src/legacy/core_plugins/vis_default_editor/public/components/agg_group.test.tsx
@@ -24,7 +24,7 @@ import { IAggConfigs, IAggConfig } from 'src/plugins/data/public';
import { DefaultEditorAggGroup, DefaultEditorAggGroupProps } from './agg_group';
import { DefaultEditorAgg } from './agg';
import { DefaultEditorAggAdd } from './agg_add';
-import { Schema } from '../schemas';
+import { ISchemas, Schemas } from '../schemas';
import { EditorVisState } from './sidebar/state/reducers';
jest.mock('@elastic/eui', () => ({
@@ -47,6 +47,7 @@ jest.mock('./agg_add', () => ({
describe('DefaultEditorAgg component', () => {
let defaultProps: DefaultEditorAggGroupProps;
let aggs: IAggConfigs;
+ let schemas: ISchemas;
let setTouched: jest.Mock;
let setValidity: jest.Mock;
let reorderAggs: jest.Mock;
@@ -55,6 +56,18 @@ describe('DefaultEditorAgg component', () => {
setTouched = jest.fn();
setValidity = jest.fn();
reorderAggs = jest.fn();
+ schemas = new Schemas([
+ {
+ name: 'metrics',
+ group: 'metrics',
+ max: 1,
+ },
+ {
+ name: 'buckets',
+ group: 'buckets',
+ max: 1,
+ },
+ ]);
aggs = {
aggs: [
@@ -95,18 +108,7 @@ describe('DefaultEditorAgg component', () => {
state: {
data: { aggs },
} as EditorVisState,
- schemas: [
- {
- name: 'metrics',
- group: 'metrics',
- max: 1,
- } as Schema,
- {
- name: 'buckets',
- group: 'buckets',
- max: 1,
- } as Schema,
- ],
+ schemas: schemas.metrics,
setTouched,
setValidity,
reorderAggs,
@@ -133,6 +135,7 @@ describe('DefaultEditorAgg component', () => {
it('should last bucket has truthy isLastBucket prop', () => {
defaultProps.groupName = 'buckets';
+ defaultProps.schemas = schemas.buckets;
const comp = mount();
const lastAgg = comp.find(DefaultEditorAgg).last();
@@ -154,6 +157,8 @@ describe('DefaultEditorAgg component', () => {
it('should show add button when schemas count is less than max', () => {
defaultProps.groupName = 'buckets';
+ defaultProps.schemas = schemas.buckets;
+ defaultProps.schemas[0].max = 2;
const comp = shallow();
expect(comp.find(DefaultEditorAggAdd).exists()).toBeTruthy();
diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_group.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/agg_group.tsx
index f50abc3ebb599..792595fd421f6 100644
--- a/src/legacy/core_plugins/vis_default_editor/public/components/agg_group.tsx
+++ b/src/legacy/core_plugins/vis_default_editor/public/components/agg_group.tsx
@@ -41,7 +41,7 @@ import {
getEnabledMetricAggsCount,
} from './agg_group_helper';
import { aggGroupReducer, initAggsState, AGGS_ACTION_KEYS } from './agg_group_state';
-import { Schema, getSchemasByGroup } from '../schemas';
+import { Schema } from '../schemas';
import { TimeRange } from '../../../../../plugins/data/public';
export interface DefaultEditorAggGroupProps extends DefaultEditorAggCommonProps {
@@ -73,7 +73,7 @@ function DefaultEditorAggGroup({
}: DefaultEditorAggGroupProps) {
const groupNameLabel = (search.aggs.aggGroupNamesMap() as any)[groupName];
// e.g. buckets can have no aggs
- const schemaNames = getSchemasByGroup(schemas, groupName).map(s => s.name);
+ const schemaNames = schemas.map(s => s.name);
const group: IAggConfig[] = useMemo(
() =>
state.data.aggs!.aggs.filter(
diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/date_ranges.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/controls/date_ranges.tsx
index ca4a9315d6bfb..15e864bfd026d 100644
--- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/date_ranges.tsx
+++ b/src/legacy/core_plugins/vis_default_editor/public/components/controls/date_ranges.tsx
@@ -125,7 +125,7 @@ function DateRangesParamEditor({
- {ranges.map(({ from, to, id }) => {
+ {ranges.map(({ from, to, id }, index) => {
const deleteBtnTitle = i18n.translate(
'visDefaultEditor.controls.dateRanges.removeRangeButtonAriaLabel',
{
@@ -154,6 +154,7 @@ function DateRangesParamEditor({
placeholder={FROM_PLACEHOLDER}
value={from || ''}
onChange={ev => onChangeRange(id, 'from', ev.target.value)}
+ data-test-subj={`visEditorDateRange${index}__from`}
/>
@@ -168,6 +169,7 @@ function DateRangesParamEditor({
description: 'End of a date range, e.g. From 2018-02-26 *To* 2018-02-28',
}
)}
+ data-test-subj={`visEditorDateRange${index}__to`}
compressed
fullWidth={true}
isInvalid={areBothEmpty || !validateDateMath(to)}
@@ -203,7 +205,12 @@ function DateRangesParamEditor({
-
+
(state.data.aggs ? state.data.aggs.getResponseAggs() : []), [
state.data.aggs,
]);
- const metricSchemas = getSchemasByGroup(vis.type.schemas.all || [], AggGroupNames.Metrics).map(
- s => s.name
- );
+ const metricSchemas = (vis.type.schemas.metrics || []).map((s: Schema) => s.name);
const metricAggs = useMemo(
() => responseAggs.filter(agg => metricSchemas.includes(get(agg, 'schema'))),
[responseAggs, metricSchemas]
diff --git a/src/legacy/core_plugins/vis_default_editor/public/schemas.ts b/src/legacy/core_plugins/vis_default_editor/public/schemas.ts
index 94e3ad6023f4e..4e632da44afc0 100644
--- a/src/legacy/core_plugins/vis_default_editor/public/schemas.ts
+++ b/src/legacy/core_plugins/vis_default_editor/public/schemas.ts
@@ -17,11 +17,10 @@
* under the License.
*/
-import _ from 'lodash';
+import _, { defaults } from 'lodash';
import { Optional } from '@kbn/utility-types';
-import { IndexedArray } from 'ui/indexed_array';
import { AggGroupNames, AggParam, IAggGroupNames } from '../../../../plugins/data/public';
export interface ISchemas {
@@ -45,9 +44,10 @@ export interface Schema {
aggSettings?: any;
}
-export class Schemas {
- // @ts-ignore
- all: IndexedArray;
+export class Schemas implements ISchemas {
+ all: Schema[] = [];
+ [AggGroupNames.Buckets]: Schema[] = [];
+ [AggGroupNames.Metrics]: Schema[] = [];
constructor(
schemas: Array<
@@ -70,7 +70,7 @@ export class Schemas {
] as AggParam[];
}
- _.defaults(schema, {
+ defaults(schema, {
min: 0,
max: Infinity,
group: AggGroupNames.Buckets,
@@ -83,22 +83,12 @@ export class Schemas {
return schema as Schema;
})
.tap((fullSchemas: Schema[]) => {
- this.all = new IndexedArray({
- index: ['name'],
- group: ['group'],
- immutable: true,
- initialSet: fullSchemas,
- });
+ this.all = fullSchemas;
})
.groupBy('group')
.forOwn((group, groupName) => {
// @ts-ignore
- this[groupName] = new IndexedArray({
- index: ['name'],
- immutable: true,
- // @ts-ignore
- initialSet: group,
- });
+ this[groupName] = group;
})
.commit();
}
@@ -107,7 +97,3 @@ export class Schemas {
export const getSchemaByName = (schemas: Schema[], schemaName?: string) => {
return schemas.find(s => s.name === schemaName) || ({} as Schema);
};
-
-export const getSchemasByGroup = (schemas: Schema[], schemaGroup?: string) => {
- return schemas.filter(s => s.group === schemaGroup);
-};
diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/data.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/data.js
index 2df157b6f121d..2adabd4d315b4 100644
--- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/data.js
+++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/data.js
@@ -25,6 +25,26 @@ import { orderXValues } from '../components/zero_injection/ordered_x_keys';
import { labels } from '../components/labels/labels';
import { getFormatService } from '../../services';
+// X axis and split series values in a data table can sometimes be objects,
+// e.g. when working with date ranges. d3 casts all ordinal values to strings
+// which is a problem for these objects because they just return `[object Object]`
+// and thus all map to the same value.
+// This little helper overwrites the toString method of an object and keeps it the
+// same otherwise - allowing d3 to correctly work with the values.
+class D3MappableObject {
+ constructor(data) {
+ for (const key in data) {
+ if (data.hasOwnProperty(key)) {
+ this[key] = data[key];
+ }
+ }
+ }
+
+ toString() {
+ return JSON.stringify(this);
+ }
+}
+
/**
* Provides an API for pulling values off the data
* and calculating values using the data
@@ -52,9 +72,14 @@ export class Data {
const copyChart = data => {
const newData = {};
Object.keys(data).forEach(key => {
- if (key !== 'series') {
- newData[key] = data[key];
- } else {
+ if (key === 'xAxisOrderedValues') {
+ newData[key] = data[key].map(val => {
+ if (typeof val === 'object') {
+ return new D3MappableObject(val);
+ }
+ return val;
+ });
+ } else if (key === 'series') {
newData[key] = data[key].map(seri => {
const converter = getFormatService().deserialize(seri.format);
const zConverter = getFormatService().deserialize(seri.zFormat);
@@ -67,12 +92,17 @@ export class Data {
const newVal = _.clone(val);
newVal.extraMetrics = val.extraMetrics;
newVal.series = val.series || seri.label;
+ if (typeof newVal.x === 'object') {
+ newVal.x = new D3MappableObject(newVal.x);
+ }
return newVal;
}),
yAxisFormatter: val => converter.convert(val),
zAxisFormatter: val => zConverter.convert(val),
};
});
+ } else {
+ newData[key] = data[key];
}
});
diff --git a/src/legacy/server/index_patterns/index.ts b/src/legacy/server/index_patterns/index.ts
deleted file mode 100644
index 75d0038cf9023..0000000000000
--- a/src/legacy/server/index_patterns/index.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export { indexPatternsMixin } from './mixin';
-export {
- IndexPatternsFetcher,
- FieldDescriptor,
-} from '../../../plugins/data/server/index_patterns/fetcher';
-export { IndexPatternsServiceFactory } from './mixin';
diff --git a/src/legacy/server/index_patterns/mixin.ts b/src/legacy/server/index_patterns/mixin.ts
deleted file mode 100644
index 6b04c3842007b..0000000000000
--- a/src/legacy/server/index_patterns/mixin.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { IndexPatternsFetcher } from '../../../plugins/data/server';
-import KbnServer from '../kbn_server';
-import { APICaller, CallAPIOptions } from '../../../core/server';
-import { Legacy } from '../../../../kibana';
-
-export function indexPatternsMixin(kbnServer: KbnServer, server: Legacy.Server) {
- /**
- * Create an instance of the IndexPatternsService
- *
- * @method server.indexPatternsServiceFactory
- * @type {IndexPatternsService}
- */
- server.decorate('server', 'indexPatternsServiceFactory', ({ callCluster }) => {
- return new IndexPatternsFetcher(callCluster);
- });
-
- /**
- * Get an instance of the IndexPatternsService configured for use
- * the current request
- *
- * @method request.getIndexPatternsService
- * @type {IndexPatternsService}
- */
- server.addMemoizedFactoryToRequest('getIndexPatternsService', (request: Legacy.Request) => {
- const { callWithRequest } = request.server.plugins.elasticsearch.getCluster('data');
- const callCluster: APICaller = (
- endpoint: string,
- params?: Record,
- options?: CallAPIOptions
- ) => callWithRequest(request, endpoint, params, options);
- return server.indexPatternsServiceFactory({ callCluster });
- });
-}
-
-export type IndexPatternsServiceFactory = (args: {
- callCluster: (endpoint: string, clientParams: any, options: any) => Promise;
-}) => IndexPatternsFetcher;
diff --git a/src/legacy/server/kbn_server.d.ts b/src/legacy/server/kbn_server.d.ts
index d43ddf581da90..a9b8c29374854 100644
--- a/src/legacy/server/kbn_server.d.ts
+++ b/src/legacy/server/kbn_server.d.ts
@@ -44,7 +44,6 @@ import { LegacyConfig, ILegacyService, ILegacyInternals } from '../../core/serve
import { ApmOssPlugin } from '../core_plugins/apm_oss';
import { CallClusterWithRequest, ElasticsearchPlugin } from '../core_plugins/elasticsearch';
import { UsageCollectionSetup } from '../../plugins/usage_collection/server';
-import { IndexPatternsServiceFactory } from './index_patterns';
import { Capabilities } from '../../core/server';
import { UiSettingsServiceFactoryOptions } from '../../legacy/ui/ui_settings/ui_settings_service_factory';
import { HomeServerPluginSetup } from '../../plugins/home/server';
@@ -68,7 +67,6 @@ declare module 'hapi' {
interface Server {
config: () => KibanaConfig;
- indexPatternsServiceFactory: IndexPatternsServiceFactory;
savedObjects: SavedObjectsLegacyService;
injectUiAppVars: (pluginName: string, getAppVars: () => { [key: string]: any }) => void;
getHiddenUiAppById(appId: string): UiApp;
@@ -175,5 +173,4 @@ export default class KbnServer {
export { Server, Request, ResponseToolkit } from 'hapi';
// Re-export commonly accessed api types.
-export { IndexPatternsFetcher as IndexPatternsService } from './index_patterns';
export { SavedObjectsLegacyService, SavedObjectsClient } from 'src/core/server';
diff --git a/src/legacy/server/kbn_server.js b/src/legacy/server/kbn_server.js
index e06212d87e3e3..1168d24254911 100644
--- a/src/legacy/server/kbn_server.js
+++ b/src/legacy/server/kbn_server.js
@@ -33,7 +33,6 @@ import pidMixin from './pid';
import configCompleteMixin from './config/complete';
import optimizeMixin from '../../optimize';
import * as Plugins from './plugins';
-import { indexPatternsMixin } from './index_patterns';
import { savedObjectsMixin } from './saved_objects/saved_objects_mixin';
import { capabilitiesMixin } from './capabilities';
import { serverExtensionsMixin } from './server_extensions';
@@ -114,7 +113,6 @@ export default class KbnServer {
// setup this.uiBundles
uiMixin,
- indexPatternsMixin,
// setup saved object routes
savedObjectsMixin,
diff --git a/src/legacy/server/saved_objects/saved_objects_mixin.test.js b/src/legacy/server/saved_objects/saved_objects_mixin.test.js
index 3fa9f9a936988..d49b18ee2ce6c 100644
--- a/src/legacy/server/saved_objects/saved_objects_mixin.test.js
+++ b/src/legacy/server/saved_objects/saved_objects_mixin.test.js
@@ -118,11 +118,6 @@ describe('Saved Objects Mixin', () => {
get: stubConfig,
};
},
- indexPatternsServiceFactory: () => {
- return {
- getFieldsForWildcard: jest.fn(),
- };
- },
plugins: {
elasticsearch: {
getCluster: () => {
diff --git a/src/plugins/dashboard/public/actions/expand_panel_action.test.tsx b/src/plugins/dashboard/public/actions/expand_panel_action.test.tsx
index 22cf854a46623..e9696938b8629 100644
--- a/src/plugins/dashboard/public/actions/expand_panel_action.test.tsx
+++ b/src/plugins/dashboard/public/actions/expand_panel_action.test.tsx
@@ -17,7 +17,7 @@
* under the License.
*/
-import { isErrorEmbeddable, EmbeddableFactory } from '../embeddable_plugin';
+import { isErrorEmbeddable } from '../embeddable_plugin';
import { ExpandPanelAction } from './expand_panel_action';
import { DashboardContainer } from '../embeddable';
import { getSampleDashboardInput, getSampleDashboardPanel } from '../test_helpers';
@@ -29,11 +29,16 @@ import {
ContactCardEmbeddableOutput,
} from '../embeddable_plugin_test_samples';
-const embeddableFactories = new Map();
-embeddableFactories.set(
+// eslint-disable-next-line
+import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks';
+
+const { setup, doStart } = embeddablePluginMock.createInstance();
+
+setup.registerEmbeddableFactory(
CONTACT_CARD_EMBEDDABLE,
- new ContactCardEmbeddableFactory({} as any, (() => null) as any, {} as any)
+ new ContactCardEmbeddableFactory((() => null) as any, {} as any)
);
+const start = doStart();
let container: DashboardContainer;
let embeddable: ContactCardEmbeddable;
@@ -43,9 +48,7 @@ beforeEach(async () => {
ExitFullScreenButton: () => null,
SavedObjectFinder: () => null,
application: {} as any,
- embeddable: {
- getEmbeddableFactory: (id: string) => embeddableFactories.get(id)!,
- } as any,
+ embeddable: start,
inspector: {} as any,
notifications: {} as any,
overlays: {} as any,
diff --git a/src/plugins/dashboard/public/actions/replace_panel_action.test.tsx b/src/plugins/dashboard/public/actions/replace_panel_action.test.tsx
index 69346dc8c118a..2252928f46f6a 100644
--- a/src/plugins/dashboard/public/actions/replace_panel_action.test.tsx
+++ b/src/plugins/dashboard/public/actions/replace_panel_action.test.tsx
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { isErrorEmbeddable, EmbeddableFactory } from '../embeddable_plugin';
+import { isErrorEmbeddable } from '../embeddable_plugin';
import { ReplacePanelAction } from './replace_panel_action';
import { DashboardContainer } from '../embeddable';
import { getSampleDashboardInput, getSampleDashboardPanel } from '../test_helpers';
@@ -30,12 +30,15 @@ import {
import { coreMock } from '../../../../core/public/mocks';
import { CoreStart } from 'kibana/public';
-const embeddableFactories = new Map();
-embeddableFactories.set(
+// eslint-disable-next-line
+import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks';
+
+const { setup, doStart } = embeddablePluginMock.createInstance();
+setup.registerEmbeddableFactory(
CONTACT_CARD_EMBEDDABLE,
- new ContactCardEmbeddableFactory({} as any, (() => null) as any, {} as any)
+ new ContactCardEmbeddableFactory((() => null) as any, {} as any)
);
-const getEmbeddableFactories = () => embeddableFactories.values();
+const start = doStart();
let container: DashboardContainer;
let embeddable: ContactCardEmbeddable;
@@ -46,9 +49,7 @@ beforeEach(async () => {
ExitFullScreenButton: () => null,
SavedObjectFinder: () => null,
application: {} as any,
- embeddable: {
- getEmbeddableFactory: (id: string) => embeddableFactories.get(id)!,
- } as any,
+ embeddable: start,
inspector: {} as any,
notifications: {} as any,
overlays: coreStart.overlays,
@@ -87,7 +88,7 @@ test('Executes the replace panel action', async () => {
coreStart,
SavedObjectFinder,
notifications,
- getEmbeddableFactories
+ start.getEmbeddableFactories
);
action.execute({ embeddable });
});
@@ -99,7 +100,7 @@ test('Is not compatible when embeddable is not in a dashboard container', async
coreStart,
SavedObjectFinder,
notifications,
- getEmbeddableFactories
+ start.getEmbeddableFactories
);
expect(
await action.isCompatible({
@@ -118,7 +119,7 @@ test('Execute throws an error when called with an embeddable not in a parent', a
coreStart,
SavedObjectFinder,
notifications,
- getEmbeddableFactories
+ start.getEmbeddableFactories
);
async function check() {
await action.execute({ embeddable: container });
@@ -133,7 +134,7 @@ test('Returns title', async () => {
coreStart,
SavedObjectFinder,
notifications,
- getEmbeddableFactories
+ start.getEmbeddableFactories
);
expect(action.getDisplayName({ embeddable })).toBeDefined();
});
@@ -145,7 +146,7 @@ test('Returns an icon', async () => {
coreStart,
SavedObjectFinder,
notifications,
- getEmbeddableFactories
+ start.getEmbeddableFactories
);
expect(action.getIconType({ embeddable })).toBeDefined();
});
diff --git a/src/plugins/dashboard/public/embeddable/dashboard_container.test.tsx b/src/plugins/dashboard/public/embeddable/dashboard_container.test.tsx
index 770c46c62e42f..6a734cb68fd9c 100644
--- a/src/plugins/dashboard/public/embeddable/dashboard_container.test.tsx
+++ b/src/plugins/dashboard/public/embeddable/dashboard_container.test.tsx
@@ -20,7 +20,7 @@
// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
import { nextTick } from 'test_utils/enzyme_helpers';
-import { isErrorEmbeddable, ViewMode, EmbeddableFactory } from '../embeddable_plugin';
+import { isErrorEmbeddable, ViewMode } from '../embeddable_plugin';
import { DashboardContainer, DashboardContainerOptions } from './dashboard_container';
import { getSampleDashboardInput, getSampleDashboardPanel } from '../test_helpers';
import {
@@ -30,14 +30,12 @@ import {
ContactCardEmbeddable,
ContactCardEmbeddableOutput,
} from '../embeddable_plugin_test_samples';
+// eslint-disable-next-line
+import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks';
const options: DashboardContainerOptions = {
application: {} as any,
- embeddable: {
- getTriggerCompatibleActions: (() => []) as any,
- getEmbeddableFactories: (() => []) as any,
- getEmbeddableFactory: undefined as any,
- } as any,
+ embeddable: {} as any,
notifications: {} as any,
overlays: {} as any,
inspector: {} as any,
@@ -47,12 +45,12 @@ const options: DashboardContainerOptions = {
};
beforeEach(() => {
- const embeddableFactories = new Map();
- embeddableFactories.set(
+ const { setup, doStart } = embeddablePluginMock.createInstance();
+ setup.registerEmbeddableFactory(
CONTACT_CARD_EMBEDDABLE,
- new ContactCardEmbeddableFactory({} as any, (() => null) as any, {} as any)
+ new ContactCardEmbeddableFactory((() => null) as any, {} as any)
);
- options.embeddable.getEmbeddableFactory = (id: string) => embeddableFactories.get(id) as any;
+ options.embeddable = doStart();
});
test('DashboardContainer initializes embeddables', async done => {
diff --git a/src/plugins/dashboard/public/embeddable/dashboard_container_factory.tsx b/src/plugins/dashboard/public/embeddable/dashboard_container_factory.tsx
index 0fa62fc875603..9ff48cb45adfd 100644
--- a/src/plugins/dashboard/public/embeddable/dashboard_container_factory.tsx
+++ b/src/plugins/dashboard/public/embeddable/dashboard_container_factory.tsx
@@ -23,7 +23,7 @@ import { EmbeddableStart } from '../../../../../src/plugins/embeddable/public';
import { CoreStart } from '../../../../core/public';
import {
ContainerOutput,
- EmbeddableFactory,
+ EmbeddableFactoryDefinition,
ErrorEmbeddable,
Container,
} from '../embeddable_plugin';
@@ -43,27 +43,24 @@ interface StartServices {
uiActions: UiActionsStart;
}
-export class DashboardContainerFactory extends EmbeddableFactory<
- DashboardContainerInput,
- ContainerOutput
-> {
+export class DashboardContainerFactory
+ implements
+ EmbeddableFactoryDefinition {
public readonly isContainerType = true;
public readonly type = DASHBOARD_CONTAINER_TYPE;
- constructor(private readonly getStartServices: () => Promise) {
- super();
- }
+ constructor(private readonly getStartServices: () => Promise) {}
- public async isEditable() {
+ public isEditable = async () => {
const { capabilities } = await this.getStartServices();
return !!capabilities.createNew && !!capabilities.showWriteControls;
- }
+ };
- public getDisplayName() {
+ public readonly getDisplayName = () => {
return i18n.translate('dashboard.factory.displayName', {
defaultMessage: 'dashboard',
});
- }
+ };
public getDefaultInput(): Partial {
return {
@@ -73,11 +70,11 @@ export class DashboardContainerFactory extends EmbeddableFactory<
};
}
- public async create(
+ public create = async (
initialInput: DashboardContainerInput,
parent?: Container
- ): Promise {
+ ): Promise => {
const services = await this.getStartServices();
return new DashboardContainer(initialInput, services, parent);
- }
+ };
}
diff --git a/src/plugins/dashboard/public/embeddable/grid/dashboard_grid.test.tsx b/src/plugins/dashboard/public/embeddable/grid/dashboard_grid.test.tsx
index 0f1b9c6dc9307..a946c21765311 100644
--- a/src/plugins/dashboard/public/embeddable/grid/dashboard_grid.test.tsx
+++ b/src/plugins/dashboard/public/embeddable/grid/dashboard_grid.test.tsx
@@ -23,7 +23,6 @@ import sizeMe from 'react-sizeme';
import React from 'react';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
import { skip } from 'rxjs/operators';
-import { EmbeddableFactory } from '../../embeddable_plugin';
import { DashboardGrid, DashboardGridProps } from './dashboard_grid';
import { DashboardContainer, DashboardContainerOptions } from '../dashboard_container';
import { getSampleDashboardInput } from '../../test_helpers';
@@ -32,16 +31,20 @@ import {
ContactCardEmbeddableFactory,
} from '../../embeddable_plugin_test_samples';
import { KibanaContextProvider } from '../../../../kibana_react/public';
+// eslint-disable-next-line
+import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks';
let dashboardContainer: DashboardContainer | undefined;
function prepare(props?: Partial) {
- const embeddableFactories = new Map();
- embeddableFactories.set(
+ const { setup, doStart } = embeddablePluginMock.createInstance();
+ setup.registerEmbeddableFactory(
CONTACT_CARD_EMBEDDABLE,
- new ContactCardEmbeddableFactory({} as any, (() => {}) as any, {} as any)
+ new ContactCardEmbeddableFactory((() => null) as any, {} as any)
);
- const getEmbeddableFactory = (id: string) => embeddableFactories.get(id);
+ const start = doStart();
+
+ const getEmbeddableFactory = start.getEmbeddableFactory;
const initialInput = getSampleDashboardInput({
panels: {
'1': {
@@ -60,7 +63,7 @@ function prepare(props?: Partial) {
application: {} as any,
embeddable: {
getTriggerCompatibleActions: (() => []) as any,
- getEmbeddableFactories: (() => []) as any,
+ getEmbeddableFactories: start.getEmbeddableFactories,
getEmbeddableFactory,
} as any,
notifications: {} as any,
diff --git a/src/plugins/dashboard/public/embeddable/viewport/dashboard_viewport.test.tsx b/src/plugins/dashboard/public/embeddable/viewport/dashboard_viewport.test.tsx
index e3d9b8552f060..be4d2e3851f11 100644
--- a/src/plugins/dashboard/public/embeddable/viewport/dashboard_viewport.test.tsx
+++ b/src/plugins/dashboard/public/embeddable/viewport/dashboard_viewport.test.tsx
@@ -24,7 +24,6 @@ import { skip } from 'rxjs/operators';
import { mount } from 'enzyme';
import { I18nProvider } from '@kbn/i18n/react';
import { nextTick } from 'test_utils/enzyme_helpers';
-import { EmbeddableFactory } from '../../embeddable_plugin';
import { DashboardViewport, DashboardViewportProps } from './dashboard_viewport';
import { DashboardContainer, DashboardContainerOptions } from '../dashboard_container';
import { getSampleDashboardInput } from '../../test_helpers';
@@ -33,6 +32,8 @@ import {
ContactCardEmbeddableFactory,
} from '../../embeddable_plugin_test_samples';
import { KibanaContextProvider } from '../../../../../plugins/kibana_react/public';
+// eslint-disable-next-line
+import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks';
let dashboardContainer: DashboardContainer | undefined;
@@ -41,18 +42,19 @@ const ExitFullScreenButton = () =>
function getProps(
props?: Partial
): { props: DashboardViewportProps; options: DashboardContainerOptions } {
- const embeddableFactories = new Map();
- embeddableFactories.set(
+ const { setup, doStart } = embeddablePluginMock.createInstance();
+ setup.registerEmbeddableFactory(
CONTACT_CARD_EMBEDDABLE,
- new ContactCardEmbeddableFactory({}, (() => null) as any, {} as any)
+ new ContactCardEmbeddableFactory((() => null) as any, {} as any)
);
+ const start = doStart();
const options: DashboardContainerOptions = {
application: {} as any,
embeddable: {
getTriggerCompatibleActions: (() => []) as any,
- getEmbeddableFactories: (() => []) as any,
- getEmbeddableFactory: (id: string) => embeddableFactories.get(id),
+ getEmbeddableFactories: start.getEmbeddableFactories,
+ getEmbeddableFactory: start.getEmbeddableFactory,
} as any,
notifications: {} as any,
overlays: {} as any,
diff --git a/src/plugins/dashboard/public/tests/dashboard_container.test.tsx b/src/plugins/dashboard/public/tests/dashboard_container.test.tsx
index a81d80b440e04..1c72ad34e5446 100644
--- a/src/plugins/dashboard/public/tests/dashboard_container.test.tsx
+++ b/src/plugins/dashboard/public/tests/dashboard_container.test.tsx
@@ -52,7 +52,7 @@ test('DashboardContainer in edit mode shows edit mode actions', async () => {
uiActionsSetup.attachAction(CONTEXT_MENU_TRIGGER, editModeAction);
setup.registerEmbeddableFactory(
CONTACT_CARD_EMBEDDABLE,
- new ContactCardEmbeddableFactory({} as any, (() => null) as any, {} as any)
+ new ContactCardEmbeddableFactory((() => null) as any, {} as any)
);
const start = doStart();
diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts
index eca74af4ec253..23275fbe8e8f0 100644
--- a/src/plugins/embeddable/public/index.ts
+++ b/src/plugins/embeddable/public/index.ts
@@ -38,6 +38,7 @@ export {
EmbeddableChildPanel,
EmbeddableChildPanelProps,
EmbeddableContext,
+ EmbeddableFactoryDefinition,
EmbeddableFactory,
EmbeddableFactoryNotFoundError,
EmbeddableFactoryRenderer,
diff --git a/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx b/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx
index 9aeaf34f3311b..ce733bba6dda5 100644
--- a/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx
+++ b/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx
@@ -18,14 +18,14 @@
*/
import { EditPanelAction } from './edit_panel_action';
-import { EmbeddableFactory, Embeddable, EmbeddableInput } from '../embeddables';
+import { Embeddable, EmbeddableInput } from '../embeddables';
import { ViewMode } from '../types';
import { ContactCardEmbeddable } from '../test_samples';
-import { EmbeddableStart } from '../../plugin';
+import { embeddablePluginMock } from '../../mocks';
-const embeddableFactories = new Map();
-const getFactory = ((id: string) =>
- embeddableFactories.get(id)) as EmbeddableStart['getEmbeddableFactory'];
+const { doStart } = embeddablePluginMock.createInstance();
+const start = doStart();
+const getFactory = start.getEmbeddableFactory;
class EditableEmbeddable extends Embeddable {
public readonly type = 'EDITABLE_EMBEDDABLE';
@@ -83,9 +83,7 @@ test('is not compatible when edit url is not available', async () => {
});
test('is not visible when edit url is available but in view mode', async () => {
- embeddableFactories.clear();
- const action = new EditPanelAction((type =>
- embeddableFactories.get(type)) as EmbeddableStart['getEmbeddableFactory']);
+ const action = new EditPanelAction(getFactory);
expect(
await action.isCompatible({
embeddable: new EditableEmbeddable(
@@ -100,9 +98,7 @@ test('is not visible when edit url is available but in view mode', async () => {
});
test('is not compatible when edit url is available, in edit mode, but not editable', async () => {
- embeddableFactories.clear();
- const action = new EditPanelAction((type =>
- embeddableFactories.get(type)) as EmbeddableStart['getEmbeddableFactory']);
+ const action = new EditPanelAction(getFactory);
expect(
await action.isCompatible({
embeddable: new EditableEmbeddable(
diff --git a/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx b/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx
index 07915ce59e6ca..9e47da5cea032 100644
--- a/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx
+++ b/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx
@@ -20,7 +20,6 @@
import React from 'react';
import { nextTick } from 'test_utils/enzyme_helpers';
import { EmbeddableChildPanel } from './embeddable_child_panel';
-import { EmbeddableFactory } from '../embeddables';
import { CONTACT_CARD_EMBEDDABLE } from '../test_samples/embeddables/contact_card/contact_card_embeddable_factory';
import { SlowContactCardEmbeddableFactory } from '../test_samples/embeddables/contact_card/slow_contact_card_embeddable_factory';
import { HelloWorldContainer } from '../test_samples/embeddables/hello_world_container';
@@ -32,16 +31,17 @@ import {
// eslint-disable-next-line
import { inspectorPluginMock } from 'src/plugins/inspector/public/mocks';
import { mount } from 'enzyme';
+import { embeddablePluginMock } from '../../mocks';
test('EmbeddableChildPanel renders an embeddable when it is done loading', async () => {
const inspector = inspectorPluginMock.createStartContract();
-
- const embeddableFactories = new Map();
- embeddableFactories.set(
+ const { setup, doStart } = embeddablePluginMock.createInstance();
+ setup.registerEmbeddableFactory(
CONTACT_CARD_EMBEDDABLE,
new SlowContactCardEmbeddableFactory({ execAction: (() => null) as any })
);
- const getEmbeddableFactory = (id: string) => embeddableFactories.get(id);
+ const start = doStart();
+ const getEmbeddableFactory = start.getEmbeddableFactory;
const container = new HelloWorldContainer({ id: 'hello', panels: {} }, {
getEmbeddableFactory,
@@ -63,8 +63,8 @@ test('EmbeddableChildPanel renders an embeddable when it is done loading', async
container={container}
embeddableId={newEmbeddable.id}
getActions={() => Promise.resolve([])}
- getAllEmbeddableFactories={(() => []) as any}
- getEmbeddableFactory={(() => undefined) as any}
+ getAllEmbeddableFactories={start.getEmbeddableFactories}
+ getEmbeddableFactory={getEmbeddableFactory}
notifications={{} as any}
overlays={{} as any}
inspector={inspector}
diff --git a/src/plugins/embeddable/public/lib/embeddables/default_embeddable_factory_provider.ts b/src/plugins/embeddable/public/lib/embeddables/default_embeddable_factory_provider.ts
new file mode 100644
index 0000000000000..570a78fc41ea9
--- /dev/null
+++ b/src/plugins/embeddable/public/lib/embeddables/default_embeddable_factory_provider.ts
@@ -0,0 +1,52 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { SavedObjectAttributes } from 'kibana/public';
+import { EmbeddableFactoryDefinition } from './embeddable_factory_definition';
+import { EmbeddableInput, EmbeddableOutput, IEmbeddable } from './i_embeddable';
+import { EmbeddableFactory } from './embeddable_factory';
+import { IContainer } from '..';
+
+export const defaultEmbeddableFactoryProvider = <
+ I extends EmbeddableInput = EmbeddableInput,
+ O extends EmbeddableOutput = EmbeddableOutput,
+ E extends IEmbeddable = IEmbeddable,
+ T extends SavedObjectAttributes = SavedObjectAttributes
+>(
+ def: EmbeddableFactoryDefinition
+): EmbeddableFactory => {
+ const factory: EmbeddableFactory = {
+ isContainerType: def.isContainerType ?? false,
+ canCreateNew: def.canCreateNew ? def.canCreateNew.bind(def) : () => true,
+ getDefaultInput: def.getDefaultInput ? def.getDefaultInput.bind(def) : () => ({}),
+ getExplicitInput: def.getExplicitInput
+ ? def.getExplicitInput.bind(def)
+ : () => Promise.resolve({}),
+ createFromSavedObject:
+ def.createFromSavedObject ??
+ ((savedObjectId: string, input: Partial, parent?: IContainer) => {
+ throw new Error(`Creation from saved object not supported by type ${def.type}`);
+ }),
+ create: def.create.bind(def),
+ type: def.type,
+ isEditable: def.isEditable.bind(def),
+ getDisplayName: def.getDisplayName.bind(def),
+ savedObjectMetaData: def.savedObjectMetaData,
+ };
+ return factory;
+};
diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx
index eb10c16806640..a135484ff61be 100644
--- a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx
+++ b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx
@@ -158,6 +158,8 @@ export abstract class Embeddable<
*/
public destroy(): void {
this.destoyed = true;
+ this.input$.complete();
+ this.output$.complete();
if (this.parentSubscription) {
this.parentSubscription.unsubscribe();
}
diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_factory.ts b/src/plugins/embeddable/public/lib/embeddables/embeddable_factory.ts
index 81f7f35c900c9..7949b6fb8ba27 100644
--- a/src/plugins/embeddable/public/lib/embeddables/embeddable_factory.ts
+++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_factory.ts
@@ -22,32 +22,21 @@ import { SavedObjectMetaData } from '../../../../saved_objects/public';
import { EmbeddableInput, EmbeddableOutput, IEmbeddable } from './i_embeddable';
import { ErrorEmbeddable } from './error_embeddable';
import { IContainer } from '../containers/i_container';
+import { PropertySpec } from '../types';
export interface EmbeddableInstanceConfiguration {
id: string;
savedObjectId?: string;
}
-export interface PropertySpec {
- displayName: string;
- accessPath: string;
- id: string;
- description: string;
- value?: string;
-}
-
export interface OutputSpec {
[key: string]: PropertySpec;
}
-export interface EmbeddableFactoryOptions {
- savedObjectMetaData?: SavedObjectMetaData;
-}
-
/**
- * The EmbeddableFactory creates and initializes an embeddable instance
+ * EmbeddableFactories create and initialize an embeddable instance
*/
-export abstract class EmbeddableFactory<
+export interface EmbeddableFactory<
TEmbeddableInput extends EmbeddableInput = EmbeddableInput,
TEmbeddableOutput extends EmbeddableOutput = EmbeddableOutput,
TEmbeddable extends IEmbeddable = IEmbeddable<
@@ -58,9 +47,15 @@ export abstract class EmbeddableFactory<
> {
// A unique identified for this factory, which will be used to map an embeddable spec to
// a factory that can generate an instance of it.
- public abstract readonly type: string;
+ readonly type: string;
+
+ /**
+ * Returns whether the current user should be allowed to edit this type of
+ * embeddable. Most of the time this should be based off the capabilities service, hence it's async.
+ */
+ readonly isEditable: () => Promise;
- public readonly savedObjectMetaData?: SavedObjectMetaData;
+ readonly savedObjectMetaData?: SavedObjectMetaData;
/**
* True if is this factory create embeddables that are Containers. Used in the add panel to
@@ -68,31 +63,19 @@ export abstract class EmbeddableFactory<
* supported right now, but once nested containers are officially supported we can probably get
* rid of this interface.
*/
- public readonly isContainerType: boolean = false;
-
- constructor({ savedObjectMetaData }: EmbeddableFactoryOptions = {}) {
- this.savedObjectMetaData = savedObjectMetaData;
- }
-
- /**
- * Returns whether the current user should be allowed to edit this type of
- * embeddable. Most of the time this should be based off the capabilities service, hence it's async.
- */
- public abstract async isEditable(): Promise;
+ readonly isContainerType: boolean;
/**
* Returns a display name for this type of embeddable. Used in "Create new... " options
* in the add panel for containers.
*/
- public abstract getDisplayName(): string;
+ getDisplayName(): string;
/**
* If false, this type of embeddable can't be created with the "createNew" functionality. Instead,
* use createFromSavedObject, where an existing saved object must first exist.
*/
- public canCreateNew() {
- return true;
- }
+ canCreateNew(): boolean;
/**
* Can be used to get any default input, to be passed in to during the creation process. Default
@@ -100,18 +83,14 @@ export abstract class EmbeddableFactory<
* default input parameters.
* @param partial
*/
- public getDefaultInput(partial: Partial): Partial {
- return {};
- }
+ getDefaultInput(partial: Partial): Partial;
/**
* Can be used to request explicit input from the user, to be passed in to `EmbeddableFactory:create`.
* Explicit input is stored on the parent container for this embeddable. It overrides any inherited
* input passed down from the parent container.
*/
- public async getExplicitInput(): Promise> {
- return {};
- }
+ getExplicitInput(): Promise>;
/**
* Creates a new embeddable instance based off the saved object id.
@@ -120,13 +99,11 @@ export abstract class EmbeddableFactory<
* range of the parent container.
* @param parent
*/
- public createFromSavedObject(
+ createFromSavedObject(
savedObjectId: string,
input: Partial,
parent?: IContainer
- ): Promise {
- throw new Error(`Creation from saved object not supported by type ${this.type}`);
- }
+ ): Promise;
/**
* Resolves to undefined if a new Embeddable cannot be directly created and the user will instead be redirected
@@ -134,7 +111,7 @@ export abstract class EmbeddableFactory<
*
* This will likely change in future iterations when we improve in place editing capabilities.
*/
- public abstract create(
+ create(
initialInput: TEmbeddableInput,
parent?: IContainer
): Promise;
diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_factory_definition.ts b/src/plugins/embeddable/public/lib/embeddables/embeddable_factory_definition.ts
new file mode 100644
index 0000000000000..b8985f7311ea9
--- /dev/null
+++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_factory_definition.ts
@@ -0,0 +1,44 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { SavedObjectAttributes } from 'kibana/server';
+import { IEmbeddable } from './i_embeddable';
+import { EmbeddableFactory } from './embeddable_factory';
+import { EmbeddableInput, EmbeddableOutput } from '..';
+
+export type EmbeddableFactoryDefinition<
+ I extends EmbeddableInput = EmbeddableInput,
+ O extends EmbeddableOutput = EmbeddableOutput,
+ E extends IEmbeddable = IEmbeddable,
+ T extends SavedObjectAttributes = SavedObjectAttributes
+> =
+ // Required parameters
+ Pick, 'create' | 'type' | 'isEditable' | 'getDisplayName'> &
+ // Optional parameters
+ Partial<
+ Pick<
+ EmbeddableFactory,
+ | 'createFromSavedObject'
+ | 'isContainerType'
+ | 'getExplicitInput'
+ | 'savedObjectMetaData'
+ | 'canCreateNew'
+ | 'getDefaultInput'
+ >
+ >;
diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_factory_renderer.test.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable_factory_renderer.test.tsx
index 51b83ea0ecaa3..e27045495af5b 100644
--- a/src/plugins/embeddable/public/lib/embeddables/embeddable_factory_renderer.test.tsx
+++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_factory_renderer.test.tsx
@@ -21,22 +21,22 @@ import {
HELLO_WORLD_EMBEDDABLE,
HelloWorldEmbeddableFactory,
} from '../../../../../../examples/embeddable_examples/public';
-import { EmbeddableFactory } from './embeddable_factory';
import { EmbeddableFactoryRenderer } from './embeddable_factory_renderer';
import { mount } from 'enzyme';
import { nextTick } from 'test_utils/enzyme_helpers';
// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
-import { EmbeddableStart } from '../../plugin';
+import { embeddablePluginMock } from '../../mocks';
test('EmbeddableFactoryRenderer renders an embeddable', async () => {
- const embeddableFactories = new Map();
- embeddableFactories.set(HELLO_WORLD_EMBEDDABLE, new HelloWorldEmbeddableFactory());
- const getEmbeddableFactory = (id: string) => embeddableFactories.get(id);
+ const { setup, doStart } = embeddablePluginMock.createInstance();
+ setup.registerEmbeddableFactory(HELLO_WORLD_EMBEDDABLE, new HelloWorldEmbeddableFactory());
+
+ const getEmbeddableFactory = doStart().getEmbeddableFactory;
const component = mount(
diff --git a/src/plugins/embeddable/public/lib/embeddables/index.ts b/src/plugins/embeddable/public/lib/embeddables/index.ts
index 2175c3a59aa58..4d6ab37a50c05 100644
--- a/src/plugins/embeddable/public/lib/embeddables/index.ts
+++ b/src/plugins/embeddable/public/lib/embeddables/index.ts
@@ -18,11 +18,9 @@
*/
export { EmbeddableOutput, EmbeddableInput, IEmbeddable } from './i_embeddable';
export { Embeddable } from './embeddable';
-export {
- EmbeddableInstanceConfiguration,
- EmbeddableFactory,
- OutputSpec,
-} from './embeddable_factory';
+export * from './embeddable_factory';
+export * from './embeddable_factory_definition';
+export * from './default_embeddable_factory_provider';
export { ErrorEmbeddable, isErrorEmbeddable } from './error_embeddable';
export { withEmbeddableSubscription } from './with_subscription';
export { EmbeddableFactoryRenderer } from './embeddable_factory_renderer';
diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx
index 757d4e6bfddef..649677dc67c7d 100644
--- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx
+++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx
@@ -27,7 +27,7 @@ import { I18nProvider } from '@kbn/i18n/react';
import { CONTEXT_MENU_TRIGGER } from '../triggers';
import { Action, UiActionsStart, ActionType } from 'src/plugins/ui_actions/public';
import { Trigger, ViewMode } from '../types';
-import { EmbeddableFactory, isErrorEmbeddable } from '../embeddables';
+import { isErrorEmbeddable } from '../embeddables';
import { EmbeddablePanel } from './embeddable_panel';
import { createEditModeAction } from '../test_samples/actions';
import {
@@ -43,26 +43,25 @@ import {
// eslint-disable-next-line
import { inspectorPluginMock } from 'src/plugins/inspector/public/mocks';
import { EuiBadge } from '@elastic/eui';
+import { embeddablePluginMock } from '../../mocks';
const actionRegistry = new Map>();
const triggerRegistry = new Map();
-const embeddableFactories = new Map();
-const getEmbeddableFactory = (id: string) => embeddableFactories.get(id);
+
+const { setup, doStart } = embeddablePluginMock.createInstance();
const editModeAction = createEditModeAction();
const trigger: Trigger = {
id: CONTEXT_MENU_TRIGGER,
};
-const embeddableFactory = new ContactCardEmbeddableFactory(
- {} as any,
- (() => null) as any,
- {} as any
-);
+const embeddableFactory = new ContactCardEmbeddableFactory((() => null) as any, {} as any);
actionRegistry.set(editModeAction.id, editModeAction);
triggerRegistry.set(trigger.id, trigger);
-embeddableFactories.set(embeddableFactory.type, embeddableFactory);
+setup.registerEmbeddableFactory(embeddableFactory.type, embeddableFactory);
+const start = doStart();
+const getEmbeddableFactory = start.getEmbeddableFactory;
test('HelloWorldContainer initializes embeddables', async done => {
const container = new HelloWorldContainer(
{
@@ -157,8 +156,8 @@ test('HelloWorldContainer in view mode hides edit mode actions', async () => {
Promise.resolve([])}
- getAllEmbeddableFactories={(() => []) as any}
- getEmbeddableFactory={(() => undefined) as any}
+ getAllEmbeddableFactories={start.getEmbeddableFactories}
+ getEmbeddableFactory={start.getEmbeddableFactory}
notifications={{} as any}
overlays={{} as any}
inspector={inspector}
@@ -195,8 +194,8 @@ const renderInEditModeAndOpenContextMenu = async (
[]) as any}
- getEmbeddableFactory={(() => undefined) as any}
+ getAllEmbeddableFactories={start.getEmbeddableFactories}
+ getEmbeddableFactory={start.getEmbeddableFactory}
notifications={{} as any}
overlays={{} as any}
inspector={inspector}
@@ -293,8 +292,8 @@ test('HelloWorldContainer in edit mode shows edit mode actions', async () => {
Promise.resolve([])}
- getAllEmbeddableFactories={(() => []) as any}
- getEmbeddableFactory={(() => undefined) as any}
+ getAllEmbeddableFactories={start.getEmbeddableFactories}
+ getEmbeddableFactory={start.getEmbeddableFactory}
notifications={{} as any}
overlays={{} as any}
inspector={inspector}
@@ -355,8 +354,8 @@ test('Updates when hidePanelTitles is toggled', async () => {
Promise.resolve([])}
- getAllEmbeddableFactories={(() => []) as any}
- getEmbeddableFactory={(() => undefined) as any}
+ getAllEmbeddableFactories={start.getEmbeddableFactories}
+ getEmbeddableFactory={start.getEmbeddableFactory}
notifications={{} as any}
overlays={{} as any}
inspector={inspector}
@@ -407,8 +406,8 @@ test('Check when hide header option is false', async () => {
Promise.resolve([])}
- getAllEmbeddableFactories={(() => []) as any}
- getEmbeddableFactory={(() => undefined) as any}
+ getAllEmbeddableFactories={start.getEmbeddableFactories}
+ getEmbeddableFactory={start.getEmbeddableFactory}
notifications={{} as any}
overlays={{} as any}
inspector={inspector}
@@ -444,8 +443,8 @@ test('Check when hide header option is true', async () => {
Promise.resolve([])}
- getAllEmbeddableFactories={(() => []) as any}
- getEmbeddableFactory={(() => undefined) as any}
+ getAllEmbeddableFactories={start.getEmbeddableFactories}
+ getEmbeddableFactory={start.getEmbeddableFactory}
notifications={{} as any}
overlays={{} as any}
inspector={inspector}
diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.test.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.test.tsx
index 8ee8c8dad9df3..74b08535bf27a 100644
--- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.test.tsx
+++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.test.tsx
@@ -19,7 +19,6 @@
import { ViewMode, EmbeddableOutput, isErrorEmbeddable } from '../../../../';
import { AddPanelAction } from './add_panel_action';
-import { EmbeddableFactory } from '../../../../embeddables';
import {
FILTERABLE_EMBEDDABLE,
FilterableEmbeddable,
@@ -31,11 +30,12 @@ import { FilterableContainer } from '../../../../test_samples/embeddables/filter
import { coreMock } from '../../../../../../../../core/public/mocks';
import { ContactCardEmbeddable } from '../../../../test_samples';
import { esFilters, Filter } from '../../../../../../../../plugins/data/public';
-import { EmbeddableStart } from 'src/plugins/embeddable/public/plugin';
+import { EmbeddableStart } from '../../../../../plugin';
+import { embeddablePluginMock } from '../../../../../mocks';
-const embeddableFactories = new Map();
-embeddableFactories.set(FILTERABLE_EMBEDDABLE, new FilterableEmbeddableFactory());
-const getFactory = (id: string) => embeddableFactories.get(id);
+const { setup, doStart } = embeddablePluginMock.createInstance();
+setup.registerEmbeddableFactory(FILTERABLE_EMBEDDABLE, new FilterableEmbeddableFactory());
+const getFactory = doStart().getEmbeddableFactory;
let container: FilterableContainer;
let embeddable: FilterableEmbeddable;
diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx
index 2fa21e40ca0f0..282b0f05891e0 100644
--- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx
+++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx
@@ -31,7 +31,7 @@ import { ReactWrapper } from 'enzyme';
import { coreMock } from '../../../../../../../../core/public/mocks';
// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
-import { EmbeddableStart } from 'src/plugins/embeddable/public/plugin';
+import { embeddablePluginMock } from '../../../../../mocks';
function DummySavedObjectFinder(props: { children: React.ReactNode }) {
return (
@@ -43,10 +43,10 @@ function DummySavedObjectFinder(props: { children: React.ReactNode }) {
}
test('createNewEmbeddable() add embeddable to container', async () => {
+ const { setup, doStart } = embeddablePluginMock.createInstance();
const core = coreMock.createStart();
const { overlays } = core;
const contactCardEmbeddableFactory = new ContactCardEmbeddableFactory(
- {},
(() => null) as any,
overlays
);
@@ -55,7 +55,9 @@ test('createNewEmbeddable() add embeddable to container', async () => {
firstName: 'foo',
lastName: 'bar',
} as any);
- const getEmbeddableFactory = (id: string) => contactCardEmbeddableFactory;
+ setup.registerEmbeddableFactory(CONTACT_CARD_EMBEDDABLE, contactCardEmbeddableFactory);
+ const start = doStart();
+ const getEmbeddableFactory = start.getEmbeddableFactory;
const input: ContainerInput<{ firstName: string; lastName: string }> = {
id: '1',
panels: {},
@@ -66,8 +68,8 @@ test('createNewEmbeddable() add embeddable to container', async () => {
new Set([contactCardEmbeddableFactory]).values()}
+ getFactory={getEmbeddableFactory}
+ getAllFactories={start.getEmbeddableFactories}
notifications={core.notifications}
SavedObjectFinder={() => null}
/>
@@ -88,10 +90,10 @@ test('createNewEmbeddable() add embeddable to container', async () => {
});
test('selecting embeddable in "Create new ..." list calls createNewEmbeddable()', async () => {
+ const { setup, doStart } = embeddablePluginMock.createInstance();
const core = coreMock.createStart();
const { overlays } = core;
const contactCardEmbeddableFactory = new ContactCardEmbeddableFactory(
- {},
(() => null) as any,
overlays
);
@@ -100,8 +102,10 @@ test('selecting embeddable in "Create new ..." list calls createNewEmbeddable()'
firstName: 'foo',
lastName: 'bar',
} as any);
- const getEmbeddableFactory = ((id: string) =>
- contactCardEmbeddableFactory) as EmbeddableStart['getEmbeddableFactory'];
+
+ setup.registerEmbeddableFactory(CONTACT_CARD_EMBEDDABLE, contactCardEmbeddableFactory);
+ const start = doStart();
+ const getEmbeddableFactory = start.getEmbeddableFactory;
const input: ContainerInput<{ firstName: string; lastName: string }> = {
id: '1',
panels: {},
@@ -113,7 +117,7 @@ test('selecting embeddable in "Create new ..." list calls createNewEmbeddable()'
container={container}
onClose={onClose}
getFactory={getEmbeddableFactory}
- getAllFactories={() => new Set([contactCardEmbeddableFactory]).values()}
+ getAllFactories={start.getEmbeddableFactories}
notifications={core.notifications}
SavedObjectFinder={props => }
/>
diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx
index 95eeb63710c32..06c47bd1bcad8 100644
--- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx
+++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx
@@ -121,15 +121,16 @@ export class AddPanelFlyout extends React.Component {
public render() {
const SavedObjectFinder = this.props.SavedObjectFinder;
+ const metaData = [...this.props.getAllFactories()]
+ .filter(
+ embeddableFactory =>
+ Boolean(embeddableFactory.savedObjectMetaData) && !embeddableFactory.isContainerType
+ )
+ .map(({ savedObjectMetaData }) => savedObjectMetaData as any);
const savedObjectsFinder = (
- Boolean(embeddableFactory.savedObjectMetaData) && !embeddableFactory.isContainerType
- )
- .map(({ savedObjectMetaData }) => savedObjectMetaData as any)}
+ savedObjectMetaData={metaData}
showFilter={true}
noItemsMessage={i18n.translate('embeddableApi.addPanel.noMatchingObjectsMessage', {
defaultMessage: 'No matching objects found.',
diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts
index 3f7c917cd1617..2f66d8eb0d619 100644
--- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts
+++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts
@@ -32,18 +32,19 @@ import {
ContactCardEmbeddableFactory,
} from '../../../../test_samples/embeddables/contact_card/contact_card_embeddable_factory';
import { HelloWorldContainer } from '../../../../test_samples/embeddables/hello_world_container';
-import { EmbeddableFactory } from '../../../../embeddables';
+import { embeddablePluginMock } from '../../../../../mocks';
let container: Container;
let embeddable: ContactCardEmbeddable;
function createHelloWorldContainer(input = { id: '123', panels: {} }) {
- const embeddableFactories = new Map();
- const getEmbeddableFactory = (id: string) => embeddableFactories.get(id);
- embeddableFactories.set(
+ const { setup, doStart } = embeddablePluginMock.createInstance();
+ setup.registerEmbeddableFactory(
CONTACT_CARD_EMBEDDABLE,
- new ContactCardEmbeddableFactory({}, (() => {}) as any, {} as any)
+ new ContactCardEmbeddableFactory((() => {}) as any, {} as any)
);
+ const getEmbeddableFactory = doStart().getEmbeddableFactory;
+
return new HelloWorldContainer(input, { getEmbeddableFactory } as any);
}
diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.test.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.test.tsx
index e19acda8419da..ee31127cb5a40 100644
--- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.test.tsx
+++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.test.tsx
@@ -28,20 +28,16 @@ import {
} from '../../../test_samples';
// eslint-disable-next-line
import { inspectorPluginMock } from 'src/plugins/inspector/public/mocks';
-import {
- EmbeddableFactory,
- EmbeddableOutput,
- isErrorEmbeddable,
- ErrorEmbeddable,
-} from '../../../embeddables';
+import { EmbeddableOutput, isErrorEmbeddable, ErrorEmbeddable } from '../../../embeddables';
import { of } from '../../../../tests/helpers';
import { esFilters } from '../../../../../../../plugins/data/public';
-import { EmbeddableStart } from 'src/plugins/embeddable/public/plugin';
+import { embeddablePluginMock } from '../../../../mocks';
+import { EmbeddableStart } from '../../../../plugin';
-const setup = async () => {
- const embeddableFactories = new Map();
- embeddableFactories.set(FILTERABLE_EMBEDDABLE, new FilterableEmbeddableFactory());
- const getFactory = (id: string) => embeddableFactories.get(id);
+const setupTests = async () => {
+ const { setup, doStart } = embeddablePluginMock.createInstance();
+ setup.registerEmbeddableFactory(FILTERABLE_EMBEDDABLE, new FilterableEmbeddableFactory());
+ const getFactory = doStart().getEmbeddableFactory;
const container = new FilterableContainer(
{
id: 'hello',
@@ -79,7 +75,7 @@ test('Is compatible when inspector adapters are available', async () => {
const inspector = inspectorPluginMock.createStartContract();
inspector.isAvailable.mockImplementation(() => true);
- const { embeddable } = await setup();
+ const { embeddable } = await setupTests();
const inspectAction = new InspectPanelAction(inspector);
expect(await inspectAction.isCompatible({ embeddable })).toBe(true);
@@ -114,7 +110,7 @@ test('Executes when inspector adapters are available', async () => {
const inspector = inspectorPluginMock.createStartContract();
inspector.isAvailable.mockImplementation(() => true);
- const { embeddable } = await setup();
+ const { embeddable } = await setupTests();
const inspectAction = new InspectPanelAction(inspector);
expect(inspector.open).toHaveBeenCalledTimes(0);
diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/remove_panel_action.test.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/remove_panel_action.test.tsx
index f4d5aa148373b..dea4a88bda082 100644
--- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/remove_panel_action.test.tsx
+++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/remove_panel_action.test.tsx
@@ -19,7 +19,6 @@
import { EmbeddableOutput, isErrorEmbeddable } from '../../../';
import { RemovePanelAction } from './remove_panel_action';
-import { EmbeddableFactory } from '../../../embeddables';
import { EmbeddableStart } from '../../../../plugin';
import {
FILTERABLE_EMBEDDABLE,
@@ -31,11 +30,11 @@ import { FilterableContainer } from '../../../test_samples/embeddables/filterabl
import { ViewMode } from '../../../types';
import { ContactCardEmbeddable } from '../../../test_samples/embeddables/contact_card/contact_card_embeddable';
import { esFilters, Filter } from '../../../../../../../plugins/data/public';
+import { embeddablePluginMock } from '../../../../mocks';
-const embeddableFactories = new Map();
-embeddableFactories.set(FILTERABLE_EMBEDDABLE, new FilterableEmbeddableFactory());
-const getFactory = (id: string) => embeddableFactories.get(id);
-
+const { setup, doStart } = embeddablePluginMock.createInstance();
+setup.registerEmbeddableFactory(FILTERABLE_EMBEDDABLE, new FilterableEmbeddableFactory());
+const getFactory = doStart().getEmbeddableFactory;
let container: FilterableContainer;
let embeddable: FilterableEmbeddable;
diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx
index 20a5a8112f4d3..f977329562b9b 100644
--- a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx
+++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx
@@ -23,24 +23,21 @@ import { UiActionsStart } from 'src/plugins/ui_actions/public';
import { CoreStart } from 'src/core/public';
import { toMountPoint } from '../../../../../../kibana_react/public';
-import { EmbeddableFactory } from '../../../embeddables';
+import { EmbeddableFactoryDefinition } from '../../../embeddables';
import { Container } from '../../../containers';
import { ContactCardEmbeddable, ContactCardEmbeddableInput } from './contact_card_embeddable';
import { ContactCardInitializer } from './contact_card_initializer';
-import { EmbeddableFactoryOptions } from '../../../embeddables/embeddable_factory';
export const CONTACT_CARD_EMBEDDABLE = 'CONTACT_CARD_EMBEDDABLE';
-export class ContactCardEmbeddableFactory extends EmbeddableFactory {
+export class ContactCardEmbeddableFactory
+ implements EmbeddableFactoryDefinition {
public readonly type = CONTACT_CARD_EMBEDDABLE;
constructor(
- options: EmbeddableFactoryOptions,
private readonly execTrigger: UiActionsStart['executeTriggerActions'],
private readonly overlays: CoreStart['overlays']
- ) {
- super(options);
- }
+ ) {}
public async isEditable() {
return true;
@@ -52,7 +49,7 @@ export class ContactCardEmbeddableFactory extends EmbeddableFactory> {
+ public getExplicitInput = (): Promise> => {
return new Promise(resolve => {
const modalSession = this.overlays.openModal(
toMountPoint(
@@ -72,9 +69,9 @@ export class ContactCardEmbeddableFactory extends EmbeddableFactory {
return new ContactCardEmbeddable(
initialInput,
{
@@ -82,5 +79,5 @@ export class ContactCardEmbeddableFactory extends EmbeddableFactory {
+export class SlowContactCardEmbeddableFactory
+ implements EmbeddableFactoryDefinition {
private loadTickCount = 0;
public readonly type = CONTACT_CARD_EMBEDDABLE;
constructor(private readonly options: SlowContactCardEmbeddableFactoryOptions) {
- super();
if (options.loadTickCount) {
this.loadTickCount = options.loadTickCount;
}
@@ -48,10 +46,10 @@ export class SlowContactCardEmbeddableFactory extends EmbeddableFactory<
return 'slow to load contact card';
}
- public async create(initialInput: ContactCardEmbeddableInput, parent?: Container) {
+ public create = async (initialInput: ContactCardEmbeddableInput, parent?: Container) => {
for (let i = 0; i < this.loadTickCount; i++) {
await Promise.resolve();
}
return new ContactCardEmbeddable(initialInput, { execAction: this.options.execAction }, parent);
- }
+ };
}
diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_container_factory.ts b/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_container_factory.ts
index 3488f6a2e038d..f27c7e8b011fd 100644
--- a/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_container_factory.ts
+++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_container_factory.ts
@@ -18,24 +18,21 @@
*/
import { i18n } from '@kbn/i18n';
-import { Container, EmbeddableFactory } from '../..';
+import { Container, EmbeddableFactoryDefinition } from '../..';
import {
FilterableContainer,
FilterableContainerInput,
FILTERABLE_CONTAINER,
} from './filterable_container';
-import { EmbeddableFactoryOptions } from '../../embeddables/embeddable_factory';
import { EmbeddableStart } from '../../../plugin';
-export class FilterableContainerFactory extends EmbeddableFactory {
+export class FilterableContainerFactory
+ implements EmbeddableFactoryDefinition {
public readonly type = FILTERABLE_CONTAINER;
constructor(
- private readonly getFactory: EmbeddableStart['getEmbeddableFactory'],
- options: EmbeddableFactoryOptions = {}
- ) {
- super(options);
- }
+ private readonly getFactory: () => Promise
+ ) {}
public getDisplayName() {
return i18n.translate('embeddableApi.samples.filterableContainer.displayName', {
@@ -47,7 +44,8 @@ export class FilterableContainerFactory extends EmbeddableFactory {
+ const getEmbeddableFactory = await this.getFactory();
+ return new FilterableContainer(initialInput, getEmbeddableFactory, parent);
+ };
}
diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_embeddable_factory.ts b/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_embeddable_factory.ts
index f37a16ea86c43..4c941ee22abfa 100644
--- a/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_embeddable_factory.ts
+++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_embeddable_factory.ts
@@ -23,10 +23,11 @@ import {
FilterableEmbeddableInput,
FILTERABLE_EMBEDDABLE,
} from './filterable_embeddable';
-import { EmbeddableFactory } from '../../embeddables';
+import { EmbeddableFactoryDefinition } from '../../embeddables';
import { IContainer } from '../../containers';
-export class FilterableEmbeddableFactory extends EmbeddableFactory {
+export class FilterableEmbeddableFactory
+ implements EmbeddableFactoryDefinition {
public readonly type = FILTERABLE_EMBEDDABLE;
public async isEditable() {
diff --git a/src/plugins/embeddable/public/mocks.ts b/src/plugins/embeddable/public/mocks.ts
index ba2f78e42e10e..2ee05d8316ace 100644
--- a/src/plugins/embeddable/public/mocks.ts
+++ b/src/plugins/embeddable/public/mocks.ts
@@ -30,6 +30,7 @@ export type Start = jest.Mocked;
const createSetupContract = (): Setup => {
const setupContract: Setup = {
registerEmbeddableFactory: jest.fn(),
+ setCustomEmbeddableFactoryProvider: jest.fn(),
};
return setupContract;
};
diff --git a/src/plugins/embeddable/public/plugin.test.ts b/src/plugins/embeddable/public/plugin.test.ts
index c334411004e2c..804f3e2e8a7b4 100644
--- a/src/plugins/embeddable/public/plugin.test.ts
+++ b/src/plugins/embeddable/public/plugin.test.ts
@@ -18,6 +18,9 @@
*/
import { coreMock } from '../../../core/public/mocks';
import { testPlugin } from './tests/test_plugin';
+import { EmbeddableFactoryProvider } from './types';
+import { defaultEmbeddableFactoryProvider } from './lib';
+import { HelloWorldEmbeddable } from '../../../../examples/embeddable_examples/public';
test('cannot register embeddable factory with the same ID', async () => {
const coreSetup = coreMock.createSetup();
@@ -33,3 +36,75 @@ test('cannot register embeddable factory with the same ID', async () => {
'Embeddable factory [embeddableFactoryId = ID] already registered in Embeddables API.'
);
});
+
+test('can set custom embeddable factory provider', async () => {
+ const coreSetup = coreMock.createSetup();
+ const coreStart = coreMock.createStart();
+ const { setup, doStart } = testPlugin(coreSetup, coreStart);
+
+ const customProvider: EmbeddableFactoryProvider = def => ({
+ ...defaultEmbeddableFactoryProvider(def),
+ getDisplayName: () => 'Intercepted!',
+ });
+
+ setup.setCustomEmbeddableFactoryProvider(customProvider);
+ setup.registerEmbeddableFactory('test', {
+ type: 'test',
+ create: () => Promise.resolve(undefined),
+ getDisplayName: () => 'Test',
+ isEditable: () => Promise.resolve(true),
+ });
+
+ const start = doStart();
+ const factory = start.getEmbeddableFactory('test');
+ expect(factory!.getDisplayName()).toEqual('Intercepted!');
+});
+
+test('custom embeddable factory provider test for intercepting embeddable creation and destruction', async () => {
+ const coreSetup = coreMock.createSetup();
+ const coreStart = coreMock.createStart();
+ const { setup, doStart } = testPlugin(coreSetup, coreStart);
+
+ let updateCount = 0;
+ const customProvider: EmbeddableFactoryProvider = def => {
+ return {
+ ...defaultEmbeddableFactoryProvider(def),
+ create: async (input, parent) => {
+ const embeddable = await defaultEmbeddableFactoryProvider(def).create(input, parent);
+ if (embeddable) {
+ const subscription = embeddable.getInput$().subscribe(
+ () => {
+ updateCount++;
+ },
+ () => {},
+ () => {
+ subscription.unsubscribe();
+ updateCount = 0;
+ }
+ );
+ }
+ return embeddable;
+ },
+ };
+ };
+
+ setup.setCustomEmbeddableFactoryProvider(customProvider);
+ setup.registerEmbeddableFactory('test', {
+ type: 'test',
+ create: (input, parent) => Promise.resolve(new HelloWorldEmbeddable(input, parent)),
+ getDisplayName: () => 'Test',
+ isEditable: () => Promise.resolve(true),
+ });
+
+ const start = doStart();
+ const factory = start.getEmbeddableFactory('test');
+
+ const embeddable = await factory?.create({ id: '123' });
+ embeddable!.updateInput({ title: 'boo' });
+ // initial subscription, plus the second update.
+ expect(updateCount).toEqual(2);
+
+ embeddable!.destroy();
+ await new Promise(resolve => process.nextTick(resolve));
+ expect(updateCount).toEqual(0);
+});
diff --git a/src/plugins/embeddable/public/plugin.ts b/src/plugins/embeddable/public/plugin.ts
index 381665c359ffd..a483f90f76dde 100644
--- a/src/plugins/embeddable/public/plugin.ts
+++ b/src/plugins/embeddable/public/plugin.ts
@@ -18,9 +18,16 @@
*/
import { UiActionsSetup } from 'src/plugins/ui_actions/public';
import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/public';
-import { EmbeddableFactoryRegistry } from './types';
+import { EmbeddableFactoryRegistry, EmbeddableFactoryProvider } from './types';
import { bootstrap } from './bootstrap';
-import { EmbeddableFactory, EmbeddableInput, EmbeddableOutput } from './lib';
+import {
+ EmbeddableFactory,
+ EmbeddableInput,
+ EmbeddableOutput,
+ defaultEmbeddableFactoryProvider,
+ IEmbeddable,
+} from './lib';
+import { EmbeddableFactoryDefinition } from './lib/embeddables/embeddable_factory_definition';
export interface EmbeddableSetupDependencies {
uiActions: UiActionsSetup;
@@ -29,21 +36,29 @@ export interface EmbeddableSetupDependencies {
export interface EmbeddableSetup {
registerEmbeddableFactory: (
id: string,
- factory: EmbeddableFactory
+ factory: EmbeddableFactoryDefinition
) => void;
+ setCustomEmbeddableFactoryProvider: (customProvider: EmbeddableFactoryProvider) => void;
}
+
export interface EmbeddableStart {
getEmbeddableFactory: <
I extends EmbeddableInput = EmbeddableInput,
- O extends EmbeddableOutput = EmbeddableOutput
+ O extends EmbeddableOutput = EmbeddableOutput,
+ E extends IEmbeddable = IEmbeddable
>(
embeddableFactoryId: string
- ) => EmbeddableFactory | undefined;
+ ) => EmbeddableFactory | undefined;
getEmbeddableFactories: () => IterableIterator;
}
export class EmbeddablePublicPlugin implements Plugin {
+ private readonly embeddableFactoryDefinitions: Map<
+ string,
+ EmbeddableFactoryDefinition
+ > = new Map();
private readonly embeddableFactories: EmbeddableFactoryRegistry = new Map();
+ private customEmbeddableFactoryProvider?: EmbeddableFactoryProvider;
constructor(initializerContext: PluginInitializerContext) {}
@@ -52,34 +67,57 @@ export class EmbeddablePublicPlugin implements Plugin {
+ if (this.customEmbeddableFactoryProvider) {
+ throw new Error(
+ 'Custom embeddable factory provider is already set, and can only be set once'
+ );
+ }
+ this.customEmbeddableFactoryProvider = provider;
+ },
};
}
- public start(core: CoreStart) {
+ public start(core: CoreStart): EmbeddableStart {
+ this.embeddableFactoryDefinitions.forEach(def => {
+ this.embeddableFactories.set(
+ def.type,
+ this.customEmbeddableFactoryProvider
+ ? this.customEmbeddableFactoryProvider(def)
+ : defaultEmbeddableFactoryProvider(def)
+ );
+ });
return {
getEmbeddableFactory: this.getEmbeddableFactory,
- getEmbeddableFactories: () => this.embeddableFactories.values(),
+ getEmbeddableFactories: () => {
+ this.ensureFactoriesExist();
+ return this.embeddableFactories.values();
+ },
};
}
public stop() {}
- private registerEmbeddableFactory = (embeddableFactoryId: string, factory: EmbeddableFactory) => {
- if (this.embeddableFactories.has(embeddableFactoryId)) {
+ private registerEmbeddableFactory = (
+ embeddableFactoryId: string,
+ factory: EmbeddableFactoryDefinition
+ ) => {
+ if (this.embeddableFactoryDefinitions.has(embeddableFactoryId)) {
throw new Error(
`Embeddable factory [embeddableFactoryId = ${embeddableFactoryId}] already registered in Embeddables API.`
);
}
-
- this.embeddableFactories.set(embeddableFactoryId, factory);
+ this.embeddableFactoryDefinitions.set(embeddableFactoryId, factory);
};
private getEmbeddableFactory = <
I extends EmbeddableInput = EmbeddableInput,
- O extends EmbeddableOutput = EmbeddableOutput
+ O extends EmbeddableOutput = EmbeddableOutput,
+ E extends IEmbeddable = IEmbeddable
>(
embeddableFactoryId: string
- ) => {
+ ): EmbeddableFactory => {
+ this.ensureFactoryExists(embeddableFactoryId);
const factory = this.embeddableFactories.get(embeddableFactoryId);
if (!factory) {
@@ -88,6 +126,24 @@ export class EmbeddablePublicPlugin implements Plugin;
+ return factory as EmbeddableFactory;
};
+
+ // These two functions are only to support legacy plugins registering factories after the start lifecycle.
+ private ensureFactoriesExist() {
+ this.embeddableFactoryDefinitions.forEach(def => this.ensureFactoryExists(def.type));
+ }
+
+ private ensureFactoryExists(type: string) {
+ if (!this.embeddableFactories.get(type)) {
+ const def = this.embeddableFactoryDefinitions.get(type);
+ if (!def) return;
+ this.embeddableFactories.set(
+ type,
+ this.customEmbeddableFactoryProvider
+ ? this.customEmbeddableFactoryProvider(def)
+ : defaultEmbeddableFactoryProvider(def)
+ );
+ }
+ }
}
diff --git a/src/plugins/embeddable/public/tests/apply_filter_action.test.ts b/src/plugins/embeddable/public/tests/apply_filter_action.test.ts
index 6beef35bbe136..54f3ac2887f6c 100644
--- a/src/plugins/embeddable/public/tests/apply_filter_action.test.ts
+++ b/src/plugins/embeddable/public/tests/apply_filter_action.test.ts
@@ -36,13 +36,13 @@ import { esFilters } from '../../../../plugins/data/public';
test('ApplyFilterAction applies the filter to the root of the container tree', async () => {
const { doStart, setup } = testPlugin();
- const api = doStart();
- const factory1 = new FilterableContainerFactory(api.getEmbeddableFactory);
const factory2 = new FilterableEmbeddableFactory();
-
- setup.registerEmbeddableFactory(factory1.type, factory1);
+ const factory1 = new FilterableContainerFactory(async () => await api.getEmbeddableFactory);
setup.registerEmbeddableFactory(factory2.type, factory2);
+ setup.registerEmbeddableFactory(factory1.type, factory1);
+
+ const api = doStart();
const applyFilterAction = createFilterAction();
@@ -63,7 +63,9 @@ test('ApplyFilterAction applies the filter to the root of the container tree', a
FilterableContainer
>(FILTERABLE_CONTAINER, { panels: {}, id: 'Node2' });
- if (isErrorEmbeddable(node1) || isErrorEmbeddable(node2)) throw new Error();
+ if (isErrorEmbeddable(node1) || isErrorEmbeddable(node2)) {
+ throw new Error();
+ }
const embeddable = await node2.addNewEmbeddable<
FilterableEmbeddableInput,
@@ -94,9 +96,11 @@ test('ApplyFilterAction applies the filter to the root of the container tree', a
test('ApplyFilterAction is incompatible if the root container does not accept a filter as input', async () => {
const { doStart, coreStart, setup } = testPlugin();
- const api = doStart();
const inspector = inspectorPluginMock.createStartContract();
+ const factory = new FilterableEmbeddableFactory();
+ setup.registerEmbeddableFactory(factory.type, factory);
+ const api = doStart();
const applyFilterAction = createFilterAction();
const parent = new HelloWorldContainer(
{ id: 'root', panels: {} },
@@ -110,10 +114,6 @@ test('ApplyFilterAction is incompatible if the root container does not accept a
SavedObjectFinder: () => null,
}
);
-
- const factory = new FilterableEmbeddableFactory();
- setup.registerEmbeddableFactory(factory.type, factory);
-
const embeddable = await parent.addNewEmbeddable<
FilterableContainerInput,
EmbeddableOutput,
@@ -130,12 +130,12 @@ test('ApplyFilterAction is incompatible if the root container does not accept a
test('trying to execute on incompatible context throws an error ', async () => {
const { doStart, coreStart, setup } = testPlugin();
- const api = doStart();
const inspector = inspectorPluginMock.createStartContract();
const factory = new FilterableEmbeddableFactory();
setup.registerEmbeddableFactory(factory.type, factory);
+ const api = doStart();
const applyFilterAction = createFilterAction();
const parent = new HelloWorldContainer(
{ id: 'root', panels: {} },
diff --git a/src/plugins/embeddable/public/tests/container.test.ts b/src/plugins/embeddable/public/tests/container.test.ts
index 1ee52f4749135..87076399465d3 100644
--- a/src/plugins/embeddable/public/tests/container.test.ts
+++ b/src/plugins/embeddable/public/tests/container.test.ts
@@ -56,8 +56,6 @@ async function creatHelloWorldContainerAndEmbeddable(
const coreSetup = coreMock.createSetup();
const coreStart = coreMock.createStart();
const { setup, doStart, uiActions } = testPlugin(coreSetup, coreStart);
- const start = doStart();
-
const filterableFactory = new FilterableEmbeddableFactory();
const slowContactCardFactory = new SlowContactCardEmbeddableFactory({
execAction: uiActions.executeTriggerActions,
@@ -68,6 +66,8 @@ async function creatHelloWorldContainerAndEmbeddable(
setup.registerEmbeddableFactory(slowContactCardFactory.type, slowContactCardFactory);
setup.registerEmbeddableFactory(helloWorldFactory.type, helloWorldFactory);
+ const start = doStart();
+
const container = new HelloWorldContainer(containerInput, {
getActions: uiActions.getTriggerCompatibleActions,
getEmbeddableFactory: start.getEmbeddableFactory,
@@ -563,6 +563,13 @@ test('Container changes made directly after adding a new embeddable are propagat
const coreSetup = coreMock.createSetup();
const coreStart = coreMock.createStart();
const { setup, doStart, uiActions } = testPlugin(coreSetup, coreStart);
+
+ const factory = new SlowContactCardEmbeddableFactory({
+ loadTickCount: 3,
+ execAction: uiActions.executeTriggerActions,
+ });
+ setup.registerEmbeddableFactory(factory.type, factory);
+
const start = doStart();
const container = new HelloWorldContainer(
@@ -582,12 +589,6 @@ test('Container changes made directly after adding a new embeddable are propagat
}
);
- const factory = new SlowContactCardEmbeddableFactory({
- loadTickCount: 3,
- execAction: uiActions.executeTriggerActions,
- });
- setup.registerEmbeddableFactory(factory.type, factory);
-
const subscription = Rx.merge(container.getOutput$(), container.getInput$())
.pipe(skip(2))
.subscribe(() => {
@@ -759,12 +760,13 @@ test('untilEmbeddableLoaded resolves with undefined if child is subsequently rem
coreMock.createSetup(),
coreMock.createStart()
);
- const start = doStart();
const factory = new SlowContactCardEmbeddableFactory({
loadTickCount: 3,
execAction: uiActions.executeTriggerActions,
});
setup.registerEmbeddableFactory(factory.type, factory);
+
+ const start = doStart();
const container = new HelloWorldContainer(
{
id: 'hello',
@@ -799,12 +801,12 @@ test('adding a panel then subsequently removing it before its loaded removes the
coreMock.createSetup(),
coreMock.createStart()
);
- const start = doStart();
const factory = new SlowContactCardEmbeddableFactory({
loadTickCount: 1,
execAction: uiActions.executeTriggerActions,
});
setup.registerEmbeddableFactory(factory.type, factory);
+ const start = doStart();
const container = new HelloWorldContainer(
{
id: 'hello',
diff --git a/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx b/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx
index 99d5a7c747d15..19e461b8bde7e 100644
--- a/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx
+++ b/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx
@@ -47,15 +47,14 @@ beforeEach(async () => {
coreMock.createSetup(),
coreMock.createStart()
);
- api = doStart();
const contactCardFactory = new ContactCardEmbeddableFactory(
- {},
uiActions.executeTriggerActions,
{} as any
);
setup.registerEmbeddableFactory(contactCardFactory.type, contactCardFactory);
+ api = doStart();
container = new HelloWorldContainer(
{ id: '123', panels: {} },
{
diff --git a/src/plugins/embeddable/public/tests/explicit_input.test.ts b/src/plugins/embeddable/public/tests/explicit_input.test.ts
index f0a7219531b59..0e03db3ec8358 100644
--- a/src/plugins/embeddable/public/tests/explicit_input.test.ts
+++ b/src/plugins/embeddable/public/tests/explicit_input.test.ts
@@ -41,7 +41,6 @@ const { setup, doStart, coreStart, uiActions } = testPlugin(
coreMock.createSetup(),
coreMock.createStart()
);
-const start = doStart();
setup.registerEmbeddableFactory(FILTERABLE_EMBEDDABLE, new FilterableEmbeddableFactory());
const factory = new SlowContactCardEmbeddableFactory({
@@ -51,6 +50,8 @@ const factory = new SlowContactCardEmbeddableFactory({
setup.registerEmbeddableFactory(CONTACT_CARD_EMBEDDABLE, factory);
setup.registerEmbeddableFactory(HELLO_WORLD_EMBEDDABLE, new HelloWorldEmbeddableFactory());
+const start = doStart();
+
test('Explicit embeddable input mapped to undefined will default to inherited', async () => {
const derivedFilter: Filter = {
$state: { store: esFilters.FilterStateStore.APP_STATE },
diff --git a/src/plugins/embeddable/public/tests/get_embeddable_factories.test.ts b/src/plugins/embeddable/public/tests/get_embeddable_factories.test.ts
index 1a222cd548de7..1989d6356cbd1 100644
--- a/src/plugins/embeddable/public/tests/get_embeddable_factories.test.ts
+++ b/src/plugins/embeddable/public/tests/get_embeddable_factories.test.ts
@@ -35,16 +35,16 @@ test('returns empty list if there are no embeddable factories', () => {
test('returns existing embeddable factories', () => {
const { setup, doStart } = testPlugin();
- const start = doStart();
- const { length } = [...start.getEmbeddableFactories()];
- const factory1 = new FilterableContainerFactory(start.getEmbeddableFactory);
- const factory2 = new ContactCardEmbeddableFactory({} as any, (() => null) as any, {} as any);
+ const factory1 = new FilterableContainerFactory(async () => await start.getEmbeddableFactory);
+ const factory2 = new ContactCardEmbeddableFactory((() => null) as any, {} as any);
setup.registerEmbeddableFactory(factory1.type, factory1);
setup.registerEmbeddableFactory(factory2.type, factory2);
+ const start = doStart();
+
const list = [...start.getEmbeddableFactories()];
- expect(list.length - length).toBe(2);
+ expect(list.length).toBe(2);
expect(!!list.find(({ type }) => factory1.type === type)).toBe(true);
expect(!!list.find(({ type }) => factory2.type === type)).toBe(true);
});
diff --git a/src/plugins/embeddable/public/types.ts b/src/plugins/embeddable/public/types.ts
index 7a879c389f3c4..2d112b2359818 100644
--- a/src/plugins/embeddable/public/types.ts
+++ b/src/plugins/embeddable/public/types.ts
@@ -17,6 +17,22 @@
* under the License.
*/
-import { EmbeddableFactory } from './lib/embeddables';
+import { SavedObjectAttributes } from 'kibana/public';
+import {
+ EmbeddableFactory,
+ EmbeddableInput,
+ EmbeddableOutput,
+ IEmbeddable,
+ EmbeddableFactoryDefinition,
+} from './lib/embeddables';
export type EmbeddableFactoryRegistry = Map;
+
+export type EmbeddableFactoryProvider = <
+ I extends EmbeddableInput = EmbeddableInput,
+ O extends EmbeddableOutput = EmbeddableOutput,
+ E extends IEmbeddable = IEmbeddable,
+ T extends SavedObjectAttributes = SavedObjectAttributes
+>(
+ def: EmbeddableFactoryDefinition
+) => EmbeddableFactory;
diff --git a/src/plugins/es_ui_shared/public/request/np_ready_request.ts b/src/plugins/es_ui_shared/public/request/np_ready_request.ts
index 6771abd64df7e..06af698f2ce02 100644
--- a/src/plugins/es_ui_shared/public/request/np_ready_request.ts
+++ b/src/plugins/es_ui_shared/public/request/np_ready_request.ts
@@ -43,7 +43,7 @@ export interface UseRequestResponse {
isInitialRequest: boolean;
isLoading: boolean;
error: E | null;
- data: D | null;
+ data?: D | null;
sendRequest: (...args: any[]) => Promise>;
}
diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.ts
index a8d24984cec7c..0509b8081c35b 100644
--- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.ts
+++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.ts
@@ -28,9 +28,9 @@ interface Props {
}
export const FormDataProvider = React.memo(({ children, pathsToWatch }: Props) => {
- const [formData, setFormData] = useState({});
- const previousRawData = useRef({});
const form = useFormContext();
+ const previousRawData = useRef(form.__formData$.current.value);
+ const [formData, setFormData] = useState(previousRawData.current);
useEffect(() => {
const subscription = form.subscribe(({ data: { raw } }) => {
@@ -41,6 +41,7 @@ export const FormDataProvider = React.memo(({ children, pathsToWatch }: Props) =
const valuesToWatchArray = Array.isArray(pathsToWatch)
? (pathsToWatch as string[])
: ([pathsToWatch] as string[]);
+
if (valuesToWatchArray.some(value => previousRawData.current[value] !== raw[value])) {
previousRawData.current = raw;
setFormData(raw);
diff --git a/src/plugins/home/public/application/components/home.js b/src/plugins/home/public/application/components/home.js
index 6d00b1c6a5d14..77cde6a574aec 100644
--- a/src/plugins/home/public/application/components/home.js
+++ b/src/plugins/home/public/application/components/home.js
@@ -36,6 +36,7 @@ import {
EuiPageBody,
EuiScreenReaderOnly,
} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { Welcome } from './welcome';
import { getServices } from '../kibana_services';
@@ -69,6 +70,9 @@ export class Home extends Component {
componentDidMount() {
this._isMounted = true;
this.fetchIsNewKibanaInstance();
+
+ const homeTitle = i18n.translate('home.breadcrumbs.homeTitle', { defaultMessage: 'Home' });
+ getServices().chrome.setBreadcrumbs([{ text: homeTitle }]);
}
fetchIsNewKibanaInstance = async () => {
diff --git a/src/plugins/home/public/application/components/home.test.js b/src/plugins/home/public/application/components/home.test.js
index ca8297800b53e..99722df18e069 100644
--- a/src/plugins/home/public/application/components/home.test.js
+++ b/src/plugins/home/public/application/components/home.test.js
@@ -29,6 +29,9 @@ jest.mock('../kibana_services', () => ({
getBasePath: () => 'path',
tutorialVariables: () => ({}),
homeConfig: { disableWelcomeScreen: false },
+ chrome: {
+ setBreadcrumbs: () => {},
+ },
}),
}));
diff --git a/src/plugins/visualizations/public/embeddable/create_vis_embeddable_from_object.ts b/src/plugins/visualizations/public/embeddable/create_vis_embeddable_from_object.ts
new file mode 100644
index 0000000000000..bf2d174f594b2
--- /dev/null
+++ b/src/plugins/visualizations/public/embeddable/create_vis_embeddable_from_object.ts
@@ -0,0 +1,69 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { Vis } from '../types';
+import { VisualizeInput, VisualizeEmbeddable } from './visualize_embeddable';
+import { IContainer, ErrorEmbeddable } from '../../../../plugins/embeddable/public';
+import { DisabledLabEmbeddable } from './disabled_lab_embeddable';
+import {
+ getSavedVisualizationsLoader,
+ getUISettings,
+ getHttp,
+ getTimeFilter,
+ getCapabilities,
+} from '../services';
+
+export const createVisEmbeddableFromObject = async (
+ vis: Vis,
+ input: Partial & { id: string },
+ parent?: IContainer
+): Promise => {
+ const savedVisualizations = getSavedVisualizationsLoader();
+
+ try {
+ const visId = vis.id as string;
+
+ const editUrl = visId
+ ? getHttp().basePath.prepend(`/app/kibana${savedVisualizations.urlFor(visId)}`)
+ : '';
+ const isLabsEnabled = getUISettings().get('visualize:enableLabs');
+
+ if (!isLabsEnabled && vis.type.stage === 'experimental') {
+ return new DisabledLabEmbeddable(vis.title, input);
+ }
+
+ const indexPattern = vis.data.indexPattern;
+ const indexPatterns = indexPattern ? [indexPattern] : [];
+ const editable = getCapabilities().visualize.save as boolean;
+ return new VisualizeEmbeddable(
+ getTimeFilter(),
+ {
+ vis,
+ indexPatterns,
+ editUrl,
+ editable,
+ },
+ input,
+ parent
+ );
+ } catch (e) {
+ console.error(e); // eslint-disable-line no-console
+ return new ErrorEmbeddable(e, input, parent);
+ }
+};
diff --git a/src/plugins/visualizations/public/embeddable/index.ts b/src/plugins/visualizations/public/embeddable/index.ts
index 78f9827ffde3e..3753c4dbbb9ed 100644
--- a/src/plugins/visualizations/public/embeddable/index.ts
+++ b/src/plugins/visualizations/public/embeddable/index.ts
@@ -21,3 +21,4 @@ export { VisualizeEmbeddable, VisualizeInput } from './visualize_embeddable';
export { VisualizeEmbeddableFactory } from './visualize_embeddable_factory';
export { VISUALIZE_EMBEDDABLE_TYPE } from './constants';
export { VIS_EVENT_TO_TRIGGER } from './events';
+export { createVisEmbeddableFromObject } from './create_vis_embeddable_from_object';
diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts
index 0c7e732f0b185..e64d200251797 100644
--- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts
+++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts
@@ -33,8 +33,8 @@ import {
EmbeddableInput,
EmbeddableOutput,
Embeddable,
- Container,
EmbeddableVisTriggerContext,
+ IContainer,
} from '../../../../plugins/embeddable/public';
import { dispatchRenderComplete } from '../../../../plugins/kibana_utils/public';
import { IExpressionLoaderParams, ExpressionsStart } from '../../../../plugins/expressions/public';
@@ -89,7 +89,7 @@ export class VisualizeEmbeddable extends Embeddable {
+export class VisualizeEmbeddableFactory
+ implements
+ EmbeddableFactoryDefinition<
+ VisualizeInput,
+ VisualizeOutput | EmbeddableOutput,
+ VisualizeEmbeddable | DisabledLabEmbeddable,
+ VisualizationAttributes
+ > {
public readonly type = VISUALIZE_EMBEDDABLE_TYPE;
-
- constructor() {
- super({
- savedObjectMetaData: {
- name: i18n.translate('visualizations.savedObjectName', { defaultMessage: 'Visualization' }),
- includeFields: ['visState'],
- type: 'visualization',
- getIconForSavedObject: savedObject => {
- return (
- getTypes().get(JSON.parse(savedObject.attributes.visState).type).icon || 'visualizeApp'
- );
- },
- getTooltipForSavedObject: savedObject => {
- return `${savedObject.attributes.title} (${
- getTypes().get(JSON.parse(savedObject.attributes.visState).type).title
- })`;
- },
- showSavedObject: savedObject => {
- const typeName: string = JSON.parse(savedObject.attributes.visState).type;
- const visType = getTypes().get(typeName);
- if (!visType) {
- return false;
- }
- if (getUISettings().get('visualize:enableLabs')) {
- return true;
- }
- return visType.stage !== 'experimental';
- },
- },
- });
- }
+ public readonly savedObjectMetaData: SavedObjectMetaData = {
+ name: i18n.translate('visualizations.savedObjectName', { defaultMessage: 'Visualization' }),
+ includeFields: ['visState'],
+ type: 'visualization',
+ getIconForSavedObject: savedObject => {
+ return (
+ getTypes().get(JSON.parse(savedObject.attributes.visState).type).icon || 'visualizeApp'
+ );
+ },
+ getTooltipForSavedObject: savedObject => {
+ return `${savedObject.attributes.title} (${
+ getTypes().get(JSON.parse(savedObject.attributes.visState).type).title
+ })`;
+ },
+ showSavedObject: savedObject => {
+ const typeName: string = JSON.parse(savedObject.attributes.visState).type;
+ const visType = getTypes().get(typeName);
+ if (!visType) {
+ return false;
+ }
+ if (getUISettings().get('visualize:enableLabs')) {
+ return true;
+ }
+ return visType.stage !== 'experimental';
+ },
+ };
+ constructor() {}
public async isEditable() {
return getCapabilities().visualize.save as boolean;
@@ -93,56 +91,17 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory<
});
}
- public async createFromObject(
- vis: Vis,
- input: Partial & { id: string },
- parent?: Container
- ): Promise {
- const savedVisualizations = getSavedVisualizationsLoader();
-
- try {
- const visId = vis.id as string;
-
- const editUrl = visId
- ? getHttp().basePath.prepend(`/app/kibana${savedVisualizations.urlFor(visId)}`)
- : '';
- const isLabsEnabled = getUISettings().get('visualize:enableLabs');
-
- if (!isLabsEnabled && vis.type.stage === 'experimental') {
- return new DisabledLabEmbeddable(vis.title, input);
- }
-
- const indexPattern = vis.data.indexPattern;
- const indexPatterns = indexPattern ? [indexPattern] : [];
- const editable = await this.isEditable();
- return new VisualizeEmbeddable(
- getTimeFilter(),
- {
- vis,
- indexPatterns,
- editUrl,
- editable,
- },
- input,
- parent
- );
- } catch (e) {
- console.error(e); // eslint-disable-line no-console
- return new ErrorEmbeddable(e, input, parent);
- }
- }
-
public async createFromSavedObject(
savedObjectId: string,
input: Partial & { id: string },
- parent?: Container
+ parent?: IContainer
): Promise {
const savedVisualizations = getSavedVisualizationsLoader();
try {
const savedObject = await savedVisualizations.get(savedObjectId);
const vis = new Vis(savedObject.visState.type, await convertToSerializedVis(savedObject));
- return this.createFromObject(vis, input, parent);
+ return createVisEmbeddableFromObject(vis, input, parent);
} catch (e) {
console.error(e); // eslint-disable-line no-console
return new ErrorEmbeddable(e, input, parent);
diff --git a/src/plugins/visualizations/public/mocks.ts b/src/plugins/visualizations/public/mocks.ts
index f4983a4313c4d..2aa346423297a 100644
--- a/src/plugins/visualizations/public/mocks.ts
+++ b/src/plugins/visualizations/public/mocks.ts
@@ -43,6 +43,9 @@ const createStartContract = (): VisualizationsStart => ({
createVis: jest.fn(),
convertFromSerializedVis: jest.fn(),
convertToSerializedVis: jest.fn(),
+ __LEGACY: {
+ createVisEmbeddableFromObject: jest.fn(),
+ },
});
const createInstance = async () => {
diff --git a/src/plugins/visualizations/public/plugin.ts b/src/plugins/visualizations/public/plugin.ts
index d3e7b759a4416..216defcee9016 100644
--- a/src/plugins/visualizations/public/plugin.ts
+++ b/src/plugins/visualizations/public/plugin.ts
@@ -37,7 +37,11 @@ import {
setChrome,
setOverlays,
} from './services';
-import { VISUALIZE_EMBEDDABLE_TYPE, VisualizeEmbeddableFactory } from './embeddable';
+import {
+ VISUALIZE_EMBEDDABLE_TYPE,
+ VisualizeEmbeddableFactory,
+ createVisEmbeddableFromObject,
+} from './embeddable';
import { ExpressionsSetup, ExpressionsStart } from '../../../plugins/expressions/public';
import { EmbeddableSetup } from '../../../plugins/embeddable/public';
import { visualization as visualizationFunction } from './expressions/visualization_function';
@@ -69,6 +73,7 @@ export interface VisualizationsStart extends TypesStart {
convertToSerializedVis: typeof convertToSerializedVis;
convertFromSerializedVis: typeof convertFromSerializedVis;
showNewVisModal: typeof showNewVisModal;
+ __LEGACY: { createVisEmbeddableFromObject: typeof createVisEmbeddableFromObject };
}
export interface VisualizationsSetupDeps {
@@ -163,6 +168,7 @@ export class VisualizationsPlugin
convertToSerializedVis,
convertFromSerializedVis,
savedVisualizationsLoader,
+ __LEGACY: { createVisEmbeddableFromObject },
};
}
diff --git a/test/functional/apps/visualize/_vertical_bar_chart.js b/test/functional/apps/visualize/_vertical_bar_chart.js
index 9bb220a11a86a..d5f4c45f8bdbc 100644
--- a/test/functional/apps/visualize/_vertical_bar_chart.js
+++ b/test/functional/apps/visualize/_vertical_bar_chart.js
@@ -54,6 +54,25 @@ export default function({ getService, getPageObjects }) {
});
});
+ describe('bar charts range on x axis', () => {
+ it('should individual bars for each configured range', async function() {
+ await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.clickVerticalBarChart();
+ await PageObjects.visualize.clickNewSearch();
+ await PageObjects.timePicker.setDefaultAbsoluteRange();
+ await PageObjects.visEditor.clickBucket('X-axis');
+ log.debug('Aggregation = Date Range');
+ await PageObjects.visEditor.selectAggregation('Date Range');
+ log.debug('Field = @timestamp');
+ await PageObjects.visEditor.selectField('@timestamp');
+ await PageObjects.visEditor.clickAddDateRange();
+ await PageObjects.visEditor.setDateRangeByIndex('1', 'now-2w/w', 'now-1w/w');
+ await PageObjects.visEditor.clickGo();
+ const bottomLabels = await PageObjects.visChart.getXAxisLabels();
+ expect(bottomLabels.length).to.be(2);
+ });
+ });
+
// FLAKY: https://github.com/elastic/kibana/issues/22322
describe.skip('vertical bar chart flaky part', function() {
const vizName1 = 'Visualization VerticalBarChart';
diff --git a/test/functional/page_objects/visualize_editor_page.ts b/test/functional/page_objects/visualize_editor_page.ts
index b1c3e924b3c1b..41c12170cf4dc 100644
--- a/test/functional/page_objects/visualize_editor_page.ts
+++ b/test/functional/page_objects/visualize_editor_page.ts
@@ -103,6 +103,15 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP
await radioBtn.click();
}
+ public async clickAddDateRange() {
+ await testSubjects.click(`visEditorAddDateRange`);
+ }
+
+ public async setDateRangeByIndex(index: string, from: string, to: string) {
+ await testSubjects.setValue(`visEditorDateRange${index}__from`, from);
+ await testSubjects.setValue(`visEditorDateRange${index}__to`, to);
+ }
+
/**
* Adds new bucket
* @param bucketName bucket name, like 'X-axis', 'Split rows', 'Split series'
diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_container_example.tsx b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_container_example.tsx
index f8625e4490e51..fd07416cadbc5 100644
--- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_container_example.tsx
+++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_container_example.tsx
@@ -29,7 +29,6 @@ import {
import {
DASHBOARD_CONTAINER_TYPE,
DashboardContainer,
- DashboardContainerFactory,
DashboardContainerInput,
} from '../../../../../../../../src/plugins/dashboard/public';
@@ -70,8 +69,9 @@ export class DashboardContainerExample extends React.Component {
this.mounted = true;
const dashboardFactory = this.props.getEmbeddableFactory<
DashboardContainerInput,
- ContainerOutput
- >(DASHBOARD_CONTAINER_TYPE) as DashboardContainerFactory;
+ ContainerOutput,
+ DashboardContainer
+ >(DASHBOARD_CONTAINER_TYPE);
if (dashboardFactory) {
this.container = await dashboardFactory.create(dashboardInput);
if (this.mounted) {
diff --git a/x-pack/legacy/plugins/apm/e2e/package.json b/x-pack/legacy/plugins/apm/e2e/package.json
index 57500dfe3fdc8..150ad5bb13d0b 100644
--- a/x-pack/legacy/plugins/apm/e2e/package.json
+++ b/x-pack/legacy/plugins/apm/e2e/package.json
@@ -13,6 +13,7 @@
"@types/cypress-cucumber-preprocessor": "^1.14.1",
"@types/js-yaml": "^3.12.1",
"@types/node": "^10.12.11",
+ "axios": "^0.19.2",
"cypress": "^4.2.0",
"cypress-cucumber-preprocessor": "^2.0.1",
"js-yaml": "^3.13.1",
@@ -21,6 +22,7 @@
"p-retry": "^4.2.0",
"ts-loader": "^6.2.2",
"typescript": "3.8.3",
+ "yargs": "^15.3.1",
"wait-on": "^4.0.1",
"webpack": "^4.42.1"
}
diff --git a/x-pack/legacy/plugins/apm/e2e/run-e2e.sh b/x-pack/legacy/plugins/apm/e2e/run-e2e.sh
index 5e55dc1eb834d..7c17c14dc9601 100755
--- a/x-pack/legacy/plugins/apm/e2e/run-e2e.sh
+++ b/x-pack/legacy/plugins/apm/e2e/run-e2e.sh
@@ -75,6 +75,13 @@ if [ $? -ne 0 ]; then
exit 1
fi
+#
+# Cypress
+##################################################
+echo "\n${bold}Cypress (logs: ${TMP_DIR}/e2e-yarn.log)${normal}"
+echo "Installing cypress dependencies "
+yarn &> ${TMP_DIR}/e2e-yarn.log
+
#
# Static mock data
##################################################
@@ -99,13 +106,6 @@ if [ $? -ne 0 ]; then
exit 1
fi
-#
-# Cypress
-##################################################
-echo "\n${bold}Cypress (logs: ${TMP_DIR}/e2e-yarn.log)${normal}"
-echo "Installing cypress dependencies "
-yarn &> ${TMP_DIR}/e2e-yarn.log
-
#
# Wait for Kibana to start
##################################################
diff --git a/x-pack/legacy/plugins/apm/e2e/yarn.lock b/x-pack/legacy/plugins/apm/e2e/yarn.lock
index b7b531a9c73c0..c023c64eb1cf4 100644
--- a/x-pack/legacy/plugins/apm/e2e/yarn.lock
+++ b/x-pack/legacy/plugins/apm/e2e/yarn.lock
@@ -1288,7 +1288,7 @@ ansi-styles@^3.2.1:
dependencies:
color-convert "^1.9.0"
-ansi-styles@^4.1.0:
+ansi-styles@^4.0.0, ansi-styles@^4.1.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359"
integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==
@@ -1429,6 +1429,13 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e"
integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==
+axios@^0.19.2:
+ version "0.19.2"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27"
+ integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==
+ dependencies:
+ follow-redirects "1.5.10"
+
babel-loader@^8.0.2:
version "8.0.6"
resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.0.6.tgz#e33bdb6f362b03f4bb141a0c21ab87c501b70dfb"
@@ -1845,6 +1852,11 @@ cachedir@2.3.0:
resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8"
integrity sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==
+camelcase@^5.0.0:
+ version "5.3.1"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
+ integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
+
caniuse-lite@^1.0.30001023:
version "1.0.30001027"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001027.tgz#283e2ef17d94889cc216a22c6f85303d78ca852d"
@@ -2010,6 +2022,15 @@ cli-truncate@^0.2.1:
slice-ansi "0.0.4"
string-width "^1.0.1"
+cliui@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1"
+ integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==
+ dependencies:
+ string-width "^4.2.0"
+ strip-ansi "^6.0.0"
+ wrap-ansi "^6.2.0"
+
clone@^1.0.2:
version "1.0.4"
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
@@ -2417,7 +2438,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
dependencies:
ms "2.0.0"
-debug@3.1.0:
+debug@3.1.0, debug@=3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
@@ -2438,6 +2459,11 @@ debug@^3.0.1, debug@^3.1.0:
dependencies:
ms "^2.1.1"
+decamelize@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
+ integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
+
decode-uri-component@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
@@ -2621,6 +2647,11 @@ elliptic@^6.0.0:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.0"
+emoji-regex@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
+ integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+
emojis-list@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
@@ -2933,6 +2964,14 @@ find-up@^3.0.0:
dependencies:
locate-path "^3.0.0"
+find-up@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
+ integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
+ dependencies:
+ locate-path "^5.0.0"
+ path-exists "^4.0.0"
+
flush-write-stream@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8"
@@ -2951,6 +2990,13 @@ folktale@2.3.2:
resolved "https://registry.yarnpkg.com/folktale/-/folktale-2.3.2.tgz#38231b039e5ef36989920cbf805bf6b227bf4fd4"
integrity sha512-+8GbtQBwEqutP0v3uajDDoN64K2ehmHd0cjlghhxh0WpcfPzAIjPA03e1VvHlxL02FVGR0A6lwXsNQKn3H1RNQ==
+follow-redirects@1.5.10:
+ version "1.5.10"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
+ integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==
+ dependencies:
+ debug "=3.1.0"
+
for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@@ -3041,6 +3087,11 @@ get-assigned-identifiers@^1.2.0:
resolved "https://registry.yarnpkg.com/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz#6dbf411de648cbaf8d9169ebb0d2d576191e2ff1"
integrity sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==
+get-caller-file@^2.0.1:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
+ integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
+
get-func-name@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
@@ -3418,6 +3469,11 @@ is-fullwidth-code-point@^2.0.0:
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
+is-fullwidth-code-point@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
+ integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
+
is-generator@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/is-generator/-/is-generator-1.0.3.tgz#c14c21057ed36e328db80347966c693f886389f3"
@@ -3779,6 +3835,13 @@ locate-path@^3.0.0:
p-locate "^3.0.0"
path-exists "^3.0.0"
+locate-path@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
+ integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
+ dependencies:
+ p-locate "^4.1.0"
+
lodash.clonedeep@4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
@@ -4328,7 +4391,7 @@ p-finally@^1.0.0:
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
-p-limit@^2.0.0, p-limit@^2.2.1:
+p-limit@^2.0.0, p-limit@^2.2.0, p-limit@^2.2.1:
version "2.2.2"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e"
integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==
@@ -4342,6 +4405,13 @@ p-locate@^3.0.0:
dependencies:
p-limit "^2.0.0"
+p-locate@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
+ integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==
+ dependencies:
+ p-limit "^2.2.0"
+
p-map@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
@@ -4428,6 +4498,11 @@ path-exists@^3.0.0:
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=
+path-exists@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
+ integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
+
path-is-absolute@^1.0.0, path-is-absolute@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
@@ -4836,11 +4911,21 @@ request@cypress-io/request#b5af0d1fa47eec97ba980cde90a13e69a2afcd16:
tunnel-agent "^0.6.0"
uuid "^3.3.2"
+require-directory@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
+ integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
+
require-from-string@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
+require-main-filename@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
+ integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
+
resolve-url@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
@@ -4982,6 +5067,11 @@ serialize-javascript@^2.1.2:
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61"
integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==
+set-blocking@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
+ integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
+
set-value@^2.0.0, set-value@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b"
@@ -5316,6 +5406,15 @@ string-width@^2.1.1:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^4.0.0"
+string-width@^4.1.0, string-width@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5"
+ integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.0"
+
string_decoder@^1.0.0, string_decoder@^1.1.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
@@ -5856,6 +5955,11 @@ webpack@^4.42.1:
watchpack "^1.6.0"
webpack-sources "^1.4.1"
+which-module@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
+ integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
+
which@^1.2.9:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
@@ -5878,6 +5982,15 @@ wrap-ansi@^3.0.1:
string-width "^2.1.1"
strip-ansi "^4.0.0"
+wrap-ansi@^6.2.0:
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
+ integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
@@ -5903,6 +6016,31 @@ yallist@^3.0.2:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
+yargs-parser@^18.1.1:
+ version "18.1.2"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.2.tgz#2f482bea2136dbde0861683abea7756d30b504f1"
+ integrity sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ==
+ dependencies:
+ camelcase "^5.0.0"
+ decamelize "^1.2.0"
+
+yargs@^15.3.1:
+ version "15.3.1"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.3.1.tgz#9505b472763963e54afe60148ad27a330818e98b"
+ integrity sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==
+ dependencies:
+ cliui "^6.0.0"
+ decamelize "^1.2.0"
+ find-up "^4.1.0"
+ get-caller-file "^2.0.1"
+ require-directory "^2.1.1"
+ require-main-filename "^2.0.0"
+ set-blocking "^2.0.0"
+ string-width "^4.2.0"
+ which-module "^2.0.0"
+ y18n "^4.0.0"
+ yargs-parser "^18.1.1"
+
yauzl@2.10.0:
version "2.10.0"
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts b/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts
index 1caea1b4b728f..99a59c756e228 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts
@@ -18,7 +18,7 @@ import {
} from '../../../../../../../src/plugins/data/public';
import { ReactExpressionRendererType } from '../../../../../../../src/plugins/expressions/public';
import {
- EmbeddableFactory as AbstractEmbeddableFactory,
+ EmbeddableFactoryDefinition,
ErrorEmbeddable,
EmbeddableInput,
IContainer,
@@ -36,25 +36,22 @@ interface StartServices {
indexPatternService: IndexPatternsContract;
}
-export class EmbeddableFactory extends AbstractEmbeddableFactory {
+export class EmbeddableFactory implements EmbeddableFactoryDefinition {
type = DOC_TYPE;
+ savedObjectMetaData = {
+ name: i18n.translate('xpack.lens.lensSavedObjectLabel', {
+ defaultMessage: 'Lens Visualization',
+ }),
+ type: DOC_TYPE,
+ getIconForSavedObject: () => 'lensApp',
+ };
- constructor(private getStartServices: () => Promise) {
- super({
- savedObjectMetaData: {
- name: i18n.translate('xpack.lens.lensSavedObjectLabel', {
- defaultMessage: 'Lens Visualization',
- }),
- type: DOC_TYPE,
- getIconForSavedObject: () => 'lensApp',
- },
- });
- }
+ constructor(private getStartServices: () => Promise) {}
- public async isEditable() {
+ public isEditable = async () => {
const { capabilities } = await this.getStartServices();
return capabilities.visualize.save as boolean;
- }
+ };
canCreateNew() {
return false;
@@ -66,11 +63,11 @@ export class EmbeddableFactory extends AbstractEmbeddableFactory {
});
}
- async createFromSavedObject(
+ createFromSavedObject = async (
savedObjectId: string,
input: Partial & { id: string },
parent?: IContainer
- ) {
+ ) => {
const {
savedObjectsClient,
coreHttp,
@@ -111,7 +108,7 @@ export class EmbeddableFactory extends AbstractEmbeddableFactory {
input,
parent
);
- }
+ };
async create(input: EmbeddableInput) {
return new ErrorEmbeddable('Lens can only be created from a saved object', input);
diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization/xy_expression.test.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization/xy_expression.test.tsx
index cdc5fc2ff1c17..54abc2c2bb667 100644
--- a/x-pack/legacy/plugins/lens/public/xy_visualization/xy_expression.test.tsx
+++ b/x-pack/legacy/plugins/lens/public/xy_visualization/xy_expression.test.tsx
@@ -18,6 +18,10 @@ import {
} from '@elastic/charts';
import { xyChart, XYChart } from './xy_expression';
import { LensMultiTable } from '../types';
+import {
+ KibanaDatatable,
+ KibanaDatatableRow,
+} from '../../../../../../src/plugins/expressions/public';
import React from 'react';
import { shallow } from 'enzyme';
import { XYArgs, LegendConfig, legendConfig, layerConfig, LayerArgs } from './types';
@@ -26,57 +30,61 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers';
const executeTriggerActions = jest.fn();
+const createSampleDatatableWithRows = (rows: KibanaDatatableRow[]): KibanaDatatable => ({
+ type: 'kibana_datatable',
+ columns: [
+ {
+ id: 'a',
+ name: 'a',
+ formatHint: { id: 'number', params: { pattern: '0,0.000' } },
+ },
+ { id: 'b', name: 'b', formatHint: { id: 'number', params: { pattern: '000,0' } } },
+ {
+ id: 'c',
+ name: 'c',
+ formatHint: { id: 'string' },
+ meta: { type: 'date-histogram', aggConfigParams: { interval: '10s' } },
+ },
+ { id: 'd', name: 'ColD', formatHint: { id: 'string' } },
+ ],
+ rows,
+});
+
+const sampleLayer: LayerArgs = {
+ layerId: 'first',
+ seriesType: 'line',
+ xAccessor: 'c',
+ accessors: ['a', 'b'],
+ splitAccessor: 'd',
+ columnToLabel: '{"a": "Label A", "b": "Label B", "d": "Label D"}',
+ xScaleType: 'ordinal',
+ yScaleType: 'linear',
+ isHistogram: false,
+};
+
+const createArgsWithLayers = (layers: LayerArgs[] = [sampleLayer]): XYArgs => ({
+ xTitle: '',
+ yTitle: '',
+ legend: {
+ type: 'lens_xy_legendConfig',
+ isVisible: false,
+ position: Position.Top,
+ },
+ layers,
+});
+
function sampleArgs() {
const data: LensMultiTable = {
type: 'lens_multitable',
tables: {
- first: {
- type: 'kibana_datatable',
- columns: [
- {
- id: 'a',
- name: 'a',
- formatHint: { id: 'number', params: { pattern: '0,0.000' } },
- },
- { id: 'b', name: 'b', formatHint: { id: 'number', params: { pattern: '000,0' } } },
- {
- id: 'c',
- name: 'c',
- formatHint: { id: 'string' },
- meta: { type: 'date-histogram', aggConfigParams: { interval: '10s' } },
- },
- { id: 'd', name: 'ColD', formatHint: { id: 'string' } },
- ],
- rows: [
- { a: 1, b: 2, c: 'I', d: 'Foo' },
- { a: 1, b: 5, c: 'J', d: 'Bar' },
- ],
- },
+ first: createSampleDatatableWithRows([
+ { a: 1, b: 2, c: 'I', d: 'Foo' },
+ { a: 1, b: 5, c: 'J', d: 'Bar' },
+ ]),
},
};
- const args: XYArgs = {
- xTitle: '',
- yTitle: '',
- legend: {
- type: 'lens_xy_legendConfig',
- isVisible: false,
- position: Position.Top,
- },
- layers: [
- {
- layerId: 'first',
- seriesType: 'line',
- xAccessor: 'c',
- accessors: ['a', 'b'],
- splitAccessor: 'd',
- columnToLabel: '{"a": "Label A", "b": "Label B", "d": "Label D"}',
- xScaleType: 'ordinal',
- yScaleType: 'linear',
- isHistogram: false,
- },
- ],
- };
+ const args: XYArgs = createArgsWithLayers();
return { data, args };
}
@@ -158,35 +166,205 @@ describe('xy_expression', () => {
expect(component.find(LineSeries)).toHaveLength(1);
});
- test('it uses the full date range', () => {
- const { data, args } = sampleArgs();
+ describe('date range', () => {
+ const timeSampleLayer: LayerArgs = {
+ layerId: 'first',
+ seriesType: 'line',
+ xAccessor: 'c',
+ accessors: ['a', 'b'],
+ splitAccessor: 'd',
+ columnToLabel: '{"a": "Label A", "b": "Label B", "d": "Label D"}',
+ xScaleType: 'time',
+ yScaleType: 'linear',
+ isHistogram: false,
+ };
+ const multiLayerArgs = createArgsWithLayers([
+ timeSampleLayer,
+ {
+ ...timeSampleLayer,
+ layerId: 'second',
+ seriesType: 'bar',
+ xScaleType: 'time',
+ },
+ ]);
+ test('it uses the full date range', () => {
+ const { data, args } = sampleArgs();
+
+ const component = shallow(
+
+ );
+ expect(component.find(Settings).prop('xDomain')).toMatchInlineSnapshot(`
+ Object {
+ "max": 1546491600000,
+ "min": 1546405200000,
+ "minInterval": undefined,
+ }
+ `);
+ });
- const component = shallow(
-
- );
- expect(component.find(Settings).prop('xDomain')).toMatchInlineSnapshot(`
+ test('it generates correct xDomain for a layer with single value and a layer with no data (1-0) ', () => {
+ const data: LensMultiTable = {
+ type: 'lens_multitable',
+ tables: {
+ first: createSampleDatatableWithRows([{ a: 1, b: 2, c: 'I', d: 'Foo' }]),
+ second: createSampleDatatableWithRows([]),
+ },
+ };
+
+ const component = shallow(
+
+ );
+
+ expect(component.find(Settings).prop('xDomain')).toMatchInlineSnapshot(`
+ Object {
+ "max": 1546491600000,
+ "min": 1546405200000,
+ "minInterval": 10000,
+ }
+ `);
+ });
+
+ test('it generates correct xDomain for two layers with single value(1-1)', () => {
+ const data: LensMultiTable = {
+ type: 'lens_multitable',
+ tables: {
+ first: createSampleDatatableWithRows([{ a: 1, b: 2, c: 'I', d: 'Foo' }]),
+ second: createSampleDatatableWithRows([{ a: 10, b: 5, c: 'J', d: 'Bar' }]),
+ },
+ };
+ const component = shallow(
+
+ );
+
+ expect(component.find(Settings).prop('xDomain')).toMatchInlineSnapshot(`
Object {
"max": 1546491600000,
"min": 1546405200000,
"minInterval": 10000,
}
`);
+ });
+ test('it generates correct xDomain for a layer with single value and layer with multiple value data (1-n)', () => {
+ const data: LensMultiTable = {
+ type: 'lens_multitable',
+ tables: {
+ first: createSampleDatatableWithRows([{ a: 1, b: 2, c: 'I', d: 'Foo' }]),
+ second: createSampleDatatableWithRows([
+ { a: 10, b: 5, c: 'J', d: 'Bar' },
+ { a: 8, b: 5, c: 'K', d: 'Buzz' },
+ ]),
+ },
+ };
+ const component = shallow(
+
+ );
+
+ expect(component.find(Settings).prop('xDomain')).toMatchInlineSnapshot(`
+ Object {
+ "max": 1546491600000,
+ "min": 1546405200000,
+ "minInterval": undefined,
+ }
+ `);
+ });
+
+ test('it generates correct xDomain for 2 layers with multiple value data (n-n)', () => {
+ const data: LensMultiTable = {
+ type: 'lens_multitable',
+ tables: {
+ first: createSampleDatatableWithRows([
+ { a: 1, b: 2, c: 'I', d: 'Foo' },
+ { a: 8, b: 5, c: 'K', d: 'Buzz' },
+ { a: 9, b: 7, c: 'L', d: 'Bar' },
+ { a: 10, b: 2, c: 'G', d: 'Bear' },
+ ]),
+ second: createSampleDatatableWithRows([
+ { a: 10, b: 5, c: 'J', d: 'Bar' },
+ { a: 8, b: 4, c: 'K', d: 'Fi' },
+ { a: 1, b: 8, c: 'O', d: 'Pi' },
+ ]),
+ },
+ };
+ const component = shallow(
+
+ );
+
+ expect(component.find(Settings).prop('xDomain')).toMatchInlineSnapshot(`
+ Object {
+ "max": 1546491600000,
+ "min": 1546405200000,
+ "minInterval": undefined,
+ }
+ `);
+ });
});
test('it does not use date range if the x is not a time scale', () => {
diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization/xy_expression.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization/xy_expression.tsx
index a7d4b2a217f37..f5798688badc5 100644
--- a/x-pack/legacy/plugins/lens/public/xy_visualization/xy_expression.tsx
+++ b/x-pack/legacy/plugins/lens/public/xy_visualization/xy_expression.tsx
@@ -211,14 +211,19 @@ export function XYChart({
const shouldRotate = isHorizontalChart(layers);
const xTitle = (xAxisColumn && xAxisColumn.name) || args.xTitle;
- const interval = parseInterval(xAxisColumn?.meta?.aggConfigParams?.interval);
+
+ // add minInterval only for single row value as it cannot be determined from dataset
+
+ const minInterval = layers.every(layer => data.tables[layer.layerId].rows.length <= 1)
+ ? parseInterval(xAxisColumn?.meta?.aggConfigParams?.interval)?.asMilliseconds()
+ : undefined;
const xDomain =
data.dateRange && layers.every(l => l.xScaleType === 'time')
? {
min: data.dateRange.fromDate.getTime(),
max: data.dateRange.toDate.getTime(),
- minInterval: interval?.asMilliseconds(),
+ minInterval,
}
: undefined;
return (
diff --git a/x-pack/legacy/plugins/maps/public/actions/map_actions.js b/x-pack/legacy/plugins/maps/public/actions/map_actions.js
index 415630d9f730b..aa55cf0808ef2 100644
--- a/x-pack/legacy/plugins/maps/public/actions/map_actions.js
+++ b/x-pack/legacy/plugins/maps/public/actions/map_actions.js
@@ -174,9 +174,16 @@ export function removeTrackedLayerStateForSelectedLayer() {
export function replaceLayerList(newLayerList) {
return (dispatch, getState) => {
- getLayerListRaw(getState()).forEach(({ id }) => {
- dispatch(removeLayerFromLayerList(id));
- });
+ const isMapReady = getMapReady(getState());
+ if (!isMapReady) {
+ dispatch({
+ type: CLEAR_WAITING_FOR_MAP_READY_LAYER_LIST,
+ });
+ } else {
+ getLayerListRaw(getState()).forEach(({ id }) => {
+ dispatch(removeLayerFromLayerList(id));
+ });
+ }
newLayerList.forEach(layerDescriptor => {
dispatch(addLayer(layerDescriptor));
diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/import_editor/view.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/import_editor/view.js
index e9ef38e17b188..762409b256286 100644
--- a/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/import_editor/view.js
+++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/import_editor/view.js
@@ -5,13 +5,12 @@
*/
import React, { Fragment } from 'react';
-import { GeojsonFileSource } from '../../../layers/sources/client_file_source';
import { EuiSpacer, EuiPanel, EuiButtonEmpty } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
+import { uploadLayerWizardConfig } from '../../../layers/sources/client_file_source';
export const ImportEditor = ({ clearSource, isIndexingTriggered, ...props }) => {
const editorProperties = getEditorProperties({ isIndexingTriggered, ...props });
- const editor = GeojsonFileSource.renderEditor(editorProperties);
return (
{isIndexingTriggered ? null : (
@@ -25,7 +24,9 @@ export const ImportEditor = ({ clearSource, isIndexingTriggered, ...props }) =>
)}
- {editor}
+
+ {uploadLayerWizardConfig.renderWizard(editorProperties)}
+
);
};
diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/source_editor/view.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/source_editor/view.js
index 45c508e0d5889..50312b68277fa 100644
--- a/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/source_editor/view.js
+++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/source_editor/view.js
@@ -5,28 +5,20 @@
*/
import React, { Fragment } from 'react';
-import { ALL_SOURCES } from '../../../layers/sources/all_sources';
import { EuiSpacer, EuiPanel, EuiButtonEmpty } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
export const SourceEditor = ({
clearSource,
- sourceType,
+ layerWizard,
isIndexingTriggered,
inspectorAdapters,
previewLayer,
}) => {
- const editorProperties = {
- onPreviewSource: previewLayer,
- inspectorAdapters,
- };
- const Source = ALL_SOURCES.find(Source => {
- return Source.type === sourceType;
- });
- if (!Source) {
- throw new Error(`Unexpected source type: ${sourceType}`);
+ if (!layerWizard) {
+ return null;
}
- const editor = Source.renderEditor(editorProperties);
+
return (
{isIndexingTriggered ? null : (
@@ -40,7 +32,9 @@ export const SourceEditor = ({
)}
- {editor}
+
+ {layerWizard.renderWizard({ onPreviewSource: previewLayer, inspectorAdapters })}
+
);
};
diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/source_select/source_select.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/source_select/source_select.js
index 574a57b1041a0..b34a432bec88c 100644
--- a/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/source_select/source_select.js
+++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/source_select/source_select.js
@@ -5,30 +5,33 @@
*/
import React, { Fragment } from 'react';
-import { ALL_SOURCES } from '../../../layers/sources/all_sources';
+import { getLayerWizards } from '../../../layers/layer_wizard_registry';
import { EuiTitle, EuiSpacer, EuiCard, EuiIcon } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import _ from 'lodash';
export function SourceSelect({ updateSourceSelection }) {
- const sourceCards = ALL_SOURCES.map(Source => {
- const icon = Source.icon ? : null;
+ const sourceCards = getLayerWizards().map(layerWizard => {
+ const icon = layerWizard.icon ? : null;
- const sourceTitle = Source.title;
+ const onClick = () => {
+ updateSourceSelection({
+ layerWizard: layerWizard,
+ isIndexingSource: !!layerWizard.isIndexingSource,
+ });
+ };
return (
-
+
- updateSourceSelection({ type: Source.type, isIndexingSource: Source.isIndexingSource })
- }
- description={Source.description}
+ onClick={onClick}
+ description={layerWizard.description}
layout="horizontal"
- data-test-subj={_.camelCase(Source.title)}
+ data-test-subj={_.camelCase(layerWizard.title)}
/>
);
diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/view.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/view.js
index 425cc1cae3649..a54df69471aa0 100644
--- a/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/view.js
+++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/view.js
@@ -14,7 +14,7 @@ import { i18n } from '@kbn/i18n';
export class AddLayerPanel extends Component {
state = {
- sourceType: null,
+ layerWizard: null,
layer: null,
importView: false,
layerImportAddReady: false,
@@ -35,9 +35,9 @@ export class AddLayerPanel extends Component {
}
_getPanelDescription() {
- const { sourceType, importView, layerImportAddReady } = this.state;
+ const { layerWizard, importView, layerImportAddReady } = this.state;
let panelDescription;
- if (!sourceType) {
+ if (!layerWizard) {
panelDescription = i18n.translate('xpack.maps.addLayerPanel.selectSource', {
defaultMessage: 'Select source',
});
@@ -85,13 +85,13 @@ export class AddLayerPanel extends Component {
this.setState({
layer: null,
- ...(!keepSourceType ? { sourceType: null, importView: false } : {}),
+ ...(!keepSourceType ? { layerWizard: null, importView: false } : {}),
});
this.props.removeTransientLayer();
};
- _onSourceSelectionChange = ({ type, isIndexingSource }) => {
- this.setState({ sourceType: type, importView: isIndexingSource });
+ _onSourceSelectionChange = ({ layerWizard, isIndexingSource }) => {
+ this.setState({ layerWizard, importView: isIndexingSource });
};
_layerAddHandler = () => {
@@ -118,8 +118,8 @@ export class AddLayerPanel extends Component {
};
_renderAddLayerPanel() {
- const { sourceType, importView } = this.state;
- if (!sourceType) {
+ const { layerWizard, importView } = this.state;
+ if (!layerWizard) {
return ;
}
if (importView) {
@@ -134,7 +134,7 @@ export class AddLayerPanel extends Component {
return (
);
@@ -148,7 +148,7 @@ export class AddLayerPanel extends Component {
return (
Custom tooltip content ;
}
-const mapEmbeddable = await factory.createFromState(state, input, parent, renderTooltipContent);
+const mapEmbeddable = await factory.create(input, parent)
+mapEmbeddable.setLayerList(layerList);
+mapEmbeddable.setRenderTooltipContent(renderTooltipContent);
```
@@ -80,7 +80,10 @@ const eventHandlers = {
},
}
-const mapEmbeddable = await factory.createFromState(state, input, parent, renderTooltipContent, eventHandlers);
+const mapEmbeddable = await factory.create(input, parent);
+mapEmbeddable.setLayerList(layerList);
+mapEmbeddable.setRenderTooltipContent(renderTooltipContent);
+mapEmbeddable.setEventHandlers(eventHandlers);
```
@@ -90,55 +93,13 @@ Geojson sources will not update unless you modify `__featureCollection` property
```
const factory = new MapEmbeddableFactory();
-const state = {
- layerList: [
- {
- 'id': 'gaxya',
- 'label': 'My geospatial data',
- 'minZoom': 0,
- 'maxZoom': 24,
- 'alpha': 1,
- 'sourceDescriptor': {
- 'id': 'b7486',
- 'type': 'GEOJSON_FILE',
- '__featureCollection': {
- "type": "FeatureCollection",
- "features": [
- {
- "type": "Feature",
- "geometry": {
- "type": "Polygon",
- "coordinates": [
- [
- [0, 0], [10, 10], [10, 0], [0, 0]
- ]
- ]
- },
- "properties": {
- "name": "null island",
- "another_prop": "something else interesting"
- }
- }
- ]
- }
- },
- 'visible': true,
- 'style': {
- 'type': 'VECTOR',
- 'properties': {}
- },
- 'type': 'VECTOR'
- }
- ],
- title: 'my map',
-}
const input = {
hideFilterActions: true,
isLayerTOCOpen: false,
openTOCDetails: ['tfi3f', 'edh66'],
mapCenter: { lat: 0.0, lon: 0.0, zoom: 7 }
}
-const mapEmbeddable = await factory.createFromState(state, input, parent);
+const mapEmbeddable = await factory.create(input, parent);
mapEmbeddable.setLayerList([
{
diff --git a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.tsx
index 3c9069c7a836f..9544e8714f265 100644
--- a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.tsx
+++ b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.tsx
@@ -129,6 +129,14 @@ export class MapEmbeddable extends Embeddable this.onContainerStateChanged(input));
}
+ setRenderTooltipContent = (renderTooltipContent: RenderToolTipContent) => {
+ this._renderTooltipContent = renderTooltipContent;
+ };
+
+ setEventHandlers = (eventHandlers: EventHandlers) => {
+ this._eventHandlers = eventHandlers;
+ };
+
getInspectorAdapters() {
return getInspectorAdapters(this._store.getState());
}
diff --git a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.ts b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.ts
index b9cb66f831281..5a036ed47fb62 100644
--- a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.ts
+++ b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.ts
@@ -14,8 +14,7 @@ import { IIndexPattern } from 'src/plugins/data/public';
import { MapEmbeddable, MapEmbeddableInput } from './map_embeddable';
import { getIndexPatternService } from '../kibana_services';
import {
- EmbeddableFactory,
- ErrorEmbeddable,
+ EmbeddableFactoryDefinition,
IContainer,
} from '../../../../../../src/plugins/embeddable/public';
@@ -28,25 +27,17 @@ import { getInitialLayers } from '../angular/get_initial_layers';
import { mergeInputWithSavedMap } from './merge_input_with_saved_map';
import '../angular/services/gis_map_saved_object_loader';
import { bindSetupCoreAndPlugins, bindStartCoreAndPlugins } from '../plugin';
-import { RenderToolTipContent } from '../layers/tooltips/tooltip_property';
-import {
- EventHandlers,
- // eslint-disable-next-line @kbn/eslint/no-restricted-paths
-} from '../../../../../plugins/maps/public/reducers/non_serializable_instances';
-export class MapEmbeddableFactory extends EmbeddableFactory {
+export class MapEmbeddableFactory implements EmbeddableFactoryDefinition {
type = MAP_SAVED_OBJECT_TYPE;
-
+ savedObjectMetaData = {
+ name: i18n.translate('xpack.maps.mapSavedObjectLabel', {
+ defaultMessage: 'Map',
+ }),
+ type: MAP_SAVED_OBJECT_TYPE,
+ getIconForSavedObject: () => APP_ICON,
+ };
constructor() {
- super({
- savedObjectMetaData: {
- name: i18n.translate('xpack.maps.mapSavedObjectLabel', {
- defaultMessage: 'Map',
- }),
- type: MAP_SAVED_OBJECT_TYPE,
- getIconForSavedObject: () => APP_ICON,
- },
- });
// Init required services. Necessary while in legacy
bindSetupCoreAndPlugins(npSetup.core, npSetup.plugins);
bindStartCoreAndPlugins(npStart.core, npStart.plugins);
@@ -103,11 +94,11 @@ export class MapEmbeddableFactory extends EmbeddableFactory {
return await savedObjectLoader.get(savedObjectId);
}
- async createFromSavedObject(
+ createFromSavedObject = async (
savedObjectId: string,
input: MapEmbeddableInput,
parent?: IContainer
- ) {
+ ) => {
const savedMap = await this._fetchSavedMap(savedObjectId);
const layerList = getInitialLayers(savedMap.layerListJSON);
const indexPatterns = await this._getIndexPatterns(layerList);
@@ -135,39 +126,23 @@ export class MapEmbeddableFactory extends EmbeddableFactory {
}
return embeddable;
- }
+ };
- async createFromState(
- state: { title?: string; layerList?: unknown[] },
- input: MapEmbeddableInput,
- parent: IContainer,
- renderTooltipContent: RenderToolTipContent,
- eventHandlers: EventHandlers
- ) {
- const layerList = state && state.layerList ? state.layerList : getInitialLayers();
+ create = async (input: MapEmbeddableInput, parent?: IContainer) => {
+ const layerList = getInitialLayers();
const indexPatterns = await this._getIndexPatterns(layerList);
return new MapEmbeddable(
{
layerList,
- title: state && state.title ? state.title : '',
+ title: input.title ?? '',
indexPatterns,
editable: false,
},
input,
- parent,
- renderTooltipContent,
- eventHandlers
- );
- }
-
- async create(input: MapEmbeddableInput) {
- window.location.href = chrome.addBasePath(createMapPath(''));
- return new ErrorEmbeddable(
- 'Maps can only be created with createFromSavedObject or createFromState',
- input
+ parent
);
- }
+ };
}
npSetup.plugins.embeddable.registerEmbeddableFactory(
diff --git a/x-pack/legacy/plugins/maps/public/layers/layer.d.ts b/x-pack/legacy/plugins/maps/public/layers/layer.d.ts
index 777566298e607..de59642ede8ab 100644
--- a/x-pack/legacy/plugins/maps/public/layers/layer.d.ts
+++ b/x-pack/legacy/plugins/maps/public/layers/layer.d.ts
@@ -3,12 +3,13 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import { LayerDescriptor } from '../../common/descriptor_types';
+import { LayerDescriptor, MapExtent, MapFilters } from '../../common/descriptor_types';
import { ISource } from './sources/source';
import { DataRequest } from './util/data_request';
import { SyncContext } from '../actions/map_actions';
export interface ILayer {
+ getBounds(mapFilters: MapFilters): Promise;
getDataRequest(id: string): DataRequest | undefined;
getDisplayName(source?: ISource): Promise;
getId(): string;
@@ -25,6 +26,7 @@ export interface ILayerArguments {
export class AbstractLayer implements ILayer {
constructor(layerArguments: ILayerArguments);
+ getBounds(mapFilters: MapFilters): Promise;
getDataRequest(id: string): DataRequest | undefined;
getDisplayName(source?: ISource): Promise;
getId(): string;
diff --git a/x-pack/legacy/plugins/maps/public/layers/layer.js b/x-pack/legacy/plugins/maps/public/layers/layer.js
index d162e342dfd1a..e9616be89b601 100644
--- a/x-pack/legacy/plugins/maps/public/layers/layer.js
+++ b/x-pack/legacy/plugins/maps/public/layers/layer.js
@@ -320,12 +320,12 @@ export class AbstractLayer {
return sourceDataRequest && sourceDataRequest.hasData();
}
- async getBounds() {
+ async getBounds(/* mapFilters: MapFilters */) {
return {
- min_lon: -180,
- max_lon: 180,
- min_lat: -89,
- max_lat: 89,
+ minLon: -180,
+ maxLon: 180,
+ minLat: -89,
+ maxLat: 89,
};
}
diff --git a/x-pack/legacy/plugins/maps/public/layers/layer_wizard_registry.ts b/x-pack/legacy/plugins/maps/public/layers/layer_wizard_registry.ts
new file mode 100644
index 0000000000000..3ef4701269994
--- /dev/null
+++ b/x-pack/legacy/plugins/maps/public/layers/layer_wizard_registry.ts
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+/* eslint-disable @typescript-eslint/consistent-type-definitions */
+
+type LayerWizard = {
+ description: string;
+ icon: string;
+ isIndexingSource?: boolean;
+ renderWizard({
+ onPreviewSource,
+ inspectorAdapters,
+ }: {
+ onPreviewSource: () => void;
+ inspectorAdapters: unknown;
+ }): unknown;
+ title: string;
+};
+
+const registry: LayerWizard[] = [];
+
+export function registerLayerWizard(layerWizard: LayerWizard) {
+ registry.push(layerWizard);
+}
+
+export function getLayerWizards(): LayerWizard[] {
+ return [...registry];
+}
diff --git a/x-pack/legacy/plugins/maps/public/layers/load_layer_wizards.js b/x-pack/legacy/plugins/maps/public/layers/load_layer_wizards.js
new file mode 100644
index 0000000000000..d0169165eaa35
--- /dev/null
+++ b/x-pack/legacy/plugins/maps/public/layers/load_layer_wizards.js
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { registerLayerWizard } from './layer_wizard_registry';
+import { uploadLayerWizardConfig } from './sources/client_file_source';
+import { esDocumentsLayerWizardConfig } from './sources/es_search_source';
+import { clustersLayerWizardConfig, heatmapLayerWizardConfig } from './sources/es_geo_grid_source';
+import { point2PointLayerWizardConfig } from './sources/es_pew_pew_source/es_pew_pew_source';
+import { emsBoundariesLayerWizardConfig } from './sources/ems_file_source';
+import { emsBaseMapLayerWizardConfig } from './sources/ems_tms_source';
+import { kibanaRegionMapLayerWizardConfig } from './sources/kibana_regionmap_source';
+import { kibanaBasemapLayerWizardConfig } from './sources/kibana_tilemap_source';
+import { tmsLayerWizardConfig } from './sources/xyz_tms_source';
+import { wmsLayerWizardConfig } from './sources/wms_source';
+
+// Registration order determines display order
+registerLayerWizard(uploadLayerWizardConfig);
+registerLayerWizard(esDocumentsLayerWizardConfig);
+registerLayerWizard(clustersLayerWizardConfig);
+registerLayerWizard(heatmapLayerWizardConfig);
+registerLayerWizard(point2PointLayerWizardConfig);
+registerLayerWizard(emsBoundariesLayerWizardConfig);
+registerLayerWizard(emsBaseMapLayerWizardConfig);
+registerLayerWizard(kibanaRegionMapLayerWizardConfig);
+registerLayerWizard(kibanaBasemapLayerWizardConfig);
+registerLayerWizard(tmsLayerWizardConfig);
+registerLayerWizard(wmsLayerWizardConfig);
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/all_sources.js b/x-pack/legacy/plugins/maps/public/layers/sources/all_sources.js
deleted file mode 100644
index 6a518609dd77f..0000000000000
--- a/x-pack/legacy/plugins/maps/public/layers/sources/all_sources.js
+++ /dev/null
@@ -1,29 +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;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { EMSFileSource } from './ems_file_source';
-import { GeojsonFileSource } from './client_file_source';
-import { KibanaRegionmapSource } from './kibana_regionmap_source';
-import { XYZTMSSource } from './xyz_tms_source';
-import { EMSTMSSource } from './ems_tms_source';
-import { WMSSource } from './wms_source';
-import { KibanaTilemapSource } from './kibana_tilemap_source';
-import { ESGeoGridSource } from './es_geo_grid_source';
-import { ESSearchSource } from './es_search_source';
-import { ESPewPewSource } from './es_pew_pew_source/es_pew_pew_source';
-
-export const ALL_SOURCES = [
- GeojsonFileSource,
- ESSearchSource,
- ESGeoGridSource,
- ESPewPewSource,
- EMSFileSource,
- EMSTMSSource,
- KibanaRegionmapSource,
- KibanaTilemapSource,
- XYZTMSSource,
- WMSSource,
-];
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js
index a38669fcd1d1a..1003f8329da22 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js
@@ -16,16 +16,11 @@ import { ESSearchSource } from '../es_search_source';
import uuid from 'uuid/v4';
import _ from 'lodash';
import { i18n } from '@kbn/i18n';
+import { registerSource } from '../source_registry';
export class GeojsonFileSource extends AbstractVectorSource {
static type = GEOJSON_FILE;
- static title = i18n.translate('xpack.maps.source.geojsonFileTitle', {
- defaultMessage: 'Uploaded GeoJSON',
- });
- static description = i18n.translate('xpack.maps.source.geojsonFileDescription', {
- defaultMessage: 'Upload and index GeoJSON data in Elasticsearch',
- });
- static icon = 'importAction';
+
static isIndexingSource = true;
static createDescriptor(geoJson, name) {
@@ -59,62 +54,93 @@ export class GeojsonFileSource extends AbstractVectorSource {
};
}
- static viewIndexedData = (
- addAndViewSource,
- inspectorAdapters,
- importSuccessHandler,
- importErrorHandler
- ) => {
- return (indexResponses = {}) => {
- const { indexDataResp, indexPatternResp } = indexResponses;
-
- const indexCreationFailed = !(indexDataResp && indexDataResp.success);
- const allDocsFailed = indexDataResp.failures.length === indexDataResp.docCount;
- const indexPatternCreationFailed = !(indexPatternResp && indexPatternResp.success);
-
- if (indexCreationFailed || allDocsFailed || indexPatternCreationFailed) {
- importErrorHandler(indexResponses);
- return;
- }
- const { fields, id } = indexPatternResp;
- const geoFieldArr = fields.filter(field =>
- Object.values(ES_GEO_FIELD_TYPE).includes(field.type)
- );
- const geoField = _.get(geoFieldArr, '[0].name');
- const indexPatternId = id;
- if (!indexPatternId || !geoField) {
- addAndViewSource(null);
- } else {
- // Only turn on bounds filter for large doc counts
- const filterByMapBounds = indexDataResp.docCount > DEFAULT_MAX_RESULT_WINDOW;
- const source = new ESSearchSource(
- {
- id: uuid(),
- indexPatternId,
- geoField,
- filterByMapBounds,
- },
- inspectorAdapters
- );
- addAndViewSource(source);
- importSuccessHandler(indexResponses);
- }
+ async getGeoJsonWithMeta() {
+ return {
+ data: this._descriptor.__featureCollection,
+ meta: {},
};
+ }
+
+ async getDisplayName() {
+ return this._descriptor.name;
+ }
+
+ canFormatFeatureProperties() {
+ return true;
+ }
+
+ shouldBeIndexed() {
+ return GeojsonFileSource.isIndexingSource;
+ }
+}
+
+const viewIndexedData = (
+ addAndViewSource,
+ inspectorAdapters,
+ importSuccessHandler,
+ importErrorHandler
+) => {
+ return (indexResponses = {}) => {
+ const { indexDataResp, indexPatternResp } = indexResponses;
+
+ const indexCreationFailed = !(indexDataResp && indexDataResp.success);
+ const allDocsFailed = indexDataResp.failures.length === indexDataResp.docCount;
+ const indexPatternCreationFailed = !(indexPatternResp && indexPatternResp.success);
+
+ if (indexCreationFailed || allDocsFailed || indexPatternCreationFailed) {
+ importErrorHandler(indexResponses);
+ return;
+ }
+ const { fields, id } = indexPatternResp;
+ const geoFieldArr = fields.filter(field =>
+ Object.values(ES_GEO_FIELD_TYPE).includes(field.type)
+ );
+ const geoField = _.get(geoFieldArr, '[0].name');
+ const indexPatternId = id;
+ if (!indexPatternId || !geoField) {
+ addAndViewSource(null);
+ } else {
+ // Only turn on bounds filter for large doc counts
+ const filterByMapBounds = indexDataResp.docCount > DEFAULT_MAX_RESULT_WINDOW;
+ const source = new ESSearchSource(
+ {
+ id: uuid(),
+ indexPatternId,
+ geoField,
+ filterByMapBounds,
+ },
+ inspectorAdapters
+ );
+ addAndViewSource(source);
+ importSuccessHandler(indexResponses);
+ }
};
+};
- static previewGeojsonFile = (onPreviewSource, inspectorAdapters) => {
- return (geojsonFile, name) => {
- if (!geojsonFile) {
- onPreviewSource(null);
- return;
- }
- const sourceDescriptor = GeojsonFileSource.createDescriptor(geojsonFile, name);
- const source = new GeojsonFileSource(sourceDescriptor, inspectorAdapters);
- onPreviewSource(source);
- };
+const previewGeojsonFile = (onPreviewSource, inspectorAdapters) => {
+ return (geojsonFile, name) => {
+ if (!geojsonFile) {
+ onPreviewSource(null);
+ return;
+ }
+ const sourceDescriptor = GeojsonFileSource.createDescriptor(geojsonFile, name);
+ const source = new GeojsonFileSource(sourceDescriptor, inspectorAdapters);
+ onPreviewSource(source);
};
+};
+
+registerSource({
+ ConstructorFunction: GeojsonFileSource,
+ type: GEOJSON_FILE,
+});
- static renderEditor({
+export const uploadLayerWizardConfig = {
+ description: i18n.translate('xpack.maps.source.geojsonFileDescription', {
+ defaultMessage: 'Index GeoJSON data in Elasticsearch',
+ }),
+ icon: 'importAction',
+ isIndexingSource: true,
+ renderWizard: ({
onPreviewSource,
inspectorAdapters,
addAndViewSource,
@@ -123,15 +149,12 @@ export class GeojsonFileSource extends AbstractVectorSource {
onIndexReady,
importSuccessHandler,
importErrorHandler,
- }) {
+ }) => {
return (
);
- }
-
- async getGeoJsonWithMeta() {
- return {
- data: this._descriptor.__featureCollection,
- meta: {},
- };
- }
-
- async getDisplayName() {
- return this._descriptor.name;
- }
-
- canFormatFeatureProperties() {
- return true;
- }
-
- shouldBeIndexed() {
- return GeojsonFileSource.isIndexingSource;
- }
-}
+ },
+ title: i18n.translate('xpack.maps.source.geojsonFileTitle', {
+ defaultMessage: 'Upload GeoJSON',
+ }),
+};
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/index.js b/x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/index.js
index cf0d15dcb747a..a6a31def4b231 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/index.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/index.js
@@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { GeojsonFileSource } from './geojson_file_source';
+export { GeojsonFileSource, uploadLayerWizardConfig } from './geojson_file_source';
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.js
index 524f030862768..d3ccc0cb55821 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.js
@@ -14,16 +14,14 @@ import { i18n } from '@kbn/i18n';
import { getDataSourceLabel } from '../../../../common/i18n_getters';
import { UpdateSourceEditor } from './update_source_editor';
import { EMSFileField } from '../../fields/ems_file_field';
+import { registerSource } from '../source_registry';
+
+const sourceTitle = i18n.translate('xpack.maps.source.emsFileTitle', {
+ defaultMessage: 'EMS Boundaries',
+});
export class EMSFileSource extends AbstractVectorSource {
static type = EMS_FILE;
- static title = i18n.translate('xpack.maps.source.emsFileTitle', {
- defaultMessage: 'EMS Boundaries',
- });
- static description = i18n.translate('xpack.maps.source.emsFileDescription', {
- defaultMessage: 'Administrative boundaries from Elastic Maps Service',
- });
- static icon = 'emsApp';
static createDescriptor({ id, tooltipProperties = [] }) {
return {
@@ -33,15 +31,6 @@ export class EMSFileSource extends AbstractVectorSource {
};
}
- static renderEditor({ onPreviewSource, inspectorAdapters }) {
- const onSourceConfigChange = sourceConfig => {
- const sourceDescriptor = EMSFileSource.createDescriptor(sourceConfig);
- const source = new EMSFileSource(sourceDescriptor, inspectorAdapters);
- onPreviewSource(source);
- };
- return ;
- }
-
constructor(descriptor, inspectorAdapters) {
super(EMSFileSource.createDescriptor(descriptor), inspectorAdapters);
this._tooltipFields = this._descriptor.tooltipProperties.map(propertyKey =>
@@ -118,7 +107,7 @@ export class EMSFileSource extends AbstractVectorSource {
return [
{
label: getDataSourceLabel(),
- value: EMSFileSource.title,
+ value: sourceTitle,
},
{
label: i18n.translate('xpack.maps.source.emsFile.layerLabel', {
@@ -167,3 +156,24 @@ export class EMSFileSource extends AbstractVectorSource {
return [VECTOR_SHAPE_TYPES.POLYGON];
}
}
+
+registerSource({
+ ConstructorFunction: EMSFileSource,
+ type: EMS_FILE,
+});
+
+export const emsBoundariesLayerWizardConfig = {
+ description: i18n.translate('xpack.maps.source.emsFileDescription', {
+ defaultMessage: 'Administrative boundaries from Elastic Maps Service',
+ }),
+ icon: 'emsApp',
+ renderWizard: ({ onPreviewSource, inspectorAdapters }) => {
+ const onSourceConfigChange = sourceConfig => {
+ const sourceDescriptor = EMSFileSource.createDescriptor(sourceConfig);
+ const source = new EMSFileSource(sourceDescriptor, inspectorAdapters);
+ onPreviewSource(source);
+ };
+ return ;
+ },
+ title: sourceTitle,
+};
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/index.js b/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/index.js
index 9d0e503eb08ba..28fbc04a1a032 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/index.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/index.js
@@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { EMSFileSource } from './ems_file_source';
+export { EMSFileSource, emsBoundariesLayerWizardConfig } from './ems_file_source';
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js
index 5a2124622694c..1da3680dfdc86 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js
@@ -16,16 +16,14 @@ import { i18n } from '@kbn/i18n';
import { getDataSourceLabel } from '../../../../common/i18n_getters';
import { EMS_TMS } from '../../../../common/constants';
import { getInjectedVarFunc, getUiSettings } from '../../../kibana_services';
+import { registerSource } from '../source_registry';
+
+const sourceTitle = i18n.translate('xpack.maps.source.emsTileTitle', {
+ defaultMessage: 'EMS Basemaps',
+});
export class EMSTMSSource extends AbstractTMSSource {
static type = EMS_TMS;
- static title = i18n.translate('xpack.maps.source.emsTileTitle', {
- defaultMessage: 'EMS Basemaps',
- });
- static description = i18n.translate('xpack.maps.source.emsTileDescription', {
- defaultMessage: 'Tile map service from Elastic Maps Service',
- });
- static icon = 'emsApp';
static createDescriptor(sourceConfig) {
return {
@@ -35,16 +33,6 @@ export class EMSTMSSource extends AbstractTMSSource {
};
}
- static renderEditor({ onPreviewSource, inspectorAdapters }) {
- const onSourceConfigChange = sourceConfig => {
- const descriptor = EMSTMSSource.createDescriptor(sourceConfig);
- const source = new EMSTMSSource(descriptor, inspectorAdapters);
- onPreviewSource(source);
- };
-
- return ;
- }
-
constructor(descriptor, inspectorAdapters) {
super(
{
@@ -69,7 +57,7 @@ export class EMSTMSSource extends AbstractTMSSource {
return [
{
label: getDataSourceLabel(),
- value: EMSTMSSource.title,
+ value: sourceTitle,
},
{
label: i18n.translate('xpack.maps.source.emsTile.serviceId', {
@@ -157,3 +145,25 @@ export class EMSTMSSource extends AbstractTMSSource {
return isDarkMode ? emsTileLayerId.dark : emsTileLayerId.bright;
}
}
+
+registerSource({
+ ConstructorFunction: EMSTMSSource,
+ type: EMS_TMS,
+});
+
+export const emsBaseMapLayerWizardConfig = {
+ description: i18n.translate('xpack.maps.source.emsTileDescription', {
+ defaultMessage: 'Tile map service from Elastic Maps Service',
+ }),
+ icon: 'emsApp',
+ renderWizard: ({ onPreviewSource, inspectorAdapters }) => {
+ const onSourceConfigChange = sourceConfig => {
+ const descriptor = EMSTMSSource.createDescriptor(sourceConfig);
+ const source = new EMSTMSSource(descriptor, inspectorAdapters);
+ onPreviewSource(source);
+ };
+
+ return ;
+ },
+ title: sourceTitle,
+};
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/index.js b/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/index.js
index 81306578db4ae..60a4c9b1de891 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/index.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/index.js
@@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { EMSTMSSource } from './ems_tms_source';
+export { EMSTMSSource, emsBaseMapLayerWizardConfig } from './ems_tms_source';
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js
index 148683269ef78..4aec390bec745 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js
@@ -14,32 +14,12 @@ import { getIndexPatternService, getIndexPatternSelectComponent } from '../../..
import { NoIndexPatternCallout } from '../../../components/no_index_pattern_callout';
import { i18n } from '@kbn/i18n';
-import { EuiFormRow, EuiComboBox, EuiSpacer } from '@elastic/eui';
+import { EuiFormRow, EuiSpacer } from '@elastic/eui';
import {
AGGREGATABLE_GEO_FIELD_TYPES,
getAggregatableGeoFields,
} from '../../../index_pattern_util';
-
-const requestTypeOptions = [
- {
- label: i18n.translate('xpack.maps.source.esGeoGrid.gridRectangleDropdownOption', {
- defaultMessage: 'grid rectangles',
- }),
- value: RENDER_AS.GRID,
- },
- {
- label: i18n.translate('xpack.maps.source.esGeoGrid.heatmapDropdownOption', {
- defaultMessage: 'heat map',
- }),
- value: RENDER_AS.HEATMAP,
- },
- {
- label: i18n.translate('xpack.maps.source.esGeoGrid.pointsDropdownOption', {
- defaultMessage: 'clusters',
- }),
- value: RENDER_AS.POINT,
- },
-];
+import { RenderAsSelect } from './render_as_select';
export class CreateSourceEditor extends Component {
static propTypes = {
@@ -50,7 +30,7 @@ export class CreateSourceEditor extends Component {
isLoadingIndexPattern: false,
indexPatternId: '',
geoField: '',
- requestType: requestTypeOptions[0],
+ requestType: this.props.requestType,
noGeoIndexPatternsExist: false,
};
@@ -126,10 +106,10 @@ export class CreateSourceEditor extends Component {
);
};
- _onRequestTypeSelect = selectedOptions => {
+ _onRequestTypeSelect = newValue => {
this.setState(
{
- requestType: selectedOptions[0],
+ requestType: newValue,
},
this.previewLayer
);
@@ -139,9 +119,7 @@ export class CreateSourceEditor extends Component {
const { indexPatternId, geoField, requestType } = this.state;
const sourceConfig =
- indexPatternId && geoField
- ? { indexPatternId, geoField, requestType: requestType.value }
- : null;
+ indexPatternId && geoField ? { indexPatternId, geoField, requestType } : null;
this.props.onSourceConfigChange(sourceConfig);
};
@@ -176,28 +154,13 @@ export class CreateSourceEditor extends Component {
);
}
- _renderLayerSelect() {
- if (!this.state.indexPattern) {
+ _renderRenderAsSelect() {
+ if (this.state.requestType === RENDER_AS.HEATMAP || !this.state.indexPattern) {
return null;
}
return (
-
-
-
+
);
}
@@ -243,7 +206,7 @@ export class CreateSourceEditor extends Component {
{this._renderNoIndexPatternWarning()}
{this._renderIndexPatternSelect()}
{this._renderGeoSelect()}
- {this._renderLayerSelect()}
+ {this._renderRenderAsSelect()}
);
}
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js
index 405c8a61bfca6..dec802ac3cf1a 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js
@@ -32,17 +32,20 @@ import { AbstractESAggSource } from '../es_agg_source';
import { DynamicStyleProperty } from '../../styles/vector/properties/dynamic_style_property';
import { StaticStyleProperty } from '../../styles/vector/properties/static_style_property';
import { DataRequestAbortError } from '../../util/data_request';
+import { registerSource } from '../source_registry';
export const MAX_GEOTILE_LEVEL = 29;
+const clustersTitle = i18n.translate('xpack.maps.source.esGridClustersTitle', {
+ defaultMessage: 'Clusters and grids',
+});
+
+const heatmapTitle = i18n.translate('xpack.maps.source.esGridHeatmapTitle', {
+ defaultMessage: 'Heat map',
+});
+
export class ESGeoGridSource extends AbstractESAggSource {
static type = ES_GEO_GRID;
- static title = i18n.translate('xpack.maps.source.esGridTitle', {
- defaultMessage: 'Grid aggregation',
- });
- static description = i18n.translate('xpack.maps.source.esGridDescription', {
- defaultMessage: 'Geospatial data grouped in grids with metrics for each gridded cell',
- });
static createDescriptor({ indexPatternId, geoField, requestType, resolution }) {
return {
@@ -55,21 +58,6 @@ export class ESGeoGridSource extends AbstractESAggSource {
};
}
- static renderEditor({ onPreviewSource, inspectorAdapters }) {
- const onSourceConfigChange = sourceConfig => {
- if (!sourceConfig) {
- onPreviewSource(null);
- return;
- }
-
- const sourceDescriptor = ESGeoGridSource.createDescriptor(sourceConfig);
- const source = new ESGeoGridSource(sourceDescriptor, inspectorAdapters);
- onPreviewSource(source);
- };
-
- return ;
- }
-
renderSourceSettingsEditor({ onChange }) {
return (
{
+ const onSourceConfigChange = sourceConfig => {
+ if (!sourceConfig) {
+ onPreviewSource(null);
+ return;
+ }
+
+ const sourceDescriptor = ESGeoGridSource.createDescriptor(sourceConfig);
+ const source = new ESGeoGridSource(sourceDescriptor, inspectorAdapters);
+ onPreviewSource(source);
+ };
+
+ return (
+
+ );
+ },
+ title: clustersTitle,
+};
+
+export const heatmapLayerWizardConfig = {
+ description: i18n.translate('xpack.maps.source.esGridHeatmapDescription', {
+ defaultMessage: 'Geospatial data grouped in grids to show density',
+ }),
+ icon: 'logoElasticsearch',
+ renderWizard: ({ onPreviewSource, inspectorAdapters }) => {
+ const onSourceConfigChange = sourceConfig => {
+ if (!sourceConfig) {
+ onPreviewSource(null);
+ return;
+ }
+
+ const sourceDescriptor = ESGeoGridSource.createDescriptor(sourceConfig);
+ const source = new ESGeoGridSource(sourceDescriptor, inspectorAdapters);
+ onPreviewSource(source);
+ };
+
+ return (
+
+ );
+ },
+ title: heatmapTitle,
+};
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/index.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/index.js
index 58d74c04c5552..c2fa2356b1a3e 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/index.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/index.js
@@ -4,4 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { ESGeoGridSource } from './es_geo_grid_source';
+export {
+ ESGeoGridSource,
+ clustersLayerWizardConfig,
+ heatmapLayerWizardConfig,
+} from './es_geo_grid_source';
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/render_as_select.tsx b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/render_as_select.tsx
new file mode 100644
index 0000000000000..c82781ede186f
--- /dev/null
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/render_as_select.tsx
@@ -0,0 +1,59 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { EuiFormRow, EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { RENDER_AS } from '../../../../common/constants';
+
+const options = [
+ {
+ label: i18n.translate('xpack.maps.source.esGeoGrid.pointsDropdownOption', {
+ defaultMessage: 'clusters',
+ }),
+ value: RENDER_AS.POINT,
+ },
+ {
+ label: i18n.translate('xpack.maps.source.esGeoGrid.gridRectangleDropdownOption', {
+ defaultMessage: 'grids',
+ }),
+ value: RENDER_AS.GRID,
+ },
+];
+
+export function RenderAsSelect(props: {
+ renderAs: RENDER_AS;
+ onChange: (newValue: RENDER_AS) => void;
+}) {
+ function onChange(selectedOptions: Array>) {
+ if (!selectedOptions || !selectedOptions.length) {
+ return;
+ }
+ props.onChange(selectedOptions[0].value as RENDER_AS);
+ }
+
+ const selectedOptions = [];
+ const selectedOption = options.find(option => option.value === props.renderAs);
+ if (selectedOption) {
+ selectedOptions.push(selectedOption);
+ }
+
+ return (
+
+
+
+ );
+}
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js
index 5f6cc0a46dfb2..da2b663746b9d 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js
@@ -26,17 +26,16 @@ import { AbstractESAggSource } from '../es_agg_source';
import { DynamicStyleProperty } from '../../styles/vector/properties/dynamic_style_property';
import { COLOR_GRADIENTS } from '../../styles/color_utils';
import { indexPatterns } from '../../../../../../../../src/plugins/data/public';
+import { registerSource } from '../source_registry';
const MAX_GEOTILE_LEVEL = 29;
+const sourceTitle = i18n.translate('xpack.maps.source.pewPewTitle', {
+ defaultMessage: 'Point to point',
+});
+
export class ESPewPewSource extends AbstractESAggSource {
static type = ES_PEW_PEW;
- static title = i18n.translate('xpack.maps.source.pewPewTitle', {
- defaultMessage: 'Point to point',
- });
- static description = i18n.translate('xpack.maps.source.pewPewDescription', {
- defaultMessage: 'Aggregated data paths between the source and destination',
- });
static createDescriptor({ indexPatternId, sourceGeoField, destGeoField }) {
return {
@@ -48,21 +47,6 @@ export class ESPewPewSource extends AbstractESAggSource {
};
}
- static renderEditor({ onPreviewSource, inspectorAdapters }) {
- const onSourceConfigChange = sourceConfig => {
- if (!sourceConfig) {
- onPreviewSource(null);
- return;
- }
-
- const sourceDescriptor = ESPewPewSource.createDescriptor(sourceConfig);
- const source = new ESPewPewSource(sourceDescriptor, inspectorAdapters);
- onPreviewSource(source);
- };
-
- return ;
- }
-
renderSourceSettingsEditor({ onChange }) {
return (
{
+ const onSourceConfigChange = sourceConfig => {
+ if (!sourceConfig) {
+ onPreviewSource(null);
+ return;
+ }
+
+ const sourceDescriptor = ESPewPewSource.createDescriptor(sourceConfig);
+ const source = new ESPewPewSource(sourceDescriptor, inspectorAdapters);
+ onPreviewSource(source);
+ };
+
+ return ;
+ },
+ title: sourceTitle,
+};
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js
index cd44ef49623fa..ce9932bd15cea 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js
@@ -32,6 +32,11 @@ import { BlendedVectorLayer } from '../../blended_vector_layer';
import { DEFAULT_FILTER_BY_MAP_BOUNDS } from './constants';
import { ESDocField } from '../../fields/es_doc_field';
import { getField, addFieldToDSL } from '../../util/es_agg_utils';
+import { registerSource } from '../source_registry';
+
+const sourceTitle = i18n.translate('xpack.maps.source.esSearchTitle', {
+ defaultMessage: 'Documents',
+});
function getDocValueAndSourceFields(indexPattern, fieldNames) {
const docValueFields = [];
@@ -65,31 +70,6 @@ function getDocValueAndSourceFields(indexPattern, fieldNames) {
export class ESSearchSource extends AbstractESSource {
static type = ES_SEARCH;
- static title = i18n.translate('xpack.maps.source.esSearchTitle', {
- defaultMessage: 'Documents',
- });
- static description = i18n.translate('xpack.maps.source.esSearchDescription', {
- defaultMessage: 'Vector data from a Kibana index pattern',
- });
-
- static renderEditor({ onPreviewSource, inspectorAdapters }) {
- const onSourceConfigChange = sourceConfig => {
- if (!sourceConfig) {
- onPreviewSource(null);
- return;
- }
-
- const source = new ESSearchSource(
- {
- id: uuid(),
- ...sourceConfig,
- },
- inspectorAdapters
- );
- onPreviewSource(source);
- };
- return ;
- }
constructor(descriptor, inspectorAdapters) {
super(
@@ -206,7 +186,7 @@ export class ESSearchSource extends AbstractESSource {
return [
{
label: getDataSourceLabel(),
- value: ESSearchSource.title,
+ value: sourceTitle,
},
{
label: i18n.translate('xpack.maps.source.esSearch.indexPatternLabel', {
@@ -587,3 +567,34 @@ export class ESSearchSource extends AbstractESSource {
};
}
}
+
+registerSource({
+ ConstructorFunction: ESSearchSource,
+ type: ES_SEARCH,
+});
+
+export const esDocumentsLayerWizardConfig = {
+ description: i18n.translate('xpack.maps.source.esSearchDescription', {
+ defaultMessage: 'Vector data from a Kibana index pattern',
+ }),
+ icon: 'logoElasticsearch',
+ renderWizard: ({ onPreviewSource, inspectorAdapters }) => {
+ const onSourceConfigChange = sourceConfig => {
+ if (!sourceConfig) {
+ onPreviewSource(null);
+ return;
+ }
+
+ const source = new ESSearchSource(
+ {
+ id: uuid(),
+ ...sourceConfig,
+ },
+ inspectorAdapters
+ );
+ onPreviewSource(source);
+ };
+ return ;
+ },
+ title: sourceTitle,
+};
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/index.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/index.js
index 5fea38ee274d9..2c401ac92567e 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/index.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/index.js
@@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { ESSearchSource } from './es_search_source';
+export { ESSearchSource, esDocumentsLayerWizardConfig } from './es_search_source';
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js
index 9dc3067a70436..441d52d23398a 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js
@@ -23,8 +23,6 @@ import { DataRequestAbortError } from '../util/data_request';
import { expandToTileBoundaries } from './es_geo_grid_source/geo_tile_utils';
export class AbstractESSource extends AbstractVectorSource {
- static icon = 'logoElasticsearch';
-
constructor(descriptor, inspectorAdapters) {
super(
{
@@ -177,10 +175,10 @@ export class AbstractESSource extends AbstractVectorSource {
}
return {
- min_lon: esBounds.top_left.lon,
- max_lon: esBounds.bottom_right.lon,
- min_lat: esBounds.bottom_right.lat,
- max_lat: esBounds.top_left.lat,
+ minLon: esBounds.top_left.lon,
+ maxLon: esBounds.bottom_right.lon,
+ minLat: esBounds.bottom_right.lat,
+ maxLat: esBounds.top_left.lat,
};
}
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/index.js b/x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/index.js
index d54b135239a63..00c3bfc5f17c6 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/index.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/index.js
@@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { KibanaRegionmapSource } from './kibana_regionmap_source';
+export { KibanaRegionmapSource, kibanaRegionMapLayerWizardConfig } from './kibana_regionmap_source';
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js
index 276a3377aaae2..7f4bcfa41f7c4 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js
@@ -10,18 +10,16 @@ import { CreateSourceEditor } from './create_source_editor';
import { getKibanaRegionList } from '../../../meta';
import { i18n } from '@kbn/i18n';
import { getDataSourceLabel } from '../../../../common/i18n_getters';
-import { FIELD_ORIGIN } from '../../../../common/constants';
+import { FIELD_ORIGIN, REGIONMAP_FILE } from '../../../../common/constants';
import { KibanaRegionField } from '../../fields/kibana_region_field';
+import { registerSource } from '../source_registry';
+
+const sourceTitle = i18n.translate('xpack.maps.source.kbnRegionMapTitle', {
+ defaultMessage: 'Configured GeoJSON',
+});
export class KibanaRegionmapSource extends AbstractVectorSource {
- static type = 'REGIONMAP_FILE';
- static title = i18n.translate('xpack.maps.source.kbnRegionMapTitle', {
- defaultMessage: 'Configured GeoJSON',
- });
- static description = i18n.translate('xpack.maps.source.kbnRegionMapDescription', {
- defaultMessage: 'Vector data from hosted GeoJSON configured in kibana.yml',
- });
- static icon = 'logoKibana';
+ static type = REGIONMAP_FILE;
static createDescriptor({ name }) {
return {
@@ -30,16 +28,6 @@ export class KibanaRegionmapSource extends AbstractVectorSource {
};
}
- static renderEditor = ({ onPreviewSource, inspectorAdapters }) => {
- const onSourceConfigChange = sourceConfig => {
- const sourceDescriptor = KibanaRegionmapSource.createDescriptor(sourceConfig);
- const source = new KibanaRegionmapSource(sourceDescriptor, inspectorAdapters);
- onPreviewSource(source);
- };
-
- return ;
- };
-
createField({ fieldName }) {
return new KibanaRegionField({
fieldName,
@@ -52,7 +40,7 @@ export class KibanaRegionmapSource extends AbstractVectorSource {
return [
{
label: getDataSourceLabel(),
- value: KibanaRegionmapSource.title,
+ value: sourceTitle,
},
{
label: i18n.translate('xpack.maps.source.kbnRegionMap.vectorLayerLabel', {
@@ -108,3 +96,25 @@ export class KibanaRegionmapSource extends AbstractVectorSource {
return true;
}
}
+
+registerSource({
+ ConstructorFunction: KibanaRegionmapSource,
+ type: REGIONMAP_FILE,
+});
+
+export const kibanaRegionMapLayerWizardConfig = {
+ description: i18n.translate('xpack.maps.source.kbnRegionMapDescription', {
+ defaultMessage: 'Vector data from hosted GeoJSON configured in kibana.yml',
+ }),
+ icon: 'logoKibana',
+ renderWizard: ({ onPreviewSource, inspectorAdapters }) => {
+ const onSourceConfigChange = sourceConfig => {
+ const sourceDescriptor = KibanaRegionmapSource.createDescriptor(sourceConfig);
+ const source = new KibanaRegionmapSource(sourceDescriptor, inspectorAdapters);
+ onPreviewSource(source);
+ };
+
+ return ;
+ },
+ title: sourceTitle,
+};
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_tilemap_source/index.js b/x-pack/legacy/plugins/maps/public/layers/sources/kibana_tilemap_source/index.js
index 3226fb89b700b..9fd7f088032ca 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_tilemap_source/index.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/kibana_tilemap_source/index.js
@@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { KibanaTilemapSource } from './kibana_tilemap_source';
+export { KibanaTilemapSource, kibanaBasemapLayerWizardConfig } from './kibana_tilemap_source';
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_tilemap_source/kibana_tilemap_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/kibana_tilemap_source/kibana_tilemap_source.js
index 21ab2ba42c7bb..b21bb6bdbbad4 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_tilemap_source/kibana_tilemap_source.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/kibana_tilemap_source/kibana_tilemap_source.js
@@ -11,17 +11,15 @@ import { getKibanaTileMap } from '../../../meta';
import { i18n } from '@kbn/i18n';
import { getDataSourceLabel } from '../../../../common/i18n_getters';
import _ from 'lodash';
+import { KIBANA_TILEMAP } from '../../../../common/constants';
+import { registerSource } from '../source_registry';
-export class KibanaTilemapSource extends AbstractTMSSource {
- static type = 'KIBANA_TILEMAP';
- static title = i18n.translate('xpack.maps.source.kbnTMSTitle', {
- defaultMessage: 'Configured Tile Map Service',
- });
- static description = i18n.translate('xpack.maps.source.kbnTMSDescription', {
- defaultMessage: 'Tile map service configured in kibana.yml',
- });
+const sourceTitle = i18n.translate('xpack.maps.source.kbnTMSTitle', {
+ defaultMessage: 'Configured Tile Map Service',
+});
- static icon = 'logoKibana';
+export class KibanaTilemapSource extends AbstractTMSSource {
+ static type = KIBANA_TILEMAP;
static createDescriptor() {
return {
@@ -29,20 +27,11 @@ export class KibanaTilemapSource extends AbstractTMSSource {
};
}
- static renderEditor = ({ onPreviewSource, inspectorAdapters }) => {
- const onSourceConfigChange = () => {
- const sourceDescriptor = KibanaTilemapSource.createDescriptor();
- const source = new KibanaTilemapSource(sourceDescriptor, inspectorAdapters);
- onPreviewSource(source);
- };
- return ;
- };
-
async getImmutableProperties() {
return [
{
label: getDataSourceLabel(),
- value: KibanaTilemapSource.title,
+ value: sourceTitle,
},
{
label: i18n.translate('xpack.maps.source.kbnTMS.urlLabel', {
@@ -94,3 +83,24 @@ export class KibanaTilemapSource extends AbstractTMSSource {
}
}
}
+
+registerSource({
+ ConstructorFunction: KibanaTilemapSource,
+ type: KIBANA_TILEMAP,
+});
+
+export const kibanaBasemapLayerWizardConfig = {
+ description: i18n.translate('xpack.maps.source.kbnTMSDescription', {
+ defaultMessage: 'Tile map service configured in kibana.yml',
+ }),
+ icon: 'logoKibana',
+ renderWizard: ({ onPreviewSource, inspectorAdapters }) => {
+ const onSourceConfigChange = () => {
+ const sourceDescriptor = KibanaTilemapSource.createDescriptor();
+ const source = new KibanaTilemapSource(sourceDescriptor, inspectorAdapters);
+ onPreviewSource(source);
+ };
+ return ;
+ },
+ title: sourceTitle,
+};
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/source_registry.ts b/x-pack/legacy/plugins/maps/public/layers/sources/source_registry.ts
new file mode 100644
index 0000000000000..518cab68b601b
--- /dev/null
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/source_registry.ts
@@ -0,0 +1,35 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+/* eslint-disable @typescript-eslint/consistent-type-definitions */
+
+import { AbstractSourceDescriptor } from '../../../common/descriptor_types';
+import { ISource } from './source';
+
+type SourceRegistryEntry = {
+ ConstructorFunction: new (
+ sourceDescriptor: AbstractSourceDescriptor,
+ inspectorAdapters: unknown
+ ) => ISource;
+ type: string;
+};
+
+const registry: SourceRegistryEntry[] = [];
+
+export function registerSource(entry: SourceRegistryEntry) {
+ const sourceTypeExists = registry.some(({ type }: SourceRegistryEntry) => {
+ return entry.type === type;
+ });
+ if (sourceTypeExists) {
+ throw new Error(
+ `Unable to register source type ${entry.type}. ${entry.type} has already been registered`
+ );
+ }
+ registry.push(entry);
+}
+
+export function getSourceByType(sourceType: string): SourceRegistryEntry | undefined {
+ return registry.find((source: SourceRegistryEntry) => source.type === sourceType);
+}
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.d.ts b/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.d.ts
index 7a747da244233..1400654297e01 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.d.ts
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.d.ts
@@ -8,7 +8,11 @@
import { FeatureCollection } from 'geojson';
import { AbstractSource, ISource } from './source';
import { IField } from '../fields/field';
-import { ESSearchSourceResponseMeta } from '../../../common/descriptor_types';
+import {
+ ESSearchSourceResponseMeta,
+ MapExtent,
+ VectorSourceRequestMeta,
+} from '../../../common/descriptor_types';
export type GeoJsonFetchMeta = ESSearchSourceResponseMeta;
@@ -18,6 +22,7 @@ export type GeoJsonWithMeta = {
};
export interface IVectorSource extends ISource {
+ getBoundsForFilters(searchFilters: VectorSourceRequestMeta): MapExtent;
getGeoJsonWithMeta(
layerName: 'string',
searchFilters: unknown[],
@@ -29,6 +34,7 @@ export interface IVectorSource extends ISource {
}
export class AbstractVectorSource extends AbstractSource implements IVectorSource {
+ getBoundsForFilters(searchFilters: VectorSourceRequestMeta): MapExtent;
getGeoJsonWithMeta(
layerName: 'string',
searchFilters: unknown[],
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/wms_source/index.js b/x-pack/legacy/plugins/maps/public/layers/sources/wms_source/index.js
index 22bc50e601f56..daae552a6f772 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/wms_source/index.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/wms_source/index.js
@@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { WMSSource } from './wms_source';
+export { WMSSource, wmsLayerWizardConfig } from './wms_source';
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/wms_source/wms_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/wms_source/wms_source.js
index 61955df94e451..749560a2bb4b1 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/wms_source/wms_source.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/wms_source/wms_source.js
@@ -12,16 +12,15 @@ import { WMSCreateSourceEditor } from './wms_create_source_editor';
import { i18n } from '@kbn/i18n';
import { getDataSourceLabel, getUrlLabel } from '../../../../common/i18n_getters';
import { WmsClient } from './wms_client';
+import { WMS } from '../../../../common/constants';
+import { registerSource } from '../source_registry';
+
+const sourceTitle = i18n.translate('xpack.maps.source.wmsTitle', {
+ defaultMessage: 'Web Map Service',
+});
export class WMSSource extends AbstractTMSSource {
- static type = 'WMS';
- static title = i18n.translate('xpack.maps.source.wmsTitle', {
- defaultMessage: 'Web Map Service',
- });
- static description = i18n.translate('xpack.maps.source.wmsDescription', {
- defaultMessage: 'Maps from OGC Standard WMS',
- });
- static icon = 'grid';
+ static type = WMS;
static createDescriptor({ serviceUrl, layers, styles, attributionText, attributionUrl }) {
return {
@@ -34,23 +33,9 @@ export class WMSSource extends AbstractTMSSource {
};
}
- static renderEditor({ onPreviewSource, inspectorAdapters }) {
- const onSourceConfigChange = sourceConfig => {
- if (!sourceConfig) {
- onPreviewSource(null);
- return;
- }
-
- const sourceDescriptor = WMSSource.createDescriptor(sourceConfig);
- const source = new WMSSource(sourceDescriptor, inspectorAdapters);
- onPreviewSource(source);
- };
- return ;
- }
-
async getImmutableProperties() {
return [
- { label: getDataSourceLabel(), value: WMSSource.title },
+ { label: getDataSourceLabel(), value: sourceTitle },
{ label: getUrlLabel(), value: this._descriptor.serviceUrl },
{
label: i18n.translate('xpack.maps.source.wms.layersLabel', {
@@ -104,3 +89,29 @@ export class WMSSource extends AbstractTMSSource {
return client.getUrlTemplate(this._descriptor.layers, this._descriptor.styles || '');
}
}
+
+registerSource({
+ ConstructorFunction: WMSSource,
+ type: WMS,
+});
+
+export const wmsLayerWizardConfig = {
+ description: i18n.translate('xpack.maps.source.wmsDescription', {
+ defaultMessage: 'Maps from OGC Standard WMS',
+ }),
+ icon: 'grid',
+ renderWizard: ({ onPreviewSource, inspectorAdapters }) => {
+ const onSourceConfigChange = sourceConfig => {
+ if (!sourceConfig) {
+ onPreviewSource(null);
+ return;
+ }
+
+ const sourceDescriptor = WMSSource.createDescriptor(sourceConfig);
+ const source = new WMSSource(sourceDescriptor, inspectorAdapters);
+ onPreviewSource(source);
+ };
+ return ;
+ },
+ title: sourceTitle,
+};
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.js
index 354883372e244..d53fbffd21512 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.js
@@ -13,16 +13,14 @@ import { i18n } from '@kbn/i18n';
import { getDataSourceLabel, getUrlLabel } from '../../../common/i18n_getters';
import _ from 'lodash';
import { EMS_XYZ } from '../../../common/constants';
+import { registerSource } from './source_registry';
+
+const sourceTitle = i18n.translate('xpack.maps.source.ems_xyzTitle', {
+ defaultMessage: 'Tile Map Service',
+});
export class XYZTMSSource extends AbstractTMSSource {
static type = EMS_XYZ;
- static title = i18n.translate('xpack.maps.source.ems_xyzTitle', {
- defaultMessage: 'Tile Map Service',
- });
- static description = i18n.translate('xpack.maps.source.ems_xyzDescription', {
- defaultMessage: 'Tile map service configured in interface',
- });
- static icon = 'grid';
static createDescriptor({ urlTemplate, attributionText, attributionUrl }) {
return {
@@ -33,18 +31,9 @@ export class XYZTMSSource extends AbstractTMSSource {
};
}
- static renderEditor({ onPreviewSource, inspectorAdapters }) {
- const onSourceConfigChange = sourceConfig => {
- const sourceDescriptor = XYZTMSSource.createDescriptor(sourceConfig);
- const source = new XYZTMSSource(sourceDescriptor, inspectorAdapters);
- onPreviewSource(source);
- };
- return ;
- }
-
async getImmutableProperties() {
return [
- { label: getDataSourceLabel(), value: XYZTMSSource.title },
+ { label: getDataSourceLabel(), value: sourceTitle },
{ label: getUrlLabel(), value: this._descriptor.urlTemplate },
];
}
@@ -175,3 +164,24 @@ class XYZTMSEditor extends React.Component {
);
}
}
+
+registerSource({
+ ConstructorFunction: XYZTMSSource,
+ type: EMS_XYZ,
+});
+
+export const tmsLayerWizardConfig = {
+ description: i18n.translate('xpack.maps.source.ems_xyzDescription', {
+ defaultMessage: 'Tile map service configured in interface',
+ }),
+ icon: 'grid',
+ renderWizard: ({ onPreviewSource, inspectorAdapters }) => {
+ const onSourceConfigChange = sourceConfig => {
+ const sourceDescriptor = XYZTMSSource.createDescriptor(sourceConfig);
+ const source = new XYZTMSSource(sourceDescriptor, inspectorAdapters);
+ onPreviewSource(source);
+ };
+ return ;
+ },
+ title: sourceTitle,
+};
diff --git a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js b/x-pack/legacy/plugins/maps/public/layers/vector_layer.js
index 6b89554546330..d606420909281 100644
--- a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js
+++ b/x-pack/legacy/plugins/maps/public/layers/vector_layer.js
@@ -167,10 +167,10 @@ export class VectorLayer extends AbstractLayer {
features: visibleFeatures,
});
return {
- min_lon: bbox[0],
- min_lat: bbox[1],
- max_lon: bbox[2],
- max_lat: bbox[3],
+ minLon: bbox[0],
+ minLat: bbox[1],
+ maxLon: bbox[2],
+ maxLat: bbox[3],
};
}
diff --git a/x-pack/legacy/plugins/maps/public/plugin.ts b/x-pack/legacy/plugins/maps/public/plugin.ts
index 53c951ac787e1..c08ed6fc6da61 100644
--- a/x-pack/legacy/plugins/maps/public/plugin.ts
+++ b/x-pack/legacy/plugins/maps/public/plugin.ts
@@ -4,6 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import './layers/layer_wizard_registry';
+import './layers/sources/source_registry';
+import './layers/load_layer_wizards';
+
import { Plugin, CoreStart, CoreSetup } from 'src/core/public';
// @ts-ignore
import { wrapInI18nContext } from 'ui/i18n';
diff --git a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js
index 61eea2d172ae4..397478cfd1d1b 100644
--- a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js
+++ b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js
@@ -11,7 +11,6 @@ import { VectorTileLayer } from '../layers/vector_tile_layer';
import { VectorLayer } from '../layers/vector_layer';
import { HeatmapLayer } from '../layers/heatmap_layer';
import { BlendedVectorLayer } from '../layers/blended_vector_layer';
-import { ALL_SOURCES } from '../layers/sources/all_sources';
import { getTimeFilter } from '../kibana_services';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { getInspectorAdapters } from '../../../../../plugins/maps/public/reducers/non_serializable_instances';
@@ -21,6 +20,7 @@ import {
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
} from '../../../../../plugins/maps/public/reducers/util';
import { InnerJoin } from '../layers/joins/inner_join';
+import { getSourceByType } from '../layers/sources/source_registry';
function createLayerInstance(layerDescriptor, inspectorAdapters) {
const source = createSourceInstance(layerDescriptor.sourceDescriptor, inspectorAdapters);
@@ -49,13 +49,11 @@ function createLayerInstance(layerDescriptor, inspectorAdapters) {
}
function createSourceInstance(sourceDescriptor, inspectorAdapters) {
- const Source = ALL_SOURCES.find(Source => {
- return Source.type === sourceDescriptor.type;
- });
- if (!Source) {
+ const source = getSourceByType(sourceDescriptor.type);
+ if (!source) {
throw new Error(`Unrecognized sourceType ${sourceDescriptor.type}`);
}
- return new Source(sourceDescriptor, inspectorAdapters);
+ return new source.ConstructorFunction(sourceDescriptor, inspectorAdapters);
}
export const getOpenTooltips = ({ map }) => {
diff --git a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.test.js b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.test.js
index e7f071d5729c6..1a5ab633a569f 100644
--- a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.test.js
+++ b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.test.js
@@ -8,7 +8,6 @@ jest.mock('../layers/vector_layer', () => {});
jest.mock('../layers/blended_vector_layer', () => {});
jest.mock('../layers/heatmap_layer', () => {});
jest.mock('../layers/vector_tile_layer', () => {});
-jest.mock('../layers/sources/all_sources', () => {});
jest.mock('../layers/joins/inner_join', () => {});
jest.mock('../../../../../plugins/maps/public/reducers/non_serializable_instances', () => ({
getInspectorAdapters: () => {
diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx
index c7e368da1338f..cbb4006bbf933 100644
--- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx
@@ -10,7 +10,10 @@ import { createPortalNode, InPortal } from 'react-reverse-portal';
import styled, { css } from 'styled-components';
import { npStart } from 'ui/new_platform';
-import { EmbeddablePanel } from '../../../../../../../src/plugins/embeddable/public';
+import {
+ EmbeddablePanel,
+ ErrorEmbeddable,
+} from '../../../../../../../src/plugins/embeddable/public';
import { DEFAULT_INDEX_KEY } from '../../../common/constants';
import { getIndexPatternTitleIdMapping } from '../../hooks/api/helpers';
import { useIndexPatterns } from '../../hooks/use_index_patterns';
@@ -84,7 +87,9 @@ export const EmbeddedMapComponent = ({
setQuery,
startDate,
}: EmbeddedMapProps) => {
- const [embeddable, setEmbeddable] = React.useState(null);
+ const [embeddable, setEmbeddable] = React.useState(
+ undefined
+ );
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);
const [isIndexError, setIsIndexError] = useState(false);
diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.test.tsx
index 0ffb13cd66028..f4e6ee5f878a6 100644
--- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.test.tsx
@@ -20,8 +20,10 @@ jest.mock('ui/new_platform');
const { npStart } = createUiNewPlatformMock();
npStart.plugins.embeddable.getEmbeddableFactory = jest.fn().mockImplementation(() => ({
- createFromState: () => ({
+ create: () => ({
reload: jest.fn(),
+ setRenderTooltipContent: jest.fn(),
+ setLayerList: jest.fn(),
}),
}));
diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx
index 56211c9ff8935..0c7a1212ba280 100644
--- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx
@@ -11,10 +11,20 @@ import minimatch from 'minimatch';
import { IndexPatternMapping, SetQuery } from './types';
import { getLayerList } from './map_config';
import { MAP_SAVED_OBJECT_TYPE } from '../../../../../../plugins/maps/public';
-import { MapEmbeddable, RenderTooltipContentParams } from '../../../../maps/public';
+import {
+ MapEmbeddable,
+ RenderTooltipContentParams,
+ MapEmbeddableInput,
+} from '../../../../maps/public';
import * as i18n from './translations';
import { Query, Filter } from '../../../../../../../src/plugins/data/public';
-import { EmbeddableStart, ViewMode } from '../../../../../../../src/plugins/embeddable/public';
+import {
+ EmbeddableStart,
+ isErrorEmbeddable,
+ EmbeddableOutput,
+ ViewMode,
+ ErrorEmbeddable,
+} from '../../../../../../../src/plugins/embeddable/public';
import { IndexPatternSavedObject } from '../../hooks/types';
/**
@@ -40,14 +50,19 @@ export const createEmbeddable = async (
setQuery: SetQuery,
portalNode: PortalNode,
embeddableApi: EmbeddableStart
-): Promise => {
- const factory = embeddableApi.getEmbeddableFactory(MAP_SAVED_OBJECT_TYPE);
+): Promise => {
+ const factory = embeddableApi.getEmbeddableFactory<
+ MapEmbeddableInput,
+ EmbeddableOutput,
+ MapEmbeddable
+ >(MAP_SAVED_OBJECT_TYPE);
- const state = {
- layerList: getLayerList(indexPatterns),
+ if (!factory) {
+ throw new Error('Map embeddable factory undefined');
+ }
+
+ const input: MapEmbeddableInput = {
title: i18n.MAP_TITLE,
- };
- const input = {
id: uuid.v4(),
filters,
hidePanelTitles: true,
@@ -86,13 +101,16 @@ export const createEmbeddable = async (
return ;
};
- // @ts-ignore method added in https://github.com/elastic/kibana/pull/43878
- const embeddableObject = await factory.createFromState(
- state,
- input,
- undefined,
- renderTooltipContent
- );
+ const embeddableObject = await factory.create(input);
+
+ if (!embeddableObject) {
+ throw new Error('Map embeddable is undefined');
+ }
+
+ if (!isErrorEmbeddable(embeddableObject)) {
+ embeddableObject.setRenderTooltipContent(renderTooltipContent);
+ embeddableObject.setLayerList(getLayerList(indexPatterns));
+ }
// Wire up to app refresh action
setQuery({
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts
index 32e64138ff6e0..e74da583e9193 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts
@@ -20,7 +20,7 @@ export const rulesNotificationAlertType = ({
logger: Logger;
}): NotificationAlertTypeDefinition => ({
id: NOTIFICATIONS_ID,
- name: 'SIEM Notifications',
+ name: 'SIEM notification',
actionGroups: siemRuleActionGroups,
defaultActionGroupId: 'default',
validate: {
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts
index 78b0cd84eeda3..91905722fbca3 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts
@@ -41,7 +41,7 @@ export const signalRulesAlertType = ({
}): SignalRuleAlertTypeDefinition => {
return {
id: SIGNALS_ID,
- name: 'SIEM Signals',
+ name: 'SIEM signal',
actionGroups: siemRuleActionGroups,
defaultActionGroupId: 'default',
validate: {
diff --git a/x-pack/legacy/plugins/uptime/common/constants/index.ts b/x-pack/legacy/plugins/uptime/common/constants/index.ts
index 19f2de3c6f0f4..74783cf46550f 100644
--- a/x-pack/legacy/plugins/uptime/common/constants/index.ts
+++ b/x-pack/legacy/plugins/uptime/common/constants/index.ts
@@ -8,7 +8,6 @@ export { ACTION_GROUP_DEFINITIONS } from './alerts';
export { CHART_FORMAT_LIMITS } from './chart_format_limits';
export { CLIENT_DEFAULTS } from './client_defaults';
export { CONTEXT_DEFAULTS } from './context_defaults';
-export { INDEX_NAMES } from './index_names';
export * from './capabilities';
export { PLUGIN } from './plugin';
export { QUERY, STATES } from './query';
diff --git a/x-pack/legacy/plugins/uptime/common/constants/rest_api.ts b/x-pack/legacy/plugins/uptime/common/constants/rest_api.ts
index 7fafe6584d831..86e2b03e13f22 100644
--- a/x-pack/legacy/plugins/uptime/common/constants/rest_api.ts
+++ b/x-pack/legacy/plugins/uptime/common/constants/rest_api.ts
@@ -10,7 +10,6 @@ export enum API_URLS {
MONITOR_LOCATIONS = `/api/uptime/monitor/locations`,
MONITOR_DURATION = `/api/uptime/monitor/duration`,
MONITOR_DETAILS = `/api/uptime/monitor/details`,
- MONITOR_SELECTED = `/api/uptime/monitor/selected`,
MONITOR_STATUS = `/api/uptime/monitor/status`,
PINGS = '/api/uptime/pings',
PING_HISTOGRAM = `/api/uptime/ping/histogram`,
diff --git a/x-pack/legacy/plugins/uptime/public/components/connected/monitor/status_bar_container.tsx b/x-pack/legacy/plugins/uptime/public/components/connected/monitor/status_bar_container.tsx
index 9e7834ae6f242..dd6f7a89cf9a3 100644
--- a/x-pack/legacy/plugins/uptime/public/components/connected/monitor/status_bar_container.tsx
+++ b/x-pack/legacy/plugins/uptime/public/components/connected/monitor/status_bar_container.tsx
@@ -8,9 +8,9 @@ import React, { useContext, useEffect } from 'react';
import { connect } from 'react-redux';
import { Dispatch } from 'redux';
import { AppState } from '../../../state';
-import { monitorLocationsSelector, selectMonitorStatus } from '../../../state/selectors';
+import { monitorLocationsSelector, monitorStatusSelector } from '../../../state/selectors';
import { MonitorStatusBarComponent } from '../../functional/monitor_status_details/monitor_status_bar';
-import { getMonitorStatusAction, getSelectedMonitorAction } from '../../../state/actions';
+import { getMonitorStatusAction } from '../../../state/actions';
import { useUrlParams } from '../../../hooks';
import { Ping } from '../../../../common/graphql/types';
import { MonitorLocations } from '../../../../common/runtime_types/monitor';
@@ -23,7 +23,6 @@ interface StateProps {
interface DispatchProps {
loadMonitorStatus: typeof getMonitorStatusAction;
- loadSelectedMonitor: typeof getSelectedMonitorAction;
}
interface OwnProps {
@@ -34,7 +33,6 @@ type Props = OwnProps & StateProps & DispatchProps;
const Container: React.FC = ({
loadMonitorStatus,
- loadSelectedMonitor,
monitorId,
monitorStatus,
monitorLocations,
@@ -46,8 +44,7 @@ const Container: React.FC = ({
useEffect(() => {
loadMonitorStatus({ dateStart, dateEnd, monitorId });
- loadSelectedMonitor({ monitorId });
- }, [monitorId, dateStart, dateEnd, loadMonitorStatus, lastRefresh, loadSelectedMonitor]);
+ }, [monitorId, dateStart, dateEnd, loadMonitorStatus, lastRefresh]);
return (
= ({
};
const mapStateToProps = (state: AppState, ownProps: OwnProps) => ({
- monitorStatus: selectMonitorStatus(state),
+ monitorStatus: monitorStatusSelector(state),
monitorLocations: monitorLocationsSelector(state, ownProps.monitorId),
});
const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({
- loadSelectedMonitor: params => dispatch(getSelectedMonitorAction(params)),
loadMonitorStatus: params => dispatch(getMonitorStatusAction(params)),
});
diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx
index 89227cdd56457..85d0b1b593704 100644
--- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx
+++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx
@@ -9,7 +9,12 @@ import uuid from 'uuid';
import styled from 'styled-components';
import { npStart } from 'ui/new_platform';
-import { ViewMode } from '../../../../../../../../../src/plugins/embeddable/public';
+import {
+ ViewMode,
+ EmbeddableOutput,
+ ErrorEmbeddable,
+ isErrorEmbeddable,
+} from '../../../../../../../../../src/plugins/embeddable/public';
import * as i18n from './translations';
import { MapEmbeddable, MapEmbeddableInput } from '../../../../../../maps/public';
import { MAP_SAVED_OBJECT_TYPE } from '../../../../../../../../plugins/maps/public';
@@ -45,9 +50,13 @@ const EmbeddedPanel = styled.div`
export const EmbeddedMap = React.memo(({ upPoints, downPoints }: EmbeddedMapProps) => {
const { colors } = useContext(UptimeThemeContext);
- const [embeddable, setEmbeddable] = useState();
+ const [embeddable, setEmbeddable] = useState();
const embeddableRoot: React.RefObject = useRef(null);
- const factory = npStart.plugins.embeddable.getEmbeddableFactory(MAP_SAVED_OBJECT_TYPE);
+ const factory = npStart.plugins.embeddable.getEmbeddableFactory<
+ MapEmbeddableInput,
+ EmbeddableOutput,
+ MapEmbeddable
+ >(MAP_SAVED_OBJECT_TYPE);
const input: MapEmbeddableInput = {
id: uuid.v4(),
@@ -76,12 +85,17 @@ export const EmbeddedMap = React.memo(({ upPoints, downPoints }: EmbeddedMapProp
useEffect(() => {
async function setupEmbeddable() {
- const mapState = {
- layerList: getLayerList(upPoints, downPoints, colors),
+ if (!factory) {
+ throw new Error('Map embeddable not found.');
+ }
+ const embeddableObject = await factory.create({
+ ...input,
title: i18n.MAP_TITLE,
- };
- // @ts-ignore
- const embeddableObject = await factory.createFromState(mapState, input, undefined);
+ });
+
+ if (embeddableObject && !isErrorEmbeddable(embeddableObject)) {
+ embeddableObject.setLayerList(getLayerList(upPoints, downPoints, colors));
+ }
setEmbeddable(embeddableObject);
}
@@ -93,7 +107,7 @@ export const EmbeddedMap = React.memo(({ upPoints, downPoints }: EmbeddedMapProp
// update map layers based on points
useEffect(() => {
- if (embeddable) {
+ if (embeddable && !isErrorEmbeddable(embeddable)) {
embeddable.setLayerList(getLayerList(upPoints, downPoints, colors));
}
}, [upPoints, downPoints, embeddable, colors]);
diff --git a/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/__tests__/__snapshots__/ml_job_link.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/__tests__/__snapshots__/ml_job_link.test.tsx.snap
index 85988af04a939..9957f13fc1334 100644
--- a/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/__tests__/__snapshots__/ml_job_link.test.tsx.snap
+++ b/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/__tests__/__snapshots__/ml_job_link.test.tsx.snap
@@ -3,7 +3,7 @@
exports[`ML JobLink renders without errors 1`] = `
diff --git a/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/ml_flyout_container.tsx b/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/ml_flyout_container.tsx
index fad12de94fd81..9eed24e2810d8 100644
--- a/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/ml_flyout_container.tsx
+++ b/x-pack/legacy/plugins/uptime/public/components/monitor_details/ml/ml_flyout_container.tsx
@@ -12,6 +12,7 @@ import {
hasMLJobSelector,
hasNewMLJobSelector,
isMLJobCreatingSelector,
+ selectDynamicSettings,
} from '../../../state/selectors';
import { createMLJobAction, getExistingMLJobAction } from '../../../state/actions';
import { MLJobLink } from './ml_job_link';
@@ -24,6 +25,7 @@ import { MLFlyoutView } from './ml_flyout';
import { ML_JOB_ID } from '../../../../common/constants';
import { UptimeRefreshContext, UptimeSettingsContext } from '../../../contexts';
import { useUrlParams } from '../../../hooks';
+import { getDynamicSettings } from '../../../state/actions/dynamic_settings';
interface Props {
onClose: () => void;
@@ -48,13 +50,13 @@ const showMLJobNotification = (
),
- toastLifeTimeMs: 5000,
+ toastLifeTimeMs: 10000,
});
} else {
- notifications.toasts.warning({
+ notifications.toasts.danger({
title: {labels.JOB_CREATION_FAILED}
,
body: message ?? {labels.JOB_CREATION_FAILED_MESSAGE}
,
- toastLifeTimeMs: 5000,
+ toastLifeTimeMs: 10000,
});
}
};
@@ -65,6 +67,12 @@ export const MachineLearningFlyout: React.FC = ({ onClose }) => {
const dispatch = useDispatch();
const { data: hasMLJob, error } = useSelector(hasNewMLJobSelector);
const isMLJobCreating = useSelector(isMLJobCreatingSelector);
+ const { settings } = useSelector(selectDynamicSettings);
+ useEffect(() => {
+ // Attempt to load or refresh the dynamic settings
+ dispatch(getDynamicSettings({}));
+ }, [dispatch]);
+ const heartbeatIndices = settings?.heartbeatIndices || '';
const { basePath } = useContext(UptimeSettingsContext);
const { refreshApp } = useContext(UptimeRefreshContext);
@@ -72,9 +80,12 @@ export const MachineLearningFlyout: React.FC = ({ onClose }) => {
let { monitorId } = useParams();
monitorId = atob(monitorId || '');
- const createMLJob = () => dispatch(createMLJobAction.get({ monitorId: monitorId as string }));
+ const canCreateMLJob = useSelector(canCreateMLJobSelector) && heartbeatIndices !== '';
- const canCreateMLJob = useSelector(canCreateMLJobSelector);
+ // This function is a noop in the form's disabled state
+ const createMLJob = heartbeatIndices
+ ? () => dispatch(createMLJobAction.get({ monitorId: monitorId as string, heartbeatIndices }))
+ : () => null;
const { data: uptimeJobs } = useSelector(hasMLJobSelector);
@@ -108,7 +119,7 @@ export const MachineLearningFlyout: React.FC = ({ onClose }) => {
basePath,
{ to: dateRangeEnd, from: dateRangeStart },
false,
- error?.body?.message
+ error?.message || error?.body?.message
);
}
setIsCreatingJob(false);
@@ -130,9 +141,9 @@ export const MachineLearningFlyout: React.FC = ({ onClose }) => {
useEffect(() => {
if (hasExistingMLJob) {
setIsCreatingJob(true);
- dispatch(createMLJobAction.get({ monitorId: monitorId as string }));
+ dispatch(createMLJobAction.get({ monitorId: monitorId as string, heartbeatIndices }));
}
- }, [dispatch, hasExistingMLJob, monitorId]);
+ }, [dispatch, hasExistingMLJob, heartbeatIndices, monitorId]);
if (hasExistingMLJob) {
return null;
diff --git a/x-pack/legacy/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts b/x-pack/legacy/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts
index 34b330e9ca1b0..81402c00e484e 100644
--- a/x-pack/legacy/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts
+++ b/x-pack/legacy/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts
@@ -174,7 +174,7 @@ describe('monitor status alert type', () => {
{{context.downMonitorsWithGeo}}",
"iconClass": "uptimeApp",
"id": "xpack.uptime.alerts.monitorStatus",
- "name": "Uptime Monitor Status",
+ "name": "Uptime monitor status",
"validate": [Function],
}
`);
diff --git a/x-pack/legacy/plugins/uptime/public/lib/alert_types/monitor_status.tsx b/x-pack/legacy/plugins/uptime/public/lib/alert_types/monitor_status.tsx
index fde25ea30734f..d059274159c7f 100644
--- a/x-pack/legacy/plugins/uptime/public/lib/alert_types/monitor_status.tsx
+++ b/x-pack/legacy/plugins/uptime/public/lib/alert_types/monitor_status.tsx
@@ -61,7 +61,7 @@ export const initMonitorStatusAlertType: AlertTypeInitializer = ({
autocomplete,
}): AlertTypeModel => ({
id: 'xpack.uptime.alerts.monitorStatus',
- name: 'Uptime Monitor Status',
+ name: 'Uptime monitor status',
iconClass: 'uptimeApp',
alertParamsExpression: params => {
return ;
diff --git a/x-pack/legacy/plugins/uptime/public/pages/__tests__/__snapshots__/monitor.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/pages/__tests__/__snapshots__/monitor.test.tsx.snap
index f637af397bbeb..6064caa868bf8 100644
--- a/x-pack/legacy/plugins/uptime/public/pages/__tests__/__snapshots__/monitor.test.tsx.snap
+++ b/x-pack/legacy/plugins/uptime/public/pages/__tests__/__snapshots__/monitor.test.tsx.snap
@@ -51,6 +51,6 @@ exports[`MonitorPage shallow renders expected elements for valid props 1`] = `
}
}
>
-
+
`;
diff --git a/x-pack/legacy/plugins/uptime/public/pages/monitor.tsx b/x-pack/legacy/plugins/uptime/public/pages/monitor.tsx
index 3de636cac6ecd..21124b7323d68 100644
--- a/x-pack/legacy/plugins/uptime/public/pages/monitor.tsx
+++ b/x-pack/legacy/plugins/uptime/public/pages/monitor.tsx
@@ -5,45 +5,23 @@
*/
import { EuiSpacer } from '@elastic/eui';
-import React, { useContext, useState, useEffect } from 'react';
+import React, { useContext, useState } from 'react';
import { useParams } from 'react-router-dom';
-import { connect, MapDispatchToPropsFunction, MapStateToPropsParam } from 'react-redux';
+import { useSelector } from 'react-redux';
import { MonitorCharts, PingList } from '../components/functional';
import { UptimeRefreshContext } from '../contexts';
import { useUptimeTelemetry, useUrlParams, UptimePage } from '../hooks';
import { useTrackPageview } from '../../../../../plugins/observability/public';
import { MonitorStatusDetails } from '../components/connected';
-import { Ping } from '../../common/graphql/types';
-import { AppState } from '../state';
-import { selectSelectedMonitor } from '../state/selectors';
-import { getSelectedMonitorAction } from '../state/actions';
+import { monitorStatusSelector } from '../state/selectors';
import { PageHeader } from './page_header';
import { useBreadcrumbs } from '../hooks/use_breadcrumbs';
-interface StateProps {
- selectedMonitor: Ping | null;
-}
-
-interface DispatchProps {
- dispatchGetMonitorStatus: (monitorId: string) => void;
-}
-
-type Props = StateProps & DispatchProps;
-
-export const MonitorPageComponent: React.FC = ({
- selectedMonitor,
- dispatchGetMonitorStatus,
-}: Props) => {
+export const MonitorPage: React.FC = () => {
// decode 64 base string, it was decoded to make it a valid url, since monitor id can be a url
let { monitorId } = useParams();
monitorId = atob(monitorId || '');
- useEffect(() => {
- if (monitorId) {
- dispatchGetMonitorStatus(monitorId);
- }
- }, [dispatchGetMonitorStatus, monitorId]);
-
const [pingListPageCount, setPingListPageCount] = useState(10);
const { refreshApp } = useContext(UptimeRefreshContext);
const [getUrlParams, updateUrlParams] = useUrlParams();
@@ -53,11 +31,13 @@ export const MonitorPageComponent: React.FC = ({
const [selectedLocation, setSelectedLocation] = useState(undefined);
const [pingListIndex, setPingListIndex] = useState(0);
+ const selectedMonitor = useSelector(monitorStatusSelector);
+
const sharedVariables = {
dateRangeStart,
dateRangeEnd,
- location: selectedLocation,
monitorId,
+ location: selectedLocation,
};
useUptimeTelemetry(UptimePage.Monitor);
@@ -65,7 +45,7 @@ export const MonitorPageComponent: React.FC = ({
useTrackPageview({ app: 'uptime', path: 'monitor' });
useTrackPageview({ app: 'uptime', path: 'monitor', delay: 15000 });
- const nameOrId = selectedMonitor?.monitor?.name || selectedMonitor?.monitor?.id || '';
+ const nameOrId = selectedMonitor?.monitor?.name || monitorId || '';
useBreadcrumbs([{ text: nameOrId }]);
return (
<>
@@ -97,21 +77,3 @@ export const MonitorPageComponent: React.FC = ({
>
);
};
-
-const mapStateToProps: MapStateToPropsParam = state => ({
- selectedMonitor: selectSelectedMonitor(state),
-});
-
-const mapDispatchToProps: MapDispatchToPropsFunction = (dispatch, own) => {
- return {
- dispatchGetMonitorStatus: (monitorId: string) => {
- dispatch(
- getSelectedMonitorAction({
- monitorId,
- })
- );
- },
- };
-};
-
-export const MonitorPage = connect(mapStateToProps, mapDispatchToProps)(MonitorPageComponent);
diff --git a/x-pack/legacy/plugins/uptime/public/state/actions/ml_anomaly.ts b/x-pack/legacy/plugins/uptime/public/state/actions/ml_anomaly.ts
index 9a8e4036f2cff..2e83490b71b54 100644
--- a/x-pack/legacy/plugins/uptime/public/state/actions/ml_anomaly.ts
+++ b/x-pack/legacy/plugins/uptime/public/state/actions/ml_anomaly.ts
@@ -8,7 +8,12 @@ import { createAction } from 'redux-actions';
import { createAsyncAction } from './utils';
import { PrivilegesResponse } from '../../../../../../plugins/ml/common/types/privileges';
import { AnomaliesTableRecord } from '../../../../../../plugins/ml/common/types/anomalies';
-import { CreateMLJobSuccess, DeleteJobResults, MonitorIdParam } from './types';
+import {
+ CreateMLJobSuccess,
+ DeleteJobResults,
+ MonitorIdParam,
+ HeartbeatIndicesParam,
+} from './types';
import { JobExistResult } from '../../../../../../plugins/ml/common/types/data_recognizer';
export const resetMLState = createAction('RESET_ML_STATE');
@@ -17,9 +22,10 @@ export const getExistingMLJobAction = createAsyncAction(
- 'CREATE_ML_JOB'
-);
+export const createMLJobAction = createAsyncAction<
+ MonitorIdParam & HeartbeatIndicesParam,
+ CreateMLJobSuccess | null
+>('CREATE_ML_JOB');
export const getMLCapabilitiesAction = createAsyncAction(
'GET_ML_CAPABILITIES'
diff --git a/x-pack/legacy/plugins/uptime/public/state/actions/monitor_status.ts b/x-pack/legacy/plugins/uptime/public/state/actions/monitor_status.ts
index 7917628abf7da..a8f37d38ebae6 100644
--- a/x-pack/legacy/plugins/uptime/public/state/actions/monitor_status.ts
+++ b/x-pack/legacy/plugins/uptime/public/state/actions/monitor_status.ts
@@ -7,10 +7,6 @@ import { createAction } from 'redux-actions';
import { QueryParams } from './types';
import { Ping } from '../../../common/graphql/types';
-export const getSelectedMonitorAction = createAction<{ monitorId: string }>('GET_SELECTED_MONITOR');
-export const getSelectedMonitorActionSuccess = createAction('GET_SELECTED_MONITOR_SUCCESS');
-export const getSelectedMonitorActionFail = createAction('GET_SELECTED_MONITOR_FAIL');
-
export const getMonitorStatusAction = createAction('GET_MONITOR_STATUS');
export const getMonitorStatusActionSuccess = createAction('GET_MONITOR_STATUS_SUCCESS');
export const getMonitorStatusActionFail = createAction('GET_MONITOR_STATUS_FAIL');
diff --git a/x-pack/legacy/plugins/uptime/public/state/actions/types.ts b/x-pack/legacy/plugins/uptime/public/state/actions/types.ts
index 236b263414a26..41381afd31453 100644
--- a/x-pack/legacy/plugins/uptime/public/state/actions/types.ts
+++ b/x-pack/legacy/plugins/uptime/public/state/actions/types.ts
@@ -22,6 +22,10 @@ export interface MonitorIdParam {
monitorId: string;
}
+export interface HeartbeatIndicesParam {
+ heartbeatIndices: string;
+}
+
export interface QueryParams {
monitorId: string;
dateStart: string;
diff --git a/x-pack/legacy/plugins/uptime/public/state/api/ml_anomaly.ts b/x-pack/legacy/plugins/uptime/public/state/api/ml_anomaly.ts
index 80b97783769b5..bcd2582fe18b9 100644
--- a/x-pack/legacy/plugins/uptime/public/state/api/ml_anomaly.ts
+++ b/x-pack/legacy/plugins/uptime/public/state/api/ml_anomaly.ts
@@ -7,13 +7,18 @@
import moment from 'moment';
import { apiService } from './utils';
import { AnomalyRecords, AnomalyRecordsParams } from '../actions';
-import { API_URLS, INDEX_NAMES, ML_JOB_ID, ML_MODULE_ID } from '../../../common/constants';
+import { API_URLS, ML_JOB_ID, ML_MODULE_ID } from '../../../common/constants';
import { PrivilegesResponse } from '../../../../../../plugins/ml/common/types/privileges';
-import { CreateMLJobSuccess, DeleteJobResults, MonitorIdParam } from '../actions/types';
+import {
+ CreateMLJobSuccess,
+ DeleteJobResults,
+ MonitorIdParam,
+ HeartbeatIndicesParam,
+} from '../actions/types';
import { DataRecognizerConfigResponse } from '../../../../../../plugins/ml/common/types/modules';
import { JobExistResult } from '../../../../../../plugins/ml/common/types/data_recognizer';
-export const getMLJobId = (monitorId: string) => `${monitorId}_${ML_JOB_ID}`;
+export const getMLJobId = (monitorId: string) => `${monitorId}_${ML_JOB_ID}`.toLowerCase();
export const getMLCapabilities = async (): Promise => {
return await apiService.get(API_URLS.ML_CAPABILITIES);
@@ -25,23 +30,27 @@ export const getExistingJobs = async (): Promise => {
export const createMLJob = async ({
monitorId,
-}: MonitorIdParam): Promise => {
+ heartbeatIndices,
+}: MonitorIdParam & HeartbeatIndicesParam): Promise => {
const url = API_URLS.ML_SETUP_MODULE + ML_MODULE_ID;
+ // ML App doesn't support upper case characters in job name
+ const lowerCaseMonitorId = monitorId.toLowerCase();
+
const data = {
- prefix: `${monitorId}_`,
+ prefix: `${lowerCaseMonitorId}_`,
useDedicatedIndex: false,
startDatafeed: true,
start: moment()
.subtract(24, 'h')
.valueOf(),
- indexPatternName: INDEX_NAMES.HEARTBEAT,
+ indexPatternName: heartbeatIndices,
query: {
bool: {
filter: [
{
term: {
- 'monitor.id': monitorId,
+ 'monitor.id': lowerCaseMonitorId,
},
},
],
@@ -50,11 +59,17 @@ export const createMLJob = async ({
};
const response: DataRecognizerConfigResponse = await apiService.post(url, data);
- if (response?.jobs?.[0]?.id === getMLJobId(monitorId) && response?.jobs?.[0]?.success) {
- return {
- count: 1,
- jobId: response?.jobs?.[0]?.id,
- };
+ if (response?.jobs?.[0]?.id === getMLJobId(monitorId)) {
+ const jobResponse = response.jobs[0];
+ if (jobResponse.success) {
+ return {
+ count: 1,
+ jobId: jobResponse.id,
+ };
+ } else {
+ const { error } = jobResponse;
+ throw new Error(error?.msg);
+ }
} else {
return null;
}
diff --git a/x-pack/legacy/plugins/uptime/public/state/api/monitor_status.ts b/x-pack/legacy/plugins/uptime/public/state/api/monitor_status.ts
index 0f7608ba57ea7..f9e171adda334 100644
--- a/x-pack/legacy/plugins/uptime/public/state/api/monitor_status.ts
+++ b/x-pack/legacy/plugins/uptime/public/state/api/monitor_status.ts
@@ -7,19 +7,7 @@
import { QueryParams } from '../actions/types';
import { Ping } from '../../../common/graphql/types';
import { apiService } from './utils';
-import { API_URLS } from '../../../common/constants/rest_api';
-
-export interface APIParams {
- monitorId: string;
-}
-
-export const fetchSelectedMonitor = async ({ monitorId }: APIParams): Promise => {
- const queryParams = {
- monitorId,
- };
-
- return await apiService.get(API_URLS.MONITOR_SELECTED, queryParams);
-};
+import { API_URLS } from '../../../common/constants';
export const fetchMonitorStatus = async ({
monitorId,
diff --git a/x-pack/legacy/plugins/uptime/public/state/effects/monitor_status.ts b/x-pack/legacy/plugins/uptime/public/state/effects/monitor_status.ts
index 1207ab20bc711..2669629ed34f5 100644
--- a/x-pack/legacy/plugins/uptime/public/state/effects/monitor_status.ts
+++ b/x-pack/legacy/plugins/uptime/public/state/effects/monitor_status.ts
@@ -6,14 +6,11 @@
import { takeLatest } from 'redux-saga/effects';
import {
- getSelectedMonitorAction,
- getSelectedMonitorActionSuccess,
- getSelectedMonitorActionFail,
getMonitorStatusAction,
getMonitorStatusActionSuccess,
getMonitorStatusActionFail,
} from '../actions';
-import { fetchSelectedMonitor, fetchMonitorStatus } from '../api';
+import { fetchMonitorStatus } from '../api';
import { fetchEffectFactory } from './fetch_effect';
export function* fetchMonitorStatusEffect() {
@@ -25,13 +22,4 @@ export function* fetchMonitorStatusEffect() {
getMonitorStatusActionFail
)
);
-
- yield takeLatest(
- getSelectedMonitorAction,
- fetchEffectFactory(
- fetchSelectedMonitor,
- getSelectedMonitorActionSuccess,
- getSelectedMonitorActionFail
- )
- );
}
diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/monitor_status.ts b/x-pack/legacy/plugins/uptime/public/state/reducers/monitor_status.ts
index c2dfbd7f90ff2..6cfaa9f8f59c1 100644
--- a/x-pack/legacy/plugins/uptime/public/state/reducers/monitor_status.ts
+++ b/x-pack/legacy/plugins/uptime/public/state/reducers/monitor_status.ts
@@ -5,9 +5,6 @@
*/
import { handleActions, Action } from 'redux-actions';
import {
- getSelectedMonitorAction,
- getSelectedMonitorActionSuccess,
- getSelectedMonitorActionFail,
getMonitorStatusAction,
getMonitorStatusActionSuccess,
getMonitorStatusActionFail,
@@ -17,13 +14,11 @@ import { QueryParams } from '../actions/types';
export interface MonitorStatusState {
status: Ping | null;
- monitor: Ping | null;
loading: boolean;
}
const initialState: MonitorStatusState = {
status: null,
- monitor: null,
loading: false,
};
@@ -31,32 +26,22 @@ type MonitorStatusPayload = QueryParams & Ping;
export const monitorStatusReducer = handleActions(
{
- [String(getSelectedMonitorAction)]: (state, action: Action) => ({
- ...state,
- loading: true,
- }),
-
- [String(getSelectedMonitorActionSuccess)]: (state, action: Action) => ({
- ...state,
- loading: false,
- monitor: { ...action.payload } as Ping,
- }),
-
- [String(getSelectedMonitorActionFail)]: (state, action: Action) => ({
- ...state,
- loading: false,
- }),
-
[String(getMonitorStatusAction)]: (state, action: Action) => ({
...state,
loading: true,
}),
- [String(getMonitorStatusActionSuccess)]: (state, action: Action) => ({
- ...state,
- loading: false,
- status: { ...action.payload } as Ping,
- }),
+ [String(getMonitorStatusActionSuccess)]: (state, action: Action) => {
+ return {
+ ...state,
+ loading: false,
+ // Keeping url from prev request to display, if there is no latest status
+ status: {
+ url: action.payload?.url || state.status?.url,
+ ...action.payload,
+ } as Ping,
+ };
+ },
[String(getMonitorStatusActionFail)]: (state, action: Action) => ({
...state,
diff --git a/x-pack/legacy/plugins/uptime/public/state/selectors/__tests__/index.test.ts b/x-pack/legacy/plugins/uptime/public/state/selectors/__tests__/index.test.ts
index 573d5b1906082..3b4547514a11e 100644
--- a/x-pack/legacy/plugins/uptime/public/state/selectors/__tests__/index.test.ts
+++ b/x-pack/legacy/plugins/uptime/public/state/selectors/__tests__/index.test.ts
@@ -46,7 +46,6 @@ describe('state selectors', () => {
},
monitorStatus: {
status: null,
- monitor: null,
loading: false,
},
indexPattern: {
diff --git a/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts b/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts
index 21e01bd7d8279..0fc3c7151cb3b 100644
--- a/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts
+++ b/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts
@@ -22,9 +22,7 @@ export const monitorLocationsSelector = (state: AppState, monitorId: string) =>
return state.monitor.monitorLocationsList?.get(monitorId);
};
-export const selectSelectedMonitor = (state: AppState) => state.monitorStatus.monitor;
-
-export const selectMonitorStatus = (state: AppState) => state.monitorStatus.status;
+export const monitorStatusSelector = (state: AppState) => state.monitorStatus.status;
export const selectDynamicSettings = (state: AppState) => {
return state.dynamicSettings;
diff --git a/x-pack/legacy/plugins/xpack_main/index.js b/x-pack/legacy/plugins/xpack_main/index.js
index 4cf32b971b57e..6ce457ffbec05 100644
--- a/x-pack/legacy/plugins/xpack_main/index.js
+++ b/x-pack/legacy/plugins/xpack_main/index.js
@@ -23,11 +23,6 @@ export const xpackMain = kibana => {
config(Joi) {
return Joi.object({
enabled: Joi.boolean().default(true),
- telemetry: Joi.object({
- config: Joi.string().default(),
- enabled: Joi.boolean().default(),
- url: Joi.string().default(),
- }).default(), // deprecated
}).default();
},
diff --git a/x-pack/plugins/advanced_ui_actions/public/custom_time_range_action.test.ts b/x-pack/plugins/advanced_ui_actions/public/custom_time_range_action.test.ts
index 91b829a020048..10860fe471a3b 100644
--- a/x-pack/plugins/advanced_ui_actions/public/custom_time_range_action.test.ts
+++ b/x-pack/plugins/advanced_ui_actions/public/custom_time_range_action.test.ts
@@ -10,9 +10,7 @@ import { skip } from 'rxjs/operators';
import * as Rx from 'rxjs';
import { mount } from 'enzyme';
-import { EmbeddableFactory } from '../../../../src/plugins/embeddable/public';
import { TimeRangeEmbeddable, TimeRangeContainer, TIME_RANGE_EMBEDDABLE } from './test_helpers';
-import { TimeRangeEmbeddableFactory } from './test_helpers/time_range_embeddable_factory';
import { CustomTimeRangeAction } from './custom_time_range_action';
/* eslint-disable */
import {
@@ -21,7 +19,6 @@ import {
/* eslint-enable */
import {
- HelloWorldEmbeddableFactory,
HelloWorldEmbeddable,
HELLO_WORLD_EMBEDDABLE,
} from '../../../../examples/embeddable_examples/public';
@@ -38,9 +35,6 @@ const createOpenModalMock = () => {
};
test('Custom time range action prevents embeddable from using container time', async done => {
- const embeddableFactories = new Map();
- embeddableFactories.set(TIME_RANGE_EMBEDDABLE, new TimeRangeEmbeddableFactory());
-
const container = new TimeRangeContainer(
{
timeRange: { from: 'now-15m', to: 'now' },
@@ -105,9 +99,6 @@ test('Custom time range action prevents embeddable from using container time', a
});
test('Removing custom time range action resets embeddable back to container time', async done => {
- const embeddableFactories = new Map();
- embeddableFactories.set(TIME_RANGE_EMBEDDABLE, new TimeRangeEmbeddableFactory());
-
const container = new TimeRangeContainer(
{
timeRange: { from: 'now-15m', to: 'now' },
@@ -182,9 +173,6 @@ test('Removing custom time range action resets embeddable back to container time
});
test('Cancelling custom time range action leaves state alone', async done => {
- const embeddableFactories = new Map();
- embeddableFactories.set(TIME_RANGE_EMBEDDABLE, new TimeRangeEmbeddableFactory());
-
const container = new TimeRangeContainer(
{
timeRange: { from: 'now-15m', to: 'now' },
@@ -244,8 +232,6 @@ test('Cancelling custom time range action leaves state alone', async done => {
});
test(`badge is compatible with embeddable that inherits from parent`, async () => {
- const embeddableFactories = new Map();
- embeddableFactories.set(TIME_RANGE_EMBEDDABLE, new TimeRangeEmbeddableFactory());
const container = new TimeRangeContainer(
{
timeRange: { from: 'now-15m', to: 'now' },
@@ -279,8 +265,6 @@ test(`badge is compatible with embeddable that inherits from parent`, async () =
// TODO: uncomment when https://github.com/elastic/kibana/issues/43271 is fixed.
// test('Embeddable that does not use time range in a container that has time range is incompatible', async () => {
-// const embeddableFactories = new Map();
-// embeddableFactories.set(HELLO_WORLD_EMBEDDABLE, new HelloWorldEmbeddableFactory());
// const container = new TimeRangeContainer(
// {
// timeRange: { from: 'now-15m', to: 'now' },
@@ -315,8 +299,6 @@ test(`badge is compatible with embeddable that inherits from parent`, async () =
// });
test('Attempting to execute on incompatible embeddable throws an error', async () => {
- const embeddableFactories = new Map();
- embeddableFactories.set(HELLO_WORLD_EMBEDDABLE, new HelloWorldEmbeddableFactory());
const container = new HelloWorldContainer(
{
panels: {
diff --git a/x-pack/plugins/advanced_ui_actions/public/custom_time_range_badge.test.ts b/x-pack/plugins/advanced_ui_actions/public/custom_time_range_badge.test.ts
index d2b9fa9ac1655..3bf763470f002 100644
--- a/x-pack/plugins/advanced_ui_actions/public/custom_time_range_badge.test.ts
+++ b/x-pack/plugins/advanced_ui_actions/public/custom_time_range_badge.test.ts
@@ -9,17 +9,12 @@ import { findTestSubject } from '@elastic/eui/lib/test';
import { skip } from 'rxjs/operators';
import * as Rx from 'rxjs';
import { mount } from 'enzyme';
-import { EmbeddableFactory } from '../../../../src/plugins/embeddable/public';
import { TimeRangeEmbeddable, TimeRangeContainer, TIME_RANGE_EMBEDDABLE } from './test_helpers';
-import { TimeRangeEmbeddableFactory } from './test_helpers/time_range_embeddable_factory';
import { CustomTimeRangeBadge } from './custom_time_range_badge';
import { ReactElement } from 'react';
import { nextTick } from 'test_utils/enzyme_helpers';
test('Removing custom time range from badge resets embeddable back to container time', async done => {
- const embeddableFactories = new Map();
- embeddableFactories.set(TIME_RANGE_EMBEDDABLE, new TimeRangeEmbeddableFactory());
-
const container = new TimeRangeContainer(
{
timeRange: { from: 'now-15m', to: 'now' },
@@ -79,8 +74,6 @@ test('Removing custom time range from badge resets embeddable back to container
});
test(`badge is not compatible with embeddable that inherits from parent`, async () => {
- const embeddableFactories = new Map();
- embeddableFactories.set(TIME_RANGE_EMBEDDABLE, new TimeRangeEmbeddableFactory());
const container = new TimeRangeContainer(
{
timeRange: { from: 'now-15m', to: 'now' },
@@ -113,8 +106,6 @@ test(`badge is not compatible with embeddable that inherits from parent`, async
});
test(`badge is compatible with embeddable that has custom time range`, async () => {
- const embeddableFactories = new Map();
- embeddableFactories.set(TIME_RANGE_EMBEDDABLE, new TimeRangeEmbeddableFactory());
const container = new TimeRangeContainer(
{
timeRange: { from: 'now-15m', to: 'now' },
@@ -148,8 +139,6 @@ test(`badge is compatible with embeddable that has custom time range`, async ()
});
test('Attempting to execute on incompatible embeddable throws an error', async () => {
- const embeddableFactories = new Map();
- embeddableFactories.set(TIME_RANGE_EMBEDDABLE, new TimeRangeEmbeddableFactory());
const container = new TimeRangeContainer(
{
timeRange: { from: 'now-15m', to: 'now' },
diff --git a/x-pack/plugins/advanced_ui_actions/public/test_helpers/time_range_embeddable_factory.ts b/x-pack/plugins/advanced_ui_actions/public/test_helpers/time_range_embeddable_factory.ts
index 311d3357476b9..e8d9451f9f2a6 100644
--- a/x-pack/plugins/advanced_ui_actions/public/test_helpers/time_range_embeddable_factory.ts
+++ b/x-pack/plugins/advanced_ui_actions/public/test_helpers/time_range_embeddable_factory.ts
@@ -7,7 +7,7 @@
import {
EmbeddableInput,
IContainer,
- EmbeddableFactory,
+ EmbeddableFactoryDefinition,
} from '../../../../../src/plugins/embeddable/public';
import { TimeRange } from '../../../../../src/plugins/data/public';
import { TIME_RANGE_EMBEDDABLE, TimeRangeEmbeddable } from './time_range_embeddable';
@@ -16,7 +16,8 @@ interface EmbeddableTimeRangeInput extends EmbeddableInput {
timeRange: TimeRange;
}
-export class TimeRangeEmbeddableFactory extends EmbeddableFactory {
+export class TimeRangeEmbeddableFactory
+ implements EmbeddableFactoryDefinition {
public readonly type = TIME_RANGE_EMBEDDABLE;
public async isEditable() {
diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.test.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.test.ts
index 5c15c398dbdcd..315e4800d4c73 100644
--- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.test.ts
+++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.test.ts
@@ -20,7 +20,7 @@ describe('alertType', () => {
it('alert type creation structure is the expected value', async () => {
expect(alertType.id).toBe('.index-threshold');
- expect(alertType.name).toBe('Index Threshold');
+ expect(alertType.name).toBe('Index threshold');
expect(alertType.actionGroups).toEqual([{ id: 'threshold met', name: 'Threshold Met' }]);
expect(alertType.actionVariables).toMatchInlineSnapshot(`
diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.ts
index 6d27f8a99dd4b..4d79efc7c9478 100644
--- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.ts
+++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.ts
@@ -22,7 +22,7 @@ export function getAlertType(service: Service): AlertType {
const { logger } = service;
const alertTypeName = i18n.translate('xpack.alertingBuiltins.indexThreshold.alertTypeTitle', {
- defaultMessage: 'Index Threshold',
+ defaultMessage: 'Index threshold',
});
const actionGroupName = i18n.translate(
diff --git a/x-pack/plugins/alerting_builtins/server/plugin.test.ts b/x-pack/plugins/alerting_builtins/server/plugin.test.ts
index 6bcf0379d5abe..f93041fa3c142 100644
--- a/x-pack/plugins/alerting_builtins/server/plugin.test.ts
+++ b/x-pack/plugins/alerting_builtins/server/plugin.test.ts
@@ -37,7 +37,7 @@ describe('AlertingBuiltins Plugin', () => {
},
],
"id": ".index-threshold",
- "name": "Index Threshold",
+ "name": "Index threshold",
}
`);
});
diff --git a/x-pack/plugins/apm/common/agent_configuration/runtime_types/duration_rt.test.ts b/x-pack/plugins/apm/common/agent_configuration/runtime_types/duration_rt.test.ts
index 6b81031542c34..a83ee9262cad6 100644
--- a/x-pack/plugins/apm/common/agent_configuration/runtime_types/duration_rt.test.ts
+++ b/x-pack/plugins/apm/common/agent_configuration/runtime_types/duration_rt.test.ts
@@ -25,7 +25,7 @@ describe('durationRt', () => {
});
describe('It should accept', () => {
- ['1s', '2m', '3h'].map(input => {
+ ['1000ms', '2s', '3m'].map(input => {
it(`${JSON.stringify(input)}`, () => {
expect(isRight(durationRt.decode(input))).toBe(true);
});
diff --git a/x-pack/plugins/apm/common/agent_configuration/runtime_types/duration_rt.ts b/x-pack/plugins/apm/common/agent_configuration/runtime_types/duration_rt.ts
index 99e6a57089dee..383fd69be9a78 100644
--- a/x-pack/plugins/apm/common/agent_configuration/runtime_types/duration_rt.ts
+++ b/x-pack/plugins/apm/common/agent_configuration/runtime_types/duration_rt.ts
@@ -8,7 +8,7 @@ import * as t from 'io-ts';
import { either } from 'fp-ts/lib/Either';
import { amountAndUnitToObject } from '../amount_and_unit';
-export const DURATION_UNITS = ['s', 'm', 'h'];
+export const DURATION_UNITS = ['ms', 's', 'm'];
export const durationRt = new t.Type(
'durationRt',
diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap
index 0c585bec22f6c..81adf76ac4ce9 100644
--- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap
+++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap
@@ -17,9 +17,9 @@ Array [
"key": "api_request_time",
"type": "duration",
"units": Array [
+ "ms",
"s",
"m",
- "h",
],
"validationError": "Please specify an integer and a unit",
"validationName": "durationRt",
@@ -82,9 +82,9 @@ Array [
"key": "profiling_inferred_spans_min_duration",
"type": "duration",
"units": Array [
+ "ms",
"s",
"m",
- "h",
],
"validationError": "Please specify an integer and a unit",
"validationName": "durationRt",
@@ -93,9 +93,9 @@ Array [
"key": "profiling_inferred_spans_sampling_interval",
"type": "duration",
"units": Array [
+ "ms",
"s",
"m",
- "h",
],
"validationError": "Please specify an integer and a unit",
"validationName": "durationRt",
@@ -109,9 +109,9 @@ Array [
"key": "server_timeout",
"type": "duration",
"units": Array [
+ "ms",
"s",
"m",
- "h",
],
"validationError": "Please specify an integer and a unit",
"validationName": "durationRt",
@@ -120,9 +120,9 @@ Array [
"key": "span_frames_min_duration",
"type": "duration",
"units": Array [
+ "ms",
"s",
"m",
- "h",
],
"validationError": "Please specify an integer and a unit",
"validationName": "durationRt",
@@ -137,9 +137,9 @@ Array [
"key": "stress_monitor_cpu_duration_threshold",
"type": "duration",
"units": Array [
+ "ms",
"s",
"m",
- "h",
],
"validationError": "Please specify an integer and a unit",
"validationName": "durationRt",
diff --git a/x-pack/plugins/apm/common/alert_types.ts b/x-pack/plugins/apm/common/alert_types.ts
index 51e1f88512965..8a342fab71e66 100644
--- a/x-pack/plugins/apm/common/alert_types.ts
+++ b/x-pack/plugins/apm/common/alert_types.ts
@@ -14,7 +14,7 @@ export enum AlertType {
export const ALERT_TYPES_CONFIG = {
[AlertType.ErrorRate]: {
name: i18n.translate('xpack.apm.errorRateAlert.name', {
- defaultMessage: 'Error rate threshold'
+ defaultMessage: 'Error rate'
}),
actionGroups: [
{
@@ -28,7 +28,7 @@ export const ALERT_TYPES_CONFIG = {
},
[AlertType.TransactionDuration]: {
name: i18n.translate('xpack.apm.transactionDurationAlert.name', {
- defaultMessage: 'Transaction duration threshold'
+ defaultMessage: 'Transaction duration'
}),
actionGroups: [
{
diff --git a/x-pack/plugins/apm/common/service_map.test.ts b/x-pack/plugins/apm/common/service_map.test.ts
new file mode 100644
index 0000000000000..40b220ffe68f1
--- /dev/null
+++ b/x-pack/plugins/apm/common/service_map.test.ts
@@ -0,0 +1,97 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { License } from '../../licensing/common/license';
+import * as serviceMap from './service_map';
+
+describe('service map helpers', () => {
+ describe('isValidPlatinumLicense', () => {
+ describe('with an expired license', () => {
+ it('returns false', () => {
+ const license = new License({
+ license: {
+ uid: 'test uid',
+ expiryDateInMillis: 0,
+ mode: 'platinum',
+ type: 'platinum',
+ status: 'expired'
+ },
+ signature: 'test signature'
+ });
+
+ expect(serviceMap.isValidPlatinumLicense(license)).toEqual(false);
+ });
+ });
+
+ describe('with a basic license', () => {
+ it('returns false', () => {
+ const license = new License({
+ license: {
+ uid: 'test uid',
+ expiryDateInMillis: 0,
+ mode: 'basic',
+ type: 'basic',
+ status: 'active'
+ },
+ signature: 'test signature'
+ });
+
+ expect(serviceMap.isValidPlatinumLicense(license)).toEqual(false);
+ });
+ });
+
+ describe('with a platinum license', () => {
+ it('returns true', () => {
+ const license = new License({
+ license: {
+ uid: 'test uid',
+ expiryDateInMillis: 0,
+ mode: 'platinum',
+ type: 'platinum',
+ status: 'active'
+ },
+ signature: 'test signature'
+ });
+
+ expect(serviceMap.isValidPlatinumLicense(license)).toEqual(true);
+ });
+ });
+
+ describe('with an enterprise license', () => {
+ it('returns true', () => {
+ const license = new License({
+ license: {
+ uid: 'test uid',
+ expiryDateInMillis: 0,
+ mode: 'enterprise',
+ type: 'enterprise',
+ status: 'active'
+ },
+ signature: 'test signature'
+ });
+
+ expect(serviceMap.isValidPlatinumLicense(license)).toEqual(true);
+ });
+ });
+
+ describe('with a trial license', () => {
+ it('returns true', () => {
+ const license = new License({
+ license: {
+ uid: 'test uid',
+ expiryDateInMillis: 0,
+ mode: 'trial',
+ type: 'trial',
+ status: 'active'
+ },
+ signature: 'test signature'
+ });
+
+ expect(serviceMap.isValidPlatinumLicense(license)).toEqual(true);
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/apm/common/service_map.ts b/x-pack/plugins/apm/common/service_map.ts
index 4a8608199c037..75c1c945c5d26 100644
--- a/x-pack/plugins/apm/common/service_map.ts
+++ b/x-pack/plugins/apm/common/service_map.ts
@@ -45,10 +45,7 @@ export interface ServiceNodeMetrics {
}
export function isValidPlatinumLicense(license: ILicense) {
- return (
- license.isActive &&
- (license.type === 'platinum' || license.type === 'trial')
- );
+ return license.isActive && license.hasAtLeast('platinum');
}
export const invalidLicenseMessage = i18n.translate(
diff --git a/x-pack/plugins/endpoint/common/generate_data.ts b/x-pack/plugins/endpoint/common/generate_data.ts
index 75351bb3bf07d..430ba1d422b96 100644
--- a/x-pack/plugins/endpoint/common/generate_data.ts
+++ b/x-pack/plugins/endpoint/common/generate_data.ts
@@ -83,6 +83,11 @@ const OTHER_EVENT_CATEGORIES: EventInfo[] = [
];
interface HostInfo {
+ elastic: {
+ agent: {
+ id: string;
+ };
+ };
agent: {
version: string;
id: string;
@@ -116,6 +121,11 @@ export class EndpointDocGenerator {
version: this.randomVersion(),
id: this.seededUUIDv4(),
},
+ elastic: {
+ agent: {
+ id: this.seededUUIDv4(),
+ },
+ },
host: {
id: this.seededUUIDv4(),
hostname: this.randomHostname(),
diff --git a/x-pack/plugins/endpoint/common/types.ts b/x-pack/plugins/endpoint/common/types.ts
index b3eb518e35ae3..565f47e7a0d6f 100644
--- a/x-pack/plugins/endpoint/common/types.ts
+++ b/x-pack/plugins/endpoint/common/types.ts
@@ -257,6 +257,11 @@ export type HostMetadata = Immutable<{
event: {
created: number;
};
+ elastic: {
+ agent: {
+ id: string;
+ };
+ };
endpoint: {
policy: {
id: string;
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/host_accordion.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/host_accordion.tsx
index edaba3725e027..e332c96192fab 100644
--- a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/host_accordion.tsx
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/host_accordion.tsx
@@ -6,32 +6,60 @@
import React, { memo, useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiAccordion, EuiDescriptionList } from '@elastic/eui';
-import { Immutable, AlertData } from '../../../../../../../common/types';
+import { EuiHealth } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { Immutable, AlertDetails } from '../../../../../../../common/types';
-export const HostAccordion = memo(({ alertData }: { alertData: Immutable }) => {
+export const HostAccordion = memo(({ alertData }: { alertData: Immutable }) => {
const columns = useMemo(() => {
return [
{
- title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.hostName', {
- defaultMessage: 'Host Name',
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.hostNameCurrent', {
+ defaultMessage: 'Host Name (Current)',
+ }),
+ description: alertData.state.host_metadata.host.hostname,
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.hostNameOriginal', {
+ defaultMessage: 'Host Name (At time of alert)',
}),
description: alertData.host.hostname,
},
{
- title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.hostIP', {
- defaultMessage: 'Host IP',
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.hostIPCurrent', {
+ defaultMessage: 'Host IP (Current)',
+ }),
+ description: alertData.state.host_metadata.host.ip.join(', '),
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.hostIPOriginal', {
+ defaultMessage: 'Host IP (At time of alert)',
}),
description: alertData.host.ip.join(', '),
},
{
- title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.status', {
- defaultMessage: 'Status',
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.currentStatus', {
+ defaultMessage: 'Current Status',
+ }),
+ description: (
+
+ {' '}
+
+
+ ),
+ },
+ {
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.osCurrent', {
+ defaultMessage: 'OS (Current)',
}),
- description: 'TODO',
+ description: alertData.state.host_metadata.host.os.name,
},
{
- title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.os', {
- defaultMessage: 'OS',
+ title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.osOriginal', {
+ defaultMessage: 'OS (At time of alert)',
}),
description: alertData.host.os.name,
},
diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/factory.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/factory.ts
index c8e038869efcd..601f8a6bdc2c1 100644
--- a/x-pack/plugins/endpoint/public/embeddables/resolver/factory.ts
+++ b/x-pack/plugins/endpoint/public/embeddables/resolver/factory.ts
@@ -6,13 +6,13 @@
import { i18n } from '@kbn/i18n';
import {
- EmbeddableFactory,
IContainer,
EmbeddableInput,
+ EmbeddableFactoryDefinition,
} from '../../../../../../src/plugins/embeddable/public';
import { ResolverEmbeddable } from './embeddable';
-export class ResolverEmbeddableFactory extends EmbeddableFactory {
+export class ResolverEmbeddableFactory implements EmbeddableFactoryDefinition {
public readonly type = 'resolver';
public async isEditable() {
diff --git a/x-pack/plugins/endpoint/server/test_data/all_metadata_data.json b/x-pack/plugins/endpoint/server/test_data/all_metadata_data.json
index 3c824185ec083..3c8486aa127ea 100644
--- a/x-pack/plugins/endpoint/server/test_data/all_metadata_data.json
+++ b/x-pack/plugins/endpoint/server/test_data/all_metadata_data.json
@@ -23,6 +23,11 @@
"event" : {
"created" : "2020-01-23T21:56:55.336Z"
},
+ "elastic": {
+ "agent": {
+ "id": "56a75650-3c8a-4e4f-ac17-6dd729c650e2"
+ }
+ },
"endpoint" : {
"policy" : {
"id" : "C2A9093E-E289-4C0A-AA44-8C32A414FA7A"
@@ -73,6 +78,11 @@
"event" : {
"created" : "2020-01-23T21:56:55.336Z"
},
+ "elastic": {
+ "agent": {
+ "id": "56a75650-3c8a-4e4f-ac17-6dd729c650e2"
+ }
+ },
"endpoint" : {
"policy" : {
"id" : "C2A9093E-E289-4C0A-AA44-8C32A414FA7A"
@@ -115,6 +125,11 @@
"event" : {
"created" : "2020-01-23T21:56:55.336Z"
},
+ "elastic": {
+ "agent": {
+ "id": "c2d84d8f-d355-40de-8b54-5d318d4d1312"
+ }
+ },
"endpoint" : {
"policy" : {
"id" : "C2A9093E-E289-4C0A-AA44-8C32A414FA7A"
@@ -165,6 +180,11 @@
"event" : {
"created" : "2020-01-23T21:56:55.336Z"
},
+ "elastic": {
+ "agent": {
+ "id": "c2d84d8f-d355-40de-8b54-5d318d4d1312"
+ }
+ },
"endpoint" : {
"policy" : {
"id" : "C2A9093E-E289-4C0A-AA44-8C32A414FA7A"
diff --git a/x-pack/plugins/event_log/generated/mappings.json b/x-pack/plugins/event_log/generated/mappings.json
index 50e7fdd5a9048..d0e4652c2828e 100644
--- a/x-pack/plugins/event_log/generated/mappings.json
+++ b/x-pack/plugins/event_log/generated/mappings.json
@@ -8,7 +8,7 @@
"ignore_above": 1024,
"type": "keyword",
"meta": {
- "isArray": true
+ "isArray": "true"
}
},
"message": {
diff --git a/x-pack/plugins/event_log/scripts/create_schemas.js b/x-pack/plugins/event_log/scripts/create_schemas.js
index b46f7f295ddc7..2432a27e5c70d 100755
--- a/x-pack/plugins/event_log/scripts/create_schemas.js
+++ b/x-pack/plugins/event_log/scripts/create_schemas.js
@@ -117,7 +117,7 @@ function augmentMappings(mappings, multiValuedProperties) {
const fullProp = replaceDotWithProperties(prop);
const metaPropName = `${fullProp}.meta`;
const meta = lodash.get(mappings.properties, metaPropName) || {};
- meta.isArray = true;
+ meta.isArray = 'true';
lodash.set(mappings.properties, metaPropName, meta);
}
}
@@ -127,7 +127,7 @@ function generateSchemaLines(lineWriter, prop, mappings) {
if (mappings == null) return;
if (StringTypes.has(mappings.type)) {
- if (mappings.meta && mappings.meta.isArray) {
+ if (mappings.meta && mappings.meta.isArray === 'true') {
lineWriter.addLine(`${propKey}: ecsStringMulti(),`);
} else {
lineWriter.addLine(`${propKey}: ecsString(),`);
diff --git a/x-pack/plugins/event_log/server/types.ts b/x-pack/plugins/event_log/server/types.ts
index 4cace59752f36..f606bb2be6c6c 100644
--- a/x-pack/plugins/event_log/server/types.ts
+++ b/x-pack/plugins/event_log/server/types.ts
@@ -13,7 +13,7 @@ import { IEvent } from '../generated/schemas';
export const ConfigSchema = schema.object({
enabled: schema.boolean({ defaultValue: true }),
logEntries: schema.boolean({ defaultValue: false }),
- indexEntries: schema.boolean({ defaultValue: false }),
+ indexEntries: schema.boolean({ defaultValue: true }),
});
export type IEventLogConfig = TypeOf;
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts
index 7e3e1fba9c44a..397a78354f470 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts
@@ -16,7 +16,7 @@ import {
import { IndexManagementHome } from '../../../public/application/sections/home'; // eslint-disable-line @kbn/eslint/no-restricted-paths
import { BASE_PATH } from '../../../common/constants';
import { indexManagementStore } from '../../../public/application/store'; // eslint-disable-line @kbn/eslint/no-restricted-paths
-import { Template } from '../../../common/types';
+import { TemplateDeserialized } from '../../../common';
import { WithAppDependencies, services } from './setup_environment';
const testBedConfig: TestBedConfig = {
@@ -36,10 +36,13 @@ export interface IdxMgmtHomeTestBed extends TestBed {
selectHomeTab: (tab: 'indicesTab' | 'templatesTab') => void;
selectDetailsTab: (tab: 'summary' | 'settings' | 'mappings' | 'aliases') => void;
clickReloadButton: () => void;
- clickTemplateAction: (name: Template['name'], action: 'edit' | 'clone' | 'delete') => void;
+ clickTemplateAction: (
+ name: TemplateDeserialized['name'],
+ action: 'edit' | 'clone' | 'delete'
+ ) => void;
clickTemplateAt: (index: number) => void;
clickCloseDetailsButton: () => void;
- clickActionMenu: (name: Template['name']) => void;
+ clickActionMenu: (name: TemplateDeserialized['name']) => void;
};
}
@@ -78,7 +81,7 @@ export const setup = async (): Promise => {
find('reloadButton').simulate('click');
};
- const clickActionMenu = async (templateName: Template['name']) => {
+ const clickActionMenu = async (templateName: TemplateDeserialized['name']) => {
const { component } = testBed;
// When a table has > 2 actions, EUI displays an overflow menu with an id "-actions"
@@ -87,7 +90,7 @@ export const setup = async (): Promise => {
};
const clickTemplateAction = (
- templateName: Template['name'],
+ templateName: TemplateDeserialized['name'],
action: 'edit' | 'clone' | 'delete'
) => {
const actions = ['edit', 'clone', 'delete'];
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/template_form.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/template_form.helpers.ts
index 9d4eb631a1c40..520b62083e7d3 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/template_form.helpers.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/template_form.helpers.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { TestBed, SetupFunc, UnwrapPromise } from '../../../../../test_utils';
-import { Template } from '../../../common/types';
+import { TemplateDeserialized } from '../../../common';
import { nextTick } from './index';
interface MappingField {
@@ -62,8 +62,8 @@ export const formSetup = async (initTestBed: SetupFunc) => {
indexPatterns,
order,
version,
- }: Partial = {}) => {
- const { form, find, component } = testBed;
+ }: Partial = {}) => {
+ const { form, find, waitFor } = testBed;
if (name) {
form.setInputValue('nameField.input', name);
@@ -88,12 +88,11 @@ export const formSetup = async (initTestBed: SetupFunc) => {
}
clickNextButton();
- await nextTick();
- component.update();
+ await waitFor('stepSettings');
};
const completeStepTwo = async (settings?: string) => {
- const { find, component } = testBed;
+ const { find, component, waitFor } = testBed;
if (settings) {
find('mockCodeEditor').simulate('change', {
@@ -104,42 +103,41 @@ export const formSetup = async (initTestBed: SetupFunc) => {
}
clickNextButton();
- await nextTick();
- component.update();
+ await waitFor('stepMappings');
};
const completeStepThree = async (mappingFields?: MappingField[]) => {
- const { component } = testBed;
+ const { waitFor } = testBed;
if (mappingFields) {
for (const field of mappingFields) {
const { name, type } = field;
await addMappingField(name, type);
}
- } else {
- await nextTick();
}
- await nextTick(50); // hooks updates cycles are tricky, adding some latency is needed
clickNextButton();
- await nextTick(50);
- component.update();
+ await waitFor('stepAliases');
};
- const completeStepFour = async (aliases?: string) => {
- const { find, component } = testBed;
+ const completeStepFour = async (aliases?: string, waitForNextStep = true) => {
+ const { find, component, waitFor } = testBed;
if (aliases) {
find('mockCodeEditor').simulate('change', {
jsonString: aliases,
}); // Using mocked EuiCodeEditor
- await nextTick(50);
+ await nextTick();
component.update();
}
clickNextButton();
- await nextTick(50);
- component.update();
+
+ if (waitForNextStep) {
+ await waitFor('summaryTab');
+ } else {
+ component.update();
+ }
};
const selectSummaryTab = (tab: 'summary' | 'request') => {
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home.test.ts
index 9e8af02b74631..a987535e0c291 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/home.test.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/home.test.ts
@@ -115,11 +115,13 @@ describe('', () => {
const template1 = fixtures.getTemplate({
name: `a${getRandomString()}`,
indexPatterns: ['template1Pattern1*', 'template1Pattern2'],
- settings: {
- index: {
- number_of_shards: '1',
- lifecycle: {
- name: 'my_ilm_policy',
+ template: {
+ settings: {
+ index: {
+ number_of_shards: '1',
+ lifecycle: {
+ name: 'my_ilm_policy',
+ },
},
},
},
@@ -302,7 +304,10 @@ describe('', () => {
const templateId = rows[0].columns[2].value;
- const { name: templateName } = template1;
+ const {
+ name: templateName,
+ _kbnMeta: { formatVersion },
+ } = template1;
await actions.clickTemplateAction(templateName, 'delete');
const modal = document.body.querySelector(
@@ -327,8 +332,11 @@ describe('', () => {
const latestRequest = server.requests[server.requests.length - 1];
- expect(latestRequest.method).toBe('DELETE');
- expect(latestRequest.url).toBe(`${API_BASE_PATH}/templates/${template1.name}`);
+ expect(latestRequest.method).toBe('POST');
+ expect(latestRequest.url).toBe(`${API_BASE_PATH}/delete-templates`);
+ expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual({
+ templates: [{ name: template1.name, formatVersion }],
+ });
});
});
@@ -396,24 +404,26 @@ describe('', () => {
const template = fixtures.getTemplate({
name: `a${getRandomString()}`,
indexPatterns: ['template1Pattern1*', 'template1Pattern2'],
- settings: {
- index: {
- number_of_shards: '1',
- },
- },
- mappings: {
- _source: {
- enabled: false,
+ template: {
+ settings: {
+ index: {
+ number_of_shards: '1',
+ },
},
- properties: {
- created_at: {
- type: 'date',
- format: 'EEE MMM dd HH:mm:ss Z yyyy',
+ mappings: {
+ _source: {
+ enabled: false,
+ },
+ properties: {
+ created_at: {
+ type: 'date',
+ format: 'EEE MMM dd HH:mm:ss Z yyyy',
+ },
},
},
- },
- aliases: {
- alias1: {},
+ aliases: {
+ alias1: {},
+ },
},
});
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/template_clone.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/template_clone.test.tsx
index 5d895c8e98624..17e19bf881dee 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/template_clone.test.tsx
+++ b/x-pack/plugins/index_management/__jest__/client_integration/template_clone.test.tsx
@@ -54,21 +54,17 @@ describe('', () => {
const templateToClone = getTemplate({
name: TEMPLATE_NAME,
indexPatterns: ['indexPattern1'],
- mappings: {
- ...MAPPINGS,
- _meta: {},
- _source: {},
+ template: {
+ mappings: MAPPINGS,
},
});
beforeEach(async () => {
httpRequestsMockHelpers.setLoadTemplateResponse(templateToClone);
- testBed = await setup();
-
await act(async () => {
- await nextTick();
- testBed.component.update();
+ testBed = await setup();
+ await testBed.waitFor('templateForm');
});
});
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/template_create.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/template_create.test.tsx
index 981067c09f8aa..ad8e8c22a87fa 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/template_create.test.tsx
+++ b/x-pack/plugins/index_management/__jest__/client_integration/template_create.test.tsx
@@ -6,6 +6,7 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
+import { DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT } from '../../common';
import { setupEnvironment, pageHelpers, nextTick } from './helpers';
import { TemplateFormTestBed } from './helpers/template_form.helpers';
import {
@@ -71,6 +72,7 @@ describe('', () => {
beforeEach(async () => {
await act(async () => {
testBed = await setup();
+ await testBed.waitFor('templateForm');
});
});
@@ -100,6 +102,7 @@ describe('', () => {
beforeEach(async () => {
await act(async () => {
testBed = await setup();
+ await testBed.waitFor('templateForm');
});
});
@@ -209,7 +212,7 @@ describe('', () => {
await act(async () => {
// Complete step 4 (aliases) with invalid json
- await actions.completeStepFour('{ invalidJsonString ');
+ await actions.completeStepFour('{ invalidJsonString ', false);
});
expect(form.getErrorsMessages()).toContain('Invalid JSON format.');
@@ -221,6 +224,7 @@ describe('', () => {
beforeEach(async () => {
await act(async () => {
testBed = await setup();
+ await testBed.waitFor('templateForm');
const { actions } = testBed;
@@ -275,6 +279,7 @@ describe('', () => {
it('should render a warning message if a wildcard is used as an index pattern', async () => {
await act(async () => {
testBed = await setup();
+ await testBed.waitFor('templateForm');
const { actions } = testBed;
// Complete step 1 (logistics)
@@ -308,6 +313,7 @@ describe('', () => {
await act(async () => {
testBed = await setup();
+ await testBed.waitFor('templateForm');
const { actions } = testBed;
// Complete step 1 (logistics)
@@ -323,7 +329,6 @@ describe('', () => {
await actions.completeStepThree(MAPPING_FIELDS);
// Complete step 4 (aliases)
- await nextTick(100);
await actions.completeStepFour(JSON.stringify(ALIASES));
});
});
@@ -338,29 +343,34 @@ describe('', () => {
const latestRequest = server.requests[server.requests.length - 1];
- const expected = JSON.stringify({
+ const expected = {
isManaged: false,
name: TEMPLATE_NAME,
indexPatterns: DEFAULT_INDEX_PATTERNS,
- settings: SETTINGS,
- mappings: {
- ...MAPPINGS,
- properties: {
- [BOOLEAN_MAPPING_FIELD.name]: {
- type: BOOLEAN_MAPPING_FIELD.type,
- },
- [TEXT_MAPPING_FIELD.name]: {
- type: TEXT_MAPPING_FIELD.type,
- },
- [KEYWORD_MAPPING_FIELD.name]: {
- type: KEYWORD_MAPPING_FIELD.type,
+ template: {
+ settings: SETTINGS,
+ mappings: {
+ ...MAPPINGS,
+ properties: {
+ [BOOLEAN_MAPPING_FIELD.name]: {
+ type: BOOLEAN_MAPPING_FIELD.type,
+ },
+ [TEXT_MAPPING_FIELD.name]: {
+ type: TEXT_MAPPING_FIELD.type,
+ },
+ [KEYWORD_MAPPING_FIELD.name]: {
+ type: KEYWORD_MAPPING_FIELD.type,
+ },
},
},
+ aliases: ALIASES,
},
- aliases: ALIASES,
- });
+ _kbnMeta: {
+ formatVersion: DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT,
+ },
+ };
- expect(JSON.parse(latestRequest.requestBody).body).toEqual(expected);
+ expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual(expected);
});
it('should surface the API errors from the put HTTP request', async () => {
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/template_edit.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/template_edit.test.tsx
index 537b0d8ef4156..5b10ff226022d 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/template_edit.test.tsx
+++ b/x-pack/plugins/index_management/__jest__/client_integration/template_edit.test.tsx
@@ -99,17 +99,17 @@ describe('', () => {
const templateToEdit = fixtures.getTemplate({
name: TEMPLATE_NAME,
indexPatterns: ['indexPattern1'],
- mappings: MAPPING,
+ template: {
+ mappings: MAPPING,
+ },
});
beforeEach(async () => {
httpRequestsMockHelpers.setLoadTemplateResponse(templateToEdit);
- testBed = await setup();
-
await act(async () => {
- await nextTick();
- testBed.component.update();
+ testBed = await setup();
+ await testBed.waitFor('templateForm');
});
});
@@ -128,10 +128,9 @@ describe('', () => {
expect(nameInput.props().disabled).toEqual(true);
});
- // TODO: Flakey test
- describe.skip('form payload', () => {
+ describe('form payload', () => {
beforeEach(async () => {
- const { actions, component, find, form } = testBed;
+ const { actions } = testBed;
await act(async () => {
// Complete step 1 (logistics)
@@ -141,20 +140,32 @@ describe('', () => {
// Step 2 (index settings)
await actions.completeStepTwo(JSON.stringify(SETTINGS));
+ });
+ });
+
+ it('should send the correct payload with changed values', async () => {
+ const { actions, component, find, form } = testBed;
- // Step 3 (mappings)
- // Select the first field to edit
- actions.clickEditButtonAtField(0);
+ await act(async () => {
+ // Make some changes to the mappings (step 3)
+
+ actions.clickEditButtonAtField(0); // Select the first field to edit
await nextTick();
component.update();
- // verify edit field flyout
- expect(find('mappingsEditorFieldEdit').length).toEqual(1);
- // change field name
+ });
+
+ // verify edit field flyout
+ expect(find('mappingsEditorFieldEdit').length).toEqual(1);
+
+ await act(async () => {
+ // change the field name
form.setInputValue('nameParameterInput', UPDATED_MAPPING_TEXT_FIELD_NAME);
+
// Save changes
actions.clickEditFieldUpdateButton();
await nextTick();
component.update();
+
// Proceed to the next step
actions.clickNextButton();
await nextTick(50);
@@ -162,19 +173,13 @@ describe('', () => {
// Step 4 (aliases)
await actions.completeStepFour(JSON.stringify(ALIASES));
- });
- });
- it('should send the correct payload with changed values', async () => {
- const { actions } = testBed;
-
- await act(async () => {
+ // Submit the form
actions.clickSubmitButton();
await nextTick();
});
const latestRequest = server.requests[server.requests.length - 1];
-
const { version, order } = templateToEdit;
const expected = {
@@ -182,27 +187,31 @@ describe('', () => {
version,
order,
indexPatterns: UPDATED_INDEX_PATTERN,
- mappings: {
- ...MAPPING,
- _meta: {},
- _source: {},
- properties: {
- [UPDATED_MAPPING_TEXT_FIELD_NAME]: {
- type: 'text',
- store: false,
- index: true,
- fielddata: false,
- eager_global_ordinals: false,
- index_phrases: false,
- norms: true,
- index_options: 'positions',
+ template: {
+ mappings: {
+ ...MAPPING,
+ properties: {
+ [UPDATED_MAPPING_TEXT_FIELD_NAME]: {
+ type: 'text',
+ store: false,
+ index: true,
+ fielddata: false,
+ eager_global_ordinals: false,
+ index_phrases: false,
+ norms: true,
+ index_options: 'positions',
+ },
},
},
+ settings: SETTINGS,
+ aliases: ALIASES,
},
isManaged: false,
- settings: SETTINGS,
- aliases: ALIASES,
+ _kbnMeta: {
+ formatVersion: templateToEdit._kbnMeta.formatVersion,
+ },
};
+
expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual(expected);
});
});
diff --git a/x-pack/plugins/index_management/common/constants/index.ts b/x-pack/plugins/index_management/common/constants/index.ts
index d1700f0e611c0..966e2e8e64838 100644
--- a/x-pack/plugins/index_management/common/constants/index.ts
+++ b/x-pack/plugins/index_management/common/constants/index.ts
@@ -9,6 +9,7 @@ export { BASE_PATH } from './base_path';
export { API_BASE_PATH } from './api_base_path';
export { INVALID_INDEX_PATTERN_CHARS, INVALID_TEMPLATE_NAME_CHARS } from './invalid_characters';
export * from './index_statuses';
+export { DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT } from './index_templates';
export {
UIM_APP_NAME,
diff --git a/x-pack/plugins/index_management/common/constants/index_templates.ts b/x-pack/plugins/index_management/common/constants/index_templates.ts
new file mode 100644
index 0000000000000..788e96ee895ed
--- /dev/null
+++ b/x-pack/plugins/index_management/common/constants/index_templates.ts
@@ -0,0 +1,12 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+/**
+ * Up until the end of the 8.x release cycle we need to support both
+ * V1 and V2 index template formats. This constant keeps track of whether
+ * we create V1 or V2 index template format in the UI.
+ */
+export const DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT = 1;
diff --git a/x-pack/plugins/index_management/common/index.ts b/x-pack/plugins/index_management/common/index.ts
index 0cc4ba79711ce..459eda7552c85 100644
--- a/x-pack/plugins/index_management/common/index.ts
+++ b/x-pack/plugins/index_management/common/index.ts
@@ -4,4 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { PLUGIN, API_BASE_PATH } from './constants';
+export { PLUGIN, API_BASE_PATH, DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT } from './constants';
+
+export { getTemplateParameter } from './lib';
+
+export * from './types';
diff --git a/x-pack/plugins/index_management/common/lib/index.ts b/x-pack/plugins/index_management/common/lib/index.ts
index 83b22d8d72e92..33f7fbe45182e 100644
--- a/x-pack/plugins/index_management/common/lib/index.ts
+++ b/x-pack/plugins/index_management/common/lib/index.ts
@@ -5,6 +5,8 @@
*/
export {
deserializeTemplateList,
- deserializeTemplate,
- serializeTemplate,
+ deserializeV1Template,
+ serializeV1Template,
} from './template_serialization';
+
+export { getTemplateParameter } from './utils';
diff --git a/x-pack/plugins/index_management/common/lib/template_serialization.ts b/x-pack/plugins/index_management/common/lib/template_serialization.ts
index b7d3410c775d4..33a83d1e9335b 100644
--- a/x-pack/plugins/index_management/common/lib/template_serialization.ts
+++ b/x-pack/plugins/index_management/common/lib/template_serialization.ts
@@ -3,46 +3,25 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import { Template, TemplateEs, TemplateListItem } from '../types';
+import {
+ TemplateDeserialized,
+ TemplateV1Serialized,
+ TemplateV2Serialized,
+ TemplateListItem,
+} from '../types';
const hasEntries = (data: object = {}) => Object.entries(data).length > 0;
-export function deserializeTemplateList(
- indexTemplatesByName: any,
- managedTemplatePrefix?: string
-): TemplateListItem[] {
- const indexTemplateNames: string[] = Object.keys(indexTemplatesByName);
-
- const deserializedTemplates: TemplateListItem[] = indexTemplateNames.map((name: string) => {
- const {
- version,
- order,
- index_patterns: indexPatterns = [],
- settings = {},
- aliases = {},
- mappings = {},
- } = indexTemplatesByName[name];
-
- return {
- name,
- version,
- order,
- indexPatterns: indexPatterns.sort(),
- hasSettings: hasEntries(settings),
- hasAliases: hasEntries(aliases),
- hasMappings: hasEntries(mappings),
- ilmPolicy: settings && settings.index && settings.index.lifecycle,
- isManaged: Boolean(managedTemplatePrefix && name.startsWith(managedTemplatePrefix)),
- };
- });
-
- return deserializedTemplates;
-}
-
-export function serializeTemplate(template: Template): TemplateEs {
- const { name, version, order, indexPatterns, settings, aliases, mappings } = template;
+export function serializeV1Template(template: TemplateDeserialized): TemplateV1Serialized {
+ const {
+ name,
+ version,
+ order,
+ indexPatterns,
+ template: { settings, aliases, mappings } = {} as TemplateDeserialized['template'],
+ } = template;
- const serializedTemplate: TemplateEs = {
+ const serializedTemplate: TemplateV1Serialized = {
name,
version,
order,
@@ -55,31 +34,88 @@ export function serializeTemplate(template: Template): TemplateEs {
return serializedTemplate;
}
-export function deserializeTemplate(
- templateEs: TemplateEs,
+export function serializeV2Template(template: TemplateDeserialized): TemplateV2Serialized {
+ const { aliases, mappings, settings, ...templateV1serialized } = serializeV1Template(template);
+
+ return {
+ ...templateV1serialized,
+ template: {
+ aliases,
+ mappings,
+ settings,
+ },
+ priority: template.priority,
+ composed_of: template.composedOf,
+ };
+}
+
+export function deserializeV2Template(
+ templateEs: TemplateV2Serialized,
managedTemplatePrefix?: string
-): Template {
+): TemplateDeserialized {
const {
name,
version,
order,
index_patterns: indexPatterns,
- settings,
- aliases,
- mappings,
+ template,
+ priority,
+ composed_of: composedOf,
} = templateEs;
+ const { settings } = template;
- const deserializedTemplate: Template = {
+ const deserializedTemplate: TemplateDeserialized = {
name,
version,
order,
indexPatterns: indexPatterns.sort(),
- settings,
- aliases,
- mappings,
+ template,
ilmPolicy: settings && settings.index && settings.index.lifecycle,
isManaged: Boolean(managedTemplatePrefix && name.startsWith(managedTemplatePrefix)),
+ priority,
+ composedOf,
+ _kbnMeta: {
+ formatVersion: 2,
+ },
};
return deserializedTemplate;
}
+
+export function deserializeV1Template(
+ templateEs: TemplateV1Serialized,
+ managedTemplatePrefix?: string
+): TemplateDeserialized {
+ const { settings, aliases, mappings, ...rest } = templateEs;
+
+ const deserializedTemplateV2 = deserializeV2Template(
+ { ...rest, template: { aliases, settings, mappings } },
+ managedTemplatePrefix
+ );
+
+ return {
+ ...deserializedTemplateV2,
+ _kbnMeta: {
+ formatVersion: 1,
+ },
+ };
+}
+
+export function deserializeTemplateList(
+ indexTemplatesByName: { [key: string]: Omit },
+ managedTemplatePrefix?: string
+): TemplateListItem[] {
+ return Object.entries(indexTemplatesByName).map(([name, templateSerialized]) => {
+ const {
+ template: { mappings, settings, aliases },
+ ...deserializedTemplate
+ } = deserializeV1Template({ name, ...templateSerialized }, managedTemplatePrefix);
+
+ return {
+ ...deserializedTemplate,
+ hasSettings: hasEntries(settings),
+ hasAliases: hasEntries(aliases),
+ hasMappings: hasEntries(mappings),
+ };
+ });
+}
diff --git a/x-pack/plugins/index_management/common/lib/utils.test.ts b/x-pack/plugins/index_management/common/lib/utils.test.ts
new file mode 100644
index 0000000000000..221d1b009cede
--- /dev/null
+++ b/x-pack/plugins/index_management/common/lib/utils.test.ts
@@ -0,0 +1,35 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import { TemplateV1Serialized, TemplateV2Serialized } from '../types';
+import { getTemplateVersion } from './utils';
+
+describe('utils', () => {
+ describe('getTemplateVersion', () => {
+ test('should detect v1 template', () => {
+ const template = {
+ name: 'my_template',
+ index_patterns: ['logs*'],
+ mappings: {
+ properties: {},
+ },
+ };
+ expect(getTemplateVersion(template as TemplateV1Serialized)).toBe(1);
+ });
+
+ test('should detect v2 template', () => {
+ const template = {
+ name: 'my_template',
+ index_patterns: ['logs*'],
+ template: {
+ mappings: {
+ properties: {},
+ },
+ },
+ };
+ expect(getTemplateVersion(template as TemplateV2Serialized)).toBe(2);
+ });
+ });
+});
diff --git a/x-pack/plugins/index_management/common/lib/utils.ts b/x-pack/plugins/index_management/common/lib/utils.ts
new file mode 100644
index 0000000000000..eee35dc1ab467
--- /dev/null
+++ b/x-pack/plugins/index_management/common/lib/utils.ts
@@ -0,0 +1,29 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { TemplateDeserialized, TemplateV1Serialized, TemplateV2Serialized } from '../types';
+
+/**
+ * Helper to get the format version of an index template.
+ * v1 will be supported up until 9.x but marked as deprecated from 7.8
+ * v2 will be supported from 7.8
+ */
+export const getTemplateVersion = (
+ template: TemplateDeserialized | TemplateV1Serialized | TemplateV2Serialized
+): 1 | 2 => {
+ return {}.hasOwnProperty.call(template, 'template') ? 2 : 1;
+};
+
+export const getTemplateParameter = (
+ template: TemplateV1Serialized | TemplateV2Serialized,
+ setting: 'aliases' | 'settings' | 'mappings'
+) => {
+ const formatVersion = getTemplateVersion(template);
+
+ return formatVersion === 1
+ ? (template as TemplateV1Serialized)[setting]
+ : (template as TemplateV2Serialized).template[setting];
+};
diff --git a/x-pack/legacy/plugins/uptime/common/constants/index_names.ts b/x-pack/plugins/index_management/common/types/aliases.ts
similarity index 79%
rename from x-pack/legacy/plugins/uptime/common/constants/index_names.ts
rename to x-pack/plugins/index_management/common/types/aliases.ts
index 9f33d280a1268..76aae8585c065 100644
--- a/x-pack/legacy/plugins/uptime/common/constants/index_names.ts
+++ b/x-pack/plugins/index_management/common/types/aliases.ts
@@ -4,6 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export const INDEX_NAMES = {
- HEARTBEAT: 'heartbeat-8*',
-};
+export interface Aliases {
+ [key: string]: any;
+}
diff --git a/x-pack/plugins/index_management/common/types/index.ts b/x-pack/plugins/index_management/common/types/index.ts
index 922cb63b70da3..b467f020978a5 100644
--- a/x-pack/plugins/index_management/common/types/index.ts
+++ b/x-pack/plugins/index_management/common/types/index.ts
@@ -4,4 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
+export * from './aliases';
+
+export * from './indices';
+
+export * from './mappings';
+
export * from './templates';
diff --git a/x-pack/plugins/index_management/common/types/indices.ts b/x-pack/plugins/index_management/common/types/indices.ts
new file mode 100644
index 0000000000000..ecf5ba21fe60c
--- /dev/null
+++ b/x-pack/plugins/index_management/common/types/indices.ts
@@ -0,0 +1,43 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+interface IndexModule {
+ number_of_shards: number | string;
+ codec: string;
+ routing_partition_size: number;
+ load_fixed_bitset_filters_eagerly: boolean;
+ shard: {
+ check_on_startup: boolean | 'checksum';
+ };
+ number_of_replicas: number;
+ auto_expand_replicas: false | string;
+ lifecycle: LifecycleModule;
+}
+
+interface AnalysisModule {
+ analyzer: {
+ [key: string]: {
+ type: string;
+ tokenizer: string;
+ char_filter?: string[];
+ filter?: string[];
+ position_increment_gap?: number;
+ };
+ };
+}
+
+interface LifecycleModule {
+ name: string;
+ rollover_alias?: string;
+ parse_origination_date?: boolean;
+ origination_date?: number;
+}
+
+export interface IndexSettings {
+ index?: Partial;
+ analysis?: AnalysisModule;
+ [key: string]: any;
+}
diff --git a/x-pack/plugins/index_management/common/types/mappings.ts b/x-pack/plugins/index_management/common/types/mappings.ts
new file mode 100644
index 0000000000000..0bd3e38ed07a5
--- /dev/null
+++ b/x-pack/plugins/index_management/common/types/mappings.ts
@@ -0,0 +1,11 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+// TODO: Move mappings type from Mappings editor here
+
+export interface Mappings {
+ [key: string]: any;
+}
diff --git a/x-pack/plugins/index_management/common/types/templates.ts b/x-pack/plugins/index_management/common/types/templates.ts
index e31c10a775156..c37088982f207 100644
--- a/x-pack/plugins/index_management/common/types/templates.ts
+++ b/x-pack/plugins/index_management/common/types/templates.ts
@@ -4,6 +4,39 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { IndexSettings } from './indices';
+import { Aliases } from './aliases';
+import { Mappings } from './mappings';
+
+// Template serialized (from Elasticsearch)
+interface TemplateBaseSerialized {
+ name: string;
+ index_patterns: string[];
+ version?: number;
+ order?: number;
+}
+
+export interface TemplateV1Serialized extends TemplateBaseSerialized {
+ settings?: IndexSettings;
+ aliases?: Aliases;
+ mappings?: Mappings;
+}
+
+export interface TemplateV2Serialized extends TemplateBaseSerialized {
+ template: {
+ settings?: IndexSettings;
+ aliases?: Aliases;
+ mappings?: Mappings;
+ };
+ priority?: number;
+ composed_of?: string[];
+}
+
+/**
+ * Interface for the template list in our UI table
+ * we don't include the mappings, settings and aliases
+ * to reduce the payload size sent back to the client.
+ */
export interface TemplateListItem {
name: string;
indexPatterns: string[];
@@ -16,37 +49,35 @@ export interface TemplateListItem {
name: string;
};
isManaged: boolean;
+ _kbnMeta: {
+ formatVersion: IndexTemplateFormatVersion;
+ };
}
-export interface Template {
+
+/**
+ * TemplateDeserialized falls back to index template V2 format
+ * The UI will only be dealing with this interface, conversion from and to V1 format
+ * is done server side.
+ */
+export interface TemplateDeserialized {
name: string;
indexPatterns: string[];
+ isManaged: boolean;
+ template: {
+ settings?: IndexSettings;
+ aliases?: Aliases;
+ mappings?: Mappings;
+ };
+ _kbnMeta: {
+ formatVersion: IndexTemplateFormatVersion;
+ };
version?: number;
+ priority?: number;
order?: number;
- settings?: object;
- aliases?: object;
- mappings?: object;
ilmPolicy?: {
name: string;
};
- isManaged: boolean;
+ composedOf?: string[];
}
-export interface TemplateEs {
- name: string;
- index_patterns: string[];
- version?: number;
- order?: number;
- settings?: {
- [key: string]: any;
- index?: {
- [key: string]: any;
- lifecycle?: {
- name: string;
- };
- };
- };
- aliases?: {
- [key: string]: any;
- };
- mappings?: object;
-}
+export type IndexTemplateFormatVersion = 1 | 2;
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/load_mappings/load_mappings_provider.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/load_mappings/load_mappings_provider.test.tsx
index b66a42fc50ef4..da4b8e6f6eef2 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/load_mappings/load_mappings_provider.test.tsx
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/load_mappings/load_mappings_provider.test.tsx
@@ -38,22 +38,20 @@ const setup = (props: any) =>
defaultProps: props,
})();
-const openModalWithJsonContent = ({ find, component }: TestBed) => async (json: any) => {
- find('load-json-button').simulate('click');
- component.update();
-
+const openModalWithJsonContent = ({ find, waitFor }: TestBed) => async (json: any) => {
// Set the mappings to load
- // @ts-ignore
await act(async () => {
+ find('load-json-button').simulate('click');
+ await waitFor('mockCodeEditor');
+
find('mockCodeEditor').simulate('change', {
jsonString: JSON.stringify(json),
});
- await nextTick(300); // There is a debounce in the JsonEditor that we need to wait for
+ await nextTick(500); // There is a debounce in the JsonEditor that we need to wait for
});
};
-// FLAKY: https://github.com/elastic/kibana/issues/59030
-describe.skip('', () => {
+describe('', () => {
test('it should forward valid mapping definition', async () => {
const mappingsToLoad = {
properties: {
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_state.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_state.tsx
index 247bd183baddf..a9d26b953b96e 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_state.tsx
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_state.tsx
@@ -111,13 +111,14 @@ export const MappingsState = React.memo(({ children, onUpdate, defaultValue }: P
let nextState = state;
if (
+ state.fieldForm &&
state.documentFields.status === 'creatingField' &&
isValid &&
!bypassFieldFormValidation
) {
// If the form field is valid and we are creating a new field that has some data
// we automatically add the field to our state.
- const fieldFormData = state.fieldForm!.data.format() as Field;
+ const fieldFormData = state.fieldForm.data.format() as Field;
if (Object.keys(fieldFormData).length !== 0) {
nextState = addFieldToState(fieldFormData, state);
dispatch({ type: 'field.add', value: fieldFormData });
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/reducer.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/reducer.ts
index 1e6733b1632d7..2f30363c1ff58 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/reducer.ts
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/reducer.ts
@@ -33,11 +33,21 @@ export interface MappingsConfiguration {
}
export interface MappingsTemplates {
- dynamic_templates: Template[];
+ dynamic_templates: DynamicTemplate[];
}
-interface Template {
- [key: string]: any;
+interface DynamicTemplate {
+ [key: string]: {
+ mapping: {
+ [key: string]: any;
+ };
+ match_mapping_type?: string;
+ match?: string;
+ unmatch?: string;
+ match_pattern?: string;
+ path_match?: string;
+ path_unmatch?: string;
+ };
}
export interface MappingsFields {
diff --git a/x-pack/plugins/index_management/public/application/components/template_delete_modal.tsx b/x-pack/plugins/index_management/public/application/components/template_delete_modal.tsx
index 4e36643dbe117..b80e51d8d139f 100644
--- a/x-pack/plugins/index_management/public/application/components/template_delete_modal.tsx
+++ b/x-pack/plugins/index_management/public/application/components/template_delete_modal.tsx
@@ -4,28 +4,27 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import React, { Fragment, useState } from 'react';
import { EuiConfirmModal, EuiOverlayMask, EuiCallOut, EuiCheckbox, EuiBadge } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
-import React, { Fragment, useState } from 'react';
+
+import { IndexTemplateFormatVersion } from '../../../common';
import { deleteTemplates } from '../services/api';
import { notificationService } from '../services/notification';
-import { Template } from '../../../common/types';
export const TemplateDeleteModal = ({
templatesToDelete,
callback,
}: {
- templatesToDelete: Array;
+ templatesToDelete: Array<{ name: string; formatVersion: IndexTemplateFormatVersion }>;
callback: (data?: { hasDeletedTemplates: boolean }) => void;
}) => {
const [isDeleteConfirmed, setIsDeleteConfirmed] = useState(false);
const numTemplatesToDelete = templatesToDelete.length;
- const hasSystemTemplate = Boolean(
- templatesToDelete.find(templateName => templateName.startsWith('.'))
- );
+ const hasSystemTemplate = Boolean(templatesToDelete.find(({ name }) => name.startsWith('.')));
const handleDeleteTemplates = () => {
deleteTemplates(templatesToDelete).then(({ data: { templatesDeleted, errors }, error }) => {
@@ -38,7 +37,7 @@ export const TemplateDeleteModal = ({
'xpack.idxMgmt.deleteTemplatesModal.successDeleteSingleNotificationMessageText',
{
defaultMessage: "Deleted template '{templateName}'",
- values: { templateName: templatesToDelete[0] },
+ values: { templateName: templatesToDelete[0].name },
}
)
: i18n.translate(
@@ -120,10 +119,10 @@ export const TemplateDeleteModal = ({
- {templatesToDelete.map(template => (
- -
- {template}
- {template.startsWith('.') ? (
+ {templatesToDelete.map(({ name }) => (
+
-
+ {name}
+ {name.startsWith('.') ? (
{' '}
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_aliases.tsx b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_aliases.tsx
index 8628b6d8b8d74..50a32787c7a04 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_aliases.tsx
+++ b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_aliases.tsx
@@ -29,7 +29,7 @@ export const StepAliases: React.FunctionComponent = ({
}) => {
const { content, setContent, error } = useJsonStep({
prop: 'aliases',
- defaultValue: template.aliases,
+ defaultValue: template?.template.aliases,
setDataGetter,
onStepValidityChange,
});
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings.tsx b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings.tsx
index d51d512429ea4..cf9b57dcbcb14 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings.tsx
+++ b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings.tsx
@@ -15,7 +15,7 @@ import {
EuiText,
} from '@elastic/eui';
import { documentationService } from '../../../services/documentation';
-import { StepProps } from '../types';
+import { StepProps, DataGetterFunc } from '../types';
import { MappingsEditor, OnUpdateHandler, LoadMappingsFromJsonButton } from '../../mappings_editor';
export const StepMappings: React.FunctionComponent = ({
@@ -23,16 +23,23 @@ export const StepMappings: React.FunctionComponent = ({
setDataGetter,
onStepValidityChange,
}) => {
- const [mappings, setMappings] = useState(template.mappings);
+ const [mappings, setMappings] = useState(template?.template.mappings);
const onMappingsEditorUpdate = useCallback(
({ isValid, getData, validate }) => {
onStepValidityChange(isValid);
- setDataGetter(async () => {
+
+ const dataGetterFunc: DataGetterFunc = async () => {
const isMappingsValid = isValid === undefined ? await validate() : isValid;
const data = getData(isMappingsValid);
- return Promise.resolve({ isValid: isMappingsValid, data: { mappings: data } });
- });
+ return {
+ isValid: isMappingsValid,
+ data: { mappings: data },
+ path: 'template',
+ };
+ };
+
+ setDataGetter(dataGetterFunc);
},
[setDataGetter, onStepValidityChange]
);
@@ -96,7 +103,7 @@ export const StepMappings: React.FunctionComponent = ({
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_review.tsx b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_review.tsx
index 09da43b83c3c5..0cb2ae9fbcd92 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_review.tsx
+++ b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_review.tsx
@@ -22,8 +22,11 @@ import {
import { FormattedMessage } from '@kbn/i18n/react';
import { serializers } from '../../../../shared_imports';
-import { serializeTemplate } from '../../../../../common/lib/template_serialization';
-import { Template } from '../../../../../common/types';
+import {
+ serializeV1Template,
+ serializeV2Template,
+} from '../../../../../common/lib/template_serialization';
+import { TemplateDeserialized, getTemplateParameter } from '../../../../../common';
import { StepProps } from '../types';
const { stripEmptyFields } = serializers;
@@ -52,16 +55,25 @@ const getDescriptionText = (data: any) => {
};
export const StepReview: React.FunctionComponent = ({ template, updateCurrentStep }) => {
- const { name, indexPatterns, version, order } = template;
+ const {
+ name,
+ indexPatterns,
+ version,
+ order,
+ _kbnMeta: { formatVersion },
+ } = template!;
+
+ const serializedTemplate =
+ formatVersion === 1
+ ? serializeV1Template(stripEmptyFields(template!) as TemplateDeserialized)
+ : serializeV2Template(stripEmptyFields(template!) as TemplateDeserialized);
- const serializedTemplate = serializeTemplate(stripEmptyFields(template) as Template);
// Name not included in ES request body
delete serializedTemplate.name;
- const {
- mappings: serializedMappings,
- settings: serializedSettings,
- aliases: serializedAliases,
- } = serializedTemplate;
+
+ const serializedMappings = getTemplateParameter(serializedTemplate, 'mappings');
+ const serializedSettings = getTemplateParameter(serializedTemplate, 'settings');
+ const serializedAliases = getTemplateParameter(serializedTemplate, 'aliases');
const numIndexPatterns = indexPatterns!.length;
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_settings.tsx b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_settings.tsx
index cead652c9f6fc..7c1ee6388a618 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_settings.tsx
+++ b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_settings.tsx
@@ -29,7 +29,7 @@ export const StepSettings: React.FunctionComponent = ({
}) => {
const { content, setContent, error } = useJsonStep({
prop: 'settings',
- defaultValue: template.settings,
+ defaultValue: template?.template.settings,
setDataGetter,
onStepValidityChange,
});
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/use_json_step.ts b/x-pack/plugins/index_management/public/application/components/template_form/steps/use_json_step.ts
index fbe479ea0cf23..25dbe784db3a1 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/steps/use_json_step.ts
+++ b/x-pack/plugins/index_management/public/application/components/template_form/steps/use_json_step.ts
@@ -8,7 +8,7 @@ import { useEffect, useState, useCallback } from 'react';
import { i18n } from '@kbn/i18n';
import { isJSON } from '../../../../shared_imports';
-import { StepProps } from '../types';
+import { StepProps, DataGetterFunc } from '../types';
interface Parameters {
prop: 'settings' | 'mappings' | 'aliases';
@@ -44,11 +44,13 @@ export const useJsonStep = ({
return isValid;
}, [content]);
- const dataGetter = useCallback(() => {
+ const dataGetter = useCallback(() => {
const isValid = validateContent();
const value = isValid && content.trim() !== '' ? JSON.parse(content) : {};
- const data = { [prop]: value };
- return Promise.resolve({ isValid, data });
+ // If no key has been added to the JSON object, we strip it out so an empty object is not sent in the request
+ const data = { [prop]: Object.keys(value).length > 0 ? value : undefined };
+
+ return Promise.resolve({ isValid, data, path: 'template' });
}, [content, validateContent, prop]);
useEffect(() => {
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx b/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx
index 58be9b2c63365..f6193bc71aa91 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx
+++ b/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx
@@ -15,7 +15,7 @@ import {
} from '@elastic/eui';
import { serializers } from '../../../shared_imports';
-import { Template } from '../../../../common/types';
+import { TemplateDeserialized, DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT } from '../../../../common';
import { TemplateSteps } from './template_steps';
import { StepAliases, StepLogistics, StepMappings, StepSettings, StepReview } from './steps';
import { StepProps, DataGetterFunc } from './types';
@@ -24,11 +24,11 @@ import { SectionError } from '../section_error';
const { stripEmptyFields } = serializers;
interface Props {
- onSave: (template: Template) => void;
+ onSave: (template: TemplateDeserialized) => void;
clearSaveError: () => void;
isSaving: boolean;
saveError: any;
- defaultValue?: Template;
+ defaultValue?: TemplateDeserialized;
isEditing?: boolean;
}
@@ -47,7 +47,15 @@ const stepComponentMap: { [key: number]: React.FunctionComponent } =
};
export const TemplateForm: React.FunctionComponent = ({
- defaultValue = { isManaged: false },
+ defaultValue = {
+ name: '',
+ indexPatterns: [],
+ template: {},
+ isManaged: false,
+ _kbnMeta: {
+ formatVersion: DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT,
+ },
+ },
onSave,
isSaving,
saveError,
@@ -63,7 +71,7 @@ export const TemplateForm: React.FunctionComponent = ({
5: defaultValidation,
});
- const template = useRef>(defaultValue);
+ const template = useRef(defaultValue);
const stepsDataGetters = useRef>({});
const lastStep = Object.keys(stepComponentMap).length;
@@ -91,17 +99,31 @@ export const TemplateForm: React.FunctionComponent = ({
);
const validateAndGetDataFromCurrentStep = async () => {
- const validateAndGetData = stepsDataGetters.current[currentStep];
+ const validateAndGetStepData = stepsDataGetters.current[currentStep];
- if (!validateAndGetData) {
+ if (!validateAndGetStepData) {
throw new Error(`No data getter has been set for step "${currentStep}"`);
}
- const { isValid, data } = await validateAndGetData();
+ const { isValid, data, path } = await validateAndGetStepData();
if (isValid) {
- // Update the template object
- template.current = { ...template.current, ...data };
+ // Update the template object with the current step data
+ if (path) {
+ // We only update a "slice" of the template
+ const sliceToUpdate = template.current[path as keyof TemplateDeserialized];
+
+ if (sliceToUpdate === null || typeof sliceToUpdate !== 'object') {
+ return { isValid, data };
+ }
+
+ template.current = {
+ ...template.current,
+ [path]: { ...sliceToUpdate, ...data },
+ };
+ } else {
+ template.current = { ...template.current, ...data };
+ }
}
return { isValid, data };
@@ -111,9 +133,9 @@ export const TemplateForm: React.FunctionComponent = ({
// All steps needs validation, except for the last step
const shouldValidate = currentStep !== lastStep;
- let isValid = isStepValid;
if (shouldValidate) {
- isValid = isValid === false ? false : (await validateAndGetDataFromCurrentStep()).isValid;
+ const isValid =
+ isStepValid === false ? false : (await validateAndGetDataFromCurrentStep()).isValid;
// If step is invalid do not let user proceed
if (!isValid) {
@@ -222,7 +244,10 @@ export const TemplateForm: React.FunctionComponent = ({
fill
color="secondary"
iconType="check"
- onClick={onSave.bind(null, stripEmptyFields(template.current) as Template)}
+ onClick={onSave.bind(
+ null,
+ stripEmptyFields(template.current!) as TemplateDeserialized
+ )}
data-test-subj="submitButton"
isLoading={isSaving}
>
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/types.ts b/x-pack/plugins/index_management/public/application/components/template_form/types.ts
index 9385f0c9f738b..5db53e91ed261 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/types.ts
+++ b/x-pack/plugins/index_management/public/application/components/template_form/types.ts
@@ -4,14 +4,21 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { Template } from '../../../../common/types';
+import { TemplateDeserialized } from '../../../../common';
export interface StepProps {
- template: Partial;
+ template?: TemplateDeserialized;
setDataGetter: (dataGetter: DataGetterFunc) => void;
updateCurrentStep: (step: number) => void;
onStepValidityChange: (isValid: boolean | undefined) => void;
isEditing?: boolean;
}
-export type DataGetterFunc = () => Promise<{ isValid: boolean; data: any }>;
+export type DataGetterFunc = () => Promise<{
+ /** Is the step data valid or not */
+ isValid: boolean;
+ /** The current step data (can be invalid) */
+ data: any;
+ /** Optional "slice" of the complete object the step is updating */
+ path?: string;
+}>;
diff --git a/x-pack/plugins/index_management/public/application/lib/index_templates.ts b/x-pack/plugins/index_management/public/application/lib/index_templates.ts
new file mode 100644
index 0000000000000..7129e536287c1
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/lib/index_templates.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import { parse } from 'query-string';
+import { Location } from 'history';
+
+export const getFormatVersionFromQueryparams = (location: Location): 1 | 2 | undefined => {
+ const { v: version } = parse(location.search.substring(1));
+
+ if (!Boolean(version) || typeof version !== 'string') {
+ return undefined;
+ }
+
+ return +version as 1 | 2;
+};
diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_aliases.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_aliases.tsx
index 421119bd8df96..fa7d734ad0d2b 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_aliases.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_aliases.tsx
@@ -7,14 +7,16 @@
import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiCodeBlock, EuiCallOut } from '@elastic/eui';
-import { Template } from '../../../../../../../common/types';
+import { TemplateDeserialized } from '../../../../../../../common';
interface Props {
- templateDetails: Template;
+ templateDetails: TemplateDeserialized;
}
export const TabAliases: React.FunctionComponent = ({ templateDetails }) => {
- const { aliases } = templateDetails;
+ const {
+ template: { aliases },
+ } = templateDetails;
if (aliases && Object.keys(aliases).length) {
return (
diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_mappings.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_mappings.tsx
index 83f2e67fb12c5..6e0257c6b377b 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_mappings.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_mappings.tsx
@@ -7,14 +7,16 @@
import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiCodeBlock, EuiCallOut } from '@elastic/eui';
-import { Template } from '../../../../../../../common/types';
+import { TemplateDeserialized } from '../../../../../../../common';
interface Props {
- templateDetails: Template;
+ templateDetails: TemplateDeserialized;
}
export const TabMappings: React.FunctionComponent = ({ templateDetails }) => {
- const { mappings } = templateDetails;
+ const {
+ template: { mappings },
+ } = templateDetails;
if (mappings && Object.keys(mappings).length) {
return (
diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_settings.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_settings.tsx
index 8b2a431bee65a..8f75c2cb77801 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_settings.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_settings.tsx
@@ -7,14 +7,16 @@
import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiCodeBlock, EuiCallOut } from '@elastic/eui';
-import { Template } from '../../../../../../../common/types';
+import { TemplateDeserialized } from '../../../../../../../common';
interface Props {
- templateDetails: Template;
+ templateDetails: TemplateDeserialized;
}
export const TabSettings: React.FunctionComponent = ({ templateDetails }) => {
- const { settings } = templateDetails;
+ const {
+ template: { settings },
+ } = templateDetails;
if (settings && Object.keys(settings).length) {
return (
diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx
index 99f5db54b4ba2..9ce29ab746a2f 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx
@@ -14,11 +14,11 @@ import {
EuiText,
EuiTitle,
} from '@elastic/eui';
-import { Template } from '../../../../../../../common/types';
+import { TemplateDeserialized } from '../../../../../../../common';
import { getILMPolicyPath } from '../../../../../services/navigation';
interface Props {
- templateDetails: Template;
+ templateDetails: TemplateDeserialized;
}
const NoneDescriptionText = () => (
@@ -35,6 +35,7 @@ export const TabSummary: React.FunctionComponent = ({ templateDetails })
return (
+ {/* Index patterns */}
= ({ templateDetails })
indexPatterns.toString()
)}
+
+ {/* // ILM Policy */}
= ({ templateDetails })
)}
+
+ {/* // Order */}
= ({ templateDetails })
{order || order === 0 ? order : }
+
+ {/* // Version */}
void;
- editTemplate: (templateName: Template['name']) => void;
- cloneTemplate: (templateName: Template['name']) => void;
+ editTemplate: (name: string, formatVersion: IndexTemplateFormatVersion) => void;
+ cloneTemplate: (name: string, formatVersion: IndexTemplateFormatVersion) => void;
reload: () => Promise;
}
@@ -79,7 +79,7 @@ const TABS = [
];
const tabToComponentMap: {
- [key: string]: React.FunctionComponent<{ templateDetails: Template }>;
+ [key: string]: React.FunctionComponent<{ templateDetails: TemplateDeserialized }>;
} = {
[SUMMARY_TAB_ID]: TabSummary,
[SETTINGS_TAB_ID]: TabSettings,
@@ -95,7 +95,7 @@ const tabToUiMetricMap: { [key: string]: string } = {
};
export const TemplateDetails: React.FunctionComponent = ({
- templateName,
+ template: { name: templateName, formatVersion },
onClose,
editTemplate,
cloneTemplate,
@@ -103,10 +103,14 @@ export const TemplateDetails: React.FunctionComponent = ({
}) => {
const { uiMetricService } = useServices();
const decodedTemplateName = decodePath(templateName);
- const { error, data: templateDetails, isLoading } = useLoadIndexTemplate(decodedTemplateName);
- // TS complains if we use destructuring here. Fixed in 3.6.0 (https://github.com/microsoft/TypeScript/pull/31711).
- const isManaged = templateDetails ? templateDetails.isManaged : undefined;
- const [templateToDelete, setTemplateToDelete] = useState>([]);
+ const { error, data: templateDetails, isLoading } = useLoadIndexTemplate(
+ decodedTemplateName,
+ formatVersion
+ );
+ const isManaged = templateDetails?.isManaged;
+ const [templateToDelete, setTemplateToDelete] = useState<
+ Array<{ name: string; formatVersion: IndexTemplateFormatVersion }>
+ >([]);
const [activeTab, setActiveTab] = useState(SUMMARY_TAB_ID);
const [isPopoverOpen, setIsPopOverOpen] = useState(false);
@@ -275,7 +279,7 @@ export const TemplateDetails: React.FunctionComponent = ({
defaultMessage: 'Edit',
}),
icon: 'pencil',
- onClick: () => editTemplate(decodedTemplateName),
+ onClick: () => editTemplate(templateName, formatVersion),
disabled: isManaged,
},
{
@@ -283,7 +287,7 @@ export const TemplateDetails: React.FunctionComponent = ({
defaultMessage: 'Clone',
}),
icon: 'copy',
- onClick: () => cloneTemplate(decodedTemplateName),
+ onClick: () => cloneTemplate(templateName, formatVersion),
},
{
name: i18n.translate(
@@ -293,7 +297,8 @@ export const TemplateDetails: React.FunctionComponent = ({
}
),
icon: 'trash',
- onClick: () => setTemplateToDelete([decodedTemplateName]),
+ onClick: () =>
+ setTemplateToDelete([{ name: decodedTemplateName, formatVersion }]),
disabled: isManaged,
},
],
diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx
index ffdb224f16271..1e84202639ee8 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx
@@ -16,31 +16,35 @@ import {
EuiFlexItem,
EuiFlexGroup,
} from '@elastic/eui';
+
+import { UIM_TEMPLATE_LIST_LOAD } from '../../../../../common/constants';
+import { IndexTemplateFormatVersion } from '../../../../../common';
import { SectionError, SectionLoading, Error } from '../../../components';
-import { TemplateTable } from './template_table';
import { useLoadIndexTemplates } from '../../../services/api';
-import { Template } from '../../../../../common/types';
import { useServices } from '../../../app_context';
import {
getTemplateEditLink,
getTemplateListLink,
getTemplateCloneLink,
} from '../../../services/routing';
-import { UIM_TEMPLATE_LIST_LOAD } from '../../../../../common/constants';
+import { getFormatVersionFromQueryparams } from '../../../lib/index_templates';
+import { TemplateTable } from './template_table';
import { TemplateDetails } from './template_details';
interface MatchParams {
- templateName?: Template['name'];
+ templateName?: string;
}
export const TemplateList: React.FunctionComponent> = ({
match: {
params: { templateName },
},
+ location,
history,
}) => {
const { uiMetricService } = useServices();
const { error, isLoading, data: templates, sendRequest: reload } = useLoadIndexTemplates();
+ const queryParamsFormatVersion = getFormatVersionFromQueryparams(location);
let content;
@@ -48,8 +52,7 @@ export const TemplateList: React.FunctionComponent
- templates ? templates.filter((template: Template) => !template.name.startsWith('.')) : [],
+ () => (templates ? templates.filter(template => !template.name.startsWith('.')) : []),
[templates]
);
@@ -57,12 +60,12 @@ export const TemplateList: React.FunctionComponent {
- history.push(getTemplateEditLink(name));
+ const editTemplate = (name: string, formatVersion: IndexTemplateFormatVersion) => {
+ history.push(getTemplateEditLink(name, formatVersion));
};
- const cloneTemplate = (name: Template['name']) => {
- history.push(getTemplateCloneLink(name));
+ const cloneTemplate = (name: string, formatVersion: IndexTemplateFormatVersion) => {
+ history.push(getTemplateCloneLink(name, formatVersion));
};
// Track component loaded
@@ -149,9 +152,12 @@ export const TemplateList: React.FunctionComponent
{content}
- {templateName && (
+ {templateName && queryParamsFormatVersion !== undefined && (
Promise;
- editTemplate: (name: Template['name']) => void;
- cloneTemplate: (name: Template['name']) => void;
+ editTemplate: (name: string, formatVersion: IndexTemplateFormatVersion) => void;
+ cloneTemplate: (name: string, formatVersion: IndexTemplateFormatVersion) => void;
}
export const TemplateTable: React.FunctionComponent = ({
@@ -30,7 +30,9 @@ export const TemplateTable: React.FunctionComponent = ({
}) => {
const { uiMetricService } = useServices();
const [selection, setSelection] = useState([]);
- const [templatesToDelete, setTemplatesToDelete] = useState>([]);
+ const [templatesToDelete, setTemplatesToDelete] = useState<
+ Array<{ name: string; formatVersion: IndexTemplateFormatVersion }>
+ >([]);
const columns: Array> = [
{
@@ -40,11 +42,11 @@ export const TemplateTable: React.FunctionComponent = ({
}),
truncateText: true,
sortable: true,
- render: (name: TemplateListItem['name']) => {
+ render: (name: TemplateListItem['name'], item: TemplateListItem) => {
return (
/* eslint-disable-next-line @elastic/eui/href-or-on-click */
uiMetricService.trackMetric('click', UIM_TEMPLATE_SHOW_DETAILS_CLICK)}
>
@@ -133,10 +135,10 @@ export const TemplateTable: React.FunctionComponent = ({
}),
icon: 'pencil',
type: 'icon',
- onClick: ({ name }: Template) => {
- editTemplate(name);
+ onClick: ({ name, _kbnMeta: { formatVersion } }: TemplateListItem) => {
+ editTemplate(name, formatVersion);
},
- enabled: ({ isManaged }: Template) => !isManaged,
+ enabled: ({ isManaged }: TemplateListItem) => !isManaged,
},
{
type: 'icon',
@@ -147,8 +149,8 @@ export const TemplateTable: React.FunctionComponent = ({
defaultMessage: 'Clone this template',
}),
icon: 'copy',
- onClick: ({ name }: Template) => {
- cloneTemplate(name);
+ onClick: ({ name, _kbnMeta: { formatVersion } }: TemplateListItem) => {
+ cloneTemplate(name, formatVersion);
},
},
{
@@ -161,11 +163,11 @@ export const TemplateTable: React.FunctionComponent = ({
icon: 'trash',
color: 'danger',
type: 'icon',
- onClick: ({ name }: Template) => {
- setTemplatesToDelete([name]);
+ onClick: ({ name, _kbnMeta: { formatVersion } }: TemplateListItem) => {
+ setTemplatesToDelete([{ name, formatVersion }]);
},
isPrimary: true,
- enabled: ({ isManaged }: Template) => !isManaged,
+ enabled: ({ isManaged }: TemplateListItem) => !isManaged,
},
],
},
@@ -185,7 +187,7 @@ export const TemplateTable: React.FunctionComponent = ({
const selectionConfig = {
onSelectionChange: setSelection,
- selectable: ({ isManaged }: Template) => !isManaged,
+ selectable: ({ isManaged }: TemplateListItem) => !isManaged,
selectableMessage: (selectable: boolean) => {
if (!selectable) {
return i18n.translate('xpack.idxMgmt.templateList.table.deleteManagedTemplateTooltip', {
@@ -205,7 +207,12 @@ export const TemplateTable: React.FunctionComponent = ({
- setTemplatesToDelete(selection.map((selected: TemplateListItem) => selected.name))
+ setTemplatesToDelete(
+ selection.map(({ name, _kbnMeta: { formatVersion } }: TemplateListItem) => ({
+ name,
+ formatVersion,
+ }))
+ )
}
color="danger"
>
diff --git a/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx b/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx
index cf6ca3c065777..b69e441feb176 100644
--- a/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx
@@ -7,11 +7,13 @@ import React, { useEffect, useState } from 'react';
import { RouteComponentProps } from 'react-router-dom';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiPageBody, EuiPageContent, EuiSpacer, EuiTitle } from '@elastic/eui';
+
+import { TemplateDeserialized, DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT } from '../../../../common';
import { TemplateForm, SectionLoading, SectionError, Error } from '../../components';
import { breadcrumbService } from '../../services/breadcrumbs';
import { decodePath, getTemplateDetailsLink } from '../../services/routing';
-import { Template } from '../../../../common/types';
import { saveTemplate, useLoadIndexTemplate } from '../../services/api';
+import { getFormatVersionFromQueryparams } from '../../lib/index_templates';
interface MatchParams {
name: string;
@@ -21,17 +23,21 @@ export const TemplateClone: React.FunctionComponent {
const decodedTemplateName = decodePath(name);
+ const formatVersion =
+ getFormatVersionFromQueryparams(location) ?? DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT;
+
const [isSaving, setIsSaving] = useState(false);
const [saveError, setSaveError] = useState(null);
-
const { error: templateToCloneError, data: templateToClone, isLoading } = useLoadIndexTemplate(
- decodedTemplateName
+ decodedTemplateName,
+ formatVersion
);
- const onSave = async (template: Template) => {
+ const onSave = async (template: TemplateDeserialized) => {
setIsSaving(true);
setSaveError(null);
@@ -46,7 +52,7 @@ export const TemplateClone: React.FunctionComponent {
@@ -85,7 +91,7 @@ export const TemplateClone: React.FunctionComponent = ({ h
const [isSaving, setIsSaving] = useState(false);
const [saveError, setSaveError] = useState(null);
- const onSave = async (template: Template) => {
+ const onSave = async (template: TemplateDeserialized) => {
const { name } = template;
setIsSaving(true);
@@ -32,7 +33,7 @@ export const TemplateCreate: React.FunctionComponent = ({ h
return;
}
- history.push(getTemplateDetailsLink(name));
+ history.push(getTemplateDetailsLink(name, template._kbnMeta.formatVersion));
};
const clearSaveError = () => {
diff --git a/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx b/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx
index 1e9d5f294de34..9ad26d0af802d 100644
--- a/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx
@@ -7,11 +7,13 @@ import React, { useEffect, useState, Fragment } from 'react';
import { RouteComponentProps } from 'react-router-dom';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiPageBody, EuiPageContent, EuiTitle, EuiSpacer, EuiCallOut } from '@elastic/eui';
+
+import { TemplateDeserialized, DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT } from '../../../../common';
import { breadcrumbService } from '../../services/breadcrumbs';
import { useLoadIndexTemplate, updateTemplate } from '../../services/api';
import { decodePath, getTemplateDetailsLink } from '../../services/routing';
import { SectionLoading, SectionError, TemplateForm, Error } from '../../components';
-import { Template } from '../../../../common/types';
+import { getFormatVersionFromQueryparams } from '../../lib/index_templates';
interface MatchParams {
name: string;
@@ -21,19 +23,26 @@ export const TemplateEdit: React.FunctionComponent {
const decodedTemplateName = decodePath(name);
+ const formatVersion =
+ getFormatVersionFromQueryparams(location) ?? DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT;
+
const [isSaving, setIsSaving] = useState(false);
const [saveError, setSaveError] = useState(null);
- const { error, data: template, isLoading } = useLoadIndexTemplate(decodedTemplateName);
+ const { error, data: template, isLoading } = useLoadIndexTemplate(
+ decodedTemplateName,
+ formatVersion
+ );
useEffect(() => {
breadcrumbService.setBreadcrumbs('templateEdit');
}, []);
- const onSave = async (updatedTemplate: Template) => {
+ const onSave = async (updatedTemplate: TemplateDeserialized) => {
setIsSaving(true);
setSaveError(null);
@@ -46,7 +55,7 @@ export const TemplateEdit: React.FunctionComponent {
diff --git a/x-pack/plugins/index_management/public/application/services/api.ts b/x-pack/plugins/index_management/public/application/services/api.ts
index 88887f40972e4..c09c4f44c4e3a 100644
--- a/x-pack/plugins/index_management/public/application/services/api.ts
+++ b/x-pack/plugins/index_management/public/application/services/api.ts
@@ -37,7 +37,11 @@ import { TAB_SETTINGS, TAB_MAPPING, TAB_STATS } from '../constants';
import { useRequest, sendRequest } from './use_request';
import { httpService } from './http';
import { UiMetricService } from './ui_metric';
-import { Template } from '../../../common/types';
+import {
+ TemplateDeserialized,
+ TemplateListItem,
+ IndexTemplateFormatVersion,
+} from '../../../common';
import { IndexMgmtMetricsType } from '../../types';
// Temporary hack to provide the uiMetricService instance to this file.
@@ -201,33 +205,42 @@ export async function loadIndexData(type: string, indexName: string) {
}
export function useLoadIndexTemplates() {
- return useRequest({
+ return useRequest({
path: `${API_BASE_PATH}/templates`,
method: 'get',
});
}
-export async function deleteTemplates(names: Array) {
+export async function deleteTemplates(
+ templates: Array<{ name: string; formatVersion: IndexTemplateFormatVersion }>
+) {
const result = sendRequest({
- path: `${API_BASE_PATH}/templates/${names.map(name => encodeURIComponent(name)).join(',')}`,
- method: 'delete',
+ path: `${API_BASE_PATH}/delete-templates`,
+ method: 'post',
+ body: { templates },
});
- const uimActionType = names.length > 1 ? UIM_TEMPLATE_DELETE_MANY : UIM_TEMPLATE_DELETE;
+ const uimActionType = templates.length > 1 ? UIM_TEMPLATE_DELETE_MANY : UIM_TEMPLATE_DELETE;
uiMetricService.trackMetric('count', uimActionType);
return result;
}
-export function useLoadIndexTemplate(name: Template['name']) {
- return useRequest({
+export function useLoadIndexTemplate(
+ name: TemplateDeserialized['name'],
+ formatVersion: IndexTemplateFormatVersion
+) {
+ return useRequest({
path: `${API_BASE_PATH}/templates/${encodeURIComponent(name)}`,
method: 'get',
+ query: {
+ v: formatVersion,
+ },
});
}
-export async function saveTemplate(template: Template, isClone?: boolean) {
+export async function saveTemplate(template: TemplateDeserialized, isClone?: boolean) {
const result = await sendRequest({
path: `${API_BASE_PATH}/templates`,
method: 'put',
@@ -241,7 +254,7 @@ export async function saveTemplate(template: Template, isClone?: boolean) {
return result;
}
-export async function updateTemplate(template: Template) {
+export async function updateTemplate(template: TemplateDeserialized) {
const { name } = template;
const result = await sendRequest({
path: `${API_BASE_PATH}/templates/${encodeURIComponent(name)}`,
diff --git a/x-pack/plugins/index_management/public/application/services/routing.ts b/x-pack/plugins/index_management/public/application/services/routing.ts
index aceadab18b1ff..a6d8f67751cd1 100644
--- a/x-pack/plugins/index_management/public/application/services/routing.ts
+++ b/x-pack/plugins/index_management/public/application/services/routing.ts
@@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { BASE_PATH } from '../../../common/constants';
+import { IndexTemplateFormatVersion } from '../../../common';
export const getTemplateListLink = () => {
return `${BASE_PATH}templates`;
@@ -11,18 +12,28 @@ export const getTemplateListLink = () => {
// Need to add some additonal encoding/decoding logic to work with React Router
// For background, see: https://github.com/ReactTraining/history/issues/505
-export const getTemplateDetailsLink = (name: string, withHash = false) => {
- const baseUrl = `${BASE_PATH}templates/${encodeURIComponent(encodeURIComponent(name))}`;
+export const getTemplateDetailsLink = (
+ name: string,
+ formatVersion: IndexTemplateFormatVersion,
+ withHash = false
+) => {
+ const baseUrl = `${BASE_PATH}templates/${encodeURIComponent(
+ encodeURIComponent(name)
+ )}?v=${formatVersion}`;
const url = withHash ? `#${baseUrl}` : baseUrl;
return encodeURI(url);
};
-export const getTemplateEditLink = (name: string) => {
- return encodeURI(`${BASE_PATH}edit_template/${encodeURIComponent(encodeURIComponent(name))}`);
+export const getTemplateEditLink = (name: string, formatVersion: IndexTemplateFormatVersion) => {
+ return encodeURI(
+ `${BASE_PATH}edit_template/${encodeURIComponent(encodeURIComponent(name))}?v=${formatVersion}`
+ );
};
-export const getTemplateCloneLink = (name: string) => {
- return encodeURI(`${BASE_PATH}clone_template/${encodeURIComponent(encodeURIComponent(name))}`);
+export const getTemplateCloneLink = (name: string, formatVersion: IndexTemplateFormatVersion) => {
+ return encodeURI(
+ `${BASE_PATH}clone_template/${encodeURIComponent(encodeURIComponent(name))}?v=${formatVersion}`
+ );
};
export const decodePath = (pathname: string): string => {
diff --git a/x-pack/plugins/index_management/public/application/services/use_request.ts b/x-pack/plugins/index_management/public/application/services/use_request.ts
index 8ede3d196911c..b87a6db20f0bf 100644
--- a/x-pack/plugins/index_management/public/application/services/use_request.ts
+++ b/x-pack/plugins/index_management/public/application/services/use_request.ts
@@ -18,6 +18,6 @@ export const sendRequest = (config: SendRequestConfig): Promise {
- return _useRequest(httpService.httpClient, config);
+export const useRequest = (config: UseRequestConfig) => {
+ return _useRequest(httpService.httpClient, config);
};
diff --git a/x-pack/plugins/index_management/server/routes/api/templates/register_create_route.ts b/x-pack/plugins/index_management/server/routes/api/templates/register_create_route.ts
index 817893976767f..22715e457a832 100644
--- a/x-pack/plugins/index_management/server/routes/api/templates/register_create_route.ts
+++ b/x-pack/plugins/index_management/server/routes/api/templates/register_create_route.ts
@@ -6,8 +6,8 @@
import { i18n } from '@kbn/i18n';
-import { Template, TemplateEs } from '../../../../common/types';
-import { serializeTemplate } from '../../../../common/lib';
+import { TemplateDeserialized } from '../../../../common';
+import { serializeV1Template } from '../../../../common/lib';
import { RouteDependencies } from '../../../types';
import { addBasePath } from '../index';
import { templateSchema } from './validate_schemas';
@@ -19,8 +19,18 @@ export function registerCreateRoute({ router, license, lib }: RouteDependencies)
{ path: addBasePath('/templates'), validate: { body: bodySchema } },
license.guardApiRoute(async (ctx, req, res) => {
const { callAsCurrentUser } = ctx.core.elasticsearch.dataClient;
- const template = req.body as Template;
- const serializedTemplate = serializeTemplate(template) as TemplateEs;
+ const template = req.body as TemplateDeserialized;
+ const {
+ _kbnMeta: { formatVersion },
+ } = template;
+
+ if (formatVersion !== 1) {
+ return res.badRequest({ body: 'Only index template version 1 can be created.' });
+ }
+
+ // For now we format to V1 index templates.
+ // When the V2 API is ready we will only create V2 template format.
+ const serializedTemplate = serializeV1Template(template);
const {
name,
diff --git a/x-pack/plugins/index_management/server/routes/api/templates/register_delete_route.ts b/x-pack/plugins/index_management/server/routes/api/templates/register_delete_route.ts
index c9f1995204d8c..4c8fdd0c2f1c7 100644
--- a/x-pack/plugins/index_management/server/routes/api/templates/register_delete_route.ts
+++ b/x-pack/plugins/index_management/server/routes/api/templates/register_delete_route.ts
@@ -4,35 +4,47 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { schema } from '@kbn/config-schema';
+import { schema, TypeOf } from '@kbn/config-schema';
import { RouteDependencies } from '../../../types';
import { addBasePath } from '../index';
import { wrapEsError } from '../../helpers';
-import { Template } from '../../../../common/types';
+import { TemplateDeserialized } from '../../../../common';
-const paramsSchema = schema.object({
- names: schema.string(),
+const bodySchema = schema.object({
+ templates: schema.arrayOf(
+ schema.object({
+ name: schema.string(),
+ formatVersion: schema.oneOf([schema.literal(1), schema.literal(2)]),
+ })
+ ),
});
export function registerDeleteRoute({ router, license }: RouteDependencies) {
- router.delete(
- { path: addBasePath('/templates/{names}'), validate: { params: paramsSchema } },
+ router.post(
+ {
+ path: addBasePath('/delete-templates'),
+ validate: { body: bodySchema },
+ },
license.guardApiRoute(async (ctx, req, res) => {
- const { names } = req.params as typeof paramsSchema.type;
- const templateNames = names.split(',');
- const response: { templatesDeleted: Array; errors: any[] } = {
+ const { templates } = req.body as TypeOf;
+ const response: { templatesDeleted: Array; errors: any[] } = {
templatesDeleted: [],
errors: [],
};
await Promise.all(
- templateNames.map(async name => {
+ templates.map(async ({ name, formatVersion }) => {
try {
+ if (formatVersion !== 1) {
+ return res.badRequest({ body: 'Only index template version 1 can be deleted.' });
+ }
+
await ctx.core.elasticsearch.dataClient.callAsCurrentUser('indices.deleteTemplate', {
name,
});
+
return response.templatesDeleted.push(name);
} catch (e) {
return response.errors.push({
diff --git a/x-pack/plugins/index_management/server/routes/api/templates/register_get_routes.ts b/x-pack/plugins/index_management/server/routes/api/templates/register_get_routes.ts
index d6776d774d3bf..c0915c72a4b91 100644
--- a/x-pack/plugins/index_management/server/routes/api/templates/register_get_routes.ts
+++ b/x-pack/plugins/index_management/server/routes/api/templates/register_get_routes.ts
@@ -3,9 +3,9 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import { schema } from '@kbn/config-schema';
+import { schema, TypeOf } from '@kbn/config-schema';
-import { deserializeTemplate, deserializeTemplateList } from '../../../../common/lib';
+import { deserializeV1Template, deserializeTemplateList } from '../../../../common/lib';
import { getManagedTemplatePrefix } from '../../../lib/get_managed_templates';
import { RouteDependencies } from '../../../types';
import { addBasePath } from '../index';
@@ -18,8 +18,9 @@ export function registerGetAllRoute({ router, license }: RouteDependencies) {
const managedTemplatePrefix = await getManagedTemplatePrefix(callAsCurrentUser);
const indexTemplatesByName = await callAsCurrentUser('indices.getTemplate');
+ const body = deserializeTemplateList(indexTemplatesByName, managedTemplatePrefix);
- return res.ok({ body: deserializeTemplateList(indexTemplatesByName, managedTemplatePrefix) });
+ return res.ok({ body });
})
);
}
@@ -28,20 +29,34 @@ const paramsSchema = schema.object({
name: schema.string(),
});
+// Require the template format version (V1 or V2) to be provided as Query param
+const querySchema = schema.object({
+ v: schema.oneOf([schema.literal('1'), schema.literal('2')]),
+});
+
export function registerGetOneRoute({ router, license, lib }: RouteDependencies) {
router.get(
- { path: addBasePath('/templates/{name}'), validate: { params: paramsSchema } },
+ {
+ path: addBasePath('/templates/{name}'),
+ validate: { params: paramsSchema, query: querySchema },
+ },
license.guardApiRoute(async (ctx, req, res) => {
const { name } = req.params as typeof paramsSchema.type;
const { callAsCurrentUser } = ctx.core.elasticsearch.dataClient;
+ const { v: version } = req.query as TypeOf;
+
+ if (version !== '1') {
+ return res.badRequest({ body: 'Only index template version 1 can be fetched.' });
+ }
+
try {
const managedTemplatePrefix = await getManagedTemplatePrefix(callAsCurrentUser);
const indexTemplateByName = await callAsCurrentUser('indices.getTemplate', { name });
if (indexTemplateByName[name]) {
return res.ok({
- body: deserializeTemplate(
+ body: deserializeV1Template(
{ ...indexTemplateByName[name], name },
managedTemplatePrefix
),
diff --git a/x-pack/plugins/index_management/server/routes/api/templates/register_update_route.ts b/x-pack/plugins/index_management/server/routes/api/templates/register_update_route.ts
index e7f541fa67f8a..2df72bec9d252 100644
--- a/x-pack/plugins/index_management/server/routes/api/templates/register_update_route.ts
+++ b/x-pack/plugins/index_management/server/routes/api/templates/register_update_route.ts
@@ -5,8 +5,8 @@
*/
import { schema } from '@kbn/config-schema';
-import { Template, TemplateEs } from '../../../../common/types';
-import { serializeTemplate } from '../../../../common/lib';
+import { TemplateDeserialized } from '../../../../common';
+import { serializeV1Template } from '../../../../common/lib';
import { RouteDependencies } from '../../../types';
import { addBasePath } from '../index';
import { templateSchema } from './validate_schemas';
@@ -25,8 +25,16 @@ export function registerUpdateRoute({ router, license, lib }: RouteDependencies)
license.guardApiRoute(async (ctx, req, res) => {
const { callAsCurrentUser } = ctx.core.elasticsearch.dataClient;
const { name } = req.params as typeof paramsSchema.type;
- const template = req.body as Template;
- const serializedTemplate = serializeTemplate(template) as TemplateEs;
+ const template = req.body as TemplateDeserialized;
+ const {
+ _kbnMeta: { formatVersion },
+ } = template;
+
+ if (formatVersion !== 1) {
+ return res.badRequest({ body: 'Only index template version 1 can be edited.' });
+ }
+
+ const serializedTemplate = serializeV1Template(template);
const { order, index_patterns, version, settings, mappings, aliases } = serializedTemplate;
diff --git a/x-pack/plugins/index_management/server/routes/api/templates/validate_schemas.ts b/x-pack/plugins/index_management/server/routes/api/templates/validate_schemas.ts
index 8bf2774ac38b3..491a686f81177 100644
--- a/x-pack/plugins/index_management/server/routes/api/templates/validate_schemas.ts
+++ b/x-pack/plugins/index_management/server/routes/api/templates/validate_schemas.ts
@@ -11,9 +11,13 @@ export const templateSchema = schema.object({
indexPatterns: schema.arrayOf(schema.string()),
version: schema.maybe(schema.number()),
order: schema.maybe(schema.number()),
- settings: schema.maybe(schema.object({}, { unknowns: 'allow' })),
- aliases: schema.maybe(schema.object({}, { unknowns: 'allow' })),
- mappings: schema.maybe(schema.object({}, { unknowns: 'allow' })),
+ template: schema.maybe(
+ schema.object({
+ settings: schema.maybe(schema.object({}, { unknowns: 'allow' })),
+ aliases: schema.maybe(schema.object({}, { unknowns: 'allow' })),
+ mappings: schema.maybe(schema.object({}, { unknowns: 'allow' })),
+ })
+ ),
ilmPolicy: schema.maybe(
schema.object({
name: schema.maybe(schema.string()),
@@ -21,4 +25,7 @@ export const templateSchema = schema.object({
})
),
isManaged: schema.maybe(schema.boolean()),
+ _kbnMeta: schema.object({
+ formatVersion: schema.oneOf([schema.literal(1), schema.literal(2)]),
+ }),
});
diff --git a/x-pack/plugins/index_management/test/fixtures/template.ts b/x-pack/plugins/index_management/test/fixtures/template.ts
index 19a6e0c257647..055c32d5cd5e4 100644
--- a/x-pack/plugins/index_management/test/fixtures/template.ts
+++ b/x-pack/plugins/index_management/test/fixtures/template.ts
@@ -5,24 +5,32 @@
*/
import { getRandomString, getRandomNumber } from '../../../../test_utils';
-import { Template } from '../../common/types';
+import { TemplateDeserialized, DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT } from '../../common';
export const getTemplate = ({
name = getRandomString(),
version = getRandomNumber(),
order = getRandomNumber(),
indexPatterns = [],
- settings,
- aliases,
- mappings,
+ template: { settings, aliases, mappings } = {},
isManaged = false,
-}: Partial = {}): Template => ({
+ templateFormatVersion = DEFAULT_INDEX_TEMPLATE_VERSION_FORMAT,
+}: Partial<
+ TemplateDeserialized & {
+ templateFormatVersion?: 1 | 2;
+ }
+> = {}): TemplateDeserialized => ({
name,
version,
order,
indexPatterns,
- settings,
- aliases,
- mappings,
+ template: {
+ aliases,
+ mappings,
+ settings,
+ },
isManaged,
+ _kbnMeta: {
+ formatVersion: templateFormatVersion,
+ },
});
diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/metric_threshold_alert_type.ts b/x-pack/plugins/infra/public/components/alerting/metrics/metric_threshold_alert_type.ts
index 60c4e013d79ac..04179e34222c5 100644
--- a/x-pack/plugins/infra/public/components/alerting/metrics/metric_threshold_alert_type.ts
+++ b/x-pack/plugins/infra/public/components/alerting/metrics/metric_threshold_alert_type.ts
@@ -15,7 +15,7 @@ export function getAlertType(): AlertTypeModel {
return {
id: METRIC_THRESHOLD_ALERT_TYPE_ID,
name: i18n.translate('xpack.infra.metrics.alertFlyout.alertName', {
- defaultMessage: 'Metric Threshold',
+ defaultMessage: 'Metric threshold',
}),
iconClass: 'bell',
alertParamsExpression: Expressions,
diff --git a/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts
index ca5b4cc1e4b83..8ddd3935bcc33 100644
--- a/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts
+++ b/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts
@@ -21,9 +21,6 @@ export interface InfraServerPluginDeps {
spaces: SpacesPluginSetup;
usageCollection: UsageCollectionSetup;
metrics: VisTypeTimeseriesSetup;
- indexPatterns: {
- indexPatternsServiceFactory: any;
- };
features: FeaturesPluginSetup;
apm: APMPluginContract;
alerting: AlertingPluginContract;
diff --git a/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts
index e2ff93ce356e6..b73acd6703054 100644
--- a/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts
+++ b/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts
@@ -7,7 +7,6 @@
/* eslint-disable @typescript-eslint/array-type */
import { GraphQLSchema } from 'graphql';
-import { Legacy } from 'kibana';
import { runHttpQuery } from 'apollo-server-core';
import { schema, TypeOf } from '@kbn/config-schema';
import {
@@ -217,9 +216,7 @@ export class KibanaFramework {
});
}
- public getIndexPatternsService(
- requestContext: RequestHandlerContext
- ): Legacy.IndexPatternsService {
+ public getIndexPatternsService(requestContext: RequestHandlerContext): IndexPatternsFetcher {
return new IndexPatternsFetcher((...rest: Parameters) => {
rest[1] = rest[1] || {};
rest[1].allowNoIndices = true;
diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts
index 57f9124db923c..2ab3a3322661d 100644
--- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts
@@ -83,7 +83,7 @@ export async function registerMetricThresholdAlertType(alertingPlugin: PluginSet
alertingPlugin.registerType({
id: METRIC_THRESHOLD_ALERT_TYPE_ID,
- name: 'Metric Alert - Threshold',
+ name: 'Metric threshold',
validate: {
params: schema.object({
criteria: schema.arrayOf(schema.oneOf([countCriterion, nonCountCriterion])),
diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/package_icon.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/package_icon.tsx
index c5a0e600b7d50..8ba597a0d377e 100644
--- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/package_icon.tsx
+++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/package_icon.tsx
@@ -59,7 +59,7 @@ const usePackageIcon = (packageName: string, version?: string, icons?: Package['
}
// Use API to see if package has icons defined
- if (!icons && version !== undefined) {
+ if (!icons && version) {
fromPackageInfo(pkgKey)
.catch(() => undefined) // ignore API errors
.then(fromInput);
diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_local_search.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_local_search.tsx
index 26f1ef6a80271..c81dc15f569fa 100644
--- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_local_search.tsx
+++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_local_search.tsx
@@ -15,8 +15,6 @@ export function useLocalSearch(packageList: PackageList) {
const localSearchRef = useRef(null);
useEffect(() => {
- if (!packageList.length) return;
-
const localSearch = new LocalSearch(searchIdField);
fieldsToSearch.forEach(field => localSearch.addIndex(field));
localSearch.addDocuments(packageList);
diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts
index 30a3350ad754e..bd4406ef5ce63 100644
--- a/x-pack/plugins/maps/common/constants.ts
+++ b/x-pack/plugins/maps/common/constants.ts
@@ -62,6 +62,9 @@ export const ES_GEO_GRID = 'ES_GEO_GRID';
export const ES_SEARCH = 'ES_SEARCH';
export const ES_PEW_PEW = 'ES_PEW_PEW';
export const EMS_XYZ = 'EMS_XYZ'; // identifies a custom TMS source. Name is a little unfortunate.
+export const WMS = 'WMS';
+export const KIBANA_TILEMAP = 'KIBANA_TILEMAP';
+export const REGIONMAP_FILE = 'REGIONMAP_FILE';
export enum FIELD_ORIGIN {
SOURCE = 'source',
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 b2a4c6b85a856..798b5f335dda2 100644
--- a/x-pack/plugins/maps/common/descriptor_types/map_descriptor.ts
+++ b/x-pack/plugins/maps/common/descriptor_types/map_descriptor.ts
@@ -33,16 +33,8 @@ export type MapCenterAndZoom = MapCenter & {
zoom: number;
};
-// TODO replace with map_descriptors.MapExtent. Both define the same thing but with different casing
-type MapBounds = {
- min_lon: number;
- max_lon: number;
- min_lat: number;
- max_lat: number;
-};
-
export type Goto = {
- bounds?: MapBounds;
+ bounds?: MapExtent;
center?: MapCenterAndZoom;
};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts
index 34f1d04264900..44bfc0c5a472c 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts
@@ -138,6 +138,8 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => {
const id = await newIndexPattern.create();
+ await mlContext.indexPatterns.clearCache();
+
// id returns false if there's a duplicate index pattern.
if (id === false) {
addRequestMessage({
@@ -248,6 +250,7 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => {
};
const openModal = async () => {
+ await mlContext.indexPatterns.clearCache();
resetForm();
await prepareFormValidation();
dispatch({ type: ACTION.OPEN_MODAL });
diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js
index 94627b688b03a..0a58153e374df 100644
--- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js
+++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js
@@ -610,6 +610,8 @@ async function createKibanaIndexPattern(
const id = await emptyPattern.create();
+ await indexPatterns.clearCache();
+
// check if there's a default index pattern, if not,
// set the newly created one as the default index pattern.
if (!kibanaConfig.get('defaultIndex')) {
diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/components/connection_status/connection_status.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/components/connection_status/connection_status.js
index f032636af0bc3..e71320f51049f 100644
--- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/components/connection_status/connection_status.js
+++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/components/connection_status/connection_status.js
@@ -44,7 +44,7 @@ export function ConnectionStatus({ isConnected, mode }) {
{icon}
- {message}
+ {message}
diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/detail_panel/detail_panel.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/detail_panel/detail_panel.js
index 4006422d3df50..94c5fd6ce7c3f 100644
--- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/detail_panel/detail_panel.js
+++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/detail_panel/detail_panel.js
@@ -230,7 +230,9 @@ export class DetailPanel extends Component {
{seeds.map(seed => (
- {seed}
+
+ {seed}
+
))}
@@ -296,6 +298,7 @@ export class DetailPanel extends Component {
proxySocketConnections,
connectedSocketsCount,
mode,
+ serverName,
}) {
return (
@@ -398,6 +401,25 @@ export class DetailPanel extends Component {
+
+
+
+
+
+
+
+
+
+
+
+
+ {serverName ? serverName : '-'}
+
+
+
);
}
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx
index 49be2e67ce552..5dcaece28bdde 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx
@@ -185,6 +185,8 @@ export const StepCreateForm: FC = React.memo(
const id = await newIndexPattern.create();
+ await indexPatterns.clearCache();
+
// id returns false if there's a duplicate index pattern.
if (id === false) {
toastNotifications.addDanger(
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index aea6e2ab00094..1b4810e8fdf0a 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -7137,12 +7137,10 @@
"xpack.maps.source.esGeoGrid.geofieldLabel": "地理空間フィールド",
"xpack.maps.source.esGeoGrid.geofieldPlaceholder": "ジオフィールドを選択",
"xpack.maps.source.esGeoGrid.gridRectangleDropdownOption": "グリッド四角",
- "xpack.maps.source.esGeoGrid.heatmapDropdownOption": "ヒートマップ",
"xpack.maps.source.esGeoGrid.indexPatternLabel": "インデックスパターン",
"xpack.maps.source.esGeoGrid.indexPatternPlaceholder": "インデックスパターンを選択",
"xpack.maps.source.esGeoGrid.pointsDropdownOption": "点",
"xpack.maps.source.esGeoGrid.showAsLabel": "表示形式",
- "xpack.maps.source.esGeoGrid.showAsPlaceholder": "1 つのオプションを選択",
"xpack.maps.source.esGrid.coarseDropdownOption": "粗い",
"xpack.maps.source.esGrid.fineDropdownOption": "細かい",
"xpack.maps.source.esGrid.finestDropdownOption": "最も細かい",
@@ -7151,9 +7149,6 @@
"xpack.maps.source.esGrid.metricsLabel": "メトリック",
"xpack.maps.source.esGrid.noIndexPatternErrorMessage": "インデックスパターン {id} が見つかりません",
"xpack.maps.source.esGrid.resolutionParamErrorMessage": "グリッド解像度パラメーターが認識されません: {resolution}",
- "xpack.maps.source.esGrid.showasFieldLabel": "表示形式",
- "xpack.maps.source.esGridDescription": "それぞれのグリッド付きセルのメトリックでグリッドにグループ分けされた地理空間データです。",
- "xpack.maps.source.esGridTitle": "グリッド集約",
"xpack.maps.source.esSearch.convertToGeoJsonErrorMsg": "検索への応答を geoJson 機能コレクションに変換できません。エラー: {errorMsg}",
"xpack.maps.source.esSearch.extentFilterLabel": "マップの表示範囲でデータを動的にフィルタリング",
"xpack.maps.source.esSearch.geofieldLabel": "地理空間フィールド",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index dfc5ef065732e..5b3baa3552dbe 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -7137,12 +7137,10 @@
"xpack.maps.source.esGeoGrid.geofieldLabel": "地理空间字段",
"xpack.maps.source.esGeoGrid.geofieldPlaceholder": "选择地理字段",
"xpack.maps.source.esGeoGrid.gridRectangleDropdownOption": "网格矩形",
- "xpack.maps.source.esGeoGrid.heatmapDropdownOption": "热图",
"xpack.maps.source.esGeoGrid.indexPatternLabel": "索引模式",
"xpack.maps.source.esGeoGrid.indexPatternPlaceholder": "选择索引模式",
"xpack.maps.source.esGeoGrid.pointsDropdownOption": "磅",
"xpack.maps.source.esGeoGrid.showAsLabel": "显示为",
- "xpack.maps.source.esGeoGrid.showAsPlaceholder": "选择单个选项",
"xpack.maps.source.esGrid.coarseDropdownOption": "粗糙",
"xpack.maps.source.esGrid.fineDropdownOption": "精致",
"xpack.maps.source.esGrid.finestDropdownOption": "最精致化",
@@ -7151,9 +7149,6 @@
"xpack.maps.source.esGrid.metricsLabel": "指标",
"xpack.maps.source.esGrid.noIndexPatternErrorMessage": "找不到索引模式 {id}",
"xpack.maps.source.esGrid.resolutionParamErrorMessage": "无法识别网格分辨率参数:{resolution}",
- "xpack.maps.source.esGrid.showasFieldLabel": "显示为",
- "xpack.maps.source.esGridDescription": "地理空间数据在网格中进行分组,每个网格单元格都具有指标",
- "xpack.maps.source.esGridTitle": "网格聚合",
"xpack.maps.source.esSearch.convertToGeoJsonErrorMsg": "无法将搜索响应转换成 geoJson 功能集合,错误:{errorMsg}",
"xpack.maps.source.esSearch.extentFilterLabel": "在可见地图区域中动态筛留数据",
"xpack.maps.source.esSearch.geofieldLabel": "地理空间字段",
diff --git a/x-pack/plugins/triggers_actions_ui/README.md b/x-pack/plugins/triggers_actions_ui/README.md
index 3b6ca4f9da7cc..40a43f20d44f5 100644
--- a/x-pack/plugins/triggers_actions_ui/README.md
+++ b/x-pack/plugins/triggers_actions_ui/README.md
@@ -67,7 +67,7 @@ AlertTypeModel:
export function getAlertType(): AlertTypeModel {
return {
id: '.index-threshold',
- name: 'Index Threshold',
+ name: 'Index threshold',
iconClass: 'alert',
alertParamsExpression: IndexThresholdAlertTypeExpression,
validate: validateAlertType,
diff --git a/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts b/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts
index 9c99da63c3952..08a3bc75fa8bd 100644
--- a/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts
+++ b/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts
@@ -315,7 +315,7 @@ describe('status check alert', () => {
it('contains the expected static fields like id, name, etc.', () => {
expect(alert.id).toBe('xpack.uptime.alerts.monitorStatus');
- expect(alert.name).toBe('Uptime Monitor Status');
+ expect(alert.name).toBe('Uptime monitor status');
expect(alert.defaultActionGroupId).toBe('xpack.uptime.alerts.actionGroups.monitorStatus');
expect(alert.actionGroups).toMatchInlineSnapshot(`
Array [
diff --git a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts
index 2c731687c52fc..829e6f92d3702 100644
--- a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts
+++ b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts
@@ -176,7 +176,7 @@ const DEFAULT_MAX_MESSAGE_ROWS = 3;
export const statusCheckAlertFactory: UptimeAlertTypeFactory = (server, libs) => ({
id: 'xpack.uptime.alerts.monitorStatus',
name: i18n.translate('xpack.uptime.alerts.monitorStatus', {
- defaultMessage: 'Uptime Monitor Status',
+ defaultMessage: 'Uptime monitor status',
}),
validate: {
params: schema.object({
diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts
index b7e340fddbd2c..112c8e97d4c00 100644
--- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts
+++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts
@@ -31,49 +31,25 @@ describe('getLatestMonitor', () => {
],
},
},
- aggs: {
- by_id: {
- terms: {
- field: 'monitor.id',
- size: 1000,
- },
- aggs: {
- latest: {
- top_hits: {
- size: 1,
- sort: {
- '@timestamp': { order: 'desc' },
- },
- },
- },
- },
- },
+ size: 1,
+ _source: ['url', 'monitor', 'observer', 'tls', '@timestamp'],
+ sort: {
+ '@timestamp': { order: 'desc' },
},
- size: 0,
},
};
mockEsSearchResult = {
- aggregations: {
- by_id: {
- buckets: [
- {
- latest: {
- hits: {
- hits: [
- {
- _source: {
- '@timestamp': 123456,
- monitor: {
- id: 'testMonitor',
- },
- },
- },
- ],
- },
+ hits: {
+ hits: [
+ {
+ _source: {
+ timestamp: 123456,
+ monitor: {
+ id: 'testMonitor',
},
},
- ],
- },
+ },
+ ],
},
};
});
@@ -87,6 +63,7 @@ describe('getLatestMonitor', () => {
dateEnd: 'now',
monitorId: 'testMonitor',
});
+
expect(result.timestamp).toBe(123456);
expect(result.monitor).not.toBeFalsy();
expect(result?.monitor?.id).toBe('testMonitor');
diff --git a/x-pack/plugins/uptime/server/lib/requests/get_latest_monitor.ts b/x-pack/plugins/uptime/server/lib/requests/get_latest_monitor.ts
index 176963a998685..299e3eb6ca3cf 100644
--- a/x-pack/plugins/uptime/server/lib/requests/get_latest_monitor.ts
+++ b/x-pack/plugins/uptime/server/lib/requests/get_latest_monitor.ts
@@ -26,8 +26,6 @@ export const getLatestMonitor: UMElasticsearchQueryFn {
- // TODO: Write tests for this function
-
const params = {
index: dynamicSettings.heartbeatIndices,
body: {
@@ -46,33 +44,20 @@ export const getLatestMonitor: UMElasticsearchQueryFn = async ({
- callES,
- dynamicSettings,
- monitorId,
-}) => {
- const params = {
- index: dynamicSettings.heartbeatIndices,
- body: {
- size: 1,
- _source: ['url', 'monitor', 'observer'],
- query: {
- bool: {
- filter: [
- {
- term: {
- 'monitor.id': monitorId,
- },
- },
- ],
- },
- },
- sort: [
- {
- '@timestamp': {
- order: 'desc',
- },
- },
- ],
- },
- };
-
- const result = await callES('search', params);
-
- return result.hits.hits[0]?._source;
-};
diff --git a/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts b/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts
index 2cebd532fd29b..cea8b819f403e 100644
--- a/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts
+++ b/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts
@@ -5,7 +5,6 @@
*/
import { UMElasticsearchQueryFn } from '../adapters';
-import { INDEX_NAMES } from '../../../../../legacy/plugins/uptime/common/constants';
export interface GetMonitorStatusParams {
filters?: string;
@@ -51,7 +50,7 @@ const getLocationClause = (locations: string[]) => ({
export const getMonitorStatus: UMElasticsearchQueryFn<
GetMonitorStatusParams,
GetMonitorStatusResult[]
-> = async ({ callES, filters, locations, numTimes, timerange: { from, to } }) => {
+> = async ({ callES, dynamicSettings, filters, locations, numTimes, timerange: { from, to } }) => {
const queryResults: Array> = [];
let afterKey: MonitorStatusKey | undefined;
@@ -60,7 +59,7 @@ export const getMonitorStatus: UMElasticsearchQueryFn<
// multiple status types for this alert, and this will become a parameter
const STATUS = 'down';
const esParams: any = {
- index: INDEX_NAMES.HEARTBEAT,
+ index: dynamicSettings.heartbeatIndices,
body: {
query: {
bool: {
diff --git a/x-pack/plugins/uptime/server/lib/requests/index.ts b/x-pack/plugins/uptime/server/lib/requests/index.ts
index 7225d329d3c7f..445adc3c15a93 100644
--- a/x-pack/plugins/uptime/server/lib/requests/index.ts
+++ b/x-pack/plugins/uptime/server/lib/requests/index.ts
@@ -7,7 +7,6 @@
export { getFilterBar, GetFilterBarParams } from './get_filter_bar';
export { getUptimeIndexPattern as getIndexPattern } from './get_index_pattern';
export { getLatestMonitor, GetLatestMonitorParams } from './get_latest_monitor';
-export { getMonitor, GetMonitorParams } from './get_monitor';
export { getMonitorDurationChart, GetMonitorChartsParams } from './get_monitor_duration';
export { getMonitorDetails, GetMonitorDetailsParams } from './get_monitor_details';
export { getMonitorLocations, GetMonitorLocationsParams } from './get_monitor_locations';
diff --git a/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts b/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts
index 6eeea5ba4c3e9..9d3fa5aa08aed 100644
--- a/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts
+++ b/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts
@@ -9,7 +9,6 @@ import { Ping, PingResults } from '../../../../../legacy/plugins/uptime/common/g
import {
GetFilterBarParams,
GetLatestMonitorParams,
- GetMonitorParams,
GetMonitorChartsParams,
GetMonitorDetailsParams,
GetMonitorLocationsParams,
@@ -39,7 +38,6 @@ export interface UptimeRequests {
getFilterBar: ESQ;
getIndexPattern: ESQ<{}, {}>;
getLatestMonitor: ESQ;
- getMonitor: ESQ;
getMonitorDurationChart: ESQ;
getMonitorDetails: ESQ;
getMonitorLocations: ESQ;
diff --git a/x-pack/plugins/uptime/server/rest_api/index.ts b/x-pack/plugins/uptime/server/rest_api/index.ts
index 561997c3567d0..c84ea71037953 100644
--- a/x-pack/plugins/uptime/server/rest_api/index.ts
+++ b/x-pack/plugins/uptime/server/rest_api/index.ts
@@ -11,7 +11,6 @@ import { createLogPageViewRoute } from './telemetry';
import { createGetSnapshotCount } from './snapshot';
import { UMRestApiRouteFactory } from './types';
import {
- createGetMonitorRoute,
createGetMonitorDetailsRoute,
createGetMonitorLocationsRoute,
createGetStatusBarRoute,
@@ -31,7 +30,6 @@ export const restApiRoutes: UMRestApiRouteFactory[] = [
createGetIndexStatusRoute,
createGetDynamicSettingsRoute,
createPostDynamicSettingsRoute,
- createGetMonitorRoute,
createGetMonitorDetailsRoute,
createGetMonitorLocationsRoute,
createGetStatusBarRoute,
diff --git a/x-pack/plugins/uptime/server/rest_api/monitors/index.ts b/x-pack/plugins/uptime/server/rest_api/monitors/index.ts
index 7f1f10081dc4e..7da717b50c149 100644
--- a/x-pack/plugins/uptime/server/rest_api/monitors/index.ts
+++ b/x-pack/plugins/uptime/server/rest_api/monitors/index.ts
@@ -6,4 +6,4 @@
export { createGetMonitorDetailsRoute } from './monitors_details';
export { createGetMonitorLocationsRoute } from './monitor_locations';
-export { createGetMonitorRoute, createGetStatusBarRoute } from './status';
+export { createGetStatusBarRoute } from './status';
diff --git a/x-pack/plugins/uptime/server/rest_api/monitors/status.ts b/x-pack/plugins/uptime/server/rest_api/monitors/status.ts
index e1fcaf54f2824..9bccd64c4bd65 100644
--- a/x-pack/plugins/uptime/server/rest_api/monitors/status.ts
+++ b/x-pack/plugins/uptime/server/rest_api/monitors/status.ts
@@ -7,30 +7,7 @@
import { schema } from '@kbn/config-schema';
import { UMServerLibs } from '../../lib/lib';
import { UMRestApiRouteFactory } from '../types';
-import { API_URLS } from '../../../../../legacy/plugins/uptime/common/constants/rest_api';
-
-export const createGetMonitorRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({
- method: 'GET',
- path: API_URLS.MONITOR_SELECTED,
-
- validate: {
- query: schema.object({
- monitorId: schema.string(),
- }),
- },
- options: {
- tags: ['access:uptime-read'],
- },
- handler: async ({ callES, dynamicSettings }, _context, request, response): Promise => {
- const { monitorId } = request.query;
-
- return response.ok({
- body: {
- ...(await libs.requests.getMonitor({ callES, dynamicSettings, monitorId })),
- },
- });
- },
-});
+import { API_URLS } from '../../../../../legacy/plugins/uptime/common/constants';
export const createGetStatusBarRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({
method: 'GET',
diff --git a/x-pack/test/api_integration/apis/endpoint/alerts.ts b/x-pack/test/api_integration/apis/endpoint/alerts.ts
index 5d42e85645b2d..7e72a2f9072f3 100644
--- a/x-pack/test/api_integration/apis/endpoint/alerts.ts
+++ b/x-pack/test/api_integration/apis/endpoint/alerts.ts
@@ -72,7 +72,7 @@ export default function({ getService }: FtrProviderContext) {
describe('when data is in elasticsearch', () => {
before(async () => {
await esArchiver.load('endpoint/alerts/api_feature');
- await esArchiver.load('endpoint/metadata/api_feature');
+ await esArchiver.load('endpoint/alerts/host_api_feature');
const res = await es.search({
index: 'events-endpoint-1',
body: ES_QUERY_MISSING,
@@ -82,7 +82,7 @@ export default function({ getService }: FtrProviderContext) {
after(async () => {
await esArchiver.unload('endpoint/alerts/api_feature');
- await esArchiver.unload('endpoint/metadata/api_feature');
+ await esArchiver.unload('endpoint/alerts/host_api_feature');
});
it('should not support POST requests', async () => {
@@ -93,7 +93,7 @@ export default function({ getService }: FtrProviderContext) {
.expect(404);
});
- it.skip('should return one entry for each alert with default paging', async () => {
+ it('should return one entry for each alert with default paging', async () => {
const { body } = await supertest
.get('/api/endpoint/alerts')
.set('kbn-xsrf', 'xxx')
@@ -111,7 +111,7 @@ export default function({ getService }: FtrProviderContext) {
expect(body.result_from_index).to.eql(0);
});
- it.skip('should return the page_size and page_index specified in the query params', async () => {
+ it('should return the page_size and page_index specified in the query params', async () => {
const pageSize = 1;
const pageIndex = 1;
const { body } = await supertest
@@ -140,7 +140,7 @@ export default function({ getService }: FtrProviderContext) {
.expect(200);
body = response.body;
});
- it.skip('should return accurate total counts', async () => {
+ it('should return accurate total counts', async () => {
expect(body.total).to.eql(numberOfAlertsInFixture);
/**
* Nothing was returned due to pagination.
@@ -160,7 +160,7 @@ export default function({ getService }: FtrProviderContext) {
expect(body.message).to.contain('Value must be equal to or greater than [1]');
});
- it.skip('should return links to the next and previous pages using cursor-based pagination', async () => {
+ it('should return links to the next and previous pages using cursor-based pagination', async () => {
const { body } = await supertest
.get('/api/endpoint/alerts?page_index=0')
.set('kbn-xsrf', 'xxx')
@@ -346,7 +346,7 @@ export default function({ getService }: FtrProviderContext) {
expect(valid).to.eql(true);
});
- it.skip('should filter results of alert data using rison-encoded filters', async () => {
+ it('should filter results of alert data using rison-encoded filters', async () => {
const hostname = 'Host-abmfhmc5ku';
const { body } = await supertest
.get(
@@ -361,7 +361,7 @@ export default function({ getService }: FtrProviderContext) {
expect(body.result_from_index).to.eql(0);
});
- it.skip('should filter results of alert data using KQL', async () => {
+ it('should filter results of alert data using KQL', async () => {
const agentID = '7cf9f7a3-28a6-4d1e-bb45-005aa28f18d0';
const { body } = await supertest
.get(
@@ -376,7 +376,7 @@ export default function({ getService }: FtrProviderContext) {
expect(body.result_from_index).to.eql(0);
});
- it.skip('should return alert details by id, getting last alert', async () => {
+ it('should return alert details by id, getting last alert', async () => {
const documentID = 'zbNm0HABdD75WLjLYgcB';
const prevDocumentID = '2rNm0HABdD75WLjLYgcU';
const { body } = await supertest
diff --git a/x-pack/test/api_integration/apis/endpoint/metadata.ts b/x-pack/test/api_integration/apis/endpoint/metadata.ts
index a363f17df9faa..ad495d4505404 100644
--- a/x-pack/test/api_integration/apis/endpoint/metadata.ts
+++ b/x-pack/test/api_integration/apis/endpoint/metadata.ts
@@ -192,6 +192,27 @@ export default function({ getService }: FtrProviderContext) {
expect(body.request_page_index).to.eql(0);
});
+ it('metadata api should return the endpoint based on the elastic agent id', async () => {
+ const targetEndpointId = 'fc0ff548-feba-41b6-8367-65e8790d0eaf';
+ const targetElasticAgentId = '023fa40c-411d-4188-a941-4147bfadd095';
+ const { body } = await supertest
+ .post('/api/endpoint/metadata')
+ .set('kbn-xsrf', 'xxx')
+ .send({
+ filter: `elastic.agent.id:${targetElasticAgentId}`,
+ })
+ .expect(200);
+ expect(body.total).to.eql(1);
+ const resultHostId: string = body.hosts[0].host.id;
+ const resultElasticAgentId: string = body.hosts[0].elastic.agent.id;
+ expect(resultHostId).to.eql(targetEndpointId);
+ expect(resultElasticAgentId).to.eql(targetElasticAgentId);
+ expect(body.hosts[0].event.created).to.eql(1579881969541);
+ expect(body.hosts.length).to.eql(1);
+ expect(body.request_page_size).to.eql(10);
+ expect(body.request_page_index).to.eql(0);
+ });
+
it('metadata api should return all hosts when filter is empty string', async () => {
const { body } = await supertest
.post('/api/endpoint/metadata')
diff --git a/x-pack/test/api_integration/apis/management/index_management/templates.helpers.js b/x-pack/test/api_integration/apis/management/index_management/templates.helpers.js
index d409d66e3459c..5d8364a8b92c2 100644
--- a/x-pack/test/api_integration/apis/management/index_management/templates.helpers.js
+++ b/x-pack/test/api_integration/apis/management/index_management/templates.helpers.js
@@ -9,37 +9,43 @@ import { API_BASE_PATH, INDEX_PATTERNS } from './constants';
export const registerHelpers = ({ supertest }) => {
const getAllTemplates = () => supertest.get(`${API_BASE_PATH}/templates`);
- const getOneTemplate = name => supertest.get(`${API_BASE_PATH}/templates/${name}`);
+ const getOneTemplate = (name, formatVersion = 1) =>
+ supertest.get(`${API_BASE_PATH}/templates/${name}?v=${formatVersion}`);
- const getTemplatePayload = name => ({
+ const getTemplatePayload = (name, formatVersion = 1) => ({
name,
order: 1,
indexPatterns: INDEX_PATTERNS,
version: 1,
- settings: {
- number_of_shards: 1,
- index: {
- lifecycle: {
- name: 'my_policy',
+ template: {
+ settings: {
+ number_of_shards: 1,
+ index: {
+ lifecycle: {
+ name: 'my_policy',
+ },
},
},
- },
- mappings: {
- _source: {
- enabled: false,
- },
- properties: {
- host_name: {
- type: 'keyword',
+ mappings: {
+ _source: {
+ enabled: false,
},
- created_at: {
- type: 'date',
- format: 'EEE MMM dd HH:mm:ss Z yyyy',
+ properties: {
+ host_name: {
+ type: 'keyword',
+ },
+ created_at: {
+ type: 'date',
+ format: 'EEE MMM dd HH:mm:ss Z yyyy',
+ },
},
},
+ aliases: {
+ alias1: {},
+ },
},
- aliases: {
- alias1: {},
+ _kbnMeta: {
+ formatVersion,
},
});
@@ -49,14 +55,11 @@ export const registerHelpers = ({ supertest }) => {
.set('kbn-xsrf', 'xxx')
.send(payload);
- const deleteTemplates = templatesToDelete =>
+ const deleteTemplates = templates =>
supertest
- .delete(
- `${API_BASE_PATH}/templates/${templatesToDelete
- .map(template => encodeURIComponent(template))
- .join(',')}`
- )
- .set('kbn-xsrf', 'xxx');
+ .post(`${API_BASE_PATH}/delete-templates`)
+ .set('kbn-xsrf', 'xxx')
+ .send({ templates });
const updateTemplate = (payload, templateName) =>
supertest
diff --git a/x-pack/test/api_integration/apis/management/index_management/templates.js b/x-pack/test/api_integration/apis/management/index_management/templates.js
index d9344846ebb91..63bfd494301b3 100644
--- a/x-pack/test/api_integration/apis/management/index_management/templates.js
+++ b/x-pack/test/api_integration/apis/management/index_management/templates.js
@@ -35,7 +35,7 @@ export default function({ getService }) {
await createTemplate(payload).expect(200);
});
- it('should list all the index templates with the expected properties', async () => {
+ it('should list all the index templates with the expected parameters', async () => {
const { body: templates } = await getAllTemplates().expect(200);
const createdTemplate = templates.find(template => template.name === payload.name);
@@ -46,8 +46,13 @@ export default function({ getService }) {
'hasAliases',
'hasMappings',
'ilmPolicy',
- ];
- expectedKeys.forEach(key => expect(Object.keys(createdTemplate).includes(key)).to.be(true));
+ 'isManaged',
+ 'order',
+ 'version',
+ '_kbnMeta',
+ ].sort();
+
+ expect(Object.keys(createdTemplate).sort()).to.eql(expectedKeys);
});
});
@@ -59,19 +64,23 @@ export default function({ getService }) {
await createTemplate(payload).expect(200);
});
- it('should list the index template with the expected properties', async () => {
+ it('should return the index template with the expected parameters', async () => {
const { body } = await getOneTemplate(templateName).expect(200);
const expectedKeys = [
'name',
'indexPatterns',
- 'settings',
- 'aliases',
- 'mappings',
+ 'template',
'ilmPolicy',
- ];
+ 'isManaged',
+ 'order',
+ 'version',
+ '_kbnMeta',
+ ].sort();
+ const expectedTemplateKeys = ['aliases', 'mappings', 'settings'].sort();
expect(body.name).to.equal(templateName);
- expectedKeys.forEach(key => expect(Object.keys(body).includes(key)).to.be(true));
+ expect(Object.keys(body).sort()).to.eql(expectedKeys);
+ expect(Object.keys(body.template).sort()).to.eql(expectedTemplateKeys);
});
});
@@ -145,7 +154,9 @@ export default function({ getService }) {
templateName
);
- const { body } = await deleteTemplates([templateName]).expect(200);
+ const { body } = await deleteTemplates([
+ { name: templateName, formatVersion: payload._kbnMeta.formatVersion },
+ ]).expect(200);
expect(body.errors).to.be.empty;
expect(body.templatesDeleted[0]).to.equal(templateName);
diff --git a/x-pack/test/api_integration/apis/uptime/rest/fixtures/monitor_latest_status.json b/x-pack/test/api_integration/apis/uptime/rest/fixtures/monitor_latest_status.json
index 1702cb2c21007..5d41cdf611824 100644
--- a/x-pack/test/api_integration/apis/uptime/rest/fixtures/monitor_latest_status.json
+++ b/x-pack/test/api_integration/apis/uptime/rest/fixtures/monitor_latest_status.json
@@ -1,89 +1,29 @@
{
- "@timestamp": "2019-09-11T03:40:34.371Z",
- "agent": {
- "ephemeral_id": "412a92a8-2142-4b1a-a7a2-1afd32e12f85",
- "hostname": "avc-x1x",
- "id": "04e1d082-65bc-4929-8d65-d0768a2621c4",
- "type": "heartbeat",
- "version": "8.0.0"
- },
- "ecs": {
- "version": "1.1.0"
- },
- "event": {
- "dataset": "uptime"
- },
- "host": {
- "name": "avc-x1x"
- },
- "http": {
- "response": {
- "body": {
- "bytes": 3,
- "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf"
- },
- "status_code": 200
- },
- "rtt": {
- "content": {
- "us": 57
- },
- "response_header": {
- "us": 262
- },
- "total": {
- "us": 20331
- },
- "validate": {
- "us": 319
- },
- "write_request": {
- "us": 82
- }
- }
- },
- "monitor": {
- "check_group": "d76f0762-d445-11e9-88e3-3e80641b9c71",
- "duration": {
- "us": 24627
- },
- "id": "0002-up",
- "ip": "127.0.0.1",
- "name": "",
- "status": "up",
- "type": "http"
- },
- "observer": {
- "geo": {
- "location": "37.926868, -78.024902",
- "name": "mpls"
- },
- "hostname": "avc-x1x"
- },
- "resolve": {
- "ip": "127.0.0.1",
- "rtt": {
- "us": 4218
- }
- },
- "summary": {
- "down": 0,
- "up": 1
- },
- "tcp": {
- "rtt": {
- "connect": {
- "us": 103
- }
- }
- },
- "timestamp": "2019-09-11T03:40:34.371Z",
- "url": {
- "domain": "localhost",
- "full": "http://localhost:5678/pattern?r=200x1",
- "path": "/pattern",
- "port": 5678,
- "query": "r=200x1",
- "scheme": "http"
- }
+ "timestamp": "2019-09-11T03:40:34.371Z",
+ "observer": {
+ "geo": {
+ "name": "mpls",
+ "location": "37.926868, -78.024902"
+ },
+ "hostname": "avc-x1x"
+ },
+ "monitor": {
+ "duration": {
+ "us": 24627
+ },
+ "ip": "127.0.0.1",
+ "name": "",
+ "check_group": "d76f0762-d445-11e9-88e3-3e80641b9c71",
+ "id": "0002-up",
+ "type": "http",
+ "status": "up"
+ },
+ "url": {
+ "path": "/pattern",
+ "scheme": "http",
+ "port": 5678,
+ "domain": "localhost",
+ "query": "r=200x1",
+ "full": "http://localhost:5678/pattern?r=200x1"
}
+}
\ No newline at end of file
diff --git a/x-pack/test/api_integration/apis/uptime/rest/fixtures/selected_monitor.json b/x-pack/test/api_integration/apis/uptime/rest/fixtures/selected_monitor.json
deleted file mode 100644
index d8367ea67052f..0000000000000
--- a/x-pack/test/api_integration/apis/uptime/rest/fixtures/selected_monitor.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
- "monitor": {
- "check_group": "d76f0762-d445-11e9-88e3-3e80641b9c71",
- "duration": {
- "us": 24627
- },
- "id": "0002-up",
- "ip": "127.0.0.1",
- "name": "",
- "status": "up",
- "type": "http"
- },
- "observer": {
- "geo": {
- "location": "37.926868, -78.024902",
- "name": "mpls"
- },
- "hostname": "avc-x1x"
- },
- "url": {
- "domain": "localhost",
- "full": "http://localhost:5678/pattern?r=200x1",
- "path": "/pattern",
- "port": 5678,
- "query": "r=200x1",
- "scheme": "http"
- }
-}
diff --git a/x-pack/test/api_integration/apis/uptime/rest/index.ts b/x-pack/test/api_integration/apis/uptime/rest/index.ts
index b2236e1bb6308..f89bec589847e 100644
--- a/x-pack/test/api_integration/apis/uptime/rest/index.ts
+++ b/x-pack/test/api_integration/apis/uptime/rest/index.ts
@@ -47,7 +47,6 @@ export default function({ getService, loadTestFile }: FtrProviderContext) {
before('load heartbeat data', async () => await esArchiver.load('uptime/full_heartbeat'));
after('unload', async () => await esArchiver.unload('uptime/full_heartbeat'));
loadTestFile(require.resolve('./monitor_latest_status'));
- loadTestFile(require.resolve('./selected_monitor'));
loadTestFile(require.resolve('./ping_histogram'));
loadTestFile(require.resolve('./monitor_duration'));
loadTestFile(require.resolve('./doc_count'));
diff --git a/x-pack/test/api_integration/apis/uptime/rest/selected_monitor.ts b/x-pack/test/api_integration/apis/uptime/rest/selected_monitor.ts
deleted file mode 100644
index ed034f58a5f59..0000000000000
--- a/x-pack/test/api_integration/apis/uptime/rest/selected_monitor.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { expectFixtureEql } from '../graphql/helpers/expect_fixture_eql';
-import { FtrProviderContext } from '../../../ftr_provider_context';
-
-export default function({ getService }: FtrProviderContext) {
- describe('get selected monitor by ID', () => {
- const monitorId = '0002-up';
-
- const supertest = getService('supertest');
-
- it('returns the monitor for give ID', async () => {
- const apiResponse = await supertest.get(
- `/api/uptime/monitor/selected?monitorId=${monitorId}`
- );
- expectFixtureEql(apiResponse.body, 'selected_monitor');
- });
- });
-}
diff --git a/x-pack/test/functional/apps/endpoint/alerts.ts b/x-pack/test/functional/apps/endpoint/alerts.ts
index 759574702c0f1..6a5a5cb5c32ad 100644
--- a/x-pack/test/functional/apps/endpoint/alerts.ts
+++ b/x-pack/test/functional/apps/endpoint/alerts.ts
@@ -16,6 +16,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) {
this.tags(['ciGroup7']);
before(async () => {
await esArchiver.load('endpoint/alerts/api_feature');
+ await esArchiver.load('endpoint/alerts/host_api_feature');
await pageObjects.common.navigateToUrlWithBrowserHistory('endpoint', '/alerts');
});
it('loads the Alert List Page', async () => {
@@ -66,6 +67,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) {
after(async () => {
await esArchiver.unload('endpoint/alerts/api_feature');
+ await esArchiver.unload('endpoint/alerts/host_api_feature');
});
});
}
diff --git a/x-pack/test/functional/es_archives/endpoint/alerts/host_api_feature/data.json.gz b/x-pack/test/functional/es_archives/endpoint/alerts/host_api_feature/data.json.gz
new file mode 100644
index 0000000000000..94a96c54ee9cb
Binary files /dev/null and b/x-pack/test/functional/es_archives/endpoint/alerts/host_api_feature/data.json.gz differ
diff --git a/x-pack/test/functional/es_archives/endpoint/alerts/host_api_feature/mappings.json b/x-pack/test/functional/es_archives/endpoint/alerts/host_api_feature/mappings.json
new file mode 100644
index 0000000000000..61ddf3c4e65db
--- /dev/null
+++ b/x-pack/test/functional/es_archives/endpoint/alerts/host_api_feature/mappings.json
@@ -0,0 +1,156 @@
+{
+ "type": "index",
+ "value": {
+ "aliases": {
+ },
+ "index": "endpoint-agent-1",
+ "mappings": {
+ "properties": {
+ "@timestamp": {
+ "type": "long"
+ },
+ "agent": {
+ "properties": {
+ "id": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "version": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ }
+ }
+ },
+ "endpoint": {
+ "properties": {
+ "policy": {
+ "properties": {
+ "id": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "name": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ }
+ }
+ }
+ }
+ },
+ "event": {
+ "properties": {
+ "created": {
+ "type": "long"
+ }
+ }
+ },
+ "host": {
+ "properties": {
+ "hostname": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "id": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "ip": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "mac": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "os": {
+ "properties": {
+ "full": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "name": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "variant": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ },
+ "version": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "settings": {
+ "index": {
+ "number_of_replicas": "1",
+ "number_of_shards": "1"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/data.json b/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/data.json
index 56c58a2baa039..2046f46db9f53 100644
--- a/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/data.json
+++ b/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/data.json
@@ -10,6 +10,11 @@
"version": "6.6.1",
"name" : "Elastic Endpoint"
},
+ "elastic": {
+ "agent": {
+ "id": "11488bae-880b-4e7b-8d28-aac2aa9de816"
+ }
+ },
"endpoint": {
"policy": {
"id": "C2A9093E-E289-4C0A-AA44-8C32A414FA7A"
@@ -54,6 +59,11 @@
"version": "6.0.0",
"name" : "Elastic Endpoint"
},
+ "elastic": {
+ "agent": {
+ "id": "92ac1ce0-e1f7-409e-8af6-f17e97b1fc71"
+ }
+ },
"endpoint": {
"policy": {
"id": "C2A9093E-E289-4C0A-AA44-8C32A414FA7A"
@@ -97,6 +107,11 @@
"version": "6.8.0",
"name" : "Elastic Endpoint"
},
+ "elastic": {
+ "agent": {
+ "id": "023fa40c-411d-4188-a941-4147bfadd095"
+ }
+ },
"endpoint": {
"policy": {
"id": "00000000-0000-0000-0000-000000000000"
@@ -138,6 +153,11 @@
"version": "6.6.1",
"name" : "Elastic Endpoint"
},
+ "elastic": {
+ "agent": {
+ "id": "11488bae-880b-4e7b-8d28-aac2aa9de816"
+ }
+ },
"endpoint": {
"policy": {
"id": "C2A9093E-E289-4C0A-AA44-8C32A414FA7A"
@@ -182,6 +202,11 @@
"version": "6.0.0",
"name" : "Elastic Endpoint"
},
+ "elastic": {
+ "agent": {
+ "id": "92ac1ce0-e1f7-409e-8af6-f17e97b1fc71"
+ }
+ },
"endpoint": {
"policy": {
"id": "C2A9093E-E289-4C0A-AA44-8C32A414FA7A"
@@ -224,6 +249,11 @@
"version": "6.8.0",
"name" : "Elastic Endpoint"
},
+ "elastic": {
+ "agent": {
+ "id": "023fa40c-411d-4188-a941-4147bfadd095"
+ }
+ },
"endpoint": {
"policy": {
"id": "00000000-0000-0000-0000-000000000000"
@@ -266,6 +296,11 @@
"version": "6.6.1",
"name" : "Elastic Endpoint"
},
+ "elastic": {
+ "agent": {
+ "id": "11488bae-880b-4e7b-8d28-aac2aa9de816"
+ }
+ },
"endpoint": {
"policy": {
"id": "00000000-0000-0000-0000-000000000000"
@@ -309,6 +344,11 @@
"version": "6.0.0",
"name" : "Elastic Endpoint"
},
+ "elastic": {
+ "agent": {
+ "id": "92ac1ce0-e1f7-409e-8af6-f17e97b1fc71"
+ }
+ },
"endpoint": {
"policy": {
"id": "C2A9093E-E289-4C0A-AA44-8C32A414FA7A"
@@ -351,6 +391,11 @@
"version": "6.8.0",
"name" : "Elastic Endpoint"
},
+ "elastic": {
+ "agent": {
+ "id": "023fa40c-411d-4188-a941-4147bfadd095"
+ }
+ },
"endpoint": {
"policy": {
"id": "00000000-0000-0000-0000-000000000000"
@@ -379,4 +424,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/mappings.json b/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/mappings.json
index 61ddf3c4e65db..c9a6c183f0489 100644
--- a/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/mappings.json
+++ b/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/mappings.json
@@ -9,6 +9,23 @@
"@timestamp": {
"type": "long"
},
+ "elastic": {
+ "properties": {
+ "agent": {
+ "properties": {
+ "id": {
+ "fields": {
+ "keyword": {
+ "ignore_above": 256,
+ "type": "keyword"
+ }
+ },
+ "type": "text"
+ }
+ }
+ }
+ }
+ },
"agent": {
"properties": {
"id": {
@@ -153,4 +170,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/x-pack/test/functional/page_objects/gis_page.js b/x-pack/test/functional/page_objects/gis_page.js
index f8d1808c1ef8d..700575220297e 100644
--- a/x-pack/test/functional/page_objects/gis_page.js
+++ b/x-pack/test/functional/page_objects/gis_page.js
@@ -511,7 +511,7 @@ export function GisPageProvider({ getService, getPageObjects }) {
async selectGeoJsonUploadSource() {
log.debug(`Select upload geojson source`);
- await testSubjects.click('uploadedGeoJson');
+ await testSubjects.click('uploadGeoJson');
}
async uploadJsonFileForIndexing(path) {
diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts
index beedd6390388d..347eb5e14d0a8 100644
--- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts
+++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts
@@ -92,7 +92,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
{
name: alertName,
tagsText: '',
- alertType: 'Index Threshold',
+ alertType: 'Index threshold',
interval: '1m',
},
]);
@@ -154,7 +154,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
{
name: createdAlert.name,
tagsText: 'foo, bar',
- alertType: 'Index Threshold',
+ alertType: 'Index threshold',
interval: '1m',
},
]);
@@ -179,7 +179,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
{
name: updatedAlertName,
tagsText: 'foo, bar',
- alertType: 'Index Threshold',
+ alertType: 'Index threshold',
interval: '1m',
},
]);
@@ -210,7 +210,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
{
name: createdAlert.name,
tagsText: 'foo, bar',
- alertType: 'Index Threshold',
+ alertType: 'Index threshold',
interval: '1m',
},
]);
@@ -260,7 +260,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
{
name: createdAlert.name,
tagsText: 'foo, bar',
- alertType: 'Index Threshold',
+ alertType: 'Index threshold',
interval: '1m',
},
]);
diff --git a/x-pack/test_utils/testbed/testbed.ts b/x-pack/test_utils/testbed/testbed.ts
index f32fb42a8a8b0..2bd53adda2cdf 100644
--- a/x-pack/test_utils/testbed/testbed.ts
+++ b/x-pack/test_utils/testbed/testbed.ts
@@ -138,6 +138,44 @@ export const registerTestBed = (
});
};
+ const waitFor: TestBed['waitFor'] = async (testSubject: T) => {
+ const triggeredAt = Date.now();
+
+ /**
+ * The way jest run tests in parallel + the not deterministic DOM update from React "hooks"
+ * add flakiness to the tests. This is especially true for component integration tests that
+ * make many update to the DOM.
+ *
+ * For this reason, when we _know_ that an element should be there after we updated some state,
+ * we will give it 30 seconds to appear in the DOM, checking every 100 ms for its presence.
+ */
+ const MAX_WAIT_TIME = 30000;
+ const WAIT_INTERVAL = 100;
+
+ const process = async (): Promise => {
+ const elemFound = exists(testSubject);
+
+ if (elemFound) {
+ // Great! nothing else to do here.
+ return;
+ }
+
+ const timeElapsed = Date.now() - triggeredAt;
+ if (timeElapsed > MAX_WAIT_TIME) {
+ throw new Error(
+ `I waited patiently for the "${testSubject}" test subject to appear with no luck. It is nowhere to be found!`
+ );
+ }
+
+ return new Promise(resolve => setTimeout(resolve, WAIT_INTERVAL)).then(() => {
+ component.update();
+ return process();
+ });
+ };
+
+ return process();
+ };
+
/**
* ----------------------------------------------------------------
* Forms
@@ -254,6 +292,7 @@ export const registerTestBed = (
exists,
find,
setProps,
+ waitFor,
table: {
getMetaData,
},
diff --git a/x-pack/test_utils/testbed/types.ts b/x-pack/test_utils/testbed/types.ts
index c51e6a256f66f..e4bb3ee57adec 100644
--- a/x-pack/test_utils/testbed/types.ts
+++ b/x-pack/test_utils/testbed/types.ts
@@ -55,6 +55,12 @@ export interface TestBed {
* @param updatedProps The updated prop object
*/
setProps: (updatedProps: any) => void;
+ /**
+ * Helper to wait until an element appears in the DOM as hooks updates cycles are tricky.
+ * Useful when loading a component that fetches a resource from the server
+ * and we need to wait for the data to be fetched (and bypass any "loading" state).
+ */
+ waitFor: (testSubject: T) => Promise;
form: {
/**
* Set the value of a form text input.
diff --git a/yarn.lock b/yarn.lock
index a6b3154c89d05..8176eab436afd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4627,6 +4627,11 @@
resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d"
integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==
+"@types/safe-json-stringify@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@types/safe-json-stringify/-/safe-json-stringify-1.1.0.tgz#4cd786442d7abc037f8e9026b22e3b401005c287"
+ integrity sha512-iIQqHp8fqDgxTlWor4DrTrKGVmjDeGDodQBipQkPSlRU1QeKIytv37U4aFN9N65VJcFJx67+zOnpbTNQzqHTOg==
+
"@types/seedrandom@>=2.0.0 <4.0.0":
version "2.4.28"
resolved "https://registry.yarnpkg.com/@types/seedrandom/-/seedrandom-2.4.28.tgz#9ce8fa048c1e8c85cb71d7fe4d704e000226036f"
@@ -7196,11 +7201,12 @@ backo2@1.0.2:
resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"
integrity sha1-MasayLEpNjRj41s+u2n038+6eUc=
-backport@5.1.2:
- version "5.1.2"
- resolved "https://registry.yarnpkg.com/backport/-/backport-5.1.2.tgz#d9be0b0f89e5140fdcc778158db00beef92481ec"
- integrity sha512-CexygdCdhUR7Br16CNUo/bfgvoa6ItKoEWJ9oPIFeIONWatYV2NA7U3uMpn74qmuJkDmrOi9SmDutxg96+qWPw==
+backport@5.1.3:
+ version "5.1.3"
+ resolved "https://registry.yarnpkg.com/backport/-/backport-5.1.3.tgz#6fa788f48ee90e2b98b4e8d6d9385b0fb2e2f689"
+ integrity sha512-fTrXAyXvsg+lOuuWQosHzz/YnFfrkBsVkPcygjrDZVlWhbD+cA8mY3GrcJ8sIFwUg9Ja8qCeBFfLIRKlOwuzEg==
dependencies:
+ "@types/safe-json-stringify" "^1.1.0"
axios "^0.19.2"
dedent "^0.7.0"
del "^5.1.0"
@@ -7211,6 +7217,7 @@ backport@5.1.2:
lodash.uniq "^4.5.0"
make-dir "^3.0.2"
ora "^4.0.3"
+ safe-json-stringify "^1.2.0"
strip-json-comments "^3.0.1"
winston "^3.2.1"
yargs "^15.3.1"
@@ -26003,6 +26010,11 @@ safe-json-parse@~1.0.1:
resolved "https://registry.yarnpkg.com/safe-json-parse/-/safe-json-parse-1.0.1.tgz#3e76723e38dfdda13c9b1d29a1e07ffee4b30b57"
integrity sha1-PnZyPjjf3aE8mx0poeB//uSzC1c=
+safe-json-stringify@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz#356e44bc98f1f93ce45df14bcd7c01cda86e0afd"
+ integrity sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==
+
safe-regex@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"