diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index 23275fbe8e8f0..3f671b520fb2e 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -23,23 +23,24 @@ import { PluginInitializerContext } from 'src/core/public'; import { EmbeddablePublicPlugin } from './plugin'; export { - Adapters, ACTION_ADD_PANEL, - AddPanelAction, ACTION_APPLY_FILTER, + ACTION_EDIT_PANEL, + Adapters, + AddPanelAction, Container, ContainerInput, ContainerOutput, CONTEXT_MENU_TRIGGER, contextMenuTrigger, - ACTION_EDIT_PANEL, + defaultEmbeddableFactoryProvider, EditPanelAction, Embeddable, EmbeddableChildPanel, EmbeddableChildPanelProps, EmbeddableContext, - EmbeddableFactoryDefinition, EmbeddableFactory, + EmbeddableFactoryDefinition, EmbeddableFactoryNotFoundError, EmbeddableFactoryRenderer, EmbeddableInput, diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx index e3a6a3359132c..3c5374d6c124c 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx @@ -22,12 +22,7 @@ import { Adapters, ViewMode } from '../types'; import { IContainer } from '../containers'; import { EmbeddableInput, EmbeddableOutput, IEmbeddable } from './i_embeddable'; import { TriggerContextMapping } from '../ui_actions'; -import { EmbeddableActionStorage } from './embeddable_action_storage'; -import { - UiActionsDynamicActionManager, - UiActionsStart, -} from '../../../../../plugins/ui_actions/public'; -import { EmbeddableContext } from '../triggers'; +import { UiActionsStart } from '../../../../../plugins/ui_actions/public'; function getPanelTitle(input: EmbeddableInput, output: EmbeddableOutput) { return input.hidePanelTitles ? '' : input.title === undefined ? output.defaultTitle : input.title; @@ -60,28 +55,9 @@ export abstract class Embeddable< // to update input when the parent changes. private parentSubscription?: Rx.Subscription; - private storageSubscription?: Rx.Subscription; - // TODO: Rename to destroyed. private destoyed: boolean = false; - private storage = new EmbeddableActionStorage((this as unknown) as Embeddable); - - private cachedDynamicActions?: UiActionsDynamicActionManager; - public get dynamicActions(): UiActionsDynamicActionManager | undefined { - if (!this.params.uiActions) return undefined; - if (!this.cachedDynamicActions) { - this.cachedDynamicActions = new UiActionsDynamicActionManager({ - isCompatible: async (context: unknown) => - (context as EmbeddableContext).embeddable.runtimeId === this.runtimeId, - storage: this.storage, - uiActions: this.params.uiActions, - }); - } - - return this.cachedDynamicActions; - } - constructor( input: TEmbeddableInput, output: TEmbeddableOutput, @@ -111,18 +87,6 @@ export abstract class Embeddable< this.onResetInput(newInput); }); } - - if (this.dynamicActions) { - this.dynamicActions.start().catch(error => { - /* eslint-disable */ - console.log('Failed to start embeddable dynamic actions', this); - console.error(error); - /* eslint-enable */ - }); - this.storageSubscription = this.input$.subscribe(() => { - this.storage.reload$.next(); - }); - } } public getIsContainer(): this is IContainer { @@ -202,19 +166,6 @@ export abstract class Embeddable< public destroy(): void { this.destoyed = true; - if (this.dynamicActions) { - this.dynamicActions.stop().catch(error => { - /* eslint-disable */ - console.log('Failed to stop embeddable dynamic actions', this); - console.error(error); - /* eslint-enable */ - }); - } - - if (this.storageSubscription) { - this.storageSubscription.unsubscribe(); - } - this.input$.complete(); this.output$.complete(); diff --git a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts index 9a4452aceba00..e5d8e6e1b7de9 100644 --- a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts +++ b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts @@ -18,7 +18,6 @@ */ import { Observable } from 'rxjs'; -import { UiActionsDynamicActionManager } from '../../../../../plugins/ui_actions/public'; import { Adapters } from '../types'; import { IContainer } from '../containers/i_container'; import { ViewMode } from '../types'; @@ -92,9 +91,9 @@ export interface IEmbeddable< readonly runtimeId?: number; /** - * Default implementation of dynamic action API for embeddables. + * Extra abilities added to Embeddable by `*_enhanced` plugins. */ - dynamicActions?: UiActionsDynamicActionManager; + enhancements?: object; /** * A functional representation of the isContainer variable, but helpful for typescript to diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx index c6537f2d94994..d4d23874cfb19 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx @@ -203,13 +203,15 @@ export class EmbeddablePanel extends React.Component { this.props.embeddable.render(this.embeddableRoot.current); } - const dynamicActions = this.props.embeddable.dynamicActions; + const dynamicActions = (this.props.embeddable.enhancements as any)?.dynamicActions; if (dynamicActions) { this.setState({ eventCount: dynamicActions.state.get().events.length }); - this.eventCountSubscription = dynamicActions.state.state$.subscribe(({ events }) => { - if (!this.mounted) return; - this.setState({ eventCount: events.length }); - }); + this.eventCountSubscription = dynamicActions.state.state$.subscribe( + ({ events }: { events: unknown[] }) => { + if (!this.mounted) return; + this.setState({ eventCount: events.length }); + } + ); } } diff --git a/src/plugins/ui_actions/public/actions/index.ts b/src/plugins/ui_actions/public/actions/index.ts index 0ddba197aced6..0b0e60b3cf75c 100644 --- a/src/plugins/ui_actions/public/actions/index.ts +++ b/src/plugins/ui_actions/public/actions/index.ts @@ -23,6 +23,4 @@ export * from './action_factory_definition'; export * from './action_factory'; export * from './create_action'; export * from './incompatible_action_error'; -export * from './dynamic_action_storage'; -export * from './dynamic_action_manager'; export * from './types'; diff --git a/src/plugins/ui_actions/public/actions/types.ts b/src/plugins/ui_actions/public/actions/types.ts index 465f091e45ef1..d29e97eea532f 100644 --- a/src/plugins/ui_actions/public/actions/types.ts +++ b/src/plugins/ui_actions/public/actions/types.ts @@ -22,3 +22,9 @@ export interface SerializedAction { readonly name: string; readonly config: Config; } + +export interface SerializedEvent { + eventId: string; + triggers: string[]; + action: SerializedAction; +} diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts index 9265d35bad9a9..4f00ac4a26fc3 100644 --- a/src/plugins/ui_actions/public/index.ts +++ b/src/plugins/ui_actions/public/index.ts @@ -31,11 +31,7 @@ export { ActionDefinition as UiActionsActionDefinition, ActionFactoryDefinition as UiActionsActionFactoryDefinition, ActionInternal as UiActionsActionInternal, - ActionStorage as UiActionsActionStorage, - AbstractActionStorage as UiActionsAbstractActionStorage, createAction, - DynamicActionManager, - DynamicActionManagerState, IncompatibleActionError, SerializedAction as UiActionsSerializedAction, SerializedEvent as UiActionsSerializedEvent, @@ -57,4 +53,4 @@ export { applyFilterTrigger, } from './triggers'; export { TriggerContextMapping, TriggerId, ActionContextMapping, ActionType } from './types'; -export { ActionByType, DynamicActionManager as UiActionsDynamicActionManager } from './actions'; +export { ActionByType } from './actions'; diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_manager.test.ts b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager.test.ts similarity index 94% rename from src/plugins/ui_actions/public/actions/dynamic_action_manager.test.ts rename to x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager.test.ts index 2574a9e529ebf..e4bd73558b357 100644 --- a/src/plugins/ui_actions/public/actions/dynamic_action_manager.test.ts +++ b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager.test.ts @@ -1,29 +1,18 @@ /* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. */ import { DynamicActionManager } from './dynamic_action_manager'; import { ActionStorage, MemoryActionStorage, SerializedEvent } from './dynamic_action_storage'; -import { UiActionsService } from '../service'; -import { ActionFactoryDefinition } from './action_factory_definition'; -import { ActionRegistry } from '../types'; -import { SerializedAction } from './types'; -import { of } from '../../../kibana_utils'; +import { + UiActionsService, + UiActionsActionFactoryDefinition as ActionFactoryDefinition, + UiActionsSerializedAction as SerializedAction, + UiActionsActionInternal as ActionInternal, +} from '../../../../../src/plugins/ui_actions/public'; +import { of } from '../../../../../src/plugins/kibana_utils'; const actionFactoryDefinition1: ActionFactoryDefinition = { id: 'ACTION_FACTORY_1', @@ -82,7 +71,7 @@ const event3: SerializedEvent = { const setup = (events: readonly SerializedEvent[] = []) => { const isCompatible = async () => true; const storage: ActionStorage = new MemoryActionStorage(events); - const actions: ActionRegistry = new Map(); + const actions = new Map(); const uiActions = new UiActionsService({ actions, }); diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager.ts similarity index 88% rename from src/plugins/ui_actions/public/actions/dynamic_action_manager.ts rename to x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager.ts index 97eb5b05fbbc2..73aff70332fc3 100644 --- a/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts +++ b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager.ts @@ -1,31 +1,20 @@ /* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. */ import { v4 as uuidv4 } from 'uuid'; import { Subscription } from 'rxjs'; import { ActionStorage, SerializedEvent } from './dynamic_action_storage'; -import { UiActionsService } from '../service'; -import { SerializedAction } from './types'; -import { TriggerContextMapping } from '../types'; -import { ActionDefinition } from './action'; +import { + UiActionsService, + UiActionsSerializedAction as SerializedAction, + TriggerContextMapping, + UiActionsActionDefinition as ActionDefinition, +} from '../../../../../src/plugins/ui_actions/public'; import { defaultState, transitions, selectors, State } from './dynamic_action_manager_state'; -import { StateContainer, createStateContainer } from '../../../kibana_utils'; +import { StateContainer, createStateContainer } from '../../../../../src/plugins/kibana_utils'; const compareEvents = ( a: ReadonlyArray<{ eventId: string }>, diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_manager_state.ts b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager_state.ts similarity index 75% rename from src/plugins/ui_actions/public/actions/dynamic_action_manager_state.ts rename to x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager_state.ts index 636af076ea39f..9f10eced43a65 100644 --- a/src/plugins/ui_actions/public/actions/dynamic_action_manager_state.ts +++ b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager_state.ts @@ -1,20 +1,7 @@ /* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. */ import { SerializedEvent } from './dynamic_action_storage'; diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_storage.ts b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_storage.ts similarity index 77% rename from src/plugins/ui_actions/public/actions/dynamic_action_storage.ts rename to x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_storage.ts index 28550a671782e..92d2e8f0c3da7 100644 --- a/src/plugins/ui_actions/public/actions/dynamic_action_storage.ts +++ b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_storage.ts @@ -1,26 +1,13 @@ /* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. */ /* eslint-disable max-classes-per-file */ import { Observable, Subject } from 'rxjs'; -import { SerializedAction } from './types'; +import { UiActionsSerializedAction as SerializedAction } from '../../../../../src/plugins/ui_actions/public'; /** * Serialized representation of event-action pair, used to persist in storage. diff --git a/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/index.ts b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/index.ts new file mode 100644 index 0000000000000..a2f2818d29731 --- /dev/null +++ b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './dynamic_action_storage'; +export * from './dynamic_action_manager_state'; +export * from './dynamic_action_manager'; diff --git a/x-pack/plugins/advanced_ui_actions/public/index.ts b/x-pack/plugins/advanced_ui_actions/public/index.ts index 55361105dcb0f..9672fb10f9e16 100644 --- a/x-pack/plugins/advanced_ui_actions/public/index.ts +++ b/x-pack/plugins/advanced_ui_actions/public/index.ts @@ -26,3 +26,11 @@ export { Configurable as AdvancedUiActionsConfigurable, CollectConfigProps as AdvancedUiActionsCollectConfigProps, } from './util'; + +export { + AbstractActionStorage as UiActionsEnhancedAbstractActionStorage, + DynamicActionManager as UiActionsEnhancedDynamicActionManager, + DynamicActionManagerParams as UiActionsEnhancedDynamicActionManagerParams, + DynamicActionManagerState as UiActionsEnhancedDynamicActionManagerState, + MemoryActionStorage as UiActionsEnhancedMemoryActionStorage, +} from './dynamic_actions'; diff --git a/x-pack/plugins/dashboard_enhanced/public/plugin.ts b/x-pack/plugins/dashboard_enhanced/public/plugin.ts index 30b3f3c080f49..fff0f67e14f90 100644 --- a/x-pack/plugins/dashboard_enhanced/public/plugin.ts +++ b/x-pack/plugins/dashboard_enhanced/public/plugin.ts @@ -6,17 +6,20 @@ import { CoreStart, CoreSetup, Plugin, PluginInitializerContext } from 'src/core/public'; import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public'; +import { EmbeddableSetup, EmbeddableStart } from '../../../../src/plugins/embeddable/public'; import { DashboardDrilldownsService } from './services'; import { DrilldownsSetup, DrilldownsStart } from '../../drilldowns/public'; export interface SetupDependencies { - uiActions: UiActionsSetup; drilldowns: DrilldownsSetup; + embeddable: EmbeddableSetup; + uiActions: UiActionsSetup; } export interface StartDependencies { - uiActions: UiActionsStart; drilldowns: DrilldownsStart; + embeddable: EmbeddableStart; + uiActions: UiActionsStart; } // eslint-disable-next-line @@ -36,7 +39,7 @@ export class DashboardEnhancedPlugin public setup(core: CoreSetup, plugins: SetupDependencies): SetupContract { this.drilldowns.bootstrap(core, plugins, { - enableDrilldowns: this.config.drilldowns.enabled, + enableDrilldowns: true, }); return {}; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.test.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.test.tsx index 31ee9e29938cb..dcdefba04d882 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.test.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.test.tsx @@ -13,7 +13,7 @@ import { drilldownsPluginMock } from '../../../../../../drilldowns/public/mocks' import { ViewMode } from '../../../../../../../../src/plugins/embeddable/public'; import { uiActionsPluginMock } from '../../../../../../../../src/plugins/ui_actions/public/mocks'; import { TriggerContextMapping } from '../../../../../../../../src/plugins/ui_actions/public'; -import { MockEmbeddable } from '../test_helpers'; +import { MockEmbeddable, enhanceEmbeddable } from '../test_helpers'; const overlays = coreMock.createStart().overlays; const drilldowns = drilldownsPluginMock.createStartContract(); @@ -40,85 +40,91 @@ test('icon exists', () => { ); }); +interface CompatibilityParams { + isEdit?: boolean; + isValueClickTriggerSupported?: boolean; + isEmbeddableEnhanced?: boolean; +} + describe('isCompatible', () => { const drilldownAction = new FlyoutCreateDrilldownAction(actionParams); - function checkCompatibility(params: { - isEdit: boolean; - withUiActions: boolean; - isValueClickTriggerSupported: boolean; - }): Promise { - return drilldownAction.isCompatible({ - embeddable: new MockEmbeddable( - { id: '', viewMode: params.isEdit ? ViewMode.EDIT : ViewMode.VIEW }, - { - supportedTriggers: (params.isValueClickTriggerSupported - ? ['VALUE_CLICK_TRIGGER'] - : []) as Array, - uiActions: params.withUiActions ? uiActions : undefined, // dynamic actions support - } - ), + async function assertCompatibility( + { + isEdit = true, + isValueClickTriggerSupported = true, + isEmbeddableEnhanced = true, + }: CompatibilityParams, + expectedResult: boolean = true + ): Promise { + let embeddable = new MockEmbeddable( + { id: '', viewMode: isEdit ? ViewMode.EDIT : ViewMode.VIEW }, + { + supportedTriggers: (isValueClickTriggerSupported ? ['VALUE_CLICK_TRIGGER'] : []) as Array< + keyof TriggerContextMapping + >, + uiActions, + } + ); + + if (isEmbeddableEnhanced) { + embeddable = enhanceEmbeddable(embeddable); + } + + const result = await drilldownAction.isCompatible({ + embeddable, }); + + expect(result).toBe(expectedResult); } + const assertNonCompatibility = (params: CompatibilityParams) => + assertCompatibility(params, false); + test("compatible if dynamicUiActions enabled, 'VALUE_CLICK_TRIGGER' is supported, in edit mode", async () => { - expect( - await checkCompatibility({ - withUiActions: true, - isEdit: true, - isValueClickTriggerSupported: true, - }) - ).toBe(true); + await assertCompatibility({}); }); - test('not compatible if dynamicUiActions disabled', async () => { - expect( - await checkCompatibility({ - withUiActions: false, - isEdit: true, - isValueClickTriggerSupported: true, - }) - ).toBe(false); + test('not compatible if embeddable is not enhanced', async () => { + await assertNonCompatibility({ + isEmbeddableEnhanced: false, + }); }); test("not compatible if 'VALUE_CLICK_TRIGGER' is not supported", async () => { - expect( - await checkCompatibility({ - withUiActions: true, - isEdit: true, - isValueClickTriggerSupported: false, - }) - ).toBe(false); + await assertNonCompatibility({ + isValueClickTriggerSupported: false, + }); }); test('not compatible if in view mode', async () => { - expect( - await checkCompatibility({ - withUiActions: true, - isEdit: false, - isValueClickTriggerSupported: true, - }) - ).toBe(false); + await assertNonCompatibility({ + isEdit: false, + }); }); }); describe('execute', () => { const drilldownAction = new FlyoutCreateDrilldownAction(actionParams); + test('throws error if no dynamicUiActions', async () => { await expect( drilldownAction.execute({ embeddable: new MockEmbeddable({ id: '' }, {}), }) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Can't execute FlyoutCreateDrilldownAction without dynamicActionsManager"` + `"Need embeddable to be EnhancedEmbeddable to execute FlyoutCreateDrilldownAction."` ); }); test('should open flyout', async () => { const spy = jest.spyOn(overlays, 'openFlyout'); + const embeddable = enhanceEmbeddable(new MockEmbeddable({ id: '' }, { uiActions })); + await drilldownAction.execute({ - embeddable: new MockEmbeddable({ id: '' }, { uiActions }), + embeddable, }); + expect(spy).toBeCalled(); }); }); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx index 00e74ea570a11..d901bb8cf4e47 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx @@ -10,6 +10,7 @@ import { CoreStart } from 'src/core/public'; import { ActionByType } from '../../../../../../../../src/plugins/ui_actions/public'; import { toMountPoint } from '../../../../../../../../src/plugins/kibana_react/public'; import { DrilldownsStart } from '../../../../../../drilldowns/public'; +import { isEnhancedEmbeddable } from '../../../../../../embeddable_enhanced/public'; import { EmbeddableContext } from '../../../../../../../../src/plugins/embeddable/public'; export const OPEN_FLYOUT_ADD_DRILLDOWN = 'OPEN_FLYOUT_ADD_DRILLDOWN'; @@ -37,7 +38,7 @@ export class FlyoutCreateDrilldownAction implements ActionByType -1; @@ -51,10 +52,12 @@ export class FlyoutCreateDrilldownAction implements ActionByType handle.close()} placeContext={context} viewMode={'create'} - dynamicActionManager={dynamicActionManager} + dynamicActionManager={embeddable.enhancements.dynamicActions} /> ), { diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.test.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.test.tsx index a3f11eb976f90..06a3654258291 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.test.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.test.tsx @@ -9,11 +9,24 @@ import { coreMock } from '../../../../../../../../src/core/public/mocks'; import { drilldownsPluginMock } from '../../../../../../drilldowns/public/mocks'; import { ViewMode } from '../../../../../../../../src/plugins/embeddable/public'; import { uiActionsPluginMock } from '../../../../../../../../src/plugins/ui_actions/public/mocks'; -import { MockEmbeddable } from '../test_helpers'; +import { EnhancedEmbeddable } from '../../../../../../embeddable_enhanced/public'; +import { MockEmbeddable, enhanceEmbeddable } from '../test_helpers'; const overlays = coreMock.createStart().overlays; const drilldowns = drilldownsPluginMock.createStartContract(); -const uiActions = uiActionsPluginMock.createStartContract(); +const uiActionsPlugin = uiActionsPluginMock.createPlugin(); +const uiActions = uiActionsPlugin.doStart(); + +uiActionsPlugin.setup.registerActionFactory({ + id: 'foo', + CollectConfig: {} as any, + createConfig: () => ({}), + isConfigValid: () => true, + create: () => ({ + id: 'test', + execute: async () => {}, + }), +}); const actionParams: FlyoutEditDrilldownParams = { drilldowns: () => Promise.resolve(drilldowns), @@ -39,63 +52,93 @@ test('MenuItem exists', () => { }); describe('isCompatible', () => { - const drilldownAction = new FlyoutEditDrilldownAction(actionParams); + function setupIsCompatible({ + isEdit = true, + isEmbeddableEnhanced = true, + }: { + isEdit?: boolean; + isEmbeddableEnhanced?: boolean; + } = {}) { + const action = new FlyoutEditDrilldownAction(actionParams); + const input = { + id: '', + viewMode: isEdit ? ViewMode.EDIT : ViewMode.VIEW, + }; + const embeddable = new MockEmbeddable(input, { + uiActions, + }); + const context = { + embeddable: (isEmbeddableEnhanced + ? enhanceEmbeddable(embeddable, uiActions) + : embeddable) as EnhancedEmbeddable, + }; + + return { + action, + context, + }; + } + + test('not compatible if no drilldowns', async () => { + const { action, context } = setupIsCompatible(); + expect(await action.isCompatible(context)).toBe(false); + }); + + test('not compatible if embeddable is not enhanced', async () => { + const { action, context } = setupIsCompatible({ isEmbeddableEnhanced: false }); + expect(await action.isCompatible(context)).toBe(false); + }); + + describe('when has at least one drilldown', () => { + test('is compatible in edit mode', async () => { + const { action, context } = setupIsCompatible(); - function checkCompatibility(params: { - isEdit: boolean; - withUiActions: boolean; - }): Promise { - return drilldownAction.isCompatible({ - embeddable: new MockEmbeddable( + await context.embeddable.enhancements.dynamicActions.createEvent( { - id: '', - viewMode: params.isEdit ? ViewMode.EDIT : ViewMode.VIEW, + config: {}, + factoryId: 'foo', + name: '', }, - { - uiActions: params.withUiActions ? uiActions : undefined, // dynamic actions support - } - ), + ['VALUE_CLICK_TRIGGER'] + ); + + expect(await action.isCompatible(context)).toBe(true); }); - } - // TODO: need proper DynamicActionsMock and ActionFactory mock - test.todo('compatible if dynamicUiActions enabled, in edit view, and have at least 1 drilldown'); + test('not compatible in view mode', async () => { + const { action, context } = setupIsCompatible({ isEdit: false }); - test('not compatible if dynamicUiActions disabled', async () => { - expect( - await checkCompatibility({ - withUiActions: false, - isEdit: true, - }) - ).toBe(false); - }); + await context.embeddable.enhancements.dynamicActions.createEvent( + { + config: {}, + factoryId: 'foo', + name: '', + }, + ['VALUE_CLICK_TRIGGER'] + ); - test('not compatible if no drilldowns', async () => { - expect( - await checkCompatibility({ - withUiActions: true, - isEdit: true, - }) - ).toBe(false); + expect(await action.isCompatible(context)).toBe(false); + }); }); }); describe('execute', () => { const drilldownAction = new FlyoutEditDrilldownAction(actionParams); + test('throws error if no dynamicUiActions', async () => { await expect( drilldownAction.execute({ embeddable: new MockEmbeddable({ id: '' }, {}), }) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Can't execute FlyoutEditDrilldownAction without dynamicActionsManager"` + `"Need embeddable to be EnhancedEmbeddable to execute FlyoutEditDrilldownAction."` ); }); test('should open flyout', async () => { const spy = jest.spyOn(overlays, 'openFlyout'); await drilldownAction.execute({ - embeddable: new MockEmbeddable({ id: '' }, { uiActions }), + embeddable: enhanceEmbeddable(new MockEmbeddable({ id: '' }, { uiActions })), }); expect(spy).toBeCalled(); }); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx index 816b757592a72..23a6689bd8d9d 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx @@ -15,6 +15,7 @@ import { EmbeddableContext, ViewMode } from '../../../../../../../../src/plugins import { DrilldownsStart } from '../../../../../../drilldowns/public'; import { txtDisplayName } from './i18n'; import { MenuItem } from './menu_item'; +import { isEnhancedEmbeddable } from '../../../../../../embeddable_enhanced/public'; export const OPEN_FLYOUT_EDIT_DRILLDOWN = 'OPEN_FLYOUT_EDIT_DRILLDOWN'; @@ -42,16 +43,19 @@ export class FlyoutEditDrilldownAction implements ActionByType 0; + if (!isEnhancedEmbeddable(embeddable)) return false; + return embeddable.enhancements.dynamicActions.state.get().events.length > 0; } public async execute(context: EmbeddableContext) { const overlays = await this.params.overlays(); const drilldowns = await this.params.drilldowns(); - const dynamicActionManager = context.embeddable.dynamicActions; - if (!dynamicActionManager) { - throw new Error(`Can't execute FlyoutEditDrilldownAction without dynamicActionsManager`); + const { embeddable } = context; + + if (!isEnhancedEmbeddable(embeddable)) { + throw new Error( + 'Need embeddable to be EnhancedEmbeddable to execute FlyoutEditDrilldownAction.' + ); } const handle = overlays.openFlyout( @@ -60,7 +64,7 @@ export class FlyoutEditDrilldownAction implements ActionByType handle.close()} placeContext={context} viewMode={'manage'} - dynamicActionManager={dynamicActionManager} + dynamicActionManager={embeddable.enhancements.dynamicActions} /> ), { diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.test.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.test.tsx index be693fadf9282..c57bafc88e978 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.test.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.test.tsx @@ -8,8 +8,8 @@ import React from 'react'; import { render, cleanup, act } from '@testing-library/react/pure'; import { MenuItem } from './menu_item'; import { createStateContainer } from '../../../../../../../../src/plugins/kibana_utils/common'; -import { DynamicActionManager } from '../../../../../../../../src/plugins/ui_actions/public'; -import { IEmbeddable } from '../../../../../../../../src/plugins/embeddable/public/lib/embeddables'; +import { UiActionsEnhancedDynamicActionManager as DynamicActionManager } from '../../../../../../advanced_ui_actions/public'; +import { EnhancedEmbeddable } from '../../../../../../embeddable_enhanced/public'; import '@testing-library/jest-dom'; afterEach(cleanup); @@ -20,8 +20,10 @@ test('', () => { ); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.tsx index 4f99fca511b07..ddcea0028409c 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.tsx @@ -6,15 +6,12 @@ import React from 'react'; import { EuiNotificationBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { EmbeddableContext } from '../../../../../../../../src/plugins/embeddable/public'; -import { txtDisplayName } from './i18n'; import { useContainerState } from '../../../../../../../../src/plugins/kibana_utils/common'; +import { EnhancedEmbeddableContext } from '../../../../../../embeddable_enhanced/public'; +import { txtDisplayName } from './i18n'; -export const MenuItem: React.FC<{ context: EmbeddableContext }> = ({ context }) => { - if (!context.embeddable.dynamicActions) - throw new Error('Flyout edit drillldown context menu item requires `dynamicActions`'); - - const { events } = useContainerState(context.embeddable.dynamicActions.state); +export const MenuItem: React.FC<{ context: EnhancedEmbeddableContext }> = ({ context }) => { + const { events } = useContainerState(context.embeddable.enhancements.dynamicActions.state); const count = events.length; return ( diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/test_helpers.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/test_helpers.ts index 9b156b0ba85b4..07751f383fe15 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/test_helpers.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/test_helpers.ts @@ -4,11 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Embeddable, EmbeddableInput } from '../../../../../../../src/plugins/embeddable/public/'; +import { Embeddable, EmbeddableInput } from '../../../../../../../src/plugins/embeddable/public'; +import { EnhancedEmbeddable } from '../../../../../embeddable_enhanced/public'; +import { + UiActionsEnhancedMemoryActionStorage as MemoryActionStorage, + UiActionsEnhancedDynamicActionManager as DynamicActionManager, +} from '../../../../../advanced_ui_actions/public'; import { TriggerContextMapping, UiActionsStart, } from '../../../../../../../src/plugins/ui_actions/public'; +import { uiActionsPluginMock } from '../../../../../../../src/plugins/ui_actions/public/mocks'; export class MockEmbeddable extends Embeddable { public readonly type = 'mock'; @@ -26,3 +32,17 @@ export class MockEmbeddable extends Embeddable { return this.triggers; } } + +export const enhanceEmbeddable = ( + embeddable: E, + uiActions: UiActionsStart = uiActionsPluginMock.createStartContract() +): EnhancedEmbeddable => { + (embeddable as EnhancedEmbeddable).enhancements = { + dynamicActions: new DynamicActionManager({ + storage: new MemoryActionStorage(), + isCompatible: async () => true, + uiActions, + }), + }; + return embeddable as EnhancedEmbeddable; +}; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts index 4bdf03dff3531..3c7089937488b 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts @@ -6,10 +6,8 @@ import { CoreSetup } from 'src/core/public'; import { SetupDependencies } from '../../plugin'; -import { - CONTEXT_MENU_TRIGGER, - EmbeddableContext, -} from '../../../../../../src/plugins/embeddable/public'; +import { CONTEXT_MENU_TRIGGER } from '../../../../../../src/plugins/embeddable/public'; +import { EnhancedEmbeddableContext } from '../../../../embeddable_enhanced/public'; import { FlyoutCreateDrilldownAction, FlyoutEditDrilldownAction, @@ -21,8 +19,8 @@ import { DashboardToDashboardDrilldown } from './dashboard_to_dashboard_drilldow declare module '../../../../../../src/plugins/ui_actions/public' { export interface ActionContextMapping { - [OPEN_FLYOUT_ADD_DRILLDOWN]: EmbeddableContext; - [OPEN_FLYOUT_EDIT_DRILLDOWN]: EmbeddableContext; + [OPEN_FLYOUT_ADD_DRILLDOWN]: EnhancedEmbeddableContext; + [OPEN_FLYOUT_EDIT_DRILLDOWN]: EnhancedEmbeddableContext; } } diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx index f22ccc2f26f02..f4dcbf64d895e 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx @@ -9,13 +9,13 @@ import useMountedState from 'react-use/lib/useMountedState'; import { AdvancedUiActionsActionFactory as ActionFactory, AdvancedUiActionsStart, + UiActionsEnhancedDynamicActionManager as DynamicActionManager, } from '../../../../advanced_ui_actions/public'; import { NotificationsStart } from '../../../../../../src/core/public'; import { DrilldownWizardConfig, FlyoutDrilldownWizard } from '../flyout_drilldown_wizard'; import { FlyoutListManageDrilldowns } from '../flyout_list_manage_drilldowns'; import { IStorageWrapper } from '../../../../../../src/plugins/kibana_utils/public'; import { - DynamicActionManager, UiActionsSerializedEvent, UiActionsSerializedAction, VALUE_CLICK_TRIGGER, diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts index b8deaa8b842bc..780c91eee56c3 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts @@ -6,8 +6,10 @@ import uuid from 'uuid'; import { - DynamicActionManager, - DynamicActionManagerState, + UiActionsEnhancedDynamicActionManager as DynamicActionManager, + UiActionsEnhancedDynamicActionManagerState as DynamicActionManagerState, +} from '../../../../advanced_ui_actions/public'; +import { UiActionsSerializedAction, TriggerContextMapping, } from '../../../../../../src/plugins/ui_actions/public'; diff --git a/x-pack/plugins/embeddable_enhanced/README.md b/x-pack/plugins/embeddable_enhanced/README.md new file mode 100644 index 0000000000000..a0be90731fdb0 --- /dev/null +++ b/x-pack/plugins/embeddable_enhanced/README.md @@ -0,0 +1 @@ +# X-Pack part of `embeddable` plugin diff --git a/x-pack/plugins/embeddable_enhanced/kibana.json b/x-pack/plugins/embeddable_enhanced/kibana.json new file mode 100644 index 0000000000000..9f63a55d5731d --- /dev/null +++ b/x-pack/plugins/embeddable_enhanced/kibana.json @@ -0,0 +1,7 @@ +{ + "id": "embeddableEnhanced", + "version": "kibana", + "server": false, + "ui": true, + "requiredPlugins": ["uiActions", "embeddable"] +} diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts similarity index 93% rename from src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts rename to x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts index 2dfa5fc30ef11..f2aa0f8ed8fdf 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts +++ b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts @@ -1,28 +1,17 @@ /* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. */ -import { Embeddable } from './embeddable'; -import { EmbeddableInput } from './i_embeddable'; -import { ViewMode } from '../types'; +import { + Embeddable, + EmbeddableInput, + ViewMode, +} from '../../../../../src/plugins/embeddable/public'; import { EmbeddableActionStorage } from './embeddable_action_storage'; -import { UiActionsSerializedEvent } from '../../../../ui_actions/public'; -import { of } from '../../../../kibana_utils/public'; +import { UiActionsSerializedEvent } from '../../../../../src/plugins/ui_actions/public'; +import { of } from '../../../../../src/plugins/kibana_utils/public'; class TestEmbeddable extends Embeddable { public readonly type = 'test'; diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts similarity index 54% rename from src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts rename to x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts index fad5b4d535d6c..ad4f82cc529b5 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts +++ b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts @@ -1,36 +1,21 @@ /* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. */ -import { - UiActionsAbstractActionStorage, - UiActionsSerializedEvent, -} from '../../../../ui_actions/public'; -import { Embeddable } from '..'; +import { UiActionsSerializedEvent as SerializedEvent } from '../../../../../src/plugins/ui_actions/public'; +import { UiActionsEnhancedAbstractActionStorage as AbstractActionStorage } from '../../../advanced_ui_actions/public'; +import { IEmbeddable } from '../../../../../src/plugins/embeddable/public'; -export class EmbeddableActionStorage extends UiActionsAbstractActionStorage { - constructor(private readonly embbeddable: Embeddable) { +export class EmbeddableActionStorage extends AbstractActionStorage { + constructor(private readonly embbeddable: IEmbeddable) { super(); } - async create(event: UiActionsSerializedEvent) { + async create(event: SerializedEvent) { const input = this.embbeddable.getInput(); - const events = (input.events || []) as UiActionsSerializedEvent[]; + const events = (input.events || []) as SerializedEvent[]; const exists = !!events.find(({ eventId }) => eventId === event.eventId); if (exists) { @@ -45,9 +30,9 @@ export class EmbeddableActionStorage extends UiActionsAbstractActionStorage { }); } - async update(event: UiActionsSerializedEvent) { + async update(event: SerializedEvent) { const input = this.embbeddable.getInput(); - const events = (input.events || []) as UiActionsSerializedEvent[]; + const events = (input.events || []) as SerializedEvent[]; const index = events.findIndex(({ eventId }) => eventId === event.eventId); if (index === -1) { @@ -65,7 +50,7 @@ export class EmbeddableActionStorage extends UiActionsAbstractActionStorage { async remove(eventId: string) { const input = this.embbeddable.getInput(); - const events = (input.events || []) as UiActionsSerializedEvent[]; + const events = (input.events || []) as SerializedEvent[]; const index = events.findIndex(event => eventId === event.eventId); if (index === -1) { @@ -81,9 +66,9 @@ export class EmbeddableActionStorage extends UiActionsAbstractActionStorage { }); } - async read(eventId: string): Promise { + async read(eventId: string): Promise { const input = this.embbeddable.getInput(); - const events = (input.events || []) as UiActionsSerializedEvent[]; + const events = (input.events || []) as SerializedEvent[]; const event = events.find(ev => eventId === ev.eventId); if (!event) { @@ -98,10 +83,10 @@ export class EmbeddableActionStorage extends UiActionsAbstractActionStorage { private __list() { const input = this.embbeddable.getInput(); - return (input.events || []) as UiActionsSerializedEvent[]; + return (input.events || []) as SerializedEvent[]; } - async list(): Promise { + async list(): Promise { return this.__list(); } } diff --git a/x-pack/plugins/embeddable_enhanced/public/embeddables/index.ts b/x-pack/plugins/embeddable_enhanced/public/embeddables/index.ts new file mode 100644 index 0000000000000..fabbc60a13f67 --- /dev/null +++ b/x-pack/plugins/embeddable_enhanced/public/embeddables/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './is_enhanced_embeddable'; +export * from './embeddable_action_storage'; diff --git a/x-pack/plugins/embeddable_enhanced/public/embeddables/is_enhanced_embeddable.ts b/x-pack/plugins/embeddable_enhanced/public/embeddables/is_enhanced_embeddable.ts new file mode 100644 index 0000000000000..f29430dc6a3de --- /dev/null +++ b/x-pack/plugins/embeddable_enhanced/public/embeddables/is_enhanced_embeddable.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IEmbeddable } from '../../../../../src/plugins/embeddable/public'; +import { EnhancedEmbeddable } from '../types'; + +export const isEnhancedEmbeddable = ( + maybeEnhancedEmbeddable: E +): maybeEnhancedEmbeddable is EnhancedEmbeddable => + typeof (maybeEnhancedEmbeddable as EnhancedEmbeddable) + ?.enhancements?.dynamicActions === 'object'; diff --git a/x-pack/plugins/embeddable_enhanced/public/index.ts b/x-pack/plugins/embeddable_enhanced/public/index.ts new file mode 100644 index 0000000000000..059acf9644820 --- /dev/null +++ b/x-pack/plugins/embeddable_enhanced/public/index.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializerContext } from 'src/core/public'; +import { EmbeddableEnhancedPlugin } from './plugin'; + +export { + SetupContract as EmbeddableEnhancedSetupContract, + SetupDependencies as EmbeddableEnhancedSetupDependencies, + StartContract as EmbeddableEnhancedStartContract, + StartDependencies as EmbeddableEnhancedStartDependencies, +} from './plugin'; + +export function plugin(context: PluginInitializerContext) { + return new EmbeddableEnhancedPlugin(context); +} + +export { EnhancedEmbeddable, EnhancedEmbeddableContext } from './types'; +export { isEnhancedEmbeddable } from './embeddables'; diff --git a/x-pack/plugins/embeddable_enhanced/public/mocks.ts b/x-pack/plugins/embeddable_enhanced/public/mocks.ts new file mode 100644 index 0000000000000..d048d1248b6ff --- /dev/null +++ b/x-pack/plugins/embeddable_enhanced/public/mocks.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EmbeddableEnhancedSetupContract, EmbeddableEnhancedStartContract } from '.'; + +export type Setup = jest.Mocked; +export type Start = jest.Mocked; + +const createSetupContract = (): Setup => { + const setupContract: Setup = {}; + + return setupContract; +}; + +const createStartContract = (): Start => { + const startContract: Start = {}; + + return startContract; +}; + +export const embeddableEnhancedPluginMock = { + createSetupContract, + createStartContract, +}; diff --git a/x-pack/plugins/embeddable_enhanced/public/plugin.ts b/x-pack/plugins/embeddable_enhanced/public/plugin.ts new file mode 100644 index 0000000000000..5a1f07a480937 --- /dev/null +++ b/x-pack/plugins/embeddable_enhanced/public/plugin.ts @@ -0,0 +1,135 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreStart, CoreSetup, Plugin, PluginInitializerContext } from 'src/core/public'; +import { SavedObjectAttributes } from 'kibana/public'; +import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public'; +import { + EmbeddableFactory, + EmbeddableFactoryDefinition, + EmbeddableInput, + EmbeddableOutput, + EmbeddableSetup, + EmbeddableStart, + IEmbeddable, + defaultEmbeddableFactoryProvider, + EmbeddableContext, +} from '../../../../src/plugins/embeddable/public'; +import { EnhancedEmbeddable } from './types'; +import { EmbeddableActionStorage } from './embeddables/embeddable_action_storage'; +import { UiActionsEnhancedDynamicActionManager as DynamicActionManager } from '../../advanced_ui_actions/public'; + +export interface SetupDependencies { + embeddable: EmbeddableSetup; + uiActions: UiActionsSetup; +} + +export interface StartDependencies { + embeddable: EmbeddableStart; + uiActions: UiActionsStart; +} + +// eslint-disable-next-line +export interface SetupContract {} + +// eslint-disable-next-line +export interface StartContract {} + +export class EmbeddableEnhancedPlugin + implements Plugin { + constructor(protected readonly context: PluginInitializerContext) {} + + private uiActions?: StartDependencies['uiActions']; + + public setup(core: CoreSetup, plugins: SetupDependencies): SetupContract { + this.setCustomEmbeddableFactoryProvider(plugins); + + return {}; + } + + public start(core: CoreStart, plugins: StartDependencies): StartContract { + this.uiActions = plugins.uiActions; + + return {}; + } + + public stop() {} + + private setCustomEmbeddableFactoryProvider(plugins: SetupDependencies) { + plugins.embeddable.setCustomEmbeddableFactoryProvider( + < + I extends EmbeddableInput = EmbeddableInput, + O extends EmbeddableOutput = EmbeddableOutput, + E extends IEmbeddable = IEmbeddable, + T extends SavedObjectAttributes = SavedObjectAttributes + >( + def: EmbeddableFactoryDefinition + ): EmbeddableFactory => { + const factory: EmbeddableFactory = defaultEmbeddableFactoryProvider( + def + ); + return { + ...factory, + create: async (...args) => { + const embeddable = await factory.create(...args); + if (!embeddable) return embeddable; + return this.enhanceEmbeddableWithDynamicActions(embeddable); + }, + createFromSavedObject: async (...args) => { + const embeddable = await factory.createFromSavedObject(...args); + if (!embeddable) return embeddable; + return this.enhanceEmbeddableWithDynamicActions(embeddable); + }, + }; + } + ); + } + + private enhanceEmbeddableWithDynamicActions( + embeddable: E + ): EnhancedEmbeddable { + const enhancedEmbeddable = embeddable as EnhancedEmbeddable; + + const storage = new EmbeddableActionStorage(embeddable); + const dynamicActions = new DynamicActionManager({ + isCompatible: async (context: unknown) => + (context as EmbeddableContext).embeddable.runtimeId === embeddable.runtimeId, + storage, + uiActions: this.uiActions!, + }); + + dynamicActions.start().catch(error => { + /* eslint-disable */ + console.log('Failed to start embeddable dynamic actions', embeddable); + console.error(error); + /* eslint-enable */ + }); + + const stop = () => { + dynamicActions.stop().catch(error => { + /* eslint-disable */ + console.log('Failed to stop embeddable dynamic actions', embeddable); + console.error(error); + /* eslint-enable */ + }); + }; + + embeddable.getInput$().subscribe({ + next: () => { + storage.reload$.next(); + }, + error: stop, + complete: stop, + }); + + enhancedEmbeddable.enhancements = { + ...enhancedEmbeddable.enhancements, + dynamicActions, + }; + + return enhancedEmbeddable; + } +} diff --git a/x-pack/plugins/embeddable_enhanced/public/types.ts b/x-pack/plugins/embeddable_enhanced/public/types.ts new file mode 100644 index 0000000000000..924605be332b2 --- /dev/null +++ b/x-pack/plugins/embeddable_enhanced/public/types.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IEmbeddable } from '../../../../src/plugins/embeddable/public'; +import { UiActionsEnhancedDynamicActionManager as DynamicActionManager } from '../../advanced_ui_actions/public'; + +export type EnhancedEmbeddable = E & { + enhancements: { + /** + * Default implementation of dynamic action manager for embeddables. + */ + dynamicActions: DynamicActionManager; + }; +}; + +export interface EnhancedEmbeddableContext { + embeddable: EnhancedEmbeddable; +}