diff --git a/examples/ui_action_examples/public/hello_world_action.tsx b/examples/ui_action_examples/public/hello_world_action.tsx index e07855a6f422c..f4c3bfeee6a6d 100644 --- a/examples/ui_action_examples/public/hello_world_action.tsx +++ b/examples/ui_action_examples/public/hello_world_action.tsx @@ -24,11 +24,16 @@ import { toMountPoint } from '../../../src/plugins/kibana_react/public'; export const HELLO_WORLD_ACTION_TYPE = 'HELLO_WORLD_ACTION_TYPE'; -export const createHelloWorldAction = (openModal: OverlayStart['openModal']) => - createAction<{}>({ +interface StartServices { + openModal: OverlayStart['openModal']; +} + +export const createHelloWorldAction = (getStartServices: () => Promise) => + createAction({ type: HELLO_WORLD_ACTION_TYPE, getDisplayName: () => 'Hello World!', execute: async () => { + const { openModal } = await getStartServices(); const overlay = openModal( toMountPoint( diff --git a/examples/ui_action_examples/public/plugin.ts b/examples/ui_action_examples/public/plugin.ts index bf62b4d973d4d..08b65714dbf66 100644 --- a/examples/ui_action_examples/public/plugin.ts +++ b/examples/ui_action_examples/public/plugin.ts @@ -17,30 +17,34 @@ * under the License. */ -import { Plugin, CoreSetup, CoreStart } from '../../../src/core/public'; -import { UiActionsSetup, UiActionsStart } from '../../../src/plugins/ui_actions/public'; -import { createHelloWorldAction, HELLO_WORLD_ACTION_TYPE } from './hello_world_action'; -import { helloWorldTrigger } from './hello_world_trigger'; +import { Plugin, CoreSetup } from '../../../src/core/public'; +import { UiActionsSetup } from '../../../src/plugins/ui_actions/public'; +import { createHelloWorldAction } from './hello_world_action'; +import { helloWorldTrigger, HELLO_WORLD_TRIGGER_ID } from './hello_world_trigger'; interface UiActionExamplesSetupDependencies { uiActions: UiActionsSetup; } -interface UiActionExamplesStartDependencies { - uiActions: UiActionsStart; +declare module '../../../src/plugins/ui_actions/public' { + export interface TriggerContextMapping { + [HELLO_WORLD_TRIGGER_ID]: undefined; + } } export class UiActionExamplesPlugin - implements - Plugin { + implements Plugin { public setup(core: CoreSetup, { uiActions }: UiActionExamplesSetupDependencies) { uiActions.registerTrigger(helloWorldTrigger); - uiActions.attachAction(helloWorldTrigger.id, HELLO_WORLD_ACTION_TYPE); - } - public start(coreStart: CoreStart, deps: UiActionExamplesStartDependencies) { - deps.uiActions.registerAction(createHelloWorldAction(coreStart.overlays.openModal)); + const helloWorldAction = createHelloWorldAction(async () => ({ + openModal: (await core.getStartServices())[0].overlays.openModal, + })); + + uiActions.registerAction(helloWorldAction); + uiActions.attachAction(helloWorldTrigger.id, helloWorldAction.id); } + public start() {} public stop() {} } diff --git a/examples/ui_actions_explorer/public/actions/actions.tsx b/examples/ui_actions_explorer/public/actions/actions.tsx index 821a1205861e6..2770b0e3bd5ff 100644 --- a/examples/ui_actions_explorer/public/actions/actions.tsx +++ b/examples/ui_actions_explorer/public/actions/actions.tsx @@ -34,16 +34,18 @@ export const EDIT_USER_ACTION = 'EDIT_USER_ACTION'; export const PHONE_USER_ACTION = 'PHONE_USER_ACTION'; export const SHOWCASE_PLUGGABILITY_ACTION = 'SHOWCASE_PLUGGABILITY_ACTION'; -export const showcasePluggability = createAction<{}>({ +export const showcasePluggability = createAction({ type: SHOWCASE_PLUGGABILITY_ACTION, getDisplayName: () => 'This is pluggable! Any plugin can inject their actions here.', - execute: async ({}) => alert("Isn't that cool?!"), + execute: async () => alert("Isn't that cool?!"), }); -export const makePhoneCallAction = createAction<{ phone: string }>({ +export type PhoneContext = string; + +export const makePhoneCallAction = createAction({ type: CALL_PHONE_NUMBER_ACTION, getDisplayName: () => 'Call phone number', - execute: async ({ phone }) => alert(`Pretend calling ${phone}...`), + execute: async phone => alert(`Pretend calling ${phone}...`), }); export const lookUpWeatherAction = createAction<{ country: string }>({ @@ -55,11 +57,13 @@ export const lookUpWeatherAction = createAction<{ country: string }>({ }, }); -export const viewInMapsAction = createAction<{ country: string }>({ +export type CountryContext = string; + +export const viewInMapsAction = createAction({ type: VIEW_IN_MAPS_ACTION, getIconType: () => 'popout', getDisplayName: () => 'View in maps', - execute: async ({ country }) => { + execute: async country => { window.open(`https://www.google.com/maps/place/${country}`, '_blank'); }, }); @@ -110,11 +114,13 @@ export const createEditUserAction = (getOpenModal: () => Promise void; +} + export const createPhoneUserAction = (getUiActionsApi: () => Promise) => - createAction<{ - user: User; - update: (user: User) => void; - }>({ + createAction({ type: PHONE_USER_ACTION, getDisplayName: () => 'Call phone number', isCompatible: async ({ user }) => user.phone !== undefined, @@ -126,6 +132,8 @@ export const createPhoneUserAction = (getUiActionsApi: () => Promise { uiActionsApi.executeTriggerActions(HELLO_WORLD_TRIGGER_ID, {})} + onClick={() => uiActionsApi.executeTriggerActions(HELLO_WORLD_TRIGGER_ID, undefined)} > Say hello world! diff --git a/examples/ui_actions_explorer/public/plugin.tsx b/examples/ui_actions_explorer/public/plugin.tsx index 953bfd3f52692..fecada71099e8 100644 --- a/examples/ui_actions_explorer/public/plugin.tsx +++ b/examples/ui_actions_explorer/public/plugin.tsx @@ -35,6 +35,9 @@ import { makePhoneCallAction, showcasePluggability, SHOWCASE_PLUGGABILITY_ACTION, + UserContext, + CountryContext, + PhoneContext, } from './actions/actions'; interface StartDeps { @@ -45,6 +48,14 @@ interface SetupDeps { uiActions: UiActionsSetup; } +declare module '../../../src/plugins/ui_actions/public' { + export interface TriggerContextMapping { + [USER_TRIGGER]: UserContext; + [COUNTRY_TRIGGER]: CountryContext; + [PHONE_TRIGGER]: PhoneContext; + } +} + export class UiActionsExplorerPlugin implements Plugin { public setup(core: CoreSetup<{ uiActions: UiActionsStart }>, deps: SetupDeps) { deps.uiActions.registerTrigger({ diff --git a/examples/ui_actions_explorer/public/trigger_context_example.tsx b/examples/ui_actions_explorer/public/trigger_context_example.tsx index 09e1de05bb313..00d974e938138 100644 --- a/examples/ui_actions_explorer/public/trigger_context_example.tsx +++ b/examples/ui_actions_explorer/public/trigger_context_example.tsx @@ -47,9 +47,7 @@ const createRowData = ( { - uiActionsApi.executeTriggerActions(COUNTRY_TRIGGER, { - country: user.countryOfResidence, - }); + uiActionsApi.executeTriggerActions(COUNTRY_TRIGGER, user.countryOfResidence); }} > {user.countryOfResidence} @@ -59,10 +57,9 @@ const createRowData = ( phone: ( { - uiActionsApi.executeTriggerActions(PHONE_TRIGGER, { - phone: user.phone, - }); + uiActionsApi.executeTriggerActions(PHONE_TRIGGER, user.phone!); }} > {user.phone} diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_embeddable.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_embeddable.ts index 2bb76386bb7ba..738a74d93449d 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_embeddable.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_embeddable.ts @@ -20,7 +20,7 @@ import _ from 'lodash'; import * as Rx from 'rxjs'; import { Subscription } from 'rxjs'; import { i18n } from '@kbn/i18n'; -import { ExecuteTriggerActions } from 'src/plugins/ui_actions/public'; +import { UiActionsStart } from 'src/plugins/ui_actions/public'; import { RequestAdapter, Adapters } from '../../../../../../../plugins/inspector/public'; import { esFilters, @@ -110,7 +110,7 @@ export class SearchEmbeddable extends Embeddable filterManager, }: SearchEmbeddableConfig, initialInput: SearchInput, - private readonly executeTriggerActions: ExecuteTriggerActions, + private readonly executeTriggerActions: UiActionsStart['executeTriggerActions'], parent?: Container ) { super( diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_embeddable_factory.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_embeddable_factory.ts index 15b3f2d4517ac..90f1549c9f369 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_embeddable_factory.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_embeddable_factory.ts @@ -19,7 +19,7 @@ import { auto } from 'angular'; import { i18n } from '@kbn/i18n'; -import { ExecuteTriggerActions } from 'src/plugins/ui_actions/public'; +import { UiActionsStart } from 'src/plugins/ui_actions/public'; import { getServices } from '../../kibana_services'; import { EmbeddableFactory, @@ -43,7 +43,7 @@ export class SearchEmbeddableFactory extends EmbeddableFactory< public isEditable: () => boolean; constructor( - private readonly executeTriggerActions: ExecuteTriggerActions, + private readonly executeTriggerActions: UiActionsStart['executeTriggerActions'], getInjector: () => Promise, isEditable: () => boolean ) { diff --git a/src/plugins/embeddable/public/bootstrap.ts b/src/plugins/embeddable/public/bootstrap.ts index 9989345df2796..93a15aab7a0dd 100644 --- a/src/plugins/embeddable/public/bootstrap.ts +++ b/src/plugins/embeddable/public/bootstrap.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { UiActionsSetup } from 'src/plugins/ui_actions/public'; +import { UiActionsSetup } from '../../ui_actions/public'; import { Filter } from '../../data/public'; import { applyFilterTrigger, @@ -27,6 +27,7 @@ import { valueClickTrigger, EmbeddableVisTriggerContext, IEmbeddable, + EmbeddableContext, APPLY_FILTER_TRIGGER, VALUE_CLICK_TRIGGER, SELECT_RANGE_TRIGGER, @@ -42,8 +43,8 @@ declare module '../../ui_actions/public' { embeddable: IEmbeddable; filters: Filter[]; }; - [CONTEXT_MENU_TRIGGER]: object; - [PANEL_BADGE_TRIGGER]: object; + [CONTEXT_MENU_TRIGGER]: EmbeddableContext; + [PANEL_BADGE_TRIGGER]: EmbeddableContext; } } diff --git a/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.tsx b/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.tsx index f604cb0c274ba..e15f1faaa397c 100644 --- a/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.tsx +++ b/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.tsx @@ -23,7 +23,7 @@ import React from 'react'; import { EuiLoadingChart } from '@elastic/eui'; import { Subscription } from 'rxjs'; import { CoreStart } from 'src/core/public'; -import { GetActionsCompatibleWithTrigger } from 'src/plugins/ui_actions/public'; +import { UiActionsService } from 'src/plugins/ui_actions/public'; import { Start as InspectorStartContract } from 'src/plugins/inspector/public'; import { ErrorEmbeddable, IEmbeddable } from '../embeddables'; @@ -35,7 +35,7 @@ export interface EmbeddableChildPanelProps { embeddableId: string; className?: string; container: IContainer; - getActions: GetActionsCompatibleWithTrigger; + getActions: UiActionsService['getTriggerCompatibleActions']; getEmbeddableFactory: GetEmbeddableFactory; getAllEmbeddableFactories: GetEmbeddableFactories; overlays: CoreStart['overlays']; 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 79d59317767d9..218660462b4ef 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: GetEmbeddableFactory = (id: string) => embeddableFactories.get(id); diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx index c5f4265ac3b0d..28474544f40b5 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx @@ -20,12 +20,12 @@ import { EuiContextMenuPanelDescriptor, EuiPanel, htmlIdGenerator } from '@elast import classNames from 'classnames'; import React from 'react'; import { Subscription } from 'rxjs'; -import { buildContextMenuForActions, GetActionsCompatibleWithTrigger, Action } from '../ui_actions'; +import { buildContextMenuForActions, UiActionsService, Action } from '../ui_actions'; import { CoreStart, OverlayStart } from '../../../../../core/public'; import { toMountPoint } from '../../../../kibana_react/public'; import { Start as InspectorStartContract } from '../inspector'; -import { CONTEXT_MENU_TRIGGER, PANEL_BADGE_TRIGGER } from '../triggers'; +import { CONTEXT_MENU_TRIGGER, PANEL_BADGE_TRIGGER, EmbeddableContext } from '../triggers'; import { IEmbeddable } from '../embeddables/i_embeddable'; import { ViewMode, GetEmbeddableFactory, GetEmbeddableFactories } from '../types'; @@ -39,7 +39,7 @@ import { CustomizePanelModal } from './panel_header/panel_actions/customize_titl interface Props { embeddable: IEmbeddable; - getActions: GetActionsCompatibleWithTrigger; + getActions: UiActionsService['getTriggerCompatibleActions']; getEmbeddableFactory: GetEmbeddableFactory; getAllEmbeddableFactories: GetEmbeddableFactories; overlays: CoreStart['overlays']; @@ -55,7 +55,7 @@ interface State { viewMode: ViewMode; hidePanelTitles: boolean; closeContextMenu: boolean; - badges: Action[]; + badges: Array>; } export class EmbeddablePanel extends React.Component { @@ -87,7 +87,7 @@ export class EmbeddablePanel extends React.Component { } private async refreshBadges() { - let badges: Action[] = await this.props.getActions(PANEL_BADGE_TRIGGER, { + let badges = await this.props.getActions(PANEL_BADGE_TRIGGER, { embeddable: this.props.embeddable, }); if (!this.mounted) return; @@ -231,7 +231,7 @@ export class EmbeddablePanel extends React.Component { // These actions are exposed on the context menu for every embeddable, they bypass the trigger // registry. - const extraActions: Array> = [ + const extraActions: Array> = [ new CustomizePanelTitleAction(createGetUserData(this.props.overlays)), new AddPanelAction( this.props.getEmbeddableFactory, @@ -245,11 +245,13 @@ export class EmbeddablePanel extends React.Component { new EditPanelAction(this.props.getEmbeddableFactory), ]; - const sorted = actions.concat(extraActions).sort((a: Action, b: Action) => { - const bOrder = b.order || 0; - const aOrder = a.order || 0; - return bOrder - aOrder; - }); + 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: sorted, 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 cc0733a08dd78..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 @@ -29,6 +29,7 @@ import React from 'react'; import { Action } from 'src/plugins/ui_actions/public'; import { PanelOptionsMenu } from './panel_options_menu'; import { IEmbeddable } from '../../embeddables'; +import { EmbeddableContext } from '../../triggers'; export interface PanelHeaderProps { title?: string; @@ -36,12 +37,12 @@ export interface PanelHeaderProps { hidePanelTitles: boolean; getActionContextMenuPanel: () => Promise; closeContextMenu: boolean; - badges: Action[]; + badges: Array>; embeddable: IEmbeddable; headerId?: string; } -function renderBadges(badges: Action[], embeddable: IEmbeddable) { +function renderBadges(badges: Array>, embeddable: IEmbeddable) { return badges.map(badge => ( ({ + return createAction({ type: EDIT_MODE_ACTION, getDisplayName: () => 'I only show up in edit mode', isCompatible: async context => context.embeddable.getInput().viewMode === ViewMode.EDIT, diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card.tsx b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card.tsx index a8c760f7b9497..01228c778754b 100644 --- a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card.tsx +++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card.tsx @@ -22,12 +22,19 @@ import { EuiCard, EuiFlexItem, EuiFlexGroup, EuiFormRow } from '@elastic/eui'; import { Subscription } from 'rxjs'; import { EuiButton } from '@elastic/eui'; import * as Rx from 'rxjs'; -import { ExecuteTriggerActions } from 'src/plugins/ui_actions/public'; +import { UiActionsStart } from '../../../../../../ui_actions/public'; import { ContactCardEmbeddable, CONTACT_USER_TRIGGER } from './contact_card_embeddable'; +import { EmbeddableContext } from '../../../triggers'; + +declare module '../../../../../../ui_actions/public' { + export interface TriggerContextMapping { + [CONTACT_USER_TRIGGER]: EmbeddableContext; + } +} interface Props { embeddable: ContactCardEmbeddable; - execTrigger: ExecuteTriggerActions; + execTrigger: UiActionsStart['executeTriggerActions']; } interface State { @@ -72,7 +79,6 @@ export class ContactCardEmbeddableComponent extends React.Component { this.props.execTrigger(CONTACT_USER_TRIGGER, { embeddable: this.props.embeddable, - triggerContext: {}, }); }; diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable.tsx b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable.tsx index 48f9cd2ce516d..078e21df0f0ce 100644 --- a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable.tsx +++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable.tsx @@ -19,7 +19,7 @@ import React from 'react'; import ReactDom from 'react-dom'; import { Subscription } from 'rxjs'; -import { ExecuteTriggerActions } from 'src/plugins/ui_actions/public'; +import { UiActionsStart } from 'src/plugins/ui_actions/public'; import { Container } from '../../../containers'; import { EmbeddableOutput, Embeddable, EmbeddableInput } from '../../../embeddables'; import { CONTACT_CARD_EMBEDDABLE } from './contact_card_embeddable_factory'; @@ -37,7 +37,7 @@ export interface ContactCardEmbeddableOutput extends EmbeddableOutput { } export interface ContactCardEmbeddableOptions { - execAction: ExecuteTriggerActions; + execAction: UiActionsStart['executeTriggerActions']; } function getFullName(input: ContactCardEmbeddableInput) { diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx index 838c8d7de8f12..7a9ba4fbbf6d6 100644 --- a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx +++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx @@ -19,7 +19,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { ExecuteTriggerActions } from 'src/plugins/ui_actions/public'; +import { UiActionsStart } from 'src/plugins/ui_actions/public'; import { CoreStart } from 'src/core/public'; import { toMountPoint } from '../../../../../../kibana_react/public'; @@ -36,7 +36,7 @@ export class ContactCardEmbeddableFactory extends EmbeddableFactory, - private readonly execTrigger: ExecuteTriggerActions, + private readonly execTrigger: UiActionsStart['executeTriggerActions'], private readonly overlays: CoreStart['overlays'] ) { super(options); diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/slow_contact_card_embeddable_factory.ts b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/slow_contact_card_embeddable_factory.ts index d16cd6dcd2187..b90e16c13fc62 100644 --- a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/slow_contact_card_embeddable_factory.ts +++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/slow_contact_card_embeddable_factory.ts @@ -17,13 +17,13 @@ * under the License. */ -import { ExecuteTriggerActions } from 'src/plugins/ui_actions/public'; +import { UiActionsStart } from 'src/plugins/ui_actions/public'; import { Container, EmbeddableFactory } from '../../..'; import { ContactCardEmbeddable, ContactCardEmbeddableInput } from './contact_card_embeddable'; import { CONTACT_CARD_EMBEDDABLE } from './contact_card_embeddable_factory'; interface SlowContactCardEmbeddableFactoryOptions { - execAction: ExecuteTriggerActions; + execAction: UiActionsStart['executeTriggerActions']; loadTickCount?: number; } diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/hello_world_container.tsx b/src/plugins/embeddable/public/lib/test_samples/embeddables/hello_world_container.tsx index 7eca9f64bf937..c5ba054bebb7a 100644 --- a/src/plugins/embeddable/public/lib/test_samples/embeddables/hello_world_container.tsx +++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/hello_world_container.tsx @@ -20,7 +20,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { I18nProvider } from '@kbn/i18n/react'; import { CoreStart } from 'src/core/public'; -import { GetActionsCompatibleWithTrigger } from 'src/plugins/ui_actions/public'; +import { UiActionsService } from 'src/plugins/ui_actions/public'; import { Start as InspectorStartContract } from 'src/plugins/inspector/public'; import { Container, ViewMode, ContainerInput } from '../..'; import { HelloWorldContainerComponent } from './hello_world_container_component'; @@ -45,7 +45,7 @@ interface HelloWorldContainerInput extends ContainerInput { } interface HelloWorldContainerOptions { - getActions: GetActionsCompatibleWithTrigger; + getActions: UiActionsService['getTriggerCompatibleActions']; getEmbeddableFactory: GetEmbeddableFactory; getAllEmbeddableFactories: GetEmbeddableFactories; overlays: CoreStart['overlays']; diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/hello_world_container_component.tsx b/src/plugins/embeddable/public/lib/test_samples/embeddables/hello_world_container_component.tsx index 413a0914bff65..e9acfd4539768 100644 --- a/src/plugins/embeddable/public/lib/test_samples/embeddables/hello_world_container_component.tsx +++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/hello_world_container_component.tsx @@ -21,14 +21,14 @@ import { Subscription } from 'rxjs'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { CoreStart } from 'src/core/public'; -import { GetActionsCompatibleWithTrigger } from 'src/plugins/ui_actions/public'; +import { UiActionsService } from 'src/plugins/ui_actions/public'; import { Start as InspectorStartContract } from 'src/plugins/inspector/public'; import { IContainer, PanelState, EmbeddableChildPanel } from '../..'; import { GetEmbeddableFactory, GetEmbeddableFactories } from '../../types'; interface Props { container: IContainer; - getActions: GetActionsCompatibleWithTrigger; + getActions: UiActionsService['getTriggerCompatibleActions']; getEmbeddableFactory: GetEmbeddableFactory; getAllEmbeddableFactories: GetEmbeddableFactories; overlays: CoreStart['overlays']; diff --git a/src/plugins/embeddable/public/lib/triggers/triggers.ts b/src/plugins/embeddable/public/lib/triggers/triggers.ts index 491d9e730eb75..a348e1ed79d8d 100644 --- a/src/plugins/embeddable/public/lib/triggers/triggers.ts +++ b/src/plugins/embeddable/public/lib/triggers/triggers.ts @@ -20,6 +20,10 @@ import { Trigger } from '../../../../ui_actions/public'; import { IEmbeddable } from '..'; +export interface EmbeddableContext { + embeddable: IEmbeddable; +} + export interface EmbeddableVisTriggerContext { embeddable: IEmbeddable; timeFieldName: string; diff --git a/src/plugins/ui_actions/public/actions/action.ts b/src/plugins/ui_actions/public/actions/action.ts index 22530f003f2cd..854e2c8c1cb09 100644 --- a/src/plugins/ui_actions/public/actions/action.ts +++ b/src/plugins/ui_actions/public/actions/action.ts @@ -19,7 +19,7 @@ import { UiComponent } from 'src/plugins/kibana_utils/common'; -export interface Action { +export interface Action { /** * Determined the order when there is more than one action matched to a trigger. * Higher numbers are displayed first. @@ -33,33 +33,33 @@ export interface Action { /** * Optional EUI icon type that can be displayed along with the title. */ - getIconType(context: ActionContext): string | undefined; + getIconType(context: Context): string | undefined; /** * Returns a title to be displayed to the user. * @param context */ - getDisplayName(context: ActionContext): string; + getDisplayName(context: Context): string; /** * `UiComponent` to render when displaying this action as a context menu item. * If not provided, `getDisplayName` will be used instead. */ - MenuItem?: UiComponent<{ context: ActionContext }>; + MenuItem?: UiComponent<{ context: Context }>; /** * Returns a promise that resolves to true if this action is compatible given the context, * otherwise resolves to false. */ - isCompatible(context: ActionContext): Promise; + isCompatible(context: Context): Promise; /** * If this returns something truthy, this is used in addition to the `execute` method when clicked. */ - getHref?(context: ActionContext): string | undefined; + getHref?(context: Context): string | undefined; /** * Executes the action. */ - execute(context: ActionContext): Promise; + execute(context: Context): Promise; } diff --git a/src/plugins/ui_actions/public/actions/create_action.ts b/src/plugins/ui_actions/public/actions/create_action.ts index 0cec076745334..4077cf1081021 100644 --- a/src/plugins/ui_actions/public/actions/create_action.ts +++ b/src/plugins/ui_actions/public/actions/create_action.ts @@ -19,11 +19,9 @@ import { Action } from './action'; -export function createAction( - action: { type: string; execute: Action['execute'] } & Partial< - Action - > -): Action { +export function createAction( + action: { type: string; execute: Action['execute'] } & Partial> +): Action { return { getIconType: () => undefined, order: 0, diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts index 1ce48d5460b2e..eb69aefdbb50e 100644 --- a/src/plugins/ui_actions/public/index.ts +++ b/src/plugins/ui_actions/public/index.ts @@ -19,7 +19,6 @@ import { PluginInitializerContext } from '../../../core/public'; import { UiActionsPlugin } from './plugin'; -import { UiActionsService } from './service'; export function plugin(initializerContext: PluginInitializerContext) { return new UiActionsPlugin(initializerContext); @@ -30,20 +29,4 @@ export { UiActionsServiceParams, UiActionsService } from './service'; export { Action, createAction, IncompatibleActionError } from './actions'; export { buildContextMenuForActions } from './context_menu'; export { Trigger, TriggerContext } from './triggers'; -export { TriggerContextMapping } from './types'; - -/** - * @deprecated - * - * Use `UiActionsStart['getTriggerCompatibleActions']` or - * `UiActionsService['getTriggerCompatibleActions']` instead. - */ -export type GetActionsCompatibleWithTrigger = UiActionsService['getTriggerCompatibleActions']; - -/** - * @deprecated - * - * Use `UiActionsStart['executeTriggerActions']` or - * `UiActionsService['executeTriggerActions']` instead. - */ -export type ExecuteTriggerActions = UiActionsService['executeTriggerActions']; +export { TriggerContextMapping, TriggerId } from './types'; diff --git a/src/plugins/ui_actions/public/mocks.ts b/src/plugins/ui_actions/public/mocks.ts index d2ba901f1040d..948450495384a 100644 --- a/src/plugins/ui_actions/public/mocks.ts +++ b/src/plugins/ui_actions/public/mocks.ts @@ -21,6 +21,7 @@ import { CoreSetup, CoreStart } from 'src/core/public'; import { UiActionsSetup, UiActionsStart } from '.'; import { plugin as pluginInitializer } from '.'; import { coreMock } from '../../../core/public/mocks'; +import { TriggerId } from './types'; export type Setup = jest.Mocked; export type Start = jest.Mocked; @@ -43,7 +44,7 @@ const createStartContract = (): Start => { detachAction: jest.fn(), executeTriggerActions: jest.fn(), getTrigger: jest.fn(), - getTriggerActions: jest.fn((id: string) => []), + getTriggerActions: jest.fn((id: TriggerId) => []), getTriggerCompatibleActions: jest.fn(), clear: jest.fn(), fork: jest.fn(), 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 8963ba4ddb005..c52b975358610 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 @@ -20,9 +20,16 @@ import { UiActionsService } from './ui_actions_service'; import { Action } from '../actions'; import { createRestrictedAction, createHelloWorldAction } from '../tests/test_samples'; -import { ActionRegistry, TriggerRegistry } from '../types'; +import { ActionRegistry, TriggerRegistry, TriggerId } from '../types'; import { Trigger } from '../triggers'; +// I tried redeclaring the module in here to extend the `TriggerContextMapping` but +// that seems to overwrite all other plugins extending it, I suspect because it's inside +// the main plugin. +const FOO_TRIGGER: TriggerId = 'FOO_TRIGGER' as TriggerId; +const BAR_TRIGGER: TriggerId = 'BAR_TRIGGER' as TriggerId; +const MY_TRIGGER: TriggerId = 'MY_TRIGGER' as TriggerId; + const testAction1: Action = { id: 'action1', order: 1, @@ -52,7 +59,7 @@ describe('UiActionsService', () => { test('can register a trigger', () => { const service = new UiActionsService(); service.registerTrigger({ - id: 'test', + id: BAR_TRIGGER, }); }); }); @@ -62,15 +69,15 @@ describe('UiActionsService', () => { const service = new UiActionsService(); service.registerTrigger({ description: 'foo', - id: 'bar', + id: BAR_TRIGGER, title: 'baz', }); - const trigger = service.getTrigger('bar'); + const trigger = service.getTrigger(BAR_TRIGGER); expect(trigger).toMatchObject({ description: 'foo', - id: 'bar', + id: BAR_TRIGGER, title: 'baz', }); }); @@ -78,8 +85,8 @@ describe('UiActionsService', () => { test('throws if trigger does not exist', () => { const service = new UiActionsService(); - expect(() => service.getTrigger('foo')).toThrowError( - 'Trigger [triggerId = foo] does not exist.' + expect(() => service.getTrigger(FOO_TRIGGER)).toThrowError( + 'Trigger [triggerId = FOO_TRIGGER] does not exist.' ); }); }); @@ -125,22 +132,22 @@ describe('UiActionsService', () => { service.registerAction(action2); service.registerTrigger({ description: 'foo', - id: 'trigger', + id: FOO_TRIGGER, title: 'baz', }); - const list0 = service.getTriggerActions('trigger'); + const list0 = service.getTriggerActions(FOO_TRIGGER); expect(list0).toHaveLength(0); - service.attachAction('trigger', 'action1'); - const list1 = service.getTriggerActions('trigger'); + service.attachAction(FOO_TRIGGER, 'action1'); + const list1 = service.getTriggerActions(FOO_TRIGGER); expect(list1).toHaveLength(1); expect(list1).toEqual([action1]); - service.attachAction('trigger', 'action2'); - const list2 = service.getTriggerActions('trigger'); + service.attachAction(FOO_TRIGGER, 'action2'); + const list2 = service.getTriggerActions(FOO_TRIGGER); expect(list2).toHaveLength(2); expect(!!list2.find(({ id }: any) => id === 'action1')).toBe(true); @@ -168,13 +175,15 @@ describe('UiActionsService', () => { service.registerAction(helloWorldAction); const testTrigger: Trigger = { - id: 'MY-TRIGGER', + id: MY_TRIGGER, title: 'My trigger', }; service.registerTrigger(testTrigger); - service.attachAction('MY-TRIGGER', helloWorldAction.id); + service.attachAction(MY_TRIGGER, helloWorldAction.id); - const compatibleActions = await service.getTriggerCompatibleActions('MY-TRIGGER', {}); + const compatibleActions = await service.getTriggerCompatibleActions(MY_TRIGGER, { + hi: 'there', + }); expect(compatibleActions.length).toBe(1); expect(compatibleActions[0].id).toBe(helloWorldAction.id); @@ -189,7 +198,7 @@ describe('UiActionsService', () => { service.registerAction(restrictedAction); const testTrigger: Trigger = { - id: 'MY-TRIGGER', + id: MY_TRIGGER, title: 'My trigger', }; @@ -212,15 +221,16 @@ describe('UiActionsService', () => { test(`throws an error with an invalid trigger ID`, async () => { const service = new UiActionsService(); - await expect(service.getTriggerCompatibleActions('I do not exist', {})).rejects.toMatchObject( - new Error('Trigger [triggerId = I do not exist] does not exist.') - ); + // Without the cast "as TriggerId" typescript will happily throw an error! + await expect( + service.getTriggerCompatibleActions('I do not exist' as TriggerId, {}) + ).rejects.toMatchObject(new Error('Trigger [triggerId = I do not exist] does not exist.')); }); test('returns empty list if trigger not attached to any action', async () => { const service = new UiActionsService(); const testTrigger: Trigger = { - id: '123', + id: '123' as TriggerId, title: '123', }; service.registerTrigger(testTrigger); @@ -243,15 +253,15 @@ describe('UiActionsService', () => { test('triggers registered in original service are available in original an forked services', () => { const service1 = new UiActionsService(); service1.registerTrigger({ - id: 'foo', + id: FOO_TRIGGER, }); const service2 = service1.fork(); - const trigger1 = service1.getTrigger('foo'); - const trigger2 = service2.getTrigger('foo'); + const trigger1 = service1.getTrigger(FOO_TRIGGER); + const trigger2 = service2.getTrigger(FOO_TRIGGER); - expect(trigger1.id).toBe('foo'); - expect(trigger2.id).toBe('foo'); + expect(trigger1.id).toBe(FOO_TRIGGER); + expect(trigger2.id).toBe(FOO_TRIGGER); }); test('triggers registered in forked service are not available in original service', () => { @@ -259,30 +269,30 @@ describe('UiActionsService', () => { const service2 = service1.fork(); service2.registerTrigger({ - id: 'foo', + id: FOO_TRIGGER, }); - expect(() => service1.getTrigger('foo')).toThrowErrorMatchingInlineSnapshot( - `"Trigger [triggerId = foo] does not exist."` + expect(() => service1.getTrigger(FOO_TRIGGER)).toThrowErrorMatchingInlineSnapshot( + `"Trigger [triggerId = FOO_TRIGGER] does not exist."` ); - const trigger2 = service2.getTrigger('foo'); - expect(trigger2.id).toBe('foo'); + const trigger2 = service2.getTrigger(FOO_TRIGGER); + expect(trigger2.id).toBe(FOO_TRIGGER); }); test('forked service preserves trigger-to-actions mapping', () => { const service1 = new UiActionsService(); service1.registerTrigger({ - id: 'foo', + id: FOO_TRIGGER, }); service1.registerAction(testAction1); - service1.attachAction('foo', testAction1.id); + service1.attachAction(FOO_TRIGGER, testAction1.id); const service2 = service1.fork(); - const actions1 = service1.getTriggerActions('foo'); - const actions2 = service2.getTriggerActions('foo'); + const actions1 = service1.getTriggerActions(FOO_TRIGGER); + const actions2 = service2.getTriggerActions(FOO_TRIGGER); expect(actions1).toHaveLength(1); expect(actions2).toHaveLength(1); @@ -294,42 +304,42 @@ describe('UiActionsService', () => { const service1 = new UiActionsService(); service1.registerTrigger({ - id: 'foo', + id: FOO_TRIGGER, }); service1.registerAction(testAction1); service1.registerAction(testAction2); - service1.attachAction('foo', testAction1.id); + service1.attachAction(FOO_TRIGGER, testAction1.id); const service2 = service1.fork(); - expect(service1.getTriggerActions('foo')).toHaveLength(1); - expect(service2.getTriggerActions('foo')).toHaveLength(1); + expect(service1.getTriggerActions(FOO_TRIGGER)).toHaveLength(1); + expect(service2.getTriggerActions(FOO_TRIGGER)).toHaveLength(1); - service2.attachAction('foo', testAction2.id); + service2.attachAction(FOO_TRIGGER, testAction2.id); - expect(service1.getTriggerActions('foo')).toHaveLength(1); - expect(service2.getTriggerActions('foo')).toHaveLength(2); + expect(service1.getTriggerActions(FOO_TRIGGER)).toHaveLength(1); + expect(service2.getTriggerActions(FOO_TRIGGER)).toHaveLength(2); }); test('new attachments in original service do not appear in fork', () => { const service1 = new UiActionsService(); service1.registerTrigger({ - id: 'foo', + id: FOO_TRIGGER, }); service1.registerAction(testAction1); service1.registerAction(testAction2); - service1.attachAction('foo', testAction1.id); + service1.attachAction(FOO_TRIGGER, testAction1.id); const service2 = service1.fork(); - expect(service1.getTriggerActions('foo')).toHaveLength(1); - expect(service2.getTriggerActions('foo')).toHaveLength(1); + expect(service1.getTriggerActions(FOO_TRIGGER)).toHaveLength(1); + expect(service2.getTriggerActions(FOO_TRIGGER)).toHaveLength(1); - service1.attachAction('foo', testAction2.id); + service1.attachAction(FOO_TRIGGER, testAction2.id); - expect(service1.getTriggerActions('foo')).toHaveLength(2); - expect(service2.getTriggerActions('foo')).toHaveLength(1); + expect(service1.getTriggerActions(FOO_TRIGGER)).toHaveLength(2); + expect(service2.getTriggerActions(FOO_TRIGGER)).toHaveLength(1); }); }); @@ -342,14 +352,14 @@ describe('UiActionsService', () => { service.registerTrigger({ description: 'foo', - id: 'bar', + id: BAR_TRIGGER, title: 'baz', }); - const triggerContract = service.getTrigger('bar'); + const triggerContract = service.getTrigger(BAR_TRIGGER); expect(triggerContract).toMatchObject({ description: 'foo', - id: 'bar', + id: BAR_TRIGGER, title: 'baz', }); }); @@ -373,7 +383,7 @@ describe('UiActionsService', () => { const service = new UiActionsService(); const trigger: Trigger = { - id: 'MY-TRIGGER', + id: MY_TRIGGER, }; const action = { id: HELLO_WORLD_ACTION_ID, @@ -382,7 +392,7 @@ describe('UiActionsService', () => { service.registerTrigger(trigger); service.registerAction(action); - service.attachAction('MY-TRIGGER', HELLO_WORLD_ACTION_ID); + service.attachAction(MY_TRIGGER, HELLO_WORLD_ACTION_ID); const actions = service.getTriggerActions(trigger.id); @@ -394,7 +404,7 @@ describe('UiActionsService', () => { const service = new UiActionsService(); const trigger: Trigger = { - id: 'MY-TRIGGER', + id: MY_TRIGGER, }; const action = { id: HELLO_WORLD_ACTION_ID, @@ -419,7 +429,9 @@ describe('UiActionsService', () => { } as any; service.registerAction(action); - expect(() => service.detachAction('i do not exist', HELLO_WORLD_ACTION_ID)).toThrowError( + expect(() => + service.detachAction('i do not exist' as TriggerId, HELLO_WORLD_ACTION_ID) + ).toThrowError( 'No trigger [triggerId = i do not exist] exists, for detaching action [actionId = HELLO_WORLD_ACTION_ID].' ); }); @@ -433,7 +445,9 @@ describe('UiActionsService', () => { } as any; service.registerAction(action); - expect(() => service.attachAction('i do not exist', HELLO_WORLD_ACTION_ID)).toThrowError( + expect(() => + service.attachAction('i do not exist' as TriggerId, HELLO_WORLD_ACTION_ID) + ).toThrowError( 'No trigger [triggerId = i do not exist] exists, for attaching action [actionId = HELLO_WORLD_ACTION_ID].' ); }); 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 ae409830bbb6e..66f038f05a4ac 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_service.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_service.ts @@ -17,7 +17,13 @@ * under the License. */ -import { TriggerRegistry, ActionRegistry, TriggerToActionsRegistry, TriggerId } from '../types'; +import { + TriggerRegistry, + ActionRegistry, + TriggerToActionsRegistry, + TriggerId, + TriggerContextMapping, +} from '../types'; import { Action } from '../actions'; import { Trigger, TriggerContext } from '../triggers/trigger'; import { TriggerInternal } from '../triggers/trigger_internal'; @@ -60,7 +66,7 @@ export class UiActionsService { }; public readonly getTrigger = (triggerId: T): TriggerContract => { - const trigger = this.triggers.get(triggerId as string); + const trigger = this.triggers.get(triggerId); if (!trigger) { throw new Error(`Trigger [triggerId = ${triggerId}] does not exist.`); @@ -69,7 +75,7 @@ export class UiActionsService { return trigger.contract; }; - public readonly registerAction = (action: Action) => { + public readonly registerAction = (action: Action) => { if (this.actions.has(action.id)) { throw new Error(`Action [action.id = ${action.id}] already registered.`); } @@ -77,7 +83,10 @@ export class UiActionsService { this.actions.set(action.id, action); }; - public readonly attachAction = (triggerId: string, actionId: string): void => { + // TODO: make this + // (triggerId: T, action: Action): \ + // to get type checks here! + public readonly attachAction = (triggerId: T, actionId: string): void => { const trigger = this.triggers.get(triggerId); if (!trigger) { @@ -93,7 +102,7 @@ export class UiActionsService { } }; - public readonly detachAction = (triggerId: string, actionId: string) => { + public readonly detachAction = (triggerId: TriggerId, actionId: string) => { const trigger = this.triggers.get(triggerId); if (!trigger) { @@ -110,23 +119,30 @@ export class UiActionsService { ); }; - public readonly getTriggerActions = (triggerId: string) => { + public readonly getTriggerActions = ( + triggerId: T + ): Array> => { // This line checks if trigger exists, otherwise throws. this.getTrigger!(triggerId); const actionIds = this.triggerToActions.get(triggerId); - const actions = actionIds! - .map(actionId => this.actions.get(actionId)) - .filter(Boolean) as Action[]; - return actions; + const actions = actionIds!.map(actionId => this.actions.get(actionId)).filter(Boolean) as Array< + Action + >; + + return actions as Array>>; }; - public readonly getTriggerCompatibleActions = async (triggerId: string, context: C) => { + public readonly getTriggerCompatibleActions = async ( + triggerId: T, + context: TriggerContextMapping[T] + ): Promise>> => { const actions = this.getTriggerActions!(triggerId); const isCompatibles = await Promise.all(actions.map(action => action.isCompatible(context))); - return actions.reduce( - (acc, action, i) => (isCompatibles[i] ? [...acc, action] : acc), + return actions.reduce( + (acc: Array>, action, i) => + isCompatibles[i] ? [...acc, action] : acc, [] ); }; 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 f8c196a623499..450bfbfc6c959 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 @@ -21,6 +21,7 @@ import { Action, createAction } from '../actions'; import { openContextMenu } from '../context_menu'; import { uiActionsPluginMock } from '../mocks'; import { Trigger } from '../triggers'; +import { TriggerId } from '../types'; jest.mock('../context_menu'); @@ -55,7 +56,7 @@ beforeEach(reset); test('executes a single action mapped to a trigger', async () => { const { setup, doStart } = uiActions; const trigger: Trigger = { - id: 'MY-TRIGGER', + id: 'MY-TRIGGER' as TriggerId, title: 'My trigger', }; const action = createTestAction('test1', () => true); @@ -66,7 +67,7 @@ test('executes a single action mapped to a trigger', async () => { const context = {}; const start = doStart(); - await start.executeTriggerActions('MY-TRIGGER', context); + await start.executeTriggerActions('MY-TRIGGER' as TriggerId, context); expect(executeFn).toBeCalledTimes(1); expect(executeFn).toBeCalledWith(context); @@ -75,7 +76,7 @@ test('executes a single action mapped to a trigger', async () => { test('throws an error if there are no compatible actions to execute', async () => { const { setup, doStart } = uiActions; const trigger: Trigger = { - id: 'MY-TRIGGER', + id: 'MY-TRIGGER' as TriggerId, title: 'My trigger', }; @@ -84,7 +85,9 @@ test('throws an error if there are no compatible actions to execute', async () = const context = {}; const start = doStart(); - await expect(start.executeTriggerActions('MY-TRIGGER', context)).rejects.toMatchObject( + await expect( + start.executeTriggerActions('MY-TRIGGER' as TriggerId, context) + ).rejects.toMatchObject( new Error('No compatible actions found to execute for trigger [triggerId = MY-TRIGGER].') ); }); @@ -92,7 +95,7 @@ test('throws an error if there are no compatible actions to execute', async () = test('does not execute an incompatible action', async () => { const { setup, doStart } = uiActions; const trigger: Trigger = { - id: 'MY-TRIGGER', + id: 'MY-TRIGGER' as TriggerId, title: 'My trigger', }; const action = createTestAction<{ name: string }>('test1', ({ name }) => name === 'executeme'); @@ -105,7 +108,7 @@ test('does not execute an incompatible action', async () => { const context = { name: 'executeme', }; - await start.executeTriggerActions('MY-TRIGGER', context); + await start.executeTriggerActions('MY-TRIGGER' as TriggerId, context); expect(executeFn).toBeCalledTimes(1); }); @@ -113,7 +116,7 @@ test('does not execute an incompatible action', async () => { test('shows a context menu when more than one action is mapped to a trigger', async () => { const { setup, doStart } = uiActions; const trigger: Trigger = { - id: 'MY-TRIGGER', + id: 'MY-TRIGGER' as TriggerId, title: 'My trigger', }; const action1 = createTestAction('test1', () => true); @@ -129,7 +132,7 @@ test('shows a context menu when more than one action is mapped to a trigger', as const start = doStart(); const context = {}; - await start.executeTriggerActions('MY-TRIGGER', context); + await start.executeTriggerActions('MY-TRIGGER' as TriggerId, context); expect(executeFn).toBeCalledTimes(0); expect(openContextMenu).toHaveBeenCalledTimes(1); @@ -138,7 +141,7 @@ test('shows a context menu when more than one action is mapped to a trigger', as test('passes whole action context to isCompatible()', async () => { const { setup, doStart } = uiActions; const trigger = { - id: 'MY-TRIGGER', + id: 'MY-TRIGGER' as TriggerId, title: 'My trigger', }; const action = createTestAction<{ foo: string }>('test', ({ foo }) => { @@ -153,5 +156,5 @@ test('passes whole action context to isCompatible()', async () => { const start = doStart(); const context = { foo: 'bar' }; - await start.executeTriggerActions('MY-TRIGGER', context); + await start.executeTriggerActions('MY-TRIGGER' as TriggerId, context); }); 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 e91acd4c7151b..ae335de4b3deb 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 @@ -19,6 +19,7 @@ import { Action } from '../actions'; import { uiActionsPluginMock } from '../mocks'; +import { TriggerId } from '../types'; const action1: Action = { id: 'action1', @@ -37,23 +38,23 @@ test('returns actions set on trigger', () => { setup.registerAction(action2); setup.registerTrigger({ description: 'foo', - id: 'trigger', + id: 'trigger' as TriggerId, title: 'baz', }); const start = doStart(); - const list0 = start.getTriggerActions('trigger'); + const list0 = start.getTriggerActions('trigger' as TriggerId); expect(list0).toHaveLength(0); - setup.attachAction('trigger', 'action1'); - const list1 = start.getTriggerActions('trigger'); + setup.attachAction('trigger' as TriggerId, 'action1'); + const list1 = start.getTriggerActions('trigger' as TriggerId); expect(list1).toHaveLength(1); expect(list1).toEqual([action1]); - setup.attachAction('trigger', 'action2'); - const list2 = start.getTriggerActions('trigger'); + setup.attachAction('trigger' as TriggerId, 'action2'); + const list2 = start.getTriggerActions('trigger' as TriggerId); expect(list2).toHaveLength(2); expect(!!list2.find(({ id }: any) => id === 'action1')).toBe(true); 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 a966003973aba..dfb55e42b9443 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 @@ -22,6 +22,7 @@ import { uiActionsPluginMock } from '../mocks'; import { createRestrictedAction, createHelloWorldAction } from '../tests/test_samples'; import { Action } from '../actions'; import { Trigger } from '../triggers'; +import { TriggerId } from '../types'; let action: Action<{ name: string }>; let uiActions: ReturnType; @@ -31,10 +32,10 @@ beforeEach(() => { uiActions.setup.registerAction(action); uiActions.setup.registerTrigger({ - id: 'trigger', + id: 'trigger' as TriggerId, title: 'trigger', }); - uiActions.setup.attachAction('trigger', action.id); + uiActions.setup.attachAction('trigger' as TriggerId, action.id); }); test('can register action', async () => { @@ -51,14 +52,14 @@ test('getTriggerCompatibleActions returns attached actions', async () => { setup.registerAction(helloWorldAction); const testTrigger: Trigger = { - id: 'MY-TRIGGER', + id: 'MY-TRIGGER' as TriggerId, title: 'My trigger', }; setup.registerTrigger(testTrigger); - setup.attachAction('MY-TRIGGER', helloWorldAction.id); + setup.attachAction('MY-TRIGGER' as TriggerId, helloWorldAction.id); const start = doStart(); - const actions = await start.getTriggerCompatibleActions('MY-TRIGGER', {}); + const actions = await start.getTriggerCompatibleActions('MY-TRIGGER' as TriggerId, {}); expect(actions.length).toBe(1); expect(actions[0].id).toBe(helloWorldAction.id); @@ -73,7 +74,7 @@ test('filters out actions not applicable based on the context', async () => { setup.registerAction(restrictedAction); const testTrigger: Trigger = { - id: 'MY-TRIGGER', + id: 'MY-TRIGGER' as TriggerId, title: 'My trigger', }; @@ -94,15 +95,15 @@ test(`throws an error with an invalid trigger ID`, async () => { const { doStart } = uiActions; const start = doStart(); - await expect(start.getTriggerCompatibleActions('I do not exist', {})).rejects.toMatchObject( - new Error('Trigger [triggerId = I do not exist] does not exist.') - ); + await expect( + start.getTriggerCompatibleActions('I do not exist' as TriggerId, {}) + ).rejects.toMatchObject(new Error('Trigger [triggerId = I do not exist] does not exist.')); }); test(`with a trigger mapping that maps to an non-existing action returns empty list`, async () => { const { setup, doStart } = uiActions; const testTrigger: Trigger = { - id: '123', + id: '123' as TriggerId, title: '123', }; setup.registerTrigger(testTrigger); diff --git a/src/plugins/ui_actions/public/triggers/trigger_contract.ts b/src/plugins/ui_actions/public/triggers/trigger_contract.ts index 853b83dccabcc..ba1c5a693f937 100644 --- a/src/plugins/ui_actions/public/triggers/trigger_contract.ts +++ b/src/plugins/ui_actions/public/triggers/trigger_contract.ts @@ -17,9 +17,8 @@ * under the License. */ -import { TriggerContext } from './trigger'; import { TriggerInternal } from './trigger_internal'; -import { TriggerId } from '../types'; +import { TriggerId, TriggerContextMapping } from '../types'; /** * This is a public representation of a trigger that is provided to other plugins. @@ -50,7 +49,7 @@ export class TriggerContract { /** * Use this method to execute action attached to this trigger. */ - public readonly exec = async (context: TriggerContext) => { + public readonly exec = async (context: TriggerContextMapping[T]) => { await this.internal.execute(context); }; } diff --git a/src/plugins/ui_actions/public/triggers/trigger_internal.ts b/src/plugins/ui_actions/public/triggers/trigger_internal.ts index efcdc72ecad57..5b670df354f78 100644 --- a/src/plugins/ui_actions/public/triggers/trigger_internal.ts +++ b/src/plugins/ui_actions/public/triggers/trigger_internal.ts @@ -17,12 +17,12 @@ * under the License. */ -import { TriggerContext, Trigger } from './trigger'; +import { Trigger } from './trigger'; import { TriggerContract } from './trigger_contract'; import { UiActionsService } from '../service'; import { Action } from '../actions'; import { buildContextMenuForActions, openContextMenu } from '../context_menu'; -import { TriggerId } from '../types'; +import { TriggerId, TriggerContextMapping } from '../types'; /** * Internal representation of a trigger kept for consumption only internally @@ -33,7 +33,7 @@ export class TriggerInternal { constructor(public readonly service: UiActionsService, public readonly trigger: Trigger) {} - public async execute(context: TriggerContext) { + public async execute(context: TriggerContextMapping[T]) { const triggerId = this.trigger.id; const actions = await this.service.getTriggerCompatibleActions!(triggerId, context); @@ -51,7 +51,10 @@ export class TriggerInternal { await this.executeMultipleActions(actions, context); } - private async executeSingleAction(action: Action>, context: TriggerContext) { + private async executeSingleAction( + action: Action, + context: TriggerContextMapping[T] + ) { const href = action.getHref && action.getHref(context); if (href) { @@ -63,8 +66,8 @@ export class TriggerInternal { } private async executeMultipleActions( - actions: Array>>, - context: TriggerContext + actions: Array>, + context: TriggerContextMapping[T] ) { const panel = await buildContextMenuForActions({ actions, diff --git a/src/plugins/ui_actions/public/types.ts b/src/plugins/ui_actions/public/types.ts index 8daa893eb4347..a762e503c532c 100644 --- a/src/plugins/ui_actions/public/types.ts +++ b/src/plugins/ui_actions/public/types.ts @@ -20,12 +20,17 @@ import { Action } from './actions/action'; import { TriggerInternal } from './triggers/trigger_internal'; -export type TriggerRegistry = Map>; -export type ActionRegistry = Map; -export type TriggerToActionsRegistry = Map; +export type TriggerRegistry = Map>; +export type ActionRegistry = Map>; +export type TriggerToActionsRegistry = Map; -export type TriggerId = string; +const DEFAULT_TRIGGER = ''; + +export type TriggerId = typeof DEFAULT_TRIGGER | keyof TriggerContextMapping; + +export type TriggerContext = BaseContext; +export type BaseContext = object | undefined | string | number; export interface TriggerContextMapping { - [key: string]: object; + [DEFAULT_TRIGGER]: TriggerContext; } diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/app.tsx b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/app.tsx index dde58eaf44f88..144954800c91f 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/app.tsx +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/app.tsx @@ -23,12 +23,12 @@ import { GetEmbeddableFactory, GetEmbeddableFactories, } from 'src/legacy/core_plugins/embeddable_api/public/np_ready/public'; -import { GetActionsCompatibleWithTrigger } from '../../../../../../../../src/plugins/ui_actions/public'; +import { UiActionsService } from '../../../../../../../../src/plugins/ui_actions/public'; import { DashboardContainerExample } from './dashboard_container_example'; import { Start as InspectorStartContract } from '../../../../../../../../src/plugins/inspector/public'; export interface AppProps { - getActions: GetActionsCompatibleWithTrigger; + getActions: UiActionsService['getTriggerCompatibleActions']; getEmbeddableFactory: GetEmbeddableFactory; getAllEmbeddableFactories: GetEmbeddableFactories; overlays: CoreStart['overlays']; diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_container_example.tsx b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_container_example.tsx index 0237df63351cf..df0c00fb48b2e 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_container_example.tsx +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_container_example.tsx @@ -35,10 +35,10 @@ import { import { CoreStart } from '../../../../../../../../src/core/public'; import { dashboardInput } from './dashboard_input'; import { Start as InspectorStartContract } from '../../../../../../../../src/plugins/inspector/public'; -import { GetActionsCompatibleWithTrigger } from '../../../../../../../../src/plugins/ui_actions/public'; +import { UiActionsService } from '../../../../../../../../src/plugins/ui_actions/public'; interface Props { - getActions: GetActionsCompatibleWithTrigger; + getActions: UiActionsService['getTriggerCompatibleActions']; getEmbeddableFactory: GetEmbeddableFactory; getAllEmbeddableFactories: GetEmbeddableFactories; overlays: CoreStart['overlays'];