Skip to content

Commit

Permalink
Drilldowns various 4 (#60264)
Browse files Browse the repository at this point in the history
* feat: 🎸 hide "Create drilldown" from context menu when needed

* style: πŸ’„ remove AnyDrilldown type

* feat: 🎸 add drilldown factory context

* chore: πŸ€– remove sample drilldown

* fix: πŸ› increase spacing between action factory picker
  • Loading branch information
streamich authored Mar 16, 2020
1 parent e66513c commit 6efef2b
Show file tree
Hide file tree
Showing 9 changed files with 177 additions and 77 deletions.
9 changes: 5 additions & 4 deletions src/plugins/ui_actions/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,15 @@ export { UiActionsSetup, UiActionsStart } from './plugin';
export { UiActionsServiceParams, UiActionsService } from './service';
export {
Action,
createAction,
IncompatibleActionError,
ActionDefinition as UiActionsActionDefinition,
ActionFactoryDefinition as UiActionsActionFactoryDefinition,
ActionInternal as UiActionsActionInternal,
ActionStorage as UiActionsActionStorage,
SerializedEvent as UiActionsSerializedEvent,
SerializedAction as UiActionsSerializedAction,
createAction,
DynamicActionManager,
IncompatibleActionError,
SerializedAction as UiActionsSerializedAction,
SerializedEvent as UiActionsSerializedEvent,
} from './actions';
export { buildContextMenuForActions, contextMenuSeparatorAction } from './context_menu';
export {
Expand Down
12 changes: 9 additions & 3 deletions src/plugins/ui_actions/public/service/ui_actions_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,14 +273,20 @@ export class UiActionsService {
* Register an action factory. Action factories are used to configure and
* serialize/deserialize dynamic actions.
*/
public readonly registerActionFactory = (definition: ActionFactoryDefinition) => {
public readonly registerActionFactory = <
Config extends object = object,
FactoryContext extends object = object,
ActionContext extends object = object
>(
definition: ActionFactoryDefinition<Config, FactoryContext, ActionContext>
) => {
if (this.actionFactories.has(definition.id)) {
throw new Error(`ActionFactory [actionFactory.id = ${definition.id}] already registered.`);
}

const actionFactory = new ActionFactory(definition);
const actionFactory = new ActionFactory<Config, FactoryContext, ActionContext>(definition);

this.actionFactories.set(actionFactory.id, actionFactory);
this.actionFactories.set(actionFactory.id, actionFactory as ActionFactory<any, any, any>);
};

public readonly getActionFactory = (actionFactoryId: string): ActionFactory => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,21 +172,23 @@ const ActionFactorySelector: React.FC<ActionFactorySelectorProps> = ({
}

return (
<EuiFlexGroup wrap>
<EuiFlexGroup gutterSize="m">
{[...actionFactories]
.sort((f1, f2) => f1.order - f2.order)
.map(actionFactory => (
<EuiKeyPadMenuItemButton
className="auaActionWizard__actionFactoryItem"
key={actionFactory.id}
label={actionFactory.getDisplayName(context)}
data-test-subj={TEST_SUBJ_ACTION_FACTORY_ITEM}
onClick={() => onActionFactorySelected(actionFactory)}
>
{actionFactory.getIconType(context) && (
<EuiIcon type={actionFactory.getIconType(context)!} size="m" />
)}
</EuiKeyPadMenuItemButton>
<EuiFlexItem grow={false}>
<EuiKeyPadMenuItemButton
className="auaActionWizard__actionFactoryItem"
key={actionFactory.id}
label={actionFactory.getDisplayName(context)}
data-test-subj={TEST_SUBJ_ACTION_FACTORY_ITEM}
onClick={() => onActionFactorySelected(actionFactory)}
>
{actionFactory.getIconType(context) && (
<EuiIcon type={actionFactory.getIconType(context)!} size="m" />
)}
</EuiKeyPadMenuItemButton>
</EuiFlexItem>
))}
</EuiFlexGroup>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,11 @@ import { i18n } from '@kbn/i18n';
import { CoreStart } from 'src/core/public';
import { ActionByType } from '../../../../../../../../src/plugins/ui_actions/public';
import { toMountPoint } from '../../../../../../../../src/plugins/kibana_react/public';
import { IEmbeddable } from '../../../../../../../../src/plugins/embeddable/public';
import { DrilldownsStartContract } from '../../../../../../drilldowns/public';
import { EmbeddableContext } from '../../../../../../../../src/plugins/embeddable/public';

export const OPEN_FLYOUT_ADD_DRILLDOWN = 'OPEN_FLYOUT_ADD_DRILLDOWN';

export interface FlyoutCreateDrilldownActionContext {
embeddable: IEmbeddable;
}

export interface OpenFlyoutAddDrilldownParams {
overlays: () => Promise<CoreStart['overlays']>;
drilldowns: () => Promise<DrilldownsStartContract>;
Expand All @@ -40,14 +36,23 @@ export class FlyoutCreateDrilldownAction implements ActionByType<typeof OPEN_FLY
return 'plusInCircle';
}

public async isCompatible({ embeddable }: FlyoutCreateDrilldownActionContext) {
return embeddable.getInput().viewMode === 'edit';
private isEmbeddableCompatible(context: EmbeddableContext) {
if (!context.embeddable.dynamicActions) return false;
const supportedTriggers = ['VALUE_CLICK_TRIGGER']; // context.embeddable.supportedTriggers();
if (!supportedTriggers || !supportedTriggers.length) return false;
return supportedTriggers.indexOf('VALUE_CLICK_TRIGGER') > -1;
}

public async execute(context: FlyoutCreateDrilldownActionContext) {
public async isCompatible(context: EmbeddableContext) {
const isEditMode = context.embeddable.getInput().viewMode === 'edit';
return isEditMode && this.isEmbeddableCompatible(context);
}

public async execute(context: EmbeddableContext) {
const overlays = await this.params.overlays();
const drilldowns = await this.params.drilldowns();
const dynamicActionManager = context.embeddable.dynamicActions;

if (!dynamicActionManager) {
throw new Error(`Can't execute FlyoutCreateDrilldownAction without dynamicActionsManager`);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
} from '../../../../../../src/plugins/embeddable/public';
import {
FlyoutCreateDrilldownAction,
FlyoutCreateDrilldownActionContext,
FlyoutEditDrilldownAction,
OPEN_FLYOUT_ADD_DRILLDOWN,
OPEN_FLYOUT_EDIT_DRILLDOWN,
Expand All @@ -22,7 +21,7 @@ import { DashboardToDashboardDrilldown } from './dashboard_to_dashboard_drilldow

declare module '../../../../../../src/plugins/ui_actions/public' {
export interface ActionContextMapping {
[OPEN_FLYOUT_ADD_DRILLDOWN]: FlyoutCreateDrilldownActionContext;
[OPEN_FLYOUT_ADD_DRILLDOWN]: EmbeddableContext;
[OPEN_FLYOUT_EDIT_DRILLDOWN]: EmbeddableContext;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,6 @@ import { DASHBOARD_TO_DASHBOARD_DRILLDOWN } from './constants';
import { DrilldownsDrilldown as Drilldown } from '../../../../../drilldowns/public';
import { txtGoToDashboard } from './i18n';

export const dashboards = [
{ id: 'dashboard1', title: 'Dashboard 1' },
{ id: 'dashboard2', title: 'Dashboard 2' },
];

export interface Params {
savedObjects: () => Promise<CoreStart['savedObjects']['client']>;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { EmbeddableVisTriggerContext } from '../../../../../../../src/plugins/embeddable/public';
import {
EmbeddableVisTriggerContext,
EmbeddableContext,
} from '../../../../../../../src/plugins/embeddable/public';
import { UiActionsCollectConfigProps } from '../../../../../../../src/plugins/ui_actions/public';

export type FactoryContext = any;
export type FactoryContext = EmbeddableContext;
export type ActionContext = EmbeddableVisTriggerContext;

export interface Config {
Expand Down
62 changes: 32 additions & 30 deletions x-pack/plugins/drilldowns/public/services/drilldown_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,8 @@

import { CoreSetup } from 'src/core/public';
import { AdvancedUiActionsSetup } from '../../../advanced_ui_actions/public';
import { AnyDrilldown } from '../types';

// TODO: MOCK DATA
import {
// dashboardDrilldownActionFactory,
urlDrilldownActionFactory,
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
} from '../../../advanced_ui_actions/public/components/action_wizard/test_data';
import { Drilldown, DrilldownFactoryContext } from '../types';
import { UiActionsActionFactoryDefinition as ActionFactoryDefinition } from '../../../../../src/plugins/ui_actions/public';

export interface DrilldownServiceSetupDeps {
advancedUiActions: AdvancedUiActionsSetup;
Expand All @@ -23,52 +17,60 @@ export interface DrilldownServiceSetupContract {
/**
* Convenience method to register a drilldown.
*/
registerDrilldown: (drilldown: AnyDrilldown) => void;
registerDrilldown: <
Config extends object = object,
CreationContext extends object = object,
ExecutionContext extends object = object
>(
drilldown: Drilldown<Config, CreationContext, ExecutionContext>
) => void;
}

export class DrilldownService {
setup(
core: CoreSetup,
{ advancedUiActions }: DrilldownServiceSetupDeps
): DrilldownServiceSetupContract {
const registerDrilldown: DrilldownServiceSetupContract['registerDrilldown'] = ({
id,
const registerDrilldown = <
Config extends object = object,
CreationContext extends object = object,
ExecutionContext extends object = object
>({
id: factoryId,
places,
CollectConfig,
createConfig,
isConfigValid,
getDisplayName,
euiIcon,
execute,
}) => {
advancedUiActions.registerActionFactory({
id,
}: Drilldown<Config, CreationContext, ExecutionContext>) => {
const actionFactory: ActionFactoryDefinition<
Config,
DrilldownFactoryContext<CreationContext>,
ExecutionContext
> = {
id: factoryId,
CollectConfig,
createConfig,
isConfigValid,
getDisplayName,
getIconType: () => euiIcon,
isCompatible: async ({ place }: any) => (!places ? true : places.indexOf(place) > -1),
create: config => ({
create: serializedAction => ({
id: '',
type: id as any,
type: factoryId,
getIconType: () => euiIcon,
execute: async context => await execute(config, context),
execute: async context => await execute(serializedAction.config, context),
}),
});
};
} as ActionFactoryDefinition<
Config,
DrilldownFactoryContext<CreationContext>,
ExecutionContext
>;

/*
registerDrilldown({
...dashboardDrilldownActionFactory,
execute: () => alert('Dashboard drilldown!'),
} as any);
*/
registerDrilldown({
...urlDrilldownActionFactory,
euiIcon: 'link',
execute: () => alert('URL drilldown!'),
} as any);
advancedUiActions.registerActionFactory(actionFactory);
};

return {
registerDrilldown,
Expand Down
109 changes: 98 additions & 11 deletions x-pack/plugins/drilldowns/public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,93 @@ import { AdvancedUiActionsActionFactoryDefinition as ActionFactoryDefinition } f

export interface Drilldown<
Config extends object = object,
FactoryContext extends object = object,
CreationContext extends object = object,
ExecutionContext extends object = object
>
extends Pick<
ActionFactoryDefinition<Config, FactoryContext, ExecutionContext>,
'id' | 'createConfig' | 'CollectConfig' | 'isConfigValid' | 'getDisplayName'
> {
> {
/**
* List of places where this drilldown should be available, e.g "dashboard".
* Globally unique identifier for this drilldown.
*/
id: string;

/**
* List of places where this drilldown should be available, e.g "dashboard", "graph".
* If omitted, the drilldown will be shown in all places.
*/
places?: string[];

/**
* Name of EUI icon to display next to this drilldown.
* Function that returns default config for this drilldown.
*/
createConfig: ActionFactoryDefinition<
Config,
DrilldownFactoryContext<CreationContext>,
ExecutionContext
>['createConfig'];

/**
* `UiComponent` that collections config for this drilldown. You can create
* a React component and transform it `UiComponent` using `uiToReactComponent`
* helper from `kibana_utils` plugin.
*
* ```tsx
* import React from 'react';
* import { uiToReactComponent } from 'src/plugins/kibana_utils';
* import { UiActionsCollectConfigProps as CollectConfigProps } from 'src/plugins/ui_actions/public';
*
* type Props = CollectConfigProps<Config>;
*
* const ReactCollectConfig: React.FC<Props> = () => {
* return <div>Collecting config...'</div>;
* };
*
* export const CollectConfig = uiToReactComponent(ReactCollectConfig);
* ```
*/
CollectConfig: ActionFactoryDefinition<
Config,
DrilldownFactoryContext<CreationContext>,
ExecutionContext
>['CollectConfig'];

/**
* A validator function for the config object. Should always return a boolean
* given any input.
*/
isConfigValid: ActionFactoryDefinition<
Config,
DrilldownFactoryContext<CreationContext>,
ExecutionContext
>['isConfigValid'];

/**
* Name of EUI icon to display when showing this drilldown to user.
*/
euiIcon?: string;

/**
* Implements the "navigation" action when user clicks something in the UI and
* instance of this drilldown is triggered.
* Should return an internationalized name of the drilldown, which will be
* displayed to the user.
*/
getDisplayName: () => string;

/**
* Whether this drilldown should be considered for execution given `config`
* and `context`. When multiple drilldowns are attached to the same trigger
* user is presented with a context menu to pick one drilldown for execute. If
* this method returns `true` this trigger will appear in the context menu
* list, if `false`, it will not be presented to the user. If `doExecute` is
* not implemented, this drilldown will always be show to the user.
*
* @param config Config object that user configured this drilldown with.
* @param context Object that represents context in which the underlying
* `UIAction` of this drilldown is being executed in.
*/
doExecute?(config: Config, context: ExecutionContext): Promise<boolean>;

/**
* Implements the "navigation" action of the drilldown. This happens when
* user clicks something in the UI that executes a trigger to which this
* drilldown was attached.
*
* @param config Config object that user configured this drilldown with.
* @param context Object that represents context in which the underlying
Expand All @@ -37,4 +103,25 @@ export interface Drilldown<
execute(config: Config, context: ExecutionContext): void;
}

export type AnyDrilldown = Drilldown<any, any, any>;
/**
* Context object used when creating a drilldown.
*/
export interface DrilldownFactoryContext<T> {
/**
* List of places as configured in @type {Drilldown} interface.
*/
places?: string[];

/**
* Context provided to the drilldown factory by the place where the UI is
* rendered. For example, for the "dashboard" place, this context contains
* the ID of the current dashboard, which could be used for filtering it out
* of the list.
*/
placeContext: T;

/**
* List of triggers that user selected in the UI.
*/
triggers: string[];
}

0 comments on commit 6efef2b

Please sign in to comment.