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/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 d876c396e3e3b..fd6f6ac140247 100644 --- a/package.json +++ b/package.json @@ -396,7 +396,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/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/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/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/map/mb/view.js b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js index fdc8ad2176d08..2995ea039e7a8 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js @@ -235,12 +235,12 @@ export class MBMapContainer extends React.Component { //clamping ot -89/89 latitudes since Mapboxgl does not seem to handle bounds that contain the poles (logs errors to the console when using -90/90) const lnLatBounds = new mapboxgl.LngLatBounds( new mapboxgl.LngLat( - clampToLonBounds(goto.bounds.min_lon), - clampToLatBounds(goto.bounds.min_lat) + clampToLonBounds(goto.bounds.minLon), + clampToLatBounds(goto.bounds.minLat) ), new mapboxgl.LngLat( - clampToLonBounds(goto.bounds.max_lon), - clampToLatBounds(goto.bounds.max_lat) + clampToLonBounds(goto.bounds.maxLon), + clampToLatBounds(goto.bounds.maxLat) ) ); //maxZoom ensure we're not zooming in too far on single points or small shapes diff --git a/x-pack/legacy/plugins/maps/public/embeddable/README.md b/x-pack/legacy/plugins/maps/public/embeddable/README.md index 1de327702fb87..8ce3794e2ed2c 100644 --- a/x-pack/legacy/plugins/maps/public/embeddable/README.md +++ b/x-pack/legacy/plugins/maps/public/embeddable/README.md @@ -30,17 +30,15 @@ ### Creating a Map embeddable from state ``` const factory = new MapEmbeddableFactory(); -const state = { - layerList: [], // where layerList is same as saved object layerListJSON property (unstringified) - 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); +// where layerList is same as saved object layerListJSON property (unstringified)) +mapEmbeddable.setLayerList([]); ``` #### Customize tooltip @@ -62,7 +60,9 @@ const renderTooltipContent = ({ addFilters, closeTooltip, features, isLocked, lo 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/sources/es_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js index bf04a73cfba77..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 @@ -175,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/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/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/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/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/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/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/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/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/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/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