Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drilldowns various 4 #60264

Merged
merged 5 commits into from
Mar 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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[];
}