Skip to content

Commit

Permalink
[maps] support URL drilldowns (elastic#83732)
Browse files Browse the repository at this point in the history
* url drilldowns

* onSingleValueTrigger

* cleanup

* tslint

* revert changes to ui_actions_service

* remove unused method added to es_tooltip_property

* remove unused file

* update drilldown docs to reflect maps supports URL drilldowns

* add functional test case for URL drilldown

* do not show URL drilldowns in geometry filter action selection

Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
nreese and kibanamachine committed Nov 20, 2020
1 parent 674179d commit ae35104
Show file tree
Hide file tree
Showing 12 changed files with 163 additions and 51 deletions.
42 changes: 21 additions & 21 deletions docs/user/dashboard/drilldowns.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
== Create custom dashboard actions

Custom dashboard actions, also known as drilldowns, allow you to create
workflows for analyzing and troubleshooting your data. Drilldowns apply only to the panel that you created the drilldown from, and are not shared across all of the panels. Each panel can have multiple drilldowns.
workflows for analyzing and troubleshooting your data. Drilldowns apply only to the panel that you created the drilldown from, and are not shared across all of the panels. Each panel can have multiple drilldowns.

Third-party developers can create drilldowns. To learn how to code drilldowns, refer to {kib-repo}blob/{branch}/x-pack/examples/ui_actions_enhanced_examples[this example plugin].

Expand All @@ -28,7 +28,7 @@ Dashboard drilldowns enable you to open a dashboard from another dashboard,
taking the time range, filters, and other parameters with you,
so the context remains the same. Dashboard drilldowns help you to continue your analysis from a new perspective.

For example, if you have a dashboard that shows the overall status of multiple data center,
For example, if you have a dashboard that shows the overall status of multiple data center,
you can create a drilldown that navigates from the overall status dashboard to a dashboard
that shows a single data center or server.

Expand All @@ -41,14 +41,14 @@ Destination URLs can be dynamic, depending on the dashboard context or user inte
For example, if you have a dashboard that shows data from a Github repository, you can create a URL drilldown
that opens Github from the dashboard.

Some panels support multiple interactions, also known as triggers.
Some panels support multiple interactions, also known as triggers.
The <<variables,variables>> you use to create a <<url_templating, URL template>> depends on the trigger you choose. URL drilldowns support these types of triggers:

* *Single click* &mdash; A single data point in the visualization.

* *Range selection* &mdash; A range of values in a visualization.

For example, *Single click* has `{{event.value}}` and *Range selection* has `{{event.from}}` and `{{event.to}}`.
For example, *Single click* has `{{event.value}}` and *Range selection* has `{{event.from}}` and `{{event.to}}`.

To disable URL drilldowns on your {kib} instance, disable the plugin:

Expand Down Expand Up @@ -77,20 +77,20 @@ The following panels support dashboard and URL drilldowns.
^| X

| Controls
^|
^|
^|
^|

| Data Table
^| X
^| X

| Gauge
^|
^|
^|
^|

| Goal
^|
^|
^|
^|

| Heat map
^| X
Expand All @@ -106,35 +106,35 @@ The following panels support dashboard and URL drilldowns.

| Maps
^| X
^|
^| X

| Markdown
^|
^|
^|
^|

| Metric
^|
^|
^|
^|

| Pie
^| X
^| X

| TSVB
^| X
^|
^|

| Tag Cloud
^| X
^| X

| Timelion
^| X
^|
^|

| Vega
^| X
^|
^|

| Vertical Bar
^| X
Expand Down Expand Up @@ -192,7 +192,7 @@ image::images/drilldown_create.png[Create drilldown with entries for drilldown n

. Click *Create drilldown*.
+
The drilldown is stored as dashboard metadata.
The drilldown is stored as dashboard metadata.

. Save the dashboard.
+
Expand Down Expand Up @@ -226,7 +226,7 @@ image:images/url_drilldown_go_to_github.gif[Drilldown on pie chart that navigate

.. Select *Go to URL*.

.. Enter the URL template:
.. Enter the URL template:
+
[source, bash]
----
Expand All @@ -240,7 +240,7 @@ image:images/url_drilldown_url_template.png[URL template input]

.. Click *Create drilldown*.
+
The drilldown is stored as dashboard metadata.
The drilldown is stored as dashboard metadata.

. Save the dashboard.
+
Expand Down
3 changes: 2 additions & 1 deletion x-pack/plugins/maps/public/components/action_select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import React, { Component } from 'react';
import { EuiFormRow, EuiSuperSelect, EuiIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { ActionExecutionContext, Action } from 'src/plugins/ui_actions/public';
import { isUrlDrilldown } from '../trigger_actions/trigger_utils';

interface Props {
value?: string;
Expand Down Expand Up @@ -41,7 +42,7 @@ export class ActionSelect extends Component<Props, State> {
}
const actions = await this.props.getFilterActions();
if (this._isMounted) {
this.setState({ actions });
this.setState({ actions: actions.filter((action) => !isUrlDrilldown(action)) });
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { LayerPanel } from '../layer_panel';
import { AddLayerPanel } from '../add_layer_panel';
import { ExitFullScreenButton } from '../../../../../../src/plugins/kibana_react/public';
import { getIndexPatternsFromIds } from '../../index_pattern_util';
import { ES_GEO_FIELD_TYPE } from '../../../common/constants';
import { ES_GEO_FIELD_TYPE, RawValue } from '../../../common/constants';
import { indexPatterns as indexPatternsUtils } from '../../../../../../src/plugins/data/public';
import { FLYOUT_STATE } from '../../reducers/ui';
import { MapSettingsPanel } from '../map_settings_panel';
Expand All @@ -40,6 +40,7 @@ interface Props {
backgroundColor: string;
getFilterActions?: () => Promise<Action[]>;
getActionContext?: () => ActionExecutionContext;
onSingleValueTrigger?: (actionId: string, key: string, value: RawValue) => void;
areLayersLoaded: boolean;
cancelAllInFlightRequests: () => void;
exitFullScreen: () => void;
Expand Down Expand Up @@ -191,6 +192,7 @@ export class MapContainer extends Component<Props, State> {
addFilters,
getFilterActions,
getActionContext,
onSingleValueTrigger,
flyoutDisplay,
isFullScreen,
exitFullScreen,
Expand Down Expand Up @@ -250,6 +252,7 @@ export class MapContainer extends Component<Props, State> {
addFilters={addFilters}
getFilterActions={getFilterActions}
getActionContext={getActionContext}
onSingleValueTrigger={onSingleValueTrigger}
geoFields={this.state.geoFields}
renderTooltipContent={renderTooltipContent}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { ACTION_GLOBAL_APPLY_FILTER } from '../../../../../../../src/plugins/data/public';
import { isUrlDrilldown } from '../../../trigger_actions/trigger_utils';

export class FeatureProperties extends React.Component {
state = {
Expand Down Expand Up @@ -114,21 +115,37 @@ export class FeatureProperties extends React.Component {
_renderFilterActions(tooltipProperty) {
const panel = {
id: 0,
items: this.state.actions.map((action) => {
const actionContext = this.props.getActionContext();
const iconType = action.getIconType(actionContext);
const name = action.getDisplayName(actionContext);
return {
name,
icon: iconType ? <EuiIcon type={iconType} /> : null,
onClick: async () => {
this.props.onCloseTooltip();
const filters = await tooltipProperty.getESFilters();
this.props.addFilters(filters, action.id);
},
['data-test-subj']: `mapFilterActionButton__${name}`,
};
}),
items: this.state.actions
.filter((action) => {
if (isUrlDrilldown(action)) {
return !!this.props.onSingleValueTrigger;
}
return true;
})
.map((action) => {
const actionContext = this.props.getActionContext();
const iconType = action.getIconType(actionContext);
const name = action.getDisplayName(actionContext);
return {
name: name ? name : action.id,
icon: iconType ? <EuiIcon type={iconType} /> : null,
onClick: async () => {
this.props.onCloseTooltip();

if (isUrlDrilldown(action)) {
this.props.onSingleValueTrigger(
action.id,
tooltipProperty.getPropertyKey(),
tooltipProperty.getRawValue()
);
} else {
const filters = await tooltipProperty.getESFilters();
this.props.addFilters(filters, action.id);
}
},
['data-test-subj']: `mapFilterActionButton__${name}`,
};
}),
};

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ export class FeaturesTooltip extends Component {
addFilters={this.props.addFilters}
getFilterActions={this.props.getFilterActions}
getActionContext={this.props.getActionContext}
onSingleValueTrigger={this.props.onSingleValueTrigger}
showFilterActions={this._showFilterActionsView}
/>
{this._renderActions(geoFields)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ export class MBMap extends React.Component {
addFilters={this.props.addFilters}
getFilterActions={this.props.getFilterActions}
getActionContext={this.props.getActionContext}
onSingleValueTrigger={this.props.onSingleValueTrigger}
geoFields={this.props.geoFields}
renderTooltipContent={this.props.renderTooltipContent}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ export class TooltipControl extends React.Component {
addFilters={this.props.addFilters}
getFilterActions={this.props.getFilterActions}
getActionContext={this.props.getActionContext}
onSingleValueTrigger={this.props.onSingleValueTrigger}
renderTooltipContent={this.props.renderTooltipContent}
geoFields={this.props.geoFields}
features={features}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export class TooltipPopover extends Component {
addFilters: this.props.addFilters,
getFilterActions: this.props.getFilterActions,
getActionContext: this.props.getActionContext,
onSingleValueTrigger: this.props.onSingleValueTrigger,
closeTooltip: this.props.closeTooltip,
features: this.props.features,
isLocked: this.props.isLocked,
Expand Down
36 changes: 34 additions & 2 deletions x-pack/plugins/maps/public/embeddable/map_embeddable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
import { ACTION_GLOBAL_APPLY_FILTER } from '../../../../../src/plugins/data/public';
import {
APPLY_FILTER_TRIGGER,
VALUE_CLICK_TRIGGER,
ActionExecutionContext,
TriggerContextMapping,
} from '../../../../../src/plugins/ui_actions/public';
Expand Down Expand Up @@ -57,6 +58,7 @@ import {
getExistingMapPath,
MAP_SAVED_OBJECT_TYPE,
MAP_PATH,
RawValue,
} from '../../common/constants';
import { RenderToolTipContent } from '../classes/tooltips/tooltip_property';
import { getUiActions, getCoreI18n, getHttp } from '../kibana_services';
Expand All @@ -65,6 +67,7 @@ import { MapContainer } from '../connected_components/map_container';
import { SavedMap } from '../routes/map_page';
import { getIndexPatternsFromIds } from '../index_pattern_util';
import { getMapAttributeService } from '../map_attribute_service';
import { isUrlDrilldown, toValueClickDataFormat } from '../trigger_actions/trigger_utils';

import {
MapByValueInput,
Expand Down Expand Up @@ -202,7 +205,7 @@ export class MapEmbeddable
}

public supportedTriggers(): Array<keyof TriggerContextMapping> {
return [APPLY_FILTER_TRIGGER];
return [APPLY_FILTER_TRIGGER, VALUE_CLICK_TRIGGER];
}

setRenderTooltipContent = (renderTooltipContent: RenderToolTipContent) => {
Expand Down Expand Up @@ -290,6 +293,7 @@ export class MapEmbeddable
<Provider store={this._savedMap.getStore()}>
<I18nContext>
<MapContainer
onSingleValueTrigger={this.onSingleValueTrigger}
addFilters={this.input.hideFilterActions ? null : this.addFilters}
getFilterActions={this.getFilterActions}
getActionContext={this.getActionContext}
Expand Down Expand Up @@ -320,6 +324,20 @@ export class MapEmbeddable
return await getIndexPatternsFromIds(queryableIndexPatternIds);
}

onSingleValueTrigger = (actionId: string, key: string, value: RawValue) => {
const action = getUiActions().getAction(actionId);
if (!action) {
throw new Error('Unable to apply action, could not locate action');
}
const executeContext = {
...this.getActionContext(),
data: {
data: toValueClickDataFormat(key, value),
},
};
action.execute(executeContext);
};

addFilters = async (filters: Filter[], actionId: string = ACTION_GLOBAL_APPLY_FILTER) => {
const executeContext = {
...this.getActionContext(),
Expand All @@ -333,10 +351,24 @@ export class MapEmbeddable
};

getFilterActions = async () => {
return await getUiActions().getTriggerCompatibleActions(APPLY_FILTER_TRIGGER, {
const filterActions = await getUiActions().getTriggerCompatibleActions(APPLY_FILTER_TRIGGER, {
embeddable: this,
filters: [],
});
const valueClickActions = await getUiActions().getTriggerCompatibleActions(
VALUE_CLICK_TRIGGER,
{
embeddable: this,
data: {
// uiActions.getTriggerCompatibleActions validates action with provided context
// so if event.key and event.value are used in the URL template but can not be parsed from context
// then the action is filtered out.
// To prevent filtering out actions, provide dummy context when initially fetching actions.
data: toValueClickDataFormat('anyfield', 'anyvalue'),
},
}
);
return [...filterActions, ...valueClickActions.filter(isUrlDrilldown)];
};

getActionContext = () => {
Expand Down
Loading

0 comments on commit ae35104

Please sign in to comment.