mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
parent
1ef6373f77
commit
01daabcb53
124 changed files with 1422 additions and 1029 deletions
|
@ -28,7 +28,8 @@
|
|||
"kbnESQuery": "packages/kbn-es-query",
|
||||
"inspector": "src/plugins/inspector",
|
||||
"kibana-react": "src/plugins/kibana_react",
|
||||
"esUi": "src/plugins/es_ui_shared"
|
||||
"esUi": "src/plugins/es_ui_shared",
|
||||
"uiActions": "src/plugins/ui_actions"
|
||||
},
|
||||
"exclude": ["src/legacy/ui/ui_render/ui_render_mixin.js"],
|
||||
"translations": []
|
||||
|
|
|
@ -51,6 +51,7 @@ export default {
|
|||
'!src/legacy/ui/public/{agg_types,vis}/**/*.d.ts',
|
||||
],
|
||||
moduleNameMapper: {
|
||||
'^src/plugins/(.*)': '<rootDir>/src/plugins/$1',
|
||||
'^plugins/([^\/.]*)(.*)': '<rootDir>/src/legacy/core_plugins/$1/public$2',
|
||||
'^ui/(.*)': '<rootDir>/src/legacy/ui/public/$1',
|
||||
'^uiExports/(.*)': '<rootDir>/src/dev/jest/mocks/file_mock.js',
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
"version": "kibana",
|
||||
"requiredPlugins": [
|
||||
"embeddable",
|
||||
"inspector"
|
||||
"inspector",
|
||||
"ui_actions"
|
||||
],
|
||||
"server": false,
|
||||
"ui": true
|
||||
|
|
|
@ -33,6 +33,7 @@ const pluginInstance = plugin({} as any);
|
|||
|
||||
export const setup = pluginInstance.setup(npSetup.core, {
|
||||
embeddable: embeddableSetup,
|
||||
uiActions: npSetup.plugins.uiActions,
|
||||
});
|
||||
|
||||
export const start = pluginInstance.start(npStart.core, {
|
||||
|
@ -42,4 +43,5 @@ export const start = pluginInstance.start(npStart.core, {
|
|||
SavedObjectFinder,
|
||||
ExitFullScreenButton,
|
||||
},
|
||||
uiActions: npStart.plugins.uiActions,
|
||||
});
|
||||
|
|
|
@ -53,6 +53,7 @@ beforeEach(async () => {
|
|||
notifications: {} as any,
|
||||
overlays: {} as any,
|
||||
savedObjectMetaData: {} as any,
|
||||
uiActions: {} as any,
|
||||
};
|
||||
const input = getSampleDashboardInput({
|
||||
panels: {
|
||||
|
|
|
@ -18,12 +18,12 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
Action,
|
||||
IEmbeddable,
|
||||
IncompatibleActionError,
|
||||
} from '../../../../../../embeddable_api/public/np_ready/public';
|
||||
import { IEmbeddable } from '../../../../../../embeddable_api/public/np_ready/public';
|
||||
import { DASHBOARD_CONTAINER_TYPE, DashboardContainer } from '../embeddable';
|
||||
import {
|
||||
IAction,
|
||||
IncompatibleActionError,
|
||||
} from '../../../../../../../../../src/plugins/ui_actions/public';
|
||||
|
||||
export const EXPAND_PANEL_ACTION = 'togglePanel';
|
||||
|
||||
|
@ -43,13 +43,12 @@ interface ActionContext {
|
|||
embeddable: IEmbeddable;
|
||||
}
|
||||
|
||||
export class ExpandPanelAction extends Action<ActionContext> {
|
||||
export class ExpandPanelAction implements IAction<ActionContext> {
|
||||
public readonly type = EXPAND_PANEL_ACTION;
|
||||
public readonly id = EXPAND_PANEL_ACTION;
|
||||
public order = 7;
|
||||
|
||||
constructor() {
|
||||
super(EXPAND_PANEL_ACTION);
|
||||
this.order = 7;
|
||||
}
|
||||
constructor() {}
|
||||
|
||||
public getDisplayName({ embeddable }: ActionContext) {
|
||||
if (!embeddable.parent || !isDashboard(embeddable.parent)) {
|
||||
|
|
|
@ -45,6 +45,7 @@ const options: DashboardContainerOptions = {
|
|||
inspector: {} as any,
|
||||
SavedObjectFinder: () => null,
|
||||
ExitFullScreenButton: () => null,
|
||||
uiActions: {} as any,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -22,6 +22,7 @@ import ReactDOM from 'react-dom';
|
|||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import { Filter } from '@kbn/es-query';
|
||||
import { RefreshInterval, TimeRange } from 'src/plugins/data/public';
|
||||
import { IUiActionsStart } from '../../../../../../../../plugins/ui_actions/public';
|
||||
import {
|
||||
Container,
|
||||
ContainerInput,
|
||||
|
@ -82,6 +83,7 @@ export interface DashboardContainerOptions {
|
|||
inspector: InspectorStartContract;
|
||||
SavedObjectFinder: React.ComponentType<any>;
|
||||
ExitFullScreenButton: React.ComponentType<any>;
|
||||
uiActions: IUiActionsStart;
|
||||
}
|
||||
|
||||
export type DashboardReactContextValue = KibanaReactContextValue<DashboardContainerOptions>;
|
||||
|
|
|
@ -68,6 +68,7 @@ function prepare(props?: Partial<DashboardGridProps>) {
|
|||
inspector: {} as any,
|
||||
SavedObjectFinder: () => null,
|
||||
ExitFullScreenButton: () => null,
|
||||
uiActions: {} as any,
|
||||
};
|
||||
dashboardContainer = new DashboardContainer(initialInput, options);
|
||||
const defaultTestProps: DashboardGridProps = {
|
||||
|
|
|
@ -266,7 +266,7 @@ class DashboardGridUi extends React.Component<DashboardGridProps, State> {
|
|||
<EmbeddableChildPanel
|
||||
embeddableId={panel.explicitInput.id}
|
||||
container={this.props.container}
|
||||
getActions={this.props.kibana.services.embeddable.getTriggerCompatibleActions}
|
||||
getActions={this.props.kibana.services.uiActions.getTriggerCompatibleActions}
|
||||
getEmbeddableFactory={this.props.kibana.services.embeddable.getEmbeddableFactory}
|
||||
getAllEmbeddableFactories={this.props.kibana.services.embeddable.getEmbeddableFactories}
|
||||
overlays={this.props.kibana.services.overlays}
|
||||
|
|
|
@ -59,6 +59,7 @@ function getProps(
|
|||
inspector: {} as any,
|
||||
SavedObjectFinder: () => null,
|
||||
ExitFullScreenButton,
|
||||
uiActions: {} as any,
|
||||
};
|
||||
|
||||
const input = getSampleDashboardInput({
|
||||
|
|
|
@ -18,17 +18,20 @@
|
|||
*/
|
||||
|
||||
import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public';
|
||||
import { IUiActionsSetup, IUiActionsStart } from '../../../../../../plugins/ui_actions/public';
|
||||
import { CONTEXT_MENU_TRIGGER, Plugin as EmbeddablePlugin } from './lib/embeddable_api';
|
||||
import { ExpandPanelAction, DashboardContainerFactory } from './lib';
|
||||
import { Start as InspectorStartContract } from '../../../../../../plugins/inspector/public';
|
||||
|
||||
interface SetupDependencies {
|
||||
embeddable: ReturnType<EmbeddablePlugin['setup']>;
|
||||
uiActions: IUiActionsSetup;
|
||||
}
|
||||
|
||||
interface StartDependencies {
|
||||
embeddable: ReturnType<EmbeddablePlugin['start']>;
|
||||
inspector: InspectorStartContract;
|
||||
uiActions: IUiActionsStart;
|
||||
__LEGACY: {
|
||||
SavedObjectFinder: React.ComponentType<any>;
|
||||
ExitFullScreenButton: React.ComponentType<any>;
|
||||
|
@ -42,15 +45,15 @@ export class DashboardEmbeddableContainerPublicPlugin
|
|||
implements Plugin<Setup, Start, SetupDependencies, StartDependencies> {
|
||||
constructor(initializerContext: PluginInitializerContext) {}
|
||||
|
||||
public setup(core: CoreSetup, { embeddable }: SetupDependencies): Setup {
|
||||
public setup(core: CoreSetup, { embeddable, uiActions }: SetupDependencies): Setup {
|
||||
const expandPanelAction = new ExpandPanelAction();
|
||||
embeddable.registerAction(expandPanelAction);
|
||||
embeddable.attachAction(CONTEXT_MENU_TRIGGER, expandPanelAction.id);
|
||||
uiActions.registerAction(expandPanelAction);
|
||||
uiActions.attachAction(CONTEXT_MENU_TRIGGER, expandPanelAction.id);
|
||||
}
|
||||
|
||||
public start(core: CoreStart, plugins: StartDependencies): Start {
|
||||
const { application, notifications, overlays } = core;
|
||||
const { embeddable, inspector, __LEGACY } = plugins;
|
||||
const { embeddable, inspector, __LEGACY, uiActions } = plugins;
|
||||
|
||||
const factory = new DashboardContainerFactory({
|
||||
application,
|
||||
|
@ -60,6 +63,7 @@ export class DashboardEmbeddableContainerPublicPlugin
|
|||
inspector,
|
||||
SavedObjectFinder: __LEGACY.SavedObjectFinder,
|
||||
ExitFullScreenButton: __LEGACY.ExitFullScreenButton,
|
||||
uiActions,
|
||||
});
|
||||
|
||||
embeddable.registerEmbeddableFactory(factory.type, factory);
|
||||
|
|
|
@ -39,18 +39,21 @@ import {
|
|||
ContactCardEmbeddableOutput,
|
||||
} from '../../../../../embeddable_api/public/np_ready/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable';
|
||||
import { embeddablePluginMock } from '../../../../../embeddable_api/public/np_ready/public/mocks';
|
||||
import { EditModeAction } from '../../../../../embeddable_api/public/np_ready/public/lib/test_samples/actions/edit_mode_action';
|
||||
import { createEditModeAction } from '../../../../../embeddable_api/public/np_ready/public/lib/test_samples/actions/edit_mode_action';
|
||||
// eslint-disable-next-line
|
||||
import { inspectorPluginMock } from '../../../../../../../plugins/inspector/public/mocks';
|
||||
import { KibanaContextProvider } from '../../../../../../../plugins/kibana_react/public';
|
||||
// eslint-disable-next-line
|
||||
import { uiActionsPluginMock } from 'src/plugins/ui_actions/public/mocks';
|
||||
|
||||
test('DashboardContainer in edit mode shows edit mode actions', async () => {
|
||||
const inspector = inspectorPluginMock.createStartContract();
|
||||
const { setup, doStart } = embeddablePluginMock.createInstance();
|
||||
const uiActionsSetup = uiActionsPluginMock.createSetupContract();
|
||||
|
||||
const editModeAction = new EditModeAction();
|
||||
setup.registerAction(editModeAction);
|
||||
setup.attachAction(CONTEXT_MENU_TRIGGER, editModeAction.id);
|
||||
const editModeAction = createEditModeAction();
|
||||
uiActionsSetup.registerAction(editModeAction);
|
||||
uiActionsSetup.attachAction(CONTEXT_MENU_TRIGGER, editModeAction.id);
|
||||
setup.registerEmbeddableFactory(
|
||||
CONTACT_CARD_EMBEDDABLE,
|
||||
new ContactCardEmbeddableFactory({} as any, (() => null) as any, {} as any)
|
||||
|
@ -67,6 +70,7 @@ test('DashboardContainer in edit mode shows edit mode actions', async () => {
|
|||
inspector: {} as any,
|
||||
SavedObjectFinder: () => null,
|
||||
ExitFullScreenButton: () => null,
|
||||
uiActions: {} as any,
|
||||
};
|
||||
const container = new DashboardContainer(initialInput, options);
|
||||
|
||||
|
|
|
@ -2,5 +2,9 @@
|
|||
"id": "embeddable",
|
||||
"version": "kibana",
|
||||
"server": false,
|
||||
"ui": true
|
||||
"ui": true,
|
||||
"requiredPlugins": [
|
||||
"embeddable",
|
||||
"inspector"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -23,32 +23,16 @@ import {
|
|||
EmbeddableApi,
|
||||
EmbeddableDependenciesInternal,
|
||||
} from './types';
|
||||
import { attachAction } from './attach_action';
|
||||
import { detachAction } from './detach_action';
|
||||
import { executeTriggerActions } from './execute_trigger_actions';
|
||||
import { getEmbeddableFactories } from './get_embeddable_factories';
|
||||
import { getEmbeddableFactory } from './get_embeddable_factory';
|
||||
import { getTrigger } from './get_trigger';
|
||||
import { getTriggerActions } from './get_trigger_actions';
|
||||
import { getTriggerCompatibleActions } from './get_trigger_compatible_actions';
|
||||
import { registerAction } from './register_action';
|
||||
import { registerEmbeddableFactory } from './register_embeddable_factory';
|
||||
import { registerTrigger } from './register_trigger';
|
||||
|
||||
export * from './types';
|
||||
|
||||
export const pureApi: EmbeddableApiPure = {
|
||||
attachAction,
|
||||
detachAction,
|
||||
executeTriggerActions,
|
||||
getEmbeddableFactories,
|
||||
getEmbeddableFactory,
|
||||
getTrigger,
|
||||
getTriggerActions,
|
||||
getTriggerCompatibleActions,
|
||||
registerAction,
|
||||
registerEmbeddableFactory,
|
||||
registerTrigger,
|
||||
};
|
||||
|
||||
export const createApi = (deps: EmbeddableDependencies) => {
|
||||
|
|
|
@ -21,8 +21,6 @@ import { EmbeddableDependencies } from '../types';
|
|||
|
||||
export const createDeps = (): EmbeddableDependencies => {
|
||||
const deps: EmbeddableDependencies = {
|
||||
triggers: new Map<any, any>(),
|
||||
actions: new Map<any, any>(),
|
||||
embeddableFactories: new Map<any, any>(),
|
||||
};
|
||||
return deps;
|
||||
|
|
|
@ -19,151 +19,6 @@
|
|||
|
||||
import { createApi } from '..';
|
||||
import { createDeps } from './helpers';
|
||||
import { expectError } from '../../tests/helpers';
|
||||
|
||||
const HELLO_WORLD_ACTION_ID = 'HELLO_WORLD_ACTION_ID';
|
||||
|
||||
test('can register trigger', () => {
|
||||
const deps = createDeps();
|
||||
const { api } = createApi(deps);
|
||||
|
||||
api.registerTrigger({
|
||||
actionIds: [],
|
||||
description: 'foo',
|
||||
id: 'bar',
|
||||
title: 'baz',
|
||||
});
|
||||
|
||||
expect(deps.triggers.get('bar')).toEqual({
|
||||
actionIds: [],
|
||||
description: 'foo',
|
||||
id: 'bar',
|
||||
title: 'baz',
|
||||
});
|
||||
});
|
||||
|
||||
test('can register action', () => {
|
||||
const deps = createDeps();
|
||||
const { api } = createApi(deps);
|
||||
|
||||
api.registerAction({
|
||||
id: HELLO_WORLD_ACTION_ID,
|
||||
order: 13,
|
||||
} as any);
|
||||
|
||||
expect(deps.actions.get(HELLO_WORLD_ACTION_ID)).toMatchObject({
|
||||
id: HELLO_WORLD_ACTION_ID,
|
||||
order: 13,
|
||||
});
|
||||
});
|
||||
|
||||
test('can attach an action to a trigger', () => {
|
||||
const deps = createDeps();
|
||||
const { api } = createApi(deps);
|
||||
const trigger = {
|
||||
id: 'MY-TRIGGER',
|
||||
actionIds: [],
|
||||
};
|
||||
const action = {
|
||||
id: HELLO_WORLD_ACTION_ID,
|
||||
order: 25,
|
||||
} as any;
|
||||
|
||||
expect(trigger.actionIds).toEqual([]);
|
||||
|
||||
api.registerTrigger(trigger);
|
||||
api.registerAction(action);
|
||||
api.attachAction('MY-TRIGGER', HELLO_WORLD_ACTION_ID);
|
||||
|
||||
expect(trigger.actionIds).toEqual([HELLO_WORLD_ACTION_ID]);
|
||||
});
|
||||
|
||||
test('can detach an action to a trigger', () => {
|
||||
const deps = createDeps();
|
||||
const { api } = createApi(deps);
|
||||
const trigger = {
|
||||
id: 'MY-TRIGGER',
|
||||
actionIds: [],
|
||||
};
|
||||
const action = {
|
||||
id: HELLO_WORLD_ACTION_ID,
|
||||
order: 25,
|
||||
} as any;
|
||||
|
||||
expect(trigger.actionIds).toEqual([]);
|
||||
|
||||
api.registerTrigger(trigger);
|
||||
api.registerAction(action);
|
||||
api.attachAction('MY-TRIGGER', HELLO_WORLD_ACTION_ID);
|
||||
api.detachAction('MY-TRIGGER', HELLO_WORLD_ACTION_ID);
|
||||
|
||||
expect(trigger.actionIds).toEqual([]);
|
||||
});
|
||||
|
||||
test('detaching an invalid action from a trigger throws an error', async () => {
|
||||
const deps = createDeps();
|
||||
const { api } = createApi(deps);
|
||||
const action = {
|
||||
id: HELLO_WORLD_ACTION_ID,
|
||||
order: 25,
|
||||
} as any;
|
||||
|
||||
api.registerAction(action);
|
||||
const error = expectError(() => api.detachAction('i do not exist', HELLO_WORLD_ACTION_ID));
|
||||
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect(error.message).toMatchInlineSnapshot(
|
||||
`"No trigger [triggerId = i do not exist] exists, for detaching action [actionId = HELLO_WORLD_ACTION_ID]."`
|
||||
);
|
||||
});
|
||||
|
||||
test('attaching an invalid action to a trigger throws an error', async () => {
|
||||
const deps = createDeps();
|
||||
const { api } = createApi(deps);
|
||||
const action = {
|
||||
id: HELLO_WORLD_ACTION_ID,
|
||||
order: 25,
|
||||
} as any;
|
||||
|
||||
api.registerAction(action);
|
||||
const error = expectError(() => api.attachAction('i do not exist', HELLO_WORLD_ACTION_ID));
|
||||
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect(error.message).toMatchInlineSnapshot(
|
||||
`"No trigger [triggerId = i do not exist] exists, for attaching action [actionId = HELLO_WORLD_ACTION_ID]."`
|
||||
);
|
||||
});
|
||||
|
||||
test('cannot register another action with the same ID', async () => {
|
||||
const deps = createDeps();
|
||||
const { api } = createApi(deps);
|
||||
const action = {
|
||||
id: HELLO_WORLD_ACTION_ID,
|
||||
order: 25,
|
||||
} as any;
|
||||
|
||||
api.registerAction(action);
|
||||
const error = expectError(() => api.registerAction(action));
|
||||
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect(error.message).toMatchInlineSnapshot(
|
||||
`"Action [action.id = HELLO_WORLD_ACTION_ID] already registered in Embeddables API."`
|
||||
);
|
||||
});
|
||||
|
||||
test('cannot register another trigger with the same ID', async () => {
|
||||
const deps = createDeps();
|
||||
const { api } = createApi(deps);
|
||||
const trigger = { id: 'MY-TRIGGER' } as any;
|
||||
|
||||
api.registerTrigger(trigger);
|
||||
const error = expectError(() => api.registerTrigger(trigger));
|
||||
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect(error.message).toMatchInlineSnapshot(
|
||||
`"Trigger [trigger.id = MY-TRIGGER] already registered in Embeddables API."`
|
||||
);
|
||||
});
|
||||
|
||||
test('cannot register embeddable factory with the same ID', async () => {
|
||||
const deps = createDeps();
|
||||
|
@ -172,12 +27,7 @@ test('cannot register embeddable factory with the same ID', async () => {
|
|||
const embeddableFactory = {} as any;
|
||||
|
||||
api.registerEmbeddableFactory(embeddableFactoryId, embeddableFactory);
|
||||
const error = expectError(() =>
|
||||
api.registerEmbeddableFactory(embeddableFactoryId, embeddableFactory)
|
||||
);
|
||||
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect(error.message).toMatchInlineSnapshot(
|
||||
`"Embeddable factory [embeddableFactoryId = ID] already registered in Embeddables API."`
|
||||
expect(() => api.registerEmbeddableFactory(embeddableFactoryId, embeddableFactory)).toThrowError(
|
||||
'Embeddable factory [embeddableFactoryId = ID] already registered in Embeddables API.'
|
||||
);
|
||||
});
|
||||
|
|
|
@ -17,34 +17,18 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { TriggerRegistry, ActionRegistry, EmbeddableFactoryRegistry } from '../types';
|
||||
import {
|
||||
Trigger,
|
||||
Action,
|
||||
EmbeddableFactory,
|
||||
ExecuteTriggerActions,
|
||||
GetEmbeddableFactories,
|
||||
} from '../lib';
|
||||
import { EmbeddableFactoryRegistry } from '../types';
|
||||
import { EmbeddableFactory, GetEmbeddableFactories } from '../lib';
|
||||
|
||||
export interface EmbeddableApi {
|
||||
attachAction: (triggerId: string, actionId: string) => void;
|
||||
detachAction: (triggerId: string, actionId: string) => void;
|
||||
executeTriggerActions: ExecuteTriggerActions;
|
||||
getEmbeddableFactory: (embeddableFactoryId: string) => EmbeddableFactory;
|
||||
getEmbeddableFactories: GetEmbeddableFactories;
|
||||
getTrigger: (id: string) => Trigger;
|
||||
getTriggerActions: (id: string) => Action[];
|
||||
getTriggerCompatibleActions: <C>(triggerId: string, context: C) => Promise<Array<Action<C>>>;
|
||||
registerAction: (action: Action) => void;
|
||||
// TODO: Make `registerEmbeddableFactory` receive only `factory` argument.
|
||||
registerEmbeddableFactory: (id: string, factory: EmbeddableFactory) => void;
|
||||
registerTrigger: (trigger: Trigger) => void;
|
||||
}
|
||||
|
||||
export interface EmbeddableDependencies {
|
||||
actions: ActionRegistry;
|
||||
embeddableFactories: EmbeddableFactoryRegistry;
|
||||
triggers: TriggerRegistry;
|
||||
}
|
||||
|
||||
export interface EmbeddableDependenciesInternal extends EmbeddableDependencies {
|
||||
|
|
|
@ -17,11 +17,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { EmbeddableApi } from './api/types';
|
||||
import { IUiActionsSetup } from 'src/plugins/ui_actions/public';
|
||||
import {
|
||||
CONTEXT_MENU_TRIGGER,
|
||||
APPLY_FILTER_TRIGGER,
|
||||
ApplyFilterAction,
|
||||
createFilterAction,
|
||||
PANEL_BADGE_TRIGGER,
|
||||
} from './lib';
|
||||
|
||||
|
@ -31,7 +31,7 @@ import {
|
|||
*
|
||||
* @param api
|
||||
*/
|
||||
export const bootstrap = (api: EmbeddableApi) => {
|
||||
export const bootstrap = (uiActions: IUiActionsSetup) => {
|
||||
const triggerContext = {
|
||||
id: CONTEXT_MENU_TRIGGER,
|
||||
title: 'Context menu',
|
||||
|
@ -50,11 +50,11 @@ export const bootstrap = (api: EmbeddableApi) => {
|
|||
description: 'Actions appear in title bar when an embeddable loads in a panel',
|
||||
actionIds: [],
|
||||
};
|
||||
const actionApplyFilter = new ApplyFilterAction();
|
||||
const actionApplyFilter = createFilterAction();
|
||||
|
||||
api.registerTrigger(triggerContext);
|
||||
api.registerTrigger(triggerFilter);
|
||||
api.registerAction(actionApplyFilter);
|
||||
api.registerTrigger(triggerBadge);
|
||||
api.attachAction(triggerFilter.id, actionApplyFilter.id);
|
||||
uiActions.registerTrigger(triggerContext);
|
||||
uiActions.registerTrigger(triggerFilter);
|
||||
uiActions.registerAction(actionApplyFilter);
|
||||
uiActions.registerTrigger(triggerBadge);
|
||||
uiActions.attachAction(triggerFilter.id, actionApplyFilter.id);
|
||||
};
|
||||
|
|
|
@ -25,10 +25,8 @@ export {
|
|||
APPLY_FILTER_ACTION,
|
||||
APPLY_FILTER_TRIGGER,
|
||||
PANEL_BADGE_TRIGGER,
|
||||
Action,
|
||||
Adapters,
|
||||
AddPanelAction,
|
||||
ApplyFilterAction,
|
||||
CONTEXT_MENU_TRIGGER,
|
||||
Container,
|
||||
ContainerInput,
|
||||
|
@ -45,19 +43,15 @@ export {
|
|||
EmbeddableOutput,
|
||||
EmbeddablePanel,
|
||||
ErrorEmbeddable,
|
||||
ExecuteTriggerActions,
|
||||
GetActionsCompatibleWithTrigger,
|
||||
GetEmbeddableFactories,
|
||||
GetEmbeddableFactory,
|
||||
IContainer,
|
||||
IEmbeddable,
|
||||
IncompatibleActionError,
|
||||
OutputSpec,
|
||||
PanelNotFoundError,
|
||||
PanelState,
|
||||
PropertySpec,
|
||||
SavedObjectMetaData,
|
||||
Trigger,
|
||||
ViewMode,
|
||||
isErrorEmbeddable,
|
||||
openAddPanelFlyout,
|
||||
|
|
|
@ -24,5 +24,5 @@ import { npSetup, npStart } from 'ui/new_platform';
|
|||
import { plugin } from '.';
|
||||
|
||||
const pluginInstance = plugin({} as any);
|
||||
export const setup = pluginInstance.setup(npSetup.core);
|
||||
export const setup = pluginInstance.setup(npSetup.core, { uiActions: npSetup.plugins.uiActions });
|
||||
export const start = pluginInstance.start(npStart.core);
|
||||
|
|
|
@ -17,29 +17,23 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { Action } from './action';
|
||||
import { ApplyFilterAction } from './apply_filter_action';
|
||||
import { createFilterAction } from './apply_filter_action';
|
||||
import { expectError } from '../../tests/helpers';
|
||||
|
||||
test('is instance of Action', () => {
|
||||
const action = new ApplyFilterAction();
|
||||
expect(action).toBeInstanceOf(Action);
|
||||
});
|
||||
|
||||
test('has APPLY_FILTER_ACTION type and id', () => {
|
||||
const action = new ApplyFilterAction();
|
||||
const action = createFilterAction();
|
||||
expect(action.id).toBe('APPLY_FILTER_ACTION');
|
||||
expect(action.type).toBe('APPLY_FILTER_ACTION');
|
||||
});
|
||||
|
||||
test('has expected display name', () => {
|
||||
const action = new ApplyFilterAction();
|
||||
expect(action.getDisplayName()).toMatchInlineSnapshot(`"Apply filter to current view"`);
|
||||
const action = createFilterAction();
|
||||
expect(action.getDisplayName({} as any)).toMatchInlineSnapshot(`"Apply filter to current view"`);
|
||||
});
|
||||
|
||||
describe('isCompatible()', () => {
|
||||
test('when embeddable filters and triggerContext filters exist, returns true', async () => {
|
||||
const action = new ApplyFilterAction();
|
||||
test('when embeddable filters and filters exist, returns true', async () => {
|
||||
const action = createFilterAction();
|
||||
const result = await action.isCompatible({
|
||||
embeddable: {
|
||||
getRoot: () => ({
|
||||
|
@ -54,7 +48,7 @@ describe('isCompatible()', () => {
|
|||
});
|
||||
|
||||
test('when embeddable filters not set, returns false', async () => {
|
||||
const action = new ApplyFilterAction();
|
||||
const action = createFilterAction();
|
||||
const result = await action.isCompatible({
|
||||
embeddable: {
|
||||
getRoot: () => ({
|
||||
|
@ -68,8 +62,8 @@ describe('isCompatible()', () => {
|
|||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
test('when triggerContext or triggerContext filters are not set, returns false', async () => {
|
||||
const action = new ApplyFilterAction();
|
||||
test('when triggerContext or filters are not set, returns false', async () => {
|
||||
const action = createFilterAction();
|
||||
|
||||
const result1 = await action.isCompatible({
|
||||
embeddable: {
|
||||
|
@ -98,9 +92,9 @@ const getEmbeddable = () => {
|
|||
};
|
||||
|
||||
describe('execute()', () => {
|
||||
describe('when triggerContext not set', () => {
|
||||
describe('when no filters are given', () => {
|
||||
test('throws an error', async () => {
|
||||
const action = new ApplyFilterAction();
|
||||
const action = createFilterAction();
|
||||
const error = expectError(() =>
|
||||
action.execute({
|
||||
embeddable: getEmbeddable(),
|
||||
|
@ -109,8 +103,8 @@ describe('execute()', () => {
|
|||
expect(error).toBeInstanceOf(Error);
|
||||
});
|
||||
|
||||
test('updates filter input on success', async done => {
|
||||
const action = new ApplyFilterAction();
|
||||
test('updates filter input on success', async () => {
|
||||
const action = createFilterAction();
|
||||
const [embeddable, root] = getEmbeddable();
|
||||
|
||||
await action.execute({
|
||||
|
@ -122,23 +116,6 @@ describe('execute()', () => {
|
|||
expect(root.updateInput.mock.calls[0][0]).toMatchObject({
|
||||
filters: ['FILTER'],
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
test('checks if action isCompatible', async done => {
|
||||
const action = new ApplyFilterAction();
|
||||
const spy = jest.spyOn(action, 'isCompatible');
|
||||
const [embeddable] = getEmbeddable();
|
||||
|
||||
await action.execute({
|
||||
embeddable,
|
||||
filters: ['FILTER' as any],
|
||||
});
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -19,9 +19,12 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Filter } from '@kbn/es-query';
|
||||
import {
|
||||
IAction,
|
||||
createAction,
|
||||
IncompatibleActionError,
|
||||
} from '../../../../../../../../plugins/ui_actions/public';
|
||||
import { IEmbeddable, EmbeddableInput } from '../embeddables';
|
||||
import { Action } from './action';
|
||||
import { IncompatibleActionError } from '../errors';
|
||||
|
||||
export const APPLY_FILTER_ACTION = 'APPLY_FILTER_ACTION';
|
||||
|
||||
|
@ -31,40 +34,38 @@ interface ActionContext {
|
|||
filters: Filter[];
|
||||
}
|
||||
|
||||
export class ApplyFilterAction extends Action<ActionContext> {
|
||||
public readonly type = APPLY_FILTER_ACTION;
|
||||
|
||||
constructor() {
|
||||
super(APPLY_FILTER_ACTION);
|
||||
}
|
||||
|
||||
public getDisplayName() {
|
||||
return i18n.translate('embeddableApi.actions.applyFilterActionTitle', {
|
||||
defaultMessage: 'Apply filter to current view',
|
||||
});
|
||||
}
|
||||
|
||||
public async isCompatible(context: ActionContext) {
|
||||
if (context.embeddable === undefined) {
|
||||
return false;
|
||||
}
|
||||
const root = context.embeddable.getRoot() as RootEmbeddable;
|
||||
return Boolean(root.getInput().filters !== undefined && context.filters !== undefined);
|
||||
}
|
||||
|
||||
public async execute({ embeddable, filters }: ActionContext) {
|
||||
if (!filters || !embeddable) {
|
||||
throw new Error('Applying a filter requires a filter and embeddable as context');
|
||||
}
|
||||
|
||||
if (!(await this.isCompatible({ embeddable, filters }))) {
|
||||
throw new IncompatibleActionError();
|
||||
}
|
||||
|
||||
const root = embeddable.getRoot() as RootEmbeddable;
|
||||
|
||||
root.updateInput({
|
||||
filters,
|
||||
});
|
||||
async function isCompatible(context: ActionContext) {
|
||||
if (context.embeddable === undefined) {
|
||||
return false;
|
||||
}
|
||||
const root = context.embeddable.getRoot() as RootEmbeddable;
|
||||
return Boolean(root.getInput().filters !== undefined && context.filters !== undefined);
|
||||
}
|
||||
|
||||
export function createFilterAction(): IAction<ActionContext> {
|
||||
return createAction<ActionContext>({
|
||||
type: APPLY_FILTER_ACTION,
|
||||
id: APPLY_FILTER_ACTION,
|
||||
getDisplayName: () => {
|
||||
return i18n.translate('embeddableApi.actions.applyFilterActionTitle', {
|
||||
defaultMessage: 'Apply filter to current view',
|
||||
});
|
||||
},
|
||||
isCompatible,
|
||||
execute: async ({ embeddable, filters }) => {
|
||||
if (!filters || !embeddable) {
|
||||
throw new Error('Applying a filter requires a filter and embeddable as context');
|
||||
}
|
||||
|
||||
if (!(await isCompatible({ embeddable, filters }))) {
|
||||
throw new IncompatibleActionError();
|
||||
}
|
||||
|
||||
const root = embeddable.getRoot() as RootEmbeddable;
|
||||
|
||||
root.updateInput({
|
||||
filters,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Action } from './action';
|
||||
import { IAction } from 'src/plugins/ui_actions/public';
|
||||
import { GetEmbeddableFactory, ViewMode } from '../types';
|
||||
import { EmbeddableFactoryNotFoundError } from '../errors';
|
||||
import { IEmbeddable } from '../embeddables';
|
||||
|
@ -29,12 +29,12 @@ interface ActionContext {
|
|||
embeddable: IEmbeddable;
|
||||
}
|
||||
|
||||
export class EditPanelAction extends Action<ActionContext> {
|
||||
export class EditPanelAction implements IAction<ActionContext> {
|
||||
public readonly type = EDIT_PANEL_ACTION_ID;
|
||||
constructor(private readonly getEmbeddableFactory: GetEmbeddableFactory) {
|
||||
super(EDIT_PANEL_ACTION_ID);
|
||||
this.order = 15;
|
||||
}
|
||||
public readonly id = EDIT_PANEL_ACTION_ID;
|
||||
public order = 15;
|
||||
|
||||
constructor(private readonly getEmbeddableFactory: GetEmbeddableFactory) {}
|
||||
|
||||
public getDisplayName({ embeddable }: ActionContext) {
|
||||
const factory = this.getEmbeddableFactory(embeddable.type);
|
||||
|
|
|
@ -17,6 +17,5 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { Action } from './action';
|
||||
export * from './apply_filter_action';
|
||||
export * from './edit_panel_action';
|
||||
|
|
|
@ -23,21 +23,19 @@ import React from 'react';
|
|||
import { EuiLoadingChart } from '@elastic/eui';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { CoreStart } from 'src/core/public';
|
||||
import { TGetActionsCompatibleWithTrigger } from 'src/plugins/ui_actions/public';
|
||||
|
||||
import { ErrorEmbeddable, IEmbeddable } from '../embeddables';
|
||||
import { EmbeddablePanel } from '../panel';
|
||||
import { IContainer } from './i_container';
|
||||
import {
|
||||
GetActionsCompatibleWithTrigger,
|
||||
GetEmbeddableFactory,
|
||||
GetEmbeddableFactories,
|
||||
} from '../types';
|
||||
import { GetEmbeddableFactory, GetEmbeddableFactories } from '../types';
|
||||
import { Start as InspectorStartContract } from '../../../../../../../../plugins/inspector/public';
|
||||
|
||||
export interface EmbeddableChildPanelProps {
|
||||
embeddableId: string;
|
||||
className?: string;
|
||||
container: IContainer;
|
||||
getActions: GetActionsCompatibleWithTrigger;
|
||||
getActions: TGetActionsCompatibleWithTrigger;
|
||||
getEmbeddableFactory: GetEmbeddableFactory;
|
||||
getAllEmbeddableFactories: GetEmbeddableFactories;
|
||||
overlays: CoreStart['overlays'];
|
||||
|
|
|
@ -17,11 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
IncompatibleActionError,
|
||||
PanelNotFoundError,
|
||||
EmbeddableFactoryNotFoundError,
|
||||
} from './errors';
|
||||
import { IncompatibleActionError } from 'src/plugins/ui_actions/public';
|
||||
import { PanelNotFoundError, EmbeddableFactoryNotFoundError } from './errors';
|
||||
|
||||
describe('IncompatibleActionError', () => {
|
||||
test('is instance of error', () => {
|
||||
|
|
|
@ -20,18 +20,6 @@
|
|||
/* eslint-disable max-classes-per-file */
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export class IncompatibleActionError extends Error {
|
||||
code = 'INCOMPATIBLE_ACTION';
|
||||
|
||||
constructor() {
|
||||
super(
|
||||
i18n.translate('embeddableApi.errors.incompatibleAction', {
|
||||
defaultMessage: 'Action is incompatible',
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class PanelNotFoundError extends Error {
|
||||
code = 'PANEL_NOT_FOUND';
|
||||
|
||||
|
|
|
@ -24,4 +24,3 @@ export * from './actions';
|
|||
export * from './triggers';
|
||||
export * from './containers';
|
||||
export * from './panel';
|
||||
export * from './context_menu_actions';
|
||||
|
|
|
@ -25,11 +25,11 @@ import { nextTick } from 'test_utils/enzyme_helpers';
|
|||
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import { CONTEXT_MENU_TRIGGER } from '../triggers';
|
||||
import { Action } from '../actions';
|
||||
import { IAction, ITrigger } from 'src/plugins/ui_actions/public';
|
||||
import { Trigger, GetEmbeddableFactory, ViewMode } from '../types';
|
||||
import { EmbeddableFactory, isErrorEmbeddable } from '../embeddables';
|
||||
import { EmbeddablePanel } from './embeddable_panel';
|
||||
import { EditModeAction } from '../test_samples/actions/edit_mode_action';
|
||||
import { createEditModeAction } from '../test_samples/actions';
|
||||
import {
|
||||
ContactCardEmbeddableFactory,
|
||||
CONTACT_CARD_EMBEDDABLE,
|
||||
|
@ -43,12 +43,12 @@ import {
|
|||
// eslint-disable-next-line
|
||||
import { inspectorPluginMock } from '../../../../../../../../plugins/inspector/public/mocks';
|
||||
|
||||
const actionRegistry = new Map<string, Action>();
|
||||
const triggerRegistry = new Map<string, Trigger>();
|
||||
const actionRegistry = new Map<string, IAction>();
|
||||
const triggerRegistry = new Map<string, ITrigger>();
|
||||
const embeddableFactories = new Map<string, EmbeddableFactory>();
|
||||
const getEmbeddableFactory: GetEmbeddableFactory = (id: string) => embeddableFactories.get(id);
|
||||
|
||||
const editModeAction = new EditModeAction();
|
||||
const editModeAction = createEditModeAction();
|
||||
const trigger: Trigger = {
|
||||
id: CONTEXT_MENU_TRIGGER,
|
||||
actionIds: [editModeAction.id],
|
||||
|
|
|
@ -20,30 +20,29 @@ import { EuiContextMenuPanelDescriptor, EuiPanel } from '@elastic/eui';
|
|||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import { Subscription } from 'rxjs';
|
||||
import {
|
||||
buildContextMenuForActions,
|
||||
TGetActionsCompatibleWithTrigger,
|
||||
IAction,
|
||||
} from '../../../../../../../../plugins/ui_actions/public';
|
||||
import { CoreStart } from '../../../../../../../../core/public';
|
||||
import { buildContextMenuForActions } from '../context_menu_actions';
|
||||
|
||||
import { CONTEXT_MENU_TRIGGER, PANEL_BADGE_TRIGGER } from '../triggers';
|
||||
import { IEmbeddable } from '../embeddables/i_embeddable';
|
||||
import {
|
||||
ViewMode,
|
||||
GetActionsCompatibleWithTrigger,
|
||||
GetEmbeddableFactory,
|
||||
GetEmbeddableFactories,
|
||||
} from '../types';
|
||||
import { ViewMode, GetEmbeddableFactory, GetEmbeddableFactories } from '../types';
|
||||
|
||||
import { RemovePanelAction } from './panel_header/panel_actions';
|
||||
import { AddPanelAction } from './panel_header/panel_actions/add_panel/add_panel_action';
|
||||
import { CustomizePanelTitleAction } from './panel_header/panel_actions/customize_title/customize_panel_action';
|
||||
import { PanelHeader } from './panel_header/panel_header';
|
||||
import { InspectPanelAction } from './panel_header/panel_actions/inspect_panel_action';
|
||||
import { EditPanelAction, Action } from '../actions';
|
||||
import { EditPanelAction } from '../actions';
|
||||
import { CustomizePanelModal } from './panel_header/panel_actions/customize_title/customize_panel_modal';
|
||||
import { Start as InspectorStartContract } from '../../../../../../../../plugins/inspector/public';
|
||||
|
||||
interface Props {
|
||||
embeddable: IEmbeddable<any, any>;
|
||||
getActions: GetActionsCompatibleWithTrigger;
|
||||
getActions: TGetActionsCompatibleWithTrigger;
|
||||
getEmbeddableFactory: GetEmbeddableFactory;
|
||||
getAllEmbeddableFactories: GetEmbeddableFactories;
|
||||
overlays: CoreStart['overlays'];
|
||||
|
@ -58,7 +57,7 @@ interface State {
|
|||
viewMode: ViewMode;
|
||||
hidePanelTitles: boolean;
|
||||
closeContextMenu: boolean;
|
||||
badges: Action[];
|
||||
badges: IAction[];
|
||||
}
|
||||
|
||||
export class EmbeddablePanel extends React.Component<Props, State> {
|
||||
|
@ -211,7 +210,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 = [
|
||||
const extraActions: Array<IAction<{ embeddable: IEmbeddable }>> = [
|
||||
new CustomizePanelTitleAction(createGetUserData(this.props.overlays)),
|
||||
new AddPanelAction(
|
||||
this.props.getEmbeddableFactory,
|
||||
|
@ -225,8 +224,10 @@ export class EmbeddablePanel extends React.Component<Props, State> {
|
|||
new EditPanelAction(this.props.getEmbeddableFactory),
|
||||
];
|
||||
|
||||
const sorted = actions.concat(extraActions).sort((a: Action, b: Action) => {
|
||||
return b.order - a.order;
|
||||
const sorted = actions.concat(extraActions).sort((a: IAction, b: IAction) => {
|
||||
const bOrder = b.order || 0;
|
||||
const aOrder = a.order || 0;
|
||||
return bOrder - aOrder;
|
||||
});
|
||||
|
||||
return await buildContextMenuForActions({
|
||||
|
|
|
@ -30,6 +30,7 @@ import { FilterableEmbeddableFactory } from '../../../../test_samples/embeddable
|
|||
import { FilterableContainer } from '../../../../test_samples/embeddables/filterable_container';
|
||||
import { GetEmbeddableFactory } from '../../../../types';
|
||||
import { coreMock } from '../../../../../../../../../../../core/public/mocks';
|
||||
import { ContactCardEmbeddable } from '../../../../test_samples';
|
||||
|
||||
const embeddableFactories = new Map<string, EmbeddableFactory>();
|
||||
embeddableFactories.set(FILTERABLE_EMBEDDABLE, new FilterableEmbeddableFactory());
|
||||
|
@ -88,10 +89,7 @@ test('Is not compatible when container is in view mode', async () => {
|
|||
});
|
||||
|
||||
test('Is not compatible when embeddable is not a container', async () => {
|
||||
expect(
|
||||
// @ts-ignore
|
||||
await action.isCompatible({ embeddable })
|
||||
).toBe(false);
|
||||
expect(await action.isCompatible({ embeddable } as any)).toBe(false);
|
||||
});
|
||||
|
||||
test('Is compatible when embeddable is a parent and in edit mode', async () => {
|
||||
|
@ -102,13 +100,15 @@ test('Is compatible when embeddable is a parent and in edit mode', async () => {
|
|||
test('Execute throws an error when called with an embeddable that is not a container', async () => {
|
||||
async function check() {
|
||||
await action.execute({
|
||||
// @ts-ignore
|
||||
embeddable: new ContactCardEmbeddable({
|
||||
firstName: 'sue',
|
||||
id: '123',
|
||||
viewMode: ViewMode.EDIT,
|
||||
}),
|
||||
});
|
||||
embeddable: new ContactCardEmbeddable(
|
||||
{
|
||||
firstName: 'sue',
|
||||
id: '123',
|
||||
viewMode: ViewMode.EDIT,
|
||||
},
|
||||
{} as any
|
||||
),
|
||||
} as any);
|
||||
}
|
||||
await expect(check()).rejects.toThrow(Error);
|
||||
});
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { IAction } from 'src/plugins/ui_actions/public';
|
||||
import { ViewMode, GetEmbeddableFactory, GetEmbeddableFactories } from '../../../../types';
|
||||
import { Action } from '../../../../actions';
|
||||
import { openAddPanelFlyout } from './open_add_panel_flyout';
|
||||
import { NotificationsStart } from '../../../../../../../../../../../core/public';
|
||||
import { KibanaReactOverlays } from '../../../../../../../../../../../plugins/kibana_react/public';
|
||||
|
@ -30,8 +30,9 @@ interface ActionContext {
|
|||
embeddable: IContainer;
|
||||
}
|
||||
|
||||
export class AddPanelAction extends Action<ActionContext> {
|
||||
export class AddPanelAction implements IAction<ActionContext> {
|
||||
public readonly type = ADD_PANEL_ACTION_ID;
|
||||
public readonly id = ADD_PANEL_ACTION_ID;
|
||||
|
||||
constructor(
|
||||
private readonly getFactory: GetEmbeddableFactory,
|
||||
|
@ -39,9 +40,7 @@ export class AddPanelAction extends Action<ActionContext> {
|
|||
private readonly overlays: KibanaReactOverlays,
|
||||
private readonly notifications: NotificationsStart,
|
||||
private readonly SavedObjectFinder: React.ComponentType<any>
|
||||
) {
|
||||
super(ADD_PANEL_ACTION_ID);
|
||||
}
|
||||
) {}
|
||||
|
||||
public getDisplayName() {
|
||||
return i18n.translate('embeddableApi.addPanel.displayName', {
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Action } from '../../../../actions';
|
||||
import { IAction } from 'src/plugins/ui_actions/public';
|
||||
import { ViewMode } from '../../../../types';
|
||||
import { IEmbeddable } from '../../../../embeddables';
|
||||
|
||||
|
@ -30,11 +30,12 @@ interface ActionContext {
|
|||
embeddable: IEmbeddable;
|
||||
}
|
||||
|
||||
export class CustomizePanelTitleAction extends Action<ActionContext> {
|
||||
export class CustomizePanelTitleAction implements IAction<ActionContext> {
|
||||
public readonly type = CUSTOMIZE_PANEL_ACTION_ID;
|
||||
public id = CUSTOMIZE_PANEL_ACTION_ID;
|
||||
public order = 10;
|
||||
|
||||
constructor(private readonly getDataFromUser: GetUserData) {
|
||||
super(CUSTOMIZE_PANEL_ACTION_ID);
|
||||
this.order = 10;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Action } from '../../../actions';
|
||||
import { IAction } from '../../../../../../../../../../plugins/ui_actions/public';
|
||||
import { Start as InspectorStartContract } from '../../../../../../../../../../plugins/inspector/public';
|
||||
import { IEmbeddable } from '../../../embeddables';
|
||||
|
||||
|
@ -28,13 +28,12 @@ interface ActionContext {
|
|||
embeddable: IEmbeddable;
|
||||
}
|
||||
|
||||
export class InspectPanelAction extends Action<ActionContext> {
|
||||
export class InspectPanelAction implements IAction<ActionContext> {
|
||||
public readonly type = INSPECT_PANEL_ACTION_ID;
|
||||
public readonly id = INSPECT_PANEL_ACTION_ID;
|
||||
public order = 10;
|
||||
|
||||
constructor(private readonly inspector: InspectorStartContract) {
|
||||
super(INSPECT_PANEL_ACTION_ID);
|
||||
this.order = 20;
|
||||
}
|
||||
constructor(private readonly inspector: InspectorStartContract) {}
|
||||
|
||||
public getDisplayName() {
|
||||
return i18n.translate('embeddableApi.panel.inspectPanel.displayName', {
|
||||
|
|
|
@ -17,10 +17,12 @@
|
|||
* under the License.
|
||||
*/
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
IAction,
|
||||
IncompatibleActionError,
|
||||
} from '../../../../../../../../../../plugins/ui_actions/public';
|
||||
import { ContainerInput, IContainer } from '../../../containers';
|
||||
import { ViewMode } from '../../../types';
|
||||
import { Action } from '../../../actions';
|
||||
import { IncompatibleActionError } from '../../../errors';
|
||||
import { IEmbeddable } from '../../../embeddables';
|
||||
|
||||
export const REMOVE_PANEL_ACTION = 'deletePanel';
|
||||
|
@ -39,13 +41,12 @@ function hasExpandedPanelInput(
|
|||
return (container as IContainer<{}, ExpandedPanelInput>).getInput().expandedPanelId !== undefined;
|
||||
}
|
||||
|
||||
export class RemovePanelAction extends Action<ActionContext> {
|
||||
export class RemovePanelAction implements IAction<ActionContext> {
|
||||
public readonly type = REMOVE_PANEL_ACTION;
|
||||
constructor() {
|
||||
super(REMOVE_PANEL_ACTION);
|
||||
public readonly id = REMOVE_PANEL_ACTION;
|
||||
public order = 5;
|
||||
|
||||
this.order = 5;
|
||||
}
|
||||
constructor() {}
|
||||
|
||||
public getDisplayName() {
|
||||
return i18n.translate('embeddableApi.panel.removePanel.displayName', {
|
||||
|
|
|
@ -20,8 +20,8 @@ import { i18n } from '@kbn/i18n';
|
|||
import { EuiContextMenuPanelDescriptor, EuiBadge, EuiIcon, EuiToolTip } from '@elastic/eui';
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import { IAction } from 'src/plugins/ui_actions/public';
|
||||
import { PanelOptionsMenu } from './panel_options_menu';
|
||||
import { Action } from '../../actions';
|
||||
import { IEmbeddable } from '../../embeddables';
|
||||
import { VisualizeEmbeddable } from '../../../../../../../kibana/public/visualize/embeddable/visualize_embeddable';
|
||||
import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../../../../kibana/public/visualize/embeddable/constants';
|
||||
|
@ -32,11 +32,11 @@ export interface PanelHeaderProps {
|
|||
hidePanelTitles: boolean;
|
||||
getActionContextMenuPanel: () => Promise<EuiContextMenuPanelDescriptor>;
|
||||
closeContextMenu: boolean;
|
||||
badges: Action[];
|
||||
badges: IAction[];
|
||||
embeddable: IEmbeddable;
|
||||
}
|
||||
|
||||
function renderBadges(badges: Action[], embeddable: IEmbeddable) {
|
||||
function renderBadges(badges: IAction[], embeddable: IEmbeddable) {
|
||||
return badges.map(badge => (
|
||||
<EuiBadge
|
||||
key={badge.id}
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { createAction } from '../../../../../../../../../plugins/ui_actions/public';
|
||||
import { ViewMode } from '../../types';
|
||||
import { Action } from '../../actions';
|
||||
import { IEmbeddable } from '../../embeddables';
|
||||
|
||||
export const EDIT_MODE_ACTION = 'EDIT_MODE_ACTION';
|
||||
|
@ -26,22 +26,12 @@ export const EDIT_MODE_ACTION = 'EDIT_MODE_ACTION';
|
|||
interface ActionContext {
|
||||
embeddable: IEmbeddable;
|
||||
}
|
||||
export class EditModeAction extends Action<ActionContext> {
|
||||
public readonly type = EDIT_MODE_ACTION;
|
||||
|
||||
constructor() {
|
||||
super(EDIT_MODE_ACTION);
|
||||
}
|
||||
|
||||
getDisplayName() {
|
||||
return `I should only show up in edit mode`;
|
||||
}
|
||||
|
||||
async isCompatible(context: ActionContext) {
|
||||
return context.embeddable.getInput().viewMode === ViewMode.EDIT;
|
||||
}
|
||||
|
||||
async execute() {
|
||||
return;
|
||||
}
|
||||
export function createEditModeAction() {
|
||||
return createAction<ActionContext>({
|
||||
type: EDIT_MODE_ACTION,
|
||||
getDisplayName: () => 'I only show up in edit mode',
|
||||
isCompatible: async context => context.embeddable.getInput().viewMode === ViewMode.EDIT,
|
||||
execute: async () => {},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -18,8 +18,5 @@
|
|||
*/
|
||||
|
||||
export * from './edit_mode_action';
|
||||
export * from './get_message_modal';
|
||||
export * from './hello_world_action';
|
||||
export * from './restricted_action';
|
||||
export * from './say_hello_action';
|
||||
export * from './send_message_action';
|
||||
|
|
|
@ -17,9 +17,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { Action } from '../../actions';
|
||||
import {
|
||||
IAction,
|
||||
IncompatibleActionError,
|
||||
} from '../../../../../../../../../plugins/ui_actions/public';
|
||||
import { EmbeddableInput, Embeddable, EmbeddableOutput, IEmbeddable } from '../../embeddables';
|
||||
import { IncompatibleActionError } from '../../errors';
|
||||
|
||||
export const SAY_HELLO_ACTION = 'SAY_HELLO_ACTION';
|
||||
|
||||
|
@ -41,14 +43,15 @@ interface ActionContext {
|
|||
message?: string;
|
||||
}
|
||||
|
||||
export class SayHelloAction extends Action<ActionContext> {
|
||||
export class SayHelloAction implements IAction<ActionContext> {
|
||||
public readonly type = SAY_HELLO_ACTION;
|
||||
public readonly id = SAY_HELLO_ACTION;
|
||||
|
||||
private sayHello: (name: string) => void;
|
||||
|
||||
// Taking in a function, instead of always directly interacting with the dom,
|
||||
// can make testing the execute part of the action easier.
|
||||
constructor(sayHello: (name: string) => void) {
|
||||
super(SAY_HELLO_ACTION);
|
||||
this.sayHello = sayHello;
|
||||
}
|
||||
|
||||
|
@ -56,6 +59,10 @@ export class SayHelloAction extends Action<ActionContext> {
|
|||
return 'Say hello';
|
||||
}
|
||||
|
||||
getIconType() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Can use typescript generics to get compiler time warnings for immediate feedback if
|
||||
// the context is not compatible.
|
||||
async isCompatible(context: ActionContext) {
|
||||
|
|
|
@ -18,7 +18,10 @@
|
|||
*/
|
||||
import React from 'react';
|
||||
import { EuiFlyoutBody } from '@elastic/eui';
|
||||
import { Action, IncompatibleActionError } from '../..';
|
||||
import {
|
||||
createAction,
|
||||
IncompatibleActionError,
|
||||
} from '../../../../../../../../../plugins/ui_actions/public';
|
||||
import { Embeddable, EmbeddableInput } from '../../embeddables';
|
||||
import { GetMessageModal } from './get_message_modal';
|
||||
import { FullNameEmbeddableOutput, hasFullNameOutput } from './say_hello_action';
|
||||
|
@ -30,41 +33,35 @@ interface ActionContext {
|
|||
embeddable: Embeddable<EmbeddableInput, FullNameEmbeddableOutput>;
|
||||
message: string;
|
||||
}
|
||||
export class SendMessageAction extends Action {
|
||||
public readonly type = SEND_MESSAGE_ACTION;
|
||||
|
||||
constructor(private readonly overlays: CoreStart['overlays']) {
|
||||
super(SEND_MESSAGE_ACTION);
|
||||
}
|
||||
const isCompatible = async (context: ActionContext) => hasFullNameOutput(context.embeddable);
|
||||
|
||||
getDisplayName() {
|
||||
return 'Send message';
|
||||
}
|
||||
|
||||
async isCompatible(context: ActionContext) {
|
||||
return hasFullNameOutput(context.embeddable);
|
||||
}
|
||||
|
||||
async sendMessage(context: ActionContext, message: string) {
|
||||
export function createSendMessageAction(overlays: CoreStart['overlays']) {
|
||||
const sendMessage = async (context: ActionContext, message: string) => {
|
||||
const greeting = `Hello, ${context.embeddable.getOutput().fullName}`;
|
||||
|
||||
const content = message ? `${greeting}. ${message}` : greeting;
|
||||
this.overlays.openFlyout(<EuiFlyoutBody>{content}</EuiFlyoutBody>);
|
||||
}
|
||||
overlays.openFlyout(<EuiFlyoutBody>{content}</EuiFlyoutBody>);
|
||||
};
|
||||
|
||||
async execute(context: ActionContext) {
|
||||
if (!(await this.isCompatible(context))) {
|
||||
throw new IncompatibleActionError();
|
||||
}
|
||||
return createAction<ActionContext>({
|
||||
type: SEND_MESSAGE_ACTION,
|
||||
getDisplayName: () => 'Send message',
|
||||
isCompatible,
|
||||
execute: async context => {
|
||||
if (!(await isCompatible(context))) {
|
||||
throw new IncompatibleActionError();
|
||||
}
|
||||
|
||||
const modal = this.overlays.openModal(
|
||||
<GetMessageModal
|
||||
onCancel={() => modal.close()}
|
||||
onDone={message => {
|
||||
modal.close();
|
||||
this.sendMessage(context, message);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const modal = overlays.openModal(
|
||||
<GetMessageModal
|
||||
onCancel={() => modal.close()}
|
||||
onDone={message => {
|
||||
modal.close();
|
||||
sendMessage(context, message);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -28,12 +28,12 @@ import {
|
|||
import { Subscription } from 'rxjs';
|
||||
import { EuiButton } from '@elastic/eui';
|
||||
import * as Rx from 'rxjs';
|
||||
import { TExecuteTriggerActions } from 'src/plugins/ui_actions/public';
|
||||
import { ContactCardEmbeddable, CONTACT_USER_TRIGGER } from './contact_card_embeddable';
|
||||
import { ExecuteTriggerActions } from '../../../types';
|
||||
|
||||
interface Props {
|
||||
embeddable: ContactCardEmbeddable;
|
||||
execTrigger: ExecuteTriggerActions;
|
||||
execTrigger: TExecuteTriggerActions;
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
|
|
@ -19,11 +19,11 @@
|
|||
import React from 'react';
|
||||
import ReactDom from 'react-dom';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { TExecuteTriggerActions } 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';
|
||||
import { ContactCardEmbeddableComponent } from './contact_card';
|
||||
import { ExecuteTriggerActions } from '../../../types';
|
||||
|
||||
export interface ContactCardEmbeddableInput extends EmbeddableInput {
|
||||
firstName: string;
|
||||
|
@ -37,7 +37,7 @@ export interface ContactCardEmbeddableOutput extends EmbeddableOutput {
|
|||
}
|
||||
|
||||
export interface ContactCardEmbeddableOptions {
|
||||
execAction: ExecuteTriggerActions;
|
||||
execAction: TExecuteTriggerActions;
|
||||
}
|
||||
|
||||
function getFullName(input: ContactCardEmbeddableInput) {
|
||||
|
|
|
@ -19,12 +19,13 @@
|
|||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { TExecuteTriggerActions } from 'src/plugins/ui_actions/public';
|
||||
|
||||
import { EmbeddableFactory } from '../../../embeddables';
|
||||
import { Container } from '../../../containers';
|
||||
import { ContactCardEmbeddable, ContactCardEmbeddableInput } from './contact_card_embeddable';
|
||||
import { ContactCardInitializer } from './contact_card_initializer';
|
||||
import { EmbeddableFactoryOptions } from '../../../embeddables/embeddable_factory';
|
||||
import { ExecuteTriggerActions } from '../../../types';
|
||||
import { CoreStart } from '../../../../../../../../../../core/public';
|
||||
|
||||
export const CONTACT_CARD_EMBEDDABLE = 'CONTACT_CARD_EMBEDDABLE';
|
||||
|
@ -34,7 +35,7 @@ export class ContactCardEmbeddableFactory extends EmbeddableFactory<ContactCardE
|
|||
|
||||
constructor(
|
||||
options: EmbeddableFactoryOptions<any>,
|
||||
private readonly execTrigger: ExecuteTriggerActions,
|
||||
private readonly execTrigger: TExecuteTriggerActions,
|
||||
private readonly overlays: CoreStart['overlays']
|
||||
) {
|
||||
super(options);
|
||||
|
|
|
@ -17,13 +17,13 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { TExecuteTriggerActions } 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';
|
||||
import { ExecuteTriggerActions } from '../../../types';
|
||||
|
||||
interface SlowContactCardEmbeddableFactoryOptions {
|
||||
execAction: ExecuteTriggerActions;
|
||||
execAction: TExecuteTriggerActions;
|
||||
loadTickCount?: number;
|
||||
}
|
||||
|
||||
|
|
|
@ -54,6 +54,7 @@ export class FilterableContainer extends Container<
|
|||
public getInheritedInput() {
|
||||
return {
|
||||
filters: this.input.filters,
|
||||
viewMode: this.input.viewMode,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -20,13 +20,10 @@ import React from 'react';
|
|||
import ReactDOM from 'react-dom';
|
||||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import { CoreStart } from 'src/core/public';
|
||||
import { TGetActionsCompatibleWithTrigger } from 'src/plugins/ui_actions/public';
|
||||
import { Container, ViewMode, ContainerInput } from '../..';
|
||||
import { HelloWorldContainerComponent } from './hello_world_container_component';
|
||||
import {
|
||||
GetEmbeddableFactory,
|
||||
GetActionsCompatibleWithTrigger,
|
||||
GetEmbeddableFactories,
|
||||
} from '../../types';
|
||||
import { GetEmbeddableFactory, GetEmbeddableFactories } from '../../types';
|
||||
import { Start as InspectorStartContract } from '../../../../../../../../../plugins/inspector/public';
|
||||
|
||||
export const HELLO_WORLD_CONTAINER = 'HELLO_WORLD_CONTAINER';
|
||||
|
@ -48,7 +45,7 @@ interface HelloWorldContainerInput extends ContainerInput {
|
|||
}
|
||||
|
||||
interface HelloWorldContainerOptions {
|
||||
getActions: GetActionsCompatibleWithTrigger;
|
||||
getActions: TGetActionsCompatibleWithTrigger;
|
||||
getEmbeddableFactory: GetEmbeddableFactory;
|
||||
getAllEmbeddableFactories: GetEmbeddableFactories;
|
||||
overlays: CoreStart['overlays'];
|
||||
|
|
|
@ -21,17 +21,14 @@ import { Subscription } from 'rxjs';
|
|||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||
import { CoreStart } from 'src/core/public';
|
||||
import { TGetActionsCompatibleWithTrigger } from 'src/plugins/ui_actions/public';
|
||||
import { IContainer, PanelState, EmbeddableChildPanel } from '../..';
|
||||
import {
|
||||
GetActionsCompatibleWithTrigger,
|
||||
GetEmbeddableFactory,
|
||||
GetEmbeddableFactories,
|
||||
} from '../../types';
|
||||
import { GetEmbeddableFactory, GetEmbeddableFactories } from '../../types';
|
||||
import { Start as InspectorStartContract } from '../../../../../../../../../plugins/inspector/public';
|
||||
|
||||
interface Props {
|
||||
container: IContainer;
|
||||
getActions: GetActionsCompatibleWithTrigger;
|
||||
getActions: TGetActionsCompatibleWithTrigger;
|
||||
getEmbeddableFactory: GetEmbeddableFactory;
|
||||
getAllEmbeddableFactories: GetEmbeddableFactories;
|
||||
overlays: CoreStart['overlays'];
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { Action } from './actions';
|
||||
import { EmbeddableFactory } from './embeddables/embeddable_factory';
|
||||
import { Adapters } from '../../../../../../../plugins/inspector/public';
|
||||
|
||||
|
@ -53,10 +52,5 @@ export interface SavedObjectMetaData<T> {
|
|||
showSavedObject?(savedObject: any): any;
|
||||
}
|
||||
|
||||
export type ExecuteTriggerActions = <A>(triggerId: string, actionContext: A) => Promise<void>;
|
||||
export type GetActionsCompatibleWithTrigger = <C>(
|
||||
triggerId: string,
|
||||
context: C
|
||||
) => Promise<Action[]>;
|
||||
export type GetEmbeddableFactory = (id: string) => EmbeddableFactory | undefined;
|
||||
export type GetEmbeddableFactories = () => IterableIterator<EmbeddableFactory>;
|
||||
|
|
|
@ -20,39 +20,33 @@
|
|||
import { Plugin } from '.';
|
||||
import { coreMock } from '../../../../../../core/public/mocks';
|
||||
|
||||
// eslint-disable-next-line
|
||||
import { uiActionsPluginMock } from '../../../../../../plugins/ui_actions/public/mocks';
|
||||
|
||||
export type Setup = jest.Mocked<ReturnType<Plugin['setup']>>;
|
||||
export type Start = jest.Mocked<ReturnType<Plugin['start']>>;
|
||||
|
||||
const createSetupContract = (): Setup => {
|
||||
const setupContract: Setup = {
|
||||
attachAction: jest.fn(),
|
||||
registerAction: jest.fn(),
|
||||
registerEmbeddableFactory: jest.fn(),
|
||||
registerTrigger: jest.fn(),
|
||||
};
|
||||
return setupContract;
|
||||
};
|
||||
|
||||
const createStartContract = (): Start => {
|
||||
const startContract: Start = {
|
||||
attachAction: jest.fn(),
|
||||
registerAction: jest.fn(),
|
||||
registerEmbeddableFactory: jest.fn(),
|
||||
registerTrigger: jest.fn(),
|
||||
detachAction: jest.fn(),
|
||||
executeTriggerActions: jest.fn(),
|
||||
getEmbeddableFactories: jest.fn(),
|
||||
getEmbeddableFactory: jest.fn(),
|
||||
getTrigger: jest.fn(),
|
||||
getTriggerActions: jest.fn(),
|
||||
getTriggerCompatibleActions: jest.fn(),
|
||||
};
|
||||
return startContract;
|
||||
};
|
||||
|
||||
const createInstance = () => {
|
||||
const plugin = new Plugin({} as any);
|
||||
const setup = plugin.setup(coreMock.createSetup());
|
||||
const setup = plugin.setup(coreMock.createSetup(), {
|
||||
uiActions: uiActionsPluginMock.createSetupContract(),
|
||||
});
|
||||
const doStart = () => plugin.start(coreMock.createStart());
|
||||
return {
|
||||
plugin,
|
||||
|
|
|
@ -17,39 +17,37 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { IUiActionsSetup } from 'src/plugins/ui_actions/public';
|
||||
import {
|
||||
PluginInitializerContext,
|
||||
CoreSetup,
|
||||
CoreStart,
|
||||
Plugin,
|
||||
} from '../../../../../../core/public';
|
||||
import { TriggerRegistry, ActionRegistry, EmbeddableFactoryRegistry } from './types';
|
||||
import { EmbeddableFactoryRegistry } from './types';
|
||||
import { createApi, EmbeddableApi } from './api';
|
||||
import { bootstrap } from './bootstrap';
|
||||
|
||||
export interface IEmbeddableSetupDependencies {
|
||||
uiActions: IUiActionsSetup;
|
||||
}
|
||||
|
||||
export class EmbeddablePublicPlugin implements Plugin<any, any> {
|
||||
private readonly triggers: TriggerRegistry = new Map();
|
||||
private readonly actions: ActionRegistry = new Map();
|
||||
private readonly embeddableFactories: EmbeddableFactoryRegistry = new Map();
|
||||
private api!: EmbeddableApi;
|
||||
|
||||
constructor(initializerContext: PluginInitializerContext) {}
|
||||
|
||||
public setup(core: CoreSetup) {
|
||||
public setup(core: CoreSetup, { uiActions }: IEmbeddableSetupDependencies) {
|
||||
({ api: this.api } = createApi({
|
||||
actions: this.actions,
|
||||
embeddableFactories: this.embeddableFactories,
|
||||
triggers: this.triggers,
|
||||
}));
|
||||
bootstrap(this.api);
|
||||
bootstrap(uiActions);
|
||||
|
||||
const { registerTrigger, registerAction, registerEmbeddableFactory, attachAction } = this.api;
|
||||
const { registerEmbeddableFactory } = this.api;
|
||||
|
||||
return {
|
||||
registerTrigger,
|
||||
registerAction,
|
||||
registerEmbeddableFactory,
|
||||
attachAction,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
import { testPlugin } from './test_plugin';
|
||||
import { ApplyFilterAction, EmbeddableOutput, isErrorEmbeddable } from '../lib';
|
||||
import { EmbeddableOutput, isErrorEmbeddable, createFilterAction } from '../lib';
|
||||
import {
|
||||
FilterableContainer,
|
||||
FilterableContainerInput,
|
||||
|
@ -44,7 +44,7 @@ test('ApplyFilterAction applies the filter to the root of the container tree', a
|
|||
api.registerEmbeddableFactory(factory1.type, factory1);
|
||||
api.registerEmbeddableFactory(factory2.type, factory2);
|
||||
|
||||
const applyFilterAction = new ApplyFilterAction();
|
||||
const applyFilterAction = createFilterAction();
|
||||
|
||||
const root = new FilterableContainer(
|
||||
{ id: 'root', panels: {}, filters: [] },
|
||||
|
@ -97,11 +97,11 @@ test('ApplyFilterAction is incompatible if the root container does not accept a
|
|||
const api = doStart();
|
||||
const inspector = inspectorPluginMock.createStartContract();
|
||||
|
||||
const applyFilterAction = new ApplyFilterAction();
|
||||
const applyFilterAction = createFilterAction();
|
||||
const parent = new HelloWorldContainer(
|
||||
{ id: 'root', panels: {} },
|
||||
{
|
||||
getActions: api.getTriggerCompatibleActions,
|
||||
getActions: () => Promise.resolve([]),
|
||||
getEmbeddableFactory: api.getEmbeddableFactory,
|
||||
getAllEmbeddableFactories: api.getEmbeddableFactories,
|
||||
overlays: coreStart.overlays,
|
||||
|
@ -136,11 +136,11 @@ test('trying to execute on incompatible context throws an error ', async () => {
|
|||
const factory = new FilterableEmbeddableFactory();
|
||||
api.registerEmbeddableFactory(factory.type, factory);
|
||||
|
||||
const applyFilterAction = new ApplyFilterAction();
|
||||
const applyFilterAction = createFilterAction();
|
||||
const parent = new HelloWorldContainer(
|
||||
{ id: 'root', panels: {} },
|
||||
{
|
||||
getActions: api.getTriggerCompatibleActions,
|
||||
getActions: () => Promise.resolve([]),
|
||||
getEmbeddableFactory: api.getEmbeddableFactory,
|
||||
getAllEmbeddableFactories: api.getEmbeddableFactories,
|
||||
overlays: coreStart.overlays,
|
||||
|
@ -161,13 +161,12 @@ test('trying to execute on incompatible context throws an error ', async () => {
|
|||
}
|
||||
|
||||
async function check() {
|
||||
// @ts-ignore
|
||||
await applyFilterAction.execute({ embeddable });
|
||||
await applyFilterAction.execute({ embeddable } as any);
|
||||
}
|
||||
await expect(check()).rejects.toThrow(Error);
|
||||
});
|
||||
|
||||
test('gets title', async () => {
|
||||
const applyFilterAction = new ApplyFilterAction();
|
||||
expect(applyFilterAction.getDisplayName()).toBeDefined();
|
||||
const applyFilterAction = createFilterAction();
|
||||
expect(applyFilterAction.getDisplayName({} as any)).toBeDefined();
|
||||
});
|
||||
|
|
|
@ -53,12 +53,12 @@ async function creatHelloWorldContainerAndEmbeddable(
|
|||
) {
|
||||
const coreSetup = coreMock.createSetup();
|
||||
const coreStart = coreMock.createStart();
|
||||
const { setup, doStart } = testPlugin(coreSetup, coreStart);
|
||||
const { setup, doStart, uiActions } = testPlugin(coreSetup, coreStart);
|
||||
const start = doStart();
|
||||
|
||||
const filterableFactory = new FilterableEmbeddableFactory();
|
||||
const slowContactCardFactory = new SlowContactCardEmbeddableFactory({
|
||||
execAction: start.executeTriggerActions,
|
||||
execAction: uiActions.executeTriggerActions,
|
||||
});
|
||||
const helloWorldFactory = new HelloWorldEmbeddableFactory();
|
||||
|
||||
|
@ -67,7 +67,7 @@ async function creatHelloWorldContainerAndEmbeddable(
|
|||
setup.registerEmbeddableFactory(helloWorldFactory.type, helloWorldFactory);
|
||||
|
||||
const container = new HelloWorldContainer(containerInput, {
|
||||
getActions: start.getTriggerCompatibleActions,
|
||||
getActions: uiActions.getTriggerCompatibleActions,
|
||||
getEmbeddableFactory: start.getEmbeddableFactory,
|
||||
getAllEmbeddableFactories: start.getEmbeddableFactories,
|
||||
overlays: coreStart.overlays,
|
||||
|
@ -85,7 +85,7 @@ async function creatHelloWorldContainerAndEmbeddable(
|
|||
throw new Error('Error adding embeddable');
|
||||
}
|
||||
|
||||
return { container, embeddable, coreSetup, coreStart, setup, start };
|
||||
return { container, embeddable, coreSetup, coreStart, setup, start, uiActions };
|
||||
}
|
||||
|
||||
test('Container initializes embeddables', async done => {
|
||||
|
@ -128,7 +128,7 @@ test('Container.addNewEmbeddable', async () => {
|
|||
});
|
||||
|
||||
test('Container.removeEmbeddable removes and cleans up', async done => {
|
||||
const { start, coreStart } = await creatHelloWorldContainerAndEmbeddable();
|
||||
const { start, coreStart, uiActions } = await creatHelloWorldContainerAndEmbeddable();
|
||||
const container = new HelloWorldContainer(
|
||||
{
|
||||
id: 'hello',
|
||||
|
@ -140,7 +140,7 @@ test('Container.removeEmbeddable removes and cleans up', async done => {
|
|||
},
|
||||
},
|
||||
{
|
||||
getActions: start.getTriggerCompatibleActions,
|
||||
getActions: uiActions.getTriggerCompatibleActions,
|
||||
getEmbeddableFactory: start.getEmbeddableFactory,
|
||||
getAllEmbeddableFactories: start.getEmbeddableFactories,
|
||||
overlays: coreStart.overlays,
|
||||
|
@ -291,7 +291,13 @@ test('Container view mode change propagates to children', async () => {
|
|||
});
|
||||
|
||||
test(`Container updates its state when a child's input is updated`, async done => {
|
||||
const { container, embeddable, start, coreStart } = await creatHelloWorldContainerAndEmbeddable(
|
||||
const {
|
||||
container,
|
||||
embeddable,
|
||||
start,
|
||||
coreStart,
|
||||
uiActions,
|
||||
} = await creatHelloWorldContainerAndEmbeddable(
|
||||
{ id: 'hello', panels: {}, viewMode: ViewMode.VIEW },
|
||||
{
|
||||
id: '123',
|
||||
|
@ -314,7 +320,7 @@ test(`Container updates its state when a child's input is updated`, async done =
|
|||
// with "Dr.", not the default the embeddable was first added with. Makes sure changed input
|
||||
// is preserved with the container.
|
||||
const containerClone = new HelloWorldContainer(container.getInput(), {
|
||||
getActions: start.getTriggerCompatibleActions,
|
||||
getActions: uiActions.getTriggerCompatibleActions,
|
||||
getAllEmbeddableFactories: start.getEmbeddableFactories,
|
||||
getEmbeddableFactory: start.getEmbeddableFactory,
|
||||
notifications: coreStart.notifications,
|
||||
|
@ -554,7 +560,7 @@ test('Panel added to input state', async () => {
|
|||
test('Container changes made directly after adding a new embeddable are propagated', async done => {
|
||||
const coreSetup = coreMock.createSetup();
|
||||
const coreStart = coreMock.createStart();
|
||||
const { doStart } = testPlugin(coreSetup, coreStart);
|
||||
const { doStart, uiActions } = testPlugin(coreSetup, coreStart);
|
||||
const start = doStart();
|
||||
|
||||
const container = new HelloWorldContainer(
|
||||
|
@ -564,7 +570,7 @@ test('Container changes made directly after adding a new embeddable are propagat
|
|||
viewMode: ViewMode.EDIT,
|
||||
},
|
||||
{
|
||||
getActions: start.getTriggerCompatibleActions,
|
||||
getActions: uiActions.getTriggerCompatibleActions,
|
||||
getEmbeddableFactory: start.getEmbeddableFactory,
|
||||
getAllEmbeddableFactories: start.getEmbeddableFactories,
|
||||
overlays: coreStart.overlays,
|
||||
|
@ -576,7 +582,7 @@ test('Container changes made directly after adding a new embeddable are propagat
|
|||
|
||||
const factory = new SlowContactCardEmbeddableFactory({
|
||||
loadTickCount: 3,
|
||||
execAction: start.executeTriggerActions,
|
||||
execAction: uiActions.executeTriggerActions,
|
||||
});
|
||||
start.registerEmbeddableFactory(factory.type, factory);
|
||||
|
||||
|
@ -685,7 +691,10 @@ test('untilEmbeddableLoaded() throws an error if there is no such child panel in
|
|||
});
|
||||
|
||||
test('untilEmbeddableLoaded() throws an error if there is no such child panel in the container - 2', async () => {
|
||||
const { doStart, coreStart } = testPlugin(coreMock.createSetup(), coreMock.createStart());
|
||||
const { doStart, coreStart, uiActions } = testPlugin(
|
||||
coreMock.createSetup(),
|
||||
coreMock.createStart()
|
||||
);
|
||||
const start = doStart();
|
||||
const container = new HelloWorldContainer(
|
||||
{
|
||||
|
@ -693,7 +702,7 @@ test('untilEmbeddableLoaded() throws an error if there is no such child panel in
|
|||
panels: {},
|
||||
},
|
||||
{
|
||||
getActions: start.getTriggerCompatibleActions,
|
||||
getActions: uiActions.getTriggerCompatibleActions,
|
||||
getEmbeddableFactory: start.getEmbeddableFactory,
|
||||
getAllEmbeddableFactories: start.getEmbeddableFactories,
|
||||
overlays: coreStart.overlays,
|
||||
|
@ -709,7 +718,10 @@ test('untilEmbeddableLoaded() throws an error if there is no such child panel in
|
|||
});
|
||||
|
||||
test('untilEmbeddableLoaded() resolves if child is loaded in the container', async done => {
|
||||
const { setup, doStart, coreStart } = testPlugin(coreMock.createSetup(), coreMock.createStart());
|
||||
const { setup, doStart, coreStart, uiActions } = testPlugin(
|
||||
coreMock.createSetup(),
|
||||
coreMock.createStart()
|
||||
);
|
||||
const factory = new HelloWorldEmbeddableFactory();
|
||||
setup.registerEmbeddableFactory(factory.type, factory);
|
||||
const start = doStart();
|
||||
|
@ -724,7 +736,7 @@ test('untilEmbeddableLoaded() resolves if child is loaded in the container', asy
|
|||
},
|
||||
},
|
||||
{
|
||||
getActions: start.getTriggerCompatibleActions,
|
||||
getActions: uiActions.getTriggerCompatibleActions,
|
||||
getEmbeddableFactory: start.getEmbeddableFactory,
|
||||
getAllEmbeddableFactories: start.getEmbeddableFactories,
|
||||
overlays: coreStart.overlays,
|
||||
|
@ -741,11 +753,14 @@ test('untilEmbeddableLoaded() resolves if child is loaded in the container', asy
|
|||
});
|
||||
|
||||
test('untilEmbeddableLoaded rejects with an error if child is subsequently removed', async done => {
|
||||
const { doStart, coreStart } = testPlugin(coreMock.createSetup(), coreMock.createStart());
|
||||
const { doStart, coreStart, uiActions } = testPlugin(
|
||||
coreMock.createSetup(),
|
||||
coreMock.createStart()
|
||||
);
|
||||
const start = doStart();
|
||||
const factory = new SlowContactCardEmbeddableFactory({
|
||||
loadTickCount: 3,
|
||||
execAction: start.executeTriggerActions,
|
||||
execAction: uiActions.executeTriggerActions,
|
||||
});
|
||||
start.registerEmbeddableFactory(factory.type, factory);
|
||||
const container = new HelloWorldContainer(
|
||||
|
@ -759,7 +774,7 @@ test('untilEmbeddableLoaded rejects with an error if child is subsequently remov
|
|||
},
|
||||
},
|
||||
{
|
||||
getActions: start.getTriggerCompatibleActions,
|
||||
getActions: uiActions.getTriggerCompatibleActions,
|
||||
getEmbeddableFactory: start.getEmbeddableFactory,
|
||||
getAllEmbeddableFactories: start.getEmbeddableFactories,
|
||||
overlays: coreStart.overlays,
|
||||
|
@ -778,11 +793,14 @@ test('untilEmbeddableLoaded rejects with an error if child is subsequently remov
|
|||
});
|
||||
|
||||
test('adding a panel then subsequently removing it before its loaded removes the panel', async done => {
|
||||
const { doStart, coreStart } = testPlugin(coreMock.createSetup(), coreMock.createStart());
|
||||
const { doStart, coreStart, uiActions } = testPlugin(
|
||||
coreMock.createSetup(),
|
||||
coreMock.createStart()
|
||||
);
|
||||
const start = doStart();
|
||||
const factory = new SlowContactCardEmbeddableFactory({
|
||||
loadTickCount: 1,
|
||||
execAction: start.executeTriggerActions,
|
||||
execAction: uiActions.executeTriggerActions,
|
||||
});
|
||||
start.registerEmbeddableFactory(factory.type, factory);
|
||||
const container = new HelloWorldContainer(
|
||||
|
@ -796,7 +814,7 @@ test('adding a panel then subsequently removing it before its loaded removes the
|
|||
},
|
||||
},
|
||||
{
|
||||
getActions: start.getTriggerCompatibleActions,
|
||||
getActions: uiActions.getTriggerCompatibleActions,
|
||||
getEmbeddableFactory: start.getEmbeddableFactory,
|
||||
getAllEmbeddableFactories: start.getEmbeddableFactories,
|
||||
overlays: coreStart.overlays,
|
||||
|
|
|
@ -42,12 +42,15 @@ let container: Container;
|
|||
let embeddable: ContactCardEmbeddable;
|
||||
|
||||
beforeEach(async () => {
|
||||
const { doStart, coreStart } = testPlugin(coreMock.createSetup(), coreMock.createStart());
|
||||
const { doStart, coreStart, uiActions } = testPlugin(
|
||||
coreMock.createSetup(),
|
||||
coreMock.createStart()
|
||||
);
|
||||
api = doStart();
|
||||
|
||||
const contactCardFactory = new ContactCardEmbeddableFactory(
|
||||
{},
|
||||
api.executeTriggerActions,
|
||||
uiActions.executeTriggerActions,
|
||||
{} as any
|
||||
);
|
||||
api.registerEmbeddableFactory(contactCardFactory.type, contactCardFactory);
|
||||
|
@ -55,7 +58,7 @@ beforeEach(async () => {
|
|||
container = new HelloWorldContainer(
|
||||
{ id: '123', panels: {} },
|
||||
{
|
||||
getActions: api.getTriggerCompatibleActions,
|
||||
getActions: uiActions.getTriggerCompatibleActions,
|
||||
getEmbeddableFactory: api.getEmbeddableFactory,
|
||||
getAllEmbeddableFactories: api.getEmbeddableFactories,
|
||||
overlays: coreStart.overlays,
|
||||
|
|
|
@ -35,12 +35,16 @@ import { HelloWorldContainer } from '../lib/test_samples/embeddables/hello_world
|
|||
// eslint-disable-next-line
|
||||
import { coreMock } from '../../../../../../../core/public/mocks';
|
||||
|
||||
const { setup, doStart, coreStart } = testPlugin(coreMock.createSetup(), coreMock.createStart());
|
||||
const { setup, doStart, coreStart, uiActions } = testPlugin(
|
||||
coreMock.createSetup(),
|
||||
coreMock.createStart()
|
||||
);
|
||||
const start = doStart();
|
||||
|
||||
setup.registerEmbeddableFactory(FILTERABLE_EMBEDDABLE, new FilterableEmbeddableFactory());
|
||||
const factory = new SlowContactCardEmbeddableFactory({
|
||||
loadTickCount: 2,
|
||||
execAction: start.executeTriggerActions,
|
||||
execAction: uiActions.executeTriggerActions,
|
||||
});
|
||||
setup.registerEmbeddableFactory(CONTACT_CARD_EMBEDDABLE, factory);
|
||||
setup.registerEmbeddableFactory(HELLO_WORLD_EMBEDDABLE_TYPE, new HelloWorldEmbeddableFactory());
|
||||
|
@ -76,7 +80,7 @@ test('Explicit embeddable input mapped to undefined with no inherited value will
|
|||
const container = new HelloWorldContainer(
|
||||
{ id: 'hello', panels: {} },
|
||||
{
|
||||
getActions: start.getTriggerCompatibleActions,
|
||||
getActions: uiActions.getTriggerCompatibleActions,
|
||||
getAllEmbeddableFactories: start.getEmbeddableFactories,
|
||||
getEmbeddableFactory: start.getEmbeddableFactory,
|
||||
notifications: coreStart.notifications,
|
||||
|
@ -124,7 +128,7 @@ test('Explicit input tests in async situations', (done: () => void) => {
|
|||
},
|
||||
},
|
||||
{
|
||||
getActions: start.getTriggerCompatibleActions,
|
||||
getActions: uiActions.getTriggerCompatibleActions,
|
||||
getAllEmbeddableFactories: start.getEmbeddableFactories,
|
||||
getEmbeddableFactory: start.getEmbeddableFactory,
|
||||
notifications: coreStart.notifications,
|
||||
|
|
|
@ -18,6 +18,9 @@
|
|||
*/
|
||||
|
||||
import { CoreSetup, CoreStart } from 'src/core/public';
|
||||
// eslint-disable-next-line
|
||||
import { uiActionsTestPlugin } from 'src/plugins/ui_actions/public/tests';
|
||||
import { IUiActionsApi } from 'src/plugins/ui_actions/public';
|
||||
import { EmbeddablePublicPlugin } from '../plugin';
|
||||
|
||||
export interface TestPluginReturn {
|
||||
|
@ -26,15 +29,17 @@ export interface TestPluginReturn {
|
|||
coreStart: CoreStart;
|
||||
setup: ReturnType<EmbeddablePublicPlugin['setup']>;
|
||||
doStart: (anotherCoreStart?: CoreStart) => ReturnType<EmbeddablePublicPlugin['start']>;
|
||||
uiActions: IUiActionsApi;
|
||||
}
|
||||
|
||||
export const testPlugin = (
|
||||
coreSetup: CoreSetup = {} as CoreSetup,
|
||||
coreStart: CoreStart = {} as CoreStart
|
||||
): TestPluginReturn => {
|
||||
const uiActions = uiActionsTestPlugin(coreSetup, coreStart);
|
||||
const initializerContext = {} as any;
|
||||
const plugin = new EmbeddablePublicPlugin(initializerContext);
|
||||
const setup = plugin.setup(coreSetup);
|
||||
const setup = plugin.setup(coreSetup, { uiActions: uiActions.setup });
|
||||
|
||||
return {
|
||||
plugin,
|
||||
|
@ -45,5 +50,6 @@ export const testPlugin = (
|
|||
const start = plugin.start(anotherCoreStart);
|
||||
return start;
|
||||
},
|
||||
uiActions: uiActions.doStart(coreStart),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -17,10 +17,6 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { Action } from './lib/actions';
|
||||
import { EmbeddableFactory } from './lib/embeddables';
|
||||
import { Trigger } from './lib/types';
|
||||
|
||||
export type TriggerRegistry = Map<string, Trigger>;
|
||||
export type ActionRegistry = Map<string, Action>;
|
||||
export type EmbeddableFactoryRegistry = Map<string, EmbeddableFactory>;
|
||||
|
|
|
@ -37,12 +37,12 @@ import { i18n } from '@kbn/i18n';
|
|||
import { toastNotifications } from 'ui/notify';
|
||||
import { timefilter, getTime } from 'ui/timefilter';
|
||||
import { TimeRange } from 'src/plugins/data/public';
|
||||
import { TExecuteTriggerActions } from 'src/plugins/ui_actions/public';
|
||||
import { Query, onlyDisabledFiltersChanged } from '../../../../data/public';
|
||||
import {
|
||||
APPLY_FILTER_TRIGGER,
|
||||
Embeddable,
|
||||
Container,
|
||||
ExecuteTriggerActions,
|
||||
} from '../../../../embeddable_api/public/np_ready/public';
|
||||
import * as columnActions from '../doc_table/actions/columns';
|
||||
import { SavedSearch } from '../types';
|
||||
|
@ -124,7 +124,7 @@ export class SearchEmbeddable extends Embeddable<SearchInput, SearchOutput>
|
|||
queryFilter,
|
||||
}: SearchEmbeddableConfig,
|
||||
initialInput: SearchInput,
|
||||
private readonly executeTriggerActions: ExecuteTriggerActions,
|
||||
private readonly executeTriggerActions: TExecuteTriggerActions,
|
||||
parent?: Container
|
||||
) {
|
||||
super(
|
||||
|
|
|
@ -19,18 +19,19 @@
|
|||
|
||||
import '../doc_table';
|
||||
import { capabilities } from 'ui/capabilities';
|
||||
import { npStart } from 'ui/new_platform';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import chrome from 'ui/chrome';
|
||||
import { IPrivate } from 'ui/private';
|
||||
import { TimeRange } from 'src/plugins/data/public';
|
||||
import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter';
|
||||
import { TExecuteTriggerActions } from 'src/plugins/ui_actions/public';
|
||||
import {
|
||||
EmbeddableFactory,
|
||||
ErrorEmbeddable,
|
||||
Container,
|
||||
ExecuteTriggerActions,
|
||||
} from '../../../../embeddable_api/public/np_ready/public';
|
||||
import { setup, start } from '../../../../embeddable_api/public/np_ready/public/legacy';
|
||||
import { setup } from '../../../../embeddable_api/public/np_ready/public/legacy';
|
||||
import { SavedSearchLoader } from '../types';
|
||||
import { SearchEmbeddable, SEARCH_EMBEDDABLE_TYPE } from './search_embeddable';
|
||||
import { SearchInput, SearchOutput } from './types';
|
||||
|
@ -42,7 +43,7 @@ export class SearchEmbeddableFactory extends EmbeddableFactory<
|
|||
> {
|
||||
public readonly type = SEARCH_EMBEDDABLE_TYPE;
|
||||
|
||||
constructor(private readonly executeTriggerActions: ExecuteTriggerActions) {
|
||||
constructor(private readonly executeTriggerActions: TExecuteTriggerActions) {
|
||||
super({
|
||||
savedObjectMetaData: {
|
||||
name: i18n.translate('kbn.discover.savedSearch.savedObjectName', {
|
||||
|
@ -110,5 +111,5 @@ export class SearchEmbeddableFactory extends EmbeddableFactory<
|
|||
}
|
||||
}
|
||||
|
||||
const factory = new SearchEmbeddableFactory(start.executeTriggerActions);
|
||||
const factory = new SearchEmbeddableFactory(npStart.plugins.uiActions.executeTriggerActions);
|
||||
setup.registerEmbeddableFactory(factory.type, factory);
|
||||
|
|
|
@ -36,8 +36,6 @@ import {
|
|||
EmbeddableOutput,
|
||||
Embeddable,
|
||||
Container,
|
||||
APPLY_FILTER_TRIGGER,
|
||||
Trigger,
|
||||
} from '../../../../embeddable_api/public/np_ready/public';
|
||||
import { Query, onlyDisabledFiltersChanged } from '../../../../data/public';
|
||||
import { VISUALIZE_EMBEDDABLE_TYPE } from './constants';
|
||||
|
@ -130,10 +128,6 @@ export class VisualizeEmbeddable extends Embeddable<VisualizeInput, VisualizeOut
|
|||
return this.handler.inspectorAdapters;
|
||||
}
|
||||
|
||||
public supportsTrigger(trigger: Trigger) {
|
||||
return trigger.id !== APPLY_FILTER_TRIGGER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfers all changes in the containerState.customization into
|
||||
* the uiState of this visualization.
|
||||
|
|
|
@ -22,6 +22,7 @@ import { coreMock } from '../../../../../core/public/mocks';
|
|||
import { dataPluginMock } from '../../../../../plugins/data/public/mocks';
|
||||
import { expressionsPluginMock } from '../../../../../plugins/expressions/public/mocks';
|
||||
import { inspectorPluginMock } from '../../../../../plugins/inspector/public/mocks';
|
||||
import { uiActionsPluginMock } from '../../../../../plugins/ui_actions/public/mocks';
|
||||
/* eslint-enable @kbn/eslint/no-restricted-paths */
|
||||
|
||||
export const pluginsMock = {
|
||||
|
@ -29,11 +30,13 @@ export const pluginsMock = {
|
|||
data: dataPluginMock.createSetupContract(),
|
||||
inspector: inspectorPluginMock.createSetupContract(),
|
||||
expressions: expressionsPluginMock.createSetupContract(),
|
||||
uiActions: uiActionsPluginMock.createSetupContract(),
|
||||
}),
|
||||
createStart: () => ({
|
||||
data: dataPluginMock.createStartContract(),
|
||||
inspector: inspectorPluginMock.createStartContract(),
|
||||
expressions: expressionsPluginMock.createStartContract(),
|
||||
uiActions: uiActionsPluginMock.createStartContract(),
|
||||
}),
|
||||
};
|
||||
|
||||
|
|
|
@ -39,6 +39,11 @@ export const npSetup = {
|
|||
},
|
||||
},
|
||||
},
|
||||
uiActions: {
|
||||
attachAction: sinon.fake(),
|
||||
registerAction: sinon.fake(),
|
||||
registerTrigger: sinon.fake(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -60,6 +65,16 @@ export const npStart = {
|
|||
close: () => Promise.resolve(undefined),
|
||||
}),
|
||||
},
|
||||
uiActions: {
|
||||
attachAction: sinon.fake(),
|
||||
registerAction: sinon.fake(),
|
||||
registerTrigger: sinon.fake(),
|
||||
detachAction: sinon.fake(),
|
||||
executeTriggerActions: sinon.fake(),
|
||||
getTrigger: sinon.fake(),
|
||||
getTriggerActions: sinon.fake(),
|
||||
getTriggerCompatibleActions: sinon.fake(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { IUiActionsStart, IUiActionsSetup } from 'src/plugins/ui_actions/public';
|
||||
import { LegacyCoreSetup, LegacyCoreStart } from '../../../../core/public';
|
||||
import { Plugin as DataPlugin } from '../../../../plugins/data/public';
|
||||
import { Plugin as ExpressionsPlugin } from '../../../../plugins/expressions/public';
|
||||
|
@ -28,12 +29,14 @@ export interface PluginsSetup {
|
|||
data: ReturnType<DataPlugin['setup']>;
|
||||
expressions: ReturnType<ExpressionsPlugin['setup']>;
|
||||
inspector: InspectorSetup;
|
||||
uiActions: IUiActionsSetup;
|
||||
}
|
||||
|
||||
export interface PluginsStart {
|
||||
data: ReturnType<DataPlugin['start']>;
|
||||
expressions: ReturnType<ExpressionsPlugin['start']>;
|
||||
inspector: InspectorStart;
|
||||
uiActions: IUiActionsStart;
|
||||
}
|
||||
|
||||
export const npSetup = {
|
||||
|
|
10
src/plugins/ui_actions/README.md
Normal file
10
src/plugins/ui_actions/README.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
# UI Actions
|
||||
|
||||
An API for:
|
||||
- creating custom functionality (`actions`)
|
||||
- creating custom user interaction events (`triggers`)
|
||||
- attaching and detaching `actions` to `triggers`.
|
||||
- emitting `trigger` events
|
||||
- executing `actions` attached to a given `trigger`.
|
||||
- exposing a context menu for the user to choose the appropriate action when there are multiple actions attached to a single trigger.
|
||||
|
6
src/plugins/ui_actions/kibana.json
Normal file
6
src/plugins/ui_actions/kibana.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"id": "uiActions",
|
||||
"version": "kibana",
|
||||
"server": false,
|
||||
"ui": true
|
||||
}
|
|
@ -17,21 +17,17 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { SayHelloAction } from '../test_samples/actions/say_hello_action';
|
||||
import { HelloWorldAction } from '../test_samples/actions/hello_world_action';
|
||||
import { EmptyEmbeddable } from '../test_samples/embeddables/empty_embeddable';
|
||||
import { createSayHelloAction } from '../tests/test_samples/say_hello_action';
|
||||
|
||||
test('SayHelloAction is not compatible with not matching embeddables', async () => {
|
||||
const sayHelloAction = new SayHelloAction(() => {});
|
||||
const emptyEmbeddable = new EmptyEmbeddable({ id: '234' });
|
||||
test('SayHelloAction is not compatible with not matching context', async () => {
|
||||
const sayHelloAction = createSayHelloAction((() => {}) as any);
|
||||
|
||||
const isCompatible = await sayHelloAction.isCompatible({ embeddable: emptyEmbeddable as any });
|
||||
const isCompatible = await sayHelloAction.isCompatible({} as any);
|
||||
expect(isCompatible).toBe(false);
|
||||
});
|
||||
|
||||
test('HelloWorldAction inherits isCompatible from base action', async () => {
|
||||
const helloWorldAction = new HelloWorldAction({} as any);
|
||||
const emptyEmbeddable = new EmptyEmbeddable({ id: '234' });
|
||||
const isCompatible = await helloWorldAction.isCompatible({ embeddable: emptyEmbeddable });
|
||||
const helloWorldAction = createSayHelloAction({} as any);
|
||||
const isCompatible = await helloWorldAction.isCompatible({ name: 'Sue' });
|
||||
expect(isCompatible).toBe(true);
|
||||
});
|
|
@ -17,10 +17,20 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { CONTEXT_MENU_TRIGGER } from '../../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public';
|
||||
import { setup } from '../../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public/legacy';
|
||||
import { EditModeAction } from '../../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/test_samples/actions/edit_mode_action';
|
||||
import { IAction } from './i_action';
|
||||
|
||||
const editModeAction = new EditModeAction();
|
||||
setup.registerAction(editModeAction);
|
||||
setup.attachAction(CONTEXT_MENU_TRIGGER, editModeAction.id);
|
||||
export function createAction<ActionContext extends {} = {}>(
|
||||
action: { type: string; execute: IAction<ActionContext>['execute'] } & Partial<
|
||||
IAction<ActionContext>
|
||||
>
|
||||
): IAction<ActionContext> {
|
||||
return {
|
||||
getIconType: () => undefined,
|
||||
order: 0,
|
||||
id: action.type,
|
||||
isCompatible: () => Promise.resolve(true),
|
||||
getDisplayName: () => '',
|
||||
getHref: () => undefined,
|
||||
...action,
|
||||
};
|
||||
}
|
|
@ -17,46 +17,41 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export abstract class Action<ActionContext extends {} = {}> {
|
||||
export interface IAction<ActionContext extends {} = {}> {
|
||||
/**
|
||||
* Determined the order when there is more than one action matched to a trigger.
|
||||
* Higher numbers are displayed first.
|
||||
*/
|
||||
public order: number = 0;
|
||||
order?: number;
|
||||
|
||||
public abstract readonly type: string;
|
||||
constructor(public readonly id: string) {}
|
||||
id: string;
|
||||
|
||||
readonly type: string;
|
||||
|
||||
/**
|
||||
* Optional EUI icon type that can be displayed along with the title.
|
||||
*/
|
||||
public getIconType(context: ActionContext): string | undefined {
|
||||
return undefined;
|
||||
}
|
||||
getIconType(context: ActionContext): string | undefined;
|
||||
|
||||
/**
|
||||
* Returns a title to be displayed to the user.
|
||||
* @param context
|
||||
*/
|
||||
public abstract getDisplayName(context: ActionContext): string;
|
||||
getDisplayName(context: ActionContext): string;
|
||||
|
||||
/**
|
||||
* Returns a promise that resolves to true if this action is compatible given the context,
|
||||
* otherwise resolves to false.
|
||||
*/
|
||||
public async isCompatible(context: ActionContext): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
isCompatible(context: ActionContext): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* If this returns something truthy, this is used in addition to the `execute` method when clicked.
|
||||
*/
|
||||
public getHref(context: ActionContext): string | undefined {
|
||||
return undefined;
|
||||
}
|
||||
getHref?(context: ActionContext): string | undefined;
|
||||
|
||||
/**
|
||||
* Executes the action.
|
||||
*/
|
||||
public abstract async execute(context: ActionContext): Promise<void>;
|
||||
execute(context: ActionContext): Promise<void>;
|
||||
}
|
21
src/plugins/ui_actions/public/actions/index.ts
Normal file
21
src/plugins/ui_actions/public/actions/index.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export { IAction } from './i_action';
|
||||
export { createAction } from './create_action';
|
|
@ -17,11 +17,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { EmbeddableApiPure } from './types';
|
||||
import { IUiActionsApiPure } from '../types';
|
||||
|
||||
export const registerAction: EmbeddableApiPure['registerAction'] = ({ actions }) => action => {
|
||||
export const registerAction: IUiActionsApiPure['registerAction'] = ({ actions }) => action => {
|
||||
if (actions.has(action.id)) {
|
||||
throw new Error(`Action [action.id = ${action.id}] already registered in Embeddables API.`);
|
||||
throw new Error(`Action [action.id = ${action.id}] already registered.`);
|
||||
}
|
||||
|
||||
actions.set(action.id, action);
|
55
src/plugins/ui_actions/public/api.ts
Normal file
55
src/plugins/ui_actions/public/api.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
IUiActionsApi,
|
||||
IUiActionsDependenciesInternal,
|
||||
IUiActionsDependencies,
|
||||
IUiActionsApiPure,
|
||||
} from './types';
|
||||
import { attachAction } from './triggers/attach_action';
|
||||
import { detachAction } from './triggers/detach_action';
|
||||
import { executeTriggerActions } from './triggers/execute_trigger_actions';
|
||||
import { getTrigger } from './triggers/get_trigger';
|
||||
import { getTriggerActions } from './triggers/get_trigger_actions';
|
||||
import { getTriggerCompatibleActions } from './triggers/get_trigger_compatible_actions';
|
||||
import { registerAction } from './actions/register_action';
|
||||
import { registerTrigger } from './triggers/register_trigger';
|
||||
|
||||
export const pureApi: IUiActionsApiPure = {
|
||||
attachAction,
|
||||
detachAction,
|
||||
executeTriggerActions,
|
||||
getTrigger,
|
||||
getTriggerActions,
|
||||
getTriggerCompatibleActions,
|
||||
registerAction,
|
||||
registerTrigger,
|
||||
};
|
||||
|
||||
export const createApi = (deps: IUiActionsDependencies) => {
|
||||
const partialApi: Partial<IUiActionsApi> = {};
|
||||
const depsInternal: IUiActionsDependenciesInternal = { ...deps, api: partialApi };
|
||||
for (const [key, fn] of Object.entries(pureApi)) {
|
||||
(partialApi as any)[key] = fn(depsInternal);
|
||||
}
|
||||
Object.freeze(partialApi);
|
||||
const api = partialApi as IUiActionsApi;
|
||||
return { api, depsInternal };
|
||||
};
|
|
@ -20,7 +20,7 @@
|
|||
import { EuiContextMenuPanelDescriptor, EuiContextMenuPanelItemDescriptor } from '@elastic/eui';
|
||||
import _ from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Action } from '../actions';
|
||||
import { IAction } from '../actions';
|
||||
|
||||
/**
|
||||
* Transforms an array of Actions to the shape EuiContextMenuPanel expects.
|
||||
|
@ -30,7 +30,7 @@ export async function buildContextMenuForActions<A>({
|
|||
actionContext,
|
||||
closeMenu,
|
||||
}: {
|
||||
actions: Array<Action<A>>;
|
||||
actions: Array<IAction<A>>;
|
||||
actionContext: A;
|
||||
closeMenu: () => void;
|
||||
}): Promise<EuiContextMenuPanelDescriptor> {
|
||||
|
@ -42,7 +42,7 @@ export async function buildContextMenuForActions<A>({
|
|||
|
||||
return {
|
||||
id: 'mainMenu',
|
||||
title: i18n.translate('embeddableApi.actionPanel.title', {
|
||||
title: i18n.translate('uiActions.actionPanel.title', {
|
||||
defaultMessage: 'Options',
|
||||
}),
|
||||
items: menuItems,
|
||||
|
@ -57,7 +57,7 @@ async function buildEuiContextMenuPanelItems<A>({
|
|||
actionContext,
|
||||
closeMenu,
|
||||
}: {
|
||||
actions: Array<Action<A>>;
|
||||
actions: Array<IAction<A>>;
|
||||
actionContext: A;
|
||||
closeMenu: () => void;
|
||||
}) {
|
||||
|
@ -93,7 +93,7 @@ function convertPanelActionToContextMenuItem<A>({
|
|||
actionContext,
|
||||
closeMenu,
|
||||
}: {
|
||||
action: Action<A>;
|
||||
action: IAction<A>;
|
||||
actionContext: A;
|
||||
closeMenu: () => void;
|
||||
}): EuiContextMenuPanelItemDescriptor {
|
||||
|
@ -109,7 +109,7 @@ function convertPanelActionToContextMenuItem<A>({
|
|||
closeMenu();
|
||||
};
|
||||
|
||||
if (action.getHref(actionContext)) {
|
||||
if (action.getHref && action.getHref(actionContext)) {
|
||||
menuPanelItem.href = action.getHref(actionContext);
|
||||
}
|
||||
|
37
src/plugins/ui_actions/public/index.ts
Normal file
37
src/plugins/ui_actions/public/index.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { PluginInitializerContext } from '../../../core/public';
|
||||
import { UiActionsPlugin } from './plugin';
|
||||
|
||||
export function plugin(initializerContext: PluginInitializerContext) {
|
||||
return new UiActionsPlugin(initializerContext);
|
||||
}
|
||||
|
||||
export { IUiActionsSetup, IUiActionsStart } from './plugin';
|
||||
export {
|
||||
IAction,
|
||||
ITrigger,
|
||||
IUiActionsApi,
|
||||
TGetActionsCompatibleWithTrigger,
|
||||
TExecuteTriggerActions,
|
||||
} from './types';
|
||||
export { createAction } from './actions';
|
||||
export { buildContextMenuForActions } from './context_menu';
|
||||
export { IncompatibleActionError } from './triggers';
|
73
src/plugins/ui_actions/public/mocks.ts
Normal file
73
src/plugins/ui_actions/public/mocks.ts
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { IUiActionsSetup, IUiActionsStart } from '.';
|
||||
import { plugin as pluginInitializer } from '.';
|
||||
// eslint-disable-next-line
|
||||
import { coreMock } from '../../../core/public/mocks';
|
||||
|
||||
export type Setup = jest.Mocked<IUiActionsSetup>;
|
||||
export type Start = jest.Mocked<IUiActionsStart>;
|
||||
|
||||
const createSetupContract = (): Setup => {
|
||||
const setupContract: Setup = {
|
||||
attachAction: jest.fn(),
|
||||
registerAction: jest.fn(),
|
||||
registerTrigger: jest.fn(),
|
||||
};
|
||||
return setupContract;
|
||||
};
|
||||
|
||||
const createStartContract = (): Start => {
|
||||
const startContract: Start = {
|
||||
attachAction: jest.fn(),
|
||||
registerAction: jest.fn(),
|
||||
registerTrigger: jest.fn(),
|
||||
detachAction: jest.fn(),
|
||||
executeTriggerActions: jest.fn(),
|
||||
getTrigger: jest.fn(),
|
||||
getTriggerActions: jest.fn((id: string) => []),
|
||||
getTriggerCompatibleActions: jest.fn(),
|
||||
};
|
||||
|
||||
return startContract;
|
||||
};
|
||||
|
||||
const createPlugin = async () => {
|
||||
const pluginInitializerContext = coreMock.createPluginInitializerContext();
|
||||
const coreSetup = coreMock.createSetup();
|
||||
const coreStart = coreMock.createStart();
|
||||
const plugin = pluginInitializer(pluginInitializerContext);
|
||||
const setup = await plugin.setup(coreSetup);
|
||||
|
||||
return {
|
||||
pluginInitializerContext,
|
||||
coreSetup,
|
||||
coreStart,
|
||||
plugin,
|
||||
setup,
|
||||
doStart: async () => await plugin.start(coreStart),
|
||||
};
|
||||
};
|
||||
|
||||
export const uiActionsPluginMock = {
|
||||
createSetupContract,
|
||||
createStartContract,
|
||||
createPlugin,
|
||||
};
|
54
src/plugins/ui_actions/public/plugin.ts
Normal file
54
src/plugins/ui_actions/public/plugin.ts
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { CoreStart, PluginInitializerContext, CoreSetup, Plugin } from 'src/core/public';
|
||||
import { IUiActionsApi, IActionRegistry, ITriggerRegistry } from './types';
|
||||
import { createApi } from './api';
|
||||
|
||||
export interface IUiActionsSetup {
|
||||
attachAction: IUiActionsApi['attachAction'];
|
||||
registerAction: IUiActionsApi['registerAction'];
|
||||
registerTrigger: IUiActionsApi['registerTrigger'];
|
||||
}
|
||||
|
||||
export type IUiActionsStart = IUiActionsApi;
|
||||
|
||||
export class UiActionsPlugin implements Plugin<IUiActionsSetup, IUiActionsStart> {
|
||||
private readonly triggers: ITriggerRegistry = new Map();
|
||||
private readonly actions: IActionRegistry = new Map();
|
||||
private api!: IUiActionsApi;
|
||||
|
||||
constructor(initializerContext: PluginInitializerContext) {
|
||||
this.api = createApi({ triggers: this.triggers, actions: this.actions }).api;
|
||||
}
|
||||
|
||||
public setup(core: CoreSetup): IUiActionsSetup {
|
||||
return {
|
||||
registerTrigger: this.api.registerTrigger,
|
||||
registerAction: this.api.registerAction,
|
||||
attachAction: this.api.attachAction,
|
||||
};
|
||||
}
|
||||
|
||||
public start(core: CoreStart): IUiActionsStart {
|
||||
return this.api;
|
||||
}
|
||||
|
||||
public stop() {}
|
||||
}
|
28
src/plugins/ui_actions/public/tests/helpers.ts
Normal file
28
src/plugins/ui_actions/public/tests/helpers.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { IUiActionsDependencies } from '../types';
|
||||
|
||||
export const createDeps = (): IUiActionsDependencies => {
|
||||
const deps: IUiActionsDependencies = {
|
||||
actions: new Map<any, any>(),
|
||||
triggers: new Map<any, any>(),
|
||||
};
|
||||
return deps;
|
||||
};
|
20
src/plugins/ui_actions/public/tests/index.ts
Normal file
20
src/plugins/ui_actions/public/tests/index.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export { uiActionsTestPlugin } from './test_plugin';
|
49
src/plugins/ui_actions/public/tests/test_plugin.ts
Normal file
49
src/plugins/ui_actions/public/tests/test_plugin.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { CoreSetup, CoreStart } from 'src/core/public';
|
||||
import { UiActionsPlugin, IUiActionsSetup, IUiActionsStart } from '../plugin';
|
||||
|
||||
export interface IUiActionsTestPluginReturn {
|
||||
plugin: UiActionsPlugin;
|
||||
coreSetup: CoreSetup;
|
||||
coreStart: CoreStart;
|
||||
setup: IUiActionsSetup;
|
||||
doStart: (anotherCoreStart?: CoreStart) => IUiActionsStart;
|
||||
}
|
||||
|
||||
export const uiActionsTestPlugin = (
|
||||
coreSetup: CoreSetup = {} as CoreSetup,
|
||||
coreStart: CoreStart = {} as CoreStart
|
||||
): IUiActionsTestPluginReturn => {
|
||||
const initializerContext = {} as any;
|
||||
const plugin = new UiActionsPlugin(initializerContext);
|
||||
const setup = plugin.setup(coreSetup);
|
||||
|
||||
return {
|
||||
plugin,
|
||||
coreSetup,
|
||||
coreStart,
|
||||
setup,
|
||||
doStart: (anotherCoreStart: CoreStart = coreStart) => {
|
||||
const start = plugin.start(anotherCoreStart);
|
||||
return start;
|
||||
},
|
||||
};
|
||||
};
|
|
@ -19,30 +19,23 @@
|
|||
|
||||
import React from 'react';
|
||||
import { EuiFlyout } from '@elastic/eui';
|
||||
import { CoreStart } from '../../../../../../../../../core/public';
|
||||
import { Action } from '../..';
|
||||
import { CoreStart } from 'src/core/public';
|
||||
import { createAction, IAction } from '../../actions';
|
||||
|
||||
export const HELLO_WORLD_ACTION_ID = 'HELLO_WORLD_ACTION_ID';
|
||||
|
||||
export class HelloWorldAction extends Action {
|
||||
public readonly type = HELLO_WORLD_ACTION_ID;
|
||||
|
||||
constructor(private readonly overlays: CoreStart['overlays']) {
|
||||
super(HELLO_WORLD_ACTION_ID);
|
||||
}
|
||||
|
||||
public getDisplayName() {
|
||||
return 'Hello World Action!';
|
||||
}
|
||||
|
||||
public async execute() {
|
||||
const flyoutSession = this.overlays.openFlyout(
|
||||
<EuiFlyout ownFocus onClose={() => flyoutSession && flyoutSession.close()}>
|
||||
Hello World, I am a hello world action!
|
||||
</EuiFlyout>,
|
||||
{
|
||||
'data-test-subj': 'helloWorldAction',
|
||||
}
|
||||
);
|
||||
}
|
||||
export function createHelloWorldAction(overlays: CoreStart['overlays']): IAction {
|
||||
return createAction({
|
||||
type: HELLO_WORLD_ACTION_ID,
|
||||
execute: async () => {
|
||||
const flyoutSession = overlays.openFlyout(
|
||||
<EuiFlyout ownFocus onClose={() => flyoutSession && flyoutSession.close()}>
|
||||
Hello World, I am a hello world action!
|
||||
</EuiFlyout>,
|
||||
{
|
||||
'data-test-subj': 'helloWorldAction',
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
21
src/plugins/ui_actions/public/tests/test_samples/index.ts
Normal file
21
src/plugins/ui_actions/public/tests/test_samples/index.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
export { createRestrictedAction } from './restricted_action';
|
||||
export { createSayHelloAction } from './say_hello_action';
|
||||
export { createHelloWorldAction } from './hello_world_action';
|
|
@ -17,26 +17,14 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { Action } from '../../actions';
|
||||
import { IAction, createAction } from '../../actions';
|
||||
|
||||
export const RESTRICTED_ACTION = 'RESTRICTED_ACTION';
|
||||
|
||||
export class RestrictedAction<A> extends Action<A> {
|
||||
public readonly type = RESTRICTED_ACTION;
|
||||
|
||||
private isCompatibleFn: (context: A) => boolean;
|
||||
constructor(isCompatible: (context: A) => boolean) {
|
||||
super(RESTRICTED_ACTION);
|
||||
this.isCompatibleFn = isCompatible;
|
||||
}
|
||||
|
||||
getDisplayName() {
|
||||
return `I am only sometimes compatible`;
|
||||
}
|
||||
|
||||
async isCompatible(context: A) {
|
||||
return this.isCompatibleFn(context);
|
||||
}
|
||||
|
||||
async execute() {}
|
||||
export function createRestrictedAction<C>(isCompatibleIn: (context: C) => boolean): IAction<C> {
|
||||
return createAction<C>({
|
||||
type: RESTRICTED_ACTION,
|
||||
isCompatible: async context => isCompatibleIn(context),
|
||||
execute: async () => {},
|
||||
});
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiFlyout } from '@elastic/eui';
|
||||
import { CoreStart } from 'src/core/public';
|
||||
import { IAction, createAction } from '../../actions';
|
||||
|
||||
export const SAY_HELLO_ACTION = 'SAY_HELLO_ACTION';
|
||||
|
||||
export function createSayHelloAction(overlays: CoreStart['overlays']): IAction<{ name: string }> {
|
||||
return createAction<{ name: string }>({
|
||||
type: SAY_HELLO_ACTION,
|
||||
getDisplayName: ({ name }) => `Hello, ${name}`,
|
||||
isCompatible: async ({ name }) => name !== undefined,
|
||||
execute: async context => {
|
||||
const flyoutSession = overlays.openFlyout(
|
||||
<EuiFlyout ownFocus onClose={() => flyoutSession && flyoutSession.close()}>
|
||||
this.getDisplayName(context)
|
||||
</EuiFlyout>,
|
||||
{
|
||||
'data-test-subj': 'sayHelloAction',
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
|
@ -17,9 +17,9 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { EmbeddableApiPure } from './types';
|
||||
import { IUiActionsApiPure } from '../types';
|
||||
|
||||
export const attachAction: EmbeddableApiPure['attachAction'] = ({ triggers }) => (
|
||||
export const attachAction: IUiActionsApiPure['attachAction'] = ({ triggers }) => (
|
||||
triggerId,
|
||||
actionId
|
||||
) => {
|
|
@ -17,9 +17,9 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { EmbeddableApiPure } from './types';
|
||||
import { IUiActionsApiPure } from '../types';
|
||||
|
||||
export const detachAction: EmbeddableApiPure['detachAction'] = ({ triggers }) => (
|
||||
export const detachAction: IUiActionsApiPure['detachAction'] = ({ triggers }) => (
|
||||
triggerId,
|
||||
actionId
|
||||
) => {
|
|
@ -17,49 +17,33 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { testPlugin, TestPluginReturn } from './test_plugin';
|
||||
import { of } from './helpers';
|
||||
import { Action, openContextMenu, IEmbeddable } from '../lib';
|
||||
import {
|
||||
ContactCardEmbeddable,
|
||||
CONTACT_USER_TRIGGER,
|
||||
} from '../lib/test_samples/embeddables/contact_card/contact_card_embeddable';
|
||||
import { SEND_MESSAGE_ACTION } from '../lib/test_samples/actions/send_message_action';
|
||||
import { IAction, createAction } from '../actions';
|
||||
import { openContextMenu } from '../context_menu';
|
||||
import { IUiActionsTestPluginReturn, uiActionsTestPlugin } from '../tests/test_plugin';
|
||||
|
||||
jest.mock('../lib/context_menu_actions');
|
||||
jest.mock('../context_menu');
|
||||
|
||||
const executeFn = jest.fn();
|
||||
const openContextMenuSpy = (openContextMenu as any) as jest.SpyInstance;
|
||||
|
||||
class TestAction<A> extends Action<A> {
|
||||
public readonly type = 'testAction';
|
||||
public checkCompatibility: (context: A) => boolean;
|
||||
const CONTACT_USER_TRIGGER = 'CONTACT_USER_TRIGGER';
|
||||
|
||||
constructor(id: string, checkCompatibility: (context: A) => boolean) {
|
||||
super(id);
|
||||
this.checkCompatibility = checkCompatibility;
|
||||
}
|
||||
|
||||
public getDisplayName() {
|
||||
return 'test';
|
||||
}
|
||||
|
||||
async isCompatible(context: A) {
|
||||
return this.checkCompatibility(context);
|
||||
}
|
||||
|
||||
async execute(context: unknown) {
|
||||
executeFn(context);
|
||||
}
|
||||
function createTestAction<A>(id: string, checkCompatibility: (context: A) => boolean): IAction<A> {
|
||||
return createAction<A>({
|
||||
type: 'testAction',
|
||||
id,
|
||||
isCompatible: context => Promise.resolve(checkCompatibility(context)),
|
||||
execute: context => executeFn(context),
|
||||
});
|
||||
}
|
||||
|
||||
let embeddables: TestPluginReturn;
|
||||
let uiActions: IUiActionsTestPluginReturn;
|
||||
const reset = () => {
|
||||
embeddables = testPlugin();
|
||||
uiActions = uiActionsTestPlugin();
|
||||
|
||||
embeddables.setup.registerTrigger({
|
||||
uiActions.setup.registerTrigger({
|
||||
id: CONTACT_USER_TRIGGER,
|
||||
actionIds: [SEND_MESSAGE_ACTION],
|
||||
actionIds: ['SEND_MESSAGE_ACTION'],
|
||||
});
|
||||
|
||||
executeFn.mockReset();
|
||||
|
@ -68,23 +52,17 @@ const reset = () => {
|
|||
beforeEach(reset);
|
||||
|
||||
test('executes a single action mapped to a trigger', async () => {
|
||||
const { setup, doStart } = embeddables;
|
||||
const { setup, doStart } = uiActions;
|
||||
const trigger = {
|
||||
id: 'MY-TRIGGER',
|
||||
title: 'My trigger',
|
||||
actionIds: ['test1'],
|
||||
};
|
||||
const action = new TestAction('test1', () => true);
|
||||
const action = createTestAction('test1', () => true);
|
||||
setup.registerTrigger(trigger);
|
||||
setup.registerAction(action);
|
||||
|
||||
const context = {
|
||||
embeddable: new ContactCardEmbeddable(
|
||||
{ id: '123', firstName: 'Stacey', lastName: 'G' },
|
||||
{ execAction: (() => null) as any }
|
||||
),
|
||||
triggerContext: {},
|
||||
};
|
||||
const context = {};
|
||||
const start = doStart();
|
||||
await start.executeTriggerActions('MY-TRIGGER', context);
|
||||
|
||||
|
@ -93,7 +71,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 } = embeddables;
|
||||
const { setup, doStart } = uiActions;
|
||||
const trigger = {
|
||||
id: 'MY-TRIGGER',
|
||||
title: 'My trigger',
|
||||
|
@ -101,48 +79,27 @@ test('throws an error if there are no compatible actions to execute', async () =
|
|||
};
|
||||
setup.registerTrigger(trigger);
|
||||
|
||||
const context = {
|
||||
embeddable: new ContactCardEmbeddable(
|
||||
{ id: '123', firstName: 'Stacey', lastName: 'G' },
|
||||
{ execAction: (() => null) as any }
|
||||
),
|
||||
triggerContext: {},
|
||||
};
|
||||
const context = {};
|
||||
const start = doStart();
|
||||
const [, error] = await of(start.executeTriggerActions('MY-TRIGGER', context));
|
||||
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect(error.message).toMatchInlineSnapshot(
|
||||
`"No compatible actions found to execute for trigger [triggerId = MY-TRIGGER]."`
|
||||
await expect(start.executeTriggerActions('MY-TRIGGER', context)).rejects.toMatchObject(
|
||||
new Error('No compatible actions found to execute for trigger [triggerId = MY-TRIGGER].')
|
||||
);
|
||||
});
|
||||
|
||||
test('does not execute an incompatible action', async () => {
|
||||
const { setup, doStart } = embeddables;
|
||||
const { setup, doStart } = uiActions;
|
||||
const trigger = {
|
||||
id: 'MY-TRIGGER',
|
||||
title: 'My trigger',
|
||||
actionIds: ['test1'],
|
||||
};
|
||||
const action = new TestAction<{ embeddable: IEmbeddable }>(
|
||||
'test1',
|
||||
({ embeddable }) => embeddable.id === 'executeme'
|
||||
);
|
||||
const embeddable = new ContactCardEmbeddable(
|
||||
{
|
||||
id: 'executeme',
|
||||
firstName: 'Stacey',
|
||||
lastName: 'G',
|
||||
},
|
||||
{} as any
|
||||
);
|
||||
const action = createTestAction<{ name: string }>('test1', ({ name }) => name === 'executeme');
|
||||
setup.registerTrigger(trigger);
|
||||
setup.registerAction(action);
|
||||
|
||||
const start = doStart();
|
||||
const context = {
|
||||
embeddable,
|
||||
triggerContext: {},
|
||||
name: 'executeme',
|
||||
};
|
||||
await start.executeTriggerActions('MY-TRIGGER', context);
|
||||
|
||||
|
@ -150,22 +107,14 @@ 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 } = embeddables;
|
||||
const { setup, doStart } = uiActions;
|
||||
const trigger = {
|
||||
id: 'MY-TRIGGER',
|
||||
title: 'My trigger',
|
||||
actionIds: ['test1', 'test2'],
|
||||
};
|
||||
const action1 = new TestAction('test1', () => true);
|
||||
const action2 = new TestAction('test2', () => true);
|
||||
const embeddable = new ContactCardEmbeddable(
|
||||
{
|
||||
id: 'executeme',
|
||||
firstName: 'Stacey',
|
||||
lastName: 'G',
|
||||
},
|
||||
{} as any
|
||||
);
|
||||
const action1 = createTestAction('test1', () => true);
|
||||
const action2 = createTestAction('test2', () => true);
|
||||
setup.registerTrigger(trigger);
|
||||
setup.registerAction(action1);
|
||||
setup.registerAction(action2);
|
||||
|
@ -173,10 +122,7 @@ test('shows a context menu when more than one action is mapped to a trigger', as
|
|||
expect(openContextMenu).toHaveBeenCalledTimes(0);
|
||||
|
||||
const start = doStart();
|
||||
const context = {
|
||||
embeddable,
|
||||
triggerContext: {},
|
||||
};
|
||||
const context = {};
|
||||
await start.executeTriggerActions('MY-TRIGGER', context);
|
||||
|
||||
expect(executeFn).toBeCalledTimes(0);
|
||||
|
@ -184,16 +130,14 @@ 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 } = embeddables;
|
||||
const { setup, doStart } = uiActions;
|
||||
const trigger = {
|
||||
id: 'MY-TRIGGER',
|
||||
title: 'My trigger',
|
||||
actionIds: ['test'],
|
||||
};
|
||||
const action = new TestAction<{ triggerContext: any }>('test', ({ triggerContext }) => {
|
||||
expect(triggerContext).toEqual({
|
||||
foo: 'bar',
|
||||
});
|
||||
const action = createTestAction<{ foo: string }>('test', ({ foo }) => {
|
||||
expect(foo).toEqual('bar');
|
||||
return true;
|
||||
});
|
||||
|
||||
|
@ -201,11 +145,6 @@ test('passes whole action context to isCompatible()', async () => {
|
|||
setup.registerAction(action);
|
||||
const start = doStart();
|
||||
|
||||
const context = {
|
||||
embeddable: {} as any,
|
||||
triggerContext: {
|
||||
foo: 'bar',
|
||||
},
|
||||
};
|
||||
const context = { foo: 'bar' };
|
||||
await start.executeTriggerActions('MY-TRIGGER', context);
|
||||
});
|
|
@ -17,11 +17,12 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { EmbeddableApiPure } from './types';
|
||||
import { Action, buildContextMenuForActions, openContextMenu } from '../lib';
|
||||
import { IUiActionsApiPure } from '../types';
|
||||
import { buildContextMenuForActions, openContextMenu } from '../context_menu';
|
||||
import { IAction } from '../actions';
|
||||
|
||||
const executeSingleAction = async <A extends {} = {}>(action: Action<A>, actionContext: A) => {
|
||||
const href = action.getHref(actionContext);
|
||||
const executeSingleAction = async <A extends {} = {}>(action: IAction<A>, actionContext: A) => {
|
||||
const href = action.getHref && action.getHref(actionContext);
|
||||
|
||||
// TODO: Do we need a `getHref()` special case?
|
||||
if (href) {
|
||||
|
@ -32,7 +33,7 @@ const executeSingleAction = async <A extends {} = {}>(action: Action<A>, actionC
|
|||
await action.execute(actionContext);
|
||||
};
|
||||
|
||||
export const executeTriggerActions: EmbeddableApiPure['executeTriggerActions'] = ({
|
||||
export const executeTriggerActions: IUiActionsApiPure['executeTriggerActions'] = ({
|
||||
api,
|
||||
}) => async (triggerId, actionContext) => {
|
||||
const actions = await api.getTriggerCompatibleActions!(triggerId, actionContext);
|
|
@ -17,9 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { createApi } from '..';
|
||||
import { createDeps } from './helpers';
|
||||
import { expectError } from '../../tests/helpers';
|
||||
import { createApi } from '../api';
|
||||
import { createDeps } from '../tests/helpers';
|
||||
|
||||
test('can get Trigger from registry', () => {
|
||||
const deps = createDeps();
|
||||
|
@ -45,8 +44,5 @@ test('throws if trigger does not exist', () => {
|
|||
const deps = createDeps();
|
||||
const { api } = createApi(deps);
|
||||
|
||||
const error = expectError(() => api.getTrigger('foo'));
|
||||
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect(error.message).toMatchInlineSnapshot(`"Trigger [triggerId = foo] does not exist."`);
|
||||
expect(() => api.getTrigger('foo')).toThrowError('Trigger [triggerId = foo] does not exist.');
|
||||
});
|
|
@ -17,9 +17,9 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { EmbeddableApiPure } from './types';
|
||||
import { IUiActionsApiPure } from '../types';
|
||||
|
||||
export const getTrigger: EmbeddableApiPure['getTrigger'] = ({ triggers }) => id => {
|
||||
export const getTrigger: IUiActionsApiPure['getTrigger'] = ({ triggers }) => id => {
|
||||
const trigger = triggers.get(id);
|
||||
|
||||
if (!trigger) {
|
|
@ -17,22 +17,22 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { testPlugin } from './test_plugin';
|
||||
import { Action } from '../lib';
|
||||
import { IAction } from '../actions';
|
||||
import { uiActionsTestPlugin } from '../tests/test_plugin';
|
||||
|
||||
const action1 = ({
|
||||
const action1: IAction = {
|
||||
id: 'action1',
|
||||
order: 1,
|
||||
type: 'type1',
|
||||
} as any) as Action;
|
||||
const action2 = ({
|
||||
} as any;
|
||||
const action2: IAction = {
|
||||
id: 'action2',
|
||||
order: 2,
|
||||
type: 'type2',
|
||||
} as any) as Action;
|
||||
} as any;
|
||||
|
||||
test('returns actions set on trigger', () => {
|
||||
const { setup, doStart } = testPlugin();
|
||||
const { setup, doStart } = uiActionsTestPlugin();
|
||||
setup.registerAction(action1);
|
||||
setup.registerAction(action2);
|
||||
setup.registerTrigger({
|
|
@ -17,13 +17,13 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { EmbeddableApiPure } from './types';
|
||||
import { Action } from '../lib';
|
||||
import { IUiActionsApiPure } from '../types';
|
||||
import { IAction } from '../actions';
|
||||
|
||||
export const getTriggerActions: EmbeddableApiPure['getTriggerActions'] = ({
|
||||
export const getTriggerActions: IUiActionsApiPure['getTriggerActions'] = ({
|
||||
api,
|
||||
actions,
|
||||
}) => id => {
|
||||
const trigger = api.getTrigger!(id);
|
||||
return trigger.actionIds.map(actionId => actions.get(actionId)).filter(Boolean) as Action[];
|
||||
return trigger.actionIds.map(actionId => actions.get(actionId)).filter(Boolean) as IAction[];
|
||||
};
|
|
@ -17,27 +17,29 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { testPlugin, TestPluginReturn } from './test_plugin';
|
||||
import { HelloWorldAction } from '../lib/test_samples/actions/hello_world_action';
|
||||
import { SayHelloAction } from '../lib/test_samples/actions/say_hello_action';
|
||||
import { RestrictedAction } from '../lib/test_samples/actions/restricted_action';
|
||||
import { EmptyEmbeddable } from '../lib/test_samples/embeddables/empty_embeddable';
|
||||
import { CONTEXT_MENU_TRIGGER, IEmbeddable } from '../lib';
|
||||
import { of } from './helpers';
|
||||
import { createSayHelloAction } from '../tests/test_samples/say_hello_action';
|
||||
import { IUiActionsTestPluginReturn, uiActionsTestPlugin } from '../tests/test_plugin';
|
||||
import { createRestrictedAction, createHelloWorldAction } from '../tests/test_samples';
|
||||
import { IAction } from '../actions';
|
||||
|
||||
let action: SayHelloAction;
|
||||
let embeddables: TestPluginReturn;
|
||||
let action: IAction<{ name: string }>;
|
||||
let uiActions: IUiActionsTestPluginReturn;
|
||||
beforeEach(() => {
|
||||
embeddables = testPlugin();
|
||||
action = new SayHelloAction(() => {});
|
||||
uiActions = uiActionsTestPlugin();
|
||||
action = createSayHelloAction({} as any);
|
||||
|
||||
embeddables.setup.registerAction(action);
|
||||
embeddables.setup.attachAction(CONTEXT_MENU_TRIGGER, action.id);
|
||||
uiActions.setup.registerAction(action);
|
||||
uiActions.setup.registerTrigger({
|
||||
id: 'trigger',
|
||||
title: 'trigger',
|
||||
actionIds: [],
|
||||
});
|
||||
uiActions.setup.attachAction('trigger', action.id);
|
||||
});
|
||||
|
||||
test('can register and get actions', async () => {
|
||||
const { setup, plugin } = embeddables;
|
||||
const helloWorldAction = new HelloWorldAction({} as any);
|
||||
const { setup, plugin } = uiActions;
|
||||
const helloWorldAction = createHelloWorldAction({} as any);
|
||||
const length = (plugin as any).actions.size;
|
||||
|
||||
setup.registerAction(helloWorldAction);
|
||||
|
@ -48,9 +50,8 @@ test('can register and get actions', async () => {
|
|||
});
|
||||
|
||||
test('getTriggerCompatibleActions returns attached actions', async () => {
|
||||
const { setup, doStart } = embeddables;
|
||||
const embeddable = new EmptyEmbeddable({ id: '123' });
|
||||
const helloWorldAction = new HelloWorldAction({} as any);
|
||||
const { setup, doStart } = uiActions;
|
||||
const helloWorldAction = createHelloWorldAction({} as any);
|
||||
|
||||
setup.registerAction(helloWorldAction);
|
||||
|
||||
|
@ -63,25 +64,20 @@ test('getTriggerCompatibleActions returns attached actions', async () => {
|
|||
setup.attachAction('MY-TRIGGER', helloWorldAction.id);
|
||||
|
||||
const start = doStart();
|
||||
const actions = await start.getTriggerCompatibleActions('MY-TRIGGER', {
|
||||
embeddable,
|
||||
});
|
||||
const actions = await start.getTriggerCompatibleActions('MY-TRIGGER', {});
|
||||
|
||||
expect(actions.length).toBe(1);
|
||||
expect(actions[0].id).toBe(helloWorldAction.id);
|
||||
});
|
||||
|
||||
test('filters out actions not applicable based on the context', async () => {
|
||||
const { setup, doStart } = embeddables;
|
||||
const restrictedAction = new RestrictedAction<{ embeddable: IEmbeddable }>(context => {
|
||||
return context.embeddable.id === 'accept';
|
||||
const { setup, doStart } = uiActions;
|
||||
const restrictedAction = createRestrictedAction<{ accept: boolean }>(context => {
|
||||
return context.accept;
|
||||
});
|
||||
|
||||
setup.registerAction(restrictedAction);
|
||||
|
||||
const acceptEmbeddable = new EmptyEmbeddable({ id: 'accept' });
|
||||
const rejectEmbeddable = new EmptyEmbeddable({ id: 'reject' });
|
||||
|
||||
const testTrigger = {
|
||||
id: 'MY-TRIGGER',
|
||||
title: 'My trigger',
|
||||
|
@ -91,36 +87,26 @@ test('filters out actions not applicable based on the context', async () => {
|
|||
setup.registerTrigger(testTrigger);
|
||||
|
||||
const start = doStart();
|
||||
let actions = await start.getTriggerCompatibleActions(testTrigger.id, {
|
||||
embeddable: acceptEmbeddable,
|
||||
});
|
||||
let actions = await start.getTriggerCompatibleActions(testTrigger.id, { accept: true });
|
||||
|
||||
expect(actions.length).toBe(1);
|
||||
|
||||
actions = await start.getTriggerCompatibleActions(testTrigger.id, {
|
||||
embeddable: rejectEmbeddable,
|
||||
});
|
||||
actions = await start.getTriggerCompatibleActions(testTrigger.id, { accept: false });
|
||||
|
||||
expect(actions.length).toBe(0);
|
||||
});
|
||||
|
||||
test(`throws an error with an invalid trigger ID`, async () => {
|
||||
const { doStart } = embeddables;
|
||||
const { doStart } = uiActions;
|
||||
const start = doStart();
|
||||
const [, error] = await of(
|
||||
start.getTriggerCompatibleActions('I do not exist', {
|
||||
embeddable: new EmptyEmbeddable({ id: 'empty' }),
|
||||
})
|
||||
);
|
||||
|
||||
await expect(error).toBeInstanceOf(Error);
|
||||
await expect(error.message).toMatchInlineSnapshot(
|
||||
`"Trigger [triggerId = I do not exist] does not exist."`
|
||||
await expect(start.getTriggerCompatibleActions('I do not exist', {})).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 } = embeddables;
|
||||
const { setup, doStart } = uiActions;
|
||||
const testTrigger = {
|
||||
id: '123',
|
||||
title: '123',
|
||||
|
@ -129,9 +115,7 @@ test(`with a trigger mapping that maps to an non-existing action returns empty l
|
|||
setup.registerTrigger(testTrigger);
|
||||
|
||||
const start = doStart();
|
||||
const actions = await start.getTriggerCompatibleActions(testTrigger.id, {
|
||||
embeddable: new EmptyEmbeddable({ id: 'empty' }),
|
||||
});
|
||||
const actions = await start.getTriggerCompatibleActions(testTrigger.id, {});
|
||||
|
||||
expect(actions).toEqual([]);
|
||||
});
|
|
@ -17,15 +17,15 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { EmbeddableApiPure } from './types';
|
||||
import { Action } from '../lib';
|
||||
import { IUiActionsApiPure } from '../types';
|
||||
import { IAction } from '../actions/i_action';
|
||||
|
||||
export const getTriggerCompatibleActions: EmbeddableApiPure['getTriggerCompatibleActions'] = ({
|
||||
export const getTriggerCompatibleActions: IUiActionsApiPure['getTriggerCompatibleActions'] = ({
|
||||
api,
|
||||
}) => async (triggerId, context) => {
|
||||
const actions = api.getTriggerActions!(triggerId);
|
||||
const isCompatibles = await Promise.all(actions.map(action => action.isCompatible(context)));
|
||||
return actions.reduce<Action[]>(
|
||||
return actions.reduce<IAction[]>(
|
||||
(acc, action, i) => (isCompatibles[i] ? [...acc, action] : acc),
|
||||
[]
|
||||
);
|
25
src/plugins/ui_actions/public/triggers/i_trigger.ts
Normal file
25
src/plugins/ui_actions/public/triggers/i_trigger.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export interface ITrigger {
|
||||
id: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
actionIds: string[];
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export class IncompatibleActionError extends Error {
|
||||
code = 'INCOMPATIBLE_ACTION';
|
||||
|
||||
constructor() {
|
||||
super(
|
||||
i18n.translate('uiActions.errors.incompatibleAction', {
|
||||
defaultMessage: 'Action is incompatible',
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
20
src/plugins/ui_actions/public/triggers/index.ts
Normal file
20
src/plugins/ui_actions/public/triggers/index.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export { IncompatibleActionError } from './incompatible_action_error';
|
|
@ -17,11 +17,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { EmbeddableApiPure } from './types';
|
||||
import { IUiActionsApiPure } from '../types';
|
||||
|
||||
export const registerTrigger: EmbeddableApiPure['registerTrigger'] = ({ triggers }) => trigger => {
|
||||
export const registerTrigger: IUiActionsApiPure['registerTrigger'] = ({ triggers }) => trigger => {
|
||||
if (triggers.has(trigger.id)) {
|
||||
throw new Error(`Trigger [trigger.id = ${trigger.id}] already registered in Embeddables API.`);
|
||||
throw new Error(`Trigger [trigger.id = ${trigger.id}] already registered.`);
|
||||
}
|
||||
|
||||
triggers.set(trigger.id, trigger);
|
149
src/plugins/ui_actions/public/triggers/registry.test.ts
Normal file
149
src/plugins/ui_actions/public/triggers/registry.test.ts
Normal file
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { createApi } from '../api';
|
||||
import { createDeps } from '../tests/helpers';
|
||||
|
||||
const HELLO_WORLD_ACTION_ID = 'HELLO_WORLD_ACTION_ID';
|
||||
|
||||
test('can register trigger', () => {
|
||||
const deps = createDeps();
|
||||
const { api } = createApi(deps);
|
||||
|
||||
api.registerTrigger({
|
||||
actionIds: [],
|
||||
description: 'foo',
|
||||
id: 'bar',
|
||||
title: 'baz',
|
||||
});
|
||||
|
||||
expect(deps.triggers.get('bar')).toEqual({
|
||||
actionIds: [],
|
||||
description: 'foo',
|
||||
id: 'bar',
|
||||
title: 'baz',
|
||||
});
|
||||
});
|
||||
|
||||
test('can register action', () => {
|
||||
const deps = createDeps();
|
||||
const { api } = createApi(deps);
|
||||
|
||||
api.registerAction({
|
||||
id: HELLO_WORLD_ACTION_ID,
|
||||
order: 13,
|
||||
} as any);
|
||||
|
||||
expect(deps.actions.get(HELLO_WORLD_ACTION_ID)).toMatchObject({
|
||||
id: HELLO_WORLD_ACTION_ID,
|
||||
order: 13,
|
||||
});
|
||||
});
|
||||
|
||||
test('can attach an action to a trigger', () => {
|
||||
const deps = createDeps();
|
||||
const { api } = createApi(deps);
|
||||
const trigger = {
|
||||
id: 'MY-TRIGGER',
|
||||
actionIds: [],
|
||||
};
|
||||
const action = {
|
||||
id: HELLO_WORLD_ACTION_ID,
|
||||
order: 25,
|
||||
} as any;
|
||||
|
||||
expect(trigger.actionIds).toEqual([]);
|
||||
|
||||
api.registerTrigger(trigger);
|
||||
api.registerAction(action);
|
||||
api.attachAction('MY-TRIGGER', HELLO_WORLD_ACTION_ID);
|
||||
|
||||
expect(trigger.actionIds).toEqual([HELLO_WORLD_ACTION_ID]);
|
||||
});
|
||||
|
||||
test('can detach an action to a trigger', () => {
|
||||
const deps = createDeps();
|
||||
const { api } = createApi(deps);
|
||||
const trigger = {
|
||||
id: 'MY-TRIGGER',
|
||||
actionIds: [],
|
||||
};
|
||||
const action = {
|
||||
id: HELLO_WORLD_ACTION_ID,
|
||||
order: 25,
|
||||
} as any;
|
||||
|
||||
expect(trigger.actionIds).toEqual([]);
|
||||
|
||||
api.registerTrigger(trigger);
|
||||
api.registerAction(action);
|
||||
api.attachAction('MY-TRIGGER', HELLO_WORLD_ACTION_ID);
|
||||
api.detachAction('MY-TRIGGER', HELLO_WORLD_ACTION_ID);
|
||||
|
||||
expect(trigger.actionIds).toEqual([]);
|
||||
});
|
||||
|
||||
test('detaching an invalid action from a trigger throws an error', async () => {
|
||||
const { api } = createApi({ actions: new Map<any, any>(), triggers: new Map<any, any>() });
|
||||
const action = {
|
||||
id: HELLO_WORLD_ACTION_ID,
|
||||
order: 25,
|
||||
} as any;
|
||||
|
||||
api.registerAction(action);
|
||||
expect(() => api.detachAction('i do not exist', HELLO_WORLD_ACTION_ID)).toThrowError(
|
||||
'No trigger [triggerId = i do not exist] exists, for detaching action [actionId = HELLO_WORLD_ACTION_ID].'
|
||||
);
|
||||
});
|
||||
|
||||
test('attaching an invalid action to a trigger throws an error', async () => {
|
||||
const { api } = createApi({ actions: new Map<any, any>(), triggers: new Map<any, any>() });
|
||||
const action = {
|
||||
id: HELLO_WORLD_ACTION_ID,
|
||||
order: 25,
|
||||
} as any;
|
||||
|
||||
api.registerAction(action);
|
||||
expect(() => api.attachAction('i do not exist', HELLO_WORLD_ACTION_ID)).toThrowError(
|
||||
'No trigger [triggerId = i do not exist] exists, for attaching action [actionId = HELLO_WORLD_ACTION_ID].'
|
||||
);
|
||||
});
|
||||
|
||||
test('cannot register another action with the same ID', async () => {
|
||||
const { api } = createApi({ actions: new Map<any, any>(), triggers: new Map<any, any>() });
|
||||
const action = {
|
||||
id: HELLO_WORLD_ACTION_ID,
|
||||
order: 25,
|
||||
} as any;
|
||||
|
||||
api.registerAction(action);
|
||||
expect(() => api.registerAction(action)).toThrowError(
|
||||
'Action [action.id = HELLO_WORLD_ACTION_ID] already registered.'
|
||||
);
|
||||
});
|
||||
|
||||
test('cannot register another trigger with the same ID', async () => {
|
||||
const { api } = createApi({ actions: new Map<any, any>(), triggers: new Map<any, any>() });
|
||||
const trigger = { id: 'MY-TRIGGER' } as any;
|
||||
|
||||
api.registerTrigger(trigger);
|
||||
expect(() => api.registerTrigger(trigger)).toThrowError(
|
||||
'Trigger [trigger.id = MY-TRIGGER] already registered.'
|
||||
);
|
||||
});
|
58
src/plugins/ui_actions/public/types.ts
Normal file
58
src/plugins/ui_actions/public/types.ts
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { IAction } from './actions/i_action';
|
||||
import { ITrigger } from './triggers/i_trigger';
|
||||
|
||||
export { IAction } from './actions';
|
||||
export { ITrigger } from './triggers/i_trigger';
|
||||
|
||||
export type TExecuteTriggerActions = <A>(triggerId: string, actionContext: A) => Promise<void>;
|
||||
|
||||
export type TGetActionsCompatibleWithTrigger = <C>(
|
||||
triggerId: string,
|
||||
context: C
|
||||
) => Promise<IAction[]>;
|
||||
|
||||
export interface IUiActionsApi {
|
||||
attachAction: (triggerId: string, actionId: string) => void;
|
||||
detachAction: (triggerId: string, actionId: string) => void;
|
||||
executeTriggerActions: TExecuteTriggerActions;
|
||||
getTrigger: (id: string) => ITrigger;
|
||||
getTriggerActions: (id: string) => IAction[];
|
||||
getTriggerCompatibleActions: <C>(triggerId: string, context: C) => Promise<Array<IAction<C>>>;
|
||||
registerAction: (action: IAction) => void;
|
||||
registerTrigger: (trigger: ITrigger) => void;
|
||||
}
|
||||
|
||||
export interface IUiActionsDependencies {
|
||||
actions: IActionRegistry;
|
||||
triggers: ITriggerRegistry;
|
||||
}
|
||||
|
||||
export interface IUiActionsDependenciesInternal extends IUiActionsDependencies {
|
||||
api: Readonly<Partial<IUiActionsApi>>;
|
||||
}
|
||||
|
||||
export type IUiActionsApiPure = {
|
||||
[K in keyof IUiActionsApi]: (deps: IUiActionsDependenciesInternal) => IUiActionsApi[K];
|
||||
};
|
||||
|
||||
export type ITriggerRegistry = Map<string, ITrigger>;
|
||||
export type IActionRegistry = Map<string, IAction>;
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue