Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Visualize Refactor #11786

Merged
merged 40 commits into from
Jul 7, 2017
Merged

Visualize Refactor #11786

merged 40 commits into from
Jul 7, 2017

Conversation

ppisljar
Copy link
Member

@ppisljar ppisljar commented May 15, 2017

Release Note: When creating new visualizations, developers are no longer restricted to using just Angular as a rendering technology. The changes now also enables developers to create custom editors that do not conform to the current sidebar-layout. Commonly used functionality - such as access to the query bar or timefilter - is now also exposed on the visualization object. This avoids the need to import individual modules from inside Kibana. These changes are a first step in a longer term effort to provide a robust long-lived programming interface for building visualizations in Kibana.

=====
This PR has 3 main goals:

  • make it easy to include visualizations anywhere in kibana
  • make it easy for developers to create new visualizations
  • make visualize editor more powerful in terms of what kind of visualizations can you create

<visualize> component accepts a savedSearch and takes care of loading data and showing visualization
<visualization> component takes visConfig and visData and takes care of rendering the visualization

visualization can now define its own requestHandler (responsible for loading data), responseHandler (responsible for converting data) and editor (responsible for configuring the visualization) which allows creating pretty much anything. timelion and tsvb now nicely fit inside this framework without using any hacks.

one way data flow makes reasoning about visualize editor easier
471bc7ea-212c-11e7-9985-612e9826381a

@ppisljar ppisljar added the Feature:Visualizations Generic visualization features (in case no more specific feature label is available) label May 15, 2017
@ppisljar ppisljar self-assigned this May 15, 2017
@ppisljar
Copy link
Member Author

ppisljar commented May 15, 2017

TOC

USING VISUALIZATIONS

There are two ways to insert visualization in your page. Using <visualize> directive or using <visualization> directive.

<visualize> directive

<visualize> directive takes a savedVis object and it takes care of loading data, parsing data, rendering editor (if in edit mode) and rendering the visualization

<visualize saved-vis='savedVis' app-state='appState' ui-state='uiState' ></visualize> where

savedVis is an instance of savedVisualization object.

appState is an instance of AppState. visualize is expecting two parameters defined on it:

  • filters which is an instance of searchSource filter object and
  • query which is an instance of searchSource query object

uiState should be an instance of PersistedState. if not provided visualize will generate one, but you will have no access to it.

code example: showing saved visualization, without linking to querybar or filterbar

<div ng-controller="KbnTestController" class="test_vis">
  <visualize saved-vis='savedVis' app-state='appState' ui-state='uiState' ></visualize>
</div>
import { uiModules } from 'ui/modules';

uiModules.get('kibana')
.controller('KbnTestController', function ($scope, AppState, savedVisualizations) {
  const visId = 'enter_your_vis_id';
  $scope.appState = new AppState({ uiState: {} });
  $scope.uiState = $scope.appState.makeStateful('uiState');
  savedVisualizations.get(visId).then(savedVis => $scope.savedVis = savedVis);
});

<visualization> directive

<visualization> directive takes visualization config and data and renders the appropriate visualization.

<visualization vis='vis' vis-data='visData' ui-state='uiState' ></visualization> where

vis is an instance of Vis object

visData is the data object visualization expects. this is a bit tricky at the moment as visualizations still expect object which is tightly coupled to ES, but this should change (visualizations should probably take some sort of tabular data .... response_converters should be responsible for converting ES data in appropriate format.

uiState is an instance of PersistedState

code example: create single metric visualization

<div ng-controller="KbnTestController" class="test_vis">
  <visualization vis='vis' vis-data='visData' ui-state='uiState' ></visualize>
</div>
import { uiModules } from 'ui/modules';

uiModules.get('kibana')
.controller('KbnTestController', function ($scope, AppState, ) {
  const visConfig = {
    type: 'metric'
  };
  $scope.uiState = new AppState({ uiState: {} }).makeStateful('uiState');
  $scope.vis = new Vis('.logstash*', visConfig, uiState);
  $scope.visData = [{ name: 'Count', value: '543534' }]; // this won't yet work as metric vis still accepts data as aggresponse object
});

CREATING NEW VISUALIZATIONS

[EDIT: the yoman generator will not be part of this PR. We might reintroduce it later]
To quickly start developing a new visualization yeoman generator is available. To install it you can run this command:

npm install -g yo generator-kbnvis

Move into kibana folder and run yo kbnvis. Generator will ask you some questions and then generate visualization skeleton for you, which you can find under src/core_plugins/your_visualization_name.
To better understand the questions generator ask please read the rest of documentation.

Visualization Factory

To create a new visualization you need to create new visualization type using the VisualizationFactory. The factory can create Base, Angular and Vislib visualization, but we should probably add React and maybe Flot factory methods in the future to make it easier to get started.

import { VisFactoryProvider } from 'ui/vis/vis_factory';

export default function MyNewVisType(Private) {
  const VisFactory = Private(VisFactoryProvider);

  return  VisFactory.createAngularVisualization({
    name: 'my_new_vis',
    title: 'My New Vis',
    icon: 'my_icon',
    description: 'Cool new chart',
    ...
  });
}

The list of common parameters:

  • name: unique visualization name, only lowercase letters and underscore
  • title: title of your visualization as displayed in kibana
  • icon: the icon class to use (font awesome)
  • image: instead of icon you can provide svg image (imported)
  • description: description of your visualization as shown in kibana
  • category: the category your visualization falls into (one of ui/vis/vis_category values)
  • visConfig: object holding visualization parameters
  • visConfig.defaults: object holding default visualization configuration
  • visualization: A constructor function for a Visualization.
  • requestHandler: one of the available request handlers or for custom one
  • responseHandler: one of the available response handlers or for custom one
  • editor: one of the available editors or Editor class for custom one
  • editorConfig: object holding editor parameters
  • options.showTimePicker: show or hide time picker (defaults to true)
  • options.showQueryBar: show or hide query bar (defaults to true) [not implemented]
  • options.showFilterBar: show or hide filter bar (defaults to true) [not implemented]
  • options.hierarchicalData: use hierarchical data (defaults to false) [BWC]
  • isExperimental: mark visualization as experimental (defaults to false)

Each of the factories have some of the custom parameters

Base Visualization Type

this is the base factory. you need to provide an object with render function which will be called everytime options or data change and destroy function which will be called to cleanup. The render function needs to return a promise, which should be resolved once the visualization is done rendering

import { VisFactoryProvider } from 'ui/vis/vis_factory';

class MyVisualization {
   constructor(vis, el) {
      this.el = el;
      this.vis = vis;
   }
   render(visData) {
      return new Promise(resolve => {
         ...
         resolve('done rendering');
      }),
   resize() {
   }
   destroy() {
      console.log('destroying');
   }
}

export default function MyNewVisType(Private) {
  const VisFactory = Private(VisFactoryProvider);

  return VisFactory.createBaseVisualization({
    name: 'my_new_vis',
    title: 'My New Vis',
    icon: 'my_icon',
    description: 'Cool new chart',
    visualization: MyVisualization
  });
}

Angular Vis Type

the only required parameter is the angular template to render. Visualization will receive vis, uiState and visData on the $scope and needs to call $scope.renderComplete() once its done rendering

return VisFactory.createAngularVisualization({
    name: 'my_new_vis',
    title: 'My New Vis',
    icon: 'my_icon',
    description: 'Cool new chart',
    visConfig: {
       template: '<div ng-controller="MyAngularController"></div>`
    }
  });

Editors

By default visualization will use the default editor (which is the sidebar editor you see in current kibana charts) but can be changed. Currently no other editors are provided (timelion editor will probably be added in the near future). Plugin can register a new editor in registry to make it available to other visualizations, but is not necessary (if its a one-time thing which will not be reused)

default editor controller

the default editor controller can receive optionsTemplate or optionsTabs parameter which can be either an angular template or react component. React component will receive two params: scope, which is an angular scope and stageEditorParams which will update the editor with parameters set in react component.

{
    name: 'my_new_vis',
    title: 'My New Vis',
    icon: 'my_icon',
    description: 'Cool new chart',
    editorController: 'default',
    editorConfig: {
       optionsTemplate: '<my-custom-options-directive></my-custom-options-directive>' // or
       optionsTabs: [
           { title: 'tab 1', template: '<div>....</div> },
           { title: 'tab 2', template: '<my-custom-options-directive></my-custom-options-directive>' }
       ]
    }
  }

custom editor controller

You can create a custom editor controller. To do so pass an Editor object (the same format as VisController class)r. You can make your controller take extra configuration which is passed to the editorConfig property.

import { VisFactoryProvider } from 'ui/vis/vis_factory';

class MyEditorController {
    constructor(el, vis) {
      this.el = el;
      this.vis = vis;
   }
   render(visData) {
      return new Promise(resolve => {
         ...
         resolve('done rendering');
      }),
   resize() {
   }
   destroy() {
      console.log('destroying');
   }
}

export default function MyNewVisType(Private) {
  const VisFactory = Private(VisFactoryProvider);

  return VisFactory.createAngularVisualization({
    name: 'my_new_vis',
    title: 'My New Vis',
    icon: 'my_icon',
    description: 'Cool new chart',
    editorController: MyEditorController,
    editorConfig: { my: 'custom config' }
  });
}

request handlers

by default visualizations will use courier request handler. They can also choose to use any of the other provided request handler (currently 'none' ... which disables request handler, so no search will be done, and 'timelion' which will make a request against timelion backend. Its also possible to define your own request handler (which you can then register to be used by other visualizations)

courier request handler

'courier' is the default request handler which works with out 'default' side bar editor

timelion request handler

'timelion' request handler is used by Timelion visualization builder

none

using 'none' as your request handles means your visualization does not require search

custom request handler.

you can define your custom request handler by providing a function with the following definition:
function (vis, appState, uiState, searchSource) { ... }

response handlers

response handler will receive the data from request handler and an instance of Vis object. Its job is to convert the data to a format visualization can use.
by default 'none' request handler is used which just passes the data it receives back, but can be changed to 'basic' (which VislibVisTypeFactory uses) or to a custom one.

custom response handler

you can define your custom response handler by providing a function with the following definition:
'function (vis, response) { ... }'

Vis object

Vis object holds the visualization state and is the window into kibana.

vis.params: holds the visualization parameters
vis.indexPattern: selected index pattern object
vis.getState(): gets current visualization state
vis.updateState(): updates current state with values from vis.params
vis.resetState(): resets vis.params to the values in the current state
vis.getUiState(): gets UI state of visualization
vis.uiStateVal(name, val): updates a property in UI state
vis.isEditorMode(): returns true if in editor mode
vis.API.timeFilter: allows you to access time picker
vis.API.events.click: default click handler
vis.API.events.brush: default brush handler

Visualization gets all its parameters in vis.params (defaults merged with the current state). If it wants to update them it should update them in this object and then call vis.updateState() which will inform about the change, which will call request and response handler and then your visualizations render method.

For the parameters that should not be saved with visualization you should use UI state (such as current state: popup open/closed, custom colors applied to the series etc)

You can access filter bar and time picker thru the objects defined on vis.API

timeFilter

update the timefilter time values and call update() method on it to update time picker

   timefilter.time.from = moment(ranges.xaxis.from);
   timefilter.time.to = moment(ranges.xaxis.to);
   timefilter.time.mode = 'absolute';
   timefilter.update();

@ppisljar
Copy link
Member Author

i'll be adding some docs this afternoon

@ppisljar
Copy link
Member Author

ppisljar commented May 15, 2017

this should be in a working state now (except for TSVB and Maps) ... bug hunting can begin

@ppisljar ppisljar force-pushed the feature/visualize branch 4 times, most recently from 78bcc79 to fe89eda Compare May 19, 2017 09:07
This was referenced May 19, 2017
@Bargs
Copy link
Contributor

Bargs commented May 22, 2017

@weltenwort @lukasolson notice the mention of queryFilter in the new viz API at the bottom of #11786 (comment). I don't know about you guys, but I think we should provide an API with less baggage for these guys.

My problems with queryFilter:

  • It's coupled to AppState and modifies appState/globalState directly.
  • It's coupled to the existing filter model. It won't work with the new query language
  • It relies on in place mutations of objects
  • It expects its clients to know how to construct filter objects with raw query DSL in them

@ppisljar you mentioned having a follow up discussion, could you include @weltenwort and @lukasolson as well? I'd like to get their input.

@thomasneirynck
Copy link
Contributor

thomasneirynck commented May 24, 2017

Running list of examples here:

@lynnic26
Copy link

Can lower version of kibana (e.g. 5.2.1)use this fearture also?

@ppisljar
Copy link
Member Author

this will hopefully be part of 6.0, its still work in progress

@ppisljar ppisljar merged commit 3000221 into master Jul 7, 2017
thomasneirynck added a commit to thomasneirynck/kibana that referenced this pull request Jul 7, 2017
thomasneirynck added a commit that referenced this pull request Jul 7, 2017
This reverts commit 3000221.

This change broke Xpack, due to some modules being moved to a different location.
@thomasneirynck
Copy link
Contributor

I reverted this changes #12711. The reason for this is that we broke Xpack. Should merge these together.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature:Visualizations Generic visualization features (in case no more specific feature label is available) v6.0.0-beta1
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants