Improve action and trigger types (#58657) (#58909)

* Improve types so emitting the wrong context shape complains, as does using a trigger id that has not been added to the trigger context mapping.

* remove unneccessary code
This commit is contained in:
Stacey Gammon 2020-02-28 16:40:10 -05:00 committed by GitHub
parent 1dfb5386e1
commit f0eaad05d7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 279 additions and 220 deletions

View file

@ -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<StartServices>) =>
createAction({
type: HELLO_WORLD_ACTION_TYPE,
getDisplayName: () => 'Hello World!',
execute: async () => {
const { openModal } = await getStartServices();
const overlay = openModal(
toMountPoint(
<EuiModalBody>

View file

@ -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<void, void, UiActionExamplesSetupDependencies, UiActionExamplesStartDependencies> {
implements Plugin<void, void, UiActionExamplesSetupDependencies> {
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() {}
}

View file

@ -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<PhoneContext>({
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<CountryContext>({
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<OverlayStart['o
},
});
export interface UserContext {
user: User;
update: (user: User) => void;
}
export const createPhoneUserAction = (getUiActionsApi: () => Promise<UiActionsStart>) =>
createAction<{
user: User;
update: (user: User) => void;
}>({
createAction<UserContext>({
type: PHONE_USER_ACTION,
getDisplayName: () => 'Call phone number',
isCompatible: async ({ user }) => user.phone !== undefined,
@ -126,6 +132,8 @@ export const createPhoneUserAction = (getUiActionsApi: () => Promise<UiActionsSt
// to the phone number trigger.
// TODO: we need to figure out the best way to handle these nested actions however, since
// we don't want multiple context menu's to pop up.
(await getUiActionsApi()).executeTriggerActions(PHONE_TRIGGER, { phone: user.phone });
if (user.phone !== undefined) {
(await getUiActionsApi()).executeTriggerActions(PHONE_TRIGGER, user.phone);
}
},
});

View file

@ -60,7 +60,7 @@ const ActionsExplorer = ({ uiActionsApi, openModal }: Props) => {
</EuiText>
<EuiButton
data-test-subj="emitHelloWorldTrigger"
onClick={() => uiActionsApi.executeTriggerActions(HELLO_WORLD_TRIGGER_ID, {})}
onClick={() => uiActionsApi.executeTriggerActions(HELLO_WORLD_TRIGGER_ID, undefined)}
>
Say hello world!
</EuiButton>

View file

@ -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<void, void, {}, StartDeps> {
public setup(core: CoreSetup<{ uiActions: UiActionsStart }>, deps: SetupDeps) {
deps.uiActions.registerTrigger({

View file

@ -47,9 +47,7 @@ const createRowData = (
<Fragment>
<EuiButtonEmpty
onClick={() => {
uiActionsApi.executeTriggerActions(COUNTRY_TRIGGER, {
country: user.countryOfResidence,
});
uiActionsApi.executeTriggerActions(COUNTRY_TRIGGER, user.countryOfResidence);
}}
>
{user.countryOfResidence}
@ -59,10 +57,9 @@ const createRowData = (
phone: (
<Fragment>
<EuiButtonEmpty
disabled={user.phone === undefined}
onClick={() => {
uiActionsApi.executeTriggerActions(PHONE_TRIGGER, {
phone: user.phone,
});
uiActionsApi.executeTriggerActions(PHONE_TRIGGER, user.phone!);
}}
>
{user.phone}

View file

@ -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<SearchInput, SearchOutput>
filterManager,
}: SearchEmbeddableConfig,
initialInput: SearchInput,
private readonly executeTriggerActions: ExecuteTriggerActions,
private readonly executeTriggerActions: UiActionsStart['executeTriggerActions'],
parent?: Container
) {
super(

View file

@ -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<auto.IInjectorService>,
isEditable: () => boolean
) {

View file

@ -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;
}
}

View file

@ -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'];

View file

@ -44,7 +44,7 @@ import {
import { inspectorPluginMock } from 'src/plugins/inspector/public/mocks';
import { EuiBadge } from '@elastic/eui';
const actionRegistry = new Map<string, Action>();
const actionRegistry = new Map<string, Action<object | undefined | string | number>>();
const triggerRegistry = new Map<string, Trigger>();
const embeddableFactories = new Map<string, EmbeddableFactory>();
const getEmbeddableFactory: GetEmbeddableFactory = (id: string) => embeddableFactories.get(id);

View file

@ -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<any, any>;
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<Action<EmbeddableContext>>;
}
export class EmbeddablePanel extends React.Component<Props, State> {
@ -87,7 +87,7 @@ export class EmbeddablePanel extends React.Component<Props, State> {
}
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<Props, State> {
// These actions are exposed on the context menu for every embeddable, they bypass the trigger
// registry.
const extraActions: Array<Action<{ embeddable: IEmbeddable }>> = [
const extraActions: Array<Action<EmbeddableContext>> = [
new CustomizePanelTitleAction(createGetUserData(this.props.overlays)),
new AddPanelAction(
this.props.getEmbeddableFactory,
@ -245,11 +245,13 @@ export class EmbeddablePanel extends React.Component<Props, State> {
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<EmbeddableContext>, b: Action<EmbeddableContext>) => {
const bOrder = b.order || 0;
const aOrder = a.order || 0;
return bOrder - aOrder;
});
return await buildContextMenuForActions({
actions: sorted,

View file

@ -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<EuiContextMenuPanelDescriptor>;
closeContextMenu: boolean;
badges: Action[];
badges: Array<Action<EmbeddableContext>>;
embeddable: IEmbeddable;
headerId?: string;
}
function renderBadges(badges: Action[], embeddable: IEmbeddable) {
function renderBadges(badges: Array<Action<EmbeddableContext>>, embeddable: IEmbeddable) {
return badges.map(badge => (
<EuiBadge
key={badge.id}

View file

@ -19,16 +19,12 @@
import { createAction } from '../../ui_actions';
import { ViewMode } from '../../types';
import { IEmbeddable } from '../../embeddables';
import { EmbeddableContext } from '../../triggers';
export const EDIT_MODE_ACTION = 'EDIT_MODE_ACTION';
interface ActionContext {
embeddable: IEmbeddable;
}
export function createEditModeAction() {
return createAction<ActionContext>({
return createAction<EmbeddableContext>({
type: EDIT_MODE_ACTION,
getDisplayName: () => 'I only show up in edit mode',
isCompatible: async context => context.embeddable.getInput().viewMode === ViewMode.EDIT,

View file

@ -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<Props, State
emitContactTrigger = () => {
this.props.execTrigger(CONTACT_USER_TRIGGER, {
embeddable: this.props.embeddable,
triggerContext: {},
});
};

View file

@ -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) {

View file

@ -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<ContactCardE
constructor(
options: EmbeddableFactoryOptions<any>,
private readonly execTrigger: ExecuteTriggerActions,
private readonly execTrigger: UiActionsStart['executeTriggerActions'],
private readonly overlays: CoreStart['overlays']
) {
super(options);

View file

@ -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;
}

View file

@ -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'];

View file

@ -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'];

View file

@ -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;

View file

@ -19,7 +19,7 @@
import { UiComponent } from 'src/plugins/kibana_utils/common';
export interface Action<ActionContext extends {} = {}> {
export interface Action<Context = undefined> {
/**
* 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<ActionContext extends {} = {}> {
/**
* 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<boolean>;
isCompatible(context: Context): Promise<boolean>;
/**
* 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<void>;
execute(context: Context): Promise<void>;
}

View file

@ -19,11 +19,9 @@
import { Action } from './action';
export function createAction<ActionContext extends {} = {}>(
action: { type: string; execute: Action<ActionContext>['execute'] } & Partial<
Action<ActionContext>
>
): Action<ActionContext> {
export function createAction<Context = undefined>(
action: { type: string; execute: Action<Context>['execute'] } & Partial<Action<Context>>
): Action<Context> {
return {
getIconType: () => undefined,
order: 0,

View file

@ -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';

View file

@ -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<UiActionsSetup>;
export type Start = jest.Mocked<UiActionsStart>;
@ -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(),

View file

@ -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].'
);
});

View file

@ -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 = <T extends TriggerId>(triggerId: T): TriggerContract<T> => {
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 = <Context>(action: Action<Context>) => {
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
// <T extends TriggerId>(triggerId: T, action: Action<TriggerContextMapping[T]>): \
// to get type checks here!
public readonly attachAction = <T extends TriggerId>(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 = <T extends TriggerId>(
triggerId: T
): Array<Action<TriggerContextMapping[T]>> => {
// 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<TriggerContextMapping[T]>
>;
return actions as Array<Action<TriggerContext<T>>>;
};
public readonly getTriggerCompatibleActions = async <C>(triggerId: string, context: C) => {
public readonly getTriggerCompatibleActions = async <T extends TriggerId>(
triggerId: T,
context: TriggerContextMapping[T]
): Promise<Array<Action<TriggerContextMapping[T]>>> => {
const actions = this.getTriggerActions!(triggerId);
const isCompatibles = await Promise.all(actions.map(action => action.isCompatible(context)));
return actions.reduce<Action[]>(
(acc, action, i) => (isCompatibles[i] ? [...acc, action] : acc),
return actions.reduce(
(acc: Array<Action<TriggerContextMapping[T]>>, action, i) =>
isCompatibles[i] ? [...acc, action] : acc,
[]
);
};

View file

@ -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);
});

View file

@ -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);

View file

@ -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<typeof uiActionsPluginMock.createPlugin>;
@ -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);

View file

@ -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<T extends TriggerId> {
/**
* Use this method to execute action attached to this trigger.
*/
public readonly exec = async (context: TriggerContext<T>) => {
public readonly exec = async (context: TriggerContextMapping[T]) => {
await this.internal.execute(context);
};
}

View file

@ -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<T extends TriggerId> {
constructor(public readonly service: UiActionsService, public readonly trigger: Trigger<T>) {}
public async execute(context: TriggerContext<T>) {
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<T extends TriggerId> {
await this.executeMultipleActions(actions, context);
}
private async executeSingleAction(action: Action<TriggerContext<T>>, context: TriggerContext<T>) {
private async executeSingleAction(
action: Action<TriggerContextMapping[T]>,
context: TriggerContextMapping[T]
) {
const href = action.getHref && action.getHref(context);
if (href) {
@ -63,8 +66,8 @@ export class TriggerInternal<T extends TriggerId> {
}
private async executeMultipleActions(
actions: Array<Action<TriggerContext<T>>>,
context: TriggerContext<T>
actions: Array<Action<TriggerContextMapping[T]>>,
context: TriggerContextMapping[T]
) {
const panel = await buildContextMenuForActions({
actions,

View file

@ -20,12 +20,17 @@
import { Action } from './actions/action';
import { TriggerInternal } from './triggers/trigger_internal';
export type TriggerRegistry = Map<string, TriggerInternal<any>>;
export type ActionRegistry = Map<string, Action>;
export type TriggerToActionsRegistry = Map<string, string[]>;
export type TriggerRegistry = Map<TriggerId, TriggerInternal<any>>;
export type ActionRegistry = Map<string, Action<any>>;
export type TriggerToActionsRegistry = Map<TriggerId, string[]>;
export type TriggerId = string;
const DEFAULT_TRIGGER = '';
export type TriggerId = keyof TriggerContextMapping;
export type TriggerContext = BaseContext;
export type BaseContext = object | undefined | string | number;
export interface TriggerContextMapping {
[key: string]: object;
[DEFAULT_TRIGGER]: TriggerContext;
}

View file

@ -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'];

View file

@ -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'];