mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Drilldowns for TSVB / Vega / Timelion (#74848)
* 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
This commit is contained in:
parent
67e28ac8b4
commit
f6f59ec261
16 changed files with 124 additions and 40 deletions
|
@ -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;
|
||||
|
|
|
@ -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 = '<br>';
|
||||
|
||||
function Panel({ interval, seriesList, renderComplete }: PanelProps) {
|
||||
function Panel({ interval, seriesList, renderComplete, applyFilter }: PanelProps) {
|
||||
const kibana = useKibana<TimelionVisDependencies>();
|
||||
const [chart, setChart] = useState(() => cloneDeep(seriesList.list));
|
||||
const [canvasElem, setCanvasElem] = useState<HTMLDivElement>();
|
||||
|
@ -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(() => {
|
||||
|
|
|
@ -38,6 +38,7 @@ function TimelionVisComponent(props: TimelionVisComponentProp) {
|
|||
return (
|
||||
<div className="timVis">
|
||||
<ChartComponent
|
||||
applyFilter={props.vis.API.events.applyFilter}
|
||||
seriesList={props.visData.sheet[0]}
|
||||
renderComplete={props.renderComplete}
|
||||
interval={props.vis.getState().params.interval}
|
||||
|
|
|
@ -27,6 +27,8 @@ import { TimelionVisComponent, TimelionVisComponentProp } from './components';
|
|||
import { TimelionOptions, TimelionOptionsProps } from './timelion_options';
|
||||
import { TimelionVisDependencies } from './plugin';
|
||||
|
||||
import { VIS_EVENT_TO_TRIGGER } from '../../visualizations/public';
|
||||
|
||||
export const TIMELION_VIS_NAME = 'timelion';
|
||||
|
||||
export function getTimelionVisDefinition(dependencies: TimelionVisDependencies) {
|
||||
|
@ -63,6 +65,9 @@ export function getTimelionVisDefinition(dependencies: TimelionVisDependencies)
|
|||
requestHandler: timelionRequestHandler,
|
||||
responseHandler: 'none',
|
||||
inspectorAdapters: {},
|
||||
getSupportedTriggers: () => {
|
||||
return [VIS_EVENT_TO_TRIGGER.applyFilter];
|
||||
},
|
||||
options: {
|
||||
showIndexSelection: false,
|
||||
showQueryBar: false,
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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<typeof createBrushHandler>;
|
||||
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' } },
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
|
@ -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',
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -105,6 +105,11 @@ describe('VegaVisualizations', () => {
|
|||
|
||||
vis = {
|
||||
type: vegaVisType,
|
||||
API: {
|
||||
events: {
|
||||
applyFilter: jest.fn(),
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -310,12 +310,21 @@ export class VisualizeEmbeddable extends Embeddable<VisualizeInput, VisualizeOut
|
|||
}
|
||||
|
||||
if (!this.input.disableTriggers) {
|
||||
const triggerId =
|
||||
event.name === 'brush' ? VIS_EVENT_TO_TRIGGER.brush : VIS_EVENT_TO_TRIGGER.filter;
|
||||
const context = {
|
||||
embeddable: this,
|
||||
data: { timeFieldName: this.vis.data.indexPattern?.timeFieldName!, ...event.data },
|
||||
};
|
||||
const triggerId = get(VIS_EVENT_TO_TRIGGER, event.name, VIS_EVENT_TO_TRIGGER.filter);
|
||||
let context;
|
||||
|
||||
if (triggerId === VIS_EVENT_TO_TRIGGER.applyFilter) {
|
||||
context = {
|
||||
embeddable: this,
|
||||
timeFieldName: this.vis.data.indexPattern?.timeFieldName!,
|
||||
...event.data,
|
||||
};
|
||||
} else {
|
||||
context = {
|
||||
embeddable: this,
|
||||
data: { timeFieldName: this.vis.data.indexPattern?.timeFieldName!, ...event.data },
|
||||
};
|
||||
}
|
||||
|
||||
getUiActions().getTrigger(triggerId).exec(context);
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ export interface ExprVisState {
|
|||
export interface ExprVisAPIEvents {
|
||||
filter: (data: any) => 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 });
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue