diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 0b192fcd71043..03c6cf9592614 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -56,7 +56,7 @@ pageLoadAssetSize: embeddableAlertsTable: 6524 enterpriseSearch: 37565 entityStore: 9718 - esql: 29547 + esql: 29469 esqlDataGrid: 10209 esUiShared: 101220 evals: 6117 @@ -116,7 +116,7 @@ pageLoadAssetSize: logstash: 15882 maintenanceWindows: 8775 management: 14304 - maps: 45917 + maps: 36281 mapsEms: 6734 metricsDataAccess: 44950 ml: 99842 @@ -206,7 +206,7 @@ pageLoadAssetSize: visTypeTagcloud: 7876 visTypeTimelion: 12512 visTypeTimeseries: 20000 - visTypeVega: 38538 + visTypeVega: 11562 visTypeVislib: 14679 visTypeXy: 32342 visualizationListing: 5148 diff --git a/src/platform/plugins/private/vis_types/vega/public/async_module.ts b/src/platform/plugins/private/vis_types/vega/public/async_module.ts new file mode 100644 index 0000000000000..b9d6be5ea351e --- /dev/null +++ b/src/platform/plugins/private/vis_types/vega/public/async_module.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +export { vegaVisType } from './vega_type'; +export { createVegaFn } from './vega_fn'; +export { getVegaVisRenderer } from './vega_vis_renderer'; diff --git a/src/platform/plugins/private/vis_types/vega/public/plugin.ts b/src/platform/plugins/private/vis_types/vega/public/plugin.ts index 2e84c1c3e6b2a..3172d2d8b79ed 100644 --- a/src/platform/plugins/private/vis_types/vega/public/plugin.ts +++ b/src/platform/plugins/private/vis_types/vega/public/plugin.ts @@ -33,14 +33,11 @@ import { setUsageCollectionStart, } from './services'; -import { createVegaFn } from './vega_fn'; -import { vegaVisType } from './vega_type'; import type { IServiceSettings } from './vega_view/vega_map_view/service_settings/service_settings_types'; import type { ConfigSchema } from '../server/config'; import { getVegaInspectorView } from './vega_inspector'; -import { getVegaVisRenderer } from './vega_vis_renderer'; import { getServiceSettingsLazy } from './vega_view/vega_map_view/service_settings/get_service_settings_lazy'; /** @internal */ @@ -64,6 +61,7 @@ export interface VegaPluginSetupDependencies { export interface VegaPluginStartDependencies { data: DataPublicPluginStart; embeddable: EmbeddableStart; + expressions: ReturnType; mapsEms: MapsEmsPluginPublicStart; dataViews: DataViewsPublicPluginStart; uiActions: UiActionsStart; @@ -79,7 +77,7 @@ export class VegaPlugin implements Plugin { } public setup( - core: CoreSetup, + core: CoreSetup, { inspector, data, expressions, visualizations }: VegaPluginSetupDependencies ) { setInjectedVars({ @@ -96,10 +94,15 @@ export class VegaPlugin implements Plugin { inspector.registerView(getVegaInspectorView({ uiSettings: core.uiSettings })); - expressions.registerFunction(() => createVegaFn(visualizationDependencies)); - expressions.registerRenderer(getVegaVisRenderer(visualizationDependencies)); - - visualizations.createBaseVisualization(vegaVisType); + visualizations.createBaseVisualizationAsync('vega', async () => { + const [[, startPlugins], { vegaVisType, createVegaFn, getVegaVisRenderer }] = + await Promise.all([core.getStartServices(), import('./async_module')]); + if (!startPlugins.expressions.getFunction('vega')) { + expressions.registerFunction(() => createVegaFn(visualizationDependencies)); + expressions.registerRenderer(getVegaVisRenderer(visualizationDependencies)); + } + return vegaVisType; + }); } public start(core: CoreStart, deps: VegaPluginStartDependencies) { diff --git a/src/platform/plugins/shared/visualizations/moon.yml b/src/platform/plugins/shared/visualizations/moon.yml index 279067d134641..e4094c2e2cf9d 100644 --- a/src/platform/plugins/shared/visualizations/moon.yml +++ b/src/platform/plugins/shared/visualizations/moon.yml @@ -93,6 +93,7 @@ dependsOn: - '@kbn/cps' - '@kbn/kql' - '@kbn/cps-utils' + - '@kbn/std' tags: - plugin - prod diff --git a/src/platform/plugins/shared/visualizations/public/legacy/memoize.test.ts b/src/platform/plugins/shared/visualizations/public/legacy/memoize.test.ts deleted file mode 100644 index 5ad7549c4fadd..0000000000000 --- a/src/platform/plugins/shared/visualizations/public/legacy/memoize.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { memoizeLast } from './memoize'; - -describe('memoizeLast', () => { - type SumFn = (a: number, b: number) => number; - let originalSum: SumFn; - let memoizedSum: SumFn; - - beforeEach(() => { - originalSum = jest.fn((a, b) => a + b); - memoizedSum = memoizeLast(originalSum); - }); - - it('should call through function', () => { - expect(memoizedSum(26, 16)).toBe(42); - expect(originalSum).toHaveBeenCalledWith(26, 16); - }); - - it('should memoize the last call', () => { - memoizedSum(26, 16); - expect(originalSum).toHaveBeenCalledTimes(1); - memoizedSum(26, 16); - expect(originalSum).toHaveBeenCalledTimes(1); - }); - - it('should use parameters as cache keys', () => { - expect(memoizedSum(26, 16)).toBe(42); - expect(originalSum).toHaveBeenCalledTimes(1); - expect(memoizedSum(16, 26)).toBe(42); - expect(originalSum).toHaveBeenCalledTimes(2); - }); -}); diff --git a/src/platform/plugins/shared/visualizations/public/legacy/memoize.ts b/src/platform/plugins/shared/visualizations/public/legacy/memoize.ts deleted file mode 100644 index 237b065e51961..0000000000000 --- a/src/platform/plugins/shared/visualizations/public/legacy/memoize.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -interface MemoizedCall { - args: any[]; - returnValue: any; - this: any; -} - -// A symbol expressing, that the memoized function has never been called -const neverCalled: unique symbol = Symbol(); -type NeverCalled = typeof neverCalled; - -/** - * A simple memoize function, that only stores the last returned value - * and uses the identity of all passed parameters as a cache key. - */ -function memoizeLast any>(func: T): T { - let prevCall: MemoizedCall | NeverCalled = neverCalled; - - // We need to use a `function` here for proper this passing. - const memoizedFunction = function (this: any, ...args: any[]) { - if ( - prevCall !== neverCalled && - prevCall.this === this && - prevCall.args.length === args.length && - prevCall.args.every((arg, index) => arg === args[index]) - ) { - return prevCall.returnValue; - } - - prevCall = { - args, - this: this, - returnValue: func.apply(this, args), - }; - - return prevCall.returnValue; - } as T; - - return memoizedFunction; -} - -export { memoizeLast }; diff --git a/src/platform/plugins/shared/visualizations/public/mocks.ts b/src/platform/plugins/shared/visualizations/public/mocks.ts index d5ff1c3c966ea..b6263b5c74789 100644 --- a/src/platform/plugins/shared/visualizations/public/mocks.ts +++ b/src/platform/plugins/shared/visualizations/public/mocks.ts @@ -34,6 +34,7 @@ import type { Schema, VisualizationsSetup, VisualizationsStart } from '.'; const createSetupContract = (): VisualizationsSetup => ({ createBaseVisualization: jest.fn(), + createBaseVisualizationAsync: jest.fn(), registerAlias: jest.fn(), visEditorsRegistry: { registerDefault: jest.fn(), register: jest.fn(), get: jest.fn() }, listingViewRegistry: { add: jest.fn() }, diff --git a/src/platform/plugins/shared/visualizations/public/plugin.ts b/src/platform/plugins/shared/visualizations/public/plugin.ts index ff32d23a20b72..43c8ce5a49e57 100644 --- a/src/platform/plugins/shared/visualizations/public/plugin.ts +++ b/src/platform/plugins/shared/visualizations/public/plugin.ts @@ -115,7 +115,6 @@ import { setSavedSearch, setDataViews, setInspector, - getTypes, setNotifications, } from './services'; import type { ListingViewRegistry } from './types'; @@ -501,9 +500,8 @@ export class VisualizationsPlugin savedObjectName: i18n.translate('visualizations.visualizeSavedObjectName', { defaultMessage: 'Visualization', }), - getIconForSavedObject: (savedObject) => { - const visState = JSON.parse(savedObject.attributes.visState ?? '{}'); - return getTypes().get(visState.type)?.icon ?? ''; + getIconForSavedObject: () => { + return 'visualizeApp'; }, }); embeddable.registerLegacyURLTransform( diff --git a/src/platform/plugins/shared/visualizations/public/utils/saved_visualize_utils.ts b/src/platform/plugins/shared/visualizations/public/utils/saved_visualize_utils.ts index 3056033b464d2..70e2c2675f339 100644 --- a/src/platform/plugins/shared/visualizations/public/utils/saved_visualize_utils.ts +++ b/src/platform/plugins/shared/visualizations/public/utils/saved_visualize_utils.ts @@ -8,6 +8,7 @@ */ import _ from 'lodash'; +import { asyncMap } from '@kbn/std'; import type { Reference } from '@kbn/content-management-utils'; import { SavedObjectNotFound } from '@kbn/kibana-utils-plugin/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; @@ -52,7 +53,7 @@ const getDefaults = (opts: GetVisOptions) => ({ version: 1, }); -function mapHitSource( +async function mapHitSource( visTypes: Pick, { attributes, id, references, updatedAt, managed }: VisualizationSavedObject ) { @@ -88,18 +89,19 @@ function mapHitSource( } } - if (!typeName || !visTypes.get(typeName as string)) { + const visType = typeName ? await visTypes.get(typeName) : undefined; + if (!typeName || !visType) { newAttributes.error = 'Unknown visualization type'; return newAttributes; } - newAttributes.type = visTypes.get(typeName as string); + newAttributes.type = visType; newAttributes.savedObjectType = 'visualization'; newAttributes.icon = newAttributes.type?.icon; newAttributes.image = newAttributes.type?.image; newAttributes.typeTitle = newAttributes.type?.title; newAttributes.editor = { editUrl: `/edit/${id}` }; - newAttributes.readOnly = Boolean(visTypes.get(typeName as string)?.disableEdit); + newAttributes.readOnly = Boolean(newAttributes.type?.disableEdit); return newAttributes; } @@ -177,7 +179,7 @@ export async function findListItems( return { total, - hits: savedObjects.map((savedObject: SavedObjectsFindResult) => { + hits: await asyncMap(savedObjects, async (savedObject: SavedObjectsFindResult) => { const config = extensionByType[savedObject.type]; const { updated_at, updated_by, created_at, created_by, ...rest } = savedObject; const visObject = { @@ -195,7 +197,7 @@ export async function findListItems( references: savedObject.references, }; } else { - return mapHitSource(visTypes, visObject); + return await mapHitSource(visTypes, visObject); } }), }; diff --git a/src/platform/plugins/shared/visualizations/public/vis.test.ts b/src/platform/plugins/shared/visualizations/public/vis.test.ts index a7c76d240ade5..5e915ca696b4c 100644 --- a/src/platform/plugins/shared/visualizations/public/vis.test.ts +++ b/src/platform/plugins/shared/visualizations/public/vis.test.ts @@ -8,35 +8,16 @@ */ import { Vis } from './vis'; +import type { VisTypeDefinition } from './vis_types'; +import { BaseVisType } from './vis_types'; jest.mock('./services', () => { - class MockVisualizationController { - constructor() {} - - render(): Promise { - return new Promise((resolve) => { - resolve(); - }); - } - - destroy() {} - } - - // eslint-disable-next-line @typescript-eslint/no-var-requires - const { BaseVisType } = require('./vis_types/base_vis_type'); // eslint-disable-next-line @typescript-eslint/no-var-requires const { SearchSource } = require('@kbn/data-plugin/common/search/search_source'); // eslint-disable-next-line @typescript-eslint/no-var-requires const stubIndexPattern = require('@kbn/data-plugin/common/stubs'); - const visType = new BaseVisType({ - name: 'pie', - title: 'pie', - icon: 'pie-icon', - visualization: MockVisualizationController, - }); return { - getTypes: () => ({ get: () => visType }), getAggs: () => ({ createAggConfigs: (indexPattern: any, cfg: any) => ({ aggs: cfg.map((aggConfig: any) => ({ ...aggConfig, serialize: () => aggConfig })), @@ -71,7 +52,14 @@ describe('Vis Class', function () { }; beforeEach(async function () { - vis = new Vis('test', stateFixture as any); + vis = new Vis( + new BaseVisType({ + name: 'pie', + title: 'pie', + icon: 'pie-icon', + } as unknown as VisTypeDefinition), + stateFixture as any + ); await vis.setState(stateFixture as any); }); diff --git a/src/platform/plugins/shared/visualizations/public/vis.ts b/src/platform/plugins/shared/visualizations/public/vis.ts index c57de9585132a..0be943870fa45 100644 --- a/src/platform/plugins/shared/visualizations/public/vis.ts +++ b/src/platform/plugins/shared/visualizations/public/vis.ts @@ -60,7 +60,7 @@ const getSearchSource = async (inputSearchSource: ISearchSource, savedSearchId?: type PartialVisState = Assign }>; export class Vis { - public readonly type: BaseVisType; + public type: BaseVisType; public readonly id?: string; public title: string = ''; public description: string = ''; @@ -69,27 +69,13 @@ export class Vis { public readonly uiState: PersistedState; - constructor(visType: string, visState: SerializedVis = {} as any) { - this.type = this.getType(visType); + constructor(visType: BaseVisType, visState: SerializedVis = {} as any) { + this.type = visType; this.params = this.getParams(visState.params); this.uiState = new PersistedState(visState.uiState); this.id = visState.id; } - private getType(visType: string) { - const type = getTypes().get(visType); - if (!type) { - const errorMessage = i18n.translate('visualizations.visualizationTypeInvalidMessage', { - defaultMessage: 'Invalid visualization type "{visType}"', - values: { - visType, - }, - }); - throw new Error(errorMessage); - } - return type; - } - private getParams(params: VisParams) { return defaults({}, cloneDeep(params ?? {}), cloneDeep(this.type.visConfig?.defaults ?? {})); } @@ -109,8 +95,18 @@ export class Vis { let typeChanged = false; if (state.type && this.type.name !== state.type) { - // @ts-ignore - this.type = this.getType(state.type); + const newVisType = await getTypes().get(state.type); + if (!newVisType) { + throw new Error( + i18n.translate('visualizations.visualizationTypeInvalidMessage', { + defaultMessage: 'Invalid visualization type "{visType}"', + values: { + visType: state.type, + }, + }) + ); + } + this.type = newVisType; typeChanged = true; } if (state.title !== undefined) { @@ -174,7 +170,7 @@ export class Vis { clone(): Vis { const { data, ...restOfSerialized } = this.serialize(); - const vis = new Vis(this.type.name, restOfSerialized as any); + const vis = new Vis(this.type, restOfSerialized as any); vis.setState({ ...restOfSerialized, data: {} }); const aggs = this.data.indexPattern ? getAggs().createAggConfigs(this.data.indexPattern, data.aggs) diff --git a/src/platform/plugins/shared/visualizations/public/vis_async.ts b/src/platform/plugins/shared/visualizations/public/vis_async.ts index 1c135ff296d71..4d9d79e7f02f2 100644 --- a/src/platform/plugins/shared/visualizations/public/vis_async.ts +++ b/src/platform/plugins/shared/visualizations/public/vis_async.ts @@ -7,13 +7,27 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ +import { i18n } from '@kbn/i18n'; import type { VisParams } from '@kbn/visualizations-common'; import type { SerializedVis } from './vis'; +import { getTypes } from './services'; export const createVisAsync = async ( - visType: string, + visTypeName: string, visState: SerializedVis = {} as any ) => { + const visType = await getTypes().get(visTypeName); + if (!visType) { + throw new Error( + i18n.translate('visualizations.visualizationTypeInvalidMessage', { + defaultMessage: 'Invalid visualization type "{visType}"', + values: { + visType: visTypeName, + }, + }) + ); + } + const { Vis } = await import('./vis'); const vis = new Vis(visType, visState); diff --git a/src/platform/plugins/shared/visualizations/public/vis_types/types_service.ts b/src/platform/plugins/shared/visualizations/public/vis_types/types_service.ts index 4779280ebfbf2..4580ef02ed29e 100644 --- a/src/platform/plugins/shared/visualizations/public/vis_types/types_service.ts +++ b/src/platform/plugins/shared/visualizations/public/vis_types/types_service.ts @@ -8,6 +8,7 @@ */ import type { VisParams } from '@kbn/visualizations-common'; +import { asyncMap } from '@kbn/std'; import type { VisTypeAlias } from './vis_type_alias_registry'; import { visTypeAliasRegistry } from './vis_type_alias_registry'; import { BaseVisType } from './base_vis_type'; @@ -20,28 +21,49 @@ import type { VisGroups } from './vis_groups_enum'; * @internal */ export class TypesService { - private types: Record> = {}; + private types: Record Promise>> = {}; - private registerVisualization( - visDefinition: BaseVisType + private registerVisualization( + name: string, + getVisDefinition: () => Promise> ) { - if (this.types[visDefinition.name]) { + if (this.types[name]) { throw new Error('type already exists!'); } - this.types[visDefinition.name] = visDefinition; + this.types[name] = async () => { + const config = await getVisDefinition(); + return new BaseVisType(config); + }; } + private getAll = async () => { + return await asyncMap(Object.values(this.types), async (getVisType) => { + return await getVisType(); + }); + }; + public setup() { return { /** - * registers a visualization type + * @deprecated + * + * Use createBaseVisualizationAsync * @param config - visualization type definition */ createBaseVisualization: ( config: VisTypeDefinition ): void => { - const vis = new BaseVisType(config); - this.registerVisualization(vis); + this.registerVisualization(config.name, async () => config); + }, + /** + * registers a visualization type + * @param config - visualization type definition + */ + createBaseVisualizationAsync: ( + name: string, + getVisDefinition: () => Promise> + ): void => { + this.registerVisualization(name, getVisDefinition); }, /** * registers a visualization alias @@ -58,17 +80,15 @@ export class TypesService { * returns specific visualization or undefined if not found * @param {string} visualization - id of visualization to return */ - get: ( + get: async ( visualization: string - ): BaseVisType | undefined => { - return this.types[visualization]; + ): Promise | undefined> => { + return await this.types[visualization]?.(); }, /** * returns all registered visualization types */ - all: (): BaseVisType[] => { - return [...Object.values(this.types)]; - }, + all: this.getAll, /** * returns all registered aliases */ @@ -83,8 +103,8 @@ export class TypesService { * returns all visualizations of specific group * @param {VisGroups} group - group type (aggbased | other | tools) */ - getByGroup: (group: VisGroups) => { - return Object.values(this.types).filter((type) => { + getByGroup: async (group: VisGroups) => { + return (await this.getAll()).filter((type) => { return type.group === group; }); }, diff --git a/src/platform/plugins/shared/visualizations/public/vis_types/use_vis_types.ts b/src/platform/plugins/shared/visualizations/public/vis_types/use_vis_types.ts new file mode 100644 index 0000000000000..cc64a8c8e7b39 --- /dev/null +++ b/src/platform/plugins/shared/visualizations/public/vis_types/use_vis_types.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { useEffect, useState } from 'react'; +import type { BaseVisType } from './base_vis_type'; +import type { TypesStart } from './types_service'; + +export function useVisTypes(visTypesRegistry: TypesStart) { + const [isLoading, setIsLoading] = useState(false); + const [visTypes, setVisTypes] = useState([]); + useEffect(() => { + let canceled = false; + setIsLoading(true); + visTypesRegistry + .all() + .then((types) => { + if (!canceled) { + setIsLoading(false); + setVisTypes(types); + } + }) + .catch((error) => { + // eslint-disable-next-line no-console + console.warn('Unable to load visTypes, error: ', error); + if (!canceled) { + setIsLoading(false); + setVisTypes([]); + } + }); + + return () => { + canceled = true; + }; + }, [visTypesRegistry]); + + return { + isLoading, + visTypes, + }; +} diff --git a/src/platform/plugins/shared/visualizations/public/visualize_app/utils/use/use_saved_vis_instance.test.ts b/src/platform/plugins/shared/visualizations/public/visualize_app/utils/use/use_saved_vis_instance.test.ts index ed0071267ec6e..b4cf064ba8e62 100644 --- a/src/platform/plugins/shared/visualizations/public/visualize_app/utils/use/use_saved_vis_instance.test.ts +++ b/src/platform/plugins/shared/visualizations/public/visualize_app/utils/use/use_saved_vis_instance.test.ts @@ -267,17 +267,17 @@ describe('useSavedVisInstance', () => { result.current.visEditorRef.current = document.createElement('div'); + await waitFor(() => { + expect(result.current.visEditorController).toBeDefined(); + expect(result.current.savedVisInstance).toBeDefined(); + }); + expect(mockGetVisualizationInstance).toHaveBeenCalledWith(mockServices, { indexPattern: '1a2b3c4d', type: 'area', }); - - await waitFor(() => new Promise((resolve) => resolve(null))); - expect(getCreateBreadcrumbs).toHaveBeenCalled(); expect(mockEmbeddableHandlerRender).not.toHaveBeenCalled(); - expect(result.current.visEditorController).toBeDefined(); - expect(result.current.savedVisInstance).toBeDefined(); }); test('should throw error if vis type is invalid', async () => { @@ -288,9 +288,11 @@ describe('useSavedVisInstance', () => { renderHook(() => useSavedVisInstance(mockServices, eventEmitter, true, undefined, undefined)); - expect(mockGetVisualizationInstance).not.toHaveBeenCalled(); - expect(redirectWhenMissing).toHaveBeenCalled(); - expect(toastNotifications.addWarning).toHaveBeenCalled(); + await waitFor(() => { + expect(mockGetVisualizationInstance).not.toHaveBeenCalled(); + expect(redirectWhenMissing).toHaveBeenCalled(); + expect(toastNotifications.addWarning).toHaveBeenCalled(); + }); }); test("should throw error if index pattern or saved search id doesn't exist in search params", async () => { @@ -301,9 +303,11 @@ describe('useSavedVisInstance', () => { renderHook(() => useSavedVisInstance(mockServices, eventEmitter, true, undefined, undefined)); - expect(mockGetVisualizationInstance).not.toHaveBeenCalled(); - expect(redirectWhenMissing).toHaveBeenCalled(); - expect(toastNotifications.addWarning).toHaveBeenCalled(); + await waitFor(() => { + expect(mockGetVisualizationInstance).not.toHaveBeenCalled(); + expect(redirectWhenMissing).toHaveBeenCalled(); + expect(toastNotifications.addWarning).toHaveBeenCalled(); + }); }); }); diff --git a/src/platform/plugins/shared/visualizations/public/visualize_app/utils/use/use_saved_vis_instance.ts b/src/platform/plugins/shared/visualizations/public/visualize_app/utils/use/use_saved_vis_instance.ts index 224864cfe233c..ddee4db989b28 100644 --- a/src/platform/plugins/shared/visualizations/public/visualize_app/utils/use/use_saved_vis_instance.ts +++ b/src/platform/plugins/shared/visualizations/public/visualize_app/utils/use/use_saved_vis_instance.ts @@ -62,9 +62,7 @@ export const useSavedVisInstance = ( let savedVisInstance: SavedVisInstance; if (history.location.pathname === '/create') { const searchParams = parse(history.location.search); - const visType = getTypes() - .all() - .find(({ name }) => name === searchParams.type); + const visType = (await getTypes().all()).find(({ name }) => name === searchParams.type); if (!visType) { throw new Error( diff --git a/src/platform/plugins/shared/visualizations/public/wizard/agg_based_selection/agg_based_selection.test.tsx b/src/platform/plugins/shared/visualizations/public/wizard/agg_based_selection/agg_based_selection.test.tsx index ff5dbd437311e..9ad7cb5f8b823 100644 --- a/src/platform/plugins/shared/visualizations/public/wizard/agg_based_selection/agg_based_selection.test.tsx +++ b/src/platform/plugins/shared/visualizations/public/wizard/agg_based_selection/agg_based_selection.test.tsx @@ -8,11 +8,12 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import type { TypesStart, BaseVisType } from '../../vis_types'; import { VisGroups } from '../../vis_types'; import { AggBasedSelection } from './agg_based_selection'; import type { VisParams } from '@kbn/visualizations-common'; +import { act } from 'react-dom/test-utils'; describe('AggBasedSelection', () => { const defaultVisTypeParams = { @@ -50,13 +51,19 @@ describe('AggBasedSelection', () => { ] as BaseVisType[]; const visTypes: TypesStart = { - get(id: string): BaseVisType { + async get(id: string) { return _visTypes.find((vis) => vis.name === id) as unknown as BaseVisType; }, - all: () => _visTypes, + all: async () => { + return _visTypes as unknown as BaseVisType[]; + }, getAliases: () => [], unRegisterAlias: () => [], - getByGroup: (group: VisGroups) => _visTypes.filter((type) => type.group === group), + getByGroup: async (group: VisGroups) => { + return _visTypes.filter((type) => { + return type.group === group; + }) as unknown as BaseVisType[]; + }, }; beforeAll(() => { @@ -86,7 +93,7 @@ describe('AggBasedSelection', () => { }); describe('filter for visualization types', () => { - it('should render as expected', () => { + it('should render as expected', async () => { const wrapper = mountWithIntl( { onVisTypeSelected={jest.fn()} /> ); + + await act(async () => { + await nextTick(); + }); + wrapper.update(); + const searchBox = wrapper.find('input[data-test-subj="filterVisType"]'); searchBox.simulate('change', { target: { value: 'with' } }); expect(wrapper.find('[data-test-subj="visType-visWithSearch"]').exists()).toBe(true); diff --git a/src/platform/plugins/shared/visualizations/public/wizard/agg_based_selection/agg_based_selection.tsx b/src/platform/plugins/shared/visualizations/public/wizard/agg_based_selection/agg_based_selection.tsx index cfd60f5348bda..980faff16672e 100644 --- a/src/platform/plugins/shared/visualizations/public/wizard/agg_based_selection/agg_based_selection.tsx +++ b/src/platform/plugins/shared/visualizations/public/wizard/agg_based_selection/agg_based_selection.tsx @@ -11,7 +11,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { orderBy } from 'lodash'; import type { ChangeEvent } from 'react'; -import React from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { EuiFieldSearch, @@ -24,17 +24,13 @@ import { EuiModalBody, EuiModalHeader, EuiModalHeaderTitle, + EuiSkeletonRectangle, } from '@elastic/eui'; -import { memoizeLast } from '../../legacy/memoize'; import { VisGroups } from '../../vis_types/vis_groups_enum'; import type { BaseVisType, TypesStart } from '../../vis_types'; import { DialogNavigation } from '../dialog_navigation'; - -interface VisTypeListEntry { - type: BaseVisType; - highlighted: boolean; -} +import { useVisTypes } from '../../vis_types/use_vis_types'; interface AggBasedSelectionProps { openedAsRoot?: boolean; @@ -42,92 +38,38 @@ interface AggBasedSelectionProps { visTypesRegistry: TypesStart; showMainDialog: (flag: boolean) => void; } -interface AggBasedSelectionState { - query: string; -} -class AggBasedSelection extends React.Component { - public state: AggBasedSelectionState = { - query: '', - }; +export function AggBasedSelection(props: AggBasedSelectionProps) { + const [query, setQuery] = useState(''); + const onQueryChange = useCallback((ev: ChangeEvent) => { + setQuery(ev.target.value); + }, []); - private readonly getFilteredVisTypes = memoizeLast(this.filteredVisTypes); + const { isLoading, visTypes } = useVisTypes(props.visTypesRegistry); - public render() { - const { query } = this.state; - const visTypes = this.getFilteredVisTypes(this.props.visTypesRegistry, query); - return ( - <> - - - - - - - {this.props.openedAsRoot ? null : ( - this.props.showMainDialog(true)} /> - )} - - - - - {query && ( - type.highlighted).length, - }} - /> - )} - - - - {visTypes.map(this.renderVisType)} - - - - ); - } - - private filteredVisTypes(visTypes: TypesStart, query: string): VisTypeListEntry[] { - const types = visTypes.getByGroup(VisGroups.AGGBASED).filter((type) => { + const visTypesWithHighlight = useMemo(() => { + const aggVisTypes = visTypes.filter((type) => { // Filter out hidden visualizations and visualizations that are only aggregations based - return !type.disableCreate; + return type.group === VisGroups.AGGBASED && !type.disableCreate; }); - let entries: VisTypeListEntry[]; - if (!query) { - entries = types.map((type) => ({ type, highlighted: false })); - } else { - const q = query.toLowerCase(); - entries = types.map((type) => { - const matchesQuery = - type.name.toLowerCase().includes(q) || - type.title.toLowerCase().includes(q) || - (typeof type.description === 'string' && type.description.toLowerCase().includes(q)); - return { type, highlighted: matchesQuery }; - }); - } + const q = query.toLowerCase(); + const entries = !query + ? aggVisTypes.map((type: any) => ({ type, highlighted: false })) + : aggVisTypes.map((type) => { + const matchesQuery = + type.name.toLowerCase().includes(q) || + type.title.toLowerCase().includes(q) || + (typeof type.description === 'string' && type.description.toLowerCase().includes(q)); + return { type, highlighted: matchesQuery }; + }); return orderBy(entries, ['highlighted', 'type.title'], ['desc', 'asc']); - } + }, [query, visTypes]); - private renderVisType = (visType: VisTypeListEntry) => { - const isDisabled = this.state.query !== '' && !visType.highlighted; - const onClick = () => this.props.onVisTypeSelected(visType.type); + function renderVisType(visType: { type: BaseVisType; highlighted: boolean }) { + const isDisabled = query !== '' && !visType.highlighted; + const onClick = () => props.onVisTypeSelected(visType.type); return ( @@ -154,13 +96,52 @@ class AggBasedSelection extends React.Component ); - }; + } - private onQueryChange = (ev: ChangeEvent) => { - this.setState({ - query: ev.target.value, - }); - }; + return ( + <> + + + + + + + {props.openedAsRoot ? null : props.showMainDialog(true)} />} + + + + <> + + + {query && ( + type.highlighted).length, + }} + /> + )} + + + + {visTypesWithHighlight.map(renderVisType)} + + + + + + ); } - -export { AggBasedSelection }; diff --git a/src/platform/plugins/shared/visualizations/public/wizard/group_selection/group_selection.test.tsx b/src/platform/plugins/shared/visualizations/public/wizard/group_selection/group_selection.test.tsx index 9e78d7fb0c227..7b03f9fcbfef3 100644 --- a/src/platform/plugins/shared/visualizations/public/wizard/group_selection/group_selection.test.tsx +++ b/src/platform/plugins/shared/visualizations/public/wizard/group_selection/group_selection.test.tsx @@ -14,7 +14,7 @@ import { VisGroups } from '../../vis_types'; import type { GroupSelectionProps } from './group_selection'; import { GroupSelection } from './group_selection'; import type { DocLinksStart } from '@kbn/core/public'; -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import { I18nProvider } from '@kbn/i18n-react'; import type { VisParams } from '@kbn/visualizations-common'; @@ -67,15 +67,15 @@ describe('GroupSelection', () => { const visTypesRegistry = (visTypes: BaseVisType[]): TypesStart => { return { - get(id: string): BaseVisType { + async get(id: string) { return visTypes.find((vis) => vis.name === id) as unknown as BaseVisType; }, - all: () => { + all: async () => { return visTypes as unknown as BaseVisType[]; }, getAliases: () => [], unRegisterAlias: () => [], - getByGroup: (group: VisGroups) => { + getByGroup: async (group: VisGroups) => { return visTypes.filter((type) => { return type.group === group; }) as unknown as BaseVisType[]; @@ -144,9 +144,11 @@ describe('GroupSelection', () => { tab: 'legacy', }); - expect(screen.queryByRole('tab', { name: /legacy/i })).toBeInTheDocument(); - expect(screen.queryByRole('tab', { name: /recommended/i })).toBeInTheDocument(); - expect(screen.getByTestId('visType-aggbased')).toHaveTextContent('Aggregation-based'); + await waitFor(() => { + expect(screen.queryByRole('tab', { name: /legacy/i })).toBeInTheDocument(); + expect(screen.queryByRole('tab', { name: /recommended/i })).toBeInTheDocument(); + expect(screen.getByTestId('visType-aggbased')).toHaveTextContent('Aggregation-based'); + }); }); it('should call the showMainDialog if the aggBased group card is clicked', async () => { @@ -164,20 +166,26 @@ describe('GroupSelection', () => { tab: 'legacy', }); + await waitFor(() => { + expect(screen.getByTestId('visType-aggbased')).toHaveTextContent('Aggregation-based'); + }); + await userEvent.click(screen.getByRole('button', { name: /Aggregation-based/i })); expect(showMainDialog).toHaveBeenCalledWith(false); }); - it('should only show promoted visualizations in recommended tab', () => { + it('should only show promoted visualizations in recommended tab', async () => { renderGroupSelectionComponent(); - const cards = screen.getAllByRole('button').map((el) => el.textContent); + await waitFor(() => { + const cards = screen.getAllByRole('button').map((el) => el.textContent); - expect(cards).toEqual([ - 'Vis alias with promotion', - 'Vis Type 1', - 'Vis Type 2', - 'Vis with alias Url', - ]); + expect(cards).toEqual([ + 'Vis alias with promotion', + 'Vis Type 1', + 'Vis Type 2', + 'Vis with alias Url', + ]); + }); }); }); diff --git a/src/platform/plugins/shared/visualizations/public/wizard/group_selection/group_selection.tsx b/src/platform/plugins/shared/visualizations/public/wizard/group_selection/group_selection.tsx index 2d1423334b71e..ca861dde4bdad 100644 --- a/src/platform/plugins/shared/visualizations/public/wizard/group_selection/group_selection.tsx +++ b/src/platform/plugins/shared/visualizations/public/wizard/group_selection/group_selection.tsx @@ -28,6 +28,7 @@ import { EuiTabs, EuiTab, EuiIconTip, + EuiSkeletonText, type UseEuiTheme, euiBreakpoint, } from '@elastic/eui'; @@ -38,6 +39,7 @@ import { useMemoCss } from '@kbn/css-utils/public/use_memo_css'; import type { BaseVisType, TypesStart } from '../../vis_types'; import { VisGroups } from '../../vis_types/vis_groups_enum'; import type { VisTypeAlias } from '../../vis_types/vis_type_alias_registry'; +import { useVisTypes } from '../../vis_types/use_vis_types'; const groupSelectionStyles = { body: css({ @@ -125,13 +127,6 @@ const tabs: Array<{ id: 'recommended' | 'legacy'; label: ReactNode; dataTestSubj }, ]; -const getVisTypesFromGroup = ( - visTypesRegistry: TypesStart, - group: VisGroups -): Array => { - return visTypesRegistry.getByGroup(group).filter(({ disableCreate }) => !disableCreate); -}; - function GroupSelection({ tab = 'recommended', setTab, @@ -140,34 +135,46 @@ function GroupSelection({ }: GroupSelectionProps) { const styles = useMemoCss(groupSelectionStyles); const visualizeGuideLink = props.docLinks.links.visualize.guide; - const promotedVisGroups = useMemo( + const { isLoading, visTypes } = useVisTypes(visTypesRegistry); + const promotedVisTypes = useMemo( () => orderBy( [ ...visTypesRegistry.getAliases(), - ...visTypesRegistry.getByGroup(VisGroups.PROMOTED), + ...visTypes.filter((visType) => visType.group === VisGroups.PROMOTED), ].filter((visDefinition) => { return !visDefinition.disableCreate; }), ['promotion', 'title'], ['asc', 'asc'] ), - [visTypesRegistry] + [visTypes, visTypesRegistry] ); - const aggBasedTypes = getVisTypesFromGroup(visTypesRegistry, VisGroups.AGGBASED); - const legacyTypes = getVisTypesFromGroup(visTypesRegistry, VisGroups.LEGACY); - - const shouldDisplayLegacyTab = legacyTypes.length + aggBasedTypes.length > 0; - - const [tsvbProps] = legacyTypes.map((visType) => ({ - visType: { - ...visType, - icon: visType.name === 'metrics' ? 'visualizeApp' : (visType.icon as string), - }, - onVisTypeSelected: props.onVisTypeSelected, - key: visType.name, - })); + const { PromotedLegacyVisCard, shouldDisplayLegacyTab } = useMemo(() => { + const getVisTypesFromGroup = (group: VisGroups): Array => { + return visTypes.filter((visType) => visType.group === group && !visType.disableCreate); + }; + const aggBasedTypes = getVisTypesFromGroup(VisGroups.AGGBASED); + const legacyTypes = getVisTypesFromGroup(VisGroups.LEGACY); + return { + PromotedLegacyVisCard: + legacyTypes.length >= 1 ? ( + + ) : null, + shouldDisplayLegacyTab: Boolean(legacyTypes.length || aggBasedTypes.length), + }; + }, [visTypes, props.onVisTypeSelected]); return ( <> @@ -180,66 +187,68 @@ function GroupSelection({ - {shouldDisplayLegacyTab && ( -
- - {tabs.map((t) => ( - setTab(t.id)} - key={t.id} - > - {t.label} - - ))} - -
- )} - -
- - {tab === 'recommended' ? ( - - {promotedVisGroups.map((visType) => ( - - ))} - - ) : ( - - {tsvbProps ? : null} - { - { - props.showMainDialog(false); - }} - /> - } - - )} - - -
+ + <> + {shouldDisplayLegacyTab && ( +
+ + {tabs.map((t) => ( + setTab(t.id)} + key={t.id} + > + {t.label} + + ))} + +
+ )} +
+ + {tab === 'recommended' ? ( + + {promotedVisTypes.map((visType) => ( + + ))} + + ) : ( + + {PromotedLegacyVisCard} + { + { + props.showMainDialog(false); + }} + /> + } + + )} + +
+ +
@@ -271,7 +280,7 @@ const ModalFooter = ({ visualizeGuideLink }: { visualizeGuideLink: string }) => ); }; -const VisGroup = ({ visType, onVisTypeSelected, shouldStretch = false }: VisCardProps) => { +const VisTypeCard = ({ visType, onVisTypeSelected, shouldStretch = false }: VisCardProps) => { const onClick = useCallback(() => { onVisTypeSelected(visType); }, [onVisTypeSelected, visType]); diff --git a/src/platform/plugins/shared/visualizations/public/wizard/new_vis_modal.test.tsx b/src/platform/plugins/shared/visualizations/public/wizard/new_vis_modal.test.tsx index bdef5f1c54b4b..a37b2ec77202f 100644 --- a/src/platform/plugins/shared/visualizations/public/wizard/new_vis_modal.test.tsx +++ b/src/platform/plugins/shared/visualizations/public/wizard/new_vis_modal.test.tsx @@ -15,7 +15,7 @@ import NewVisModal from './new_vis_modal'; import type { ApplicationStart, DocLinksStart } from '@kbn/core/public'; import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; import { contentManagementMock } from '@kbn/content-management-plugin/public/mocks'; -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import { I18nProvider } from '@kbn/i18n-react'; import userEvent from '@testing-library/user-event'; import { EuiThemeProvider } from '@elastic/eui'; @@ -67,13 +67,19 @@ describe('NewVisModal', () => { }, ] as BaseVisType[]; const visTypes: TypesStart = { - get(id: string): BaseVisType { + async get(id: string) { return _visTypes.find((vis) => vis.name === id) as unknown as BaseVisType; }, - all: () => _visTypes, + all: async () => { + return _visTypes as unknown as BaseVisType[]; + }, getAliases: () => [], unRegisterAlias: () => [], - getByGroup: (group: VisGroups) => _visTypes.filter((type) => type.group === group), + getByGroup: async (group: VisGroups) => { + return _visTypes.filter((type) => { + return type.group === group; + }) as unknown as BaseVisType[]; + }, }; const addBasePath = (url: string) => `testbasepath${url}`; const settingsGet = jest.fn(); @@ -122,33 +128,43 @@ describe('NewVisModal', () => { it('should show the aggbased group but not the visualization assigned to this group', async () => { renderNewVisModal(); - expect(screen.queryByText('Aggregation-based')).not.toBeInTheDocument(); - expect(screen.queryByText('Vis with search')).not.toBeInTheDocument(); - await userEvent.click(screen.getByRole('tab', { name: /Legacy/i })); - expect(screen.queryByText('Aggregation-based')).toBeInTheDocument(); - expect(screen.queryByText('Vis with search')).not.toBeInTheDocument(); + waitFor(async () => { + expect(screen.queryByText('Aggregation-based')).not.toBeInTheDocument(); + expect(screen.queryByText('Vis with search')).not.toBeInTheDocument(); + await userEvent.click(screen.getByRole('tab', { name: /Legacy/i })); + expect(screen.queryByText('Aggregation-based')).toBeInTheDocument(); + expect(screen.queryByText('Vis with search')).not.toBeInTheDocument(); + }); }); it('should display the visualizations of the other group', () => { renderNewVisModal(); - expect(screen.queryByText('Vis Type 2')).toBeInTheDocument(); + waitFor(async () => { + expect(screen.queryByText('Vis Type 2')).toBeInTheDocument(); + }); }); describe('open editor', () => { it('should open the editor for visualizations without search', async () => { renderNewVisModal(); - await userEvent.click(screen.getByText('Vis Type 1')); - expect(window.location.assign).toBeCalledWith('testbasepath/app/visualize#/create?type=vis'); + waitFor(async () => { + await userEvent.click(screen.getByText('Vis Type 1')); + expect(window.location.assign).toBeCalledWith( + 'testbasepath/app/visualize#/create?type=vis' + ); + }); }); it('passes through editor params to the editor URL', async () => { renderNewVisModal({ editorParams: ['foo=true', 'bar=42'], }); - await userEvent.click(screen.getByText('Vis Type 1')); - expect(window.location.assign).toBeCalledWith( - 'testbasepath/app/visualize#/create?type=vis&foo=true&bar=42' - ); + waitFor(async () => { + await userEvent.click(screen.getByText('Vis Type 1')); + expect(window.location.assign).toBeCalledWith( + 'testbasepath/app/visualize#/create?type=vis&foo=true&bar=42' + ); + }); }); it('closes and redirects properly if visualization with alias.path and originatingApp in props', async () => { @@ -162,12 +178,14 @@ describe('NewVisModal', () => { originatingApp: 'coolJestTestApp', stateTransfer, }); - await userEvent.click(screen.getByText('Vis with alias Url')); - expect(stateTransfer.navigateToEditor).toBeCalledWith('otherApp', { - path: '#/aliasUrl', - state: { originatingApp: 'coolJestTestApp' }, + waitFor(async () => { + await userEvent.click(screen.getByText('Vis with alias Url')); + expect(stateTransfer.navigateToEditor).toBeCalledWith('otherApp', { + path: '#/aliasUrl', + state: { originatingApp: 'coolJestTestApp' }, + }); + expect(onClose).toHaveBeenCalled(); }); - expect(onClose).toHaveBeenCalled(); }); it('closes and redirects properly if visualization with aliasApp and without originatingApp in props', async () => { @@ -179,19 +197,23 @@ describe('NewVisModal', () => { onClose, application: { navigateToApp } as unknown as ApplicationStart, }); - await userEvent.click(screen.getByText('Vis with alias Url')); - expect(navigateToApp).toBeCalledWith('otherApp', { path: '#/aliasUrl' }); - expect(onClose).toHaveBeenCalled(); + waitFor(async () => { + await userEvent.click(screen.getByText('Vis with alias Url')); + expect(navigateToApp).toBeCalledWith('otherApp', { path: '#/aliasUrl' }); + expect(onClose).toHaveBeenCalled(); + }); }); }); describe('aggBased visualizations', () => { it('should render as expected', async () => { renderNewVisModal(); - await userEvent.click(screen.getByRole('tab', { name: /Legacy/i })); - expect(screen.queryByText('Aggregation-based')).toBeInTheDocument(); - await userEvent.click(screen.getByText('Aggregation-based')); - expect(screen.queryByText('Vis with search')).toBeInTheDocument(); + waitFor(async () => { + await userEvent.click(screen.getByRole('tab', { name: /Legacy/i })); + expect(screen.queryByText('Aggregation-based')).toBeInTheDocument(); + await userEvent.click(screen.getByText('Aggregation-based')); + expect(screen.queryByText('Vis with search')).toBeInTheDocument(); + }); }); }); }); diff --git a/src/platform/plugins/shared/visualizations/tsconfig.json b/src/platform/plugins/shared/visualizations/tsconfig.json index a636ae4bd22f3..b3bc4ec880561 100644 --- a/src/platform/plugins/shared/visualizations/tsconfig.json +++ b/src/platform/plugins/shared/visualizations/tsconfig.json @@ -80,7 +80,8 @@ "@kbn/visualization-listing-components", "@kbn/cps", "@kbn/kql", - "@kbn/cps-utils" + "@kbn/cps-utils", + "@kbn/std" ], "exclude": ["target/**/*"] } diff --git a/x-pack/platform/plugins/shared/maps/public/legacy_visualizations/async_module.ts b/x-pack/platform/plugins/shared/maps/public/legacy_visualizations/async_module.ts new file mode 100644 index 0000000000000..e2be0a40b3d1d --- /dev/null +++ b/x-pack/platform/plugins/shared/maps/public/legacy_visualizations/async_module.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { regionMapVisType } from './region_map/region_map_vis_type'; +export { createRegionMapFn } from './region_map/region_map_fn'; +export { regionMapRenderer } from './region_map/region_map_renderer'; +export { tileMapVisType } from './tile_map/tile_map_vis_type'; +export { createTileMapFn } from './tile_map/tile_map_fn'; +export { tileMapRenderer } from './tile_map/tile_map_renderer'; +export { GEOHASH_GRID, getGeoHashBucketAgg } from './tile_map/geo_hash'; diff --git a/x-pack/platform/plugins/shared/maps/public/legacy_visualizations/index.ts b/x-pack/platform/plugins/shared/maps/public/legacy_visualizations/index.ts deleted file mode 100644 index 177cc608a975c..0000000000000 --- a/x-pack/platform/plugins/shared/maps/public/legacy_visualizations/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export { GEOHASH_GRID, getGeoHashBucketAgg } from './tile_map'; -export { createRegionMapFn, regionMapRenderer, regionMapVisType } from './region_map'; -export { createTileMapFn, tileMapRenderer, tileMapVisType } from './tile_map'; diff --git a/x-pack/platform/plugins/shared/maps/public/legacy_visualizations/region_map/index.ts b/x-pack/platform/plugins/shared/maps/public/legacy_visualizations/region_map/index.ts deleted file mode 100644 index cda57b1088793..0000000000000 --- a/x-pack/platform/plugins/shared/maps/public/legacy_visualizations/region_map/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export { regionMapVisType } from './region_map_vis_type'; -export { createRegionMapFn } from './region_map_fn'; -export { regionMapRenderer } from './region_map_renderer'; diff --git a/x-pack/platform/plugins/shared/maps/public/legacy_visualizations/tile_map/index.ts b/x-pack/platform/plugins/shared/maps/public/legacy_visualizations/tile_map/index.ts deleted file mode 100644 index 78fcd0bd8d17e..0000000000000 --- a/x-pack/platform/plugins/shared/maps/public/legacy_visualizations/tile_map/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export { GEOHASH_GRID, getGeoHashBucketAgg } from './geo_hash'; -export { tileMapVisType } from './tile_map_vis_type'; -export { createTileMapFn } from './tile_map_fn'; -export { tileMapRenderer } from './tile_map_renderer'; diff --git a/x-pack/platform/plugins/shared/maps/public/plugin.ts b/x-pack/platform/plugins/shared/maps/public/plugin.ts index c8d4f2eaaee6e..2648dc4063545 100644 --- a/x-pack/platform/plugins/shared/maps/public/plugin.ts +++ b/x-pack/platform/plugins/shared/maps/public/plugin.ts @@ -45,16 +45,6 @@ import type { ServerlessPluginStart } from '@kbn/serverless/public'; import type { CPSPluginStart } from '@kbn/cps/public'; import type { KqlPluginStart } from '@kbn/kql/public'; -import { - createRegionMapFn, - GEOHASH_GRID, - getGeoHashBucketAgg, - regionMapRenderer, - regionMapVisType, - createTileMapFn, - tileMapRenderer, - tileMapVisType, -} from './legacy_visualizations'; import { MapsAppLocatorDefinition } from './locators/map_locator/locator_definition'; import { MapsAppTileMapLocatorDefinition } from './locators/tile_map_locator/locator_definition'; import { MapsAppRegionMapLocatorDefinition } from './locators/region_map_locator/locator_definition'; @@ -82,6 +72,8 @@ import { CONTENT_ID, LATEST_VERSION } from '../common/content_management'; import { setupMapEmbeddable } from './react_embeddable/setup_map_embeddable'; import { MapRendererLazy } from './react_embeddable/map_renderer/map_renderer_lazy'; import { registerUiActions } from './trigger_actions/register_ui_actions'; +import { REGION_MAP_VIS_TYPE } from './legacy_visualizations/region_map/types'; +import { TILE_MAP_VIS_TYPE } from './legacy_visualizations/tile_map/types'; export interface MapsPluginSetupDependencies { cloud?: CloudSetup; @@ -122,6 +114,7 @@ export interface MapsPluginStartDependencies { screenshotMode?: ScreenshotModePluginSetup; usageCollection?: UsageCollectionSetup; serverless?: ServerlessPluginStart; + expressions: ReturnType; } /** @@ -225,13 +218,33 @@ export class MapsPlugin setupLensChoroplethChart(core, plugins.expressions, plugins.lens); // register wrapper around legacy tile_map and region_map visualizations - plugins.data.search.aggs.types.registerLegacy(GEOHASH_GRID, getGeoHashBucketAgg); - plugins.expressions.registerFunction(createRegionMapFn); - plugins.expressions.registerRenderer(regionMapRenderer); - plugins.visualizations.createBaseVisualization(regionMapVisType); - plugins.expressions.registerFunction(createTileMapFn); - plugins.expressions.registerRenderer(tileMapRenderer); - plugins.visualizations.createBaseVisualization(tileMapVisType); + plugins.visualizations.createBaseVisualizationAsync(REGION_MAP_VIS_TYPE, async () => { + const [[, startPlugins], { regionMapVisType, createRegionMapFn, regionMapRenderer }] = + await Promise.all([ + core.getStartServices(), + import('./legacy_visualizations/async_module'), + ]); + if (!startPlugins.expressions.getFunction('regionmap')) { + plugins.expressions.registerFunction(createRegionMapFn); + plugins.expressions.registerRenderer(regionMapRenderer); + } + return regionMapVisType; + }); + plugins.visualizations.createBaseVisualizationAsync(TILE_MAP_VIS_TYPE, async () => { + const [ + [, startPlugins], + { tileMapVisType, createTileMapFn, tileMapRenderer, GEOHASH_GRID, getGeoHashBucketAgg }, + ] = await Promise.all([ + core.getStartServices(), + import('./legacy_visualizations/async_module'), + ]); + if (!startPlugins.expressions.getFunction('tilemap')) { + plugins.data.search.aggs.types.registerLegacy(GEOHASH_GRID, getGeoHashBucketAgg); + plugins.expressions.registerFunction(createTileMapFn); + plugins.expressions.registerRenderer(tileMapRenderer); + } + return tileMapVisType; + }); setIsCloudEnabled(!!plugins.cloud?.isCloudEnabled);