Skip to content

Commit

Permalink
[Vega] Implement context filter modification
Browse files Browse the repository at this point in the history
elastic#17210

Testing code (click button)

```
{
  "$schema": "https://vega.github.io/schema/vega/v3.json",
  "marks": [
    {
      "name": "myButton",
      "type": "rect",
      "encode": {
        "enter": {
          "xc": {"signal": "width/2"},
          "yc": {"signal": "height/2"},
          "width": {"signal": "width*0.8"},
          "height": {"signal": "height*0.8"},

          "cornerRadius": {"value": 6},
          "strokeWidth": {"value": 10}
        },
        "update": {
          "stroke": {"value": "gray"},
          "fill": {"value": "lightgray"}
        },
        "hover": {"fill": {"value": "gray"}}
      }
    }
  ],
  "signals": [
    {
      "name": "%ADD_FILTER%",
      "on": [
        {
          "events": "@mybutton:click",
          "update": "{field: 'SRC', value: 10, operator: 'IS'}"
        }
      ]
    }
  ]
}
```

changed Vega signal to custom func

rough draft implementation

support timefilter, filter removal

use buildQueryFilter wrapper

rebase and sync with tooltip

func error handling, filters

(WIP) console.log to dbg removeFilter

manual merge of upstream patch
  • Loading branch information
nyurik committed Jul 2, 2018
1 parent 75309ff commit eeb5d84
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 11 deletions.
4 changes: 2 additions & 2 deletions src/core_plugins/vega/public/vega_type.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ VisTypesRegistryProvider.register((Private) => {
responseHandler: 'none',
options: {
showIndexSelection: false,
showQueryBar: false,
showFilterBar: false,
showQueryBar: true,
showFilterBar: true,
},
stage: 'lab',
feedbackMessage: defaultFeedbackMessage,
Expand Down
142 changes: 136 additions & 6 deletions src/core_plugins/vega/public/vega_view/vega_base_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,47 @@
*/

import $ from 'jquery';
import moment from 'moment';
import * as vega from 'vega-lib';
import * as vegaLite from 'vega-lite';
import { Utils } from '../data_model/utils';
import { VISUALIZATION_COLORS } from '@elastic/eui';
import { TooltipHandler } from './vega_tooltip';
import { buildQueryFilter } from 'ui/filter_manager/lib';

vega.scheme('elastic', VISUALIZATION_COLORS);

// Vega's extension functions are global. When called,
// we forward execution to the instance-specific handler
// This functions must be declared in the VegaBaseView class
const vegaFunctions = {
kibanaAddFilter: 'addFilterHandler',
kibanaRemoveFilter: 'removeFilterHandler',
kibanaRemoveAllFilters: 'removeAllFiltersHandler',
kibanaSetTimeFilter: 'setTimeFilterHandler',
};

for (const funcName of Object.keys(vegaFunctions)) {
if (!vega.expressionFunction(funcName)) {
vega.expressionFunction(
funcName,
function handlerFwd(...args) {




console.log('Handling ' + funcName, ...args);




const view = this.context.dataflow;
view.runAfter(() => view._kibanaView.vegaFunctionsHandler(funcName, ...args));
}
);
}
}

const bypassToken = Symbol();

export function bypassExternalUrlCheck(url) {
Expand All @@ -34,12 +67,15 @@ export function bypassExternalUrlCheck(url) {
}

export class VegaBaseView {
constructor(vegaConfig, editorMode, parentEl, vegaParser, serviceSettings) {
this._vegaConfig = vegaConfig;
this._editorMode = editorMode;
this._$parentEl = $(parentEl);
this._parser = vegaParser;
this._serviceSettings = serviceSettings;
constructor(opts) {
this._vegaConfig = opts.vegaConfig;
this._editorMode = opts.editorMode;
this._$parentEl = $(opts.parentEl);
this._parser = opts.vegaParser;
this._serviceSettings = opts.serviceSettings;
this._queryfilter = opts.queryfilter;
this._timefilter = opts.timefilter;
this._findIndex = opts.findIndex;
this._view = null;
this._vegaViewConfig = null;
this._$messages = null;
Expand Down Expand Up @@ -175,6 +211,10 @@ export class VegaBaseView {
this._view = view;

if (view) {

// Global vega expression handler uses it to call custom functions
view._kibanaView = this;

if (this._parser.tooltips) {
// position and padding can be specified with
// {config:{kibana:{tooltips: {position: 'top', padding: 15 } }}}
Expand All @@ -188,6 +228,96 @@ export class VegaBaseView {
}
}

/**
* Handle
* @param funcName
* @param args
* @returns {Promise<void>}
*/
async vegaFunctionsHandler(funcName, ...args) {





console.log('Executing ' + funcName, ...args);





try {
const handlerFunc = vegaFunctions[funcName];
if (!handlerFunc || !this[handlerFunc]) {
// in case functions don't match the list above
throw new Error(`${funcName}() is not defined for this graph`);
}
await this[handlerFunc].apply(this, args);
} catch (err) {
this.onError(err);
}
}

/**
* @param {object} query Elastic Query DSL snippet, as used in the query DSL editor
* @param {string} [index] as defined in Kibana, or default if missing
*/
async addFilterHandler(query, index) {
const indexId = await this._findIndex(index);
const filter = buildQueryFilter(query, indexId);
this._queryfilter.addFilters(filter);
}

/**
* @param {object} query Elastic Query DSL snippet, as used in the query DSL editor
* @param {string} [index] as defined in Kibana, or default if missing
*/
async removeFilterHandler(query, index) {
const indexId = await this._findIndex(index);
const filter = buildQueryFilter(query, indexId);



console.log('removing filter', filter);



this._queryfilter.removeFilter(filter);
}

removeAllFiltersHandler() {
this._queryfilter.removeAll();
}

/**
* @param {number|string|Date} start
* @param {number|string|Date} end
* @param {string} [mode]
*/
setTimeFilterHandler(start, end, mode) {
const tf = this._timefilter;

let from = moment(start);
let to = moment(end);

if (from.isValid() && to.isValid()) {
if (from.isAfter(to)) {
[from, to] = [to, from];
}
} else if (typeof start === 'string' && typeof end === 'string') {

// TODO/FIXME: should strings be allowed as is, or is there a parser?
// Also, should the default mode be changed in this case?

[from, to] = [start, end];
}

tf.time.from = from;
tf.time.to = to;
tf.time.mode = mode || 'absolute';
tf.update();
}

/**
* Set global debug variable to simplify vega debugging in console. Show info message first time
*/
Expand Down
43 changes: 40 additions & 3 deletions src/core_plugins/vega/public/vega_visualization.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@
import { Notifier } from 'ui/notify';
import { VegaView } from './vega_view/vega_view';
import { VegaMapView } from './vega_view/vega_map_view';
import { SavedObjectsClientProvider, findObjectByTitle } from 'ui/saved_objects';

export function VegaVisualizationProvider(vegaConfig, serviceSettings) {
export function VegaVisualizationProvider(Private, vegaConfig, serviceSettings) {

const savedObjectsClient = Private(SavedObjectsClientProvider);
const notify = new Notifier({ location: 'Vega' });

return class VegaVisualization {
Expand All @@ -31,13 +33,37 @@ export function VegaVisualizationProvider(vegaConfig, serviceSettings) {
this._vis = vis;
}

/**
* Find index pattern by its title, of if not given, gets default
* @param {string} [index]
* @returns {Promise<string>} index id
*/
async findIndex(index) {
let idxObj;
if (index) {
idxObj = await findObjectByTitle(savedObjectsClient, 'index-pattern', index);
if (!idxObj) throw new Error(`Index "${index}" not found`);
} else {
idxObj = await this._vis.API.indexPatterns.getDefault();
if (!idxObj) throw new Error('Unable to find default index');
}
return idxObj.id;
}

/**
*
* @param {VegaParser} visData
* @param {*} status
* @returns {Promise<void>}
*/
async render(visData, status) {



console.log('render()', new Date());



if (!visData && !this._vegaView) {
notify.warning('Unable to render without data');
return;
Expand Down Expand Up @@ -65,10 +91,21 @@ export function VegaVisualizationProvider(vegaConfig, serviceSettings) {
this._vegaView = null;
}

const vegaViewParams = {
vegaConfig,
editorMode: this._vis.editorMode,
parentEl: this._el,
vegaParser,
serviceSettings,
queryfilter: this._vis.API.queryFilter,
timefilter: this._vis.API.timeFilter,
findIndex: this.findIndex.bind(this),
};

if (vegaParser.useMap) {
this._vegaView = new VegaMapView(vegaConfig, this._vis.editorMode, this._el, vegaParser, serviceSettings);
this._vegaView = new VegaMapView(vegaViewParams);
} else {
this._vegaView = new VegaView(vegaConfig, this._vis.editorMode, this._el, vegaParser, serviceSettings);
this._vegaView = new VegaView(vegaViewParams);
}
await this._vegaView.init();

Expand Down

0 comments on commit eeb5d84

Please sign in to comment.