From fbae5534547fe84f4f0e1fa3e177dcdbe60f9235 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 16 Nov 2020 16:31:51 -0700 Subject: [PATCH 01/10] url drilldowns --- .../public/service/ui_actions_service.ts | 6 +- .../features_tooltip/feature_properties.js | 3 +- .../maps/public/embeddable/map_embeddable.tsx | 59 ++++++++++++++++--- 3 files changed, 57 insertions(+), 11 deletions(-) diff --git a/src/plugins/ui_actions/public/service/ui_actions_service.ts b/src/plugins/ui_actions/public/service/ui_actions_service.ts index ec5f3afa19c94..9c85b5d68a36f 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_service.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_service.ts @@ -183,12 +183,12 @@ export class UiActionsService { ): Promise>> => { const actions = this.getTriggerActions!(triggerId); const isCompatibles = await Promise.all( - actions.map((action) => - action.isCompatible({ + actions.map((action) => { + return action.isCompatible({ ...context, trigger: this.getTrigger(triggerId), }) - ) + }) ); return actions.reduce( (acc: Array>, action, i) => diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_properties.js b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_properties.js index edd501f266690..81e8e83f64a48 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_properties.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_properties.js @@ -112,6 +112,7 @@ export class FeatureProperties extends React.Component { }; _renderFilterActions(tooltipProperty) { + console.log(this.state.actions); const panel = { id: 0, items: this.state.actions.map((action) => { @@ -119,7 +120,7 @@ export class FeatureProperties extends React.Component { const iconType = action.getIconType(actionContext); const name = action.getDisplayName(actionContext); return { - name, + name: name ? name : action.id, icon: iconType ? : null, onClick: async () => { this.props.onCloseTooltip(); diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx index caf21431145d5..ca932a5e33d39 100644 --- a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx +++ b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx @@ -202,7 +202,7 @@ export class MapEmbeddable } public supportedTriggers(): Array { - return [APPLY_FILTER_TRIGGER]; + return [APPLY_FILTER_TRIGGER, 'VALUE_CLICK_TRIGGER']; } setRenderTooltipContent = (renderTooltipContent: RenderToolTipContent) => { @@ -321,22 +321,67 @@ export class MapEmbeddable } addFilters = async (filters: Filter[], actionId: string = ACTION_GLOBAL_APPLY_FILTER) => { - const executeContext = { - ...this.getActionContext(), - filters, - }; const action = getUiActions().getAction(actionId); if (!action) { throw new Error('Unable to apply filter, could not locate action'); } - action.execute(executeContext); + if (action.type === 'URL_DRILLDOWN') { + action.execute({ + ...this.getActionContext(), + data: { + data: [ + { + table: { + columns: [{ + id: 'column0', + }], + rows: [{ + id: 'row0', + }], + type: 'datatable' + }, + column: 0, + row: 0, + value: 'US', + } + ] + }, + }); + return; + } + action.execute({ + ...this.getActionContext(), + filters, + }); }; 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: { + data: [ + { + table: { + columns: [{ + id: 'column0', + }], + rows: [{ + id: 'row0', + }], + type: 'datatable' + }, + column: 0, + row: 0, + value: 'fake value', + } + ] + }, + }); + return [ ...filterActions, ...valueClickActions ]; }; getActionContext = () => { From c42118e54ec39bad09850dcb2ae0a9f4441a0558 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 18 Nov 2020 12:03:35 -0700 Subject: [PATCH 02/10] onSingleValueTrigger --- .../classes/tooltips/es_tooltip_property.ts | 4 + .../map_container/map_container.tsx | 4 + .../features_tooltip/feature_properties.js | 53 +++++--- .../features_tooltip/features_tooltip.js | 1 + .../connected_components/mb_map/mb_map.js | 1 + .../mb_map/tooltip_control/tooltip_control.js | 1 + .../mb_map/tooltip_control/tooltip_popover.js | 1 + .../maps/public/connected_components/types.ts | 16 +++ .../maps/public/embeddable/map_embeddable.tsx | 124 +++++++++++------- .../public/trigger_actions/trigger_utils.ts | 11 ++ 10 files changed, 155 insertions(+), 61 deletions(-) create mode 100644 x-pack/plugins/maps/public/connected_components/types.ts create mode 100644 x-pack/plugins/maps/public/trigger_actions/trigger_utils.ts diff --git a/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.ts b/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.ts index 348cd5e552258..cadb528e1f062 100644 --- a/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.ts +++ b/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.ts @@ -25,6 +25,10 @@ export class ESTooltipProperty implements ITooltipProperty { this._field = field; } + getIndexPattern(): IndexPattern { + return this._indexPattern; + } + getPropertyKey(): string { return this._tooltipProperty.getPropertyKey(); } diff --git a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx index 169875e63a536..a90ce1c20d93e 100644 --- a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx +++ b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx @@ -32,6 +32,7 @@ import { RenderToolTipContent } from '../../classes/tooltips/tooltip_property'; import { GeoFieldWithIndex } from '../../components/geo_field_with_index'; import { MapRefreshConfig } from '../../../common/descriptor_types'; import 'mapbox-gl/dist/mapbox-gl.css'; +import { OnSingleValueTriggerParams } from '../types'; const RENDER_COMPLETE_EVENT = 'renderComplete'; @@ -39,6 +40,7 @@ interface Props { addFilters: ((filters: Filter[]) => Promise) | null; getFilterActions?: () => Promise; getActionContext?: () => ActionExecutionContext; + onSingleValueTrigger?: (params: OnSingleValueTriggerParams) => void; areLayersLoaded: boolean; cancelAllInFlightRequests: () => void; exitFullScreen: () => void; @@ -190,6 +192,7 @@ export class MapContainer extends Component { addFilters, getFilterActions, getActionContext, + onSingleValueTrigger, flyoutDisplay, isFullScreen, exitFullScreen, @@ -246,6 +249,7 @@ export class MapContainer extends Component { addFilters={addFilters} getFilterActions={getFilterActions} getActionContext={getActionContext} + onSingleValueTrigger={onSingleValueTrigger} geoFields={this.state.geoFields} renderTooltipContent={renderTooltipContent} /> diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_properties.js b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_properties.js index 81e8e83f64a48..08d98a5677135 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_properties.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_properties.js @@ -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 = { @@ -112,24 +113,44 @@ export class FeatureProperties extends React.Component { }; _renderFilterActions(tooltipProperty) { - console.log(this.state.actions); 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: name ? name : action.id, - icon: 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 ? : null, + onClick: async () => { + this.props.onCloseTooltip(); + + if (isUrlDrilldown(action)) { + this.props.onSingleValueTrigger({ + actionId: action.id, + indexPattern: + 'getIndexPattern' in tooltipProperty + ? tooltipProperty.getIndexPattern() + : undefined, + key: tooltipProperty.getPropertyKey(), + label: tooltipProperty.getPropertyName(), + value: tooltipProperty.getRawValue(), + }); + } else { + const filters = await tooltipProperty.getESFilters(); + this.props.addFilters(filters, action.id); + } + }, + ['data-test-subj']: `mapFilterActionButton__${name}`, + }; + }), }; return ( diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/features_tooltip.js b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/features_tooltip.js index 8547219b42e30..60d9e57d15e23 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/features_tooltip.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/features_tooltip.js @@ -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)} diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.js b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.js index 04c376a093623..0ea40f6e3182f 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.js @@ -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} /> diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.js b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.js index b178eef6fa5d3..c5c3ad4d78f7e 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.js @@ -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} diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_popover.js b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_popover.js index ca4864f79940e..4983e394ed93c 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_popover.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_popover.js @@ -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, diff --git a/x-pack/plugins/maps/public/connected_components/types.ts b/x-pack/plugins/maps/public/connected_components/types.ts new file mode 100644 index 0000000000000..f5774be797605 --- /dev/null +++ b/x-pack/plugins/maps/public/connected_components/types.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RawValue } from '../../common/constants'; +import { IndexPattern } from '../../../../../src/plugins/data/public'; + +export interface OnSingleValueTriggerParams { + actionId: string; + indexPattern?: IndexPattern; + key: string; + label: string; + value: RawValue; +} diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx index ca932a5e33d39..7316902441375 100644 --- a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx +++ b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx @@ -65,6 +65,8 @@ 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 type { OnSingleValueTriggerParams } from '../connected_components/types'; +import { isUrlDrilldown } from '../trigger_actions/trigger_utils'; import { MapByValueInput, @@ -290,6 +292,7 @@ export class MapEmbeddable { + const action = getUiActions().getAction(actionId); + if (!action) { + throw new Error('Unable to apply action, could not locate action'); + } + action.execute({ + ...this.getActionContext(), + data: { + data: [ + { + table: { + columns: [ + { + id: key, + meta: { + name: 'field1', + index: 'indexPattern1', + type: 'number', + }, + name: label, + }, + ], + rows: [ + { + column0: value, + }, + ], + }, + column: 0, + row: 0, + value, + }, + ], + }, + }); + }; + addFilters = async (filters: Filter[], actionId: string = ACTION_GLOBAL_APPLY_FILTER) => { const action = getUiActions().getAction(actionId); if (!action) { throw new Error('Unable to apply filter, could not locate action'); } - if (action.type === 'URL_DRILLDOWN') { - action.execute({ - ...this.getActionContext(), - data: { - data: [ - { - table: { - columns: [{ - id: 'column0', - }], - rows: [{ - id: 'row0', - }], - type: 'datatable' - }, - column: 0, - row: 0, - value: 'US', - } - ] - }, - }); - return; - } action.execute({ ...this.getActionContext(), filters, @@ -360,28 +382,40 @@ export class MapEmbeddable embeddable: this, filters: [], }); - const valueClickActions = await getUiActions().getTriggerCompatibleActions('VALUE_CLICK_TRIGGER', { - embeddable: this, - data: { - data: [ - { - table: { - columns: [{ - id: 'column0', - }], - rows: [{ - id: 'row0', - }], - type: 'datatable' + const valueClickActions = await getUiActions().getTriggerCompatibleActions( + 'VALUE_CLICK_TRIGGER', + { + embeddable: this, + data: { + data: [ + { + table: { + columns: [ + { + id: 'column0', + meta: { + name: 'field1', + index: 'indexPattern1', + type: 'number', + }, + name: 'column0', + }, + ], + rows: [ + { + column0: 'fake value', + }, + ], + }, + column: 0, + row: 0, + value: 'fake value', }, - column: 0, - row: 0, - value: 'fake value', - } - ] - }, - }); - return [ ...filterActions, ...valueClickActions ]; + ], + }, + } + ); + return [...filterActions, ...valueClickActions.filter(isUrlDrilldown)]; }; getActionContext = () => { diff --git a/x-pack/plugins/maps/public/trigger_actions/trigger_utils.ts b/x-pack/plugins/maps/public/trigger_actions/trigger_utils.ts new file mode 100644 index 0000000000000..ba71ef4381835 --- /dev/null +++ b/x-pack/plugins/maps/public/trigger_actions/trigger_utils.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Action } from 'src/plugins/ui_actions/public'; + +export function isUrlDrilldown(action: Action) { + return action.type === 'URL_DRILLDOWN'; +} From d76add21e292ca51d068c163a287842d5eca40b2 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 18 Nov 2020 13:32:58 -0700 Subject: [PATCH 03/10] cleanup --- .../maps/public/embeddable/map_embeddable.tsx | 63 +++---------------- .../public/trigger_actions/trigger_utils.ts | 30 +++++++++ 2 files changed, 40 insertions(+), 53 deletions(-) diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx index 7316902441375..57ff44e6adea4 100644 --- a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx +++ b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx @@ -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'; @@ -66,7 +67,7 @@ import { SavedMap } from '../routes/map_page'; import { getIndexPatternsFromIds } from '../index_pattern_util'; import { getMapAttributeService } from '../map_attribute_service'; import type { OnSingleValueTriggerParams } from '../connected_components/types'; -import { isUrlDrilldown } from '../trigger_actions/trigger_utils'; +import { isUrlDrilldown, toUrlDrilldownDatatable } from '../trigger_actions/trigger_utils'; import { MapByValueInput, @@ -204,7 +205,7 @@ export class MapEmbeddable } public supportedTriggers(): Array { - return [APPLY_FILTER_TRIGGER, 'VALUE_CLICK_TRIGGER']; + return [APPLY_FILTER_TRIGGER, VALUE_CLICK_TRIGGER]; } setRenderTooltipContent = (renderTooltipContent: RenderToolTipContent) => { @@ -337,31 +338,7 @@ export class MapEmbeddable action.execute({ ...this.getActionContext(), data: { - data: [ - { - table: { - columns: [ - { - id: key, - meta: { - name: 'field1', - index: 'indexPattern1', - type: 'number', - }, - name: label, - }, - ], - rows: [ - { - column0: value, - }, - ], - }, - column: 0, - row: 0, - value, - }, - ], + data: toUrlDrilldownDatatable(key, value), }, }); }; @@ -383,35 +360,15 @@ export class MapEmbeddable filters: [], }); const valueClickActions = await getUiActions().getTriggerCompatibleActions( - 'VALUE_CLICK_TRIGGER', + VALUE_CLICK_TRIGGER, { embeddable: this, data: { - data: [ - { - table: { - columns: [ - { - id: 'column0', - meta: { - name: 'field1', - index: 'indexPattern1', - type: 'number', - }, - name: 'column0', - }, - ], - rows: [ - { - column0: 'fake value', - }, - ], - }, - column: 0, - row: 0, - value: 'fake value', - }, - ], + // uiActions.getTriggerCompatibleActions validates action againts 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: toUrlDrilldownDatatable('anyfield', 'anyvalue'), }, } ); diff --git a/x-pack/plugins/maps/public/trigger_actions/trigger_utils.ts b/x-pack/plugins/maps/public/trigger_actions/trigger_utils.ts index ba71ef4381835..577e55efa1935 100644 --- a/x-pack/plugins/maps/public/trigger_actions/trigger_utils.ts +++ b/x-pack/plugins/maps/public/trigger_actions/trigger_utils.ts @@ -9,3 +9,33 @@ import { Action } from 'src/plugins/ui_actions/public'; export function isUrlDrilldown(action: Action) { return action.type === 'URL_DRILLDOWN'; } + +// VALUE_CLICK_TRIGGER is coupled with expressions and Datatable type +// URL drilldown parses event scope from Datatable +// https://github.com/elastic/kibana/blob/master/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts#L151 +// In order to use URL drilldown, maps has to package its data in Datatable compatiable format. +export function toUrlDrilldownDatatable(key: string, value: RawValue) { + return [ + { + table: { + columns: [ + { + id: key, + meta: { + field: key, + }, + name: key, + }, + ], + rows: [ + { + column0: value, + }, + ], + }, + column: 0, + row: 0, + value, + }, + ]; +} From 658ddc3f0b2dda492ab71c65d78cf6dbb3ed1577 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 18 Nov 2020 15:21:34 -0700 Subject: [PATCH 04/10] tslint --- .../map_container/map_container.tsx | 5 ++- .../features_tooltip/feature_properties.js | 15 +++----- .../maps/public/embeddable/map_embeddable.tsx | 34 ++++++++----------- .../public/trigger_actions/trigger_utils.ts | 10 ++++-- 4 files changed, 29 insertions(+), 35 deletions(-) diff --git a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx index a90ce1c20d93e..ee1cf9b1bd81a 100644 --- a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx +++ b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx @@ -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'; @@ -32,7 +32,6 @@ import { RenderToolTipContent } from '../../classes/tooltips/tooltip_property'; import { GeoFieldWithIndex } from '../../components/geo_field_with_index'; import { MapRefreshConfig } from '../../../common/descriptor_types'; import 'mapbox-gl/dist/mapbox-gl.css'; -import { OnSingleValueTriggerParams } from '../types'; const RENDER_COMPLETE_EVENT = 'renderComplete'; @@ -40,7 +39,7 @@ interface Props { addFilters: ((filters: Filter[]) => Promise) | null; getFilterActions?: () => Promise; getActionContext?: () => ActionExecutionContext; - onSingleValueTrigger?: (params: OnSingleValueTriggerParams) => void; + onSingleValueTrigger?: (actionId: string, key: string, value: RawValue) => void; areLayersLoaded: boolean; cancelAllInFlightRequests: () => void; exitFullScreen: () => void; diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_properties.js b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_properties.js index 08d98a5677135..97b47358ec089 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_properties.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_properties.js @@ -133,16 +133,11 @@ export class FeatureProperties extends React.Component { this.props.onCloseTooltip(); if (isUrlDrilldown(action)) { - this.props.onSingleValueTrigger({ - actionId: action.id, - indexPattern: - 'getIndexPattern' in tooltipProperty - ? tooltipProperty.getIndexPattern() - : undefined, - key: tooltipProperty.getPropertyKey(), - label: tooltipProperty.getPropertyName(), - value: tooltipProperty.getRawValue(), - }); + this.props.onSingleValueTrigger( + action.id, + tooltipProperty.getPropertyKey(), + tooltipProperty.getRawValue() + ); } else { const filters = await tooltipProperty.getESFilters(); this.props.addFilters(filters, action.id); diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx index 57ff44e6adea4..7aaabc427790a 100644 --- a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx +++ b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx @@ -58,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'; @@ -66,8 +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 type { OnSingleValueTriggerParams } from '../connected_components/types'; -import { isUrlDrilldown, toUrlDrilldownDatatable } from '../trigger_actions/trigger_utils'; +import { isUrlDrilldown, toValueClickDataFormat } from '../trigger_actions/trigger_utils'; import { MapByValueInput, @@ -324,34 +324,30 @@ export class MapEmbeddable return await getIndexPatternsFromIds(queryableIndexPatternIds); } - onSingleValueTrigger = ({ - actionId, - key, - label, - value, - indexPattern, - }: OnSingleValueTriggerParams) => { + 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'); } - action.execute({ + const executeContext = { ...this.getActionContext(), data: { - data: toUrlDrilldownDatatable(key, value), + data: toValueClickDataFormat(key, value), }, - }); + }; + action.execute(executeContext); }; addFilters = async (filters: Filter[], actionId: string = ACTION_GLOBAL_APPLY_FILTER) => { + const executeContext = { + ...this.getActionContext(), + filters, + }; const action = getUiActions().getAction(actionId); if (!action) { throw new Error('Unable to apply filter, could not locate action'); } - action.execute({ - ...this.getActionContext(), - filters, - }); + action.execute(executeContext); }; getFilterActions = async () => { @@ -364,11 +360,11 @@ export class MapEmbeddable { embeddable: this, data: { - // uiActions.getTriggerCompatibleActions validates action againts context + // 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: toUrlDrilldownDatatable('anyfield', 'anyvalue'), + // To prevent filtering out actions, provide dummy context when initially fetching actions. + data: toValueClickDataFormat('anyfield', 'anyvalue'), }, } ); diff --git a/x-pack/plugins/maps/public/trigger_actions/trigger_utils.ts b/x-pack/plugins/maps/public/trigger_actions/trigger_utils.ts index 577e55efa1935..3505588a9c049 100644 --- a/x-pack/plugins/maps/public/trigger_actions/trigger_utils.ts +++ b/x-pack/plugins/maps/public/trigger_actions/trigger_utils.ts @@ -5,16 +5,19 @@ */ import { Action } from 'src/plugins/ui_actions/public'; +import { RawValue } from '../../common/constants'; +import { DatatableColumnType } from '../../../../../src/plugins/expressions'; export function isUrlDrilldown(action: Action) { + // @ts-expect-error return action.type === 'URL_DRILLDOWN'; } // VALUE_CLICK_TRIGGER is coupled with expressions and Datatable type // URL drilldown parses event scope from Datatable -// https://github.com/elastic/kibana/blob/master/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts#L151 +// https://github.com/elastic/kibana/blob/7.10/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts#L140 // In order to use URL drilldown, maps has to package its data in Datatable compatiable format. -export function toUrlDrilldownDatatable(key: string, value: RawValue) { +export function toValueClickDataFormat(key: string, value: RawValue) { return [ { table: { @@ -22,6 +25,7 @@ export function toUrlDrilldownDatatable(key: string, value: RawValue) { { id: key, meta: { + type: 'unknown' as DatatableColumnType, // type is not used by URL drilldown to parse event but is required by DatatableColumnMeta field: key, }, name: key, @@ -29,7 +33,7 @@ export function toUrlDrilldownDatatable(key: string, value: RawValue) { ], rows: [ { - column0: value, + [key]: value, }, ], }, From 52a71c85e970c89b81202de0b58d33d88a35c938 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 18 Nov 2020 15:25:25 -0700 Subject: [PATCH 05/10] revert changes to ui_actions_service --- src/plugins/ui_actions/public/service/ui_actions_service.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/ui_actions/public/service/ui_actions_service.ts b/src/plugins/ui_actions/public/service/ui_actions_service.ts index 9c85b5d68a36f..ec5f3afa19c94 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_service.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_service.ts @@ -183,12 +183,12 @@ export class UiActionsService { ): Promise>> => { const actions = this.getTriggerActions!(triggerId); const isCompatibles = await Promise.all( - actions.map((action) => { - return action.isCompatible({ + actions.map((action) => + action.isCompatible({ ...context, trigger: this.getTrigger(triggerId), }) - }) + ) ); return actions.reduce( (acc: Array>, action, i) => From 844037b309e439caab72389e85d93e6f1992e53b Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 18 Nov 2020 15:27:26 -0700 Subject: [PATCH 06/10] remove unused method added to es_tooltip_property --- .../maps/public/classes/tooltips/es_tooltip_property.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.ts b/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.ts index cadb528e1f062..348cd5e552258 100644 --- a/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.ts +++ b/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.ts @@ -25,10 +25,6 @@ export class ESTooltipProperty implements ITooltipProperty { this._field = field; } - getIndexPattern(): IndexPattern { - return this._indexPattern; - } - getPropertyKey(): string { return this._tooltipProperty.getPropertyKey(); } From 5354629b95db2b100507ef93555aa1c133c25d10 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 18 Nov 2020 15:32:31 -0700 Subject: [PATCH 07/10] remove unused file --- .../maps/public/connected_components/types.ts | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 x-pack/plugins/maps/public/connected_components/types.ts diff --git a/x-pack/plugins/maps/public/connected_components/types.ts b/x-pack/plugins/maps/public/connected_components/types.ts deleted file mode 100644 index f5774be797605..0000000000000 --- a/x-pack/plugins/maps/public/connected_components/types.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { RawValue } from '../../common/constants'; -import { IndexPattern } from '../../../../../src/plugins/data/public'; - -export interface OnSingleValueTriggerParams { - actionId: string; - indexPattern?: IndexPattern; - key: string; - label: string; - value: RawValue; -} From a38da1a586b555531d1ba104b8ec5c401d4391ef Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 18 Nov 2020 15:37:41 -0700 Subject: [PATCH 08/10] update drilldown docs to reflect maps supports URL drilldowns --- docs/user/dashboard/drilldowns.asciidoc | 42 ++++++++++++------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/user/dashboard/drilldowns.asciidoc b/docs/user/dashboard/drilldowns.asciidoc index ca788020d9286..1b9896d7dea56 100644 --- a/docs/user/dashboard/drilldowns.asciidoc +++ b/docs/user/dashboard/drilldowns.asciidoc @@ -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]. @@ -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. @@ -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 <> you use to create a <> depends on the trigger you choose. URL drilldowns support these types of triggers: * *Single click* — A single data point in the visualization. * *Range selection* — 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: @@ -77,20 +77,20 @@ The following panels support dashboard and URL drilldowns. ^| X | Controls -^| -^| +^| +^| | Data Table ^| X ^| X | Gauge -^| -^| +^| +^| | Goal -^| -^| +^| +^| | Heat map ^| X @@ -106,15 +106,15 @@ The following panels support dashboard and URL drilldowns. | Maps ^| X -^| +^| X | Markdown -^| -^| +^| +^| | Metric -^| -^| +^| +^| | Pie ^| X @@ -122,7 +122,7 @@ The following panels support dashboard and URL drilldowns. | TSVB ^| X -^| +^| | Tag Cloud ^| X @@ -130,11 +130,11 @@ The following panels support dashboard and URL drilldowns. | Timelion ^| X -^| +^| | Vega ^| X -^| +^| | Vertical Bar ^| X @@ -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. + @@ -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] ---- @@ -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. + From d244f58682e5a802126c438d2595230e51745ce6 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Thu, 19 Nov 2020 08:23:22 -0700 Subject: [PATCH 09/10] add functional test case for URL drilldown --- .../maps/embeddable/tooltip_filter_actions.js | 21 ++++++++++++------- .../es_archives/maps/kibana/data.json | 11 +++++++--- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js b/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js index 10754d20118e9..d612a3776d211 100644 --- a/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js +++ b/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; export default function ({ getPageObjects, getService }) { - const PageObjects = getPageObjects(['common', 'dashboard', 'maps']); + const PageObjects = getPageObjects(['common', 'dashboard', 'discover', 'maps']); const kibanaServer = getService('kibanaServer'); const testSubjects = getService('testSubjects'); const filterBar = getService('filterBar'); @@ -48,16 +48,11 @@ export default function ({ getPageObjects, getService }) { }); describe('panel actions', () => { - before(async () => { + beforeEach(async () => { await loadDashboardAndOpenTooltip(); }); - it('should display more actions button when tooltip is locked', async () => { - const exists = await testSubjects.exists('mapTooltipMoreActionsButton'); - expect(exists).to.be(true); - }); - - it('should trigger drilldown action when clicked', async () => { + it('should trigger dashboard drilldown action when clicked', async () => { await testSubjects.click('mapTooltipMoreActionsButton'); await testSubjects.click('mapFilterActionButton__drilldown1'); @@ -69,6 +64,16 @@ export default function ({ getPageObjects, getService }) { const hasJoinFilter = await filterBar.hasFilter('shape_name', 'charlie'); expect(hasJoinFilter).to.be(true); }); + + it('should trigger url drilldown action when clicked', async () => { + await testSubjects.click('mapTooltipMoreActionsButton'); + await testSubjects.click('mapFilterActionButton__urlDrilldownToDiscover'); + + // Assert on discover with filter from action + await PageObjects.discover.waitForDiscoverAppOnScreen(); + const hasFilter = await filterBar.hasFilter('name', 'charlie'); + expect(hasFilter).to.be(true); + }); }); }); } diff --git a/x-pack/test/functional/es_archives/maps/kibana/data.json b/x-pack/test/functional/es_archives/maps/kibana/data.json index 79e8c14cc3982..71b4a85d63f08 100644 --- a/x-pack/test/functional/es_archives/maps/kibana/data.json +++ b/x-pack/test/functional/es_archives/maps/kibana/data.json @@ -1113,7 +1113,7 @@ "title" : "dash for tooltip filter action test", "hits" : 0, "description" : "Zoomed in so entire screen is covered by filter so click to open tooltip can not miss.", - "panelsJSON" : "[{\"version\":\"8.0.0\",\"gridData\":{\"x\":0,\"y\":0,\"w\":48,\"h\":26,\"i\":\"1\"},\"panelIndex\":\"1\",\"embeddableConfig\":{\"mapCenter\":{\"lat\":-1.31919,\"lon\":59.53306,\"zoom\":9.67},\"isLayerTOCOpen\":false,\"openTOCDetails\":[\"n1t6f\"],\"hiddenLayers\":[],\"enhancements\":{\"dynamicActions\":{\"events\":[{\"eventId\":\"669a3521-1215-4228-9ced-77e2edf5ad17\",\"triggers\":[\"FILTER_TRIGGER\"],\"action\":{\"name\":\"drilldown1\",\"config\":{\"dashboardId\":\"19906970-2e40-11e9-85cb-6965aae20f13\",\"useCurrentFilters\":true,\"useCurrentDateRange\":true},\"factoryId\":\"DASHBOARD_TO_DASHBOARD_DRILLDOWN\"}}]}}},\"panelRefName\":\"panel_0\"}]", + "panelsJSON" : "[{\"version\":\"8.0.0\",\"gridData\":{\"x\":0,\"y\":0,\"w\":48,\"h\":26,\"i\":\"1\"},\"panelIndex\":\"1\",\"embeddableConfig\":{\"mapCenter\":{\"lat\":-1.31919,\"lon\":59.53306,\"zoom\":9.67},\"isLayerTOCOpen\":false,\"openTOCDetails\":[\"n1t6f\"],\"hiddenLayers\":[],\"enhancements\":{\"dynamicActions\":{\"events\":[{\"eventId\":\"669a3521-1215-4228-9ced-77e2edf5ad17\",\"triggers\":[\"FILTER_TRIGGER\"],\"action\":{\"name\":\"drilldown1\",\"config\":{\"useCurrentFilters\":true,\"useCurrentDateRange\":true},\"factoryId\":\"DASHBOARD_TO_DASHBOARD_DRILLDOWN\"}},{\"eventId\":\"b9c20d96-03ce-4dcc-8823-e3503311172e\",\"triggers\":[\"VALUE_CLICK_TRIGGER\"],\"action\":{\"name\":\"urlDrilldownToDiscover\",\"config\":{\"url\":{\"template\":\"{{kibanaUrl}}/app/discover#/?_a=(columns:!(_source),filters:!(('$state':(store:appState),meta:(alias:!n,disabled:!f,index:'561253e0-f731-11e8-8487-11b9dd924f96',key:{{event.key}},negate:!f,params:(query:{{event.value}}),type:phrase),query:(match_phrase:({{event.key}}:{{event.value}})))),index:'561253e0-f731-11e8-8487-11b9dd924f96',interval:auto,query:(language:kuery,query:''),sort:!())\"},\"openInNewTab\":false},\"factoryId\":\"URL_DRILLDOWN\"}}]}}},\"panelRefName\":\"panel_0\"}]", "optionsJSON" : "{\"useMargins\":true,\"hidePanelTitles\":false}", "version" : 1, "timeRestore" : true, @@ -1129,6 +1129,11 @@ }, "type" : "dashboard", "references" : [ + { + "name" : "drilldown:DASHBOARD_TO_DASHBOARD_DRILLDOWN:669a3521-1215-4228-9ced-77e2edf5ad17:dashboardId", + "type" : "dashboard", + "id" : "19906970-2e40-11e9-85cb-6965aae20f13" + }, { "name" : "panel_0", "type" : "map", @@ -1136,9 +1141,9 @@ } ], "migrationVersion" : { - "dashboard" : "7.3.0" + "dashboard" : "7.11.0" }, - "updated_at" : "2020-08-26T14:32:27.854Z" + "updated_at" : "2020-11-19T15:12:25.703Z" } } } From ff9ab05a7be3eef518373f1cf159459eb0807f89 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Fri, 20 Nov 2020 13:18:14 -0700 Subject: [PATCH 10/10] do not show URL drilldowns in geometry filter action selection --- x-pack/plugins/maps/public/components/action_select.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/maps/public/components/action_select.tsx b/x-pack/plugins/maps/public/components/action_select.tsx index ad61a6a129974..8ea9334bba753 100644 --- a/x-pack/plugins/maps/public/components/action_select.tsx +++ b/x-pack/plugins/maps/public/components/action_select.tsx @@ -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; @@ -41,7 +42,7 @@ export class ActionSelect extends Component { } const actions = await this.props.getFilterActions(); if (this._isMounted) { - this.setState({ actions }); + this.setState({ actions: actions.filter((action) => !isUrlDrilldown(action)) }); } }