From 4b6d77fa5d5e3a830ccaabad863252044f7c1523 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Wed, 23 Sep 2020 11:12:12 +0200 Subject: [PATCH] [Drilldowns] Config to disable URL Drilldown (#77887) This pr makes sure there is way to disable URL drilldown feature. I decided to extract Url drilldown definition into a separate plugin to benefit from regular disabling a plugin feature. Having it as a separate plugin also makes sense because we will start adding registries specific to URL drilldown implementation Co-authored-by: Elastic Machine --- docs/developer/plugin-list.asciidoc | 4 ++ docs/user/dashboard/url-drilldown.asciidoc | 11 ++++ src/plugins/ui_actions/public/mocks.ts | 1 + .../public/service/ui_actions_service.ts | 4 ++ x-pack/.i18nrc.json | 1 + .../drilldowns/url_drilldown/README.md | 16 ++--- .../drilldowns/url_drilldown/kibana.json | 8 +++ .../url_drilldown/public}/index.ts | 7 ++- .../url_drilldown/public/lib}/i18n.ts | 9 +-- .../url_drilldown/public/lib}/index.ts | 0 .../public/lib}/url_drilldown.test.ts | 0 .../public/lib}/url_drilldown.tsx | 0 .../public/lib}/url_drilldown_scope.test.ts | 0 .../public/lib}/url_drilldown_scope.ts | 0 .../drilldowns/url_drilldown/public/plugin.ts | 59 +++++++++++++++++++ .../plugins/embeddable_enhanced/kibana.json | 3 +- .../embeddable_enhanced/public/plugin.ts | 16 ----- .../dynamic_action_manager.test.ts | 17 +++++- .../dynamic_actions/dynamic_action_manager.ts | 19 +++++- .../ui_actions_enhanced/public/mocks.ts | 1 + .../ui_actions_enhanced/public/plugin.ts | 7 ++- .../ui_actions_service_enhancements.ts | 4 ++ 22 files changed, 151 insertions(+), 36 deletions(-) rename x-pack/plugins/{embeddable_enhanced/public => }/drilldowns/url_drilldown/README.md (65%) create mode 100644 x-pack/plugins/drilldowns/url_drilldown/kibana.json rename x-pack/plugins/{embeddable_enhanced/public/drilldowns => drilldowns/url_drilldown/public}/index.ts (53%) rename x-pack/plugins/{embeddable_enhanced/public/drilldowns/url_drilldown => drilldowns/url_drilldown/public/lib}/i18n.ts (62%) rename x-pack/plugins/{embeddable_enhanced/public/drilldowns/url_drilldown => drilldowns/url_drilldown/public/lib}/index.ts (100%) rename x-pack/plugins/{embeddable_enhanced/public/drilldowns/url_drilldown => drilldowns/url_drilldown/public/lib}/url_drilldown.test.ts (100%) rename x-pack/plugins/{embeddable_enhanced/public/drilldowns/url_drilldown => drilldowns/url_drilldown/public/lib}/url_drilldown.tsx (100%) rename x-pack/plugins/{embeddable_enhanced/public/drilldowns/url_drilldown => drilldowns/url_drilldown/public/lib}/url_drilldown_scope.test.ts (100%) rename x-pack/plugins/{embeddable_enhanced/public/drilldowns/url_drilldown => drilldowns/url_drilldown/public/lib}/url_drilldown_scope.ts (100%) create mode 100644 x-pack/plugins/drilldowns/url_drilldown/public/plugin.ts diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index b426621fed296..5a4a60c2e628e 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -504,6 +504,10 @@ in their infrastructure. |Contains HTTP endpoints and UiSettings that are slated for removal. +|{kib-repo}blob/{branch}/x-pack/plugins/drilldowns/url_drilldown/README.md[urlDrilldown] +|NOTE: This plugin contains implementation of URL drilldown. For drilldowns infrastructure code refer to ui_actions_enhanced plugin. + + |=== include::{kibana-root}/src/plugins/dashboard/README.asciidoc[leveloffset=+1] diff --git a/docs/user/dashboard/url-drilldown.asciidoc b/docs/user/dashboard/url-drilldown.asciidoc index e6daf89d72718..ee879256a1fae 100644 --- a/docs/user/dashboard/url-drilldown.asciidoc +++ b/docs/user/dashboard/url-drilldown.asciidoc @@ -238,3 +238,14 @@ Tip: Consider using <> helper for date formatting. | Aggregation field behind the selected range, if available. |=== + +[float] +[[disable]] +==== Disable URL drilldown + +You can disable URL drilldown feature on your {kib} instance by disabling the plugin: + +["source","yml"] +----------- +url_drilldown.enabled: false +----------- diff --git a/src/plugins/ui_actions/public/mocks.ts b/src/plugins/ui_actions/public/mocks.ts index 3522ac4941ba0..759430169b613 100644 --- a/src/plugins/ui_actions/public/mocks.ts +++ b/src/plugins/ui_actions/public/mocks.ts @@ -48,6 +48,7 @@ const createStartContract = (): Start => { executeTriggerActions: jest.fn(), fork: jest.fn(), getAction: jest.fn(), + hasAction: jest.fn(), getTrigger: jest.fn(), getTriggerActions: jest.fn((id: TriggerId) => []), getTriggerCompatibleActions: jest.fn(), 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 6028177964fb7..ec5f3afa19c94 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_service.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_service.ts @@ -99,6 +99,10 @@ export class UiActionsService { this.actions.delete(actionId); }; + public readonly hasAction = (actionId: string): boolean => { + return this.actions.has(actionId); + }; + public readonly attachAction = (triggerId: T, actionId: string): void => { const trigger = this.triggers.get(triggerId); diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index b0124546944ae..66ae478b86828 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -55,6 +55,7 @@ "xpack.triggersActionsUI": "plugins/triggers_actions_ui", "xpack.upgradeAssistant": "plugins/upgrade_assistant", "xpack.uptime": ["plugins/uptime"], + "xpack.urlDrilldown": "plugins/drilldowns/url_drilldown", "xpack.watcher": "plugins/watcher", "xpack.observability": "plugins/observability" }, diff --git a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/README.md b/x-pack/plugins/drilldowns/url_drilldown/README.md similarity index 65% rename from x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/README.md rename to x-pack/plugins/drilldowns/url_drilldown/README.md index 996723ccb914d..8eedc44ca35ae 100644 --- a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/README.md +++ b/x-pack/plugins/drilldowns/url_drilldown/README.md @@ -1,24 +1,26 @@ -# Basic url drilldown implementation +## URL drilldown + +> NOTE: This plugin contains implementation of URL drilldown. For drilldowns infrastructure code refer to `ui_actions_enhanced` plugin. Url drilldown allows navigating to external URL or to internal kibana URL. By using variables in url template result url can be dynamic and depend on user's interaction. URL drilldown has 3 sources for variables: -- Global static variables like, for example, `kibanaUrl`. Such variables won’t change depending on a place where url drilldown is used. -- Context variables are dynamic and different depending on where drilldown is created and used. -- Event variables depend on a trigger context. These variables are dynamically extracted from the action context when drilldown is executed. +1. Global static variables like, for example, `kibanaUrl`. Such variables won’t change depending on a place where url drilldown is used. +2. Context variables are dynamic and different depending on where drilldown is created and used. +3. Event variables depend on a trigger context. These variables are dynamically extracted from the action context when drilldown is executed. Difference between `event` and `context` variables, is that real `context` variables are available during drilldown creation (e.g. embeddable panel), but `event` variables mapped from trigger context. Since there is no trigger context during drilldown creation, we have to provide some _mock_ variables for validating and previewing the URL. In current implementation url drilldown has to be used inside the embeddable and with `ValueClickTrigger` or `RangeSelectTrigger`. -- `context` variables extracted from `embeddable` -- `event` variables extracted from `trigger` context +* `context` variables extracted from `embeddable` +* `event` variables extracted from `trigger` context In future this basic url drilldown implementation would allow injecting more variables into `context` (e.g. `dashboard` app specific variables) and would allow providing support for new trigger types from outside. This extensibility improvements are tracked here: https://github.com/elastic/kibana/issues/55324 In case a solution app has a use case for url drilldown that has to be different from current basic implementation and -just extending variables list is not enough, then recommendation is to create own custom url drilldown and reuse building blocks from `ui_actions_enhanced`. +just extending variables list is not enough, then recommendation is to create own custom url drilldown and reuse building blocks from `ui_actions_enhanced`. \ No newline at end of file diff --git a/x-pack/plugins/drilldowns/url_drilldown/kibana.json b/x-pack/plugins/drilldowns/url_drilldown/kibana.json new file mode 100644 index 0000000000000..9bdd13fbfea26 --- /dev/null +++ b/x-pack/plugins/drilldowns/url_drilldown/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "urlDrilldown", + "version": "kibana", + "server": false, + "ui": true, + "requiredPlugins": ["embeddable", "uiActions", "uiActionsEnhanced"], + "requiredBundles": ["kibanaUtils", "kibanaReact"] +} diff --git a/x-pack/plugins/embeddable_enhanced/public/drilldowns/index.ts b/x-pack/plugins/drilldowns/url_drilldown/public/index.ts similarity index 53% rename from x-pack/plugins/embeddable_enhanced/public/drilldowns/index.ts rename to x-pack/plugins/drilldowns/url_drilldown/public/index.ts index a8d5a179dbac1..b040ef625bc1f 100644 --- a/x-pack/plugins/embeddable_enhanced/public/drilldowns/index.ts +++ b/x-pack/plugins/drilldowns/url_drilldown/public/index.ts @@ -4,4 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './url_drilldown'; +import { PluginInitializerContext } from 'src/core/public'; +import { UrlDrilldownPlugin } from './plugin'; + +export function plugin(context: PluginInitializerContext) { + return new UrlDrilldownPlugin(context); +} diff --git a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/i18n.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/i18n.ts similarity index 62% rename from x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/i18n.ts rename to x-pack/plugins/drilldowns/url_drilldown/public/lib/i18n.ts index 748f6f4cecedd..7e91c6b849035 100644 --- a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/i18n.ts +++ b/x-pack/plugins/drilldowns/url_drilldown/public/lib/i18n.ts @@ -6,9 +6,6 @@ import { i18n } from '@kbn/i18n'; -export const txtUrlDrilldownDisplayName = i18n.translate( - 'xpack.embeddableEnhanced.drilldowns.urlDrilldownDisplayName', - { - defaultMessage: 'Go to URL', - } -); +export const txtUrlDrilldownDisplayName = i18n.translate('xpack.urlDrilldown.DisplayName', { + defaultMessage: 'Go to URL', +}); diff --git a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/index.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/index.ts similarity index 100% rename from x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/index.ts rename to x-pack/plugins/drilldowns/url_drilldown/public/lib/index.ts diff --git a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown.test.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.test.ts similarity index 100% rename from x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown.test.ts rename to x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.test.ts diff --git a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown.tsx b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx similarity index 100% rename from x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown.tsx rename to x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx diff --git a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown_scope.test.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.test.ts similarity index 100% rename from x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown_scope.test.ts rename to x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.test.ts diff --git a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown_scope.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts similarity index 100% rename from x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown_scope.ts rename to x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/plugin.ts b/x-pack/plugins/drilldowns/url_drilldown/public/plugin.ts new file mode 100644 index 0000000000000..82ce7a129f497 --- /dev/null +++ b/x-pack/plugins/drilldowns/url_drilldown/public/plugin.ts @@ -0,0 +1,59 @@ +/* + * 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, CoreStart, Plugin, PluginInitializerContext } from 'src/core/public'; +import { EmbeddableSetup, EmbeddableStart } from '../../../../../src/plugins/embeddable/public'; +import { + AdvancedUiActionsSetup, + AdvancedUiActionsStart, + urlDrilldownGlobalScopeProvider, +} from '../../../ui_actions_enhanced/public'; +import { UrlDrilldown } from './lib'; +import { createStartServicesGetter } from '../../../../../src/plugins/kibana_utils/public'; + +export interface SetupDependencies { + embeddable: EmbeddableSetup; + uiActionsEnhanced: AdvancedUiActionsSetup; +} + +export interface StartDependencies { + embeddable: EmbeddableStart; + uiActionsEnhanced: AdvancedUiActionsStart; +} + +// eslint-disable-next-line +export interface SetupContract {} + +// eslint-disable-next-line +export interface StartContract {} + +export class UrlDrilldownPlugin + implements Plugin { + constructor(protected readonly context: PluginInitializerContext) {} + + public setup(core: CoreSetup, plugins: SetupDependencies): SetupContract { + const startServices = createStartServicesGetter(core.getStartServices); + plugins.uiActionsEnhanced.registerDrilldown( + new UrlDrilldown({ + getGlobalScope: urlDrilldownGlobalScopeProvider({ core }), + navigateToUrl: (url: string) => + core.getStartServices().then(([{ application }]) => application.navigateToUrl(url)), + getSyntaxHelpDocsLink: () => + startServices().core.docLinks.links.dashboard.urlDrilldownTemplateSyntax, + getVariablesHelpDocsLink: () => + startServices().core.docLinks.links.dashboard.urlDrilldownVariables, + }) + ); + + return {}; + } + + public start(core: CoreStart, plugins: StartDependencies): StartContract { + return {}; + } + + public stop() {} +} diff --git a/x-pack/plugins/embeddable_enhanced/kibana.json b/x-pack/plugins/embeddable_enhanced/kibana.json index acada946fe0d1..8d49e3e26eb7b 100644 --- a/x-pack/plugins/embeddable_enhanced/kibana.json +++ b/x-pack/plugins/embeddable_enhanced/kibana.json @@ -3,6 +3,5 @@ "version": "kibana", "server": false, "ui": true, - "requiredPlugins": ["embeddable", "kibanaReact", "uiActions", "uiActionsEnhanced"], - "requiredBundles": ["kibanaUtils"] + "requiredPlugins": ["embeddable", "kibanaReact", "uiActions", "uiActionsEnhanced"] } diff --git a/x-pack/plugins/embeddable_enhanced/public/plugin.ts b/x-pack/plugins/embeddable_enhanced/public/plugin.ts index 2138a372523b7..5d5ad852839d4 100644 --- a/x-pack/plugins/embeddable_enhanced/public/plugin.ts +++ b/x-pack/plugins/embeddable_enhanced/public/plugin.ts @@ -28,11 +28,8 @@ import { UiActionsEnhancedDynamicActionManager as DynamicActionManager, AdvancedUiActionsSetup, AdvancedUiActionsStart, - urlDrilldownGlobalScopeProvider, } from '../../ui_actions_enhanced/public'; import { PanelNotificationsAction, ACTION_PANEL_NOTIFICATIONS } from './actions'; -import { UrlDrilldown } from './drilldowns'; -import { createStartServicesGetter } from '../../../../src/plugins/kibana_utils/public'; declare module '../../../../src/plugins/ui_actions/public' { export interface ActionContextMapping { @@ -64,23 +61,10 @@ export class EmbeddableEnhancedPlugin public setup(core: CoreSetup, plugins: SetupDependencies): SetupContract { this.setCustomEmbeddableFactoryProvider(plugins); - const startServices = createStartServicesGetter(core.getStartServices); const panelNotificationAction = new PanelNotificationsAction(); plugins.uiActionsEnhanced.registerAction(panelNotificationAction); plugins.uiActionsEnhanced.attachAction(PANEL_NOTIFICATION_TRIGGER, panelNotificationAction.id); - plugins.uiActionsEnhanced.registerDrilldown( - new UrlDrilldown({ - getGlobalScope: urlDrilldownGlobalScopeProvider({ core }), - navigateToUrl: (url: string) => - core.getStartServices().then(([{ application }]) => application.navigateToUrl(url)), - getSyntaxHelpDocsLink: () => - startServices().core.docLinks.links.dashboard.urlDrilldownTemplateSyntax, - getVariablesHelpDocsLink: () => - startServices().core.docLinks.links.dashboard.urlDrilldownVariables, - }) - ); - return {}; } diff --git a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.test.ts b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.test.ts index 83232bbce1ba7..cdd357f3560b8 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.test.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.test.ts @@ -437,8 +437,7 @@ describe('DynamicActionManager', () => { name: 'foo', config: {}, }; - - await expect(manager.createEvent(action, ['SELECT_RANGE_TRIGGER'])).rejects; + await expect(manager.createEvent(action, ['SELECT_RANGE_TRIGGER'])).rejects.toThrow(); }); }); }); @@ -704,4 +703,18 @@ describe('DynamicActionManager', () => { expect(basicAndGoldActions).toHaveLength(2); }); + + test("failing to revive/kill an action doesn't fail action manager", async () => { + const { manager, uiActions, storage } = setup([event1, event3, event2]); + + uiActions.registerActionFactory(actionFactoryDefinition1); + + await manager.start(); + + expect(uiActions.getTriggerActions('VALUE_CLICK_TRIGGER')).toHaveLength(2); + expect(await storage.list()).toEqual([event1, event3, event2]); + + await manager.stop(); + expect(uiActions.getTriggerActions('VALUE_CLICK_TRIGGER')).toHaveLength(0); + }); }); diff --git a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.ts b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.ts index 471b929fdbc06..b414296690c9e 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.ts @@ -34,7 +34,13 @@ export interface DynamicActionManagerParams { storage: ActionStorage; uiActions: Pick< StartContract, - 'registerAction' | 'attachAction' | 'unregisterAction' | 'detachAction' | 'getActionFactory' + | 'registerAction' + | 'attachAction' + | 'unregisterAction' + | 'detachAction' + | 'hasAction' + | 'getActionFactory' + | 'hasActionFactory' >; isCompatible: (context: C) => Promise; } @@ -73,8 +79,17 @@ export class DynamicActionManager { const actionId = this.generateActionId(eventId); + if (!uiActions.hasActionFactory(action.factoryId)) { + // eslint-disable-next-line no-console + console.warn( + `Action factory for action [action.factoryId = ${action.factoryId}] doesn't exist. Skipping action [action.name = ${action.name}] revive.` + ); + return; + } + const factory = uiActions.getActionFactory(event.action.factoryId); const actionDefinition: ActionDefinition = factory.create(action as SerializedAction); + uiActions.registerAction({ ...actionDefinition, id: actionId, @@ -100,6 +115,7 @@ export class DynamicActionManager { protected killAction({ eventId, triggers }: SerializedEvent) { const { uiActions } = this.params; const actionId = this.generateActionId(eventId); + if (!uiActions.hasAction(actionId)) return; for (const trigger of triggers) uiActions.detachAction(trigger as any, actionId); uiActions.unregisterAction(actionId); @@ -157,6 +173,7 @@ export class DynamicActionManager { 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) }); diff --git a/x-pack/plugins/ui_actions_enhanced/public/mocks.ts b/x-pack/plugins/ui_actions_enhanced/public/mocks.ts index 9eb0a06b6dbaf..1900f04b0c7d8 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/mocks.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/mocks.ts @@ -29,6 +29,7 @@ const createStartContract = (): Start => { ...uiActionsPluginMock.createStartContract(), getActionFactories: jest.fn(), getActionFactory: jest.fn(), + hasActionFactory: jest.fn(), FlyoutManageDrilldowns: jest.fn(), telemetry: jest.fn(), extract: jest.fn(), diff --git a/x-pack/plugins/ui_actions_enhanced/public/plugin.ts b/x-pack/plugins/ui_actions_enhanced/public/plugin.ts index b05c08c4c77d0..31236d2ea9779 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/plugin.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/plugin.ts @@ -61,7 +61,12 @@ export interface StartContract extends UiActionsStart, Pick< UiActionsServiceEnhancements, - 'getActionFactory' | 'getActionFactories' | 'telemetry' | 'extract' | 'inject' + | 'getActionFactory' + | 'hasActionFactory' + | 'getActionFactories' + | 'telemetry' + | 'extract' + | 'inject' > { FlyoutManageDrilldowns: ReturnType; } diff --git a/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts b/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts index 5e40d803962de..cbbd88e65e841 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts @@ -79,6 +79,10 @@ export class UiActionsServiceEnhancements return actionFactory; }; + public readonly hasActionFactory = (actionFactoryId: string): boolean => { + return this.actionFactories.has(actionFactoryId); + }; + /** * Returns an array of all action factories. */