diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6519bf9c493f9..f9f43b804fc92 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -3,7 +3,6 @@ # For more info, see https://help.github.com/articles/about-codeowners/ # App -/x-pack/legacy/plugins/dashboard_enhanced/ @elastic/kibana-app /x-pack/legacy/plugins/lens/ @elastic/kibana-app /x-pack/legacy/plugins/graph/ @elastic/kibana-app /src/legacy/server/url_shortening/ @elastic/kibana-app diff --git a/examples/ui_action_examples/public/plugin.ts b/examples/ui_action_examples/public/plugin.ts index d053f7e82862c..c47746d4b3fd6 100644 --- a/examples/ui_action_examples/public/plugin.ts +++ b/examples/ui_action_examples/public/plugin.ts @@ -46,7 +46,7 @@ export class UiActionExamplesPlugin })); uiActions.registerAction(helloWorldAction); - uiActions.addTriggerAction(helloWorldTrigger.id, helloWorldAction); + uiActions.attachAction(helloWorldTrigger.id, helloWorldAction); } public start() {} diff --git a/examples/ui_actions_explorer/public/app.tsx b/examples/ui_actions_explorer/public/app.tsx index f08b8bb29bdd3..462f5c3bf88ba 100644 --- a/examples/ui_actions_explorer/public/app.tsx +++ b/examples/ui_actions_explorer/public/app.tsx @@ -95,7 +95,8 @@ const ActionsExplorer = ({ uiActionsApi, openModal }: Props) => { ); }, }); - uiActionsApi.addTriggerAction(HELLO_WORLD_TRIGGER_ID, dynamicAction); + uiActionsApi.registerAction(dynamicAction); + uiActionsApi.attachAction(HELLO_WORLD_TRIGGER_ID, dynamicAction); setConfirmationText( `You've successfully added a new action: ${dynamicAction.getDisplayName( {} diff --git a/examples/ui_actions_explorer/public/plugin.tsx b/examples/ui_actions_explorer/public/plugin.tsx index de86b51aee3a8..f1895905a45e1 100644 --- a/examples/ui_actions_explorer/public/plugin.tsx +++ b/examples/ui_actions_explorer/public/plugin.tsx @@ -79,21 +79,21 @@ export class UiActionsExplorerPlugin implements Plugin (await startServices)[1].uiActions) ); - deps.uiActions.addTriggerAction( + deps.uiActions.attachAction( USER_TRIGGER, createEditUserAction(async () => (await startServices)[0].overlays.openModal) ); - deps.uiActions.addTriggerAction(COUNTRY_TRIGGER, viewInMapsAction); - deps.uiActions.addTriggerAction(COUNTRY_TRIGGER, lookUpWeatherAction); - deps.uiActions.addTriggerAction(COUNTRY_TRIGGER, showcasePluggability); - deps.uiActions.addTriggerAction(PHONE_TRIGGER, makePhoneCallAction); - deps.uiActions.addTriggerAction(PHONE_TRIGGER, showcasePluggability); - deps.uiActions.addTriggerAction(USER_TRIGGER, showcasePluggability); + deps.uiActions.attachAction(COUNTRY_TRIGGER, viewInMapsAction); + deps.uiActions.attachAction(COUNTRY_TRIGGER, lookUpWeatherAction); + deps.uiActions.attachAction(COUNTRY_TRIGGER, showcasePluggability); + deps.uiActions.attachAction(PHONE_TRIGGER, makePhoneCallAction); + deps.uiActions.attachAction(PHONE_TRIGGER, showcasePluggability); + deps.uiActions.attachAction(USER_TRIGGER, showcasePluggability); core.application.register({ id: 'uiActionsExplorer', diff --git a/src/core/public/overlays/flyout/flyout_service.tsx b/src/core/public/overlays/flyout/flyout_service.tsx index 444430175d4f2..b609b2ce1d741 100644 --- a/src/core/public/overlays/flyout/flyout_service.tsx +++ b/src/core/public/overlays/flyout/flyout_service.tsx @@ -91,7 +91,6 @@ export interface OverlayFlyoutStart { export interface OverlayFlyoutOpenOptions { className?: string; closeButtonAriaLabel?: string; - ownFocus?: boolean; 'data-test-subj'?: string; } diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts index 370abc120d475..8ed64f004c9be 100644 --- a/src/dev/storybook/aliases.ts +++ b/src/dev/storybook/aliases.ts @@ -18,14 +18,12 @@ */ export const storybookAliases = { - advanced_ui_actions: 'x-pack/plugins/advanced_ui_actions/scripts/storybook.js', apm: 'x-pack/legacy/plugins/apm/scripts/storybook.js', canvas: 'x-pack/legacy/plugins/canvas/scripts/storybook_new.js', codeeditor: 'src/plugins/kibana_react/public/code_editor/scripts/storybook.ts', - dashboard_enhanced: 'x-pack/plugins/dashboard_enhanced/scripts/storybook.js', drilldowns: 'x-pack/plugins/drilldowns/scripts/storybook.js', embeddable: 'src/plugins/embeddable/scripts/storybook.js', infra: 'x-pack/legacy/plugins/infra/scripts/storybook.js', siem: 'x-pack/legacy/plugins/siem/scripts/storybook.js', - ui_actions: 'src/plugins/ui_actions/scripts/storybook.js', + ui_actions: 'x-pack/plugins/advanced_ui_actions/scripts/storybook.js', }; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts index 4b21be83f1722..342824bade3dd 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts @@ -45,7 +45,6 @@ import { PersistedState } from '../../../../../../../plugins/visualizations/publ import { buildPipeline } from '../legacy/build_pipeline'; import { Vis } from '../vis'; import { getExpressions, getUiActions } from '../services'; -import { VisualizationsStartDeps } from '../plugin'; import { VIS_EVENT_TO_TRIGGER } from './events'; const getKeys = (o: T): Array => Object.keys(o) as Array; @@ -57,7 +56,6 @@ export interface VisualizeEmbeddableConfiguration { editable: boolean; appState?: { save(): void }; uiState?: PersistedState; - uiActions?: VisualizationsStartDeps['uiActions']; } export interface VisualizeInput extends EmbeddableInput { @@ -96,7 +94,7 @@ export class VisualizeEmbeddable extends Embeddable { public readonly type = VISUALIZE_EMBEDDABLE_TYPE; - constructor( - private readonly getUiActions: () => Promise< - Pick['uiActions'] - > - ) { + constructor() { super({ savedObjectMetaData: { name: i18n.translate('visualizations.savedObjectName', { defaultMessage: 'Visualization' }), @@ -119,8 +114,6 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< const indexPattern = vis.data.indexPattern; const indexPatterns = indexPattern ? [indexPattern] : []; - const uiActions = await this.getUiActions(); - const editable = await this.isEditable(); return new VisualizeEmbeddable( getTimeFilter(), @@ -131,7 +124,6 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< editable, appState: input.appState, uiState: input.uiState, - uiActions, }, input, parent diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts index dcd11c920f17c..17f777e4e80e1 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts @@ -17,7 +17,7 @@ * under the License. */ -import { CoreSetup, PluginInitializerContext } from '../../../../../../core/public'; +import { PluginInitializerContext } from '../../../../../../core/public'; import { VisualizationsSetup, VisualizationsStart } from './'; import { VisualizationsPlugin } from './plugin'; import { coreMock } from '../../../../../../core/public/mocks'; @@ -26,7 +26,6 @@ import { expressionsPluginMock } from '../../../../../../plugins/expressions/pub import { dataPluginMock } from '../../../../../../plugins/data/public/mocks'; import { usageCollectionPluginMock } from '../../../../../../plugins/usage_collection/public/mocks'; import { uiActionsPluginMock } from '../../../../../../plugins/ui_actions/public/mocks'; -import { VisualizationsStartDeps } from './plugin'; const createSetupContract = (): VisualizationsSetup => ({ createBaseVisualization: jest.fn(), @@ -49,7 +48,7 @@ const createStartContract = (): VisualizationsStart => ({ const createInstance = async () => { const plugin = new VisualizationsPlugin({} as PluginInitializerContext); - const setup = plugin.setup(coreMock.createSetup() as CoreSetup, { + const setup = plugin.setup(coreMock.createSetup(), { data: dataPluginMock.createSetupContract(), expressions: expressionsPluginMock.createSetupContract(), embeddable: embeddablePluginMock.createSetupContract(), diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts index c826841e2bcf3..3ade6cee0d4d2 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts @@ -111,7 +111,7 @@ export class VisualizationsPlugin constructor(initializerContext: PluginInitializerContext) {} public setup( - core: CoreSetup, + core: CoreSetup, { expressions, embeddable, usageCollection, data }: VisualizationsSetupDeps ): VisualizationsSetup { setUISettings(core.uiSettings); @@ -120,9 +120,7 @@ export class VisualizationsPlugin expressions.registerFunction(visualizationFunction); expressions.registerRenderer(visualizationRenderer); - const embeddableFactory = new VisualizeEmbeddableFactory( - async () => (await core.getStartServices())[1].uiActions - ); + const embeddableFactory = new VisualizeEmbeddableFactory(); embeddable.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); return { diff --git a/src/plugins/dashboard/public/actions/replace_panel_action.tsx b/src/plugins/dashboard/public/actions/replace_panel_action.tsx index 4e20aa3c35088..21ec961917d17 100644 --- a/src/plugins/dashboard/public/actions/replace_panel_action.tsx +++ b/src/plugins/dashboard/public/actions/replace_panel_action.tsx @@ -37,7 +37,7 @@ export interface ReplacePanelActionContext { export class ReplacePanelAction implements ActionByType { public readonly type = ACTION_REPLACE_PANEL; public readonly id = ACTION_REPLACE_PANEL; - public order = 3; + public order = 11; constructor( private core: CoreStart, diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index 3d67435e6d8f7..df3c312c7ae1b 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -87,7 +87,7 @@ export class DashboardEmbeddableContainerPublicPlugin ): Setup { const expandPanelAction = new ExpandPanelAction(); uiActions.registerAction(expandPanelAction); - uiActions.attachAction(CONTEXT_MENU_TRIGGER, expandPanelAction.id); + uiActions.attachAction(CONTEXT_MENU_TRIGGER, expandPanelAction); const startServices = core.getStartServices(); if (share) { @@ -146,7 +146,7 @@ export class DashboardEmbeddableContainerPublicPlugin plugins.embeddable.getEmbeddableFactories ); uiActions.registerAction(changeViewAction); - uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, changeViewAction); + uiActions.attachAction(CONTEXT_MENU_TRIGGER, changeViewAction); const savedDashboardLoader = createSavedDashboardLoader({ savedObjectsClient: core.savedObjects.client, indexPatterns, diff --git a/src/plugins/dashboard/public/tests/dashboard_container.test.tsx b/src/plugins/dashboard/public/tests/dashboard_container.test.tsx index 4aede3f3442fb..a81d80b440e04 100644 --- a/src/plugins/dashboard/public/tests/dashboard_container.test.tsx +++ b/src/plugins/dashboard/public/tests/dashboard_container.test.tsx @@ -49,7 +49,7 @@ test('DashboardContainer in edit mode shows edit mode actions', async () => { const editModeAction = createEditModeAction(); uiActionsSetup.registerAction(editModeAction); - uiActionsSetup.addTriggerAction(CONTEXT_MENU_TRIGGER, editModeAction); + uiActionsSetup.attachAction(CONTEXT_MENU_TRIGGER, editModeAction); setup.registerEmbeddableFactory( CONTACT_CARD_EMBEDDABLE, new ContactCardEmbeddableFactory({} as any, (() => null) as any, {} as any) diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index ea2e85947aa12..fc5dde94fa851 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -109,12 +109,12 @@ export class DataPublicPlugin implements Plugin { public readonly type = ACTION_EDIT_PANEL; public readonly id = ACTION_EDIT_PANEL; - public order = 50; + public order = 15; constructor(private readonly getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory']) {} diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx index 35973cc16cf9b..eb10c16806640 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx @@ -16,35 +16,23 @@ * specific language governing permissions and limitations * under the License. */ -import { cloneDeep, isEqual } from 'lodash'; +import { isEqual, cloneDeep } from 'lodash'; import * as Rx from 'rxjs'; -import { Adapters, ViewMode } from '../types'; +import { Adapters } from '../types'; import { IContainer } from '../containers'; -import { EmbeddableInput, EmbeddableOutput, IEmbeddable } from './i_embeddable'; +import { IEmbeddable, EmbeddableInput, EmbeddableOutput } from './i_embeddable'; +import { ViewMode } from '../types'; import { TriggerContextMapping } from '../ui_actions'; import { EmbeddableActionStorage } from './embeddable_action_storage'; -import { - UiActionsDynamicActionManager, - UiActionsStart, -} from '../../../../../plugins/ui_actions/public'; -import { EmbeddableContext } from '../triggers'; function getPanelTitle(input: EmbeddableInput, output: EmbeddableOutput) { return input.hidePanelTitles ? '' : input.title === undefined ? output.defaultTitle : input.title; } -export interface EmbeddableParams { - uiActions?: UiActionsStart; -} - export abstract class Embeddable< TEmbeddableInput extends EmbeddableInput = EmbeddableInput, TEmbeddableOutput extends EmbeddableOutput = EmbeddableOutput > implements IEmbeddable { - static runtimeId: number = 0; - - public readonly runtimeId = Embeddable.runtimeId++; - public readonly parent?: IContainer; public readonly isContainer: boolean = false; public abstract readonly type: string; @@ -60,34 +48,15 @@ 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; + private __actionStorage?: EmbeddableActionStorage; + public get actionStorage(): EmbeddableActionStorage { + return this.__actionStorage || (this.__actionStorage = new EmbeddableActionStorage(this)); } - constructor( - input: TEmbeddableInput, - output: TEmbeddableOutput, - parent?: IContainer, - public readonly params: EmbeddableParams = {} - ) { + constructor(input: TEmbeddableInput, output: TEmbeddableOutput, parent?: IContainer) { this.id = input.id; this.output = { title: getPanelTitle(input, output), @@ -111,18 +80,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 { @@ -201,20 +158,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(); - } - if (this.parentSubscription) { this.parentSubscription.unsubscribe(); } diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts b/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts index 83fd3f184e098..56facc37fc666 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts @@ -20,8 +20,7 @@ import { Embeddable } from './embeddable'; import { EmbeddableInput } from './i_embeddable'; import { ViewMode } from '../types'; -import { EmbeddableActionStorage } from './embeddable_action_storage'; -import { UiActionsSerializedEvent } from '../../../../ui_actions/public'; +import { EmbeddableActionStorage, SerializedEvent } from './embeddable_action_storage'; import { of } from '../../../../kibana_utils/common'; class TestEmbeddable extends Embeddable { @@ -43,9 +42,9 @@ describe('EmbeddableActionStorage', () => { test('can add event to embeddable', async () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event: UiActionsSerializedEvent = { + const event: SerializedEvent = { eventId: 'EVENT_ID', - triggers: ['TRIGGER-ID'], + triggerId: 'TRIGGER-ID', action: {} as any, }; @@ -58,40 +57,23 @@ describe('EmbeddableActionStorage', () => { expect(events2).toEqual([event]); }); - test('does not merge .getInput() into .updateInput()', async () => { - const embeddable = new TestEmbeddable(); - const storage = new EmbeddableActionStorage(embeddable); - const event: UiActionsSerializedEvent = { - eventId: 'EVENT_ID', - triggers: ['TRIGGER-ID'], - action: {} as any, - }; - - const spy = jest.spyOn(embeddable, 'updateInput'); - - await storage.create(event); - - expect(spy.mock.calls[0][0].id).toBe(undefined); - expect(spy.mock.calls[0][0].viewMode).toBe(undefined); - }); - test('can create multiple events', async () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event1: UiActionsSerializedEvent = { + const event1: SerializedEvent = { eventId: 'EVENT_ID1', - triggers: ['TRIGGER-ID'], + triggerId: 'TRIGGER-ID', action: {} as any, }; - const event2: UiActionsSerializedEvent = { + const event2: SerializedEvent = { eventId: 'EVENT_ID2', - triggers: ['TRIGGER-ID'], + triggerId: 'TRIGGER-ID', action: {} as any, }; - const event3: UiActionsSerializedEvent = { + const event3: SerializedEvent = { eventId: 'EVENT_ID3', - triggers: ['TRIGGER-ID'], + triggerId: 'TRIGGER-ID', action: {} as any, }; @@ -113,9 +95,9 @@ describe('EmbeddableActionStorage', () => { test('throws when creating an event with the same ID', async () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event: UiActionsSerializedEvent = { + const event: SerializedEvent = { eventId: 'EVENT_ID', - triggers: ['TRIGGER-ID'], + triggerId: 'TRIGGER-ID', action: {} as any, }; @@ -140,16 +122,16 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event1: UiActionsSerializedEvent = { + const event1: SerializedEvent = { eventId: 'EVENT_ID', - triggers: ['TRIGGER-ID'], + triggerId: 'TRIGGER-ID', action: { name: 'foo', } as any, }; - const event2: UiActionsSerializedEvent = { + const event2: SerializedEvent = { eventId: 'EVENT_ID', - triggers: ['TRIGGER-ID'], + triggerId: 'TRIGGER-ID', action: { name: 'bar', } as any, @@ -166,30 +148,30 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event1: UiActionsSerializedEvent = { + const event1: SerializedEvent = { eventId: 'EVENT_ID1', - triggers: ['TRIGGER-ID'], + triggerId: 'TRIGGER-ID', action: { name: 'foo', } as any, }; - const event2: UiActionsSerializedEvent = { + const event2: SerializedEvent = { eventId: 'EVENT_ID2', - triggers: ['TRIGGER-ID'], + triggerId: 'TRIGGER-ID', action: { name: 'bar', } as any, }; - const event22: UiActionsSerializedEvent = { + const event22: SerializedEvent = { eventId: 'EVENT_ID2', - triggers: ['TRIGGER-ID'], + triggerId: 'TRIGGER-ID', action: { name: 'baz', } as any, }; - const event3: UiActionsSerializedEvent = { + const event3: SerializedEvent = { eventId: 'EVENT_ID3', - triggers: ['TRIGGER-ID'], + triggerId: 'TRIGGER-ID', action: { name: 'qux', } as any, @@ -217,9 +199,9 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event: UiActionsSerializedEvent = { + const event: SerializedEvent = { eventId: 'EVENT_ID', - triggers: ['TRIGGER-ID'], + triggerId: 'TRIGGER-ID', action: {} as any, }; @@ -235,14 +217,14 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event1: UiActionsSerializedEvent = { + const event1: SerializedEvent = { eventId: 'EVENT_ID1', - triggers: ['TRIGGER-ID'], + triggerId: 'TRIGGER-ID', action: {} as any, }; - const event2: UiActionsSerializedEvent = { + const event2: SerializedEvent = { eventId: 'EVENT_ID2', - triggers: ['TRIGGER-ID'], + triggerId: 'TRIGGER-ID', action: {} as any, }; @@ -267,9 +249,9 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event: UiActionsSerializedEvent = { + const event: SerializedEvent = { eventId: 'EVENT_ID', - triggers: ['TRIGGER-ID'], + triggerId: 'TRIGGER-ID', action: {} as any, }; @@ -284,23 +266,23 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event1: UiActionsSerializedEvent = { + const event1: SerializedEvent = { eventId: 'EVENT_ID1', - triggers: ['TRIGGER-ID'], + triggerId: 'TRIGGER-ID', action: { name: 'foo', } as any, }; - const event2: UiActionsSerializedEvent = { + const event2: SerializedEvent = { eventId: 'EVENT_ID2', - triggers: ['TRIGGER-ID'], + triggerId: 'TRIGGER-ID', action: { name: 'bar', } as any, }; - const event3: UiActionsSerializedEvent = { + const event3: SerializedEvent = { eventId: 'EVENT_ID3', - triggers: ['TRIGGER-ID'], + triggerId: 'TRIGGER-ID', action: { name: 'qux', } as any, @@ -345,9 +327,9 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event: UiActionsSerializedEvent = { + const event: SerializedEvent = { eventId: 'EVENT_ID', - triggers: ['TRIGGER-ID'], + triggerId: 'TRIGGER-ID', action: {} as any, }; @@ -373,9 +355,9 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event: UiActionsSerializedEvent = { + const event: SerializedEvent = { eventId: 'EVENT_ID', - triggers: ['TRIGGER-ID'], + triggerId: 'TRIGGER-ID', action: {} as any, }; @@ -401,9 +383,9 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event: UiActionsSerializedEvent = { + const event: SerializedEvent = { eventId: 'EVENT_ID', - triggers: ['TRIGGER-ID'], + triggerId: 'TRIGGER-ID', action: {} as any, }; @@ -420,19 +402,19 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event1: UiActionsSerializedEvent = { + const event1: SerializedEvent = { eventId: 'EVENT_ID1', - triggers: ['TRIGGER-ID'], + triggerId: 'TRIGGER-ID1', action: {} as any, }; - const event2: UiActionsSerializedEvent = { + const event2: SerializedEvent = { eventId: 'EVENT_ID2', - triggers: ['TRIGGER-ID'], + triggerId: 'TRIGGER-ID2', action: {} as any, }; - const event3: UiActionsSerializedEvent = { + const event3: SerializedEvent = { eventId: 'EVENT_ID3', - triggers: ['TRIGGER-ID'], + triggerId: 'TRIGGER-ID3', action: {} as any, }; @@ -476,7 +458,7 @@ describe('EmbeddableActionStorage', () => { await storage.create({ eventId: 'EVENT_ID1', - triggers: ['TRIGGER-ID'], + triggerId: 'TRIGGER-ID1', action: {} as any, }); @@ -484,7 +466,7 @@ describe('EmbeddableActionStorage', () => { await storage.create({ eventId: 'EVENT_ID2', - triggers: ['TRIGGER-ID'], + triggerId: 'TRIGGER-ID1', action: {} as any, }); @@ -520,15 +502,15 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event1: UiActionsSerializedEvent = { + const event1: SerializedEvent = { eventId: 'EVENT_ID1', - triggers: ['TRIGGER-ID'], + triggerId: 'TRIGGER-ID1', action: {} as any, }; - const event2: UiActionsSerializedEvent = { + const event2: SerializedEvent = { eventId: 'EVENT_ID2', - triggers: ['TRIGGER-ID'], + triggerId: 'TRIGGER-ID1', action: {} as any, }; diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts b/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts index fad5b4d535d6c..520f92840c5f9 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts @@ -17,20 +17,32 @@ * under the License. */ -import { - UiActionsAbstractActionStorage, - UiActionsSerializedEvent, -} from '../../../../ui_actions/public'; import { Embeddable } from '..'; -export class EmbeddableActionStorage extends UiActionsAbstractActionStorage { - constructor(private readonly embbeddable: Embeddable) { - super(); - } +/** + * Below two interfaces are here temporarily, they will move to `ui_actions` + * plugin once #58216 is merged. + */ +export interface SerializedEvent { + eventId: string; + triggerId: string; + action: unknown; +} +export interface ActionStorage { + create(event: SerializedEvent): Promise; + update(event: SerializedEvent): Promise; + remove(eventId: string): Promise; + read(eventId: string): Promise; + count(): Promise; + list(): Promise; +} - async create(event: UiActionsSerializedEvent) { +export class EmbeddableActionStorage implements ActionStorage { + constructor(private readonly embbeddable: Embeddable) {} + + 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) { @@ -41,13 +53,14 @@ export class EmbeddableActionStorage extends UiActionsAbstractActionStorage { } this.embbeddable.updateInput({ + ...input, events: [...events, event], }); } - 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) { @@ -59,13 +72,14 @@ export class EmbeddableActionStorage extends UiActionsAbstractActionStorage { } this.embbeddable.updateInput({ + ...input, events: [...events.slice(0, index), event, ...events.slice(index + 1)], }); } 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) { @@ -77,13 +91,14 @@ export class EmbeddableActionStorage extends UiActionsAbstractActionStorage { } this.embbeddable.updateInput({ + ...input, events: [...events.slice(0, index), ...events.slice(index + 1)], }); } - 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 +113,14 @@ export class EmbeddableActionStorage extends UiActionsAbstractActionStorage { private __list() { const input = this.embbeddable.getInput(); - return (input.events || []) as UiActionsSerializedEvent[]; + return (input.events || []) as SerializedEvent[]; + } + + async count(): Promise { + return this.__list().length; } - async list(): Promise { + async list(): Promise { return this.__list(); } } diff --git a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts index 9a4452aceba00..6345c34b0dda2 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'; @@ -34,7 +33,7 @@ export interface EmbeddableInput { /** * Reserved key for `ui_actions` events. */ - events?: Array<{ eventId: string }>; + events?: unknown; /** * List of action IDs that this embeddable should not render. @@ -83,19 +82,6 @@ export interface IEmbeddable< **/ readonly id: string; - /** - * Unique ID an embeddable is assigned each time it is initialized. This ID - * is different for different instances of the same embeddable. For example, - * if the same dashboard is rendered twice on the screen, all embeddable - * instances will have a unique `runtimeId`. - */ - readonly runtimeId?: number; - - /** - * Default implementation of dynamic action API for embeddables. - */ - dynamicActions?: UiActionsDynamicActionManager; - /** * A functional representation of the isContainer variable, but helpful for typescript to * know the shape if this returns true diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx index 83d3d5e10761b..757d4e6bfddef 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx @@ -44,7 +44,7 @@ import { import { inspectorPluginMock } from 'src/plugins/inspector/public/mocks'; import { EuiBadge } from '@elastic/eui'; -const actionRegistry = new Map(); +const actionRegistry = new Map>(); const triggerRegistry = new Map(); const embeddableFactories = new Map(); const getEmbeddableFactory = (id: string) => embeddableFactories.get(id); @@ -213,17 +213,13 @@ const renderInEditModeAndOpenContextMenu = async ( }; test('HelloWorldContainer in edit mode hides disabledActions', async () => { - const action = { + const action: Action = { id: 'FOO', type: 'FOO' as ActionType, getIconType: () => undefined, getDisplayName: () => 'foo', isCompatible: async () => true, execute: async () => {}, - order: 10, - getHref: () => { - return undefined; - }, }; const getActions = () => Promise.resolve([action]); @@ -249,17 +245,13 @@ test('HelloWorldContainer in edit mode hides disabledActions', async () => { }); test('HelloWorldContainer hides disabled badges', async () => { - const action = { + const action: Action = { id: 'BAR', type: 'BAR' as ActionType, getIconType: () => undefined, getDisplayName: () => 'bar', isCompatible: async () => true, execute: async () => {}, - order: 10, - getHref: () => { - return undefined; - }, }; const getActions = () => Promise.resolve([action]); diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx index c6537f2d94994..b95060a73252f 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx @@ -38,14 +38,6 @@ import { EditPanelAction } from '../actions'; import { CustomizePanelModal } from './panel_header/panel_actions/customize_title/customize_panel_modal'; import { EmbeddableStart } from '../../plugin'; -const sortByOrderField = ( - { order: orderA }: { order?: number }, - { order: orderB }: { order?: number } -) => (orderB || 0) - (orderA || 0); - -const removeById = (disabledActions: string[]) => ({ id }: { id: string }) => - disabledActions.indexOf(id) === -1; - interface Props { embeddable: IEmbeddable; getActions: UiActionsService['getTriggerCompatibleActions']; @@ -65,14 +57,12 @@ interface State { hidePanelTitles: boolean; closeContextMenu: boolean; badges: Array>; - eventCount?: number; } export class EmbeddablePanel extends React.Component { private embeddableRoot: React.RefObject; private parentSubscription?: Subscription; private subscription?: Subscription; - private eventCountSubscription?: Subscription; private mounted: boolean = false; private generateId = htmlIdGenerator(); @@ -146,9 +136,6 @@ export class EmbeddablePanel extends React.Component { if (this.subscription) { this.subscription.unsubscribe(); } - if (this.eventCountSubscription) { - this.eventCountSubscription.unsubscribe(); - } if (this.parentSubscription) { this.parentSubscription.unsubscribe(); } @@ -190,7 +177,6 @@ export class EmbeddablePanel extends React.Component { badges={this.state.badges} embeddable={this.props.embeddable} headerId={headerId} - eventCount={this.state.eventCount} /> )}
@@ -202,15 +188,6 @@ export class EmbeddablePanel extends React.Component { if (this.embeddableRoot.current) { this.props.embeddable.render(this.embeddableRoot.current); } - - const dynamicActions = this.props.embeddable.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 }); - }); - } } closeMyContextMenuPanel = () => { @@ -224,14 +201,13 @@ export class EmbeddablePanel extends React.Component { }; private getActionContextMenuPanel = async () => { - let regularActions = await this.props.getActions(CONTEXT_MENU_TRIGGER, { + let actions = await this.props.getActions(CONTEXT_MENU_TRIGGER, { embeddable: this.props.embeddable, }); const { disabledActions } = this.props.embeddable.getInput(); if (disabledActions) { - const removeDisabledActions = removeById(disabledActions); - regularActions = regularActions.filter(removeDisabledActions); + actions = actions.filter(action => disabledActions.indexOf(action.id) === -1); } const createGetUserData = (overlays: OverlayStart) => @@ -270,10 +246,16 @@ export class EmbeddablePanel extends React.Component { new EditPanelAction(this.props.getEmbeddableFactory), ]; - const sortedActions = [...regularActions, ...extraActions].sort(sortByOrderField); + const sorted = actions + .concat(extraActions) + .sort((a: Action, b: Action) => { + const bOrder = b.order || 0; + const aOrder = a.order || 0; + return bOrder - aOrder; + }); return await buildContextMenuForActions({ - actions: sortedActions, + actions: sorted, actionContext: { embeddable: this.props.embeddable }, closeMenu: this.closeMyContextMenuPanel, }); diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.ts b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.ts index 36957c3b79491..c0e43c0538833 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.ts +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.ts @@ -33,13 +33,15 @@ interface ActionContext { export class CustomizePanelTitleAction implements Action { public readonly type = ACTION_CUSTOMIZE_PANEL; public id = ACTION_CUSTOMIZE_PANEL; - public order = 40; + public order = 10; - constructor(private readonly getDataFromUser: GetUserData) {} + constructor(private readonly getDataFromUser: GetUserData) { + this.order = 10; + } public getDisplayName() { return i18n.translate('embeddableApi.customizePanel.action.displayName', { - defaultMessage: 'Edit panel title', + defaultMessage: 'Customize panel', }); } diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.ts b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.ts index ae9645767b267..d04f35715537c 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.ts +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.ts @@ -31,7 +31,7 @@ interface ActionContext { export class InspectPanelAction implements Action { public readonly type = ACTION_INSPECT_PANEL; public readonly id = ACTION_INSPECT_PANEL; - public order = 20; + public order = 10; constructor(private readonly inspector: InspectorStartContract) {} diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/remove_panel_action.ts b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/remove_panel_action.ts index a6d4128f3f106..ee7948f3d6a4a 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/remove_panel_action.ts +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/remove_panel_action.ts @@ -41,7 +41,7 @@ function hasExpandedPanelInput( export class RemovePanelAction implements Action { public readonly type = REMOVE_PANEL_ACTION; public readonly id = REMOVE_PANEL_ACTION; - public order = 1; + public order = 5; constructor() {} diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx index 2a856af7ae916..99516a1d21d6f 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx @@ -23,7 +23,6 @@ import { EuiIcon, EuiToolTip, EuiScreenReaderOnly, - EuiNotificationBadge, } from '@elastic/eui'; import classNames from 'classnames'; import React from 'react'; @@ -41,7 +40,6 @@ export interface PanelHeaderProps { badges: Array>; embeddable: IEmbeddable; headerId?: string; - eventCount?: number; } function renderBadges(badges: Array>, embeddable: IEmbeddable) { @@ -92,7 +90,6 @@ export function PanelHeader({ badges, embeddable, headerId, - eventCount, }: PanelHeaderProps) { const viewDescription = getViewDescription(embeddable); const showTitle = !isViewMode || (title && !hidePanelTitles) || viewDescription !== ''; @@ -150,11 +147,7 @@ export function PanelHeader({ )} {renderBadges(badges, embeddable)} - {!isViewMode && !!eventCount && ( - - {eventCount} - - )} + >( - container: Container -): UnboxState => useObservable(container.state$, container.get()); - -/** - * Apply selector to state container to extract only needed information. Will - * re-render your component only when the section changes. - * - * @param container State container which state to track. - * @param selector Function used to pick parts of state. - * @param comparator Comparator function used to memoize previous result, to not - * re-render React component if state did not change. By default uses - * `fast-deep-equal` package. - */ -export const useContainerSelector = , Result>( - container: Container, - selector: (state: UnboxState) => Result, - comparator: Comparator = defaultComparator -): Result => { - const { state$, get } = container; - const lastValueRef = useRef(get()); - const [value, setValue] = React.useState(() => { - const newValue = selector(get()); - lastValueRef.current = newValue; - return newValue; - }); - useLayoutEffect(() => { - const subscription = state$.subscribe((currentState: UnboxState) => { - const newValue = selector(currentState); - if (!comparator(lastValueRef.current, newValue)) { - lastValueRef.current = newValue; - setValue(newValue); - } - }); - return () => subscription.unsubscribe(); - }, [state$, comparator]); - return value; -}; - export const createStateContainerReactHelpers = >() => { const context = React.createContext(null as any); const useContainer = (): Container => useContext(context); const useState = (): UnboxState => { - const container = useContainer(); - return useContainerState(container); + const { state$, get } = useContainer(); + const value = useObservable(state$, get()); + return value; }; const useTransitions: () => Container['transitions'] = () => useContainer().transitions; @@ -84,8 +41,24 @@ export const createStateContainerReactHelpers = ) => Result, comparator: Comparator = defaultComparator ): Result => { - const container = useContainer(); - return useContainerSelector(container, selector, comparator); + const { state$, get } = useContainer(); + const lastValueRef = useRef(get()); + const [value, setValue] = React.useState(() => { + const newValue = selector(get()); + lastValueRef.current = newValue; + return newValue; + }); + useLayoutEffect(() => { + const subscription = state$.subscribe((currentState: UnboxState) => { + const newValue = selector(currentState); + if (!comparator(lastValueRef.current, newValue)) { + lastValueRef.current = newValue; + setValue(newValue); + } + }); + return () => subscription.unsubscribe(); + }, [state$, comparator]); + return value; }; const connect: Connect> = mapStateToProp => component => props => diff --git a/src/plugins/kibana_utils/common/state_containers/types.ts b/src/plugins/kibana_utils/common/state_containers/types.ts index 29ffa4cd486b5..26a29bc470e8a 100644 --- a/src/plugins/kibana_utils/common/state_containers/types.ts +++ b/src/plugins/kibana_utils/common/state_containers/types.ts @@ -43,7 +43,7 @@ export interface BaseStateContainer { export interface StateContainer< State extends BaseState, - PureTransitions extends object = object, + PureTransitions extends object, PureSelectors extends object = {} > extends BaseStateContainer { transitions: Readonly>; diff --git a/src/plugins/kibana_utils/index.ts b/src/plugins/kibana_utils/index.ts deleted file mode 100644 index 14d6e52dc0465..0000000000000 --- a/src/plugins/kibana_utils/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { createStateContainer, StateContainer, of } from './common'; diff --git a/src/plugins/ui_actions/public/actions/action.ts b/src/plugins/ui_actions/public/actions/action.ts index 15f1d6dd79289..2b2fc004a84c6 100644 --- a/src/plugins/ui_actions/public/actions/action.ts +++ b/src/plugins/ui_actions/public/actions/action.ts @@ -19,12 +19,10 @@ import { UiComponent } from 'src/plugins/kibana_utils/common'; import { ActionType, ActionContextMapping } from '../types'; -import { Presentable } from '../util/presentable'; export type ActionByType = Action; -export interface Action - extends Partial> { +export interface Action { /** * Determined the order when there is more than one action matched to a trigger. * Higher numbers are displayed first. @@ -65,30 +63,12 @@ export interface Action isCompatible(context: Context): Promise; /** - * Executes the action. + * If this returns something truthy, this is used in addition to the `execute` method when clicked. */ - execute(context: Context): Promise; -} - -/** - * A convenience interface used to register an action. - */ -export interface ActionDefinition - extends Partial> { - /** - * ID of the action that uniquely identifies this action in the actions registry. - */ - readonly id: string; - - /** - * ID of the factory for this action. Used to construct dynamic actions. - */ - readonly type?: ActionType; + getHref?(context: Context): string | undefined; /** * Executes the action. */ execute(context: Context): Promise; } - -export type ActionContext = A extends ActionDefinition ? Context : never; diff --git a/src/plugins/ui_actions/public/util/presentable.ts b/src/plugins/ui_actions/public/actions/action_definition.ts similarity index 50% rename from src/plugins/ui_actions/public/util/presentable.ts rename to src/plugins/ui_actions/public/actions/action_definition.ts index 945fd2065ce78..c590cf8f34ee0 100644 --- a/src/plugins/ui_actions/public/util/presentable.ts +++ b/src/plugins/ui_actions/public/actions/action_definition.ts @@ -18,46 +18,55 @@ */ import { UiComponent } from 'src/plugins/kibana_utils/common'; +import { ActionType, ActionContextMapping } from '../types'; -/** - * Represents something that can be displayed to user in UI. - */ -export interface Presentable { +export interface ActionDefinition { /** - * ID that uniquely identifies this object. + * Determined the order when there is more than one action matched to a trigger. + * Higher numbers are displayed first. */ - readonly id: string; + order?: number; /** - * Determines the display order in relation to other items. Higher numbers are - * displayed first. + * A unique identifier for this action instance. */ - readonly order: number; + id?: string; /** - * `UiComponent` to render when displaying this entity as a context menu item. - * If not provided, `getDisplayName` will be used instead. + * The action type is what determines the context shape. */ - readonly MenuItem?: UiComponent<{ context: Context }>; + readonly type: T; /** * Optional EUI icon type that can be displayed along with the title. */ - getIconType(context: Context): string | undefined; + getIconType?(context: ActionContextMapping[T]): string; /** * Returns a title to be displayed to the user. + * @param context + */ + getDisplayName?(context: ActionContextMapping[T]): string; + + /** + * `UiComponent` to render when displaying this action as a context menu item. + * If not provided, `getDisplayName` will be used instead. + */ + MenuItem?: UiComponent<{ context: ActionContextMapping[T] }>; + + /** + * Returns a promise that resolves to true if this action is compatible given the context, + * otherwise resolves to false. */ - getDisplayName(context: Context): string; + isCompatible?(context: ActionContextMapping[T]): Promise; /** - * This method should return a link if this item can be clicked on. + * If this returns something truthy, this is used in addition to the `execute` method when clicked. */ - getHref?(context: Context): string | undefined; + getHref?(context: ActionContextMapping[T]): string | undefined; /** - * Returns a promise that resolves to true if this item is compatible given - * the context and should be displayed to user, otherwise resolves to false. + * Executes the action. */ - isCompatible(context: Context): Promise; + execute(context: ActionContextMapping[T]): Promise; } diff --git a/src/plugins/ui_actions/public/actions/action_factory.ts b/src/plugins/ui_actions/public/actions/action_factory.ts deleted file mode 100644 index bc0ec844d00f5..0000000000000 --- a/src/plugins/ui_actions/public/actions/action_factory.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { uiToReactComponent } from '../../../kibana_react/public'; -import { Presentable } from '../util/presentable'; -import { ActionDefinition } from './action'; -import { ActionFactoryDefinition } from './action_factory_definition'; -import { Configurable } from '../util'; -import { SerializedAction } from './types'; - -export class ActionFactory< - Config extends object = object, - FactoryContext extends object = object, - ActionContext extends object = object -> implements Presentable, Configurable { - constructor( - protected readonly def: ActionFactoryDefinition - ) {} - - public readonly id = this.def.id; - public readonly order = this.def.order || 0; - public readonly MenuItem? = this.def.MenuItem; - public readonly ReactMenuItem? = this.MenuItem ? uiToReactComponent(this.MenuItem) : undefined; - - public readonly CollectConfig = this.def.CollectConfig; - public readonly ReactCollectConfig = uiToReactComponent(this.CollectConfig); - public readonly createConfig = this.def.createConfig; - public readonly isConfigValid = this.def.isConfigValid; - - public getIconType(context: FactoryContext): string | undefined { - if (!this.def.getIconType) return undefined; - return this.def.getIconType(context); - } - - public getDisplayName(context: FactoryContext): string { - if (!this.def.getDisplayName) return ''; - return this.def.getDisplayName(context); - } - - public async isCompatible(context: FactoryContext): Promise { - if (!this.def.isCompatible) return true; - return await this.def.isCompatible(context); - } - - public getHref(context: FactoryContext): string | undefined { - if (!this.def.getHref) return undefined; - return this.def.getHref(context); - } - - public create( - serializedAction: Omit, 'factoryId'> - ): ActionDefinition { - return this.def.create(serializedAction); - } -} diff --git a/src/plugins/ui_actions/public/actions/action_factory_definition.ts b/src/plugins/ui_actions/public/actions/action_factory_definition.ts deleted file mode 100644 index 7ac94a41e7076..0000000000000 --- a/src/plugins/ui_actions/public/actions/action_factory_definition.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { ActionDefinition } from './action'; -import { Presentable, Configurable } from '../util'; -import { SerializedAction } from './types'; - -/** - * This is a convenience interface for registering new action factories. - */ -export interface ActionFactoryDefinition< - Config extends object = object, - FactoryContext extends object = object, - ActionContext extends object = object -> extends Partial>, Configurable { - /** - * Unique ID of the action factory. This ID is used to identify this action - * factory in the registry as well as to construct actions of this type and - * identify this action factory when presenting it to the user in UI. - */ - id: string; - - /** - * This method should return a definition of a new action, normally used to - * register it in `ui_actions` registry. - */ - create( - serializedAction: Omit, 'factoryId'> - ): ActionDefinition; -} diff --git a/src/plugins/ui_actions/public/actions/action_internal.test.ts b/src/plugins/ui_actions/public/actions/action_internal.test.ts deleted file mode 100644 index b14346180c274..0000000000000 --- a/src/plugins/ui_actions/public/actions/action_internal.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { ActionDefinition } from './action'; -import { ActionInternal } from './action_internal'; - -const defaultActionDef: ActionDefinition = { - id: 'test-action', - execute: jest.fn(), -}; - -describe('ActionInternal', () => { - test('can instantiate from action definition', () => { - const action = new ActionInternal(defaultActionDef); - expect(action.id).toBe('test-action'); - }); -}); diff --git a/src/plugins/ui_actions/public/actions/action_internal.ts b/src/plugins/ui_actions/public/actions/action_internal.ts deleted file mode 100644 index 245ded991c032..0000000000000 --- a/src/plugins/ui_actions/public/actions/action_internal.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Action, ActionContext as Context, ActionDefinition } from './action'; -import { Presentable } from '../util/presentable'; -import { uiToReactComponent } from '../../../kibana_react/public'; -import { ActionType } from '../types'; - -export class ActionInternal - implements Action>, Presentable> { - constructor(public readonly definition: A) {} - - public readonly id: string = this.definition.id; - public readonly type: ActionType = this.definition.type || ''; - public readonly order: number = this.definition.order || 0; - public readonly MenuItem? = this.definition.MenuItem; - public readonly ReactMenuItem? = this.MenuItem ? uiToReactComponent(this.MenuItem) : undefined; - - public execute(context: Context) { - return this.definition.execute(context); - } - - public getIconType(context: Context): string | undefined { - if (!this.definition.getIconType) return undefined; - return this.definition.getIconType(context); - } - - public getDisplayName(context: Context): string { - if (!this.definition.getDisplayName) return `Action: ${this.id}`; - return this.definition.getDisplayName(context); - } - - public async isCompatible(context: Context): Promise { - if (!this.definition.isCompatible) return true; - return await this.definition.isCompatible(context); - } - - public getHref(context: Context): string | undefined { - if (!this.definition.getHref) return undefined; - return this.definition.getHref(context); - } -} diff --git a/src/plugins/ui_actions/public/actions/create_action.ts b/src/plugins/ui_actions/public/actions/create_action.ts index 8f1cd23715d3f..90a9415c0b497 100644 --- a/src/plugins/ui_actions/public/actions/create_action.ts +++ b/src/plugins/ui_actions/public/actions/create_action.ts @@ -17,19 +17,11 @@ * under the License. */ -import { ActionContextMapping } from '../types'; import { ActionByType } from './action'; import { ActionType } from '../types'; -import { ActionDefinition } from './action'; +import { ActionDefinition } from './action_definition'; -interface ActionDefinitionByType - extends Omit, 'id'> { - id?: string; -} - -export function createAction( - action: ActionDefinitionByType -): ActionByType { +export function createAction(action: ActionDefinition): ActionByType { return { getIconType: () => undefined, order: 0, @@ -38,5 +30,5 @@ export function createAction( getDisplayName: () => '', getHref: () => undefined, ...action, - } as ActionByType; + }; } diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_manager.test.ts b/src/plugins/ui_actions/public/actions/dynamic_action_manager.test.ts deleted file mode 100644 index 2574a9e529ebf..0000000000000 --- a/src/plugins/ui_actions/public/actions/dynamic_action_manager.test.ts +++ /dev/null @@ -1,646 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { 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'; - -const actionFactoryDefinition1: ActionFactoryDefinition = { - id: 'ACTION_FACTORY_1', - CollectConfig: {} as any, - createConfig: () => ({}), - isConfigValid: (() => true) as any, - create: ({ name }) => ({ - id: '', - execute: async () => {}, - getDisplayName: () => name, - }), -}; - -const actionFactoryDefinition2: ActionFactoryDefinition = { - id: 'ACTION_FACTORY_2', - CollectConfig: {} as any, - createConfig: () => ({}), - isConfigValid: (() => true) as any, - create: ({ name }) => ({ - id: '', - execute: async () => {}, - getDisplayName: () => name, - }), -}; - -const event1: SerializedEvent = { - eventId: 'EVENT_ID_1', - triggers: ['VALUE_CLICK_TRIGGER'], - action: { - factoryId: actionFactoryDefinition1.id, - name: 'Action 1', - config: {}, - }, -}; - -const event2: SerializedEvent = { - eventId: 'EVENT_ID_2', - triggers: ['VALUE_CLICK_TRIGGER'], - action: { - factoryId: actionFactoryDefinition1.id, - name: 'Action 2', - config: {}, - }, -}; - -const event3: SerializedEvent = { - eventId: 'EVENT_ID_3', - triggers: ['VALUE_CLICK_TRIGGER'], - action: { - factoryId: actionFactoryDefinition2.id, - name: 'Action 3', - config: {}, - }, -}; - -const setup = (events: readonly SerializedEvent[] = []) => { - const isCompatible = async () => true; - const storage: ActionStorage = new MemoryActionStorage(events); - const actions: ActionRegistry = new Map(); - const uiActions = new UiActionsService({ - actions, - }); - const manager = new DynamicActionManager({ - isCompatible, - storage, - uiActions, - }); - - uiActions.registerTrigger({ - id: 'VALUE_CLICK_TRIGGER', - }); - - return { - isCompatible, - actions, - storage, - uiActions, - manager, - }; -}; - -describe('DynamicActionManager', () => { - test('can instantiate', () => { - const { manager } = setup([event1]); - expect(manager).toBeInstanceOf(DynamicActionManager); - }); - - describe('.start()', () => { - test('instantiates stored events', async () => { - const { manager, actions, uiActions } = setup([event1]); - const create1 = jest.fn(); - const create2 = jest.fn(); - - uiActions.registerActionFactory({ ...actionFactoryDefinition1, create: create1 }); - uiActions.registerActionFactory({ ...actionFactoryDefinition2, create: create2 }); - - expect(create1).toHaveBeenCalledTimes(0); - expect(create2).toHaveBeenCalledTimes(0); - expect(actions.size).toBe(0); - - await manager.start(); - - expect(create1).toHaveBeenCalledTimes(1); - expect(create2).toHaveBeenCalledTimes(0); - expect(actions.size).toBe(1); - }); - - test('does nothing when no events stored', async () => { - const { manager, actions, uiActions } = setup(); - const create1 = jest.fn(); - const create2 = jest.fn(); - - uiActions.registerActionFactory({ ...actionFactoryDefinition1, create: create1 }); - uiActions.registerActionFactory({ ...actionFactoryDefinition2, create: create2 }); - - expect(create1).toHaveBeenCalledTimes(0); - expect(create2).toHaveBeenCalledTimes(0); - expect(actions.size).toBe(0); - - await manager.start(); - - expect(create1).toHaveBeenCalledTimes(0); - expect(create2).toHaveBeenCalledTimes(0); - expect(actions.size).toBe(0); - }); - - test('UI state is empty before manager starts', async () => { - const { manager } = setup([event1]); - - expect(manager.state.get()).toMatchObject({ - events: [], - isFetchingEvents: false, - fetchCount: 0, - }); - }); - - test('loads events into UI state', async () => { - const { manager, uiActions } = setup([event1, event2, event3]); - - uiActions.registerActionFactory(actionFactoryDefinition1); - uiActions.registerActionFactory(actionFactoryDefinition2); - - await manager.start(); - - expect(manager.state.get()).toMatchObject({ - events: [event1, event2, event3], - isFetchingEvents: false, - fetchCount: 1, - }); - }); - - test('sets isFetchingEvents to true while fetching events', async () => { - const { manager, uiActions } = setup([event1, event2, event3]); - - uiActions.registerActionFactory(actionFactoryDefinition1); - uiActions.registerActionFactory(actionFactoryDefinition2); - - const promise = manager.start().catch(() => {}); - - expect(manager.state.get().isFetchingEvents).toBe(true); - - await promise; - - expect(manager.state.get().isFetchingEvents).toBe(false); - }); - - test('throws if storage threw', async () => { - const { manager, storage } = setup([event1]); - - storage.list = async () => { - throw new Error('baz'); - }; - - const [, error] = await of(manager.start()); - - expect(error).toEqual(new Error('baz')); - }); - - test('sets UI state error if error happened during initial fetch', async () => { - const { manager, storage } = setup([event1]); - - storage.list = async () => { - throw new Error('baz'); - }; - - await of(manager.start()); - - expect(manager.state.get().fetchError!.message).toBe('baz'); - }); - }); - - describe('.stop()', () => { - test('removes events from UI actions registry', async () => { - const { manager, actions, uiActions } = setup([event1, event2]); - const create1 = jest.fn(); - const create2 = jest.fn(); - - uiActions.registerActionFactory({ ...actionFactoryDefinition1, create: create1 }); - uiActions.registerActionFactory({ ...actionFactoryDefinition2, create: create2 }); - - expect(actions.size).toBe(0); - - await manager.start(); - - expect(actions.size).toBe(2); - - await manager.stop(); - - expect(actions.size).toBe(0); - }); - }); - - describe('.createEvent()', () => { - describe('when storage succeeds', () => { - test('stores new event in storage', async () => { - const { manager, storage, uiActions } = setup([]); - - uiActions.registerActionFactory(actionFactoryDefinition1); - await manager.start(); - - const action: SerializedAction = { - factoryId: actionFactoryDefinition1.id, - name: 'foo', - config: {}, - }; - - expect(await storage.count()).toBe(0); - - await manager.createEvent(action, ['VALUE_CLICK_TRIGGER']); - - expect(await storage.count()).toBe(1); - - const [event] = await storage.list(); - - expect(event).toMatchObject({ - eventId: expect.any(String), - triggers: ['VALUE_CLICK_TRIGGER'], - action: { - factoryId: actionFactoryDefinition1.id, - name: 'foo', - config: {}, - }, - }); - }); - - test('adds event to UI state', async () => { - const { manager, uiActions } = setup([]); - const action: SerializedAction = { - factoryId: actionFactoryDefinition1.id, - name: 'foo', - config: {}, - }; - - uiActions.registerActionFactory(actionFactoryDefinition1); - - await manager.start(); - - expect(manager.state.get().events.length).toBe(0); - - await manager.createEvent(action, ['VALUE_CLICK_TRIGGER']); - - expect(manager.state.get().events.length).toBe(1); - }); - - test('optimistically adds event to UI state', async () => { - const { manager, uiActions } = setup([]); - const action: SerializedAction = { - factoryId: actionFactoryDefinition1.id, - name: 'foo', - config: {}, - }; - - uiActions.registerActionFactory(actionFactoryDefinition1); - - await manager.start(); - - expect(manager.state.get().events.length).toBe(0); - - const promise = manager.createEvent(action, ['VALUE_CLICK_TRIGGER']).catch(e => e); - - expect(manager.state.get().events.length).toBe(1); - - await promise; - - expect(manager.state.get().events.length).toBe(1); - }); - - test('instantiates event in actions service', async () => { - const { manager, uiActions, actions } = setup([]); - const action: SerializedAction = { - factoryId: actionFactoryDefinition1.id, - name: 'foo', - config: {}, - }; - - uiActions.registerActionFactory(actionFactoryDefinition1); - - await manager.start(); - - expect(actions.size).toBe(0); - - await manager.createEvent(action, ['VALUE_CLICK_TRIGGER']); - - expect(actions.size).toBe(1); - }); - }); - - describe('when storage fails', () => { - test('throws an error', async () => { - const { manager, storage, uiActions } = setup([]); - - storage.create = async () => { - throw new Error('foo'); - }; - - uiActions.registerActionFactory(actionFactoryDefinition1); - await manager.start(); - - const action: SerializedAction = { - factoryId: actionFactoryDefinition1.id, - name: 'foo', - config: {}, - }; - - const [, error] = await of(manager.createEvent(action, ['VALUE_CLICK_TRIGGER'])); - - expect(error).toEqual(new Error('foo')); - }); - - test('does not add even to UI state', async () => { - const { manager, storage, uiActions } = setup([]); - const action: SerializedAction = { - factoryId: actionFactoryDefinition1.id, - name: 'foo', - config: {}, - }; - - storage.create = async () => { - throw new Error('foo'); - }; - uiActions.registerActionFactory(actionFactoryDefinition1); - - await manager.start(); - await of(manager.createEvent(action, ['VALUE_CLICK_TRIGGER'])); - - expect(manager.state.get().events.length).toBe(0); - }); - - test('optimistically adds event to UI state and then removes it', async () => { - const { manager, storage, uiActions } = setup([]); - const action: SerializedAction = { - factoryId: actionFactoryDefinition1.id, - name: 'foo', - config: {}, - }; - - storage.create = async () => { - throw new Error('foo'); - }; - uiActions.registerActionFactory(actionFactoryDefinition1); - - await manager.start(); - - expect(manager.state.get().events.length).toBe(0); - - const promise = manager.createEvent(action, ['VALUE_CLICK_TRIGGER']).catch(e => e); - - expect(manager.state.get().events.length).toBe(1); - - await promise; - - expect(manager.state.get().events.length).toBe(0); - }); - - test('does not instantiate event in actions service', async () => { - const { manager, storage, uiActions, actions } = setup([]); - const action: SerializedAction = { - factoryId: actionFactoryDefinition1.id, - name: 'foo', - config: {}, - }; - - storage.create = async () => { - throw new Error('foo'); - }; - uiActions.registerActionFactory(actionFactoryDefinition1); - - await manager.start(); - - expect(actions.size).toBe(0); - - await of(manager.createEvent(action, ['VALUE_CLICK_TRIGGER'])); - - expect(actions.size).toBe(0); - }); - }); - }); - - describe('.updateEvent()', () => { - describe('when storage succeeds', () => { - test('un-registers old event from ui actions service and registers the new one', async () => { - const { manager, actions, uiActions } = setup([event3]); - - uiActions.registerActionFactory(actionFactoryDefinition2); - await manager.start(); - - expect(actions.size).toBe(1); - - const registeredAction1 = actions.values().next().value; - - expect(registeredAction1.getDisplayName()).toBe('Action 3'); - - const action: SerializedAction = { - factoryId: actionFactoryDefinition2.id, - name: 'foo', - config: {}, - }; - - await manager.updateEvent(event3.eventId, action, ['VALUE_CLICK_TRIGGER']); - - expect(actions.size).toBe(1); - - const registeredAction2 = actions.values().next().value; - - expect(registeredAction2.getDisplayName()).toBe('foo'); - }); - - test('updates event in storage', async () => { - const { manager, storage, uiActions } = setup([event3]); - const storageUpdateSpy = jest.spyOn(storage, 'update'); - - uiActions.registerActionFactory(actionFactoryDefinition2); - await manager.start(); - - const action: SerializedAction = { - factoryId: actionFactoryDefinition2.id, - name: 'foo', - config: {}, - }; - - expect(storageUpdateSpy).toHaveBeenCalledTimes(0); - - await manager.updateEvent(event3.eventId, action, ['VALUE_CLICK_TRIGGER']); - - expect(storageUpdateSpy).toHaveBeenCalledTimes(1); - expect(storageUpdateSpy.mock.calls[0][0]).toMatchObject({ - eventId: expect.any(String), - triggers: ['VALUE_CLICK_TRIGGER'], - action: { - factoryId: actionFactoryDefinition2.id, - }, - }); - }); - - test('updates event in UI state', async () => { - const { manager, uiActions } = setup([event3]); - - uiActions.registerActionFactory(actionFactoryDefinition2); - await manager.start(); - - const action: SerializedAction = { - factoryId: actionFactoryDefinition2.id, - name: 'foo', - config: {}, - }; - - expect(manager.state.get().events[0].action.name).toBe('Action 3'); - - await manager.updateEvent(event3.eventId, action, ['VALUE_CLICK_TRIGGER']); - - expect(manager.state.get().events[0].action.name).toBe('foo'); - }); - - test('optimistically updates event in UI state', async () => { - const { manager, uiActions } = setup([event3]); - - uiActions.registerActionFactory(actionFactoryDefinition2); - await manager.start(); - - const action: SerializedAction = { - factoryId: actionFactoryDefinition2.id, - name: 'foo', - config: {}, - }; - - expect(manager.state.get().events[0].action.name).toBe('Action 3'); - - const promise = manager - .updateEvent(event3.eventId, action, ['VALUE_CLICK_TRIGGER']) - .catch(e => e); - - expect(manager.state.get().events[0].action.name).toBe('foo'); - - await promise; - }); - }); - - describe('when storage fails', () => { - test('throws error', async () => { - const { manager, storage, uiActions } = setup([event3]); - - storage.update = () => { - throw new Error('bar'); - }; - uiActions.registerActionFactory(actionFactoryDefinition2); - await manager.start(); - - const action: SerializedAction = { - factoryId: actionFactoryDefinition2.id, - name: 'foo', - config: {}, - }; - - const [, error] = await of( - manager.updateEvent(event3.eventId, action, ['VALUE_CLICK_TRIGGER']) - ); - - expect(error).toEqual(new Error('bar')); - }); - - test('keeps the old action in actions registry', async () => { - const { manager, storage, actions, uiActions } = setup([event3]); - - storage.update = () => { - throw new Error('bar'); - }; - uiActions.registerActionFactory(actionFactoryDefinition2); - await manager.start(); - - expect(actions.size).toBe(1); - - const registeredAction1 = actions.values().next().value; - - expect(registeredAction1.getDisplayName()).toBe('Action 3'); - - const action: SerializedAction = { - factoryId: actionFactoryDefinition2.id, - name: 'foo', - config: {}, - }; - - await of(manager.updateEvent(event3.eventId, action, ['VALUE_CLICK_TRIGGER'])); - - expect(actions.size).toBe(1); - - const registeredAction2 = actions.values().next().value; - - expect(registeredAction2.getDisplayName()).toBe('Action 3'); - }); - - test('keeps old event in UI state', async () => { - const { manager, storage, uiActions } = setup([event3]); - - storage.update = () => { - throw new Error('bar'); - }; - uiActions.registerActionFactory(actionFactoryDefinition2); - await manager.start(); - - const action: SerializedAction = { - factoryId: actionFactoryDefinition2.id, - name: 'foo', - config: {}, - }; - - expect(manager.state.get().events[0].action.name).toBe('Action 3'); - - await of(manager.updateEvent(event3.eventId, action, ['VALUE_CLICK_TRIGGER'])); - - expect(manager.state.get().events[0].action.name).toBe('Action 3'); - }); - }); - }); - - describe('.deleteEvents()', () => { - describe('when storage succeeds', () => { - test('removes all actions from uiActions service', async () => { - const { manager, actions, uiActions } = setup([event2, event1]); - - uiActions.registerActionFactory(actionFactoryDefinition1); - - await manager.start(); - - expect(actions.size).toBe(2); - - await manager.deleteEvents([event1.eventId, event2.eventId]); - - expect(actions.size).toBe(0); - }); - - test('removes all events from storage', async () => { - const { manager, uiActions, storage } = setup([event2, event1]); - - uiActions.registerActionFactory(actionFactoryDefinition1); - - await manager.start(); - - expect(await storage.list()).toEqual([event2, event1]); - - await manager.deleteEvents([event1.eventId, event2.eventId]); - - expect(await storage.list()).toEqual([]); - }); - - test('removes all events from UI state', async () => { - const { manager, uiActions } = setup([event2, event1]); - - uiActions.registerActionFactory(actionFactoryDefinition1); - - await manager.start(); - - expect(manager.state.get().events).toEqual([event2, event1]); - - await manager.deleteEvents([event1.eventId, event2.eventId]); - - expect(manager.state.get().events).toEqual([]); - }); - }); - }); -}); diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts b/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts deleted file mode 100644 index 97eb5b05fbbc2..0000000000000 --- a/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { 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 { defaultState, transitions, selectors, State } from './dynamic_action_manager_state'; -import { StateContainer, createStateContainer } from '../../../kibana_utils'; - -const compareEvents = ( - a: ReadonlyArray<{ eventId: string }>, - b: ReadonlyArray<{ eventId: string }> -) => { - if (a.length !== b.length) return false; - for (let i = 0; i < a.length; i++) if (a[i].eventId !== b[i].eventId) return false; - return true; -}; - -export type DynamicActionManagerState = State; - -export interface DynamicActionManagerParams { - storage: ActionStorage; - uiActions: Pick< - UiActionsService, - 'registerAction' | 'attachAction' | 'unregisterAction' | 'detachAction' | 'getActionFactory' - >; - isCompatible: (context: C) => Promise; -} - -export class DynamicActionManager { - static idPrefixCounter = 0; - - private readonly idPrefix = `D_ACTION_${DynamicActionManager.idPrefixCounter++}_`; - private stopped: boolean = false; - private reloadSubscription?: Subscription; - - /** - * UI State of the dynamic action manager. - */ - protected readonly ui = createStateContainer(defaultState, transitions, selectors); - - constructor(protected readonly params: DynamicActionManagerParams) {} - - protected getEvent(eventId: string): SerializedEvent { - const oldEvent = this.ui.selectors.getEvent(eventId); - if (!oldEvent) throw new Error(`Could not find event [eventId = ${eventId}].`); - return oldEvent; - } - - /** - * We prefix action IDs with a unique `.idPrefix`, so we can render the - * same dashboard twice on the screen. - */ - protected generateActionId(eventId: string): string { - return this.idPrefix + eventId; - } - - protected reviveAction(event: SerializedEvent) { - const { eventId, triggers, action } = event; - const { uiActions, isCompatible } = this.params; - - const actionId = this.generateActionId(eventId); - const factory = uiActions.getActionFactory(event.action.factoryId); - const actionDefinition: ActionDefinition = { - ...factory.create(action as SerializedAction), - id: actionId, - isCompatible, - }; - - uiActions.registerAction(actionDefinition); - for (const trigger of triggers) uiActions.attachAction(trigger as any, actionId); - } - - protected killAction({ eventId, triggers }: SerializedEvent) { - const { uiActions } = this.params; - const actionId = this.generateActionId(eventId); - - for (const trigger of triggers) uiActions.detachAction(trigger as any, actionId); - uiActions.unregisterAction(actionId); - } - - private syncId = 0; - - /** - * This function is called every time stored events might have changed not by - * us. For example, when in edit mode on dashboard user presses "back" button - * in the browser, then contents of storage changes. - */ - private onSync = () => { - if (this.stopped) return; - - (async () => { - const syncId = ++this.syncId; - const events = await this.params.storage.list(); - - if (this.stopped) return; - if (syncId !== this.syncId) return; - if (compareEvents(events, this.ui.get().events)) return; - - for (const event of this.ui.get().events) this.killAction(event); - for (const event of events) this.reviveAction(event); - this.ui.transitions.finishFetching(events); - })().catch(error => { - /* eslint-disable */ - console.log('Dynamic action manager storage reload failed.'); - console.error(error); - /* eslint-enable */ - }); - }; - - // Public API: --------------------------------------------------------------- - - /** - * Read-only state container of dynamic action manager. Use it to perform all - * *read* operations. - */ - public readonly state: StateContainer = this.ui; - - /** - * 1. Loads all events from @type {DynamicActionStorage} storage. - * 2. Creates actions for each event in `ui_actions` registry. - * 3. Adds events to UI state. - * 4. Does nothing if dynamic action manager was stopped or if event fetching - * is already taking place. - */ - public async start() { - if (this.stopped) return; - if (this.ui.get().isFetchingEvents) return; - - this.ui.transitions.startFetching(); - try { - const events = await this.params.storage.list(); - for (const event of events) this.reviveAction(event); - this.ui.transitions.finishFetching(events); - } catch (error) { - this.ui.transitions.failFetching(error instanceof Error ? error : { message: String(error) }); - throw error; - } - - if (this.params.storage.reload$) { - this.reloadSubscription = this.params.storage.reload$.subscribe(this.onSync); - } - } - - /** - * 1. Removes all events from `ui_actions` registry. - * 2. Puts dynamic action manager is stopped state. - */ - public async stop() { - this.stopped = true; - const events = await this.params.storage.list(); - - for (const event of events) { - this.killAction(event); - } - - if (this.reloadSubscription) { - this.reloadSubscription.unsubscribe(); - } - } - - /** - * Creates a new event. - * - * 1. Stores event in @type {DynamicActionStorage} storage. - * 2. Optimistically adds it to UI state, and rolls back on failure. - * 3. Adds action to `ui_actions` registry. - * - * @param action Dynamic action for which to create an event. - * @param triggers List of triggers to which action should react. - */ - public async createEvent( - action: SerializedAction, - triggers: Array - ) { - const event: SerializedEvent = { - eventId: uuidv4(), - triggers, - action, - }; - - this.ui.transitions.addEvent(event); - try { - await this.params.storage.create(event); - this.reviveAction(event); - } catch (error) { - this.ui.transitions.removeEvent(event.eventId); - throw error; - } - } - - /** - * Updates an existing event. Fails if event with given `eventId` does not - * exit. - * - * 1. Updates the event in @type {DynamicActionStorage} storage. - * 2. Optimistically replaces the old event by the new one in UI state, and - * rolls back on failure. - * 3. Replaces action in `ui_actions` registry with the new event. - * - * - * @param eventId ID of the event to replace. - * @param action New action for which to create the event. - * @param triggers List of triggers to which action should react. - */ - public async updateEvent( - eventId: string, - action: SerializedAction, - triggers: Array - ) { - const event: SerializedEvent = { - eventId, - triggers, - action, - }; - const oldEvent = this.getEvent(eventId); - this.killAction(oldEvent); - - this.reviveAction(event); - this.ui.transitions.replaceEvent(event); - - try { - await this.params.storage.update(event); - } catch (error) { - this.killAction(event); - this.reviveAction(oldEvent); - this.ui.transitions.replaceEvent(oldEvent); - throw error; - } - } - - /** - * Removes existing event. Throws if event does not exist. - * - * 1. Removes the event from @type {DynamicActionStorage} storage. - * 2. Optimistically removes event from UI state, and puts it back on failure. - * 3. Removes associated action from `ui_actions` registry. - * - * @param eventId ID of the event to remove. - */ - public async deleteEvent(eventId: string) { - const event = this.getEvent(eventId); - - this.killAction(event); - this.ui.transitions.removeEvent(eventId); - - try { - await this.params.storage.remove(eventId); - } catch (error) { - this.reviveAction(event); - this.ui.transitions.addEvent(event); - throw error; - } - } - - /** - * Deletes multiple events at once. - * - * @param eventIds List of event IDs. - */ - public async deleteEvents(eventIds: string[]) { - await Promise.all(eventIds.map(this.deleteEvent.bind(this))); - } -} diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_manager_state.ts b/src/plugins/ui_actions/public/actions/dynamic_action_manager_state.ts deleted file mode 100644 index 636af076ea39f..0000000000000 --- a/src/plugins/ui_actions/public/actions/dynamic_action_manager_state.ts +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { SerializedEvent } from './dynamic_action_storage'; - -/** - * This interface represents the state of @type {DynamicActionManager} at any - * point in time. - */ -export interface State { - /** - * Whether dynamic action manager is currently in process of fetching events - * from storage. - */ - readonly isFetchingEvents: boolean; - - /** - * Number of times event fetching has been completed. - */ - readonly fetchCount: number; - - /** - * Error received last time when fetching events. - */ - readonly fetchError?: { - message: string; - }; - - /** - * List of all fetched events. - */ - readonly events: readonly SerializedEvent[]; -} - -export interface Transitions { - startFetching: (state: State) => () => State; - finishFetching: (state: State) => (events: SerializedEvent[]) => State; - failFetching: (state: State) => (error: { message: string }) => State; - addEvent: (state: State) => (event: SerializedEvent) => State; - removeEvent: (state: State) => (eventId: string) => State; - replaceEvent: (state: State) => (event: SerializedEvent) => State; -} - -export interface Selectors { - getEvent: (state: State) => (eventId: string) => SerializedEvent | null; -} - -export const defaultState: State = { - isFetchingEvents: false, - fetchCount: 0, - events: [], -}; - -export const transitions: Transitions = { - startFetching: state => () => ({ ...state, isFetchingEvents: true }), - - finishFetching: state => events => ({ - ...state, - isFetchingEvents: false, - fetchCount: state.fetchCount + 1, - fetchError: undefined, - events, - }), - - failFetching: state => ({ message }) => ({ - ...state, - isFetchingEvents: false, - fetchCount: state.fetchCount + 1, - fetchError: { message }, - }), - - addEvent: state => (event: SerializedEvent) => ({ - ...state, - events: [...state.events, event], - }), - - removeEvent: state => (eventId: string) => ({ - ...state, - events: state.events ? state.events.filter(event => event.eventId !== eventId) : state.events, - }), - - replaceEvent: state => event => { - const index = state.events.findIndex(({ eventId }) => eventId === event.eventId); - if (index === -1) return state; - - return { - ...state, - events: [...state.events.slice(0, index), event, ...state.events.slice(index + 1)], - }; - }, -}; - -export const selectors: Selectors = { - getEvent: state => eventId => state.events.find(event => event.eventId === eventId) || null, -}; diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_storage.ts b/src/plugins/ui_actions/public/actions/dynamic_action_storage.ts deleted file mode 100644 index 28550a671782e..0000000000000 --- a/src/plugins/ui_actions/public/actions/dynamic_action_storage.ts +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/* eslint-disable max-classes-per-file */ - -import { Observable, Subject } from 'rxjs'; -import { SerializedAction } from './types'; - -/** - * Serialized representation of event-action pair, used to persist in storage. - */ -export interface SerializedEvent { - eventId: string; - triggers: string[]; - action: SerializedAction; -} - -/** - * This CRUD interface needs to be implemented by dynamic action users if they - * want to persist the dynamic actions. It has a default implementation in - * Embeddables, however one can use the dynamic actions without Embeddables, - * in that case they have to implement this interface. - */ -export interface ActionStorage { - create(event: SerializedEvent): Promise; - update(event: SerializedEvent): Promise; - remove(eventId: string): Promise; - read(eventId: string): Promise; - count(): Promise; - list(): Promise; - - /** - * Triggered every time events changed in storage and should be re-loaded. - */ - readonly reload$?: Observable; -} - -export abstract class AbstractActionStorage implements ActionStorage { - public readonly reload$: Observable & Pick, 'next'> = new Subject(); - - public async count(): Promise { - return (await this.list()).length; - } - - public async read(eventId: string): Promise { - const events = await this.list(); - const event = events.find(ev => ev.eventId === eventId); - if (!event) throw new Error(`Event [eventId = ${eventId}] not found.`); - return event; - } - - abstract create(event: SerializedEvent): Promise; - abstract update(event: SerializedEvent): Promise; - abstract remove(eventId: string): Promise; - abstract list(): Promise; -} - -/** - * This is an in-memory implementation of ActionStorage. It is used in testing, - * but can also be used production code to store events in memory. - */ -export class MemoryActionStorage extends AbstractActionStorage { - constructor(public events: readonly SerializedEvent[] = []) { - super(); - } - - public async list() { - return this.events.map(event => ({ ...event })); - } - - public async create(event: SerializedEvent) { - this.events = [...this.events, { ...event }]; - } - - public async update(event: SerializedEvent) { - const index = this.events.findIndex(({ eventId }) => eventId === event.eventId); - if (index < 0) throw new Error(`Event [eventId = ${event.eventId}] not found`); - this.events = [...this.events.slice(0, index), { ...event }, ...this.events.slice(index + 1)]; - } - - public async remove(eventId: string) { - const index = this.events.findIndex(ev => eventId === ev.eventId); - if (index < 0) throw new Error(`Event [eventId = ${eventId}] not found`); - this.events = [...this.events.slice(0, index), ...this.events.slice(index + 1)]; - } -} diff --git a/src/plugins/ui_actions/public/actions/index.ts b/src/plugins/ui_actions/public/actions/index.ts index 0ddba197aced6..64bfd368e3dfa 100644 --- a/src/plugins/ui_actions/public/actions/index.ts +++ b/src/plugins/ui_actions/public/actions/index.ts @@ -18,11 +18,5 @@ */ export * from './action'; -export * from './action_internal'; -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 deleted file mode 100644 index 465f091e45ef1..0000000000000 --- a/src/plugins/ui_actions/public/actions/types.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export interface SerializedAction { - readonly factoryId: string; - readonly name: string; - readonly config: Config; -} diff --git a/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx b/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx index ec58261d9e4f7..3dce2c1f4c257 100644 --- a/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx +++ b/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx @@ -24,25 +24,19 @@ import { i18n } from '@kbn/i18n'; import { uiToReactComponent } from '../../../kibana_react/public'; import { Action } from '../actions'; -export const defaultTitle = i18n.translate('uiActions.actionPanel.title', { - defaultMessage: 'Options', -}); - /** * Transforms an array of Actions to the shape EuiContextMenuPanel expects. */ -export async function buildContextMenuForActions({ +export async function buildContextMenuForActions({ actions, actionContext, - title = defaultTitle, closeMenu, }: { - actions: Array>; - actionContext: Context; - title?: string; + actions: Array>; + actionContext: A; closeMenu: () => void; }): Promise { - const menuItems = await buildEuiContextMenuPanelItems({ + const menuItems = await buildEuiContextMenuPanelItems({ actions, actionContext, closeMenu, @@ -50,7 +44,9 @@ export async function buildContextMenuForActions({ return { id: 'mainMenu', - title, + title: i18n.translate('uiActions.actionPanel.title', { + defaultMessage: 'Options', + }), items: menuItems, }; } @@ -58,41 +54,49 @@ export async function buildContextMenuForActions({ /** * Transform an array of Actions into the shape needed to build an EUIContextMenu */ -async function buildEuiContextMenuPanelItems({ +async function buildEuiContextMenuPanelItems({ actions, actionContext, closeMenu, }: { - actions: Array>; - actionContext: Context; + actions: Array>; + actionContext: A; closeMenu: () => void; }) { - const items: EuiContextMenuPanelItemDescriptor[] = new Array(actions.length); - const promises = actions.map(async (action, index) => { + const items: EuiContextMenuPanelItemDescriptor[] = []; + const promises = actions.map(async action => { const isCompatible = await action.isCompatible(actionContext); if (!isCompatible) { return; } - items[index] = convertPanelActionToContextMenuItem({ - action, - actionContext, - closeMenu, - }); + items.push( + convertPanelActionToContextMenuItem({ + action, + actionContext, + closeMenu, + }) + ); }); await Promise.all(promises); - return items.filter(Boolean); + return items; } -function convertPanelActionToContextMenuItem({ +/** + * + * @param {ContextMenuAction} action + * @param {Embeddable} embeddable + * @return {EuiContextMenuPanelItemDescriptor} + */ +function convertPanelActionToContextMenuItem({ action, actionContext, closeMenu, }: { - action: Action; - actionContext: Context; + action: Action; + actionContext: A; closeMenu: () => void; }): EuiContextMenuPanelItemDescriptor { const menuPanelItem: EuiContextMenuPanelItemDescriptor = { @@ -111,11 +115,8 @@ function convertPanelActionToContextMenuItem({ closeMenu(); }; - if (action.getHref) { - const href = action.getHref(actionContext); - if (href) { - menuPanelItem.href = action.getHref(actionContext); - } + if (action.getHref && action.getHref(actionContext)) { + menuPanelItem.href = action.getHref(actionContext); } return menuPanelItem; diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts index 9265d35bad9a9..49b6bd5e17699 100644 --- a/src/plugins/ui_actions/public/index.ts +++ b/src/plugins/ui_actions/public/index.ts @@ -26,26 +26,8 @@ export function plugin(initializerContext: PluginInitializerContext) { export { UiActionsSetup, UiActionsStart } from './plugin'; export { UiActionsServiceParams, UiActionsService } from './service'; -export { - Action, - 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, -} from './actions'; +export { Action, createAction, IncompatibleActionError } from './actions'; export { buildContextMenuForActions } from './context_menu'; -export { - Presentable as UiActionsPresentable, - Configurable as UiActionsConfigurable, - CollectConfigProps as UiActionsCollectConfigProps, -} from './util'; export { Trigger, TriggerContext, @@ -57,4 +39,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/mocks.ts b/src/plugins/ui_actions/public/mocks.ts index 4de38eb5421e9..c1be6b2626525 100644 --- a/src/plugins/ui_actions/public/mocks.ts +++ b/src/plugins/ui_actions/public/mocks.ts @@ -28,13 +28,10 @@ export type Start = jest.Mocked; const createSetupContract = (): Setup => { const setupContract: Setup = { - addTriggerAction: jest.fn(), attachAction: jest.fn(), detachAction: jest.fn(), registerAction: jest.fn(), - registerActionFactory: jest.fn(), registerTrigger: jest.fn(), - unregisterAction: jest.fn(), }; return setupContract; }; @@ -42,21 +39,16 @@ const createSetupContract = (): Setup => { const createStartContract = (): Start => { const startContract: Start = { attachAction: jest.fn(), - unregisterAction: jest.fn(), - addTriggerAction: jest.fn(), - clear: jest.fn(), + registerAction: jest.fn(), + registerTrigger: jest.fn(), + getAction: jest.fn(), detachAction: jest.fn(), executeTriggerActions: jest.fn(), - fork: jest.fn(), - getAction: jest.fn(), - getActionFactories: jest.fn(), - getActionFactory: jest.fn(), getTrigger: jest.fn(), getTriggerActions: jest.fn((id: TriggerId) => []), getTriggerCompatibleActions: jest.fn(), - registerAction: jest.fn(), - registerActionFactory: jest.fn(), - registerTrigger: jest.fn(), + clear: jest.fn(), + fork: jest.fn(), }; return startContract; diff --git a/src/plugins/ui_actions/public/plugin.ts b/src/plugins/ui_actions/public/plugin.ts index 88a5cb04eac6f..928e57937a9b5 100644 --- a/src/plugins/ui_actions/public/plugin.ts +++ b/src/plugins/ui_actions/public/plugin.ts @@ -23,13 +23,7 @@ import { selectRangeTrigger, valueClickTrigger, applyFilterTrigger } from './tri export type UiActionsSetup = Pick< UiActionsService, - | 'addTriggerAction' - | 'attachAction' - | 'detachAction' - | 'registerAction' - | 'registerActionFactory' - | 'registerTrigger' - | 'unregisterAction' + 'attachAction' | 'detachAction' | 'registerAction' | 'registerTrigger' >; export type UiActionsStart = PublicMethodsOf; diff --git a/src/plugins/ui_actions/public/service/ui_actions_service.test.ts b/src/plugins/ui_actions/public/service/ui_actions_service.test.ts index 41e2b57d53dd8..bdf71a25e6dbc 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_service.test.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_service.test.ts @@ -18,13 +18,7 @@ */ import { UiActionsService } from './ui_actions_service'; -import { - Action, - ActionInternal, - createAction, - ActionFactoryDefinition, - ActionFactory, -} from '../actions'; +import { Action, createAction } from '../actions'; import { createHelloWorldAction } from '../tests/test_samples'; import { ActionRegistry, TriggerRegistry, TriggerId, ActionType } from '../types'; import { Trigger } from '../triggers'; @@ -108,21 +102,6 @@ describe('UiActionsService', () => { type: 'test' as ActionType, }); }); - - test('return action instance', () => { - const service = new UiActionsService(); - const action = service.registerAction({ - id: 'test', - execute: async () => {}, - getDisplayName: () => 'test', - getIconType: () => '', - isCompatible: async () => true, - type: 'test' as ActionType, - }); - - expect(action).toBeInstanceOf(ActionInternal); - expect(action.id).toBe('test'); - }); }); describe('.getTriggerActions()', () => { @@ -160,14 +139,13 @@ describe('UiActionsService', () => { expect(list0).toHaveLength(0); - service.addTriggerAction(FOO_TRIGGER, action1); + service.attachAction(FOO_TRIGGER, action1); const list1 = service.getTriggerActions(FOO_TRIGGER); expect(list1).toHaveLength(1); - expect(list1[0]).toBeInstanceOf(ActionInternal); - expect(list1[0].id).toBe(action1.id); + expect(list1).toEqual([action1]); - service.addTriggerAction(FOO_TRIGGER, action2); + service.attachAction(FOO_TRIGGER, action2); const list2 = service.getTriggerActions(FOO_TRIGGER); expect(list2).toHaveLength(2); @@ -186,7 +164,7 @@ describe('UiActionsService', () => { service.registerAction(helloWorldAction); expect(actions.size - length).toBe(1); - expect(actions.get(helloWorldAction.id)!.id).toBe(helloWorldAction.id); + expect(actions.get(helloWorldAction.id)).toBe(helloWorldAction); }); test('getTriggerCompatibleActions returns attached actions', async () => { @@ -200,7 +178,7 @@ describe('UiActionsService', () => { title: 'My trigger', }; service.registerTrigger(testTrigger); - service.addTriggerAction(MY_TRIGGER, helloWorldAction); + service.attachAction(MY_TRIGGER, helloWorldAction); const compatibleActions = await service.getTriggerCompatibleActions(MY_TRIGGER, { hi: 'there', @@ -226,7 +204,7 @@ describe('UiActionsService', () => { }; service.registerTrigger(testTrigger); - service.addTriggerAction(testTrigger.id, action); + service.attachAction(testTrigger.id, action); const compatibleActions1 = await service.getTriggerCompatibleActions(testTrigger.id, { accept: true, @@ -310,7 +288,7 @@ describe('UiActionsService', () => { id: FOO_TRIGGER, }); service1.registerAction(testAction1); - service1.addTriggerAction(FOO_TRIGGER, testAction1); + service1.attachAction(FOO_TRIGGER, testAction1); const service2 = service1.fork(); @@ -331,14 +309,14 @@ describe('UiActionsService', () => { }); service1.registerAction(testAction1); service1.registerAction(testAction2); - service1.addTriggerAction(FOO_TRIGGER, testAction1); + service1.attachAction(FOO_TRIGGER, testAction1); const service2 = service1.fork(); expect(service1.getTriggerActions(FOO_TRIGGER)).toHaveLength(1); expect(service2.getTriggerActions(FOO_TRIGGER)).toHaveLength(1); - service2.addTriggerAction(FOO_TRIGGER, testAction2); + service2.attachAction(FOO_TRIGGER, testAction2); expect(service1.getTriggerActions(FOO_TRIGGER)).toHaveLength(1); expect(service2.getTriggerActions(FOO_TRIGGER)).toHaveLength(2); @@ -352,14 +330,14 @@ describe('UiActionsService', () => { }); service1.registerAction(testAction1); service1.registerAction(testAction2); - service1.addTriggerAction(FOO_TRIGGER, testAction1); + service1.attachAction(FOO_TRIGGER, testAction1); const service2 = service1.fork(); expect(service1.getTriggerActions(FOO_TRIGGER)).toHaveLength(1); expect(service2.getTriggerActions(FOO_TRIGGER)).toHaveLength(1); - service1.addTriggerAction(FOO_TRIGGER, testAction2); + service1.attachAction(FOO_TRIGGER, testAction2); expect(service1.getTriggerActions(FOO_TRIGGER)).toHaveLength(2); expect(service2.getTriggerActions(FOO_TRIGGER)).toHaveLength(1); @@ -414,7 +392,7 @@ describe('UiActionsService', () => { } as any; service.registerTrigger(trigger); - service.addTriggerAction(MY_TRIGGER, action); + service.attachAction(MY_TRIGGER, action); const actions = service.getTriggerActions(trigger.id); @@ -422,7 +400,7 @@ describe('UiActionsService', () => { expect(actions[0].id).toBe(ACTION_HELLO_WORLD); }); - test('can detach an action from a trigger', () => { + test('can detach an action to a trigger', () => { const service = new UiActionsService(); const trigger: Trigger = { @@ -435,7 +413,7 @@ describe('UiActionsService', () => { service.registerTrigger(trigger); service.registerAction(action); - service.addTriggerAction(trigger.id, action); + service.attachAction(trigger.id, action); service.detachAction(trigger.id, action.id); const actions2 = service.getTriggerActions(trigger.id); @@ -467,7 +445,7 @@ describe('UiActionsService', () => { } as any; service.registerAction(action); - expect(() => service.addTriggerAction('i do not exist' as TriggerId, action)).toThrowError( + expect(() => service.attachAction('i do not exist' as TriggerId, action)).toThrowError( 'No trigger [triggerId = i do not exist] exists, for attaching action [actionId = ACTION_HELLO_WORLD].' ); }); @@ -497,64 +475,4 @@ describe('UiActionsService', () => { ); }); }); - - describe('action factories', () => { - const factoryDefinition1: ActionFactoryDefinition = { - id: 'test-factory-1', - CollectConfig: {} as any, - createConfig: () => ({}), - isConfigValid: () => true, - create: () => ({} as any), - }; - const factoryDefinition2: ActionFactoryDefinition = { - id: 'test-factory-2', - CollectConfig: {} as any, - createConfig: () => ({}), - isConfigValid: () => true, - create: () => ({} as any), - }; - - test('.getActionFactories() returns empty array if no action factories registered', () => { - const service = new UiActionsService(); - - const factories = service.getActionFactories(); - - expect(factories).toEqual([]); - }); - - test('can register and retrieve an action factory', () => { - const service = new UiActionsService(); - - service.registerActionFactory(factoryDefinition1); - - const factory = service.getActionFactory(factoryDefinition1.id); - - expect(factory).toBeInstanceOf(ActionFactory); - expect(factory.id).toBe(factoryDefinition1.id); - }); - - test('can retrieve all action factories', () => { - const service = new UiActionsService(); - - service.registerActionFactory(factoryDefinition1); - service.registerActionFactory(factoryDefinition2); - - const factories = service.getActionFactories(); - const factoriesSorted = [...factories].sort((f1, f2) => (f1.id > f2.id ? 1 : -1)); - - expect(factoriesSorted.length).toBe(2); - expect(factoriesSorted[0].id).toBe(factoryDefinition1.id); - expect(factoriesSorted[1].id).toBe(factoryDefinition2.id); - }); - - test('throws when retrieving action factory that does not exist', () => { - const service = new UiActionsService(); - - service.registerActionFactory(factoryDefinition1); - - expect(() => service.getActionFactory('UNKNOWN_ID')).toThrowError( - 'Action factory [actionFactoryId = UNKNOWN_ID] does not exist.' - ); - }); - }); }); diff --git a/src/plugins/ui_actions/public/service/ui_actions_service.ts b/src/plugins/ui_actions/public/service/ui_actions_service.ts index 8bd3bb34fbbd8..f7718e63773f5 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_service.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_service.ts @@ -24,17 +24,8 @@ import { TriggerId, TriggerContextMapping, ActionType, - ActionFactoryRegistry, } from '../types'; -import { - ActionInternal, - Action, - ActionByType, - ActionFactory, - ActionDefinition, - ActionFactoryDefinition, - ActionContext, -} from '../actions'; +import { Action, ActionByType } from '../actions'; import { Trigger, TriggerContext } from '../triggers/trigger'; import { TriggerInternal } from '../triggers/trigger_internal'; import { TriggerContract } from '../triggers/trigger_contract'; @@ -47,25 +38,21 @@ export interface UiActionsServiceParams { * A 1-to-N mapping from `Trigger` to zero or more `Action`. */ readonly triggerToActions?: TriggerToActionsRegistry; - readonly actionFactories?: ActionFactoryRegistry; } export class UiActionsService { protected readonly triggers: TriggerRegistry; protected readonly actions: ActionRegistry; protected readonly triggerToActions: TriggerToActionsRegistry; - protected readonly actionFactories: ActionFactoryRegistry; constructor({ triggers = new Map(), actions = new Map(), triggerToActions = new Map(), - actionFactories = new Map(), }: UiActionsServiceParams = {}) { this.triggers = triggers; this.actions = actions; this.triggerToActions = triggerToActions; - this.actionFactories = actionFactories; } public readonly registerTrigger = (trigger: Trigger) => { @@ -89,44 +76,49 @@ export class UiActionsService { return trigger.contract; }; - public readonly registerAction = ( - definition: A - ): ActionInternal => { - if (this.actions.has(definition.id)) { - throw new Error(`Action [action.id = ${definition.id}] already registered.`); + public readonly registerAction = (action: ActionByType) => { + if (this.actions.has(action.id)) { + throw new Error(`Action [action.id = ${action.id}] already registered.`); } - const action = new ActionInternal(definition); - this.actions.set(action.id, action); - - return action; }; - public readonly unregisterAction = (actionId: string): void => { - if (!this.actions.has(actionId)) { - throw new Error(`Action [action.id = ${actionId}] is not registered.`); + public readonly getAction = (id: string): ActionByType => { + if (!this.actions.has(id)) { + throw new Error(`Action [action.id = ${id}] not registered.`); } - this.actions.delete(actionId); + return this.actions.get(id) as ActionByType; }; - public readonly attachAction = ( - triggerId: TriggerId, - actionId: string + public readonly attachAction = ( + triggerId: TType, + // The action can accept partial or no context, but if it needs context not provided + // by this type of trigger, typescript will complain. yay! + action: ActionByType & Action ): void => { + if (!this.actions.has(action.id)) { + this.registerAction(action); + } else { + const registeredAction = this.actions.get(action.id); + if (registeredAction !== action) { + throw new Error(`A different action instance with this id is already registered.`); + } + } + const trigger = this.triggers.get(triggerId); if (!trigger) { throw new Error( - `No trigger [triggerId = ${triggerId}] exists, for attaching action [actionId = ${actionId}].` + `No trigger [triggerId = ${triggerId}] exists, for attaching action [actionId = ${action.id}].` ); } const actionIds = this.triggerToActions.get(triggerId); - if (!actionIds!.find(id => id === actionId)) { - this.triggerToActions.set(triggerId, [...actionIds!, actionId]); + if (!actionIds!.find(id => id === action.id)) { + this.triggerToActions.set(triggerId, [...actionIds!, action.id]); } }; @@ -147,26 +139,6 @@ export class UiActionsService { ); }; - public readonly addTriggerAction = ( - triggerId: TType, - // The action can accept partial or no context, but if it needs context not provided - // by this type of trigger, typescript will complain. yay! - action: ActionByType & Action - ): void => { - if (!this.actions.has(action.id)) this.registerAction(action); - this.attachAction(triggerId, action.id); - }; - - public readonly getAction = ( - id: string - ): Action> => { - if (!this.actions.has(id)) { - throw new Error(`Action [action.id = ${id}] not registered.`); - } - - return this.actions.get(id) as ActionInternal; - }; - public readonly getTriggerActions = ( triggerId: T ): Array> => { @@ -175,9 +147,9 @@ export class UiActionsService { const actionIds = this.triggerToActions.get(triggerId); - const actions = actionIds! - .map(actionId => this.actions.get(actionId) as ActionInternal) - .filter(Boolean); + const actions = actionIds!.map(actionId => this.actions.get(actionId)).filter(Boolean) as Array< + Action + >; return actions as Array>>; }; @@ -215,7 +187,6 @@ export class UiActionsService { this.actions.clear(); this.triggers.clear(); this.triggerToActions.clear(); - this.actionFactories.clear(); }; /** @@ -235,41 +206,4 @@ export class UiActionsService { return new UiActionsService({ triggers, actions, triggerToActions }); }; - - /** - * Register an action factory. Action factories are used to configure and - * serialize/deserialize dynamic actions. - */ - public readonly registerActionFactory = < - Config extends object = object, - FactoryContext extends object = object, - ActionContext extends object = object - >( - definition: ActionFactoryDefinition - ) => { - if (this.actionFactories.has(definition.id)) { - throw new Error(`ActionFactory [actionFactory.id = ${definition.id}] already registered.`); - } - - const actionFactory = new ActionFactory(definition); - - this.actionFactories.set(actionFactory.id, actionFactory as ActionFactory); - }; - - public readonly getActionFactory = (actionFactoryId: string): ActionFactory => { - const actionFactory = this.actionFactories.get(actionFactoryId); - - if (!actionFactory) { - throw new Error(`Action factory [actionFactoryId = ${actionFactoryId}] does not exist.`); - } - - return actionFactory; - }; - - /** - * Returns an array of all action factories. - */ - public readonly getActionFactories = (): ActionFactory[] => { - return [...this.actionFactories.values()]; - }; } diff --git a/src/plugins/ui_actions/public/tests/execute_trigger_actions.test.ts b/src/plugins/ui_actions/public/tests/execute_trigger_actions.test.ts index ade21ee4b7d91..5b427f918c173 100644 --- a/src/plugins/ui_actions/public/tests/execute_trigger_actions.test.ts +++ b/src/plugins/ui_actions/public/tests/execute_trigger_actions.test.ts @@ -69,7 +69,7 @@ test('executes a single action mapped to a trigger', async () => { const action = createTestAction('test1', () => true); setup.registerTrigger(trigger); - setup.addTriggerAction(trigger.id, action); + setup.attachAction(trigger.id, action); const context = {}; const start = doStart(); @@ -109,7 +109,7 @@ test('does not execute an incompatible action', async () => { ); setup.registerTrigger(trigger); - setup.addTriggerAction(trigger.id, action); + setup.attachAction(trigger.id, action); const start = doStart(); const context = { @@ -130,8 +130,8 @@ test('shows a context menu when more than one action is mapped to a trigger', as const action2 = createTestAction('test2', () => true); setup.registerTrigger(trigger); - setup.addTriggerAction(trigger.id, action1); - setup.addTriggerAction(trigger.id, action2); + setup.attachAction(trigger.id, action1); + setup.attachAction(trigger.id, action2); expect(openContextMenu).toHaveBeenCalledTimes(0); @@ -155,7 +155,7 @@ test('passes whole action context to isCompatible()', async () => { }); setup.registerTrigger(trigger); - setup.addTriggerAction(trigger.id, action); + setup.attachAction(trigger.id, action); const start = doStart(); diff --git a/src/plugins/ui_actions/public/tests/get_trigger_actions.test.ts b/src/plugins/ui_actions/public/tests/get_trigger_actions.test.ts index 55ccac42ff255..f5a6a96fb41a4 100644 --- a/src/plugins/ui_actions/public/tests/get_trigger_actions.test.ts +++ b/src/plugins/ui_actions/public/tests/get_trigger_actions.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { ActionInternal, Action } from '../actions'; +import { Action } from '../actions'; import { uiActionsPluginMock } from '../mocks'; import { TriggerId, ActionType } from '../types'; @@ -47,14 +47,13 @@ test('returns actions set on trigger', () => { expect(list0).toHaveLength(0); - setup.addTriggerAction('trigger' as TriggerId, action1); + setup.attachAction('trigger' as TriggerId, action1); const list1 = start.getTriggerActions('trigger' as TriggerId); expect(list1).toHaveLength(1); - expect(list1[0]).toBeInstanceOf(ActionInternal); - expect(list1[0].id).toBe(action1.id); + expect(list1).toEqual([action1]); - setup.addTriggerAction('trigger' as TriggerId, action2); + setup.attachAction('trigger' as TriggerId, action2); const list2 = start.getTriggerActions('trigger' as TriggerId); expect(list2).toHaveLength(2); diff --git a/src/plugins/ui_actions/public/tests/get_trigger_compatible_actions.test.ts b/src/plugins/ui_actions/public/tests/get_trigger_compatible_actions.test.ts index 21dd17ed82e3f..c5e68e5d5ca5a 100644 --- a/src/plugins/ui_actions/public/tests/get_trigger_compatible_actions.test.ts +++ b/src/plugins/ui_actions/public/tests/get_trigger_compatible_actions.test.ts @@ -37,7 +37,7 @@ beforeEach(() => { id: 'trigger' as TriggerId, title: 'trigger', }); - uiActions.setup.addTriggerAction('trigger' as TriggerId, action); + uiActions.setup.attachAction('trigger' as TriggerId, action); }); test('can register action', async () => { @@ -58,7 +58,7 @@ test('getTriggerCompatibleActions returns attached actions', async () => { title: 'My trigger', }; setup.registerTrigger(testTrigger); - setup.addTriggerAction('MY-TRIGGER' as TriggerId, helloWorldAction); + setup.attachAction('MY-TRIGGER' as TriggerId, helloWorldAction); const start = doStart(); const actions = await start.getTriggerCompatibleActions('MY-TRIGGER' as TriggerId, {}); @@ -84,7 +84,7 @@ test('filters out actions not applicable based on the context', async () => { setup.registerTrigger(testTrigger); setup.registerAction(action1); - setup.addTriggerAction(testTrigger.id, action1); + setup.attachAction(testTrigger.id, action1); const start = doStart(); let actions = await start.getTriggerCompatibleActions(testTrigger.id, { accept: true }); diff --git a/src/plugins/ui_actions/public/tests/test_samples/index.ts b/src/plugins/ui_actions/public/tests/test_samples/index.ts index dfa71cec89595..7d63b1b6d5669 100644 --- a/src/plugins/ui_actions/public/tests/test_samples/index.ts +++ b/src/plugins/ui_actions/public/tests/test_samples/index.ts @@ -16,5 +16,4 @@ * specific language governing permissions and limitations * under the License. */ - export { createHelloWorldAction } from './hello_world_action'; diff --git a/src/plugins/ui_actions/public/triggers/select_range_trigger.ts b/src/plugins/ui_actions/public/triggers/select_range_trigger.ts index 9758508dc3dac..c638db0ce9dab 100644 --- a/src/plugins/ui_actions/public/triggers/select_range_trigger.ts +++ b/src/plugins/ui_actions/public/triggers/select_range_trigger.ts @@ -22,6 +22,6 @@ import { Trigger } from '.'; export const SELECT_RANGE_TRIGGER = 'SELECT_RANGE_TRIGGER'; export const selectRangeTrigger: Trigger<'SELECT_RANGE_TRIGGER'> = { id: SELECT_RANGE_TRIGGER, - title: '', + title: 'Select range', description: 'Applies a range filter', }; diff --git a/src/plugins/ui_actions/public/triggers/trigger_internal.ts b/src/plugins/ui_actions/public/triggers/trigger_internal.ts index 9885ed3abe93b..5b670df354f78 100644 --- a/src/plugins/ui_actions/public/triggers/trigger_internal.ts +++ b/src/plugins/ui_actions/public/triggers/trigger_internal.ts @@ -72,7 +72,6 @@ export class TriggerInternal { const panel = await buildContextMenuForActions({ actions, actionContext: context, - title: this.trigger.title, closeMenu: () => session.close(), }); const session = openContextMenu([panel]); diff --git a/src/plugins/ui_actions/public/triggers/value_click_trigger.ts b/src/plugins/ui_actions/public/triggers/value_click_trigger.ts index 2671584d105c8..ad32bdc1b564e 100644 --- a/src/plugins/ui_actions/public/triggers/value_click_trigger.ts +++ b/src/plugins/ui_actions/public/triggers/value_click_trigger.ts @@ -22,6 +22,6 @@ import { Trigger } from '.'; export const VALUE_CLICK_TRIGGER = 'VALUE_CLICK_TRIGGER'; export const valueClickTrigger: Trigger<'VALUE_CLICK_TRIGGER'> = { id: VALUE_CLICK_TRIGGER, - title: '', + title: 'Value clicked', description: 'Value was clicked', }; diff --git a/src/plugins/ui_actions/public/types.ts b/src/plugins/ui_actions/public/types.ts index 2cb4a8f26a879..c7e6d61e15f31 100644 --- a/src/plugins/ui_actions/public/types.ts +++ b/src/plugins/ui_actions/public/types.ts @@ -17,17 +17,15 @@ * under the License. */ -import { ActionInternal } from './actions/action_internal'; +import { ActionByType } from './actions/action'; import { TriggerInternal } from './triggers/trigger_internal'; -import { ActionFactory } from './actions'; import { EmbeddableVisTriggerContext, IEmbeddable } from '../../embeddable/public'; import { Filter } from '../../data/public'; import { SELECT_RANGE_TRIGGER, VALUE_CLICK_TRIGGER, APPLY_FILTER_TRIGGER } from './triggers'; export type TriggerRegistry = Map>; -export type ActionRegistry = Map; +export type ActionRegistry = Map>; export type TriggerToActionsRegistry = Map; -export type ActionFactoryRegistry = Map; const DEFAULT_TRIGGER = ''; diff --git a/src/plugins/ui_actions/public/util/configurable.ts b/src/plugins/ui_actions/public/util/configurable.ts deleted file mode 100644 index d3a527a2183b1..0000000000000 --- a/src/plugins/ui_actions/public/util/configurable.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { UiComponent } from 'src/plugins/kibana_utils/common'; - -/** - * Represents something that can be configured by user using UI. - */ -export interface Configurable { - /** - * Create default config for this item, used when item is created for the first time. - */ - readonly createConfig: () => Config; - - /** - * Is this config valid. Used to validate user's input before saving. - */ - readonly isConfigValid: (config: Config) => boolean; - - /** - * `UiComponent` to be rendered when collecting configuration for this item. - */ - readonly CollectConfig: UiComponent>; -} - -/** - * Props provided to `CollectConfig` component on every re-render. - */ -export interface CollectConfigProps { - /** - * Current (latest) config of the item. - */ - config: Config; - - /** - * Callback called when user updates the config in UI. - */ - onConfig: (config: Config) => void; - - /** - * Context information about where component is being rendered. - */ - context: Context; -} diff --git a/src/plugins/ui_actions/public/util/index.ts b/src/plugins/ui_actions/public/util/index.ts deleted file mode 100644 index 53c6109cac4ca..0000000000000 --- a/src/plugins/ui_actions/public/util/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export * from './presentable'; -export * from './configurable'; diff --git a/src/plugins/ui_actions/scripts/storybook.js b/src/plugins/ui_actions/scripts/storybook.js deleted file mode 100644 index cb2eda610170d..0000000000000 --- a/src/plugins/ui_actions/scripts/storybook.js +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { join } from 'path'; - -// eslint-disable-next-line -require('@kbn/storybook').runStorybookCli({ - name: 'ui_actions', - storyGlobs: [join(__dirname, '..', 'public', 'components', '**', '*.story.tsx')], -}); diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx index 8ddb2e1a4803b..18ceec652392d 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx @@ -70,10 +70,11 @@ export class EmbeddableExplorerPublicPlugin const sayHelloAction = new SayHelloAction(alert); const sendMessageAction = createSendMessageAction(core.overlays); + plugins.uiActions.registerAction(helloWorldAction); plugins.uiActions.registerAction(sayHelloAction); plugins.uiActions.registerAction(sendMessageAction); - plugins.uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, helloWorldAction); + plugins.uiActions.attachAction(CONTEXT_MENU_TRIGGER, helloWorldAction); plugins.__LEGACY.onRenderComplete(() => { const root = document.getElementById(REACT_ROOT_ID); diff --git a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_action.tsx b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_action.tsx index 7c7cc689d05e5..8395fddece2a4 100644 --- a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_action.tsx +++ b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_action.tsx @@ -62,4 +62,5 @@ function createSamplePanelAction() { } const action = createSamplePanelAction(); -npSetup.plugins.uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, action); +npSetup.plugins.uiActions.registerAction(action); +npSetup.plugins.uiActions.attachAction(CONTEXT_MENU_TRIGGER, action); diff --git a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_link.ts b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_link.ts index e034fbe320608..4b09be4db8a60 100644 --- a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_link.ts +++ b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_link.ts @@ -33,4 +33,5 @@ export const createSamplePanelLink = (): Action => }); const action = createSamplePanelLink(); -npStart.plugins.uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, action); +npStart.plugins.uiActions.registerAction(action); +npStart.plugins.uiActions.attachAction(CONTEXT_MENU_TRIGGER, action); diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 784b5a5a42ace..2a28e349ace99 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -9,7 +9,6 @@ "xpack.beatsManagement": "legacy/plugins/beats_management", "xpack.canvas": "legacy/plugins/canvas", "xpack.crossClusterReplication": "legacy/plugins/cross_cluster_replication", - "xpack.dashboard": "plugins/dashboard_enhanced", "xpack.dashboardMode": "legacy/plugins/dashboard_mode", "xpack.data": "plugins/data_enhanced", "xpack.drilldowns": "plugins/drilldowns", diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.scss b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.scss index 87ec3f8fc7ec1..2ba6f9baca90d 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.scss +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.scss @@ -1,3 +1,8 @@ +.auaActionWizard__selectedActionFactoryContainer { + background-color: $euiColorLightestShade; + padding: $euiSize; +} + .auaActionWizard__actionFactoryItem { .euiKeyPadMenuItem__label { height: #{$euiSizeXL}; diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.story.tsx b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.story.tsx index 9c73f07289dc9..62f16890cade2 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.story.tsx +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.story.tsx @@ -6,26 +6,28 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; -import { Demo, dashboardFactory, urlFactory } from './test_data'; +import { dashboardDrilldownActionFactory, Demo, urlDrilldownActionFactory } from './test_data'; storiesOf('components/ActionWizard', module) - .add('default', () => ) + .add('default', () => ( + + )) .add('Only one factory is available', () => ( // to make sure layout doesn't break - + )) .add('Long list of action factories', () => ( // to make sure layout doesn't break )); diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.test.tsx b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.test.tsx index cc56714fcb2f8..aea47be693b8f 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.test.tsx +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.test.tsx @@ -8,14 +8,21 @@ import React from 'react'; import { cleanup, fireEvent, render } from '@testing-library/react/pure'; import '@testing-library/jest-dom/extend-expect'; // TODO: this should be global import { TEST_SUBJ_ACTION_FACTORY_ITEM, TEST_SUBJ_SELECTED_ACTION_FACTORY } from './action_wizard'; -import { dashboardFactory, dashboards, Demo, urlFactory } from './test_data'; +import { + dashboardDrilldownActionFactory, + dashboards, + Demo, + urlDrilldownActionFactory, +} from './test_data'; // TODO: afterEach is not available for it globally during setup // https://github.com/elastic/kibana/issues/59469 afterEach(cleanup); test('Pick and configure action', () => { - const screen = render(); + const screen = render( + + ); // check that all factories are displayed to pick expect(screen.getAllByTestId(TEST_SUBJ_ACTION_FACTORY_ITEM)).toHaveLength(2); @@ -40,7 +47,7 @@ test('Pick and configure action', () => { }); test('If only one actions factory is available then actionFactory selection is emitted without user input', () => { - const screen = render(); + const screen = render(); // check that no factories are displayed to pick from expect(screen.queryByTestId(TEST_SUBJ_ACTION_FACTORY_ITEM)).not.toBeInTheDocument(); diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx index 846f6d41eb30d..41ef863c00e44 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx @@ -16,23 +16,40 @@ import { } from '@elastic/eui'; import { txtChangeButton } from './i18n'; import './action_wizard.scss'; -import { ActionFactory } from '../../services'; -type ActionBaseConfig = object; -type ActionFactoryBaseContext = object; +// TODO: this interface is temporary for just moving forward with the component +// and it will be imported from the ../ui_actions when implemented properly +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions +export type ActionBaseConfig = {}; +export interface ActionFactory { + type: string; // TODO: type should be tied to Action and ActionByType + displayName: string; + iconType?: string; + wizard: React.FC>; + createConfig: () => Config; + isValid: (config: Config) => boolean; +} + +export interface ActionFactoryWizardProps { + config?: Config; + + /** + * Callback called when user updates the config in UI. + */ + onConfig: (config: Config) => void; +} export interface ActionWizardProps { /** * List of available action factories */ - actionFactories: ActionFactory[]; + actionFactories: Array>; // any here to be able to pass array of ActionFactory with different configs /** * Currently selected action factory * undefined - is allowed and means that non is selected */ currentActionFactory?: ActionFactory; - /** * Action factory selected changed * null - means user click "change" and removed action factory selection @@ -48,11 +65,6 @@ export interface ActionWizardProps { * config changed */ onConfigChange: (config: ActionBaseConfig) => void; - - /** - * Context will be passed into ActionFactory's methods - */ - context: ActionFactoryBaseContext; } export const ActionWizard: React.FC = ({ @@ -61,7 +73,6 @@ export const ActionWizard: React.FC = ({ onActionFactoryChange, onConfigChange, config, - context, }) => { // auto pick action factory if there is only 1 available if (!currentActionFactory && actionFactories.length === 1) { @@ -76,7 +87,6 @@ export const ActionWizard: React.FC = ({ onDeselect={() => { onActionFactoryChange(null); }} - context={context} config={config} onConfigChange={newConfig => { onConfigChange(newConfig); @@ -87,7 +97,6 @@ export const ActionWizard: React.FC = ({ return ( { onActionFactoryChange(actionFactory); @@ -96,11 +105,10 @@ export const ActionWizard: React.FC = ({ ); }; -interface SelectedActionFactoryProps { - actionFactory: ActionFactory; - config: ActionBaseConfig; - context: ActionFactoryBaseContext; - onConfigChange: (config: ActionBaseConfig) => void; +interface SelectedActionFactoryProps { + actionFactory: ActionFactory; + config: Config; + onConfigChange: (config: Config) => void; showDeselect: boolean; onDeselect: () => void; } @@ -113,28 +121,28 @@ const SelectedActionFactory: React.FC = ({ showDeselect, onConfigChange, config, - context, }) => { return (
- {actionFactory.getIconType(context) && ( + {actionFactory.iconType && ( - + )} -

{actionFactory.getDisplayName(context)}

+

{actionFactory.displayName}

{showDeselect && ( - onDeselect()}> + onDeselect()}> {txtChangeButton} @@ -143,11 +151,10 @@ const SelectedActionFactory: React.FC = ({
- + {actionFactory.wizard({ + config, + onConfig: onConfigChange, + })}
); @@ -155,7 +162,6 @@ const SelectedActionFactory: React.FC = ({ interface ActionFactorySelectorProps { actionFactories: ActionFactory[]; - context: ActionFactoryBaseContext; onActionFactorySelected: (actionFactory: ActionFactory) => void; } @@ -164,7 +170,6 @@ export const TEST_SUBJ_ACTION_FACTORY_ITEM = 'action-factory-item'; const ActionFactorySelector: React.FC = ({ actionFactories, onActionFactorySelected, - context, }) => { if (actionFactories.length === 0) { // this is not user facing, as it would be impossible to get into this state @@ -173,23 +178,19 @@ const ActionFactorySelector: React.FC = ({ } return ( - - {[...actionFactories] - .sort((f1, f2) => f1.order - f2.order) - .map(actionFactory => ( - - onActionFactorySelected(actionFactory)} - > - {actionFactory.getIconType(context) && ( - - )} - - - ))} + + {actionFactories.map(actionFactory => ( + onActionFactorySelected(actionFactory)} + > + {actionFactory.iconType && } + + ))} ); }; diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/i18n.ts b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/i18n.ts index a315184bf68ef..641f25176264a 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/i18n.ts +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/i18n.ts @@ -9,6 +9,6 @@ import { i18n } from '@kbn/i18n'; export const txtChangeButton = i18n.translate( 'xpack.advancedUiActions.components.actionWizard.changeButton', { - defaultMessage: 'Change', + defaultMessage: 'change', } ); diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/index.ts b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/index.ts index a189afbf956ee..ed224248ec4cd 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/index.ts +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { ActionWizard } from './action_wizard'; +export { ActionFactory, ActionWizard } from './action_wizard'; diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx index 167cb130fdb4a..8ecdde681069e 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx @@ -6,161 +6,124 @@ import React, { useState } from 'react'; import { EuiFieldText, EuiFormRow, EuiSelect, EuiSwitch } from '@elastic/eui'; -import { reactToUiComponent } from '../../../../../../src/plugins/kibana_react/public'; -import { ActionWizard } from './action_wizard'; -import { ActionFactoryDefinition, ActionFactory } from '../../services'; -import { CollectConfigProps } from '../../util'; - -type ActionBaseConfig = object; +import { ActionFactory, ActionBaseConfig, ActionWizard } from './action_wizard'; export const dashboards = [ { id: 'dashboard1', title: 'Dashboard 1' }, { id: 'dashboard2', title: 'Dashboard 2' }, ]; -interface DashboardDrilldownConfig { +export const dashboardDrilldownActionFactory: ActionFactory<{ dashboardId?: string; - useCurrentFilters: boolean; - useCurrentDateRange: boolean; -} - -function DashboardDrilldownCollectConfig(props: CollectConfigProps) { - const config = props.config ?? { - dashboardId: undefined, - useCurrentFilters: true, - useCurrentDateRange: true, - }; - return ( - <> - - ({ value: id, text: title }))} - value={config.dashboardId} - onChange={e => { - props.onConfig({ ...config, dashboardId: e.target.value }); - }} - /> - - - - props.onConfig({ - ...config, - useCurrentFilters: !config.useCurrentFilters, - }) - } - /> - - - - props.onConfig({ - ...config, - useCurrentDateRange: !config.useCurrentDateRange, - }) - } - /> - - - ); -} - -export const dashboardDrilldownActionFactory: ActionFactoryDefinition< - DashboardDrilldownConfig, - any, - any -> = { - id: 'Dashboard', - getDisplayName: () => 'Go to Dashboard', - getIconType: () => 'dashboardApp', + useCurrentDashboardFilters: boolean; + useCurrentDashboardDataRange: boolean; +}> = { + type: 'Dashboard', + displayName: 'Go to Dashboard', + iconType: 'dashboardApp', createConfig: () => { return { dashboardId: undefined, - useCurrentFilters: true, - useCurrentDateRange: true, + useCurrentDashboardDataRange: true, + useCurrentDashboardFilters: true, }; }, - isConfigValid: (config: DashboardDrilldownConfig): config is DashboardDrilldownConfig => { + isValid: config => { if (!config.dashboardId) return false; return true; }, - CollectConfig: reactToUiComponent(DashboardDrilldownCollectConfig), - - isCompatible(context?: object): Promise { - return Promise.resolve(true); + wizard: props => { + const config = props.config ?? { + dashboardId: undefined, + useCurrentDashboardDataRange: true, + useCurrentDashboardFilters: true, + }; + return ( + <> + + ({ value: id, text: title }))} + value={config.dashboardId} + onChange={e => { + props.onConfig({ ...config, dashboardId: e.target.value }); + }} + /> + + + + props.onConfig({ + ...config, + useCurrentDashboardFilters: !config.useCurrentDashboardFilters, + }) + } + /> + + + + props.onConfig({ + ...config, + useCurrentDashboardDataRange: !config.useCurrentDashboardDataRange, + }) + } + /> + + + ); }, - order: 0, - create: () => ({ - id: 'test', - execute: async () => alert('Navigate to dashboard!'), - }), }; -export const dashboardFactory = new ActionFactory(dashboardDrilldownActionFactory); - -interface UrlDrilldownConfig { - url: string; - openInNewTab: boolean; -} -function UrlDrilldownCollectConfig(props: CollectConfigProps) { - const config = props.config ?? { - url: '', - openInNewTab: false, - }; - return ( - <> - - props.onConfig({ ...config, url: event.target.value })} - /> - - - props.onConfig({ ...config, openInNewTab: !config.openInNewTab })} - /> - - - ); -} -export const urlDrilldownActionFactory: ActionFactoryDefinition = { - id: 'Url', - getDisplayName: () => 'Go to URL', - getIconType: () => 'link', +export const urlDrilldownActionFactory: ActionFactory<{ url: string; openInNewTab: boolean }> = { + type: 'Url', + displayName: 'Go to URL', + iconType: 'link', createConfig: () => { return { url: '', openInNewTab: false, }; }, - isConfigValid: (config: UrlDrilldownConfig): config is UrlDrilldownConfig => { + isValid: config => { if (!config.url) return false; return true; }, - CollectConfig: reactToUiComponent(UrlDrilldownCollectConfig), - - order: 10, - isCompatible(context?: object): Promise { - return Promise.resolve(true); + wizard: props => { + const config = props.config ?? { + url: '', + openInNewTab: false, + }; + return ( + <> + + props.onConfig({ ...config, url: event.target.value })} + /> + + + props.onConfig({ ...config, openInNewTab: !config.openInNewTab })} + /> + + + ); }, - create: () => null as any, }; -export const urlFactory = new ActionFactory(urlDrilldownActionFactory); - export function Demo({ actionFactories }: { actionFactories: Array> }) { const [state, setState] = useState<{ currentActionFactory?: ActionFactory; @@ -194,15 +157,14 @@ export function Demo({ actionFactories }: { actionFactories: Array

-
Action Factory Id: {state.currentActionFactory?.id}
+
Action Factory Type: {state.currentActionFactory?.type}
Action Factory Config: {JSON.stringify(state.config)}
Is config valid:{' '} - {JSON.stringify(state.currentActionFactory?.isConfigValid(state.config!) ?? false)} + {JSON.stringify(state.currentActionFactory?.isValid(state.config!) ?? false)}
); diff --git a/x-pack/plugins/advanced_ui_actions/public/custom_time_range_action.tsx b/x-pack/plugins/advanced_ui_actions/public/custom_time_range_action.tsx index c0cd8d5540db2..325a5ddc10179 100644 --- a/x-pack/plugins/advanced_ui_actions/public/custom_time_range_action.tsx +++ b/x-pack/plugins/advanced_ui_actions/public/custom_time_range_action.tsx @@ -44,7 +44,7 @@ export class CustomTimeRangeAction implements ActionByType { + implements Plugin { constructor(initializerContext: PluginInitializerContext) {} - public setup(core: CoreSetup, { uiActions }: SetupDependencies): SetupContract { - return { - ...uiActions, - }; - } + public setup(core: CoreSetup, { uiActions }: SetupDependencies): Setup {} - public start(core: CoreStart, { uiActions }: StartDependencies): StartContract { + public start(core: CoreStart, { uiActions }: StartDependencies): Start { const dateFormat = core.uiSettings.get('dateFormat') as string; const commonlyUsedRanges = core.uiSettings.get('timepicker:quickRanges') as CommonlyUsedRange[]; const { openModal } = createReactOverlays(core); @@ -72,18 +66,16 @@ export class AdvancedUiActionsPublicPlugin dateFormat, commonlyUsedRanges, }); - uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, timeRangeAction); + uiActions.registerAction(timeRangeAction); + uiActions.attachAction(CONTEXT_MENU_TRIGGER, timeRangeAction); const timeRangeBadge = new CustomTimeRangeBadge({ openModal, dateFormat, commonlyUsedRanges, }); - uiActions.addTriggerAction(PANEL_BADGE_TRIGGER, timeRangeBadge); - - return { - ...uiActions, - }; + uiActions.registerAction(timeRangeBadge); + uiActions.attachAction(PANEL_BADGE_TRIGGER, timeRangeBadge); } public stop() {} diff --git a/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory.ts b/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory.ts deleted file mode 100644 index 66e2a4eafa880..0000000000000 --- a/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -/* eslint-disable */ - -export { - ActionFactory -} from '../../../../../../src/plugins/ui_actions/public/actions/action_factory'; diff --git a/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory_definition.ts b/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory_definition.ts deleted file mode 100644 index f8669a4bf813f..0000000000000 --- a/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory_definition.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -/* eslint-disable */ - -export { - ActionFactoryDefinition -} from '../../../../../../src/plugins/ui_actions/public/actions/action_factory_definition'; diff --git a/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/index.ts b/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/index.ts deleted file mode 100644 index db5bb3aa62a16..0000000000000 --- a/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export * from './action_factory_definition'; -export * from './action_factory'; diff --git a/x-pack/plugins/advanced_ui_actions/public/util/index.ts b/x-pack/plugins/advanced_ui_actions/public/util/index.ts deleted file mode 100644 index fd3ab89973348..0000000000000 --- a/x-pack/plugins/advanced_ui_actions/public/util/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { - UiActionsConfigurable as Configurable, - UiActionsCollectConfigProps as CollectConfigProps, -} from '../../../../../src/plugins/ui_actions/public'; diff --git a/x-pack/plugins/dashboard_enhanced/README.md b/x-pack/plugins/dashboard_enhanced/README.md deleted file mode 100644 index d9296ae158621..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/README.md +++ /dev/null @@ -1 +0,0 @@ -# X-Pack part of Dashboard app diff --git a/x-pack/plugins/dashboard_enhanced/kibana.json b/x-pack/plugins/dashboard_enhanced/kibana.json deleted file mode 100644 index acbca5c33295c..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/kibana.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "id": "dashboardEnhanced", - "version": "kibana", - "server": true, - "ui": true, - "requiredPlugins": ["uiActions", "embeddable", "dashboard", "drilldowns"], - "configPath": ["xpack", "dashboardEnhanced"] -} diff --git a/x-pack/plugins/dashboard_enhanced/public/components/README.md b/x-pack/plugins/dashboard_enhanced/public/components/README.md deleted file mode 100644 index 8081f8a2451cf..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/components/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Presentation React components - -Here we keep reusable *presentation* (aka *dumb*) React components—these -components should not be connected to state and ideally should not know anything -about Kibana. diff --git a/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/dashboard_drilldown_config.story.tsx b/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/dashboard_drilldown_config.story.tsx deleted file mode 100644 index 8e204b044a136..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/dashboard_drilldown_config.story.tsx +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -/* eslint-disable no-console */ - -import * as React from 'react'; -import { storiesOf } from '@storybook/react'; -import { DashboardDrilldownConfig } from '.'; - -export const dashboards = [ - { id: 'dashboard1', title: 'Dashboard 1' }, - { id: 'dashboard2', title: 'Dashboard 2' }, - { id: 'dashboard3', title: 'Dashboard 3' }, -]; - -const InteractiveDemo: React.FC = () => { - const [activeDashboardId, setActiveDashboardId] = React.useState('dashboard1'); - const [currentFilters, setCurrentFilters] = React.useState(false); - const [keepRange, setKeepRange] = React.useState(false); - - return ( - setActiveDashboardId(id)} - onCurrentFiltersToggle={() => setCurrentFilters(old => !old)} - onKeepRangeToggle={() => setKeepRange(old => !old)} - /> - ); -}; - -storiesOf('components/DashboardDrilldownConfig', module) - .add('default', () => ( - console.log('onDashboardSelect', e)} - /> - )) - .add('with switches', () => ( - console.log('onDashboardSelect', e)} - onCurrentFiltersToggle={() => console.log('onCurrentFiltersToggle')} - onKeepRangeToggle={() => console.log('onKeepRangeToggle')} - /> - )) - .add('interactive demo', () => ); diff --git a/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/dashboard_drilldown_config.test.tsx b/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/dashboard_drilldown_config.test.tsx deleted file mode 100644 index 911ff6f632635..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/dashboard_drilldown_config.test.tsx +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -test.todo('renders list of dashboards'); -test.todo('renders correct selected dashboard'); -test.todo('can change dashboard'); -test.todo('can toggle "use current filters" switch'); -test.todo('can toggle "date range" switch'); diff --git a/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/dashboard_drilldown_config.tsx b/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/dashboard_drilldown_config.tsx deleted file mode 100644 index b45ba602b9bb1..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/dashboard_drilldown_config.tsx +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { EuiFormRow, EuiSelect, EuiSwitch } from '@elastic/eui'; -import { txtChooseDestinationDashboard } from './i18n'; - -export interface DashboardItem { - id: string; - title: string; -} - -export interface DashboardDrilldownConfigProps { - activeDashboardId?: string; - dashboards: DashboardItem[]; - currentFilters?: boolean; - keepRange?: boolean; - onDashboardSelect: (dashboardId: string) => void; - onCurrentFiltersToggle?: () => void; - onKeepRangeToggle?: () => void; -} - -export const DashboardDrilldownConfig: React.FC = ({ - activeDashboardId, - dashboards, - currentFilters, - keepRange, - onDashboardSelect, - onCurrentFiltersToggle, - onKeepRangeToggle, -}) => { - // TODO: use i18n below. - return ( - <> - - ({ value: id, text: title }))} - value={activeDashboardId} - onChange={e => onDashboardSelect(e.target.value)} - /> - - {!!onCurrentFiltersToggle && ( - - - - )} - {!!onKeepRangeToggle && ( - - - - )} - - ); -}; diff --git a/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/i18n.ts b/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/i18n.ts deleted file mode 100644 index 38fe6dd150853..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/i18n.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; - -export const txtChooseDestinationDashboard = i18n.translate( - 'xpack.dashboard.components.DashboardDrilldownConfig.chooseDestinationDashboard', - { - defaultMessage: 'Choose destination dashboard', - } -); diff --git a/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/index.ts b/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/index.ts deleted file mode 100644 index b9a64a3cc17e6..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export * from './dashboard_drilldown_config'; diff --git a/x-pack/plugins/dashboard_enhanced/public/components/index.ts b/x-pack/plugins/dashboard_enhanced/public/components/index.ts deleted file mode 100644 index b9a64a3cc17e6..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/components/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export * from './dashboard_drilldown_config'; diff --git a/x-pack/plugins/dashboard_enhanced/public/index.ts b/x-pack/plugins/dashboard_enhanced/public/index.ts deleted file mode 100644 index 53540a4a1ad2e..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { PluginInitializerContext } from 'src/core/public'; -import { DashboardEnhancedPlugin } from './plugin'; - -export { - SetupContract as DashboardEnhancedSetupContract, - SetupDependencies as DashboardEnhancedSetupDependencies, - StartContract as DashboardEnhancedStartContract, - StartDependencies as DashboardEnhancedStartDependencies, -} from './plugin'; - -export function plugin(context: PluginInitializerContext) { - return new DashboardEnhancedPlugin(context); -} diff --git a/x-pack/plugins/dashboard_enhanced/public/mocks.ts b/x-pack/plugins/dashboard_enhanced/public/mocks.ts deleted file mode 100644 index 67dc1fd97d521..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/mocks.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { DashboardEnhancedSetupContract, DashboardEnhancedStartContract } 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 dashboardEnhancedPluginMock = { - createSetupContract, - createStartContract, -}; diff --git a/x-pack/plugins/dashboard_enhanced/public/plugin.ts b/x-pack/plugins/dashboard_enhanced/public/plugin.ts deleted file mode 100644 index 30b3f3c080f49..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/plugin.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { CoreStart, CoreSetup, Plugin, PluginInitializerContext } from 'src/core/public'; -import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public'; -import { DashboardDrilldownsService } from './services'; -import { DrilldownsSetup, DrilldownsStart } from '../../drilldowns/public'; - -export interface SetupDependencies { - uiActions: UiActionsSetup; - drilldowns: DrilldownsSetup; -} - -export interface StartDependencies { - uiActions: UiActionsStart; - drilldowns: DrilldownsStart; -} - -// eslint-disable-next-line -export interface SetupContract {} - -// eslint-disable-next-line -export interface StartContract {} - -export class DashboardEnhancedPlugin - implements Plugin { - public readonly drilldowns = new DashboardDrilldownsService(); - public readonly config: { drilldowns: { enabled: boolean } }; - - constructor(protected readonly context: PluginInitializerContext) { - this.config = context.config.get(); - } - - public setup(core: CoreSetup, plugins: SetupDependencies): SetupContract { - this.drilldowns.bootstrap(core, plugins, { - enableDrilldowns: this.config.drilldowns.enabled, - }); - - return {}; - } - - public start(core: CoreStart, plugins: StartDependencies): StartContract { - return {}; - } - - public stop() {} -} 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 deleted file mode 100644 index 31ee9e29938cb..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.test.tsx +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - FlyoutCreateDrilldownAction, - OpenFlyoutAddDrilldownParams, -} from './flyout_create_drilldown'; -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 { TriggerContextMapping } from '../../../../../../../../src/plugins/ui_actions/public'; -import { MockEmbeddable } from '../test_helpers'; - -const overlays = coreMock.createStart().overlays; -const drilldowns = drilldownsPluginMock.createStartContract(); -const uiActions = uiActionsPluginMock.createStartContract(); - -const actionParams: OpenFlyoutAddDrilldownParams = { - drilldowns: () => Promise.resolve(drilldowns), - overlays: () => Promise.resolve(overlays), -}; - -test('should create', () => { - expect(() => new FlyoutCreateDrilldownAction(actionParams)).not.toThrow(); -}); - -test('title is a string', () => { - expect(typeof new FlyoutCreateDrilldownAction(actionParams).getDisplayName() === 'string').toBe( - true - ); -}); - -test('icon exists', () => { - expect(typeof new FlyoutCreateDrilldownAction(actionParams).getIconType() === 'string').toBe( - true - ); -}); - -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 - } - ), - }); - } - - 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); - }); - - test('not compatible if dynamicUiActions disabled', async () => { - expect( - await checkCompatibility({ - withUiActions: false, - isEdit: true, - isValueClickTriggerSupported: true, - }) - ).toBe(false); - }); - - test("not compatible if 'VALUE_CLICK_TRIGGER' is not supported", async () => { - expect( - await checkCompatibility({ - withUiActions: true, - isEdit: true, - isValueClickTriggerSupported: false, - }) - ).toBe(false); - }); - - test('not compatible if in view mode', async () => { - expect( - await checkCompatibility({ - withUiActions: true, - isEdit: false, - isValueClickTriggerSupported: true, - }) - ).toBe(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"` - ); - }); - - test('should open flyout', async () => { - const spy = jest.spyOn(overlays, 'openFlyout'); - await drilldownAction.execute({ - embeddable: new MockEmbeddable({ id: '' }, { uiActions }), - }); - 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 deleted file mode 100644 index 00e74ea570a11..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { i18n } from '@kbn/i18n'; -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 { EmbeddableContext } from '../../../../../../../../src/plugins/embeddable/public'; - -export const OPEN_FLYOUT_ADD_DRILLDOWN = 'OPEN_FLYOUT_ADD_DRILLDOWN'; - -export interface OpenFlyoutAddDrilldownParams { - overlays: () => Promise; - drilldowns: () => Promise; -} - -export class FlyoutCreateDrilldownAction implements ActionByType { - public readonly type = OPEN_FLYOUT_ADD_DRILLDOWN; - public readonly id = OPEN_FLYOUT_ADD_DRILLDOWN; - public order = 12; - - constructor(protected readonly params: OpenFlyoutAddDrilldownParams) {} - - public getDisplayName() { - return i18n.translate('xpack.dashboard.FlyoutCreateDrilldownAction.displayName', { - defaultMessage: 'Create drilldown', - }); - } - - public getIconType() { - return 'plusInCircle'; - } - - private isEmbeddableCompatible(context: EmbeddableContext) { - if (!context.embeddable.dynamicActions) return false; - const supportedTriggers = context.embeddable.supportedTriggers(); - if (!supportedTriggers || !supportedTriggers.length) return false; - return supportedTriggers.indexOf('VALUE_CLICK_TRIGGER') > -1; - } - - public async isCompatible(context: EmbeddableContext) { - const isEditMode = context.embeddable.getInput().viewMode === 'edit'; - return isEditMode && this.isEmbeddableCompatible(context); - } - - 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 FlyoutCreateDrilldownAction without dynamicActionsManager`); - } - - const handle = overlays.openFlyout( - toMountPoint( - handle.close()} - placeContext={context} - viewMode={'create'} - dynamicActionManager={dynamicActionManager} - /> - ), - { - ownFocus: true, - } - ); - } -} diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.ts deleted file mode 100644 index 4d2db209fc961..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { - FlyoutCreateDrilldownAction, - OpenFlyoutAddDrilldownParams, - OPEN_FLYOUT_ADD_DRILLDOWN, -} from './flyout_create_drilldown'; 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 deleted file mode 100644 index a3f11eb976f90..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.test.tsx +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { FlyoutEditDrilldownAction, FlyoutEditDrilldownParams } from './flyout_edit_drilldown'; -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'; - -const overlays = coreMock.createStart().overlays; -const drilldowns = drilldownsPluginMock.createStartContract(); -const uiActions = uiActionsPluginMock.createStartContract(); - -const actionParams: FlyoutEditDrilldownParams = { - drilldowns: () => Promise.resolve(drilldowns), - overlays: () => Promise.resolve(overlays), -}; - -test('should create', () => { - expect(() => new FlyoutEditDrilldownAction(actionParams)).not.toThrow(); -}); - -test('title is a string', () => { - expect(typeof new FlyoutEditDrilldownAction(actionParams).getDisplayName() === 'string').toBe( - true - ); -}); - -test('icon exists', () => { - expect(typeof new FlyoutEditDrilldownAction(actionParams).getIconType() === 'string').toBe(true); -}); - -test('MenuItem exists', () => { - expect(new FlyoutEditDrilldownAction(actionParams).MenuItem).toBeDefined(); -}); - -describe('isCompatible', () => { - const drilldownAction = new FlyoutEditDrilldownAction(actionParams); - - function checkCompatibility(params: { - isEdit: boolean; - withUiActions: boolean; - }): Promise { - return drilldownAction.isCompatible({ - embeddable: new MockEmbeddable( - { - id: '', - viewMode: params.isEdit ? ViewMode.EDIT : ViewMode.VIEW, - }, - { - uiActions: params.withUiActions ? uiActions : undefined, // dynamic actions support - } - ), - }); - } - - // 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 if dynamicUiActions disabled', async () => { - expect( - await checkCompatibility({ - withUiActions: false, - isEdit: true, - }) - ).toBe(false); - }); - - test('not compatible if no drilldowns', async () => { - expect( - await checkCompatibility({ - withUiActions: true, - isEdit: true, - }) - ).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"` - ); - }); - - test('should open flyout', async () => { - const spy = jest.spyOn(overlays, 'openFlyout'); - await drilldownAction.execute({ - embeddable: 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 deleted file mode 100644 index 816b757592a72..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { CoreStart } from 'src/core/public'; -import { ActionByType } from '../../../../../../../../src/plugins/ui_actions/public'; -import { - reactToUiComponent, - toMountPoint, -} from '../../../../../../../../src/plugins/kibana_react/public'; -import { EmbeddableContext, ViewMode } from '../../../../../../../../src/plugins/embeddable/public'; -import { DrilldownsStart } from '../../../../../../drilldowns/public'; -import { txtDisplayName } from './i18n'; -import { MenuItem } from './menu_item'; - -export const OPEN_FLYOUT_EDIT_DRILLDOWN = 'OPEN_FLYOUT_EDIT_DRILLDOWN'; - -export interface FlyoutEditDrilldownParams { - overlays: () => Promise; - drilldowns: () => Promise; -} - -export class FlyoutEditDrilldownAction implements ActionByType { - public readonly type = OPEN_FLYOUT_EDIT_DRILLDOWN; - public readonly id = OPEN_FLYOUT_EDIT_DRILLDOWN; - public order = 10; - - constructor(protected readonly params: FlyoutEditDrilldownParams) {} - - public getDisplayName() { - return txtDisplayName; - } - - public getIconType() { - return 'list'; - } - - MenuItem = reactToUiComponent(MenuItem); - - public async isCompatible({ embeddable }: EmbeddableContext) { - if (embeddable.getInput().viewMode !== ViewMode.EDIT) return false; - if (!embeddable.dynamicActions) return false; - return embeddable.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 handle = overlays.openFlyout( - toMountPoint( - handle.close()} - placeContext={context} - viewMode={'manage'} - dynamicActionManager={dynamicActionManager} - /> - ), - { - ownFocus: true, - } - ); - } -} diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx deleted file mode 100644 index 3e1b37f270708..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { - FlyoutEditDrilldownAction, - FlyoutEditDrilldownParams, - OPEN_FLYOUT_EDIT_DRILLDOWN, -} from './flyout_edit_drilldown'; 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 deleted file mode 100644 index be693fadf9282..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.test.tsx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import 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 '@testing-library/jest-dom'; - -afterEach(cleanup); - -test('', () => { - const state = createStateContainer<{ events: object[] }>({ events: [] }); - const { getByText, queryByText } = render( - - ); - - expect(getByText(/manage drilldowns/i)).toBeInTheDocument(); - expect(queryByText('0')).not.toBeInTheDocument(); - - act(() => { - state.set({ events: [{}] }); - }); - - expect(queryByText('1')).toBeInTheDocument(); -}); 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 deleted file mode 100644 index 4f99fca511b07..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import 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'; - -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); - const count = events.length; - - return ( - - {txtDisplayName} - {count > 0 && ( - - {count} - - )} - - ); -}; 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 deleted file mode 100644 index 9b156b0ba85b4..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/test_helpers.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Embeddable, EmbeddableInput } from '../../../../../../../src/plugins/embeddable/public/'; -import { - TriggerContextMapping, - UiActionsStart, -} from '../../../../../../../src/plugins/ui_actions/public'; - -export class MockEmbeddable extends Embeddable { - public readonly type = 'mock'; - private readonly triggers: Array = []; - constructor( - initialInput: EmbeddableInput, - params: { uiActions?: UiActionsStart; supportedTriggers?: Array } - ) { - super(initialInput, {}, undefined, params); - this.triggers = params.supportedTriggers ?? []; - } - public render(node: HTMLElement) {} - public reload() {} - public supportedTriggers(): Array { - return this.triggers; - } -} 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 deleted file mode 100644 index 4bdf03dff3531..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { CoreSetup } from 'src/core/public'; -import { SetupDependencies } from '../../plugin'; -import { - CONTEXT_MENU_TRIGGER, - EmbeddableContext, -} from '../../../../../../src/plugins/embeddable/public'; -import { - FlyoutCreateDrilldownAction, - FlyoutEditDrilldownAction, - OPEN_FLYOUT_ADD_DRILLDOWN, - OPEN_FLYOUT_EDIT_DRILLDOWN, -} from './actions'; -import { DrilldownsStart } from '../../../../drilldowns/public'; -import { DashboardToDashboardDrilldown } from './dashboard_to_dashboard_drilldown'; - -declare module '../../../../../../src/plugins/ui_actions/public' { - export interface ActionContextMapping { - [OPEN_FLYOUT_ADD_DRILLDOWN]: EmbeddableContext; - [OPEN_FLYOUT_EDIT_DRILLDOWN]: EmbeddableContext; - } -} - -interface BootstrapParams { - enableDrilldowns: boolean; -} - -export class DashboardDrilldownsService { - bootstrap( - core: CoreSetup<{ drilldowns: DrilldownsStart }>, - plugins: SetupDependencies, - { enableDrilldowns }: BootstrapParams - ) { - if (enableDrilldowns) { - this.setupDrilldowns(core, plugins); - } - } - - setupDrilldowns(core: CoreSetup<{ drilldowns: DrilldownsStart }>, plugins: SetupDependencies) { - const overlays = async () => (await core.getStartServices())[0].overlays; - const drilldowns = async () => (await core.getStartServices())[1].drilldowns; - const savedObjects = async () => (await core.getStartServices())[0].savedObjects.client; - - const actionFlyoutCreateDrilldown = new FlyoutCreateDrilldownAction({ overlays, drilldowns }); - plugins.uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, actionFlyoutCreateDrilldown); - - const actionFlyoutEditDrilldown = new FlyoutEditDrilldownAction({ overlays, drilldowns }); - plugins.uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, actionFlyoutEditDrilldown); - - const dashboardToDashboardDrilldown = new DashboardToDashboardDrilldown({ - savedObjects, - }); - plugins.drilldowns.registerDrilldown(dashboardToDashboardDrilldown); - } -} diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/collect_config.test.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/collect_config.test.tsx deleted file mode 100644 index 95101605ce468..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/collect_config.test.tsx +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -test.todo('displays all dashboard in a list'); -test.todo('does not display dashboard on which drilldown is being created'); -test.todo('updates config object correctly'); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/collect_config.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/collect_config.tsx deleted file mode 100644 index e463cc38b6fbf..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/collect_config.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { useState, useEffect } from 'react'; -import { CollectConfigProps } from './types'; -import { DashboardDrilldownConfig } from '../../../components/dashboard_drilldown_config'; -import { Params } from './drilldown'; - -export interface CollectConfigContainerProps extends CollectConfigProps { - params: Params; -} - -export const CollectConfigContainer: React.FC = ({ - config, - onConfig, - params: { savedObjects }, -}) => { - const [dashboards] = useState([ - { id: 'dashboard1', title: 'Dashboard 1' }, - { id: 'dashboard2', title: 'Dashboard 2' }, - { id: 'dashboard3', title: 'Dashboard 3' }, - { id: 'dashboard4', title: 'Dashboard 4' }, - ]); - - useEffect(() => { - // TODO: Load dashboards... - }, [savedObjects]); - - return ( - { - onConfig({ ...config, dashboardId }); - }} - onCurrentFiltersToggle={() => - onConfig({ - ...config, - useCurrentFilters: !config.useCurrentFilters, - }) - } - onKeepRangeToggle={() => - onConfig({ - ...config, - useCurrentDateRange: !config.useCurrentDateRange, - }) - } - /> - ); -}; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/constants.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/constants.ts deleted file mode 100644 index e2a530b156da5..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/constants.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export const DASHBOARD_TO_DASHBOARD_DRILLDOWN = 'DASHBOARD_TO_DASHBOARD_DRILLDOWN'; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.test.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.test.tsx deleted file mode 100644 index 0fb60bb1064a1..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.test.tsx +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -describe('.isConfigValid()', () => { - test.todo('returns false for incorrect config'); - test.todo('returns true for incorrect config'); -}); - -describe('.execute()', () => { - test.todo('navigates to correct dashboard'); - test.todo( - 'when user chooses to keep current filters, current fileters are set on destination dashboard' - ); - test.todo( - 'when user chooses to keep current time range, current time range is set on destination dashboard' - ); -}); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx deleted file mode 100644 index 9d2a378f08acd..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { CoreStart } from 'src/core/public'; -import { reactToUiComponent } from '../../../../../../../src/plugins/kibana_react/public'; -import { PlaceContext, ActionContext, Config, CollectConfigProps } from './types'; -import { CollectConfigContainer } from './collect_config'; -import { DASHBOARD_TO_DASHBOARD_DRILLDOWN } from './constants'; -import { DrilldownDefinition as Drilldown } from '../../../../../drilldowns/public'; -import { txtGoToDashboard } from './i18n'; - -export interface Params { - savedObjects: () => Promise; -} - -export class DashboardToDashboardDrilldown - implements Drilldown { - constructor(protected readonly params: Params) {} - - public readonly id = DASHBOARD_TO_DASHBOARD_DRILLDOWN; - - public readonly order = 100; - - public readonly getDisplayName = () => txtGoToDashboard; - - public readonly euiIcon = 'dashboardApp'; - - private readonly ReactCollectConfig: React.FC = props => ( - - ); - - public readonly CollectConfig = reactToUiComponent(this.ReactCollectConfig); - - public readonly createConfig = () => ({ - dashboardId: '123', - useCurrentFilters: true, - useCurrentDateRange: true, - }); - - public readonly isConfigValid = (config: Config): config is Config => { - if (!config.dashboardId) return false; - return true; - }; - - public readonly execute = () => { - alert('Go to another dashboard!'); - }; -} diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/i18n.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/i18n.ts deleted file mode 100644 index 98b746bafd24a..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/i18n.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; - -export const txtGoToDashboard = i18n.translate('xpack.dashboard.drilldown.goToDashboard', { - defaultMessage: 'Go to Dashboard', -}); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/index.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/index.ts deleted file mode 100644 index 9daa485bb6e6c..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { DASHBOARD_TO_DASHBOARD_DRILLDOWN } from './constants'; -export { - DashboardToDashboardDrilldown, - Params as DashboardToDashboardDrilldownParams, -} from './drilldown'; -export { - PlaceContext as DashboardToDashboardPlaceContext, - ActionContext as DashboardToDashboardActionContext, - Config as DashboardToDashboardConfig, -} from './types'; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/types.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/types.ts deleted file mode 100644 index 398a259491e3e..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/types.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - EmbeddableVisTriggerContext, - EmbeddableContext, -} from '../../../../../../../src/plugins/embeddable/public'; -import { UiActionsCollectConfigProps } from '../../../../../../../src/plugins/ui_actions/public'; - -export type PlaceContext = EmbeddableContext; -export type ActionContext = EmbeddableVisTriggerContext; - -export interface Config { - dashboardId?: string; - useCurrentFilters: boolean; - useCurrentDateRange: boolean; -} - -export type CollectConfigProps = UiActionsCollectConfigProps; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/index.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/index.ts deleted file mode 100644 index 7be8f1c65da12..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export * from './dashboard_drilldowns_services'; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/index.ts b/x-pack/plugins/dashboard_enhanced/public/services/index.ts deleted file mode 100644 index 8cc3e12906531..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/services/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export * from './drilldowns'; diff --git a/x-pack/plugins/dashboard_enhanced/server/config.ts b/x-pack/plugins/dashboard_enhanced/server/config.ts deleted file mode 100644 index b75c95d5f8832..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/server/config.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { schema, TypeOf } from '@kbn/config-schema'; -import { PluginConfigDescriptor } from '../../../../src/core/server'; - -export const configSchema = schema.object({ - drilldowns: schema.object({ - enabled: schema.boolean({ defaultValue: false }), - }), -}); - -export type ConfigSchema = TypeOf; - -export const config: PluginConfigDescriptor = { - schema: configSchema, - exposeToBrowser: { - drilldowns: true, - }, -}; diff --git a/x-pack/plugins/dashboard_enhanced/server/index.ts b/x-pack/plugins/dashboard_enhanced/server/index.ts deleted file mode 100644 index e361b9fb075ed..0000000000000 --- a/x-pack/plugins/dashboard_enhanced/server/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { config } from './config'; - -export const plugin = () => ({ - setup() {}, - start() {}, -}); diff --git a/x-pack/plugins/drilldowns/kibana.json b/x-pack/plugins/drilldowns/kibana.json index 8372d87166364..b951c7dc1fc87 100644 --- a/x-pack/plugins/drilldowns/kibana.json +++ b/x-pack/plugins/drilldowns/kibana.json @@ -3,5 +3,8 @@ "version": "kibana", "server": false, "ui": true, - "requiredPlugins": ["uiActions", "embeddable", "advancedUiActions"] + "requiredPlugins": [ + "uiActions", + "embeddable" + ] } diff --git a/x-pack/plugins/drilldowns/public/actions/flyout_create_drilldown/index.tsx b/x-pack/plugins/drilldowns/public/actions/flyout_create_drilldown/index.tsx new file mode 100644 index 0000000000000..4834cc8081374 --- /dev/null +++ b/x-pack/plugins/drilldowns/public/actions/flyout_create_drilldown/index.tsx @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { CoreStart } from 'src/core/public'; +import { ActionByType } from '../../../../../../src/plugins/ui_actions/public'; +import { toMountPoint } from '../../../../../../src/plugins/kibana_react/public'; +import { IEmbeddable } from '../../../../../../src/plugins/embeddable/public'; +import { FlyoutCreateDrilldown } from '../../components/flyout_create_drilldown'; + +export const OPEN_FLYOUT_ADD_DRILLDOWN = 'OPEN_FLYOUT_ADD_DRILLDOWN'; + +export interface FlyoutCreateDrilldownActionContext { + embeddable: IEmbeddable; +} + +export interface OpenFlyoutAddDrilldownParams { + overlays: () => Promise; +} + +export class FlyoutCreateDrilldownAction implements ActionByType { + public readonly type = OPEN_FLYOUT_ADD_DRILLDOWN; + public readonly id = OPEN_FLYOUT_ADD_DRILLDOWN; + public order = 100; + + constructor(protected readonly params: OpenFlyoutAddDrilldownParams) {} + + public getDisplayName() { + return i18n.translate('xpack.drilldowns.FlyoutCreateDrilldownAction.displayName', { + defaultMessage: 'Create drilldown', + }); + } + + public getIconType() { + return 'plusInCircle'; + } + + public async isCompatible({ embeddable }: FlyoutCreateDrilldownActionContext) { + return embeddable.getInput().viewMode === 'edit'; + } + + public async execute(context: FlyoutCreateDrilldownActionContext) { + const overlays = await this.params.overlays(); + const handle = overlays.openFlyout( + toMountPoint( handle.close()} />) + ); + } +} diff --git a/x-pack/plugins/drilldowns/public/actions/flyout_edit_drilldown/index.tsx b/x-pack/plugins/drilldowns/public/actions/flyout_edit_drilldown/index.tsx new file mode 100644 index 0000000000000..f109da94fcaca --- /dev/null +++ b/x-pack/plugins/drilldowns/public/actions/flyout_edit_drilldown/index.tsx @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { CoreStart } from 'src/core/public'; +import { EuiNotificationBadge } from '@elastic/eui'; +import { ActionByType } from '../../../../../../src/plugins/ui_actions/public'; +import { + toMountPoint, + reactToUiComponent, +} from '../../../../../../src/plugins/kibana_react/public'; +import { IEmbeddable } from '../../../../../../src/plugins/embeddable/public'; +import { FormCreateDrilldown } from '../../components/form_create_drilldown'; + +export const OPEN_FLYOUT_EDIT_DRILLDOWN = 'OPEN_FLYOUT_EDIT_DRILLDOWN'; + +export interface FlyoutEditDrilldownActionContext { + embeddable: IEmbeddable; +} + +export interface FlyoutEditDrilldownParams { + overlays: () => Promise; +} + +const displayName = i18n.translate('xpack.drilldowns.panel.openFlyoutEditDrilldown.displayName', { + defaultMessage: 'Manage drilldowns', +}); + +// mocked data +const drilldrownCount = 2; + +export class FlyoutEditDrilldownAction implements ActionByType { + public readonly type = OPEN_FLYOUT_EDIT_DRILLDOWN; + public readonly id = OPEN_FLYOUT_EDIT_DRILLDOWN; + public order = 100; + + constructor(protected readonly params: FlyoutEditDrilldownParams) {} + + public getDisplayName() { + return displayName; + } + + public getIconType() { + return 'list'; + } + + private ReactComp: React.FC<{ context: FlyoutEditDrilldownActionContext }> = () => { + return ( + <> + {displayName}{' '} + + {drilldrownCount} + + + ); + }; + + MenuItem = reactToUiComponent(this.ReactComp); + + public async isCompatible({ embeddable }: FlyoutEditDrilldownActionContext) { + return embeddable.getInput().viewMode === 'edit' && drilldrownCount > 0; + } + + public async execute({ embeddable }: FlyoutEditDrilldownActionContext) { + const overlays = await this.params.overlays(); + overlays.openFlyout(toMountPoint()); + } +} diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/index.ts b/x-pack/plugins/drilldowns/public/actions/index.ts similarity index 100% rename from x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/index.ts rename to x-pack/plugins/drilldowns/public/actions/index.ts diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx deleted file mode 100644 index 16b4d3a25d9e5..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import * as React from 'react'; -import { EuiFlyout } from '@elastic/eui'; -import { storiesOf } from '@storybook/react'; -import { createFlyoutManageDrilldowns } from './connected_flyout_manage_drilldowns'; -import { - dashboardFactory, - urlFactory, - // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from '../../../../advanced_ui_actions/public/components/action_wizard/test_data'; -import { Storage } from '../../../../../../src/plugins/kibana_utils/public'; -import { StubBrowserStorage } from '../../../../../../src/test_utils/public/stub_browser_storage'; -import { mockDynamicActionManager } from './test_data'; - -const FlyoutManageDrilldowns = createFlyoutManageDrilldowns({ - advancedUiActions: { - getActionFactories() { - return [dashboardFactory, urlFactory]; - }, - } as any, - storage: new Storage(new StubBrowserStorage()), - notifications: { - toasts: { - addError: (...args: any[]) => { - alert(JSON.stringify(args)); - }, - addSuccess: (...args: any[]) => { - alert(JSON.stringify(args)); - }, - } as any, - }, -}); - -storiesOf('components/FlyoutManageDrilldowns', module).add('default', () => ( - {}}> - - -)); diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx deleted file mode 100644 index 6749b41e81fc7..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { cleanup, fireEvent, render, wait } from '@testing-library/react/pure'; -import '@testing-library/jest-dom/extend-expect'; -import { createFlyoutManageDrilldowns } from './connected_flyout_manage_drilldowns'; -import { - dashboardFactory, - urlFactory, -} from '../../../../advanced_ui_actions/public/components/action_wizard/test_data'; -import { StubBrowserStorage } from '../../../../../../src/test_utils/public/stub_browser_storage'; -import { Storage } from '../../../../../../src/plugins/kibana_utils/public'; -import { mockDynamicActionManager } from './test_data'; -import { TEST_SUBJ_DRILLDOWN_ITEM } from '../list_manage_drilldowns'; -import { WELCOME_MESSAGE_TEST_SUBJ } from '../drilldown_hello_bar'; -import { coreMock } from '../../../../../../src/core/public/mocks'; -import { NotificationsStart } from 'kibana/public'; -import { toastDrilldownsCRUDError } from './i18n'; - -const storage = new Storage(new StubBrowserStorage()); -const notifications = coreMock.createStart().notifications; -const FlyoutManageDrilldowns = createFlyoutManageDrilldowns({ - advancedUiActions: { - getActionFactories() { - return [dashboardFactory, urlFactory]; - }, - } as any, - storage, - notifications, -}); - -// https://github.com/elastic/kibana/issues/59469 -afterEach(cleanup); - -beforeEach(() => { - storage.clear(); - (notifications.toasts as jest.Mocked).addSuccess.mockClear(); - (notifications.toasts as jest.Mocked).addError.mockClear(); -}); - -test('Allows to manage drilldowns', async () => { - const screen = render( - - ); - - // wait for initial render. It is async because resolving compatible action factories is async - await wait(() => expect(screen.getByText(/Manage Drilldowns/i)).toBeVisible()); - - // no drilldowns in the list - expect(screen.queryAllByTestId(TEST_SUBJ_DRILLDOWN_ITEM)).toHaveLength(0); - - fireEvent.click(screen.getByText(/Create new/i)); - - let [createHeading, createButton] = screen.getAllByText(/Create Drilldown/i); - expect(createHeading).toBeVisible(); - expect(screen.getByLabelText(/Back/i)).toBeVisible(); - - expect(createButton).toBeDisabled(); - - // input drilldown name - const name = 'Test name'; - fireEvent.change(screen.getByLabelText(/name/i), { - target: { value: name }, - }); - - // select URL one - fireEvent.click(screen.getByText(/Go to URL/i)); - - // Input url - const URL = 'https://elastic.co'; - fireEvent.change(screen.getByLabelText(/url/i), { - target: { value: URL }, - }); - - [createHeading, createButton] = screen.getAllByText(/Create Drilldown/i); - - expect(createButton).toBeEnabled(); - fireEvent.click(createButton); - - expect(screen.getByText(/Manage Drilldowns/i)).toBeVisible(); - - await wait(() => expect(screen.queryAllByTestId(TEST_SUBJ_DRILLDOWN_ITEM)).toHaveLength(1)); - expect(screen.getByText(name)).toBeVisible(); - const editButton = screen.getByText(/edit/i); - fireEvent.click(editButton); - - expect(screen.getByText(/Edit Drilldown/i)).toBeVisible(); - // check that wizard is prefilled with current drilldown values - expect(screen.getByLabelText(/name/i)).toHaveValue(name); - expect(screen.getByLabelText(/url/i)).toHaveValue(URL); - - // input new drilldown name - const newName = 'New drilldown name'; - fireEvent.change(screen.getByLabelText(/name/i), { - target: { value: newName }, - }); - fireEvent.click(screen.getByText(/save/i)); - - expect(screen.getByText(/Manage Drilldowns/i)).toBeVisible(); - await wait(() => screen.getByText(newName)); - - // delete drilldown from edit view - fireEvent.click(screen.getByText(/edit/i)); - fireEvent.click(screen.getByText(/delete/i)); - - expect(screen.getByText(/Manage Drilldowns/i)).toBeVisible(); - await wait(() => expect(screen.queryAllByTestId(TEST_SUBJ_DRILLDOWN_ITEM)).toHaveLength(0)); -}); - -test('Can delete multiple drilldowns', async () => { - const screen = render( - - ); - // wait for initial render. It is async because resolving compatible action factories is async - await wait(() => expect(screen.getByText(/Manage Drilldowns/i)).toBeVisible()); - - const createDrilldown = async () => { - const oldCount = screen.queryAllByTestId(TEST_SUBJ_DRILLDOWN_ITEM).length; - fireEvent.click(screen.getByText(/Create new/i)); - fireEvent.change(screen.getByLabelText(/name/i), { - target: { value: 'test' }, - }); - fireEvent.click(screen.getByText(/Go to URL/i)); - fireEvent.change(screen.getByLabelText(/url/i), { - target: { value: 'https://elastic.co' }, - }); - fireEvent.click(screen.getAllByText(/Create Drilldown/i)[1]); - await wait(() => - expect(screen.queryAllByTestId(TEST_SUBJ_DRILLDOWN_ITEM)).toHaveLength(oldCount + 1) - ); - }; - - await createDrilldown(); - await createDrilldown(); - await createDrilldown(); - - const checkboxes = screen.getAllByLabelText(/Select this drilldown/i); - expect(checkboxes).toHaveLength(3); - checkboxes.forEach(checkbox => fireEvent.click(checkbox)); - expect(screen.queryByText(/Create/i)).not.toBeInTheDocument(); - fireEvent.click(screen.getByText(/Delete \(3\)/i)); - - await wait(() => expect(screen.queryAllByTestId(TEST_SUBJ_DRILLDOWN_ITEM)).toHaveLength(0)); -}); - -test('Create only mode', async () => { - const onClose = jest.fn(); - const screen = render( - - ); - // wait for initial render. It is async because resolving compatible action factories is async - await wait(() => expect(screen.getAllByText(/Create/i).length).toBeGreaterThan(0)); - fireEvent.change(screen.getByLabelText(/name/i), { - target: { value: 'test' }, - }); - fireEvent.click(screen.getByText(/Go to URL/i)); - fireEvent.change(screen.getByLabelText(/url/i), { - target: { value: 'https://elastic.co' }, - }); - fireEvent.click(screen.getAllByText(/Create Drilldown/i)[1]); - - await wait(() => expect(notifications.toasts.addSuccess).toBeCalled()); - expect(onClose).toBeCalled(); - expect(await mockDynamicActionManager.state.get().events.length).toBe(1); -}); - -test.todo("Error when can't fetch drilldown list"); - -test("Error when can't save drilldown changes", async () => { - const error = new Error('Oops'); - jest.spyOn(mockDynamicActionManager, 'createEvent').mockImplementationOnce(async () => { - throw error; - }); - const screen = render( - - ); - // wait for initial render. It is async because resolving compatible action factories is async - await wait(() => expect(screen.getByText(/Manage Drilldowns/i)).toBeVisible()); - fireEvent.click(screen.getByText(/Create new/i)); - fireEvent.change(screen.getByLabelText(/name/i), { - target: { value: 'test' }, - }); - fireEvent.click(screen.getByText(/Go to URL/i)); - fireEvent.change(screen.getByLabelText(/url/i), { - target: { value: 'https://elastic.co' }, - }); - fireEvent.click(screen.getAllByText(/Create Drilldown/i)[1]); - await wait(() => - expect(notifications.toasts.addError).toBeCalledWith(error, { title: toastDrilldownsCRUDError }) - ); -}); - -test('Should show drilldown welcome message. Should be able to dismiss it', async () => { - let screen = render( - - ); - - // wait for initial render. It is async because resolving compatible action factories is async - await wait(() => expect(screen.getByText(/Manage Drilldowns/i)).toBeVisible()); - - expect(screen.getByTestId(WELCOME_MESSAGE_TEST_SUBJ)).toBeVisible(); - fireEvent.click(screen.getByText(/hide/i)); - expect(screen.queryByTestId(WELCOME_MESSAGE_TEST_SUBJ)).toBeNull(); - cleanup(); - - screen = render( - - ); - // wait for initial render. It is async because resolving compatible action factories is async - await wait(() => expect(screen.getByText(/Manage Drilldowns/i)).toBeVisible()); - expect(screen.queryByTestId(WELCOME_MESSAGE_TEST_SUBJ)).toBeNull(); -}); 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 deleted file mode 100644 index f22ccc2f26f02..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx +++ /dev/null @@ -1,332 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { useEffect, useState } from 'react'; -import useMountedState from 'react-use/lib/useMountedState'; -import { - AdvancedUiActionsActionFactory as ActionFactory, - AdvancedUiActionsStart, -} 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, - SELECT_RANGE_TRIGGER, - TriggerContextMapping, -} from '../../../../../../src/plugins/ui_actions/public'; -import { useContainerState } from '../../../../../../src/plugins/kibana_utils/common'; -import { DrilldownListItem } from '../list_manage_drilldowns'; -import { - toastDrilldownCreated, - toastDrilldownDeleted, - toastDrilldownEdited, - toastDrilldownsCRUDError, - toastDrilldownsDeleted, -} from './i18n'; -import { DrilldownFactoryContext } from '../../types'; - -interface ConnectedFlyoutManageDrilldownsProps { - placeContext: Context; - dynamicActionManager: DynamicActionManager; - viewMode?: 'create' | 'manage'; - onClose?: () => void; -} - -/** - * Represent current state (route) of FlyoutManageDrilldowns - */ -enum Routes { - Manage = 'manage', - Create = 'create', - Edit = 'edit', -} - -export function createFlyoutManageDrilldowns({ - advancedUiActions, - storage, - notifications, -}: { - advancedUiActions: AdvancedUiActionsStart; - storage: IStorageWrapper; - notifications: NotificationsStart; -}) { - // fine to assume this is static, - // because all action factories should be registered in setup phase - const allActionFactories = advancedUiActions.getActionFactories(); - const allActionFactoriesById = allActionFactories.reduce((acc, next) => { - acc[next.id] = next; - return acc; - }, {} as Record); - - return (props: ConnectedFlyoutManageDrilldownsProps) => { - const isCreateOnly = props.viewMode === 'create'; - - const selectedTriggers: Array = React.useMemo( - () => [VALUE_CLICK_TRIGGER, SELECT_RANGE_TRIGGER], - [] - ); - - const factoryContext: DrilldownFactoryContext = React.useMemo( - () => ({ - placeContext: props.placeContext, - triggers: selectedTriggers, - }), - [props.placeContext, selectedTriggers] - ); - - const actionFactories = useCompatibleActionFactoriesForCurrentContext( - allActionFactories, - factoryContext - ); - - const [route, setRoute] = useState( - () => (isCreateOnly ? Routes.Create : Routes.Manage) // initial state is different depending on `viewMode` - ); - const [currentEditId, setCurrentEditId] = useState(null); - - const [shouldShowWelcomeMessage, onHideWelcomeMessage] = useWelcomeMessage(storage); - - const { - drilldowns, - createDrilldown, - editDrilldown, - deleteDrilldown, - } = useDrilldownsStateManager(props.dynamicActionManager, notifications); - - /** - * isCompatible promise is not yet resolved. - * Skip rendering until it is resolved - */ - if (!actionFactories) return null; - /** - * Drilldowns are not fetched yet or error happened during fetching - * In case of error user is notified with toast - */ - if (!drilldowns) return null; - - /** - * Needed for edit mode to prefill wizard fields with data from current edited drilldown - */ - function resolveInitialDrilldownWizardConfig(): DrilldownWizardConfig | undefined { - if (route !== Routes.Edit) return undefined; - if (!currentEditId) return undefined; - const drilldownToEdit = drilldowns?.find(d => d.eventId === currentEditId); - if (!drilldownToEdit) return undefined; - - return { - actionFactory: allActionFactoriesById[drilldownToEdit.action.factoryId], - actionConfig: drilldownToEdit.action.config as object, // TODO: config is unknown, but we know it always extends object - name: drilldownToEdit.action.name, - }; - } - - /** - * Maps drilldown to list item view model - */ - function mapToDrilldownToDrilldownListItem( - drilldown: UiActionsSerializedEvent - ): DrilldownListItem { - const actionFactory = allActionFactoriesById[drilldown.action.factoryId]; - return { - id: drilldown.eventId, - drilldownName: drilldown.action.name, - actionName: actionFactory?.getDisplayName(factoryContext) ?? drilldown.action.factoryId, - icon: actionFactory?.getIconType(factoryContext), - }; - } - - switch (route) { - case Routes.Create: - case Routes.Edit: - return ( - setRoute(Routes.Manage)} - onSubmit={({ actionConfig, actionFactory, name }) => { - if (route === Routes.Create) { - createDrilldown( - { - name, - config: actionConfig, - factoryId: actionFactory.id, - }, - selectedTriggers - ); - } else { - editDrilldown( - currentEditId!, - { - name, - config: actionConfig, - factoryId: actionFactory.id, - }, - selectedTriggers - ); - } - - if (isCreateOnly) { - if (props.onClose) { - props.onClose(); - } - } else { - setRoute(Routes.Manage); - } - - setCurrentEditId(null); - }} - onDelete={() => { - deleteDrilldown(currentEditId!); - setRoute(Routes.Manage); - setCurrentEditId(null); - }} - actionFactoryContext={factoryContext} - initialDrilldownWizardConfig={resolveInitialDrilldownWizardConfig()} - /> - ); - - case Routes.Manage: - default: - return ( - { - setCurrentEditId(null); - deleteDrilldown(ids); - }} - onEdit={id => { - setCurrentEditId(id); - setRoute(Routes.Edit); - }} - onCreate={() => { - setCurrentEditId(null); - setRoute(Routes.Create); - }} - onClose={props.onClose} - /> - ); - } - }; -} - -function useCompatibleActionFactoriesForCurrentContext( - actionFactories: Array>, - context: Context -) { - const [compatibleActionFactories, setCompatibleActionFactories] = useState< - Array> - >(); - useEffect(() => { - let canceled = false; - async function updateCompatibleFactoriesForContext() { - const compatibility = await Promise.all( - actionFactories.map(factory => factory.isCompatible(context)) - ); - if (canceled) return; - setCompatibleActionFactories(actionFactories.filter((_, i) => compatibility[i])); - } - updateCompatibleFactoriesForContext(); - return () => { - canceled = true; - }; - }, [context, actionFactories]); - - return compatibleActionFactories; -} - -function useWelcomeMessage(storage: IStorageWrapper): [boolean, () => void] { - const key = `drilldowns:hidWelcomeMessage`; - const [hidWelcomeMessage, setHidWelcomeMessage] = useState(storage.get(key) ?? false); - - return [ - !hidWelcomeMessage, - () => { - if (hidWelcomeMessage) return; - setHidWelcomeMessage(true); - storage.set(key, true); - }, - ]; -} - -function useDrilldownsStateManager( - actionManager: DynamicActionManager, - notifications: NotificationsStart -) { - const { events: drilldowns } = useContainerState(actionManager.state); - const [isLoading, setIsLoading] = useState(false); - const isMounted = useMountedState(); - - async function run(op: () => Promise) { - setIsLoading(true); - try { - await op(); - } catch (e) { - notifications.toasts.addError(e, { - title: toastDrilldownsCRUDError, - }); - if (!isMounted) return; - setIsLoading(false); - return; - } - } - - async function createDrilldown( - action: UiActionsSerializedAction, - selectedTriggers: Array - ) { - await run(async () => { - await actionManager.createEvent(action, selectedTriggers); - notifications.toasts.addSuccess({ - title: toastDrilldownCreated.title, - text: toastDrilldownCreated.text(action.name), - }); - }); - } - - async function editDrilldown( - drilldownId: string, - action: UiActionsSerializedAction, - selectedTriggers: Array - ) { - await run(async () => { - await actionManager.updateEvent(drilldownId, action, selectedTriggers); - notifications.toasts.addSuccess({ - title: toastDrilldownEdited.title, - text: toastDrilldownEdited.text(action.name), - }); - }); - } - - async function deleteDrilldown(drilldownIds: string | string[]) { - await run(async () => { - drilldownIds = Array.isArray(drilldownIds) ? drilldownIds : [drilldownIds]; - await actionManager.deleteEvents(drilldownIds); - notifications.toasts.addSuccess( - drilldownIds.length === 1 - ? { - title: toastDrilldownDeleted.title, - text: toastDrilldownDeleted.text, - } - : { - title: toastDrilldownsDeleted.title, - text: toastDrilldownsDeleted.text(drilldownIds.length), - } - ); - }); - } - - return { drilldowns, isLoading, createDrilldown, editDrilldown, deleteDrilldown }; -} diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/i18n.ts b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/i18n.ts deleted file mode 100644 index 70f4d735e2a74..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/i18n.ts +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; - -export const toastDrilldownCreated = { - title: i18n.translate( - 'xpack.drilldowns.components.flyoutDrilldownWizard.toast.drilldownCreatedTitle', - { - defaultMessage: 'Drilldown created', - } - ), - text: (drilldownName: string) => - i18n.translate('xpack.drilldowns.components.flyoutDrilldownWizard.toast.drilldownCreatedText', { - defaultMessage: 'You created "{drilldownName}"', - values: { - drilldownName, - }, - }), -}; - -export const toastDrilldownEdited = { - title: i18n.translate( - 'xpack.drilldowns.components.flyoutDrilldownWizard.toast.drilldownEditedTitle', - { - defaultMessage: 'Drilldown edited', - } - ), - text: (drilldownName: string) => - i18n.translate('xpack.drilldowns.components.flyoutDrilldownWizard.toast.drilldownEditedText', { - defaultMessage: 'You edited "{drilldownName}"', - values: { - drilldownName, - }, - }), -}; - -export const toastDrilldownDeleted = { - title: i18n.translate( - 'xpack.drilldowns.components.flyoutDrilldownWizard.toast.drilldownDeletedTitle', - { - defaultMessage: 'Drilldown deleted', - } - ), - text: i18n.translate( - 'xpack.drilldowns.components.flyoutDrilldownWizard.toast.drilldownDeletedText', - { - defaultMessage: 'You deleted a drilldown', - } - ), -}; - -export const toastDrilldownsDeleted = { - title: i18n.translate( - 'xpack.drilldowns.components.flyoutDrilldownWizard.toast.drilldownsDeletedTitle', - { - defaultMessage: 'Drilldowns deleted', - } - ), - text: (n: number) => - i18n.translate( - 'xpack.drilldowns.components.flyoutDrilldownWizard.toast.drilldownsDeletedText', - { - defaultMessage: 'You deleted {n} drilldowns', - values: { - n, - }, - } - ), -}; - -export const toastDrilldownsCRUDError = i18n.translate( - 'xpack.drilldowns.components.flyoutDrilldownWizard.toast.drilldownsCRUDErrorTitle', - { - defaultMessage: 'Error saving drilldown', - description: 'Title for generic error toast when persisting drilldown updates failed', - } -); - -export const toastDrilldownsFetchError = i18n.translate( - 'xpack.drilldowns.components.flyoutDrilldownWizard.toast.drilldownsFetchErrorTitle', - { - defaultMessage: 'Error fetching drilldowns', - } -); diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/index.ts b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/index.ts deleted file mode 100644 index f084a3e563c23..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export * from './connected_flyout_manage_drilldowns'; 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 deleted file mode 100644 index b8deaa8b842bc..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import uuid from 'uuid'; -import { - DynamicActionManager, - DynamicActionManagerState, - UiActionsSerializedAction, - TriggerContextMapping, -} from '../../../../../../src/plugins/ui_actions/public'; -import { createStateContainer } from '../../../../../../src/plugins/kibana_utils/common'; - -class MockDynamicActionManager implements PublicMethodsOf { - public readonly state = createStateContainer({ - isFetchingEvents: false, - fetchCount: 0, - events: [], - }); - - async count() { - return this.state.get().events.length; - } - - async list() { - return this.state.get().events; - } - - async createEvent( - action: UiActionsSerializedAction, - triggers: Array - ) { - const event = { - action, - triggers, - eventId: uuid(), - }; - const state = this.state.get(); - this.state.set({ - ...state, - events: [...state.events, event], - }); - } - - async deleteEvents(eventIds: string[]) { - const state = this.state.get(); - let events = state.events; - - eventIds.forEach(id => { - events = events.filter(e => e.eventId !== id); - }); - - this.state.set({ - ...state, - events, - }); - } - - async updateEvent( - eventId: string, - action: UiActionsSerializedAction, - triggers: Array - ) { - const state = this.state.get(); - const events = state.events; - const idx = events.findIndex(e => e.eventId === eventId); - const event = { - eventId, - action, - triggers, - }; - - this.state.set({ - ...state, - events: [...events.slice(0, idx), event, ...events.slice(idx + 1)], - }); - } - - async deleteEvent() { - throw new Error('not implemented'); - } - - async start() {} - async stop() {} -} - -export const mockDynamicActionManager = (new MockDynamicActionManager() as unknown) as DynamicActionManager; diff --git a/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.story.tsx b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.story.tsx index c4a4630397f1c..7a9e19342f27c 100644 --- a/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.story.tsx +++ b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.story.tsx @@ -8,16 +8,6 @@ import * as React from 'react'; import { storiesOf } from '@storybook/react'; import { DrilldownHelloBar } from '.'; -const Demo = () => { - const [show, setShow] = React.useState(true); - return show ? ( - { - setShow(false); - }} - /> - ) : null; -}; - -storiesOf('components/DrilldownHelloBar', module).add('default', () => ); +storiesOf('components/DrilldownHelloBar', module).add('default', () => { + return ; +}); diff --git a/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.tsx b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.tsx index 8c6739a8ad6c8..1ef714f7b86e2 100644 --- a/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.tsx +++ b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.tsx @@ -5,58 +5,22 @@ */ import React from 'react'; -import { - EuiCallOut, - EuiFlexGroup, - EuiFlexItem, - EuiTextColor, - EuiText, - EuiLink, - EuiSpacer, - EuiButtonEmpty, - EuiIcon, -} from '@elastic/eui'; -import { txtHideHelpButtonLabel, txtHelpText, txtViewDocsLinkLabel } from './i18n'; export interface DrilldownHelloBarProps { docsLink?: string; - onHideClick?: () => void; } -export const WELCOME_MESSAGE_TEST_SUBJ = 'drilldowns-welcome-message-test-subj'; - -export const DrilldownHelloBar: React.FC = ({ - docsLink, - onHideClick = () => {}, -}) => { +/** + * @todo https://github.com/elastic/kibana/issues/55311 + */ +export const DrilldownHelloBar: React.FC = ({ docsLink }) => { return ( - - -
- -
-
- - - {txtHelpText} - - {docsLink && ( - <> - - {txtViewDocsLinkLabel} - - )} - - - - {txtHideHelpButtonLabel} - - - - } - /> +
); }; diff --git a/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/i18n.ts b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/i18n.ts deleted file mode 100644 index 63dc95dabc0fb..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/i18n.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; - -export const txtHelpText = i18n.translate( - 'xpack.drilldowns.components.DrilldownHelloBar.helpText', - { - defaultMessage: - 'Drilldowns provide the ability to define a new behavior when interacting with a panel. You can add multiple options or simply override the default filtering behavior.', - } -); - -export const txtViewDocsLinkLabel = i18n.translate( - 'xpack.drilldowns.components.DrilldownHelloBar.viewDocsLinkLabel', - { - defaultMessage: 'View docs', - } -); - -export const txtHideHelpButtonLabel = i18n.translate( - 'xpack.drilldowns.components.DrilldownHelloBar.hideHelpButtonLabel', - { - defaultMessage: 'Hide', - } -); diff --git a/x-pack/plugins/dashboard_enhanced/scripts/storybook.js b/x-pack/plugins/drilldowns/public/components/drilldown_picker/drilldown_picker.story.tsx similarity index 53% rename from x-pack/plugins/dashboard_enhanced/scripts/storybook.js rename to x-pack/plugins/drilldowns/public/components/drilldown_picker/drilldown_picker.story.tsx index f2cbe4135f4cb..5627a5d6f4522 100644 --- a/x-pack/plugins/dashboard_enhanced/scripts/storybook.js +++ b/x-pack/plugins/drilldowns/public/components/drilldown_picker/drilldown_picker.story.tsx @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { join } from 'path'; +import * as React from 'react'; +import { storiesOf } from '@storybook/react'; +import { DrilldownPicker } from '.'; -// eslint-disable-next-line -require('@kbn/storybook').runStorybookCli({ - name: 'dashboard_enhanced', - storyGlobs: [join(__dirname, '..', 'public', 'components', '**', '*.story.tsx')], +storiesOf('components/DrilldownPicker', module).add('default', () => { + return ; }); diff --git a/x-pack/plugins/drilldowns/public/components/drilldown_picker/drilldown_picker.tsx b/x-pack/plugins/drilldowns/public/components/drilldown_picker/drilldown_picker.tsx new file mode 100644 index 0000000000000..3748fc666c81c --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/drilldown_picker/drilldown_picker.tsx @@ -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 React from 'react'; + +// eslint-disable-next-line +export interface DrilldownPickerProps {} + +export const DrilldownPicker: React.FC = () => { + return ( + + ); +}; diff --git a/x-pack/plugins/advanced_ui_actions/public/components/index.ts b/x-pack/plugins/drilldowns/public/components/drilldown_picker/index.tsx similarity index 87% rename from x-pack/plugins/advanced_ui_actions/public/components/index.ts rename to x-pack/plugins/drilldowns/public/components/drilldown_picker/index.tsx index 236b1a6ec4611..3be289fe6d46e 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/index.ts +++ b/x-pack/plugins/drilldowns/public/components/drilldown_picker/index.tsx @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './action_wizard'; +export * from './drilldown_picker'; diff --git a/x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/flyout_create_drilldown.story.tsx b/x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/flyout_create_drilldown.story.tsx new file mode 100644 index 0000000000000..4f024b7d9cd6a --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/flyout_create_drilldown.story.tsx @@ -0,0 +1,24 @@ +/* + * 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 no-console */ + +import * as React from 'react'; +import { EuiFlyout } from '@elastic/eui'; +import { storiesOf } from '@storybook/react'; +import { FlyoutCreateDrilldown } from '.'; + +storiesOf('components/FlyoutCreateDrilldown', module) + .add('default', () => { + return ; + }) + .add('open in flyout', () => { + return ( + + + + ); + }); diff --git a/x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/flyout_create_drilldown.tsx b/x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/flyout_create_drilldown.tsx new file mode 100644 index 0000000000000..b45ac9197c7e0 --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/flyout_create_drilldown.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiButton } from '@elastic/eui'; +import { FormCreateDrilldown } from '../form_create_drilldown'; +import { FlyoutFrame } from '../flyout_frame'; +import { txtCreateDrilldown } from './i18n'; +import { FlyoutCreateDrilldownActionContext } from '../../actions'; + +export interface FlyoutCreateDrilldownProps { + context: FlyoutCreateDrilldownActionContext; + onClose?: () => void; +} + +export const FlyoutCreateDrilldown: React.FC = ({ + context, + onClose, +}) => { + const footer = ( + {}} fill> + {txtCreateDrilldown} + + ); + + return ( + + + + ); +}; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/i18n.ts b/x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/i18n.ts similarity index 64% rename from x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/i18n.ts rename to x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/i18n.ts index 4e2e5eb7092e4..ceabc6d3a9aa5 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/i18n.ts +++ b/x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/i18n.ts @@ -6,9 +6,9 @@ import { i18n } from '@kbn/i18n'; -export const txtDisplayName = i18n.translate( - 'xpack.dashboard.panel.openFlyoutEditDrilldown.displayName', +export const txtCreateDrilldown = i18n.translate( + 'xpack.drilldowns.components.FlyoutCreateDrilldown.CreateDrilldown', { - defaultMessage: 'Manage drilldowns', + defaultMessage: 'Create drilldown', } ); diff --git a/x-pack/plugins/advanced_ui_actions/public/services/index.ts b/x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/index.ts similarity index 84% rename from x-pack/plugins/advanced_ui_actions/public/services/index.ts rename to x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/index.ts index 0f8b4c8d8f409..ce235043b4ef6 100644 --- a/x-pack/plugins/advanced_ui_actions/public/services/index.ts +++ b/x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './action_factory_service'; +export * from './flyout_create_drilldown'; diff --git a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.story.tsx b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.story.tsx deleted file mode 100644 index 152cd393b9d3e..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.story.tsx +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -/* eslint-disable no-console */ - -import * as React from 'react'; -import { EuiFlyout } from '@elastic/eui'; -import { storiesOf } from '@storybook/react'; -import { FlyoutDrilldownWizard } from '.'; -import { - dashboardFactory, - urlFactory, - // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from '../../../../advanced_ui_actions/public/components/action_wizard/test_data'; - -storiesOf('components/FlyoutDrilldownWizard', module) - .add('default', () => { - return ; - }) - .add('open in flyout - create', () => { - return ( - {}}> - {}} - drilldownActionFactories={[urlFactory, dashboardFactory]} - /> - - ); - }) - .add('open in flyout - edit', () => { - return ( - {}}> - {}} - drilldownActionFactories={[urlFactory, dashboardFactory]} - initialDrilldownWizardConfig={{ - name: 'My fancy drilldown', - actionFactory: urlFactory as any, - actionConfig: { - url: 'https://elastic.co', - openInNewTab: true, - }, - }} - mode={'edit'} - /> - - ); - }) - .add('open in flyout - edit, just 1 action type', () => { - return ( - {}}> - {}} - drilldownActionFactories={[dashboardFactory]} - initialDrilldownWizardConfig={{ - name: 'My fancy drilldown', - actionFactory: urlFactory as any, - actionConfig: { - url: 'https://elastic.co', - openInNewTab: true, - }, - }} - mode={'edit'} - /> - - ); - }); diff --git a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx deleted file mode 100644 index faa965a98a4bb..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { useState } from 'react'; -import { EuiButton, EuiSpacer } from '@elastic/eui'; -import { FormDrilldownWizard } from '../form_drilldown_wizard'; -import { FlyoutFrame } from '../flyout_frame'; -import { - txtCreateDrilldownButtonLabel, - txtCreateDrilldownTitle, - txtDeleteDrilldownButtonLabel, - txtEditDrilldownButtonLabel, - txtEditDrilldownTitle, -} from './i18n'; -import { DrilldownHelloBar } from '../drilldown_hello_bar'; -import { AdvancedUiActionsActionFactory as ActionFactory } from '../../../../advanced_ui_actions/public'; - -export interface DrilldownWizardConfig { - name: string; - actionFactory?: ActionFactory; - actionConfig?: ActionConfig; -} - -export interface FlyoutDrilldownWizardProps { - drilldownActionFactories: Array>; - - onSubmit?: (drilldownWizardConfig: Required) => void; - onDelete?: () => void; - onClose?: () => void; - onBack?: () => void; - - mode?: 'create' | 'edit'; - initialDrilldownWizardConfig?: DrilldownWizardConfig; - - showWelcomeMessage?: boolean; - onWelcomeHideClick?: () => void; - - actionFactoryContext?: object; -} - -export function FlyoutDrilldownWizard({ - onClose, - onBack, - onSubmit = () => {}, - initialDrilldownWizardConfig, - mode = 'create', - onDelete = () => {}, - showWelcomeMessage = true, - onWelcomeHideClick, - drilldownActionFactories, - actionFactoryContext, -}: FlyoutDrilldownWizardProps) { - const [wizardConfig, setWizardConfig] = useState( - () => - initialDrilldownWizardConfig ?? { - name: '', - } - ); - - const isActionValid = ( - config: DrilldownWizardConfig - ): config is Required => { - if (!wizardConfig.name) return false; - if (!wizardConfig.actionFactory) return false; - if (!wizardConfig.actionConfig) return false; - - return wizardConfig.actionFactory.isConfigValid(wizardConfig.actionConfig); - }; - - const footer = ( - { - if (isActionValid(wizardConfig)) { - onSubmit(wizardConfig); - } - }} - fill - isDisabled={!isActionValid(wizardConfig)} - > - {mode === 'edit' ? txtEditDrilldownButtonLabel : txtCreateDrilldownButtonLabel} - - ); - - return ( - } - > - { - setWizardConfig({ - ...wizardConfig, - name: newName, - }); - }} - actionConfig={wizardConfig.actionConfig} - onActionConfigChange={newActionConfig => { - setWizardConfig({ - ...wizardConfig, - actionConfig: newActionConfig, - }); - }} - currentActionFactory={wizardConfig.actionFactory} - onActionFactoryChange={actionFactory => { - if (!actionFactory) { - setWizardConfig({ - ...wizardConfig, - actionFactory: undefined, - actionConfig: undefined, - }); - } else { - setWizardConfig({ - ...wizardConfig, - actionFactory, - actionConfig: actionFactory.createConfig(), - }); - } - }} - actionFactories={drilldownActionFactories} - actionFactoryContext={actionFactoryContext!} - /> - {mode === 'edit' && ( - <> - - - {txtDeleteDrilldownButtonLabel} - - - )} - - ); -} diff --git a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/i18n.ts b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/i18n.ts deleted file mode 100644 index a4a2754a444ab..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/i18n.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; - -export const txtCreateDrilldownTitle = i18n.translate( - 'xpack.drilldowns.components.flyoutDrilldownWizard.createDrilldownTitle', - { - defaultMessage: 'Create Drilldown', - } -); - -export const txtEditDrilldownTitle = i18n.translate( - 'xpack.drilldowns.components.flyoutDrilldownWizard.editDrilldownTitle', - { - defaultMessage: 'Edit Drilldown', - } -); - -export const txtCreateDrilldownButtonLabel = i18n.translate( - 'xpack.drilldowns.components.flyoutDrilldownWizard.createDrilldownButtonLabel', - { - defaultMessage: 'Create drilldown', - } -); - -export const txtEditDrilldownButtonLabel = i18n.translate( - 'xpack.drilldowns.components.flyoutDrilldownWizard.editDrilldownButtonLabel', - { - defaultMessage: 'Save', - } -); - -export const txtDeleteDrilldownButtonLabel = i18n.translate( - 'xpack.drilldowns.components.flyoutDrilldownWizard.deleteDrilldownButtonLabel', - { - defaultMessage: 'Delete drilldown', - } -); diff --git a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/index.ts b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/index.ts deleted file mode 100644 index 96ed23bf112c9..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export * from './flyout_drilldown_wizard'; diff --git a/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.story.tsx b/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.story.tsx index cb223db556f56..2715637f6392f 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.story.tsx +++ b/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.story.tsx @@ -21,13 +21,6 @@ storiesOf('components/FlyoutFrame', module) .add('with onClose', () => { return console.log('onClose')}>test; }) - .add('with onBack', () => { - return ( - console.log('onClose')} title={'Title'}> - test - - ); - }) .add('custom footer', () => { return click me!}>test; }) diff --git a/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.test.tsx b/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.test.tsx index 0a3989487745f..b5fb52fcf5c18 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.test.tsx +++ b/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.test.tsx @@ -6,11 +6,9 @@ import React from 'react'; import { render } from 'react-dom'; -import { render as renderTestingLibrary, fireEvent, cleanup } from '@testing-library/react/pure'; +import { render as renderTestingLibrary, fireEvent } from '@testing-library/react'; import { FlyoutFrame } from '.'; -afterEach(cleanup); - describe('', () => { test('renders without crashing', () => { const div = document.createElement('div'); diff --git a/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.tsx b/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.tsx index b55cbd88d0dc0..2945cfd739482 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.tsx +++ b/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.tsx @@ -13,16 +13,13 @@ import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty, - EuiButtonIcon, } from '@elastic/eui'; -import { txtClose, txtBack } from './i18n'; +import { txtClose } from './i18n'; export interface FlyoutFrameProps { title?: React.ReactNode; footer?: React.ReactNode; - banner?: React.ReactNode; onClose?: () => void; - onBack?: () => void; } /** @@ -33,31 +30,11 @@ export const FlyoutFrame: React.FC = ({ footer, onClose, children, - onBack, - banner, }) => { - const headerFragment = (title || onBack) && ( + const headerFragment = title && ( - - {onBack && ( - -
- -
-
- )} - {title && ( - -

{title}

-
- )} -
+

{title}

); @@ -87,7 +64,7 @@ export const FlyoutFrame: React.FC = ({ return ( <> {headerFragment} - {children} + {children} {footerFragment} ); diff --git a/x-pack/plugins/drilldowns/public/components/flyout_frame/i18n.ts b/x-pack/plugins/drilldowns/public/components/flyout_frame/i18n.ts index 23af89ebf9bc7..257d7d36dbee1 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_frame/i18n.ts +++ b/x-pack/plugins/drilldowns/public/components/flyout_frame/i18n.ts @@ -6,10 +6,6 @@ import { i18n } from '@kbn/i18n'; -export const txtClose = i18n.translate('xpack.drilldowns.components.FlyoutFrame.CloseButtonLabel', { +export const txtClose = i18n.translate('xpack.drilldowns.components.FlyoutFrame.Close', { defaultMessage: 'Close', }); - -export const txtBack = i18n.translate('xpack.drilldowns.components.FlyoutFrame.BackButtonLabel', { - defaultMessage: 'Back', -}); diff --git a/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/flyout_list_manage_drilldowns.story.tsx b/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/flyout_list_manage_drilldowns.story.tsx deleted file mode 100644 index 0529f0451b16a..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/flyout_list_manage_drilldowns.story.tsx +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import * as React from 'react'; -import { EuiFlyout } from '@elastic/eui'; -import { storiesOf } from '@storybook/react'; -import { FlyoutListManageDrilldowns } from './flyout_list_manage_drilldowns'; - -storiesOf('components/FlyoutListManageDrilldowns', module).add('default', () => ( - {}}> - - -)); diff --git a/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/flyout_list_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/flyout_list_manage_drilldowns.tsx deleted file mode 100644 index a44a7ccccb4dc..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/flyout_list_manage_drilldowns.tsx +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { FlyoutFrame } from '../flyout_frame'; -import { DrilldownListItem, ListManageDrilldowns } from '../list_manage_drilldowns'; -import { txtManageDrilldowns } from './i18n'; -import { DrilldownHelloBar } from '../drilldown_hello_bar'; - -export interface FlyoutListManageDrilldownsProps { - drilldowns: DrilldownListItem[]; - onClose?: () => void; - onCreate?: () => void; - onEdit?: (drilldownId: string) => void; - onDelete?: (drilldownIds: string[]) => void; - showWelcomeMessage?: boolean; - onWelcomeHideClick?: () => void; -} - -export function FlyoutListManageDrilldowns({ - drilldowns, - onClose = () => {}, - onCreate, - onDelete, - onEdit, - showWelcomeMessage = true, - onWelcomeHideClick, -}: FlyoutListManageDrilldownsProps) { - return ( - } - > - - - ); -} diff --git a/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/i18n.ts b/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/i18n.ts deleted file mode 100644 index 0dd4e37d4dddd..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/i18n.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; - -export const txtManageDrilldowns = i18n.translate( - 'xpack.drilldowns.components.FlyoutListManageDrilldowns.manageDrilldownsTitle', - { - defaultMessage: 'Manage Drilldowns', - } -); diff --git a/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/index.ts b/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/index.ts deleted file mode 100644 index f8c9d224fb292..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export * from './flyout_list_manage_drilldowns'; diff --git a/x-pack/plugins/drilldowns/public/components/form_create_drilldown/form_create_drilldown.story.tsx b/x-pack/plugins/drilldowns/public/components/form_create_drilldown/form_create_drilldown.story.tsx new file mode 100644 index 0000000000000..e7e1d67473e8c --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/form_create_drilldown/form_create_drilldown.story.tsx @@ -0,0 +1,34 @@ +/* + * 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 no-console */ + +import * as React from 'react'; +import { EuiFlyout } from '@elastic/eui'; +import { storiesOf } from '@storybook/react'; +import { FormCreateDrilldown } from '.'; + +const DemoEditName: React.FC = () => { + const [name, setName] = React.useState(''); + + return ; +}; + +storiesOf('components/FormCreateDrilldown', module) + .add('default', () => { + return ; + }) + .add('[name=foobar]', () => { + return ; + }) + .add('can edit name', () => ) + .add('open in flyout', () => { + return ( + + + + ); + }); diff --git a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.test.tsx b/x-pack/plugins/drilldowns/public/components/form_create_drilldown/form_create_drilldown.test.tsx similarity index 70% rename from x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.test.tsx rename to x-pack/plugins/drilldowns/public/components/form_create_drilldown/form_create_drilldown.test.tsx index 4560773cc8a6d..6691966e47e64 100644 --- a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.test.tsx +++ b/x-pack/plugins/drilldowns/public/components/form_create_drilldown/form_create_drilldown.test.tsx @@ -6,23 +6,21 @@ import React from 'react'; import { render } from 'react-dom'; -import { FormDrilldownWizard } from './form_drilldown_wizard'; -import { render as renderTestingLibrary, fireEvent, cleanup } from '@testing-library/react/pure'; +import { FormCreateDrilldown } from '.'; +import { render as renderTestingLibrary, fireEvent } from '@testing-library/react'; import { txtNameOfDrilldown } from './i18n'; -afterEach(cleanup); - -describe('', () => { +describe('', () => { test('renders without crashing', () => { const div = document.createElement('div'); - render( {}} actionFactoryContext={{}} />, div); + render( {}} />, div); }); describe('[name=]', () => { test('if name not provided, uses to empty string', () => { const div = document.createElement('div'); - render(, div); + render(, div); const input = div.querySelector( '[data-test-subj="dynamicActionNameInput"]' @@ -31,10 +29,10 @@ describe('', () => { expect(input?.value).toBe(''); }); - test('can set initial name input field value', () => { + test('can set name input field value', () => { const div = document.createElement('div'); - render(, div); + render(, div); const input = div.querySelector( '[data-test-subj="dynamicActionNameInput"]' @@ -42,7 +40,7 @@ describe('', () => { expect(input?.value).toBe('foo'); - render(, div); + render(, div); expect(input?.value).toBe('bar'); }); @@ -50,7 +48,7 @@ describe('', () => { test('fires onNameChange callback on name change', () => { const onNameChange = jest.fn(); const utils = renderTestingLibrary( - + ); const input = utils.getByLabelText(txtNameOfDrilldown); diff --git a/x-pack/plugins/drilldowns/public/components/form_create_drilldown/form_create_drilldown.tsx b/x-pack/plugins/drilldowns/public/components/form_create_drilldown/form_create_drilldown.tsx new file mode 100644 index 0000000000000..4422de604092b --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/form_create_drilldown/form_create_drilldown.tsx @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiForm, EuiFormRow, EuiFieldText } from '@elastic/eui'; +import { DrilldownHelloBar } from '../drilldown_hello_bar'; +import { txtNameOfDrilldown, txtUntitledDrilldown, txtDrilldownAction } from './i18n'; +import { DrilldownPicker } from '../drilldown_picker'; + +const noop = () => {}; + +export interface FormCreateDrilldownProps { + name?: string; + onNameChange?: (name: string) => void; +} + +export const FormCreateDrilldown: React.FC = ({ + name = '', + onNameChange = noop, +}) => { + const nameFragment = ( + + onNameChange(event.target.value)} + data-test-subj="dynamicActionNameInput" + /> + + ); + + const triggerPicker =
Trigger Picker will be here
; + const actionPicker = ( + + + + ); + + return ( + <> + + {nameFragment} + {triggerPicker} + {actionPicker} + + ); +}; diff --git a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/i18n.ts b/x-pack/plugins/drilldowns/public/components/form_create_drilldown/i18n.ts similarity index 89% rename from x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/i18n.ts rename to x-pack/plugins/drilldowns/public/components/form_create_drilldown/i18n.ts index e9b19ab0afa97..4c0e287935edd 100644 --- a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/i18n.ts +++ b/x-pack/plugins/drilldowns/public/components/form_create_drilldown/i18n.ts @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; export const txtNameOfDrilldown = i18n.translate( 'xpack.drilldowns.components.FormCreateDrilldown.nameOfDrilldown', { - defaultMessage: 'Name', + defaultMessage: 'Name of drilldown', } ); @@ -23,6 +23,6 @@ export const txtUntitledDrilldown = i18n.translate( export const txtDrilldownAction = i18n.translate( 'xpack.drilldowns.components.FormCreateDrilldown.drilldownAction', { - defaultMessage: 'Action', + defaultMessage: 'Drilldown action', } ); diff --git a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/index.tsx b/x-pack/plugins/drilldowns/public/components/form_create_drilldown/index.tsx similarity index 85% rename from x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/index.tsx rename to x-pack/plugins/drilldowns/public/components/form_create_drilldown/index.tsx index 4aea824de00d7..c2c5a7e435b39 100644 --- a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/index.tsx +++ b/x-pack/plugins/drilldowns/public/components/form_create_drilldown/index.tsx @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './form_drilldown_wizard'; +export * from './form_create_drilldown'; diff --git a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.story.tsx b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.story.tsx deleted file mode 100644 index 2fc35eb6b5298..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.story.tsx +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import * as React from 'react'; -import { storiesOf } from '@storybook/react'; -import { FormDrilldownWizard } from '.'; - -const DemoEditName: React.FC = () => { - const [name, setName] = React.useState(''); - - return ( - <> - {' '} -
name: {name}
- - ); -}; - -storiesOf('components/FormDrilldownWizard', module) - .add('default', () => { - return ; - }) - .add('[name=foobar]', () => { - return ; - }) - .add('can edit name', () => ); diff --git a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx deleted file mode 100644 index bdafaaf07873c..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { EuiFieldText, EuiForm, EuiFormRow, EuiSpacer } from '@elastic/eui'; -import { txtDrilldownAction, txtNameOfDrilldown, txtUntitledDrilldown } from './i18n'; -import { - AdvancedUiActionsActionFactory as ActionFactory, - ActionWizard, -} from '../../../../advanced_ui_actions/public'; - -const noopFn = () => {}; - -export interface FormDrilldownWizardProps { - name?: string; - onNameChange?: (name: string) => void; - - currentActionFactory?: ActionFactory; - onActionFactoryChange?: (actionFactory: ActionFactory | null) => void; - actionFactoryContext: object; - - actionConfig?: object; - onActionConfigChange?: (config: object) => void; - - actionFactories?: ActionFactory[]; -} - -export const FormDrilldownWizard: React.FC = ({ - name = '', - actionConfig, - currentActionFactory, - onNameChange = noopFn, - onActionConfigChange = noopFn, - onActionFactoryChange = noopFn, - actionFactories = [], - actionFactoryContext, -}) => { - const nameFragment = ( - - onNameChange(event.target.value)} - data-test-subj="dynamicActionNameInput" - /> - - ); - - const actionWizard = ( - 1 ? txtDrilldownAction : undefined} - fullWidth={true} - > - onActionFactoryChange(actionFactory)} - onConfigChange={config => onActionConfigChange(config)} - context={actionFactoryContext} - /> - - ); - - return ( - <> - - {nameFragment} - - {actionWizard} - - - ); -}; diff --git a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/i18n.ts b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/i18n.ts deleted file mode 100644 index fbc7c9dcfb4a1..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/i18n.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; - -export const txtCreateDrilldown = i18n.translate( - 'xpack.drilldowns.components.ListManageDrilldowns.createDrilldownButtonLabel', - { - defaultMessage: 'Create new', - } -); - -export const txtEditDrilldown = i18n.translate( - 'xpack.drilldowns.components.ListManageDrilldowns.editDrilldownButtonLabel', - { - defaultMessage: 'Edit', - } -); - -export const txtDeleteDrilldowns = (count: number) => - i18n.translate('xpack.drilldowns.components.ListManageDrilldowns.deleteDrilldownsButtonLabel', { - defaultMessage: 'Delete ({count})', - values: { - count, - }, - }); - -export const txtSelectDrilldown = i18n.translate( - 'xpack.drilldowns.components.ListManageDrilldowns.selectThisDrilldownCheckboxLabel', - { - defaultMessage: 'Select this drilldown', - } -); diff --git a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/index.tsx b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/index.tsx deleted file mode 100644 index 82b6ce27af6d4..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/index.tsx +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export * from './list_manage_drilldowns'; diff --git a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.story.tsx b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.story.tsx deleted file mode 100644 index eafe50bab2016..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.story.tsx +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import * as React from 'react'; -import { storiesOf } from '@storybook/react'; -import { ListManageDrilldowns } from './list_manage_drilldowns'; - -storiesOf('components/ListManageDrilldowns', module).add('default', () => ( - -)); diff --git a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.test.tsx b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.test.tsx deleted file mode 100644 index 4a4d67b08b1d3..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.test.tsx +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { cleanup, fireEvent, render } from '@testing-library/react/pure'; -import '@testing-library/jest-dom/extend-expect'; // TODO: this should be global -import { - DrilldownListItem, - ListManageDrilldowns, - TEST_SUBJ_DRILLDOWN_ITEM, -} from './list_manage_drilldowns'; - -// TODO: for some reason global cleanup from RTL doesn't work -// afterEach is not available for it globally during setup -afterEach(cleanup); - -const drilldowns: DrilldownListItem[] = [ - { id: '1', actionName: 'Dashboard', drilldownName: 'Drilldown 1' }, - { id: '2', actionName: 'Dashboard', drilldownName: 'Drilldown 2' }, - { id: '3', actionName: 'Dashboard', drilldownName: 'Drilldown 3' }, -]; - -test('Render list of drilldowns', () => { - const screen = render(); - expect(screen.getAllByTestId(TEST_SUBJ_DRILLDOWN_ITEM)).toHaveLength(drilldowns.length); -}); - -test('Emit onEdit() when clicking on edit drilldown', () => { - const fn = jest.fn(); - const screen = render(); - - const editButtons = screen.getAllByText('Edit'); - expect(editButtons).toHaveLength(drilldowns.length); - fireEvent.click(editButtons[1]); - expect(fn).toBeCalledWith(drilldowns[1].id); -}); - -test('Emit onCreate() when clicking on create drilldown', () => { - const fn = jest.fn(); - const screen = render(); - fireEvent.click(screen.getByText('Create new')); - expect(fn).toBeCalled(); -}); - -test('Delete button is not visible when non is selected', () => { - const fn = jest.fn(); - const screen = render(); - expect(screen.queryByText(/Delete/i)).not.toBeInTheDocument(); - expect(screen.queryByText(/Create/i)).toBeInTheDocument(); -}); - -test('Can delete drilldowns', () => { - const fn = jest.fn(); - const screen = render(); - - const checkboxes = screen.getAllByLabelText(/Select this drilldown/i); - expect(checkboxes).toHaveLength(3); - - fireEvent.click(checkboxes[1]); - fireEvent.click(checkboxes[2]); - - expect(screen.queryByText(/Create/i)).not.toBeInTheDocument(); - - fireEvent.click(screen.getByText(/Delete \(2\)/i)); - - expect(fn).toBeCalledWith([drilldowns[1].id, drilldowns[2].id]); -}); diff --git a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.tsx deleted file mode 100644 index 5a15781a1faf2..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.tsx +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - EuiBasicTable, - EuiBasicTableColumn, - EuiButton, - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - EuiSpacer, - EuiTextColor, -} from '@elastic/eui'; -import React, { useState } from 'react'; -import { - txtCreateDrilldown, - txtDeleteDrilldowns, - txtEditDrilldown, - txtSelectDrilldown, -} from './i18n'; - -export interface DrilldownListItem { - id: string; - actionName: string; - drilldownName: string; - icon?: string; -} - -export interface ListManageDrilldownsProps { - drilldowns: DrilldownListItem[]; - - onEdit?: (id: string) => void; - onCreate?: () => void; - onDelete?: (ids: string[]) => void; -} - -const noop = () => {}; - -export const TEST_SUBJ_DRILLDOWN_ITEM = 'list-manage-drilldowns-item'; - -export function ListManageDrilldowns({ - drilldowns, - onEdit = noop, - onCreate = noop, - onDelete = noop, -}: ListManageDrilldownsProps) { - const [selectedDrilldowns, setSelectedDrilldowns] = useState([]); - - const columns: Array> = [ - { - field: 'drilldownName', - name: 'Name', - truncateText: true, - width: '50%', - }, - { - name: 'Action', - render: (drilldown: DrilldownListItem) => ( - - {drilldown.icon && ( - - - - )} - - {drilldown.actionName} - - - ), - }, - { - align: 'right', - render: (drilldown: DrilldownListItem) => ( - onEdit(drilldown.id)}> - {txtEditDrilldown} - - ), - }, - ]; - - return ( - <> - { - setSelectedDrilldowns(selection.map(drilldown => drilldown.id)); - }, - selectableMessage: () => txtSelectDrilldown, - }} - rowProps={{ - 'data-test-subj': TEST_SUBJ_DRILLDOWN_ITEM, - }} - hasActions={true} - /> - - {selectedDrilldowns.length === 0 ? ( - onCreate()}> - {txtCreateDrilldown} - - ) : ( - onDelete(selectedDrilldowns)}> - {txtDeleteDrilldowns(selectedDrilldowns.length)} - - )} - - ); -} diff --git a/x-pack/plugins/drilldowns/public/index.ts b/x-pack/plugins/drilldowns/public/index.ts index 044e29c671de4..63e7a12235462 100644 --- a/x-pack/plugins/drilldowns/public/index.ts +++ b/x-pack/plugins/drilldowns/public/index.ts @@ -7,14 +7,12 @@ import { DrilldownsPlugin } from './plugin'; export { - SetupContract as DrilldownsSetup, - SetupDependencies as DrilldownsSetupDependencies, - StartContract as DrilldownsStart, - StartDependencies as DrilldownsStartDependencies, + DrilldownsSetupContract, + DrilldownsSetupDependencies, + DrilldownsStartContract, + DrilldownsStartDependencies, } from './plugin'; export function plugin() { return new DrilldownsPlugin(); } - -export { DrilldownDefinition } from './types'; diff --git a/x-pack/plugins/drilldowns/public/mocks.ts b/x-pack/plugins/drilldowns/public/mocks.ts index 18816243a3572..bfade1674072a 100644 --- a/x-pack/plugins/drilldowns/public/mocks.ts +++ b/x-pack/plugins/drilldowns/public/mocks.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { DrilldownsSetup, DrilldownsStart } from '.'; +import { DrilldownsSetupContract, DrilldownsStartContract } from '.'; -export type Setup = jest.Mocked; -export type Start = jest.Mocked; +export type Setup = jest.Mocked; +export type Start = jest.Mocked; const createSetupContract = (): Setup => { const setupContract: Setup = { @@ -17,14 +17,12 @@ const createSetupContract = (): Setup => { }; const createStartContract = (): Start => { - const startContract: Start = { - FlyoutManageDrilldowns: jest.fn(), - }; + const startContract: Start = {}; return startContract; }; -export const drilldownsPluginMock = { +export const bfetchPluginMock = { createSetupContract, createStartContract, }; diff --git a/x-pack/plugins/drilldowns/public/plugin.ts b/x-pack/plugins/drilldowns/public/plugin.ts index bbc06847d5842..b89172541b91e 100644 --- a/x-pack/plugins/drilldowns/public/plugin.ts +++ b/x-pack/plugins/drilldowns/public/plugin.ts @@ -6,46 +6,52 @@ import { CoreStart, CoreSetup, Plugin } from 'src/core/public'; import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public'; -import { AdvancedUiActionsSetup, AdvancedUiActionsStart } from '../../advanced_ui_actions/public'; -import { DrilldownService, DrilldownServiceSetupContract } from './services'; -import { createFlyoutManageDrilldowns } from './components/connected_flyout_manage_drilldowns'; -import { Storage } from '../../../../src/plugins/kibana_utils/public'; - -export interface SetupDependencies { +import { DrilldownService } from './service'; +import { + FlyoutCreateDrilldownActionContext, + FlyoutEditDrilldownActionContext, + OPEN_FLYOUT_ADD_DRILLDOWN, + OPEN_FLYOUT_EDIT_DRILLDOWN, +} from './actions'; + +export interface DrilldownsSetupDependencies { uiActions: UiActionsSetup; - advancedUiActions: AdvancedUiActionsSetup; } -export interface StartDependencies { +export interface DrilldownsStartDependencies { uiActions: UiActionsStart; - advancedUiActions: AdvancedUiActionsStart; } -export type SetupContract = DrilldownServiceSetupContract; +export type DrilldownsSetupContract = Pick; // eslint-disable-next-line -export interface StartContract { - FlyoutManageDrilldowns: ReturnType; +export interface DrilldownsStartContract {} + +declare module '../../../../src/plugins/ui_actions/public' { + export interface ActionContextMapping { + [OPEN_FLYOUT_ADD_DRILLDOWN]: FlyoutCreateDrilldownActionContext; + [OPEN_FLYOUT_EDIT_DRILLDOWN]: FlyoutEditDrilldownActionContext; + } } export class DrilldownsPlugin - implements Plugin { + implements + Plugin< + DrilldownsSetupContract, + DrilldownsStartContract, + DrilldownsSetupDependencies, + DrilldownsStartDependencies + > { private readonly service = new DrilldownService(); - public setup(core: CoreSetup, plugins: SetupDependencies): SetupContract { - const setup = this.service.setup(core, plugins); + public setup(core: CoreSetup, plugins: DrilldownsSetupDependencies): DrilldownsSetupContract { + this.service.bootstrap(core, plugins); - return setup; + return this.service; } - public start(core: CoreStart, plugins: StartDependencies): StartContract { - return { - FlyoutManageDrilldowns: createFlyoutManageDrilldowns({ - advancedUiActions: plugins.advancedUiActions, - storage: new Storage(localStorage), - notifications: core.notifications, - }), - }; + public start(core: CoreStart, plugins: DrilldownsStartDependencies): DrilldownsStartContract { + return {}; } public stop() {} diff --git a/x-pack/plugins/drilldowns/public/service/drilldown_service.ts b/x-pack/plugins/drilldowns/public/service/drilldown_service.ts new file mode 100644 index 0000000000000..7745c30b4e335 --- /dev/null +++ b/x-pack/plugins/drilldowns/public/service/drilldown_service.ts @@ -0,0 +1,32 @@ +/* + * 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 { CoreSetup } from 'src/core/public'; +// import { CONTEXT_MENU_TRIGGER } from '../../../../../src/plugins/embeddable/public'; +import { FlyoutCreateDrilldownAction, FlyoutEditDrilldownAction } from '../actions'; +import { DrilldownsSetupDependencies } from '../plugin'; + +export class DrilldownService { + bootstrap(core: CoreSetup, { uiActions }: DrilldownsSetupDependencies) { + const overlays = async () => (await core.getStartServices())[0].overlays; + + const actionFlyoutCreateDrilldown = new FlyoutCreateDrilldownAction({ overlays }); + uiActions.registerAction(actionFlyoutCreateDrilldown); + // uiActions.attachAction(CONTEXT_MENU_TRIGGER, actionFlyoutCreateDrilldown); + + const actionFlyoutEditDrilldown = new FlyoutEditDrilldownAction({ overlays }); + uiActions.registerAction(actionFlyoutEditDrilldown); + // uiActions.attachAction(CONTEXT_MENU_TRIGGER, actionFlyoutEditDrilldown); + } + + /** + * Convenience method to register a drilldown. (It should set-up all the + * necessary triggers and actions.) + */ + registerDrilldown = (): void => { + throw new Error('not implemented'); + }; +} diff --git a/x-pack/plugins/drilldowns/public/services/index.ts b/x-pack/plugins/drilldowns/public/service/index.ts similarity index 100% rename from x-pack/plugins/drilldowns/public/services/index.ts rename to x-pack/plugins/drilldowns/public/service/index.ts diff --git a/x-pack/plugins/drilldowns/public/services/drilldown_service.ts b/x-pack/plugins/drilldowns/public/services/drilldown_service.ts deleted file mode 100644 index bfbe514d46095..0000000000000 --- a/x-pack/plugins/drilldowns/public/services/drilldown_service.ts +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { CoreSetup } from 'src/core/public'; -import { AdvancedUiActionsSetup } from '../../../advanced_ui_actions/public'; -import { DrilldownDefinition, DrilldownFactoryContext } from '../types'; -import { UiActionsActionFactoryDefinition as ActionFactoryDefinition } from '../../../../../src/plugins/ui_actions/public'; - -export interface DrilldownServiceSetupDeps { - advancedUiActions: AdvancedUiActionsSetup; -} - -export interface DrilldownServiceSetupContract { - /** - * Convenience method to register a drilldown. - */ - registerDrilldown: < - Config extends object = object, - CreationContext extends object = object, - ExecutionContext extends object = object - >( - drilldown: DrilldownDefinition - ) => void; -} - -export class DrilldownService { - setup( - core: CoreSetup, - { advancedUiActions }: DrilldownServiceSetupDeps - ): DrilldownServiceSetupContract { - const registerDrilldown = < - Config extends object = object, - CreationContext extends object = object, - ExecutionContext extends object = object - >({ - id: factoryId, - CollectConfig, - createConfig, - isConfigValid, - getDisplayName, - euiIcon, - execute, - }: DrilldownDefinition) => { - const actionFactory: ActionFactoryDefinition< - Config, - DrilldownFactoryContext, - ExecutionContext - > = { - id: factoryId, - CollectConfig, - createConfig, - isConfigValid, - getDisplayName, - getIconType: () => euiIcon, - isCompatible: async () => true, - create: serializedAction => ({ - id: '', - type: factoryId, - getIconType: () => euiIcon, - getDisplayName: () => serializedAction.name, - execute: async context => await execute(serializedAction.config, context), - }), - } as ActionFactoryDefinition< - Config, - DrilldownFactoryContext, - ExecutionContext - >; - - advancedUiActions.registerActionFactory(actionFactory); - }; - - return { - registerDrilldown, - }; - } -} diff --git a/x-pack/plugins/drilldowns/public/types.ts b/x-pack/plugins/drilldowns/public/types.ts deleted file mode 100644 index a8232887f9ca6..0000000000000 --- a/x-pack/plugins/drilldowns/public/types.ts +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { AdvancedUiActionsActionFactoryDefinition as ActionFactoryDefinition } from '../../advanced_ui_actions/public'; - -/** - * This is a convenience interface to register a drilldown. Drilldown has - * ability to collect configuration from user. Once drilldown is executed it - * receives the collected information together with the context of the - * user's interaction. - * - * `Config` is a serializable object containing the configuration that the - * drilldown is able to collect using UI. - * - * `PlaceContext` is an object that the app that opens drilldown management - * flyout provides to the React component, specifying the contextual information - * about that app. For example, on Dashboard app this context contains - * information about the current embeddable and dashboard. - * - * `ExecutionContext` is an object created in response to user's interaction - * and provided to the `execute` function of the drilldown. This object contains - * information about the action user performed. - */ -export interface DrilldownDefinition< - Config extends object = object, - PlaceContext extends object = object, - ExecutionContext extends object = object -> { - /** - * Globally unique identifier for this drilldown. - */ - id: string; - - /** - * Function that returns default config for this drilldown. - */ - createConfig: ActionFactoryDefinition< - Config, - DrilldownFactoryContext, - ExecutionContext - >['createConfig']; - - /** - * `UiComponent` that collections config for this drilldown. You can create - * a React component and transform it `UiComponent` using `uiToReactComponent` - * helper from `kibana_utils` plugin. - * - * ```tsx - * import React from 'react'; - * import { uiToReactComponent } from 'src/plugins/kibana_utils'; - * import { UiActionsCollectConfigProps as CollectConfigProps } from 'src/plugins/ui_actions/public'; - * - * type Props = CollectConfigProps; - * - * const ReactCollectConfig: React.FC = () => { - * return
Collecting config...'
; - * }; - * - * export const CollectConfig = uiToReactComponent(ReactCollectConfig); - * ``` - */ - CollectConfig: ActionFactoryDefinition< - Config, - DrilldownFactoryContext, - ExecutionContext - >['CollectConfig']; - - /** - * A validator function for the config object. Should always return a boolean - * given any input. - */ - isConfigValid: ActionFactoryDefinition< - Config, - DrilldownFactoryContext, - ExecutionContext - >['isConfigValid']; - - /** - * Name of EUI icon to display when showing this drilldown to user. - */ - euiIcon?: string; - - /** - * Should return an internationalized name of the drilldown, which will be - * displayed to the user. - */ - getDisplayName: () => string; - - /** - * Implements the "navigation" action of the drilldown. This happens when - * user clicks something in the UI that executes a trigger to which this - * drilldown was attached. - * - * @param config Config object that user configured this drilldown with. - * @param context Object that represents context in which the underlying - * `UIAction` of this drilldown is being executed in. - */ - execute(config: Config, context: ExecutionContext): void; -} - -/** - * Context object used when creating a drilldown. - */ -export interface DrilldownFactoryContext { - /** - * Context provided to the drilldown factory by the place where the UI is - * rendered. For example, for the "dashboard" place, this context contains - * the ID of the current dashboard, which could be used for filtering it out - * of the list. - */ - placeContext: T; - - /** - * List of triggers that user selected in the UI. - */ - triggers: string[]; -} diff --git a/x-pack/plugins/reporting/public/plugin.tsx b/x-pack/plugins/reporting/public/plugin.tsx index ac46d84469513..08ba10ff69207 100644 --- a/x-pack/plugins/reporting/public/plugin.tsx +++ b/x-pack/plugins/reporting/public/plugin.tsx @@ -143,7 +143,8 @@ export class ReportingPublicPlugin implements Plugin { }, }); - uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, action); + uiActions.registerAction(action); + uiActions.attachAction(CONTEXT_MENU_TRIGGER, action); share.register(csvReportingProvider({ apiClient, toasts, license$ })); share.register( diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 09392093b8f62..129aa25c27e84 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -636,6 +636,7 @@ "embeddableApi.addPanel.noMatchingObjectsMessage": "一致するオブジェクトが見つかりませんでした。", "embeddableApi.addPanel.savedObjectAddedToContainerSuccessMessageTitle": "{savedObjectName} が追加されました", "embeddableApi.addPanel.Title": "パネルの追加", + "embeddableApi.customizePanel.action.displayName": "パネルをカスタマイズ", "embeddableApi.customizePanel.modal.cancel": "キャンセル", "embeddableApi.customizePanel.modal.optionsMenuForm.panelTitleFormRowLabel": "パネルタイトル", "embeddableApi.customizePanel.modal.optionsMenuForm.panelTitleInputAriaLabel": "パネルのカスタムタイトルを入力してください", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index a3382a19f76b7..d0a354a2108d4 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -636,6 +636,7 @@ "embeddableApi.addPanel.noMatchingObjectsMessage": "未找到任何匹配对象。", "embeddableApi.addPanel.savedObjectAddedToContainerSuccessMessageTitle": "{savedObjectName} 已添加", "embeddableApi.addPanel.Title": "添加面板", + "embeddableApi.customizePanel.action.displayName": "定制面板", "embeddableApi.customizePanel.modal.cancel": "取消", "embeddableApi.customizePanel.modal.optionsMenuForm.panelTitleFormRowLabel": "面板标题", "embeddableApi.customizePanel.modal.optionsMenuForm.panelTitleInputAriaLabel": "为面板输入定制标题",