mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[embeddable] remove embeddable factory methods from setup and start API (#204797)
Part of embeddable rebuild cleanup. PR removes legacy embeddable factory registration APIs. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
b80980694a
commit
3b61e7bea7
38 changed files with 173 additions and 1083 deletions
|
@ -10,8 +10,7 @@
|
|||
import { useCallback, useMemo, useRef } from 'react';
|
||||
import { AsyncSubject, defer, from, lastValueFrom, map, type Subscription } from 'rxjs';
|
||||
|
||||
import type { IconType } from '@elastic/eui';
|
||||
import { COMMON_EMBEDDABLE_GROUPING, EmbeddableFactory } from '@kbn/embeddable-plugin/public';
|
||||
import { COMMON_EMBEDDABLE_GROUPING } from '@kbn/embeddable-plugin/public';
|
||||
import { PresentationContainer } from '@kbn/presentation-containers';
|
||||
import { ADD_PANEL_TRIGGER } from '@kbn/ui-actions-plugin/public';
|
||||
import { VisGroups, type BaseVisType, type VisTypeAlias } from '@kbn/visualizations-plugin/public';
|
||||
|
@ -28,14 +27,6 @@ interface UseGetDashboardPanelsArgs {
|
|||
createNewVisType: (visType: BaseVisType | VisTypeAlias) => () => void;
|
||||
}
|
||||
|
||||
export interface FactoryGroup {
|
||||
id: string;
|
||||
appName: string;
|
||||
icon?: IconType;
|
||||
factories: EmbeddableFactory[];
|
||||
order: number;
|
||||
}
|
||||
|
||||
const sortGroupPanelsByOrder = <T extends { order: number }>(panelGroups: T[]): T[] => {
|
||||
return panelGroups.sort(
|
||||
// larger number sorted to the top
|
||||
|
|
|
@ -13,13 +13,8 @@ import { buildMockDashboardApi } from '../../mocks';
|
|||
import { EditorMenu } from './editor_menu';
|
||||
|
||||
import { DashboardContext } from '../../dashboard_api/use_dashboard_api';
|
||||
import {
|
||||
embeddableService,
|
||||
uiActionsService,
|
||||
visualizationsService,
|
||||
} from '../../services/kibana_services';
|
||||
import { uiActionsService, visualizationsService } from '../../services/kibana_services';
|
||||
|
||||
jest.spyOn(embeddableService, 'getEmbeddableFactories').mockReturnValue(new Map().values());
|
||||
jest.spyOn(uiActionsService, 'getTriggerCompatibleActions').mockResolvedValue([]);
|
||||
jest.spyOn(visualizationsService, 'getByGroup').mockReturnValue([]);
|
||||
jest.spyOn(visualizationsService, 'getAliases').mockReturnValue([]);
|
||||
|
|
|
@ -13,7 +13,7 @@ import type { ContentManagementPublicStart } from '@kbn/content-management-plugi
|
|||
import type { CoreStart } from '@kbn/core/public';
|
||||
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public';
|
||||
import type { EmbeddableStart } from '@kbn/embeddable-plugin/public/plugin';
|
||||
import type { EmbeddableStart } from '@kbn/embeddable-plugin/public';
|
||||
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public/plugin';
|
||||
import type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public';
|
||||
import type { NoDataPagePluginStart } from '@kbn/no-data-page-plugin/public';
|
||||
|
|
|
@ -14,7 +14,7 @@ import { extractBaseEmbeddableInput } from './migrate_base_input';
|
|||
export const getExtractFunction = (embeddables: CommonEmbeddableStartContract) => {
|
||||
return (state: EmbeddableStateWithType) => {
|
||||
const enhancements = state.enhancements || {};
|
||||
const factory = embeddables.getEmbeddableFactory(state.type);
|
||||
const factory = embeddables.getEmbeddableFactory?.(state.type);
|
||||
|
||||
const baseResponse = extractBaseEmbeddableInput(state);
|
||||
let updatedInput = baseResponse.state;
|
||||
|
|
|
@ -15,7 +15,7 @@ import { injectBaseEmbeddableInput } from './migrate_base_input';
|
|||
export const getInjectFunction = (embeddables: CommonEmbeddableStartContract) => {
|
||||
return (state: EmbeddableStateWithType, references: SavedObjectReference[]) => {
|
||||
const enhancements = state.enhancements || {};
|
||||
const factory = embeddables.getEmbeddableFactory(state.type);
|
||||
const factory = embeddables.getEmbeddableFactory?.(state.type);
|
||||
|
||||
let updatedInput = injectBaseEmbeddableInput(state, references);
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ export type MigrateFunction = (state: SerializableRecord, version: string) => Se
|
|||
export const getMigrateFunction = (embeddables: CommonEmbeddableStartContract) => {
|
||||
const migrateFn: MigrateFunction = (state: SerializableRecord, version: string) => {
|
||||
const enhancements = (state.enhancements as SerializableRecord) || {};
|
||||
const factory = embeddables.getEmbeddableFactory(state.type as string);
|
||||
const factory = embeddables.getEmbeddableFactory?.(state.type as string);
|
||||
|
||||
let updatedInput = baseEmbeddableMigrations[version]
|
||||
? baseEmbeddableMigrations[version](state)
|
||||
|
|
|
@ -17,7 +17,7 @@ export const getTelemetryFunction = (embeddables: CommonEmbeddableStartContract)
|
|||
telemetryData: Record<string, string | number | boolean> = {}
|
||||
) => {
|
||||
const enhancements = state.enhancements || {};
|
||||
const factory = embeddables.getEmbeddableFactory(state.type);
|
||||
const factory = embeddables.getEmbeddableFactory?.(state.type);
|
||||
|
||||
let outputTelemetryData = telemetryBaseEmbeddableInput(state, telemetryData);
|
||||
if (factory) {
|
||||
|
|
|
@ -97,7 +97,7 @@ export interface EmbeddableRegistryDefinition<
|
|||
export type EmbeddablePersistableStateService = PersistableStateService<EmbeddableStateWithType>;
|
||||
|
||||
export interface CommonEmbeddableStartContract {
|
||||
getEmbeddableFactory: (
|
||||
getEmbeddableFactory?: (
|
||||
embeddableFactoryId: string
|
||||
) => PersistableState & { isContainerType: boolean };
|
||||
getEnhancement: (enhancementId: string) => PersistableState;
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`embeddable factory migrateToLatest returns list of all migrations 1`] = `
|
||||
Object {
|
||||
"7.11.0": [Function],
|
||||
"7.12.0": [Function],
|
||||
}
|
||||
`;
|
|
@ -27,7 +27,6 @@ import {
|
|||
contentManagement,
|
||||
usageCollection,
|
||||
} from '../kibana_services';
|
||||
import { EmbeddableFactoryNotFoundError } from '../lib';
|
||||
import { getAddFromLibraryType, useAddFromLibraryTypes } from './registry';
|
||||
|
||||
const runAddTelemetry = (
|
||||
|
@ -61,7 +60,12 @@ export const AddFromLibraryFlyout = ({
|
|||
) => {
|
||||
const libraryType = getAddFromLibraryType(type);
|
||||
if (!libraryType) {
|
||||
core.notifications.toasts.addWarning(new EmbeddableFactoryNotFoundError(type).message);
|
||||
core.notifications.toasts.addWarning(
|
||||
i18n.translate('embeddableApi.addPanel.typeNotFound', {
|
||||
defaultMessage: 'Unable to load type: {type}',
|
||||
values: { type },
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
51
src/plugins/embeddable/public/enhancements/registry.ts
Normal file
51
src/plugins/embeddable/public/enhancements/registry.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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 { identity } from 'lodash';
|
||||
import { SerializableRecord } from '@kbn/utility-types';
|
||||
import { EnhancementRegistryDefinition, EnhancementRegistryItem } from './types';
|
||||
|
||||
export class EnhancementsRegistry {
|
||||
private registry: Map<string, EnhancementRegistryItem> = new Map();
|
||||
|
||||
public registerEnhancement = (enhancement: EnhancementRegistryDefinition) => {
|
||||
if (this.registry.has(enhancement.id)) {
|
||||
throw new Error(`enhancement with id ${enhancement.id} already exists in the registry`);
|
||||
}
|
||||
this.registry.set(enhancement.id, {
|
||||
id: enhancement.id,
|
||||
telemetry: enhancement.telemetry || ((state, stats) => stats),
|
||||
inject: enhancement.inject || identity,
|
||||
extract:
|
||||
enhancement.extract ||
|
||||
((state: SerializableRecord) => {
|
||||
return { state, references: [] };
|
||||
}),
|
||||
migrations: enhancement.migrations || {},
|
||||
});
|
||||
};
|
||||
|
||||
public getEnhancements = (): EnhancementRegistryItem[] => {
|
||||
return Array.from(this.registry.values());
|
||||
};
|
||||
|
||||
public getEnhancement = (id: string): EnhancementRegistryItem => {
|
||||
return (
|
||||
this.registry.get(id) || {
|
||||
id: 'unknown',
|
||||
telemetry: (state, stats) => stats,
|
||||
inject: identity,
|
||||
extract: (state: SerializableRecord) => {
|
||||
return { state, references: [] };
|
||||
},
|
||||
migrations: {},
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
21
src/plugins/embeddable/public/enhancements/types.ts
Normal file
21
src/plugins/embeddable/public/enhancements/types.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 type { SerializableRecord } from '@kbn/utility-types';
|
||||
import { PersistableState, PersistableStateDefinition } from '@kbn/kibana-utils-plugin/common';
|
||||
|
||||
export interface EnhancementRegistryDefinition<P extends SerializableRecord = SerializableRecord>
|
||||
extends PersistableStateDefinition<P> {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface EnhancementRegistryItem<P extends SerializableRecord = SerializableRecord>
|
||||
extends PersistableState<P> {
|
||||
id: string;
|
||||
}
|
|
@ -17,13 +17,10 @@ export {
|
|||
CELL_VALUE_TRIGGER,
|
||||
contextMenuTrigger,
|
||||
CONTEXT_MENU_TRIGGER,
|
||||
defaultEmbeddableFactoryProvider,
|
||||
Embeddable,
|
||||
EmbeddableFactoryNotFoundError,
|
||||
EmbeddableStateTransfer,
|
||||
ErrorEmbeddable,
|
||||
isContextMenuTriggerContext,
|
||||
isExplicitInputWithAttributes,
|
||||
isMultiValueClickTriggerContext,
|
||||
isRangeSelectTriggerContext,
|
||||
isRowClickTriggerContext,
|
||||
|
@ -37,7 +34,6 @@ export {
|
|||
PANEL_BADGE_TRIGGER,
|
||||
PANEL_HOVER_TRIGGER,
|
||||
PANEL_NOTIFICATION_TRIGGER,
|
||||
runEmbeddableFactoryMigrations,
|
||||
SELECT_RANGE_TRIGGER,
|
||||
VALUE_CLICK_TRIGGER,
|
||||
ViewMode,
|
||||
|
@ -47,26 +43,17 @@ export type {
|
|||
ChartActionContext,
|
||||
EmbeddableContext,
|
||||
EmbeddableEditorState,
|
||||
EmbeddableFactory,
|
||||
EmbeddableFactoryDefinition,
|
||||
EmbeddableInput,
|
||||
EmbeddableInstanceConfiguration,
|
||||
EmbeddableOutput,
|
||||
EmbeddablePackageState,
|
||||
IEmbeddable,
|
||||
MultiValueClickContext,
|
||||
OutputSpec,
|
||||
PropertySpec,
|
||||
RangeSelectContext,
|
||||
ValueClickContext,
|
||||
} from './lib';
|
||||
export type {
|
||||
EmbeddableSetup,
|
||||
EmbeddableSetupDependencies,
|
||||
EmbeddableStart,
|
||||
EmbeddableStartDependencies,
|
||||
} from './plugin';
|
||||
export type { EnhancementRegistryDefinition } from './types';
|
||||
export type { EmbeddableSetup, EmbeddableStart } from './types';
|
||||
export type { EnhancementRegistryDefinition } from './enhancements/types';
|
||||
|
||||
export {
|
||||
ReactEmbeddableRenderer,
|
||||
|
|
|
@ -11,7 +11,7 @@ import { BehaviorSubject } from 'rxjs';
|
|||
|
||||
import { CoreStart } from '@kbn/core/public';
|
||||
|
||||
import { EmbeddableStart, EmbeddableStartDependencies } from '.';
|
||||
import { EmbeddableStart, EmbeddableStartDependencies } from './types';
|
||||
|
||||
export let core: CoreStart;
|
||||
export let embeddableStart: EmbeddableStart;
|
||||
|
|
|
@ -1,65 +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 { SavedObjectAttributes } from '@kbn/core/public';
|
||||
import type { FinderAttributes } from '@kbn/saved-objects-finder-plugin/common';
|
||||
import { EmbeddableFactory } from './embeddable_factory';
|
||||
import { EmbeddableStateWithType } from '../../../common/types';
|
||||
import { EmbeddableFactoryDefinition } from './embeddable_factory_definition';
|
||||
import { EmbeddableInput, EmbeddableOutput, IEmbeddable } from './i_embeddable';
|
||||
import { runEmbeddableFactoryMigrations } from '../factory_migrations/run_factory_migrations';
|
||||
|
||||
export const defaultEmbeddableFactoryProvider = <
|
||||
I extends EmbeddableInput = EmbeddableInput,
|
||||
O extends EmbeddableOutput = EmbeddableOutput,
|
||||
E extends IEmbeddable<I, O> = IEmbeddable<I, O>,
|
||||
T extends FinderAttributes = SavedObjectAttributes
|
||||
>(
|
||||
def: EmbeddableFactoryDefinition<I, O, E, T>
|
||||
): EmbeddableFactory<I, O, E, T> => {
|
||||
if (def.migrations && !def.latestVersion) {
|
||||
throw new Error(
|
||||
'To run clientside Embeddable migrations a latest version key is required on the factory'
|
||||
);
|
||||
}
|
||||
|
||||
const factory: EmbeddableFactory<I, O, E, T> = {
|
||||
...def,
|
||||
latestVersion: def.latestVersion,
|
||||
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
|
||||
? def.createFromSavedObject.bind(def)
|
||||
: (savedObjectId: string, input: Partial<I>, parent?: unknown) => {
|
||||
throw new Error(`Creation from saved object not supported by type ${def.type}`);
|
||||
},
|
||||
create: (...args) => {
|
||||
const [initialInput, ...otherArgs] = args;
|
||||
const { input } = runEmbeddableFactoryMigrations(initialInput, def);
|
||||
const createdEmbeddable = def.create.bind(def)(input as I, ...otherArgs);
|
||||
return createdEmbeddable;
|
||||
},
|
||||
type: def.type,
|
||||
isEditable: def.isEditable.bind(def),
|
||||
getDisplayName: def.getDisplayName.bind(def),
|
||||
getDescription: def.getDescription ? def.getDescription.bind(def) : () => '',
|
||||
getIconType: def.getIconType ? def.getIconType.bind(def) : () => 'empty',
|
||||
savedObjectMetaData: def.savedObjectMetaData,
|
||||
telemetry: def.telemetry || ((state, stats) => stats),
|
||||
inject: def.inject || ((state: EmbeddableStateWithType) => state),
|
||||
extract: def.extract || ((state: EmbeddableStateWithType) => ({ state, references: [] })),
|
||||
migrations: def.migrations || {},
|
||||
grouping: def.grouping,
|
||||
};
|
||||
return factory;
|
||||
};
|
|
@ -1,153 +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 type { SavedObjectMetaData } from '@kbn/saved-objects-finder-plugin/public';
|
||||
import { PersistableState } from '@kbn/kibana-utils-plugin/common';
|
||||
import type { FinderAttributes } from '@kbn/saved-objects-finder-plugin/common';
|
||||
import { UiActionsPresentableGrouping } from '@kbn/ui-actions-plugin/public';
|
||||
import { EmbeddableInput, EmbeddableOutput, IEmbeddable } from './i_embeddable';
|
||||
import { ErrorEmbeddable } from './error_embeddable';
|
||||
import { PropertySpec } from '../types';
|
||||
import { EmbeddableStateWithType } from '../../../common/types';
|
||||
|
||||
export interface EmbeddableInstanceConfiguration {
|
||||
id: string;
|
||||
savedObjectId?: string;
|
||||
}
|
||||
|
||||
export interface OutputSpec {
|
||||
[key: string]: PropertySpec;
|
||||
}
|
||||
|
||||
export interface ExplicitInputWithAttributes {
|
||||
newInput: Partial<EmbeddableInput>;
|
||||
attributes?: unknown;
|
||||
}
|
||||
|
||||
export const isExplicitInputWithAttributes = (
|
||||
value: ExplicitInputWithAttributes | Partial<EmbeddableInput>
|
||||
): value is ExplicitInputWithAttributes => {
|
||||
return Boolean((value as ExplicitInputWithAttributes).newInput);
|
||||
};
|
||||
|
||||
/**
|
||||
* EmbeddableFactories create and initialize an embeddable instance
|
||||
*/
|
||||
export interface EmbeddableFactory<
|
||||
TEmbeddableInput extends EmbeddableInput = EmbeddableInput,
|
||||
TEmbeddableOutput extends EmbeddableOutput = EmbeddableOutput,
|
||||
TEmbeddable extends IEmbeddable<TEmbeddableInput, TEmbeddableOutput> = IEmbeddable<
|
||||
TEmbeddableInput,
|
||||
TEmbeddableOutput
|
||||
>,
|
||||
TSavedObjectAttributes extends FinderAttributes = FinderAttributes
|
||||
> extends PersistableState<EmbeddableStateWithType> {
|
||||
/**
|
||||
* The version of this Embeddable factory. This will be used in the client side migration system
|
||||
* to ensure that input from any source is compatible with the latest version of this embeddable.
|
||||
* If the latest version is not defined, all clientside migrations will be skipped. If migrations
|
||||
* are added to this factory but a latestVersion is not set, an error will be thrown on server start
|
||||
*/
|
||||
readonly latestVersion?: string;
|
||||
|
||||
// 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.
|
||||
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<boolean>;
|
||||
|
||||
readonly savedObjectMetaData?: SavedObjectMetaData<TSavedObjectAttributes>;
|
||||
|
||||
/**
|
||||
* Indicates the grouping this factory should appear in a sub-menu. Example, this is used for grouping
|
||||
* options in the editors menu in Dashboard for creating new embeddables
|
||||
*/
|
||||
readonly grouping?: UiActionsPresentableGrouping;
|
||||
|
||||
/**
|
||||
* True if is this factory create embeddables that are Containers. Used in the add panel to
|
||||
* conditionally show whether these can be added to another container. It's just not
|
||||
* supported right now, but once nested containers are officially supported we can probably get
|
||||
* rid of this interface.
|
||||
*/
|
||||
readonly isContainerType: boolean;
|
||||
|
||||
/**
|
||||
* Returns a display name for this type of embeddable. Used in "Create new... " options
|
||||
* in the add panel for containers.
|
||||
*/
|
||||
getDisplayName(): string;
|
||||
|
||||
/**
|
||||
* Returns an EUI Icon type to be displayed in a menu.
|
||||
*/
|
||||
getIconType(): string;
|
||||
|
||||
/**
|
||||
* Returns a description about the embeddable.
|
||||
*/
|
||||
getDescription(): 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.
|
||||
*/
|
||||
canCreateNew(): boolean;
|
||||
|
||||
/**
|
||||
* Can be used to get the default input, to be passed in to during the creation process. Default
|
||||
* input will not be stored in a parent container, so all inherited input from a container will trump
|
||||
* default input parameters.
|
||||
* @param partial
|
||||
*/
|
||||
getDefaultInput(partial: Partial<TEmbeddableInput>): Partial<TEmbeddableInput>;
|
||||
|
||||
/**
|
||||
* 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 all inherited
|
||||
* input passed down from the parent container.
|
||||
*
|
||||
* Can be used to edit an embeddable by re-requesting explicit input. Initial input can be provided to allow the editor to show the current state.
|
||||
*
|
||||
* If saved object information is needed for creation use-cases, getExplicitInput can also return an unknown typed attributes object which will be passed
|
||||
* into the container's addNewEmbeddable function.
|
||||
*/
|
||||
getExplicitInput(
|
||||
initialInput?: Partial<TEmbeddableInput>,
|
||||
parent?: unknown
|
||||
): Promise<Partial<TEmbeddableInput> | ExplicitInputWithAttributes>;
|
||||
|
||||
/**
|
||||
* Creates a new embeddable instance based off the saved object id.
|
||||
* @param savedObjectId
|
||||
* @param input - some input may come from a parent, or user, if it's not stored with the saved object. For example, the time
|
||||
* range of the parent container.
|
||||
* @param parent
|
||||
*/
|
||||
createFromSavedObject(
|
||||
savedObjectId: string,
|
||||
input: Partial<TEmbeddableInput>,
|
||||
parent?: unknown
|
||||
): Promise<TEmbeddable | ErrorEmbeddable>;
|
||||
|
||||
/**
|
||||
* Creates an Embeddable instance, running the inital input through all registered migrations. Resolves to undefined if a new Embeddable
|
||||
* cannot be directly created and the user will instead be redirected elsewhere.
|
||||
*/
|
||||
create(
|
||||
initialInput: TEmbeddableInput,
|
||||
parent?: unknown
|
||||
): Promise<TEmbeddable | ErrorEmbeddable | undefined>;
|
||||
|
||||
order?: number;
|
||||
}
|
|
@ -1,44 +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 type { FinderAttributes } from '@kbn/saved-objects-finder-plugin/common';
|
||||
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<I, O> = IEmbeddable<I, O>,
|
||||
T extends FinderAttributes = FinderAttributes
|
||||
> =
|
||||
// Required parameters
|
||||
Pick<
|
||||
EmbeddableFactory<I, O, E, T>,
|
||||
'create' | 'type' | 'latestVersion' | 'isEditable' | 'getDisplayName'
|
||||
> &
|
||||
// Optional parameters
|
||||
Partial<
|
||||
Pick<
|
||||
EmbeddableFactory<I, O, E, T>,
|
||||
| 'createFromSavedObject'
|
||||
| 'isContainerType'
|
||||
| 'getExplicitInput'
|
||||
| 'savedObjectMetaData'
|
||||
| 'canCreateNew'
|
||||
| 'getDefaultInput'
|
||||
| 'telemetry'
|
||||
| 'extract'
|
||||
| 'inject'
|
||||
| 'migrations'
|
||||
| 'grouping'
|
||||
| 'getIconType'
|
||||
| 'getDescription'
|
||||
>
|
||||
>;
|
|
@ -8,10 +8,7 @@
|
|||
*/
|
||||
|
||||
export * from '../../../common/lib/saved_object_embeddable';
|
||||
export * from './default_embeddable_factory_provider';
|
||||
export { Embeddable } from './embeddable';
|
||||
export { EmbeddableErrorHandler } from './embeddable_error_handler';
|
||||
export * from './embeddable_factory';
|
||||
export * from './embeddable_factory_definition';
|
||||
export { ErrorEmbeddable } from './error_embeddable';
|
||||
export type { EmbeddableInput, EmbeddableOutput, IEmbeddable } from './i_embeddable';
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
*/
|
||||
|
||||
import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
import { PanelNotFoundError, EmbeddableFactoryNotFoundError } from './errors';
|
||||
import { PanelNotFoundError } from './errors';
|
||||
|
||||
describe('IncompatibleActionError', () => {
|
||||
test('is instance of error', () => {
|
||||
|
@ -33,15 +33,3 @@ describe('PanelNotFoundError', () => {
|
|||
expect(error.code).toBe('PANEL_NOT_FOUND');
|
||||
});
|
||||
});
|
||||
|
||||
describe('EmbeddableFactoryNotFoundError', () => {
|
||||
test('is instance of error', () => {
|
||||
const error = new EmbeddableFactoryNotFoundError('type1');
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
});
|
||||
|
||||
test('has EMBEDDABLE_FACTORY_NOT_FOUND code', () => {
|
||||
const error = new EmbeddableFactoryNotFoundError('type1');
|
||||
expect(error.code).toBe('EMBEDDABLE_FACTORY_NOT_FOUND');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -33,18 +33,3 @@ export class PanelIncompatibleError extends Error {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class EmbeddableFactoryNotFoundError extends Error {
|
||||
code = 'EMBEDDABLE_FACTORY_NOT_FOUND';
|
||||
|
||||
constructor(type: string) {
|
||||
super(
|
||||
i18n.translate('embeddableApi.errors.embeddableFactoryNotFound', {
|
||||
defaultMessage: `{type} can't be loaded. Please upgrade to the default distribution of Elasticsearch and Kibana with the appropriate license.`,
|
||||
values: {
|
||||
type,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,77 +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 { EmbeddableInput } from '../embeddables';
|
||||
import { runEmbeddableFactoryMigrations } from './run_factory_migrations';
|
||||
|
||||
describe('Run embeddable factory migrations', () => {
|
||||
interface TestInputTypeVersion009 extends EmbeddableInput {
|
||||
version: '0.0.9';
|
||||
keyThatAlwaysExists: string;
|
||||
keyThatGetsRemoved: string;
|
||||
}
|
||||
interface TestInputTypeVersion100 extends EmbeddableInput {
|
||||
version: '1.0.0';
|
||||
id: string;
|
||||
keyThatAlwaysExists: string;
|
||||
keyThatGetsAdded: string;
|
||||
}
|
||||
|
||||
const migrations = {
|
||||
'1.0.0': (input: TestInputTypeVersion009): TestInputTypeVersion100 => {
|
||||
const newInput: TestInputTypeVersion100 = {
|
||||
id: input.id,
|
||||
version: '1.0.0',
|
||||
keyThatAlwaysExists: input.keyThatAlwaysExists,
|
||||
keyThatGetsAdded: 'I just got born',
|
||||
};
|
||||
return newInput;
|
||||
},
|
||||
};
|
||||
|
||||
it('should return the initial input and migrationRun=false if the current version is the latest', () => {
|
||||
const initialInput: TestInputTypeVersion100 = {
|
||||
id: 'superId',
|
||||
version: '1.0.0',
|
||||
keyThatAlwaysExists: 'Inside Problems',
|
||||
keyThatGetsAdded: 'Oh my - I just got born',
|
||||
};
|
||||
|
||||
const factory = {
|
||||
latestVersion: '1.0.0',
|
||||
migrations,
|
||||
};
|
||||
|
||||
const result = runEmbeddableFactoryMigrations<TestInputTypeVersion100>(initialInput, factory);
|
||||
|
||||
expect(result.input).toBe(initialInput);
|
||||
expect(result.migrationRun).toBe(false);
|
||||
});
|
||||
|
||||
it('should return migrated input and migrationRun=true if version does not match latestVersion', () => {
|
||||
const initialInput: TestInputTypeVersion009 = {
|
||||
id: 'superId',
|
||||
version: '0.0.9',
|
||||
keyThatAlwaysExists: 'Inside Problems',
|
||||
keyThatGetsRemoved: 'juvenile plumage',
|
||||
};
|
||||
|
||||
const factory = {
|
||||
latestVersion: '1.0.0',
|
||||
migrations,
|
||||
};
|
||||
|
||||
const result = runEmbeddableFactoryMigrations<TestInputTypeVersion100>(initialInput, factory);
|
||||
|
||||
expect(result.migrationRun).toBe(true);
|
||||
expect(result.input.version).toBe('1.0.0');
|
||||
expect((result.input as unknown as TestInputTypeVersion009).keyThatGetsRemoved).toBeUndefined();
|
||||
expect(result.input.keyThatGetsAdded).toEqual('I just got born');
|
||||
});
|
||||
});
|
|
@ -1,47 +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 { cloneDeep } from 'lodash';
|
||||
import compare from 'semver/functions/compare';
|
||||
|
||||
import { migrateToLatest } from '@kbn/kibana-utils-plugin/common';
|
||||
import { EmbeddableFactory, EmbeddableInput } from '../embeddables';
|
||||
|
||||
/**
|
||||
* A helper function that migrates an Embeddable Input to its latest version. Note that this function
|
||||
* only runs the embeddable factory's migrations.
|
||||
*/
|
||||
export const runEmbeddableFactoryMigrations = <ToType extends EmbeddableInput>(
|
||||
initialInput: { version?: string },
|
||||
factory: { migrations?: EmbeddableFactory['migrations']; latestVersion?: string }
|
||||
): { input: ToType; migrationRun: boolean } => {
|
||||
if (!factory.latestVersion) {
|
||||
return { input: initialInput as unknown as ToType, migrationRun: false };
|
||||
}
|
||||
|
||||
// any embeddable with no version set is considered to require all clientside migrations so we default to 0.0.0
|
||||
const inputVersion = initialInput.version ?? '0.0.0';
|
||||
const migrationRun = compare(inputVersion, factory.latestVersion, true) !== 0;
|
||||
|
||||
// return early to avoid extra operations when there are no migrations to run.
|
||||
if (!migrationRun) return { input: initialInput as unknown as ToType, migrationRun };
|
||||
|
||||
const factoryMigrations =
|
||||
typeof factory?.migrations === 'function' ? factory?.migrations() : factory?.migrations || {};
|
||||
const migratedInput = migrateToLatest(
|
||||
factoryMigrations ?? {},
|
||||
{
|
||||
state: cloneDeep(initialInput),
|
||||
version: inputVersion,
|
||||
},
|
||||
true
|
||||
);
|
||||
migratedInput.version = factory.latestVersion;
|
||||
return { input: migratedInput as ToType, migrationRun };
|
||||
};
|
|
@ -12,4 +12,3 @@ export * from './embeddables';
|
|||
export * from './types';
|
||||
export * from './triggers';
|
||||
export * from './state_transfer';
|
||||
export * from './factory_migrations/run_factory_migrations';
|
||||
|
|
|
@ -19,17 +19,17 @@ import { savedObjectsManagementPluginMock } from '@kbn/saved-objects-management-
|
|||
import { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public';
|
||||
import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks';
|
||||
|
||||
import { EmbeddableStateTransfer } from '.';
|
||||
import { setKibanaServices } from './kibana_services';
|
||||
import { EmbeddablePublicPlugin } from './plugin';
|
||||
import { registerReactEmbeddableFactory } from './react_embeddable_system';
|
||||
import { registerAddFromLibraryType } from './add_from_library/registry';
|
||||
import {
|
||||
EmbeddableSetup,
|
||||
EmbeddableSetupDependencies,
|
||||
EmbeddableStart,
|
||||
EmbeddableStartDependencies,
|
||||
EmbeddableStateTransfer,
|
||||
} from '.';
|
||||
import { setKibanaServices } from './kibana_services';
|
||||
import { EmbeddablePublicPlugin } from './plugin';
|
||||
import { registerReactEmbeddableFactory } from './react_embeddable_system';
|
||||
import { registerAddFromLibraryType } from './add_from_library/registry';
|
||||
} from './types';
|
||||
|
||||
export type Setup = jest.Mocked<EmbeddableSetup>;
|
||||
export type Start = jest.Mocked<EmbeddableStart>;
|
||||
|
@ -48,7 +48,6 @@ const createSetupContract = (): Setup => {
|
|||
const setupContract: Setup = {
|
||||
registerAddFromLibraryType: jest.fn().mockImplementation(registerAddFromLibraryType),
|
||||
registerReactEmbeddableFactory: jest.fn().mockImplementation(registerReactEmbeddableFactory),
|
||||
registerEmbeddableFactory: jest.fn(),
|
||||
registerEnhancement: jest.fn(),
|
||||
};
|
||||
return setupContract;
|
||||
|
@ -56,8 +55,6 @@ const createSetupContract = (): Setup => {
|
|||
|
||||
const createStartContract = (): Start => {
|
||||
const startContract: Start = {
|
||||
getEmbeddableFactories: jest.fn(),
|
||||
getEmbeddableFactory: jest.fn(),
|
||||
telemetry: jest.fn(),
|
||||
extract: jest.fn(),
|
||||
inject: jest.fn(),
|
||||
|
|
|
@ -10,104 +10,6 @@
|
|||
import { coreMock } from '@kbn/core/public/mocks';
|
||||
import { testPlugin } from './tests/test_plugin';
|
||||
|
||||
describe('embeddable factory', () => {
|
||||
const coreSetup = coreMock.createSetup();
|
||||
const coreStart = coreMock.createStart();
|
||||
const { setup, doStart } = testPlugin(coreSetup, coreStart);
|
||||
const start = doStart();
|
||||
const embeddableFactoryId = 'ID';
|
||||
const embeddableFactory = {
|
||||
type: embeddableFactoryId,
|
||||
create: jest.fn(),
|
||||
getDisplayName: () => 'Test',
|
||||
isEditable: () => Promise.resolve(true),
|
||||
extract: jest.fn().mockImplementation((state) => ({ state, references: [] })),
|
||||
inject: jest.fn().mockImplementation((state) => state),
|
||||
telemetry: jest.fn().mockResolvedValue({}),
|
||||
latestVersion: '7.11.0',
|
||||
migrations: { '7.11.0': jest.fn().mockImplementation((state) => state) },
|
||||
} as any;
|
||||
const embeddableState = {
|
||||
id: embeddableFactoryId,
|
||||
type: embeddableFactoryId,
|
||||
my: 'state',
|
||||
} as any;
|
||||
|
||||
const containerEmbeddableFactoryId = 'CONTAINER';
|
||||
const containerEmbeddableFactory = {
|
||||
type: containerEmbeddableFactoryId,
|
||||
latestVersion: '1.0.0',
|
||||
create: jest.fn(),
|
||||
getDisplayName: () => 'Container',
|
||||
isContainer: true,
|
||||
isEditable: () => Promise.resolve(true),
|
||||
extract: jest.fn().mockImplementation((state) => ({ state, references: [] })),
|
||||
inject: jest.fn().mockImplementation((state) => state),
|
||||
telemetry: jest.fn().mockResolvedValue({}),
|
||||
migrations: { '7.12.0': jest.fn().mockImplementation((state) => state) },
|
||||
};
|
||||
|
||||
const containerState = {
|
||||
id: containerEmbeddableFactoryId,
|
||||
type: containerEmbeddableFactoryId,
|
||||
some: 'state',
|
||||
panels: [
|
||||
{
|
||||
...embeddableState,
|
||||
},
|
||||
],
|
||||
} as any;
|
||||
|
||||
setup.registerEmbeddableFactory(embeddableFactoryId, embeddableFactory);
|
||||
setup.registerEmbeddableFactory(containerEmbeddableFactoryId, containerEmbeddableFactory);
|
||||
|
||||
test('cannot register embeddable factory with the same ID', async () => {
|
||||
expect(() =>
|
||||
setup.registerEmbeddableFactory(embeddableFactoryId, embeddableFactory)
|
||||
).toThrowError(
|
||||
'Embeddable factory [embeddableFactoryId = ID] already registered in Embeddables API.'
|
||||
);
|
||||
});
|
||||
|
||||
test('embeddableFactory extract function gets called when calling embeddable extract', () => {
|
||||
start.extract(embeddableState);
|
||||
expect(embeddableFactory.extract).toBeCalledWith(embeddableState);
|
||||
});
|
||||
|
||||
test('embeddableFactory inject function gets called when calling embeddable inject', () => {
|
||||
start.inject(embeddableState, []);
|
||||
expect(embeddableFactory.extract).toBeCalledWith(embeddableState);
|
||||
});
|
||||
|
||||
test('embeddableFactory telemetry function gets called when calling embeddable telemetry', () => {
|
||||
start.telemetry(embeddableState, {});
|
||||
expect(embeddableFactory.telemetry).toBeCalledWith(embeddableState, {});
|
||||
});
|
||||
|
||||
test('embeddableFactory migrate function gets called when calling embeddable migrate', () => {
|
||||
start.getAllMigrations!()['7.11.0']!(embeddableState);
|
||||
expect(embeddableFactory.migrations['7.11.0']).toBeCalledWith(embeddableState);
|
||||
});
|
||||
|
||||
test('panels inside container get automatically migrated when migrating conta1iner', () => {
|
||||
start.getAllMigrations!()['7.11.0']!(containerState);
|
||||
expect(embeddableFactory.migrations['7.11.0']).toBeCalledWith(embeddableState);
|
||||
});
|
||||
|
||||
test('migrateToLatest returns list of all migrations', () => {
|
||||
const migrations = start.getAllMigrations();
|
||||
expect(migrations).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('migrateToLatest calls correct migrate functions', () => {
|
||||
start.migrateToLatest!({
|
||||
state: embeddableState,
|
||||
version: '7.11.0',
|
||||
});
|
||||
expect(embeddableFactory.migrations['7.11.0']).toBeCalledWith(embeddableState);
|
||||
});
|
||||
});
|
||||
|
||||
describe('embeddable enhancements', () => {
|
||||
const coreSetup = coreMock.createSetup();
|
||||
const coreStart = coreMock.createStart();
|
||||
|
|
|
@ -8,10 +8,6 @@
|
|||
*/
|
||||
|
||||
import { Subscription } from 'rxjs';
|
||||
import { identity } from 'lodash';
|
||||
import type { SerializableRecord } from '@kbn/utility-types';
|
||||
import { UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/public';
|
||||
import { Start as InspectorStart } from '@kbn/inspector-plugin/public';
|
||||
import {
|
||||
PluginInitializerContext,
|
||||
CoreSetup,
|
||||
|
@ -20,26 +16,8 @@ import {
|
|||
PublicAppInfo,
|
||||
} from '@kbn/core/public';
|
||||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
import { UsageCollectionStart } from '@kbn/usage-collection-plugin/public';
|
||||
import { migrateToLatest, PersistableStateService } from '@kbn/kibana-utils-plugin/common';
|
||||
import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public';
|
||||
import type { ContentManagementPublicStart } from '@kbn/content-management-plugin/public';
|
||||
import type { SavedObjectTaggingOssPluginStart } from '@kbn/saved-objects-tagging-oss-plugin/public';
|
||||
import {
|
||||
EmbeddableFactoryRegistry,
|
||||
EnhancementsRegistry,
|
||||
EnhancementRegistryDefinition,
|
||||
EnhancementRegistryItem,
|
||||
} from './types';
|
||||
import { migrateToLatest } from '@kbn/kibana-utils-plugin/common';
|
||||
import { bootstrap } from './bootstrap';
|
||||
import {
|
||||
EmbeddableFactory,
|
||||
EmbeddableInput,
|
||||
EmbeddableOutput,
|
||||
defaultEmbeddableFactoryProvider,
|
||||
IEmbeddable,
|
||||
} from './lib';
|
||||
import { EmbeddableFactoryDefinition } from './lib/embeddables/embeddable_factory_definition';
|
||||
import { EmbeddableStateTransfer } from './lib/state_transfer';
|
||||
import { EmbeddableStateWithType, CommonEmbeddableStartContract } from '../common/types';
|
||||
import {
|
||||
|
@ -52,90 +30,19 @@ import { getAllMigrations } from '../common/lib/get_all_migrations';
|
|||
import { setKibanaServices } from './kibana_services';
|
||||
import { registerReactEmbeddableFactory } from './react_embeddable_system';
|
||||
import { registerAddFromLibraryType } from './add_from_library/registry';
|
||||
import { EnhancementsRegistry } from './enhancements/registry';
|
||||
import {
|
||||
EmbeddableSetup,
|
||||
EmbeddableSetupDependencies,
|
||||
EmbeddableStart,
|
||||
EmbeddableStartDependencies,
|
||||
} from './types';
|
||||
|
||||
export interface EmbeddableSetupDependencies {
|
||||
uiActions: UiActionsSetup;
|
||||
}
|
||||
|
||||
export interface EmbeddableStartDependencies {
|
||||
uiActions: UiActionsStart;
|
||||
inspector: InspectorStart;
|
||||
usageCollection: UsageCollectionStart;
|
||||
contentManagement: ContentManagementPublicStart;
|
||||
savedObjectsManagement: SavedObjectsManagementPluginStart;
|
||||
savedObjectsTaggingOss?: SavedObjectTaggingOssPluginStart;
|
||||
}
|
||||
|
||||
export interface EmbeddableSetup {
|
||||
/**
|
||||
* Register a saved object type with the "Add from library" flyout.
|
||||
*
|
||||
* @example
|
||||
* registerAddFromLibraryType({
|
||||
* onAdd: (container, savedObject) => {
|
||||
* container.addNewPanel({
|
||||
* panelType: CONTENT_ID,
|
||||
* initialState: savedObject.attributes,
|
||||
* });
|
||||
* },
|
||||
* savedObjectType: MAP_SAVED_OBJECT_TYPE,
|
||||
* savedObjectName: i18n.translate('xpack.maps.mapSavedObjectLabel', {
|
||||
* defaultMessage: 'Map',
|
||||
* }),
|
||||
* getIconForSavedObject: () => APP_ICON,
|
||||
* });
|
||||
*/
|
||||
registerAddFromLibraryType: typeof registerAddFromLibraryType;
|
||||
|
||||
/**
|
||||
* Registers an async {@link ReactEmbeddableFactory} getter.
|
||||
*/
|
||||
registerReactEmbeddableFactory: typeof registerReactEmbeddableFactory;
|
||||
|
||||
/**
|
||||
* @deprecated use {@link registerReactEmbeddableFactory} instead.
|
||||
*/
|
||||
registerEmbeddableFactory: <
|
||||
I extends EmbeddableInput,
|
||||
O extends EmbeddableOutput,
|
||||
E extends IEmbeddable<I, O> = IEmbeddable<I, O>
|
||||
>(
|
||||
id: string,
|
||||
factory: EmbeddableFactoryDefinition<I, O, E>
|
||||
) => () => EmbeddableFactory<I, O, E>;
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
registerEnhancement: (enhancement: EnhancementRegistryDefinition) => void;
|
||||
}
|
||||
|
||||
export interface EmbeddableStart extends PersistableStateService<EmbeddableStateWithType> {
|
||||
/**
|
||||
* @deprecated use {@link registerReactEmbeddableFactory} instead.
|
||||
*/
|
||||
getEmbeddableFactory: <
|
||||
I extends EmbeddableInput = EmbeddableInput,
|
||||
O extends EmbeddableOutput = EmbeddableOutput,
|
||||
E extends IEmbeddable<I, O> = IEmbeddable<I, O>
|
||||
>(
|
||||
embeddableFactoryId: string
|
||||
) => EmbeddableFactory<I, O, E> | undefined;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
getEmbeddableFactories: () => IterableIterator<EmbeddableFactory>;
|
||||
getStateTransfer: (storage?: Storage) => EmbeddableStateTransfer;
|
||||
}
|
||||
export class EmbeddablePublicPlugin implements Plugin<EmbeddableSetup, EmbeddableStart> {
|
||||
private readonly embeddableFactoryDefinitions: Map<string, EmbeddableFactoryDefinition> =
|
||||
new Map();
|
||||
private readonly embeddableFactories: EmbeddableFactoryRegistry = new Map();
|
||||
private readonly enhancements: EnhancementsRegistry = new Map();
|
||||
private stateTransferService: EmbeddableStateTransfer = {} as EmbeddableStateTransfer;
|
||||
private isRegistryReady = false;
|
||||
private appList?: ReadonlyMap<string, PublicAppInfo>;
|
||||
private appListSubscription?: Subscription;
|
||||
private enhancementsRegistry = new EnhancementsRegistry();
|
||||
|
||||
constructor(initializerContext: PluginInitializerContext) {}
|
||||
|
||||
|
@ -145,17 +52,11 @@ export class EmbeddablePublicPlugin implements Plugin<EmbeddableSetup, Embeddabl
|
|||
return {
|
||||
registerReactEmbeddableFactory,
|
||||
registerAddFromLibraryType,
|
||||
|
||||
registerEmbeddableFactory: this.registerEmbeddableFactory,
|
||||
registerEnhancement: this.registerEnhancement,
|
||||
registerEnhancement: this.enhancementsRegistry.registerEnhancement,
|
||||
};
|
||||
}
|
||||
|
||||
public start(core: CoreStart, deps: EmbeddableStartDependencies): EmbeddableStart {
|
||||
this.embeddableFactoryDefinitions.forEach((def) => {
|
||||
this.embeddableFactories.set(def.type, defaultEmbeddableFactoryProvider(def));
|
||||
});
|
||||
|
||||
this.appListSubscription = core.application.applications$.subscribe((appList) => {
|
||||
this.appList = appList;
|
||||
});
|
||||
|
@ -165,24 +66,19 @@ export class EmbeddablePublicPlugin implements Plugin<EmbeddableSetup, Embeddabl
|
|||
core.application.currentAppId$,
|
||||
this.appList
|
||||
);
|
||||
this.isRegistryReady = true;
|
||||
|
||||
const commonContract: CommonEmbeddableStartContract = {
|
||||
getEmbeddableFactory: this
|
||||
.getEmbeddableFactory as unknown as CommonEmbeddableStartContract['getEmbeddableFactory'],
|
||||
getEnhancement: this.getEnhancement,
|
||||
getEnhancement: this.enhancementsRegistry.getEnhancement,
|
||||
};
|
||||
|
||||
const getAllMigrationsFn = () =>
|
||||
getAllMigrations(
|
||||
Array.from(this.embeddableFactories.values()),
|
||||
Array.from(this.enhancements.values()),
|
||||
[],
|
||||
this.enhancementsRegistry.getEnhancements(),
|
||||
getMigrateFunction(commonContract)
|
||||
);
|
||||
|
||||
const embeddableStart: EmbeddableStart = {
|
||||
getEmbeddableFactory: this.getEmbeddableFactory,
|
||||
getEmbeddableFactories: this.getEmbeddableFactories,
|
||||
getStateTransfer: (storage?: Storage) =>
|
||||
storage
|
||||
? new EmbeddableStateTransfer(
|
||||
|
@ -210,89 +106,4 @@ export class EmbeddablePublicPlugin implements Plugin<EmbeddableSetup, Embeddabl
|
|||
this.appListSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
private registerEnhancement = (enhancement: EnhancementRegistryDefinition) => {
|
||||
if (this.enhancements.has(enhancement.id)) {
|
||||
throw new Error(`enhancement with id ${enhancement.id} already exists in the registry`);
|
||||
}
|
||||
this.enhancements.set(enhancement.id, {
|
||||
id: enhancement.id,
|
||||
telemetry: enhancement.telemetry || ((state, stats) => stats),
|
||||
inject: enhancement.inject || identity,
|
||||
extract:
|
||||
enhancement.extract ||
|
||||
((state: SerializableRecord) => {
|
||||
return { state, references: [] };
|
||||
}),
|
||||
migrations: enhancement.migrations || {},
|
||||
});
|
||||
};
|
||||
|
||||
private getEnhancement = (id: string): EnhancementRegistryItem => {
|
||||
return (
|
||||
this.enhancements.get(id) || {
|
||||
id: 'unknown',
|
||||
telemetry: (state, stats) => stats,
|
||||
inject: identity,
|
||||
extract: (state: SerializableRecord) => {
|
||||
return { state, references: [] };
|
||||
},
|
||||
migrations: {},
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
private getEmbeddableFactories = () => {
|
||||
this.ensureFactoriesExist();
|
||||
return this.embeddableFactories.values();
|
||||
};
|
||||
|
||||
private registerEmbeddableFactory = <
|
||||
I extends EmbeddableInput = EmbeddableInput,
|
||||
O extends EmbeddableOutput = EmbeddableOutput,
|
||||
E extends IEmbeddable<I, O> = IEmbeddable<I, O>
|
||||
>(
|
||||
embeddableFactoryId: string,
|
||||
factory: EmbeddableFactoryDefinition<I, O, E>
|
||||
): (() => EmbeddableFactory<I, O, E>) => {
|
||||
if (this.embeddableFactoryDefinitions.has(embeddableFactoryId)) {
|
||||
throw new Error(
|
||||
`Embeddable factory [embeddableFactoryId = ${embeddableFactoryId}] already registered in Embeddables API.`
|
||||
);
|
||||
}
|
||||
this.embeddableFactoryDefinitions.set(embeddableFactoryId, factory);
|
||||
|
||||
return () => {
|
||||
return this.getEmbeddableFactory(embeddableFactoryId);
|
||||
};
|
||||
};
|
||||
|
||||
private getEmbeddableFactory = <
|
||||
I extends EmbeddableInput = EmbeddableInput,
|
||||
O extends EmbeddableOutput = EmbeddableOutput,
|
||||
E extends IEmbeddable<I, O> = IEmbeddable<I, O>
|
||||
>(
|
||||
embeddableFactoryId: string
|
||||
): EmbeddableFactory<I, O, E> => {
|
||||
if (!this.isRegistryReady) {
|
||||
throw new Error('Embeddable factories can only be retrieved after setup lifecycle.');
|
||||
}
|
||||
this.ensureFactoryExists(embeddableFactoryId);
|
||||
const factory = this.embeddableFactories.get(embeddableFactoryId);
|
||||
|
||||
return factory as EmbeddableFactory<I, O, E>;
|
||||
};
|
||||
|
||||
// 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, defaultEmbeddableFactoryProvider(def));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -19,7 +19,8 @@ import {
|
|||
import { Query } from '@kbn/es-query';
|
||||
import { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public';
|
||||
import { contentManagementMock } from '@kbn/content-management-plugin/public/mocks';
|
||||
import { EmbeddablePublicPlugin, EmbeddableSetup, EmbeddableStart } from '../plugin';
|
||||
import { EmbeddablePublicPlugin } from '../plugin';
|
||||
import type { EmbeddableSetup, EmbeddableStart } from '../types';
|
||||
export interface TestPluginReturn {
|
||||
plugin: EmbeddablePublicPlugin;
|
||||
coreSetup: CoreSetup;
|
||||
|
|
|
@ -7,36 +7,65 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { SerializableRecord } from '@kbn/utility-types';
|
||||
import { SavedObjectAttributes } from '@kbn/core/public';
|
||||
import type { FinderAttributes } from '@kbn/saved-objects-finder-plugin/common';
|
||||
import { PersistableState, PersistableStateDefinition } from '@kbn/kibana-utils-plugin/common';
|
||||
import {
|
||||
EmbeddableFactory,
|
||||
EmbeddableInput,
|
||||
EmbeddableOutput,
|
||||
IEmbeddable,
|
||||
EmbeddableFactoryDefinition,
|
||||
} from './lib/embeddables';
|
||||
import type { UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/public';
|
||||
import type { Start as InspectorStart } from '@kbn/inspector-plugin/public';
|
||||
import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public';
|
||||
import type { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public';
|
||||
import type { ContentManagementPublicStart } from '@kbn/content-management-plugin/public';
|
||||
import type { SavedObjectTaggingOssPluginStart } from '@kbn/saved-objects-tagging-oss-plugin/public';
|
||||
import type { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
import type { PersistableStateService } from '@kbn/kibana-utils-plugin/common';
|
||||
import type { registerAddFromLibraryType } from './add_from_library/registry';
|
||||
import type { registerReactEmbeddableFactory } from './react_embeddable_system';
|
||||
import type { EmbeddableStateTransfer } from './lib';
|
||||
import type { EmbeddableStateWithType } from '../common';
|
||||
import { EnhancementRegistryDefinition } from './enhancements/types';
|
||||
|
||||
export type EmbeddableFactoryRegistry = Map<string, EmbeddableFactory>;
|
||||
export type EnhancementsRegistry = Map<string, EnhancementRegistryItem>;
|
||||
|
||||
export interface EnhancementRegistryDefinition<P extends SerializableRecord = SerializableRecord>
|
||||
extends PersistableStateDefinition<P> {
|
||||
id: string;
|
||||
export interface EmbeddableSetupDependencies {
|
||||
uiActions: UiActionsSetup;
|
||||
}
|
||||
|
||||
export interface EnhancementRegistryItem<P extends SerializableRecord = SerializableRecord>
|
||||
extends PersistableState<P> {
|
||||
id: string;
|
||||
export interface EmbeddableStartDependencies {
|
||||
uiActions: UiActionsStart;
|
||||
inspector: InspectorStart;
|
||||
usageCollection: UsageCollectionStart;
|
||||
contentManagement: ContentManagementPublicStart;
|
||||
savedObjectsManagement: SavedObjectsManagementPluginStart;
|
||||
savedObjectsTaggingOss?: SavedObjectTaggingOssPluginStart;
|
||||
}
|
||||
|
||||
export type EmbeddableFactoryProvider = <
|
||||
I extends EmbeddableInput = EmbeddableInput,
|
||||
O extends EmbeddableOutput = EmbeddableOutput,
|
||||
E extends IEmbeddable<I, O> = IEmbeddable<I, O>,
|
||||
T extends FinderAttributes = SavedObjectAttributes
|
||||
>(
|
||||
def: EmbeddableFactoryDefinition<I, O, E, T>
|
||||
) => EmbeddableFactory<I, O, E, T>;
|
||||
export interface EmbeddableSetup {
|
||||
/**
|
||||
* Register a saved object type with the "Add from library" flyout.
|
||||
*
|
||||
* @example
|
||||
* registerAddFromLibraryType({
|
||||
* onAdd: (container, savedObject) => {
|
||||
* container.addNewPanel({
|
||||
* panelType: CONTENT_ID,
|
||||
* initialState: savedObject.attributes,
|
||||
* });
|
||||
* },
|
||||
* savedObjectType: MAP_SAVED_OBJECT_TYPE,
|
||||
* savedObjectName: i18n.translate('xpack.maps.mapSavedObjectLabel', {
|
||||
* defaultMessage: 'Map',
|
||||
* }),
|
||||
* getIconForSavedObject: () => APP_ICON,
|
||||
* });
|
||||
*/
|
||||
registerAddFromLibraryType: typeof registerAddFromLibraryType;
|
||||
|
||||
/**
|
||||
* Registers an async {@link ReactEmbeddableFactory} getter.
|
||||
*/
|
||||
registerReactEmbeddableFactory: typeof registerReactEmbeddableFactory;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
registerEnhancement: (enhancement: EnhancementRegistryDefinition) => void;
|
||||
}
|
||||
|
||||
export interface EmbeddableStart extends PersistableStateService<EmbeddableStateWithType> {
|
||||
getStateTransfer: (storage?: Storage) => EmbeddableStateTransfer;
|
||||
}
|
||||
|
|
|
@ -2774,7 +2774,6 @@
|
|||
"embeddableApi.common.constants.grouping.other": "Autre",
|
||||
"embeddableApi.contextMenuTrigger.description": "Une nouvelle action sera ajoutée au menu contextuel du panneau",
|
||||
"embeddableApi.contextMenuTrigger.title": "Menu contextuel",
|
||||
"embeddableApi.errors.embeddableFactoryNotFound": "Impossible de charger {type}. Veuillez effectuer une mise à niveau vers la distribution par défaut d'Elasticsearch et de Kibana avec la licence appropriée.",
|
||||
"embeddableApi.errors.paneldoesNotExist": "Panneau introuvable",
|
||||
"embeddableApi.errors.panelIncompatibleError": "L'API du panneau n'est pas compatible",
|
||||
"embeddableApi.multiValueClickTrigger.description": "Sélection de plusieurs valeurs d'une même dimension dans la visualisation",
|
||||
|
|
|
@ -2769,7 +2769,6 @@
|
|||
"embeddableApi.common.constants.grouping.other": "Other",
|
||||
"embeddableApi.contextMenuTrigger.description": "新しいアクションがパネルのコンテキストメニューに追加されます",
|
||||
"embeddableApi.contextMenuTrigger.title": "コンテキストメニュー",
|
||||
"embeddableApi.errors.embeddableFactoryNotFound": "{type} を読み込めません。Elasticsearch と Kibanaのデフォルトのディストリビューションを適切なライセンスでアップグレードしてください。",
|
||||
"embeddableApi.errors.paneldoesNotExist": "パネルが見つかりません",
|
||||
"embeddableApi.errors.panelIncompatibleError": "パネルAPIに互換性がありません",
|
||||
"embeddableApi.multiValueClickTrigger.description": "ビジュアライゼーションの1つのディメンションの複数値を選択しています",
|
||||
|
|
|
@ -2761,7 +2761,6 @@
|
|||
"embeddableApi.common.constants.grouping.other": "其他",
|
||||
"embeddableApi.contextMenuTrigger.description": "会将一个新操作添加到该面板的上下文菜单",
|
||||
"embeddableApi.contextMenuTrigger.title": "上下文菜单",
|
||||
"embeddableApi.errors.embeddableFactoryNotFound": "{type} 无法加载。请升级到具有适当许可的默认 Elasticsearch 和 Kibana 分发。",
|
||||
"embeddableApi.errors.paneldoesNotExist": "未找到面板",
|
||||
"embeddableApi.errors.panelIncompatibleError": "面板 API 不兼容",
|
||||
"embeddableApi.multiValueClickTrigger.description": "在可视化上选择多个单一维度的值",
|
||||
|
|
|
@ -5,11 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { type ExplicitInputWithAttributes } from '@kbn/embeddable-plugin/public/lib';
|
||||
import { EmbeddableInput } from '../../types';
|
||||
|
||||
export const encode = (
|
||||
input: ExplicitInputWithAttributes | Partial<EmbeddableInput> | Readonly<EmbeddableInput>
|
||||
) => Buffer.from(JSON.stringify(input)).toString('base64');
|
||||
export const encode = (input: object) => Buffer.from(JSON.stringify(input)).toString('base64');
|
||||
export const decode = (serializedInput: string) =>
|
||||
JSON.parse(Buffer.from(serializedInput, 'base64').toString());
|
||||
|
|
|
@ -8,54 +8,9 @@
|
|||
import { storiesOf } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import React from 'react';
|
||||
import { EmbeddableFactoryDefinition, IEmbeddable } from '@kbn/embeddable-plugin/public';
|
||||
import { BaseVisType, VisTypeAlias } from '@kbn/visualizations-plugin/public';
|
||||
import { EditorMenu } from '../editor_menu.component';
|
||||
|
||||
const testFactories: EmbeddableFactoryDefinition[] = [
|
||||
{
|
||||
type: 'ml_anomaly_swimlane',
|
||||
getDisplayName: () => 'Anomaly swimlane',
|
||||
getIconType: () => '',
|
||||
getDescription: () => 'Description for anomaly swimlane',
|
||||
isEditable: () => Promise.resolve(true),
|
||||
latestVersion: '1.0.0',
|
||||
create: () => Promise.resolve({ id: 'swimlane_embeddable' } as IEmbeddable),
|
||||
grouping: [
|
||||
{
|
||||
id: 'ml',
|
||||
getDisplayName: () => 'machine learning',
|
||||
getIconType: () => 'machineLearningApp',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'ml_anomaly_chart',
|
||||
getDisplayName: () => 'Anomaly chart',
|
||||
getIconType: () => '',
|
||||
getDescription: () => 'Description for anomaly chart',
|
||||
isEditable: () => Promise.resolve(true),
|
||||
create: () => Promise.resolve({ id: 'anomaly_chart_embeddable' } as IEmbeddable),
|
||||
latestVersion: '1.0.0',
|
||||
grouping: [
|
||||
{
|
||||
id: 'ml',
|
||||
getDisplayName: () => 'machine learning',
|
||||
getIconType: () => 'machineLearningApp',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'log_stream',
|
||||
getDisplayName: () => 'Log stream',
|
||||
getIconType: () => '',
|
||||
getDescription: () => 'Description for log stream',
|
||||
latestVersion: '1.0.0',
|
||||
isEditable: () => Promise.resolve(true),
|
||||
create: () => Promise.resolve({ id: 'anomaly_chart_embeddable' } as IEmbeddable),
|
||||
},
|
||||
];
|
||||
|
||||
const testVisTypes: BaseVisType[] = [
|
||||
{ title: 'TSVB', icon: '', description: 'Description of TSVB', name: 'tsvb' } as BaseVisType,
|
||||
{
|
||||
|
@ -95,11 +50,9 @@ const testVisTypeAliases: VisTypeAlias[] = [
|
|||
storiesOf('components/WorkpadHeader/EditorMenu', module).add('default', () => (
|
||||
<EditorMenu
|
||||
addPanelActions={[]}
|
||||
factories={testFactories}
|
||||
promotedVisTypes={testVisTypes}
|
||||
visTypeAliases={testVisTypeAliases}
|
||||
createNewVisType={() => action('createNewVisType')}
|
||||
createNewEmbeddableFromFactory={() => action('createNewEmbeddableFromFactory')}
|
||||
createNewEmbeddableFromAction={() => action('createNewEmbeddableFromAction')}
|
||||
/>
|
||||
));
|
||||
|
|
|
@ -7,12 +7,7 @@
|
|||
|
||||
import React, { FC, useCallback } from 'react';
|
||||
|
||||
import {
|
||||
EuiContextMenu,
|
||||
EuiContextMenuItemIcon,
|
||||
EuiContextMenuPanelItemDescriptor,
|
||||
} from '@elastic/eui';
|
||||
import { EmbeddableFactoryDefinition } from '@kbn/embeddable-plugin/public';
|
||||
import { EuiContextMenu, EuiContextMenuPanelItemDescriptor } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ToolbarPopover } from '@kbn/shared-ux-button-toolbar';
|
||||
import { Action, ActionExecutionContext } from '@kbn/ui-actions-plugin/public/actions';
|
||||
|
@ -28,21 +23,11 @@ const strings = {
|
|||
}),
|
||||
};
|
||||
|
||||
interface FactoryGroup {
|
||||
id: string;
|
||||
appName: string;
|
||||
icon: EuiContextMenuItemIcon;
|
||||
panelId: number;
|
||||
factories: EmbeddableFactoryDefinition[];
|
||||
}
|
||||
|
||||
interface Props {
|
||||
factories: EmbeddableFactoryDefinition[];
|
||||
addPanelActions: Action[];
|
||||
promotedVisTypes: BaseVisType[];
|
||||
visTypeAliases: VisTypeAlias[];
|
||||
createNewVisType: (visType?: BaseVisType | VisTypeAlias) => () => void;
|
||||
createNewEmbeddableFromFactory: (factory: EmbeddableFactoryDefinition) => () => void;
|
||||
createNewEmbeddableFromAction: (
|
||||
action: Action,
|
||||
context: ActionExecutionContext<object>,
|
||||
|
@ -51,46 +36,14 @@ interface Props {
|
|||
}
|
||||
|
||||
export const EditorMenu: FC<Props> = ({
|
||||
factories,
|
||||
addPanelActions,
|
||||
promotedVisTypes,
|
||||
visTypeAliases,
|
||||
createNewVisType,
|
||||
createNewEmbeddableFromAction,
|
||||
createNewEmbeddableFromFactory,
|
||||
}: Props) => {
|
||||
const factoryGroupMap: Record<string, FactoryGroup> = {};
|
||||
const ungroupedFactories: EmbeddableFactoryDefinition[] = [];
|
||||
const canvasApi = useCanvasApi();
|
||||
|
||||
let panelCount = 1;
|
||||
|
||||
// Maps factories with a group to create nested context menus for each group type
|
||||
// and pushes ungrouped factories into a separate array
|
||||
factories.forEach((factory: EmbeddableFactoryDefinition, index) => {
|
||||
const { grouping } = factory;
|
||||
|
||||
if (grouping) {
|
||||
grouping.forEach((group) => {
|
||||
if (factoryGroupMap[group.id]) {
|
||||
factoryGroupMap[group.id].factories.push(factory);
|
||||
} else {
|
||||
factoryGroupMap[group.id] = {
|
||||
id: group.id,
|
||||
appName: group.getDisplayName ? group.getDisplayName({}) : group.id,
|
||||
icon: (group.getIconType ? group.getIconType({}) : 'empty') as EuiContextMenuItemIcon,
|
||||
factories: [factory],
|
||||
panelId: panelCount,
|
||||
};
|
||||
|
||||
panelCount++;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
ungroupedFactories.push(factory);
|
||||
}
|
||||
});
|
||||
|
||||
const getVisTypeMenuItem = (visType: BaseVisType): EuiContextMenuPanelItemDescriptor => {
|
||||
const { name, title, titleInWizard, description, icon = 'empty' } = visType;
|
||||
return {
|
||||
|
@ -116,22 +69,6 @@ export const EditorMenu: FC<Props> = ({
|
|||
};
|
||||
};
|
||||
|
||||
const getEmbeddableFactoryMenuItem = (
|
||||
factory: EmbeddableFactoryDefinition
|
||||
): EuiContextMenuPanelItemDescriptor => {
|
||||
const icon = factory?.getIconType ? factory.getIconType() : 'empty';
|
||||
|
||||
const toolTipContent = factory?.getDescription ? factory.getDescription() : undefined;
|
||||
|
||||
return {
|
||||
name: factory.getDisplayName(),
|
||||
icon,
|
||||
toolTipContent,
|
||||
onClick: createNewEmbeddableFromFactory(factory),
|
||||
'data-test-subj': `createNew-${factory.type}`,
|
||||
};
|
||||
};
|
||||
|
||||
const getAddPanelActionMenuItems = useCallback(
|
||||
(closePopover: () => void) => {
|
||||
return addPanelActions.map((item) => {
|
||||
|
@ -158,23 +95,9 @@ export const EditorMenu: FC<Props> = ({
|
|||
items: [
|
||||
...visTypeAliases.map(getVisTypeAliasMenuItem),
|
||||
...getAddPanelActionMenuItems(closePopover),
|
||||
...ungroupedFactories.map(getEmbeddableFactoryMenuItem),
|
||||
...promotedVisTypes.map(getVisTypeMenuItem),
|
||||
...Object.values(factoryGroupMap).map(({ id, appName, icon, panelId }) => ({
|
||||
name: appName,
|
||||
icon,
|
||||
panel: panelId,
|
||||
'data-test-subj': `canvasEditorMenu-${id}Group`,
|
||||
})),
|
||||
],
|
||||
},
|
||||
...Object.values(factoryGroupMap).map(
|
||||
({ appName, panelId, factories: groupFactories }: FactoryGroup) => ({
|
||||
id: panelId,
|
||||
title: appName,
|
||||
items: groupFactories.map(getEmbeddableFactoryMenuItem),
|
||||
})
|
||||
),
|
||||
];
|
||||
|
||||
return (
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import React, { FC, useCallback, useEffect, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import {
|
||||
VisGroups,
|
||||
|
@ -13,19 +13,12 @@ import {
|
|||
type VisTypeAlias,
|
||||
type VisParams,
|
||||
} from '@kbn/visualizations-plugin/public';
|
||||
import {
|
||||
EmbeddableFactory,
|
||||
EmbeddableFactoryDefinition,
|
||||
EmbeddableInput,
|
||||
} from '@kbn/embeddable-plugin/public';
|
||||
import { Action, ActionExecutionContext } from '@kbn/ui-actions-plugin/public/actions';
|
||||
|
||||
import { trackCanvasUiMetric, METRIC_TYPE } from '../../../lib/ui_metric';
|
||||
import { CANVAS_APP } from '../../../../common/lib';
|
||||
import { ElementSpec } from '../../../../types';
|
||||
import { EditorMenu as Component } from './editor_menu.component';
|
||||
import { embeddableInputToExpression } from '../../../../canvas_plugin_src/renderers/embeddable/embeddable_input_to_expression';
|
||||
import { EmbeddableInput as CanvasEmbeddableInput } from '../../../../canvas_plugin_src/expression_types';
|
||||
import { useCanvasApi } from '../../hooks/use_canvas_api';
|
||||
import { ADD_CANVAS_ELEMENT_TRIGGER } from '../../../state/triggers/add_canvas_element_trigger';
|
||||
import {
|
||||
|
@ -41,11 +34,6 @@ interface Props {
|
|||
addElement: (element: Partial<ElementSpec>) => void;
|
||||
}
|
||||
|
||||
interface UnwrappedEmbeddableFactory {
|
||||
factory: EmbeddableFactory;
|
||||
isEditable: boolean;
|
||||
}
|
||||
|
||||
export const EditorMenu: FC<Props> = ({ addElement }) => {
|
||||
const { pathname, search, hash } = useLocation();
|
||||
const stateTransferService = embeddableService.getStateTransfer();
|
||||
|
@ -53,26 +41,6 @@ export const EditorMenu: FC<Props> = ({ addElement }) => {
|
|||
|
||||
const [addPanelActions, setAddPanelActions] = useState<Array<Action<object>>>([]);
|
||||
|
||||
const embeddableFactories = useMemo(
|
||||
() => (embeddableService ? Array.from(embeddableService.getEmbeddableFactories()) : []),
|
||||
[]
|
||||
);
|
||||
|
||||
const [unwrappedEmbeddableFactories, setUnwrappedEmbeddableFactories] = useState<
|
||||
UnwrappedEmbeddableFactory[]
|
||||
>([]);
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all(
|
||||
embeddableFactories.map<Promise<UnwrappedEmbeddableFactory>>(async (factory) => ({
|
||||
factory,
|
||||
isEditable: await factory.isEditable(),
|
||||
}))
|
||||
).then((factories) => {
|
||||
setUnwrappedEmbeddableFactories(factories);
|
||||
});
|
||||
}, [embeddableFactories]);
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
async function loadPanelActions() {
|
||||
|
@ -123,33 +91,6 @@ export const EditorMenu: FC<Props> = ({ addElement }) => {
|
|||
[stateTransferService, pathname, search, hash]
|
||||
);
|
||||
|
||||
const createNewEmbeddableFromFactory = useCallback(
|
||||
(factory: EmbeddableFactoryDefinition) => async () => {
|
||||
if (trackCanvasUiMetric) {
|
||||
trackCanvasUiMetric(METRIC_TYPE.CLICK, factory.type);
|
||||
}
|
||||
|
||||
let embeddableInput;
|
||||
if (factory.getExplicitInput) {
|
||||
embeddableInput = await factory.getExplicitInput();
|
||||
} else {
|
||||
const newEmbeddable = await factory.create({} as EmbeddableInput);
|
||||
embeddableInput = newEmbeddable?.getInput();
|
||||
}
|
||||
|
||||
if (embeddableInput) {
|
||||
const expression = embeddableInputToExpression(
|
||||
embeddableInput as CanvasEmbeddableInput,
|
||||
factory.type,
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
addElement({ expression });
|
||||
}
|
||||
},
|
||||
[addElement]
|
||||
);
|
||||
|
||||
const createNewEmbeddableFromAction = useCallback(
|
||||
(action: Action, context: ActionExecutionContext<object>, closePopover: () => void) =>
|
||||
(event: React.MouseEvent) => {
|
||||
|
@ -190,31 +131,17 @@ export const EditorMenu: FC<Props> = ({ addElement }) => {
|
|||
)
|
||||
.filter(({ disableCreate }: VisTypeAlias) => !disableCreate);
|
||||
|
||||
const factories = unwrappedEmbeddableFactories
|
||||
.filter(
|
||||
({ isEditable, factory: { type, canCreateNew, isContainerType } }) =>
|
||||
isEditable &&
|
||||
!isContainerType &&
|
||||
canCreateNew() &&
|
||||
!['visualization', 'ml', 'links'].some((factoryType) => {
|
||||
return type.includes(factoryType);
|
||||
})
|
||||
)
|
||||
.map(({ factory }) => factory);
|
||||
|
||||
const promotedVisTypes = getVisTypesByGroup(VisGroups.PROMOTED);
|
||||
const legacyVisTypes = getVisTypesByGroup(VisGroups.LEGACY);
|
||||
|
||||
return (
|
||||
<Component
|
||||
createNewVisType={createNewVisType}
|
||||
createNewEmbeddableFromFactory={createNewEmbeddableFromFactory}
|
||||
createNewEmbeddableFromAction={createNewEmbeddableFromAction}
|
||||
promotedVisTypes={([] as Array<BaseVisType<VisParams>>).concat(
|
||||
promotedVisTypes,
|
||||
legacyVisTypes
|
||||
)}
|
||||
factories={factories}
|
||||
addPanelActions={addPanelActions}
|
||||
visTypeAliases={visTypeAliases}
|
||||
/>
|
||||
|
|
|
@ -11,7 +11,7 @@ import type { ContentManagementPublicStart } from '@kbn/content-management-plugi
|
|||
import type { CoreStart, PluginInitializerContext } from '@kbn/core/public';
|
||||
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
|
||||
import type { EmbeddableStart } from '@kbn/embeddable-plugin/public/plugin';
|
||||
import type { EmbeddableStart } from '@kbn/embeddable-plugin/public';
|
||||
import type { ExpressionsStart } from '@kbn/expressions-plugin/public';
|
||||
import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public';
|
||||
import type { ReportingStart } from '@kbn/reporting-plugin/public';
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { css } from '@emotion/react';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { ObservabilitySharedStart } from '../../../plugin';
|
||||
|
||||
export function ProfilingEmbeddable<T>({
|
||||
embeddableFactoryId,
|
||||
height,
|
||||
...props
|
||||
}: T & { embeddableFactoryId: string; height?: string }) {
|
||||
const { embeddable: embeddablePlugin } = useKibana<ObservabilitySharedStart>().services;
|
||||
const [embeddable, setEmbeddable] = useState<any>();
|
||||
const embeddableRoot: React.RefObject<HTMLDivElement> = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function createEmbeddable() {
|
||||
const factory = embeddablePlugin?.getEmbeddableFactory(embeddableFactoryId);
|
||||
const input = { ...props, id: 'embeddable_profiling' };
|
||||
const embeddableObject = await factory?.create(input);
|
||||
setEmbeddable(embeddableObject);
|
||||
}
|
||||
createEmbeddable();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (embeddableRoot.current && embeddable) {
|
||||
embeddable.render(embeddableRoot.current);
|
||||
}
|
||||
}, [embeddable, embeddableRoot]);
|
||||
|
||||
useEffect(() => {
|
||||
if (embeddable) {
|
||||
embeddable.updateInput(props);
|
||||
embeddable.reload();
|
||||
}
|
||||
}, [embeddable, props]);
|
||||
|
||||
return (
|
||||
<div
|
||||
css={css`
|
||||
width: 100%;
|
||||
height: ${height};
|
||||
display: flex;
|
||||
flex: 1 1 100%;
|
||||
z-index: 1;
|
||||
min-height: 0;
|
||||
`}
|
||||
ref={embeddableRoot}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -51,18 +51,8 @@ const mockPlugin = {
|
|||
observabilityAIAssistant: mockAIAssistantPlugin,
|
||||
};
|
||||
|
||||
const mockEmbeddable = embeddablePluginMock.createStartContract();
|
||||
|
||||
mockEmbeddable.getEmbeddableFactory = jest.fn().mockImplementation(() => ({
|
||||
create: () => ({
|
||||
reload: jest.fn(),
|
||||
setRenderTooltipContent: jest.fn(),
|
||||
setLayerList: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
const mockCorePlugins = {
|
||||
embeddable: mockEmbeddable,
|
||||
embeddable: embeddablePluginMock.createStartContract(),
|
||||
inspector: {},
|
||||
maps: {},
|
||||
observabilityShared: {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue