Skip to content

Commit 4b4f9ff

Browse files
committed
POC
1 parent 1e16d70 commit 4b4f9ff

File tree

40 files changed

+787
-502
lines changed

40 files changed

+787
-502
lines changed

src/plugins/data/public/actions/apply_filter_action.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,19 @@
1919

2020
import { i18n } from '@kbn/i18n';
2121
import { toMountPoint } from '../../../kibana_react/public';
22-
import { ActionByType, createAction, IncompatibleActionError } from '../../../ui_actions/public';
22+
import {
23+
ActionByType,
24+
ApplyFilterTriggerContext,
25+
createAction,
26+
IncompatibleActionError,
27+
} from '../../../ui_actions/public';
2328
import { getOverlays, getIndexPatterns } from '../services';
2429
import { applyFiltersPopover } from '../ui/apply_filters';
2530
import { Filter, FilterManager, TimefilterContract, esFilters } from '..';
2631

2732
export const ACTION_GLOBAL_APPLY_FILTER = 'ACTION_GLOBAL_APPLY_FILTER';
2833

29-
export interface ApplyGlobalFilterActionContext {
30-
filters: Filter[];
31-
timeFieldName?: string;
32-
}
33-
34-
async function isCompatible(context: ApplyGlobalFilterActionContext) {
34+
async function isCompatible(context: ApplyFilterTriggerContext) {
3535
return context.filters !== undefined;
3636
}
3737

@@ -49,7 +49,7 @@ export function createFilterAction(
4949
});
5050
},
5151
isCompatible,
52-
execute: async ({ filters, timeFieldName }: ApplyGlobalFilterActionContext) => {
52+
execute: async ({ filters, timeFieldName }: ApplyFilterTriggerContext) => {
5353
if (!filters) {
5454
throw new Error('Applying a filter requires a filter');
5555
}

src/plugins/data/public/plugin.ts

Lines changed: 61 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@
2020
import './index.scss';
2121

2222
import {
23-
PluginInitializerContext,
2423
CoreSetup,
2524
CoreStart,
26-
Plugin,
2725
PackageInfo,
26+
Plugin,
27+
PluginInitializerContext,
2828
} from 'src/core/public';
2929
import { ConfigSchema } from '../config';
30-
import { Storage, IStorageWrapper, createStartServicesGetter } from '../../kibana_utils/public';
30+
import { createStartServicesGetter, IStorageWrapper, Storage } from '../../kibana_utils/public';
3131
import {
3232
DataPublicPluginSetup,
3333
DataPublicPluginStart,
@@ -55,31 +55,24 @@ import {
5555
import { createSearchBar } from './ui/search_bar/create_search_bar';
5656
import { esaggs } from './search/expressions';
5757
import {
58-
SELECT_RANGE_TRIGGER,
59-
VALUE_CLICK_TRIGGER,
6058
APPLY_FILTER_TRIGGER,
59+
ApplyFilterTriggerContext,
60+
VALUE_CLICK_TRIGGER,
61+
SELECT_RANGE_TRIGGER,
6162
} from '../../ui_actions/public';
6263
import {
6364
ACTION_GLOBAL_APPLY_FILTER,
6465
createFilterAction,
65-
createFiltersFromValueClickAction,
6666
createFiltersFromRangeSelectAction,
67+
createFiltersFromValueClickAction,
6768
} from './actions';
68-
import { ApplyGlobalFilterActionContext } from './actions/apply_filter_action';
69-
import {
70-
selectRangeAction,
71-
SelectRangeActionContext,
72-
ACTION_SELECT_RANGE,
73-
} from './actions/select_range_action';
74-
import {
75-
valueClickAction,
76-
ACTION_VALUE_CLICK,
77-
ValueClickActionContext,
78-
} from './actions/value_click_action';
69+
import { ACTION_SELECT_RANGE, SelectRangeActionContext } from './actions/select_range_action';
70+
import { ACTION_VALUE_CLICK, ValueClickActionContext } from './actions/value_click_action';
71+
import { ValueClickTriggerContext, RangeSelectTriggerContext } from '../../embeddable/public';
7972

8073
declare module '../../ui_actions/public' {
8174
export interface ActionContextMapping {
82-
[ACTION_GLOBAL_APPLY_FILTER]: ApplyGlobalFilterActionContext;
75+
[ACTION_GLOBAL_APPLY_FILTER]: ApplyFilterTriggerContext;
8376
[ACTION_SELECT_RANGE]: SelectRangeActionContext;
8477
[ACTION_VALUE_CLICK]: ValueClickActionContext;
8578
}
@@ -126,19 +119,60 @@ export class DataPublicPlugin implements Plugin<DataPublicPluginSetup, DataPubli
126119
storage: this.storage,
127120
});
128121

129-
uiActions.registerAction(
122+
uiActions.addTriggerAction(
123+
APPLY_FILTER_TRIGGER,
130124
createFilterAction(queryService.filterManager, queryService.timefilter.timefilter)
131125
);
132126

133-
uiActions.addTriggerAction(
134-
SELECT_RANGE_TRIGGER,
135-
selectRangeAction(queryService.filterManager, queryService.timefilter.timefilter)
136-
);
127+
// uiActions.addTriggerAction(
128+
// SELECT_RANGE_TRIGGER,
129+
// selectRangeAction(queryService.filterManager, queryService.timefilter.timefilter)
130+
// );
131+
//
132+
// uiActions.addTriggerAction(
133+
// VALUE_CLICK_TRIGGER,
134+
// valueClickAction(queryService.filterManager, queryService.timefilter.timefilter)
135+
// );
137136

138-
uiActions.addTriggerAction(
139-
VALUE_CLICK_TRIGGER,
140-
valueClickAction(queryService.filterManager, queryService.timefilter.timefilter)
141-
);
137+
uiActions.registerTriggerReaction({
138+
originTrigger: VALUE_CLICK_TRIGGER,
139+
destTrigger: APPLY_FILTER_TRIGGER,
140+
isCompatible: async (context: ValueClickTriggerContext) => {
141+
const filters = await createFiltersFromValueClickAction(context.data);
142+
if (filters.length > 0) return true;
143+
return false;
144+
},
145+
originContextToDestContext: async (
146+
context: ValueClickTriggerContext
147+
): Promise<ApplyFilterTriggerContext> => {
148+
const filters = await createFiltersFromValueClickAction(context.data);
149+
return {
150+
embeddable: context.embeddable,
151+
filters,
152+
timeFieldName: context.data.timeFieldName,
153+
};
154+
},
155+
});
156+
157+
uiActions.registerTriggerReaction({
158+
originTrigger: SELECT_RANGE_TRIGGER,
159+
destTrigger: APPLY_FILTER_TRIGGER,
160+
isCompatible: async (context: RangeSelectTriggerContext) => {
161+
const filters = await createFiltersFromRangeSelectAction(context.data);
162+
if (filters.length > 0) return true;
163+
return false;
164+
},
165+
originContextToDestContext: async (
166+
context: RangeSelectTriggerContext
167+
): Promise<ApplyFilterTriggerContext> => {
168+
const filters = await createFiltersFromRangeSelectAction(context.data);
169+
return {
170+
embeddable: context.embeddable,
171+
filters,
172+
timeFieldName: context.data.timeFieldName,
173+
};
174+
},
175+
});
142176

143177
return {
144178
autocomplete: this.autocomplete.setup(core),

src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,8 +311,10 @@ export class EmbeddablePanel extends React.Component<Props, State> {
311311
const sortedActions = [...regularActions, ...extraActions].sort(sortByOrderField);
312312

313313
return await buildContextMenuForActions({
314-
actions: sortedActions,
315-
actionContext: { embeddable: this.props.embeddable },
314+
actionsWithContext: sortedActions.map((action) => [
315+
action,
316+
{ embeddable: this.props.embeddable },
317+
]),
316318
closeMenu: this.closeMyContextMenuPanel,
317319
});
318320
};

src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import _ from 'lodash';
2323
import { i18n } from '@kbn/i18n';
2424
import { uiToReactComponent } from '../../../kibana_react/public';
2525
import { Action } from '../actions';
26+
import { ActionContextTuple } from '../triggers';
2627

2728
export const defaultTitle = i18n.translate('uiActions.actionPanel.title', {
2829
defaultMessage: 'Options',
@@ -32,19 +33,16 @@ export const defaultTitle = i18n.translate('uiActions.actionPanel.title', {
3233
* Transforms an array of Actions to the shape EuiContextMenuPanel expects.
3334
*/
3435
export async function buildContextMenuForActions<Context extends object>({
35-
actions,
36-
actionContext,
36+
actionsWithContext,
3737
title = defaultTitle,
3838
closeMenu,
3939
}: {
40-
actions: Array<Action<Context>>;
41-
actionContext: Context;
40+
actionsWithContext: ActionContextTuple[];
4241
title?: string;
4342
closeMenu: () => void;
4443
}): Promise<EuiContextMenuPanelDescriptor> {
4544
const menuItems = await buildEuiContextMenuPanelItems<Context>({
46-
actions,
47-
actionContext,
45+
actionsWithContext,
4846
closeMenu,
4947
});
5048

@@ -59,16 +57,14 @@ export async function buildContextMenuForActions<Context extends object>({
5957
* Transform an array of Actions into the shape needed to build an EUIContextMenu
6058
*/
6159
async function buildEuiContextMenuPanelItems<Context extends object>({
62-
actions,
63-
actionContext,
60+
actionsWithContext,
6461
closeMenu,
6562
}: {
66-
actions: Array<Action<Context>>;
67-
actionContext: Context;
63+
actionsWithContext: ActionContextTuple[];
6864
closeMenu: () => void;
6965
}) {
70-
const items: EuiContextMenuPanelItemDescriptor[] = new Array(actions.length);
71-
const promises = actions.map(async (action, index) => {
66+
const items: EuiContextMenuPanelItemDescriptor[] = new Array(actionsWithContext.length);
67+
const promises = actionsWithContext.map(async ([action, actionContext], index) => {
7268
const isCompatible = await action.isCompatible(actionContext);
7369
if (!isCompatible) {
7470
return;

src/plugins/ui_actions/public/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,11 @@ export {
4444
APPLY_FILTER_TRIGGER,
4545
applyFilterTrigger,
4646
} from './triggers';
47-
export { TriggerContextMapping, TriggerId, ActionContextMapping, ActionType } from './types';
47+
export {
48+
TriggerContextMapping,
49+
TriggerId,
50+
ActionContextMapping,
51+
ActionType,
52+
ApplyFilterTriggerContext,
53+
} from './types';
4854
export { ActionByType } from './actions';

src/plugins/ui_actions/public/plugin.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export type UiActionsSetup = Pick<
2929
| 'registerAction'
3030
| 'registerTrigger'
3131
| 'unregisterAction'
32+
| 'registerTriggerReaction'
3233
>;
3334

3435
export type UiActionsStart = PublicMethodsOf<UiActionsService>;

src/plugins/ui_actions/public/service/ui_actions_service.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,18 @@ import { Trigger, TriggerContext } from '../triggers/trigger';
2929
import { TriggerInternal } from '../triggers/trigger_internal';
3030
import { TriggerContract } from '../triggers/trigger_contract';
3131

32+
export interface TriggerReaction<
33+
OriginTrigger extends TriggerId = TriggerId,
34+
DestTrigger extends TriggerId = TriggerId
35+
> {
36+
originTrigger: OriginTrigger;
37+
destTrigger: DestTrigger;
38+
originContextToDestContext(
39+
context: TriggerContextMapping[OriginTrigger]
40+
): Promise<TriggerContextMapping[DestTrigger]>;
41+
isCompatible(context: TriggerContextMapping[OriginTrigger]): Promise<boolean>;
42+
}
43+
3244
export interface UiActionsServiceParams {
3345
readonly triggers?: TriggerRegistry;
3446
readonly actions?: ActionRegistry;
@@ -43,6 +55,7 @@ export class UiActionsService {
4355
protected readonly triggers: TriggerRegistry;
4456
protected readonly actions: ActionRegistry;
4557
protected readonly triggerToActions: TriggerToActionsRegistry;
58+
protected readonly triggerReactions = new Map<TriggerId, TriggerReaction[]>();
4659

4760
constructor({
4861
triggers = new Map(),
@@ -63,8 +76,26 @@ export class UiActionsService {
6376

6477
this.triggers.set(trigger.id, triggerInternal);
6578
this.triggerToActions.set(trigger.id, []);
79+
this.triggerReactions.set(trigger.id, []);
80+
};
81+
82+
public readonly registerTriggerReaction = (triggerReaction: TriggerReaction) => {
83+
if (!this.triggers.has(triggerReaction.originTrigger)) {
84+
throw new Error(`Trigger [trigger.id = ${triggerReaction.originTrigger}] not registered.`);
85+
}
86+
if (!this.triggers.has(triggerReaction.destTrigger)) {
87+
throw new Error(`Trigger [trigger.id = ${triggerReaction.destTrigger}] not registered.`);
88+
}
89+
90+
this.triggerReactions.set(triggerReaction.originTrigger, [
91+
...this.triggerReactions.get(triggerReaction.originTrigger)!,
92+
triggerReaction,
93+
]);
6694
};
6795

96+
public readonly getTriggerReactions = (triggerId: TriggerId) =>
97+
Array.from(this.triggerReactions.get(triggerId) ?? []);
98+
6899
public readonly getTrigger = <T extends TriggerId>(triggerId: T): TriggerContract<T> => {
69100
const trigger = this.triggers.get(triggerId);
70101

src/plugins/ui_actions/public/triggers/trigger_internal.ts

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ import { TriggerContract } from './trigger_contract';
2222
import { UiActionsService } from '../service';
2323
import { Action } from '../actions';
2424
import { buildContextMenuForActions, openContextMenu } from '../context_menu';
25-
import { TriggerId, TriggerContextMapping } from '../types';
25+
import { TriggerId, TriggerContextMapping, BaseContext } from '../types';
26+
27+
export type ActionContextTuple = [Action, BaseContext];
2628

2729
/**
2830
* Internal representation of a trigger kept for consumption only internally
@@ -37,34 +39,46 @@ export class TriggerInternal<T extends TriggerId> {
3739
const triggerId = this.trigger.id;
3840
const actions = await this.service.getTriggerCompatibleActions!(triggerId, context);
3941

40-
if (!actions.length) {
42+
const actionsWithContexts: ActionContextTuple[] = actions.map((action) => [action, context]);
43+
44+
// TODO: make this recursive
45+
const triggerReactions = this.service.getTriggerReactions(triggerId);
46+
for (const reaction of triggerReactions) {
47+
if (await reaction.isCompatible(context)) {
48+
const reactionContext = await reaction.originContextToDestContext(context);
49+
const reactionActions = await this.service.getTriggerCompatibleActions(
50+
reaction.destTrigger,
51+
reactionContext
52+
);
53+
const reactionActionsWithContext = reactionActions.map((reactionAction) => [
54+
reactionAction,
55+
reactionContext,
56+
]);
57+
actionsWithContexts.push(...(reactionActionsWithContext as ActionContextTuple[]));
58+
}
59+
}
60+
61+
if (!actionsWithContexts.length) {
4162
throw new Error(
4263
`No compatible actions found to execute for trigger [triggerId = ${triggerId}].`
4364
);
4465
}
4566

46-
if (actions.length === 1) {
47-
await this.executeSingleAction(actions[0], context);
67+
if (actionsWithContexts.length === 1) {
68+
await this.executeSingleAction(actionsWithContexts[0]);
4869
return;
4970
}
5071

51-
await this.executeMultipleActions(actions, context);
72+
await this.executeMultipleActions(actionsWithContexts);
5273
}
5374

54-
private async executeSingleAction(
55-
action: Action<TriggerContextMapping[T]>,
56-
context: TriggerContextMapping[T]
57-
) {
75+
private async executeSingleAction([action, context]: ActionContextTuple) {
5876
await action.execute(context);
5977
}
6078

61-
private async executeMultipleActions(
62-
actions: Array<Action<TriggerContextMapping[T]>>,
63-
context: TriggerContextMapping[T]
64-
) {
79+
private async executeMultipleActions(actionsWithContext: ActionContextTuple[]) {
6580
const panel = await buildContextMenuForActions({
66-
actions,
67-
actionContext: context,
81+
actionsWithContext,
6882
title: this.trigger.title,
6983
closeMenu: () => session.close(),
7084
});

src/plugins/ui_actions/public/types.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,17 @@ export type TriggerId = keyof TriggerContextMapping;
3535
export type BaseContext = object;
3636
export type TriggerContext = BaseContext;
3737

38+
export interface ApplyFilterTriggerContext<E extends IEmbeddable = IEmbeddable> {
39+
embeddable?: IEmbeddable;
40+
filters: Filter[];
41+
timeFieldName?: string;
42+
}
43+
3844
export interface TriggerContextMapping {
3945
[DEFAULT_TRIGGER]: TriggerContext;
4046
[SELECT_RANGE_TRIGGER]: RangeSelectTriggerContext;
4147
[VALUE_CLICK_TRIGGER]: ValueClickTriggerContext;
42-
[APPLY_FILTER_TRIGGER]: {
43-
embeddable: IEmbeddable;
44-
filters: Filter[];
45-
};
48+
[APPLY_FILTER_TRIGGER]: ApplyFilterTriggerContext;
4649
}
4750

4851
const DEFAULT_ACTION = '';

0 commit comments

Comments
 (0)