Skip to content

Commit

Permalink
[UiActions] pass trigger into action execution context (#74363) (#75029)
Browse files Browse the repository at this point in the history
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
  • Loading branch information
Dosant and elasticmachine committed Aug 14, 2020
1 parent 603292c commit 455b33b
Show file tree
Hide file tree
Showing 23 changed files with 277 additions and 68 deletions.
9 changes: 7 additions & 2 deletions examples/ui_actions_explorer/public/actions/actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ import { OverlayStart } from 'kibana/public';
import { EuiFieldText, EuiModalBody, EuiButton } from '@elastic/eui';
import { useState } from 'react';
import { toMountPoint } from '../../../../src/plugins/kibana_react/public';
import { createAction, UiActionsStart } from '../../../../src/plugins/ui_actions/public';
import {
ActionExecutionContext,
createAction,
UiActionsStart,
} from '../../../../src/plugins/ui_actions/public';

export const USER_TRIGGER = 'USER_TRIGGER';
export const COUNTRY_TRIGGER = 'COUNTRY_TRIGGER';
Expand All @@ -37,7 +41,8 @@ export const ACTION_SHOWCASE_PLUGGABILITY = 'ACTION_SHOWCASE_PLUGGABILITY';
export const showcasePluggability = createAction<typeof ACTION_SHOWCASE_PLUGGABILITY>({
type: ACTION_SHOWCASE_PLUGGABILITY,
getDisplayName: () => 'This is pluggable! Any plugin can inject their actions here.',
execute: async () => alert("Isn't that cool?!"),
execute: async (context: ActionExecutionContext) =>
alert(`Isn't that cool?! Triggered by ${context.trigger?.id} trigger`),
});

export interface PhoneContext {
Expand Down
6 changes: 3 additions & 3 deletions examples/ui_actions_explorer/public/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,9 @@ const ActionsExplorer = ({ uiActionsApi, openModal }: Props) => {
});
uiActionsApi.addTriggerAction(HELLO_WORLD_TRIGGER_ID, dynamicAction);
setConfirmationText(
`You've successfully added a new action: ${dynamicAction.getDisplayName(
{}
)}. Refresh the page to reset state. It's up to the user of the system to persist state like this.`
`You've successfully added a new action: ${dynamicAction.getDisplayName({
trigger: uiActionsApi.getTrigger(HELLO_WORLD_TRIGGER_ID),
})}. Refresh the page to reset state. It's up to the user of the system to persist state like this.`
);
}}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import { createFilterAction } from './apply_filter_action';
import { expectErrorAsync } from '../../tests/helpers';
import { defaultTrigger } from '../../../../ui_actions/public/triggers';

test('has ACTION_APPLY_FILTER type and id', () => {
const action = createFilterAction();
Expand Down Expand Up @@ -51,6 +52,7 @@ describe('isCompatible()', () => {
}),
} as any,
filters: [],
trigger: defaultTrigger,
});
expect(result).toBe(true);
});
Expand All @@ -66,6 +68,7 @@ describe('isCompatible()', () => {
}),
} as any,
filters: [],
trigger: defaultTrigger,
});
expect(result).toBe(false);
});
Expand Down Expand Up @@ -119,6 +122,7 @@ describe('execute()', () => {
await action.execute({
embeddable,
filters: ['FILTER' as any],
trigger: defaultTrigger,
});

expect(root.updateInput).toHaveBeenCalledTimes(1);
Expand Down
7 changes: 6 additions & 1 deletion src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
PANEL_BADGE_TRIGGER,
PANEL_NOTIFICATION_TRIGGER,
EmbeddableContext,
contextMenuTrigger,
} from '../triggers';
import { IEmbeddable, EmbeddableOutput, EmbeddableError } from '../embeddables/i_embeddable';
import { ViewMode } from '../types';
Expand Down Expand Up @@ -311,7 +312,11 @@ export class EmbeddablePanel extends React.Component<Props, State> {
const sortedActions = [...regularActions, ...extraActions].sort(sortByOrderField);

return await buildContextMenuForActions({
actions: sortedActions.map((action) => [action, { embeddable: this.props.embeddable }]),
actions: sortedActions.map((action) => ({
action,
context: { embeddable: this.props.embeddable },
trigger: contextMenuTrigger,
})),
closeMenu: this.closeMyContextMenuPanel,
});
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { ContactCardEmbeddable } from '../../../../test_samples';
import { esFilters, Filter } from '../../../../../../../../plugins/data/public';
import { EmbeddableStart } from '../../../../../plugin';
import { embeddablePluginMock } from '../../../../../mocks';
import { defaultTrigger } from '../../../../../../../ui_actions/public/triggers';

const { setup, doStart } = embeddablePluginMock.createInstance();
setup.registerEmbeddableFactory(FILTERABLE_EMBEDDABLE, new FilterableEmbeddableFactory());
Expand Down Expand Up @@ -85,7 +86,9 @@ test('Is not compatible when container is in view mode', async () => {
() => null
);
container.updateInput({ viewMode: ViewMode.VIEW });
expect(await addPanelAction.isCompatible({ embeddable: container })).toBe(false);
expect(
await addPanelAction.isCompatible({ embeddable: container, trigger: defaultTrigger })
).toBe(false);
});

test('Is not compatible when embeddable is not a container', async () => {
Expand All @@ -94,7 +97,7 @@ test('Is not compatible when embeddable is not a container', async () => {

test('Is compatible when embeddable is a parent and in edit mode', async () => {
container.updateInput({ viewMode: ViewMode.EDIT });
expect(await action.isCompatible({ embeddable: container })).toBe(true);
expect(await action.isCompatible({ embeddable: container, trigger: defaultTrigger })).toBe(true);
});

test('Execute throws an error when called with an embeddable that is not a container', async () => {
Expand All @@ -108,6 +111,7 @@ test('Execute throws an error when called with an embeddable that is not a conta
},
{} as any
),
trigger: defaultTrigger,
} as any);
}
await expect(check()).rejects.toThrow(Error);
Expand All @@ -116,6 +120,7 @@ test('Execute does not throw an error when called with a compatible container',
container.updateInput({ viewMode: ViewMode.EDIT });
await action.execute({
embeddable: container,
trigger: defaultTrigger,
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/
import { i18n } from '@kbn/i18n';
import { Action } from 'src/plugins/ui_actions/public';
import { Action, ActionExecutionContext } from 'src/plugins/ui_actions/public';
import { NotificationsStart, OverlayStart } from 'src/core/public';
import { EmbeddableStart } from 'src/plugins/embeddable/public/plugin';
import { ViewMode } from '../../../../types';
Expand Down Expand Up @@ -52,12 +52,14 @@ export class AddPanelAction implements Action<ActionContext> {
return 'plusInCircleFilled';
}

public async isCompatible({ embeddable }: ActionContext) {
public async isCompatible(context: ActionExecutionContext<ActionContext>) {
const { embeddable } = context;
return embeddable.getIsContainer() && embeddable.getInput().viewMode === ViewMode.EDIT;
}

public async execute({ embeddable }: ActionContext) {
if (!embeddable.getIsContainer() || !(await this.isCompatible({ embeddable }))) {
public async execute(context: ActionExecutionContext<ActionContext>) {
const { embeddable } = context;
if (!embeddable.getIsContainer() || !(await this.isCompatible(context))) {
throw new Error('Context is incompatible');
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,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';
import { EmbeddableContext, panelBadgeTrigger, panelNotificationTrigger } from '../../triggers';

export interface PanelHeaderProps {
title?: string;
Expand All @@ -49,11 +49,11 @@ function renderBadges(badges: Array<Action<EmbeddableContext>>, embeddable: IEmb
<EuiBadge
key={badge.id}
className="embPanel__headerBadge"
iconType={badge.getIconType({ embeddable })}
onClick={() => badge.execute({ embeddable })}
onClickAriaLabel={badge.getDisplayName({ embeddable })}
iconType={badge.getIconType({ embeddable, trigger: panelBadgeTrigger })}
onClick={() => badge.execute({ embeddable, trigger: panelBadgeTrigger })}
onClickAriaLabel={badge.getDisplayName({ embeddable, trigger: panelBadgeTrigger })}
>
{badge.getDisplayName({ embeddable })}
{badge.getDisplayName({ embeddable, trigger: panelBadgeTrigger })}
</EuiBadge>
));
}
Expand All @@ -70,14 +70,17 @@ function renderNotifications(
data-test-subj={`embeddablePanelNotification-${notification.id}`}
key={notification.id}
style={{ marginTop: '4px', marginRight: '4px' }}
onClick={() => notification.execute(context)}
onClick={() => notification.execute({ ...context, trigger: panelNotificationTrigger })}
>
{notification.getDisplayName(context)}
{notification.getDisplayName({ ...context, trigger: panelNotificationTrigger })}
</EuiNotificationBadge>
);

if (notification.getDisplayNameTooltip) {
const tooltip = notification.getDisplayNameTooltip(context);
const tooltip = notification.getDisplayNameTooltip({
...context,
trigger: panelNotificationTrigger,
});

if (tooltip) {
badge = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/

import { ActionByType, IncompatibleActionError, ActionType } from '../../ui_actions';
import { IncompatibleActionError, ActionType, ActionDefinitionByType } from '../../ui_actions';
import { EmbeddableInput, Embeddable, EmbeddableOutput, IEmbeddable } from '../../embeddables';

// Casting to ActionType is a hack - in a real situation use
Expand All @@ -42,7 +42,7 @@ export interface SayHelloActionContext {
message?: string;
}

export class SayHelloAction implements ActionByType<typeof SAY_HELLO_ACTION> {
export class SayHelloAction implements ActionDefinitionByType<typeof SAY_HELLO_ACTION> {
public readonly type = SAY_HELLO_ACTION;
public readonly id = SAY_HELLO_ACTION;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
FilterableEmbeddableInput,
} from '../lib/test_samples';
import { esFilters } from '../../../data/public';
import { applyFilterTrigger } from '../../../ui_actions/public';

test('ApplyFilterAction applies the filter to the root of the container tree', async () => {
const { doStart, setup } = testPlugin();
Expand Down Expand Up @@ -85,7 +86,7 @@ test('ApplyFilterAction applies the filter to the root of the container tree', a
query: { match: { extension: { query: 'foo' } } },
};

await applyFilterAction.execute({ embeddable, filters: [filter] });
await applyFilterAction.execute({ embeddable, filters: [filter], trigger: applyFilterTrigger });
expect(root.getInput().filters.length).toBe(1);
expect(node1.getInput().filters.length).toBe(1);
expect(embeddable.getInput().filters.length).toBe(1);
Expand Down
13 changes: 10 additions & 3 deletions src/plugins/ui_actions/public/actions/action.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
* under the License.
*/

import { createAction } from '../../../ui_actions/public';
import { ActionExecutionContext, createAction } from '../../../ui_actions/public';
import { ActionType } from '../types';
import { defaultTrigger } from '../triggers';

const sayHelloAction = createAction({
// Casting to ActionType is a hack - in a real situation use
Expand All @@ -29,11 +30,17 @@ const sayHelloAction = createAction({
});

test('action is not compatible based on context', async () => {
const isCompatible = await sayHelloAction.isCompatible({ amICompatible: false });
const isCompatible = await sayHelloAction.isCompatible({
amICompatible: false,
trigger: defaultTrigger,
} as ActionExecutionContext);
expect(isCompatible).toBe(false);
});

test('action is compatible based on context', async () => {
const isCompatible = await sayHelloAction.isCompatible({ amICompatible: true });
const isCompatible = await sayHelloAction.isCompatible({
amICompatible: true,
trigger: defaultTrigger,
} as ActionExecutionContext);
expect(isCompatible).toBe(true);
});
Loading

0 comments on commit 455b33b

Please sign in to comment.