Skip to content

Commit 0cde5fd

Browse files
[Drilldowns] {{event.points}} in URL drilldown for VALUE_CLICK_TRIGGER (#76771)
{{event.points}} in URL drilldown for VALUE_CLICK_TRIGGER Co-authored-by: Elastic Machine <[email protected]>
1 parent cbf2844 commit 0cde5fd

File tree

7 files changed

+192
-171
lines changed

7 files changed

+192
-171
lines changed

docs/user/dashboard/url-drilldown.asciidoc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ context.panel.timeRange.indexPatternIds
197197
| ID of saved object behind a panel.
198198

199199
| *Single click*
200+
200201
| event.value
201202
| Value behind clicked data point.
202203

@@ -208,6 +209,22 @@ context.panel.timeRange.indexPatternIds
208209
| event.negate
209210
| Boolean, indicating whether clicked data point resulted in negative filter.
210211

212+
|
213+
| event.points
214+
| Some visualizations have clickable points that emit more than one data point. Use list of data points in case a single value is insufficient. +
215+
216+
Example:
217+
218+
`{{json event.points}}` +
219+
`{{event.points.[0].key}}` +
220+
`{{event.points.[0].value}}`
221+
`{{#each event.points}}key=value&{{/each}}`
222+
223+
Note:
224+
225+
`{{event.value}}` is a shorthand for `{{event.points.[0].value}}` +
226+
`{{event.key}}` is a shorthand for `{{event.points.[0].key}}`
227+
211228
| *Range selection*
212229
| event.from +
213230
event.to

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,6 @@ export const valueClickTrigger: Trigger<'VALUE_CLICK_TRIGGER'> = {
2727
defaultMessage: 'Single click',
2828
}),
2929
description: i18n.translate('uiActions.triggers.valueClickDescription', {
30-
defaultMessage: 'A single point on the visualization',
30+
defaultMessage: 'A data point click on the visualization',
3131
}),
3232
};

x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown.test.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
*/
66

77
import { UrlDrilldown, ActionContext, Config } from './url_drilldown';
8-
import { coreMock } from '../../../../../../src/core/public/mocks';
98
import { IEmbeddable } from '../../../../../../src/plugins/embeddable/public/lib/embeddables';
109

1110
const mockDataPoints = [
@@ -52,7 +51,6 @@ const mockNavigateToUrl = jest.fn(() => Promise.resolve());
5251
describe('UrlDrilldown', () => {
5352
const urlDrilldown = new UrlDrilldown({
5453
getGlobalScope: () => ({ kibanaUrl: 'http://localhost:5601/' }),
55-
getOpenModal: () => Promise.resolve(coreMock.createStart().overlays.openModal),
5654
getSyntaxHelpDocsLink: () => 'http://localhost:5601/docs',
5755
getVariablesHelpDocsLink: () => 'http://localhost:5601/docs',
5856
navigateToUrl: mockNavigateToUrl,

x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown.tsx

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
*/
66

77
import React from 'react';
8-
import { OverlayStart } from 'kibana/public';
98
import { reactToUiComponent } from '../../../../../../src/plugins/kibana_react/public';
109
import { ChartActionContext, IEmbeddable } from '../../../../../../src/plugins/embeddable/public';
1110
import { CollectConfigProps as CollectConfigPropsBase } from '../../../../../../src/plugins/kibana_utils/public';
@@ -29,7 +28,6 @@ import { txtUrlDrilldownDisplayName } from './i18n';
2928
interface UrlDrilldownDeps {
3029
getGlobalScope: () => UrlDrilldownGlobalScope;
3130
navigateToUrl: (url: string) => Promise<void>;
32-
getOpenModal: () => Promise<OverlayStart['openModal']>;
3331
getSyntaxHelpDocsLink: () => string;
3432
getVariablesHelpDocsLink: () => string;
3533
}
@@ -112,13 +110,10 @@ export class UrlDrilldown implements Drilldown<Config, UrlTrigger, ActionFactory
112110
};
113111

114112
public readonly getHref = async (config: Config, context: ActionContext) =>
115-
urlDrilldownCompileUrl(config.url.template, await this.buildRuntimeScope(context));
113+
urlDrilldownCompileUrl(config.url.template, this.buildRuntimeScope(context));
116114

117115
public readonly execute = async (config: Config, context: ActionContext) => {
118-
const url = await urlDrilldownCompileUrl(
119-
config.url.template,
120-
await this.buildRuntimeScope(context, { allowPrompts: true })
121-
);
116+
const url = urlDrilldownCompileUrl(config.url.template, this.buildRuntimeScope(context));
122117
if (config.openInNewTab) {
123118
window.open(url, '_blank', 'noopener');
124119
} else {
@@ -134,14 +129,11 @@ export class UrlDrilldown implements Drilldown<Config, UrlTrigger, ActionFactory
134129
});
135130
};
136131

137-
private buildRuntimeScope = async (
138-
context: ActionContext,
139-
opts: { allowPrompts: boolean } = { allowPrompts: false }
140-
) => {
132+
private buildRuntimeScope = (context: ActionContext) => {
141133
return urlDrilldownBuildScope({
142134
globalScope: this.deps.getGlobalScope(),
143135
contextScope: getContextScope(context),
144-
eventScope: await getEventScope(context, this.deps, opts),
136+
eventScope: getEventScope(context),
145137
});
146138
};
147139
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import {
8+
getEventScope,
9+
getMockEventScope,
10+
ValueClickTriggerEventScope,
11+
} from './url_drilldown_scope';
12+
13+
const createPoint = ({
14+
field,
15+
value,
16+
}: {
17+
field: string;
18+
value: string | null | number | boolean;
19+
}) => ({
20+
table: {
21+
columns: [
22+
{
23+
name: field,
24+
id: '1-1',
25+
meta: {
26+
type: 'histogram',
27+
indexPatternId: 'logstash-*',
28+
aggConfigParams: {
29+
field,
30+
interval: 30,
31+
otherBucket: true,
32+
},
33+
},
34+
},
35+
],
36+
rows: [
37+
{
38+
'1-1': '2048',
39+
},
40+
],
41+
},
42+
column: 0,
43+
row: 0,
44+
value,
45+
});
46+
47+
describe('VALUE_CLICK_TRIGGER', () => {
48+
describe('supports `points[]`', () => {
49+
test('getEventScope()', () => {
50+
const mockDataPoints = [
51+
createPoint({ field: 'field0', value: 'value0' }),
52+
createPoint({ field: 'field1', value: 'value1' }),
53+
createPoint({ field: 'field2', value: 'value2' }),
54+
];
55+
56+
const eventScope = getEventScope({
57+
data: { data: mockDataPoints },
58+
}) as ValueClickTriggerEventScope;
59+
60+
expect(eventScope.key).toBe('field0');
61+
expect(eventScope.value).toBe('value0');
62+
expect(eventScope.points).toHaveLength(mockDataPoints.length);
63+
expect(eventScope.points).toMatchInlineSnapshot(`
64+
Array [
65+
Object {
66+
"key": "field0",
67+
"value": "value0",
68+
},
69+
Object {
70+
"key": "field1",
71+
"value": "value1",
72+
},
73+
Object {
74+
"key": "field2",
75+
"value": "value2",
76+
},
77+
]
78+
`);
79+
});
80+
81+
test('getMockEventScope()', () => {
82+
const mockEventScope = getMockEventScope([
83+
'VALUE_CLICK_TRIGGER',
84+
]) as ValueClickTriggerEventScope;
85+
expect(mockEventScope.points.length).toBeGreaterThan(3);
86+
expect(mockEventScope.points).toMatchInlineSnapshot(`
87+
Array [
88+
Object {
89+
"key": "event.points.0.key",
90+
"value": "event.points.0.value",
91+
},
92+
Object {
93+
"key": "event.points.1.key",
94+
"value": "event.points.1.value",
95+
},
96+
Object {
97+
"key": "event.points.2.key",
98+
"value": "event.points.2.value",
99+
},
100+
Object {
101+
"key": "event.points.3.key",
102+
"value": "event.points.3.value",
103+
},
104+
]
105+
`);
106+
});
107+
});
108+
109+
describe('handles undefined, null or missing values', () => {
110+
test('undefined or missing values are removed from the result scope', () => {
111+
const point = createPoint({ field: undefined } as any);
112+
const eventScope = getEventScope({
113+
data: { data: [point] },
114+
}) as ValueClickTriggerEventScope;
115+
116+
expect('key' in eventScope).toBeFalsy();
117+
expect('value' in eventScope).toBeFalsy();
118+
});
119+
120+
test('null value stays in the result scope', () => {
121+
const point = createPoint({ field: 'field', value: null });
122+
const eventScope = getEventScope({
123+
data: { data: [point] },
124+
}) as ValueClickTriggerEventScope;
125+
126+
expect(eventScope.value).toBeNull();
127+
});
128+
});
129+
});

0 commit comments

Comments
 (0)