From 400f6049f3c70a206274eda8fa7fd289a5112555 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Fri, 14 Aug 2020 18:38:05 +0300 Subject: [PATCH] Drilldowns for TSVB / Vega / Timelion (#74848) (#75037) * Drilldowns for TSVB / Vega Closes: #60611 * fix PR comment * fix PR comments * add support for Timelion * rename vis.API.events.brush -> vis.API.events.applyFilter --- .../public/components/chart.tsx | 2 + .../public/components/panel.tsx | 22 ++++++++--- .../public/components/timelion_vis.tsx | 1 + .../public/timelion_vis_type.tsx | 5 +++ .../application/components/vis_editor.js | 2 +- ...r.test.js => create_brush_handler.test.ts} | 37 ++++++++++--------- ...ush_handler.js => create_brush_handler.ts} | 25 +++++++++---- .../public/metrics_type.ts | 4 ++ src/plugins/vis_type_vega/public/vega_type.ts | 4 ++ .../public/vega_view/vega_base_view.js | 21 ++++++++++- .../public/vega_visualization.js | 1 + .../public/vega_visualization.test.js | 5 +++ .../public/embeddable/events.ts | 8 +++- .../public/embeddable/visualize_embeddable.ts | 21 ++++++++--- .../visualizations/public/expressions/vis.ts | 5 +++ src/plugins/visualizations/public/index.ts | 1 + 16 files changed, 124 insertions(+), 40 deletions(-) rename src/plugins/vis_type_timeseries/public/application/lib/{create_brush_handler.test.js => create_brush_handler.test.ts} (58%) rename src/plugins/vis_type_timeseries/public/application/lib/{create_brush_handler.js => create_brush_handler.ts} (68%) diff --git a/src/plugins/vis_type_timelion/public/components/chart.tsx b/src/plugins/vis_type_timelion/public/components/chart.tsx index a8b03bdbc8b7e5..15a376d4e96386 100644 --- a/src/plugins/vis_type_timelion/public/components/chart.tsx +++ b/src/plugins/vis_type_timelion/public/components/chart.tsx @@ -21,8 +21,10 @@ import React from 'react'; import { Sheet } from '../helpers/timelion_request_handler'; import { Panel } from './panel'; +import { ExprVisAPIEvents } from '../../../visualizations/public'; interface ChartComponentProp { + applyFilter: ExprVisAPIEvents['applyFilter']; interval: string; renderComplete(): void; seriesList: Sheet; diff --git a/src/plugins/vis_type_timelion/public/components/panel.tsx b/src/plugins/vis_type_timelion/public/components/panel.tsx index f4f1cd84613bea..9c30a6b75d6dbc 100644 --- a/src/plugins/vis_type_timelion/public/components/panel.tsx +++ b/src/plugins/vis_type_timelion/public/components/panel.tsx @@ -33,10 +33,12 @@ import { colors, Axis, } from '../helpers/panel_utils'; + import { Series, Sheet } from '../helpers/timelion_request_handler'; import { tickFormatters } from '../helpers/tick_formatters'; import { generateTicksProvider } from '../helpers/tick_generator'; import { TimelionVisDependencies } from '../plugin'; +import { ExprVisAPIEvents } from '../../../visualizations/public'; interface CrosshairPlot extends jquery.flot.plot { setCrosshair: (pos: Position) => void; @@ -44,6 +46,7 @@ interface CrosshairPlot extends jquery.flot.plot { } interface PanelProps { + applyFilter: ExprVisAPIEvents['applyFilter']; interval: string; seriesList: Sheet; renderComplete(): void; @@ -72,7 +75,7 @@ const DEBOUNCE_DELAY = 50; // ensure legend is the same height with or without a caption so legend items do not move around const emptyCaption = '
'; -function Panel({ interval, seriesList, renderComplete }: PanelProps) { +function Panel({ interval, seriesList, renderComplete, applyFilter }: PanelProps) { const kibana = useKibana(); const [chart, setChart] = useState(() => cloneDeep(seriesList.list)); const [canvasElem, setCanvasElem] = useState(); @@ -346,12 +349,21 @@ function Panel({ interval, seriesList, renderComplete }: PanelProps) { const plotSelectedHandler = useCallback( (event: JQuery.TriggeredEvent, ranges: Ranges) => { - kibana.services.timefilter.setTime({ - from: moment(ranges.xaxis.from), - to: moment(ranges.xaxis.to), + applyFilter({ + timeFieldName: '*', + filters: [ + { + range: { + '*': { + gte: ranges.xaxis.from, + lte: ranges.xaxis.to, + }, + }, + }, + ], }); }, - [kibana.services.timefilter] + [applyFilter] ); useEffect(() => { diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis.tsx index 4bb07fe74ee821..aa594c749b600e 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis.tsx @@ -38,6 +38,7 @@ function TimelionVisComponent(props: TimelionVisComponentProp) { return (
{ + return [VIS_EVENT_TO_TRIGGER.applyFilter]; + }, options: { showIndexSelection: false, showQueryBar: false, diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_editor.js b/src/plugins/vis_type_timeseries/public/application/components/vis_editor.js index 300e70f3ae0c00..50585869862eea 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_editor.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_editor.js @@ -50,7 +50,7 @@ export class VisEditor extends Component { visFields: props.visFields, extractedIndexPatterns: [''], }; - this.onBrush = createBrushHandler(getDataStart().query.timefilter.timefilter); + this.onBrush = createBrushHandler((data) => props.vis.API.events.applyFilter(data)); this.visDataSubject = new Rx.BehaviorSubject(this.props.visData); this.visData$ = this.visDataSubject.asObservable().pipe(share()); diff --git a/src/plugins/vis_type_timeseries/public/application/lib/create_brush_handler.test.js b/src/plugins/vis_type_timeseries/public/application/lib/create_brush_handler.test.ts similarity index 58% rename from src/plugins/vis_type_timeseries/public/application/lib/create_brush_handler.test.js rename to src/plugins/vis_type_timeseries/public/application/lib/create_brush_handler.test.ts index 6ae01a384e7cae..a9568b5be9d3fa 100644 --- a/src/plugins/vis_type_timeseries/public/application/lib/create_brush_handler.test.js +++ b/src/plugins/vis_type_timeseries/public/application/lib/create_brush_handler.test.ts @@ -18,28 +18,31 @@ */ import { createBrushHandler } from './create_brush_handler'; -import moment from 'moment'; +import { ExprVisAPIEvents } from '../../../../visualizations/public'; describe('brushHandler', () => { - let mockTimefilter; - let onBrush; + let onBrush: ReturnType; + let applyFilter: ExprVisAPIEvents['applyFilter']; beforeEach(() => { - mockTimefilter = { - time: {}, - setTime: function (time) { - this.time = time; - }, - }; - onBrush = createBrushHandler(mockTimefilter); + applyFilter = jest.fn(); + + onBrush = createBrushHandler(applyFilter); }); - it('returns brushHandler() that updates timefilter', () => { - const from = '2017-01-01T00:00:00Z'; - const to = '2017-01-01T00:10:00Z'; - onBrush(from, to); - expect(mockTimefilter.time.from).toEqual(moment(from).toISOString()); - expect(mockTimefilter.time.to).toEqual(moment(to).toISOString()); - expect(mockTimefilter.time.mode).toEqual('absolute'); + test('returns brushHandler() should updates timefilter through vis.API.events.applyFilter', () => { + const gte = '2017-01-01T00:00:00Z'; + const lte = '2017-01-01T00:10:00Z'; + + onBrush(gte, lte); + + expect(applyFilter).toHaveBeenCalledWith({ + timeFieldName: '*', + filters: [ + { + range: { '*': { gte: '2017-01-01T00:00:00Z', lte: '2017-01-01T00:10:00Z' } }, + }, + ], + }); }); }); diff --git a/src/plugins/vis_type_timeseries/public/application/lib/create_brush_handler.js b/src/plugins/vis_type_timeseries/public/application/lib/create_brush_handler.ts similarity index 68% rename from src/plugins/vis_type_timeseries/public/application/lib/create_brush_handler.js rename to src/plugins/vis_type_timeseries/public/application/lib/create_brush_handler.ts index 452e85c6405fe8..38002c75529523 100644 --- a/src/plugins/vis_type_timeseries/public/application/lib/create_brush_handler.js +++ b/src/plugins/vis_type_timeseries/public/application/lib/create_brush_handler.ts @@ -17,14 +17,23 @@ * under the License. */ -import moment from 'moment'; +import { ExprVisAPIEvents } from '../../../../visualizations/public'; -const TIME_MODE = 'absolute'; - -export const createBrushHandler = (timefilter) => (from, to) => { - timefilter.setTime({ - from: moment(from).toISOString(), - to: moment(to).toISOString(), - mode: TIME_MODE, +export const createBrushHandler = (applyFilter: ExprVisAPIEvents['applyFilter']) => ( + gte: string, + lte: string +) => { + return applyFilter({ + timeFieldName: '*', + filters: [ + { + range: { + '*': { + gte, + lte, + }, + }, + }, + ], }); }; diff --git a/src/plugins/vis_type_timeseries/public/metrics_type.ts b/src/plugins/vis_type_timeseries/public/metrics_type.ts index 44b0334a37871f..d6621870fef67e 100644 --- a/src/plugins/vis_type_timeseries/public/metrics_type.ts +++ b/src/plugins/vis_type_timeseries/public/metrics_type.ts @@ -25,6 +25,7 @@ import { EditorController } from './application'; // @ts-ignore import { PANEL_TYPES } from '../common/panel_types'; import { VisEditor } from './application/components/vis_editor_lazy'; +import { VIS_EVENT_TO_TRIGGER } from '../../visualizations/public'; export const metricsVisDefinition = { name: 'metrics', @@ -78,6 +79,9 @@ export const metricsVisDefinition = { showIndexSelection: false, }, requestHandler: metricsRequestHandler, + getSupportedTriggers: () => { + return [VIS_EVENT_TO_TRIGGER.applyFilter]; + }, inspectorAdapters: {}, responseHandler: 'none', }; diff --git a/src/plugins/vis_type_vega/public/vega_type.ts b/src/plugins/vis_type_vega/public/vega_type.ts index d69eb3cfba282d..f49816017b6843 100644 --- a/src/plugins/vis_type_vega/public/vega_type.ts +++ b/src/plugins/vis_type_vega/public/vega_type.ts @@ -27,6 +27,7 @@ import { createVegaRequestHandler } from './vega_request_handler'; import { createVegaVisualization } from './vega_visualization'; import { getDefaultSpec } from './default_spec'; import { createInspectorAdapters } from './vega_inspector'; +import { VIS_EVENT_TO_TRIGGER } from '../../visualizations/public'; export const createVegaTypeDefinition = (dependencies: VegaVisualizationDependencies) => { const requestHandler = createVegaRequestHandler(dependencies); @@ -54,6 +55,9 @@ export const createVegaTypeDefinition = (dependencies: VegaVisualizationDependen showQueryBar: true, showFilterBar: true, }, + getSupportedTriggers: () => { + return [VIS_EVENT_TO_TRIGGER.applyFilter]; + }, stage: 'experimental', inspectorAdapters: createInspectorAdapters, }; diff --git a/src/plugins/vis_type_vega/public/vega_view/vega_base_view.js b/src/plugins/vis_type_vega/public/vega_view/vega_base_view.js index 4596b473644942..a2a973d232de08 100644 --- a/src/plugins/vis_type_vega/public/vega_view/vega_base_view.js +++ b/src/plugins/vis_type_vega/public/vega_view/vega_base_view.js @@ -63,6 +63,7 @@ export class VegaBaseView { this._parser = opts.vegaParser; this._serviceSettings = opts.serviceSettings; this._filterManager = opts.filterManager; + this._applyFilter = opts.applyFilter; this._timefilter = opts.timefilter; this._findIndex = opts.findIndex; this._view = null; @@ -263,7 +264,8 @@ export class VegaBaseView { async addFilterHandler(query, index) { const indexId = await this._findIndex(index); const filter = esFilters.buildQueryFilter(query, indexId); - this._filterManager.addFilters(filter); + + this._applyFilter({ filters: [filter] }); } /** @@ -298,7 +300,22 @@ export class VegaBaseView { * @param {number|string|Date} end */ setTimeFilterHandler(start, end) { - this._timefilter.setTime(VegaBaseView._parseTimeRange(start, end)); + const { from, to, mode } = VegaBaseView._parseTimeRange(start, end); + + this._applyFilter({ + timeFieldName: '*', + filters: [ + { + range: { + '*': { + mode, + gte: from, + lte: to, + }, + }, + }, + ], + }); } /** diff --git a/src/plugins/vis_type_vega/public/vega_visualization.js b/src/plugins/vis_type_vega/public/vega_visualization.js index 1fcb89f04457da..d6db0f9ea239f9 100644 --- a/src/plugins/vis_type_vega/public/vega_visualization.js +++ b/src/plugins/vis_type_vega/public/vega_visualization.js @@ -106,6 +106,7 @@ export const createVegaVisualization = ({ serviceSettings }) => const { timefilter } = this.dataPlugin.query.timefilter; const vegaViewParams = { parentEl: this._el, + applyFilter: this._vis.API.events.applyFilter, vegaParser, serviceSettings, filterManager, diff --git a/src/plugins/vis_type_vega/public/vega_visualization.test.js b/src/plugins/vis_type_vega/public/vega_visualization.test.js index 3e318fa22c195e..0912edf9503a6d 100644 --- a/src/plugins/vis_type_vega/public/vega_visualization.test.js +++ b/src/plugins/vis_type_vega/public/vega_visualization.test.js @@ -105,6 +105,11 @@ describe('VegaVisualizations', () => { vis = { type: vegaVisType, + API: { + events: { + applyFilter: jest.fn(), + }, + }, }; }); diff --git a/src/plugins/visualizations/public/embeddable/events.ts b/src/plugins/visualizations/public/embeddable/events.ts index 0957895a214036..52cac59fbffaa4 100644 --- a/src/plugins/visualizations/public/embeddable/events.ts +++ b/src/plugins/visualizations/public/embeddable/events.ts @@ -17,14 +17,20 @@ * under the License. */ -import { SELECT_RANGE_TRIGGER, VALUE_CLICK_TRIGGER } from '../../../../plugins/ui_actions/public'; +import { + APPLY_FILTER_TRIGGER, + SELECT_RANGE_TRIGGER, + VALUE_CLICK_TRIGGER, +} from '../../../../plugins/ui_actions/public'; export interface VisEventToTrigger { + ['applyFilter']: typeof APPLY_FILTER_TRIGGER; ['brush']: typeof SELECT_RANGE_TRIGGER; ['filter']: typeof VALUE_CLICK_TRIGGER; } export const VIS_EVENT_TO_TRIGGER: VisEventToTrigger = { + applyFilter: APPLY_FILTER_TRIGGER, brush: SELECT_RANGE_TRIGGER, filter: VALUE_CLICK_TRIGGER, }; diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts index 749926e1abd00b..031f628573ea67 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts @@ -301,12 +301,21 @@ export class VisualizeEmbeddable extends Embeddable void; brush: (data: any) => void; + applyFilter: (data: any) => void; } export interface ExprVisAPI { @@ -83,6 +84,10 @@ export class ExprVis extends EventEmitter { if (!this.eventsSubject) return; this.eventsSubject.next({ name: 'brush', data }); }, + applyFilter: (data: any) => { + if (!this.eventsSubject) return; + this.eventsSubject.next({ name: 'applyFilter', data }); + }, }, }; } diff --git a/src/plugins/visualizations/public/index.ts b/src/plugins/visualizations/public/index.ts index 2ac53c2c81acc9..49cfbe76aa9d0a 100644 --- a/src/plugins/visualizations/public/index.ts +++ b/src/plugins/visualizations/public/index.ts @@ -51,5 +51,6 @@ export { VisSavedObject, VisResponseValue, } from './types'; +export { ExprVisAPIEvents } from './expressions/vis'; export { VisualizationListItem } from './vis_types/vis_type_alias_registry'; export { VISUALIZE_ENABLE_LABS_SETTING } from '../common/constants';