From f23b5fd0dcafa42911fe18a2e2c1ef126faf1e40 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Thu, 18 Jun 2020 15:00:45 +0300 Subject: [PATCH 1/3] drilldown tsvb --- .../create_filters_from_range_select.test.ts | 4 +- .../create_filters_from_range_select.ts | 9 ++++- src/plugins/embeddable/public/index.ts | 4 ++ .../public/lib/triggers/triggers.ts | 37 +++++++++++++++---- .../vis_type_timeseries/common/constants.ts | 2 + .../application/components/vis_editor.js | 3 +- ...ush_handler.js => create_brush_handler.ts} | 31 ++++++++++++---- .../public/embeddable/visualize_embeddable.ts | 3 +- .../flyout_create_drilldown.tsx | 5 ++- 9 files changed, 78 insertions(+), 20 deletions(-) rename src/plugins/vis_type_timeseries/public/application/lib/{create_brush_handler.js => create_brush_handler.ts} (52%) diff --git a/src/plugins/data/public/actions/filters/create_filters_from_range_select.test.ts b/src/plugins/data/public/actions/filters/create_filters_from_range_select.test.ts index 5d21b395b994f..b522e6ce7adb6 100644 --- a/src/plugins/data/public/actions/filters/create_filters_from_range_select.test.ts +++ b/src/plugins/data/public/actions/filters/create_filters_from_range_select.test.ts @@ -25,12 +25,12 @@ import { IndexPatternsContract, RangeFilter } from '../../../public'; import { dataPluginMock } from '../../../public/mocks'; import { setIndexPatterns } from '../../../public/services'; import { mockDataServices } from '../../../public/search/aggs/test_helpers'; -import { TriggerContextMapping } from '../../../../ui_actions/public'; +import { RangeSelectTriggerContextTableDataEvent } from '../../../../embeddable/public/lib/triggers/triggers'; describe('brushEvent', () => { const DAY_IN_MS = 24 * 60 * 60 * 1000; const JAN_01_2014 = 1388559600000; - let baseEvent: TriggerContextMapping['SELECT_RANGE_TRIGGER']['data']; + let baseEvent: RangeSelectTriggerContextTableDataEvent; const indexPattern = { id: 'indexPatternId', diff --git a/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts b/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts index 409614ca9c380..3bda152658271 100644 --- a/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts +++ b/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts @@ -22,9 +22,16 @@ import moment from 'moment'; import { esFilters, IFieldType, RangeFilterParams } from '../../../public'; import { getIndexPatterns } from '../../../public/services'; import { deserializeAggConfig } from '../../search/expressions/utils'; -import { RangeSelectTriggerContext } from '../../../../embeddable/public'; +import { + RangeSelectTriggerContext, + isRangeSelectTriggerContextTimeRangeFilterEvent, +} from '../../../../embeddable/public'; export async function createFiltersFromRangeSelectAction(event: RangeSelectTriggerContext['data']) { + if (isRangeSelectTriggerContextTimeRangeFilterEvent(event)) { + return esFilters.mapAndFlattenFilters([event.timeRangeFilter]); + } + const column: Record = event.table.columns[event.column]; if (!column || !column.meta) { diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index d595197373a21..a3e416c7eee30 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -68,6 +68,10 @@ export { SavedObjectEmbeddableInput, isSavedObjectEmbeddableInput, isRangeSelectTriggerContext, + RangeSelectTriggerContextTableDataEvent, + isRangeSelectTriggerContextTableDataEvent, + RangeSelectTriggerContextTimeRangeFilterEvent, + isRangeSelectTriggerContextTimeRangeFilterEvent, isValueClickTriggerContext, EmbeddableRenderer, EmbeddableRendererProps, diff --git a/src/plugins/embeddable/public/lib/triggers/triggers.ts b/src/plugins/embeddable/public/lib/triggers/triggers.ts index 2b447c89e2850..cdcb27548b2b5 100644 --- a/src/plugins/embeddable/public/lib/triggers/triggers.ts +++ b/src/plugins/embeddable/public/lib/triggers/triggers.ts @@ -17,6 +17,7 @@ * under the License. */ +import { RangeFilter } from 'src/plugins/data/public'; import { KibanaDatatable } from '../../../../expressions'; import { Trigger } from '../../../../ui_actions/public'; import { IEmbeddable } from '..'; @@ -43,19 +44,41 @@ export const isValueClickTriggerContext = ( context: ValueClickTriggerContext | RangeSelectTriggerContext ): context is ValueClickTriggerContext => context.data && 'data' in context.data; +export interface RangeSelectTriggerContextTableDataEvent { + table: KibanaDatatable; + column: number; + range: number[]; + timeFieldName?: string; +} + +export function isRangeSelectTriggerContextTableDataEvent( + event: any +): event is RangeSelectTriggerContextTableDataEvent { + return event && 'range' in event && 'table' in event && 'column' in event; +} + +export interface RangeSelectTriggerContextTimeRangeFilterEvent { + timeFieldName: string; + timeRangeFilter: RangeFilter; +} + +export function isRangeSelectTriggerContextTimeRangeFilterEvent( + event: any +): event is RangeSelectTriggerContextTimeRangeFilterEvent { + return event && 'timeFieldName' in event && 'timeRangeFilter' in event; +} + export interface RangeSelectTriggerContext { embeddable?: T; - data: { - table: KibanaDatatable; - column: number; - range: number[]; - timeFieldName?: string; - }; + data: RangeSelectTriggerContextTableDataEvent | RangeSelectTriggerContextTimeRangeFilterEvent; } export const isRangeSelectTriggerContext = ( context: ValueClickTriggerContext | RangeSelectTriggerContext -): context is RangeSelectTriggerContext => context.data && 'range' in context.data; +): context is RangeSelectTriggerContext => + context.data && + (isRangeSelectTriggerContextTableDataEvent(context.data) || + isRangeSelectTriggerContextTimeRangeFilterEvent(context.data)); export const CONTEXT_MENU_TRIGGER = 'CONTEXT_MENU_TRIGGER'; export const contextMenuTrigger: Trigger<'CONTEXT_MENU_TRIGGER'> = { diff --git a/src/plugins/vis_type_timeseries/common/constants.ts b/src/plugins/vis_type_timeseries/common/constants.ts index fc402d6ab7db5..81dab5034e5f4 100644 --- a/src/plugins/vis_type_timeseries/common/constants.ts +++ b/src/plugins/vis_type_timeseries/common/constants.ts @@ -18,3 +18,5 @@ */ export const MAX_BUCKETS_SETTING = 'metrics:max_buckets'; + +export const DEFAULT_TIME_FIELD = '@timestamp'; 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 a96890d4d1502..a47986ba1bc46 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,8 @@ export class VisEditor extends Component { visFields: props.visFields, extractedIndexPatterns: [''], }; - this.onBrush = createBrushHandler(getDataStart().query.timefilter.timefilter); + + this.onBrush = createBrushHandler(this.props.vis); 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.js b/src/plugins/vis_type_timeseries/public/application/lib/create_brush_handler.ts similarity index 52% 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 452e85c6405fe..994fe65bf392c 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 @@ -19,12 +19,29 @@ import moment from 'moment'; -const TIME_MODE = 'absolute'; +import { IIndexPattern, esFilters, IFieldType } from '../../../../data/public'; +import { ExprVis } from '../../../../visualizations/public'; -export const createBrushHandler = (timefilter) => (from, to) => { - timefilter.setTime({ - from: moment(from).toISOString(), - to: moment(to).toISOString(), - mode: TIME_MODE, - }); +import { DEFAULT_TIME_FIELD } from '../../../common/constants'; + +export const createBrushHandler = (vis: ExprVis) => (from: string, to: string) => { + const timeFieldName = vis.params.time_field || DEFAULT_TIME_FIELD; + const field = { + name: timeFieldName, + } as IFieldType; + + const indexPattern = { + id: vis.params.index_pattern, + } as IIndexPattern; + + const timeRangeFilter = esFilters.buildRangeFilter( + field, + { + gte: moment(from).toISOString(), + lte: moment(to).toISOString(), + }, + indexPattern + ); + + vis.API.events.brush({ timeRangeFilter, timeFieldName }); }; diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts index 26fdd665192a6..d18aa79ac2e05 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts @@ -388,12 +388,13 @@ export class VisualizeEmbeddable extends Embeddable -1; + return ( + supportedTriggers.includes('VALUE_CLICK_TRIGGER') || + supportedTriggers.includes('SELECT_RANGE_TRIGGER') + ); } public async isCompatible(context: EmbeddableContext) { From 29a2cc7e421ff85e4ea19e91fc2515fe40459363 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Mon, 22 Jun 2020 11:57:29 +0300 Subject: [PATCH 2/3] fix CI --- src/plugins/data/public/public.api.md | 1 + .../public/lib/triggers/triggers.ts | 11 +++-- ...r.test.js => create_brush_handler.test.ts} | 44 ++++++++++++------- .../application/lib/create_brush_handler.ts | 6 +-- 4 files changed, 39 insertions(+), 23 deletions(-) rename src/plugins/vis_type_timeseries/public/application/lib/{create_brush_handler.test.js => create_brush_handler.test.ts} (51%) diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 23213d4d1165a..960f2d4908d7d 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -140,6 +140,7 @@ import { PopoverAnchorPosition } from '@elastic/eui'; import { PublicUiSettingsParams } from 'src/core/server/types'; import { PutScriptParams } from 'elasticsearch'; import { PutTemplateParams } from 'elasticsearch'; +import { RangeFilter as RangeFilter_2 } from 'src/plugins/data/public'; import React from 'react'; import * as React_2 from 'react'; import { ReindexParams } from 'elasticsearch'; diff --git a/src/plugins/embeddable/public/lib/triggers/triggers.ts b/src/plugins/embeddable/public/lib/triggers/triggers.ts index cdcb27548b2b5..eaae09ecdd29b 100644 --- a/src/plugins/embeddable/public/lib/triggers/triggers.ts +++ b/src/plugins/embeddable/public/lib/triggers/triggers.ts @@ -54,9 +54,13 @@ export interface RangeSelectTriggerContextTableDataEvent { export function isRangeSelectTriggerContextTableDataEvent( event: any ): event is RangeSelectTriggerContextTableDataEvent { - return event && 'range' in event && 'table' in event && 'column' in event; + return event && 'range' in event; } +/** Some of our existing visualizations (Vega, TSVB, Timelion and etc) do not use AggConfigs / esaggs. + * For such visualizations, instead of raising RangeSelectTriggerContextTableDataEvent event we can + * generate a RangeFilter inside the visualization and pass it in RangeSelectTriggerContextTimeRangeFilterEvent + **/ export interface RangeSelectTriggerContextTimeRangeFilterEvent { timeFieldName: string; timeRangeFilter: RangeFilter; @@ -76,9 +80,8 @@ export interface RangeSelectTriggerContext export const isRangeSelectTriggerContext = ( context: ValueClickTriggerContext | RangeSelectTriggerContext ): context is RangeSelectTriggerContext => - context.data && - (isRangeSelectTriggerContextTableDataEvent(context.data) || - isRangeSelectTriggerContextTimeRangeFilterEvent(context.data)); + isRangeSelectTriggerContextTableDataEvent(context.data) || + isRangeSelectTriggerContextTimeRangeFilterEvent(context.data); export const CONTEXT_MENU_TRIGGER = 'CONTEXT_MENU_TRIGGER'; export const contextMenuTrigger: Trigger<'CONTEXT_MENU_TRIGGER'> = { 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 51% 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 6ae01a384e7ca..c4b19aa1d137b 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,40 @@ */ import { createBrushHandler } from './create_brush_handler'; -import moment from 'moment'; +import { ExprVis } from '../../../../visualizations/public'; describe('brushHandler', () => { - let mockTimefilter; - let onBrush; + let onBrush: ReturnType; + let vis: ExprVis; beforeEach(() => { - mockTimefilter = { - time: {}, - setTime: function (time) { - this.time = time; + vis = ({ + API: { + events: { + brush: jest.fn(), + }, }, - }; - onBrush = createBrushHandler(mockTimefilter); + params: { + time_field: 'time_field', + index_pattern: 'index_pattern', + }, + } as unknown) as ExprVis; + + onBrush = createBrushHandler(vis); }); - 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.brush', () => { + const gte = '2017-01-01T00:00:00Z'; + const lte = '2017-01-01T00:10:00Z'; + + onBrush(gte, lte); + + expect(vis.API.events.brush).toHaveBeenCalledWith({ + timeFieldName: 'time_field', + timeRangeFilter: { + meta: { index: 'index_pattern', params: {} }, + range: { time_field: { gte: '2017-01-01T00:00:00.000Z', lte: '2017-01-01T00:10:00.000Z' } }, + }, + }); }); }); diff --git a/src/plugins/vis_type_timeseries/public/application/lib/create_brush_handler.ts b/src/plugins/vis_type_timeseries/public/application/lib/create_brush_handler.ts index 994fe65bf392c..8c34fab89160e 100644 --- a/src/plugins/vis_type_timeseries/public/application/lib/create_brush_handler.ts +++ b/src/plugins/vis_type_timeseries/public/application/lib/create_brush_handler.ts @@ -24,7 +24,7 @@ import { ExprVis } from '../../../../visualizations/public'; import { DEFAULT_TIME_FIELD } from '../../../common/constants'; -export const createBrushHandler = (vis: ExprVis) => (from: string, to: string) => { +export const createBrushHandler = (vis: ExprVis) => (gte: string, lte: string) => { const timeFieldName = vis.params.time_field || DEFAULT_TIME_FIELD; const field = { name: timeFieldName, @@ -37,8 +37,8 @@ export const createBrushHandler = (vis: ExprVis) => (from: string, to: string) = const timeRangeFilter = esFilters.buildRangeFilter( field, { - gte: moment(from).toISOString(), - lte: moment(to).toISOString(), + gte: moment(gte).toISOString(), + lte: moment(lte).toISOString(), }, indexPattern ); From 1fd985126fafeb3ed6dd1fd0a1320f779074b7a8 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Mon, 22 Jun 2020 19:49:43 +0300 Subject: [PATCH 3/3] fix CI --- .../public/components/agg_group.test.tsx | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/plugins/vis_default_editor/public/components/agg_group.test.tsx b/src/plugins/vis_default_editor/public/components/agg_group.test.tsx index 483446daed10f..d660194c2a307 100644 --- a/src/plugins/vis_default_editor/public/components/agg_group.test.tsx +++ b/src/plugins/vis_default_editor/public/components/agg_group.test.tsx @@ -27,14 +27,19 @@ import { DefaultEditorAggAdd } from './agg_add'; import { ISchemas, Schemas } from '../schemas'; import { EditorVisState } from './sidebar/state/reducers'; -jest.mock('@elastic/eui', () => ({ - EuiTitle: 'eui-title', - EuiDragDropContext: 'eui-drag-drop-context', - EuiDroppable: 'eui-droppable', - EuiDraggable: (props: any) => props.children({ dragHandleProps: {} }), - EuiSpacer: 'eui-spacer', - EuiPanel: 'eui-panel', -})); +jest.mock('@elastic/eui', () => { + const originalModule = jest.requireActual('@elastic/eui'); + + return { + ...originalModule, + EuiTitle: 'eui-title', + EuiDragDropContext: 'eui-drag-drop-context', + EuiDroppable: 'eui-droppable', + EuiDraggable: (props: any) => props.children({ dragHandleProps: {} }), + EuiSpacer: 'eui-spacer', + EuiPanel: 'eui-panel', + }; +}); jest.mock('./agg', () => ({ DefaultEditorAgg: () =>
,