Skip to content

Commit

Permalink
Drilldowns multitrigger (#60357)
Browse files Browse the repository at this point in the history
* feat: 🎸 add support for multiple triggers

* feat: 🎸 enable Drilldowns for TSVB

Although TSVB brushing event is now broken on master, KibanaApp plans to
fix it in 7.7
  • Loading branch information
streamich authored Mar 17, 2020
1 parent 62edd23 commit 7a7beeb
Show file tree
Hide file tree
Showing 8 changed files with 62 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,7 @@ export class VisualizeEmbeddable extends Embeddable<VisualizeInput, VisualizeOut
case 'histogram':
case 'horizontal_bar':
case 'line':
case 'metrics':
case 'pie':
case 'table':
case 'tagcloud':
Expand All @@ -415,7 +416,6 @@ export class VisualizeEmbeddable extends Embeddable<VisualizeInput, VisualizeOut
case 'input_control_vis':
case 'markdown':
case 'metric':
case 'metrics':
case 'region_map':
case 'tile_map':
case 'timelion':
Expand Down
31 changes: 18 additions & 13 deletions src/plugins/ui_actions/public/actions/dynamic_action_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { Subscription } from 'rxjs';
import { ActionStorage, SerializedEvent } from './dynamic_action_storage';
import { UiActionsService } from '../service';
import { SerializedAction } from './types';
import { TriggerContextMapping } from '../types';
import { ActionDefinition } from './action';
import { defaultState, transitions, selectors, State } from './dynamic_action_manager_state';
import { StateContainer, createStateContainer } from '../../../kibana_utils';
Expand All @@ -41,7 +42,7 @@ export interface DynamicActionManagerParams {
storage: ActionStorage;
uiActions: Pick<
UiActionsService,
'addTriggerAction' | 'removeTriggerAction' | 'getActionFactory'
'registerAction' | '__attachAction' | 'unregisterAction' | 'detachAction' | 'getActionFactory'
>;
isCompatible: <C = unknown>(context: C) => Promise<boolean>;
}
Expand Down Expand Up @@ -75,7 +76,7 @@ export class DynamicActionManager {
}

protected reviveAction(event: SerializedEvent) {
const { eventId, triggerId, action } = event;
const { eventId, triggers, action } = event;
const { uiActions, isCompatible } = this.params;
const { name } = action;

Expand All @@ -88,13 +89,16 @@ export class DynamicActionManager {
getDisplayName: () => name,
};

uiActions.addTriggerAction(triggerId as any, actionDefinition);
uiActions.registerAction(actionDefinition);
for (const trigger of triggers) uiActions.__attachAction(trigger as any, actionId);
}

protected killAction({ eventId, triggerId }: SerializedEvent) {
protected killAction({ eventId, triggers }: SerializedEvent) {
const { uiActions } = this.params;
const actionId = this.generateActionId(eventId);
uiActions.removeTriggerAction(triggerId as any, actionId);

for (const trigger of triggers) uiActions.detachAction(trigger as any, actionId);
uiActions.unregisterAction(actionId);
}

private syncId = 0;
Expand Down Expand Up @@ -179,15 +183,16 @@ export class DynamicActionManager {
* 2. Optimistically adds it to UI state, and rolls back on failure.
* 3. Adds action to `ui_actions` registry.
*
* @todo `triggerId` should not be optional.
*
* @param action Dynamic action for which to create an event.
* @param triggerId Trigger to which to attach the action.
* @param triggers List of triggers to which action should react.
*/
public async createEvent(action: SerializedAction<unknown>, triggerId = 'VALUE_CLICK_TRIGGER') {
public async createEvent(
action: SerializedAction<unknown>,
triggers: Array<keyof TriggerContextMapping>
) {
const event: SerializedEvent = {
eventId: uuidv4(),
triggerId,
triggers,
action,
};

Expand All @@ -212,16 +217,16 @@ export class DynamicActionManager {
*
* @param eventId ID of the event to replace.
* @param action New action for which to create the event.
* @param triggerId New trigger with which to associate the event.
* @param triggers List of triggers to which action should react.
*/
public async updateEvent(
eventId: string,
action: SerializedAction<unknown>,
triggerId = 'VALUE_CLICK_TRIGGER'
triggers: Array<keyof TriggerContextMapping>
) {
const event: SerializedEvent = {
eventId,
triggerId,
triggers,
action,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { SerializedAction } from './types';
*/
export interface SerializedEvent {
eventId: string;
triggerId: string;
triggers: string[];
action: SerializedAction<unknown>;
}

Expand Down
2 changes: 2 additions & 0 deletions src/plugins/ui_actions/public/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ const createSetupContract = (): Setup => {

const createStartContract = (): Start => {
const startContract: Start = {
__attachAction: jest.fn(),
unregisterAction: jest.fn(),
addTriggerAction: jest.fn(),
attachAction: jest.fn(),
clear: jest.fn(),
Expand Down
4 changes: 2 additions & 2 deletions src/plugins/ui_actions/public/service/ui_actions_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export class UiActionsService {
return action;
};

protected readonly unregisterAction = (actionId: string): void => {
public readonly unregisterAction = (actionId: string): void => {
if (!this.actions.has(actionId)) {
throw new Error(`Action [action.id = ${actionId}] is not registered.`);
}
Expand Down Expand Up @@ -133,7 +133,7 @@ export class UiActionsService {

// public readonly removeTriggerAction =

protected readonly __attachAction = <TriggerId extends keyof TriggerContextMapping>(
public readonly __attachAction = <TriggerId extends keyof TriggerContextMapping>(
triggerId: TriggerId,
actionId: string
): void => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ test('Create only mode', async () => {

await wait(() => expect(notifications.toasts.addSuccess).toBeCalled());
expect(onClose).toBeCalled();
expect(await mockDynamicActionManager.count()).toBe(1);
expect(await mockDynamicActionManager.state.get().events.length).toBe(1);
});

test.todo("Error when can't fetch drilldown list");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import {
DynamicActionManager,
UiActionsSerializedEvent,
UiActionsSerializedAction,
VALUE_CLICK_TRIGGER,
SELECT_RANGE_TRIGGER,
TriggerContextMapping,
} from '../../../../../../src/plugins/ui_actions/public';
import { useContainerState } from '../../../../../../src/plugins/kibana_utils/common';
import { DrilldownListItem } from '../list_manage_drilldowns';
Expand Down Expand Up @@ -66,6 +69,11 @@ export function createFlyoutManageDrilldowns({
return (props: ConnectedFlyoutManageDrilldownsProps) => {
const isCreateOnly = props.viewMode === 'create';

const selectedTriggers: Array<keyof TriggerContextMapping> = React.useMemo(
() => [VALUE_CLICK_TRIGGER, SELECT_RANGE_TRIGGER],
[]
);

const factoryContext: DrilldownFactoryContext<unknown> = React.useMemo(
() => ({
place: '',
Expand Down Expand Up @@ -149,18 +157,24 @@ export function createFlyoutManageDrilldowns({
onBack={isCreateOnly ? undefined : () => setRoute(Routes.Manage)}
onSubmit={({ actionConfig, actionFactory, name }) => {
if (route === Routes.Create) {
createDrilldown({
name,
config: actionConfig,
factoryId: actionFactory.id,
});
createDrilldown(
{
name,
config: actionConfig,
factoryId: actionFactory.id,
},
selectedTriggers
);
} else {
// edit
editDrilldown(currentEditId!, {
name,
config: actionConfig,
factoryId: actionFactory.id,
});
editDrilldown(
currentEditId!,
{
name,
config: actionConfig,
factoryId: actionFactory.id,
},
selectedTriggers
);
}

if (isCreateOnly) {
Expand Down Expand Up @@ -270,9 +284,12 @@ function useDrilldownsStateManager(
}
}

async function createDrilldown(action: UiActionsSerializedAction<any>, triggerId?: string) {
async function createDrilldown(
action: UiActionsSerializedAction<any>,
selectedTriggers: Array<keyof TriggerContextMapping>
) {
await run(async () => {
await actionManager.createEvent(action, triggerId);
await actionManager.createEvent(action, selectedTriggers);
notifications.toasts.addSuccess({
title: toastDrilldownCreated.title,
text: toastDrilldownCreated.text(action.name),
Expand All @@ -283,10 +300,10 @@ function useDrilldownsStateManager(
async function editDrilldown(
drilldownId: string,
action: UiActionsSerializedAction<any>,
triggerId?: string
selectedTriggers: Array<keyof TriggerContextMapping>
) {
await run(async () => {
await actionManager.updateEvent(drilldownId, action, triggerId);
await actionManager.updateEvent(drilldownId, action, selectedTriggers);
notifications.toasts.addSuccess({
title: toastDrilldownEdited.title,
text: toastDrilldownEdited.text(action.name),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
DynamicActionManager,
DynamicActionManagerState,
UiActionsSerializedAction,
TriggerContextMapping,
} from '../../../../../../src/plugins/ui_actions/public';
import { createStateContainer } from '../../../../../../src/plugins/kibana_utils/common';

Expand All @@ -29,11 +30,11 @@ class MockDynamicActionManager implements PublicMethodsOf<DynamicActionManager>

async createEvent(
action: UiActionsSerializedAction<any>,
triggerId: string = 'VALUE_CLICK_TRIGGER'
triggers: Array<keyof TriggerContextMapping>
) {
const event = {
action,
triggerId,
triggers,
eventId: uuid(),
};
const state = this.state.get();
Expand All @@ -60,15 +61,15 @@ class MockDynamicActionManager implements PublicMethodsOf<DynamicActionManager>
async updateEvent(
eventId: string,
action: UiActionsSerializedAction<unknown>,
triggerId: string = 'VALUE_CLICK_TRIGGER'
triggers: Array<keyof TriggerContextMapping>
) {
const state = this.state.get();
const events = state.events;
const idx = events.findIndex(e => e.eventId === eventId);
const event = {
eventId,
action,
triggerId,
triggers,
};

this.state.set({
Expand Down

0 comments on commit 7a7beeb

Please sign in to comment.