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