diff --git a/docs/development/plugin-development.asciidoc b/docs/development/plugin-development.asciidoc index c930e5fbc92c..0f0c2e2f5417 100644 --- a/docs/development/plugin-development.asciidoc +++ b/docs/development/plugin-development.asciidoc @@ -9,6 +9,7 @@ The Kibana plugin interfaces are in a state of constant development. We cannot * <> * <> * <> +* <> include::plugin/development-plugin-resources.asciidoc[] @@ -16,3 +17,5 @@ include::plugin/development-plugin-resources.asciidoc[] include::plugin/development-uiexports.asciidoc[] include::plugin/development-plugin-functional-tests.asciidoc[] + +include::visualize/development-visualize-index.asciidoc[] \ No newline at end of file diff --git a/docs/development/visualize/development-create-visualization.asciidoc b/docs/development/visualize/development-create-visualization.asciidoc new file mode 100644 index 000000000000..e4839fe4c56d --- /dev/null +++ b/docs/development/visualize/development-create-visualization.asciidoc @@ -0,0 +1,413 @@ +[[development-create-visualization]] +=== Developing Visualizations + +This is a short description of functions and interfaces provided. For more information you should check the kibana +source code and the existing visualizations provided with it. + +- <> +* <> +* <> +* <> +* <> +- <> +* <> +* <> +- <> +* <> +* <> +* <> +- <> +* <> +* <> +* <> +- <> +* <> + +[[development-visualization-factory]] +=== Visualization Factory + +Use the `VisualizationFactory` to create a new visualization. +The creation-methods create a new visualization tied to the underlying rendering technology. +You should also register the visualization with `VisTypesRegistryProvider`. + +["source","html"] +----------- +import { CATEGORY } from 'ui/vis/vis_category'; +import { VisFactoryProvider } from 'ui/vis/vis_factory'; +import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; + +const MyNewVisType = (Private) => { + const VisFactory = Private(VisFactoryProvider); + + return VisFactory.createBaseVisualization({ + name: 'my_new_vis', + title: 'My New Vis', + icon: 'my_icon', + description: 'Cool new chart', + category: CATEGORY.OTHER + ... + }); +} + +VisTypesRegistryProvider.register(MyNewVisType); +----------- + +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 a for a custom request handler +- *responseHandler*: one of the available response handlers or a for a custom response handler +- *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) +- *options.showFilterBar*: show or hide filter bar (defaults to true) +- *options.showIndexSelection*: show or hide index selection (defaults to true) +- *isExperimental*: mark visualization as experimental (defaults to false) + + +Each of the factories have some of the custom parameters, which will be described below. + +[[development-base-visualization-type]] +==== Base Visualization Type +The base visualization type does not make any assumptions about the rendering technology you are going to use and +works with pure Javascript. It is the visualization type we recommend to use. + +You need to provide a type with a constructor function, a render method which will be called every time +options or data change, and a destroy method which will be called to cleanup. + +The render function receives the data object and status object which tells what actually changed. +Render function needs to return a promise, which should be resolved once the visualization is done rendering. + +Status object has the following properties: `vis`, `aggs`, `resize`, `data`. Each of them is set to true if the matching +object changed since last call to the render function or set to false otherwise. You can use it to make your +visualization rendering more efficient. + + +image::images/visualize-flow.png[Main Flow] + +- Your visualizations constructor will get called with `vis` object and the DOM-element to which it should render. +At this point you should prepare everything for rendering, but not render yet +- `` component monitors `appState`, `uiState` and `vis` for changes +- on changes the ``-directive will call your `requestHandler`. +Implementing a request handler is optional, as you might use one of the provided ones. +- response from `requestHandler` will get passed to `responseHandler`. It should convert raw data to something that +can be consumed by visualization. Implementing `responseHandler` is optional, as you might use of of the provided ones. +- On new data from the `responseHandler` or on when the size of the surrounding DOM-element has changed, +your visualization `render`-method gets called. It needs to return a promise which resolves once the visualization +is done rendering. +- the visualization should call `vis.updateState()` any time something has changed that requires to +re-render or fetch new data. + +["source","js"] +----------- +import { VisFactoryProvider } from 'ui/vis/vis_factory'; +import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; + +class MyVisualization { + constructor(vis, el) { + this.el = el; + this.vis = vis; + } + render(visData, status) { + return new Promise(resolve => { + ... + resolve('done rendering'); + }), + destroy() { + console.log('destroying'); + } +} + +const 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 + }); +} + +VisTypesRegistryProvider.register(MyNewVisType); +----------- + +[[development-angular-visualization-type]] +==== AngularJS Visualization Type +The AngularJS visualization type assumes you are using angular as your rendering technology. Instead of providing the +controller we need to provide the angular template to render. + +The visualization will receive `vis`, `uiState` and `visData` on the $scope and needs to +call `$scope.renderComplete()` once it is done rendering. + +["source","js"] +----------- +const MyNewVisType = (Private) => { + const VisFactory = Private(VisFactoryProvider); + + return VisFactory.createAngularVisualization({ + name: 'my_new_vis', + title: 'My New Vis', + icon: 'my_icon', + description: 'Cool new chart', + visConfig: { + template: '
` + } + }); +} +----------- + +[[development-react-visualization-type]] +==== React Visualization Type +React visualization type assumes you are using React as your rendering technology. Instead of passing it an AngularJS +template you need to pass a React component. + +The visualization will receive `vis`, `uiState` and `visData` as props. +It also has a `renderComplete` function, which needs to be called once the rendering has completed. + +["source","js"] +----------- +import { ReactComponent } from './my_react_component'; + +const MyNewVisType = (Private) => { + const VisFactory = Private(VisFactoryProvider); + + return VisFactory.createReactVisualization({ + name: 'my_new_vis', + title: 'My New Vis', + icon: 'my_icon', + description: 'Cool new chart', + visConfig: { + template: ReactComponent + } + }); +} +----------- + +[[development-vislib-visualization-type]] +==== Vislib Visualization Type +This visualization type should only be used for `vislib` visualizations. Vislib is kibana's D3 library which can produce +point series charts and pie charts. + +[[development-vis-editors]] +=== Visualization Editors +By default, visualizations will use the `default` editor. +This is the sidebar editor you see in many of the Kibana visualizations. You can also write your own editor. + +[[development-default-editor]] +==== `default` editor controller +The default editor controller receives an `optionsTemplate` or `optionsTabs` parameter. +These can be either an AngularJS template or React component. + +["source","js"] +----------- +{ + name: 'my_new_vis', + title: 'My New Vis', + icon: 'my_icon', + description: 'Cool new chart', + editorController: 'default', + editorConfig: { + optionsTemplate: '' // or + optionsTemplate: MyReactComponent // or if multiple tabs are required: + optionsTabs: [ + { title: 'tab 1', template: '
....
}, + { title: 'tab 2', template: '' }, + { title: 'tab 3', template: MyReactComponent } + ] + } + } +----------- + +[[development-custom-editor]] +==== custom editor controller +You can create a custom editor controller. To do so pass an Editor object (the same format as VisController class). +You can make your controller take extra configuration which is passed to the editorConfig property. + +["source","js"] +----------- +import { VisFactoryProvider } from 'ui/vis/vis_factory'; + +class MyEditorController { + constructor(el, vis) { + this.el = el; + this.vis = vis; + this.config = vis.type.editorConfig; + } + async render(visData) { + return new Promise(resolve => { + console.log(this.config.my); + ... + resolve('done rendering'); + }), + destroy() { + console.log('destroying'); + } +} + +const 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' } + }); +} + +VisTypesRegistryProvider.register(MyNewVisType); +----------- + +[[development-visualization-request-handlers]] +=== Visualization Request Handlers +Request handler gets called when one of the following keys on AppState change: +`vis`, `query`, `filters` or `uiState` and when timepicker is updated. On top +of that it will also get called on force refresh. + +By default visualizations will use the `courier` request handler. They can also choose to use any of the other provided +request handlers. It is also possible to define your own request handler +(which you can then register to be used by other visualizations). + +[[development-default-request-handler]] +==== courier request handler +'courier' is the default request handler which works with the 'default' side bar editor. + +[[development-none-request-handler]] +==== `none` request handler +Using 'none' as your request handles means your visualization does not require any data to be requested. + +[[development-custom-request-handler]] +==== custom request handler +You can define your custom request handler by providing a function with the following definition: +`function (vis, appState, uiState, searchSource) { ... }` + +This function must return a promise, which should get resolved with new data that will be passed to responseHandler. + +It's up to function to decide when it wants to issue a new request or return previous data +(if none of the objects relevant to the request handler changed). + +["source","js"] +----------- +import { VisFactoryProvider } from 'ui/vis/vis_factory'; + +const myRequestHandler = (vis, appState, uiState, searchSource) => { + + return new Promise((resolve, reject) => { + const data = ... parse ... + resolve(data); + }); +}; + +const MyNewVisType = (Private) => { + const VisFactory = Private(VisFactoryProvider); + + return VisFactory.createAngularVisualization({ + name: 'my_new_vis', + title: 'My New Vis', + icon: 'my_icon', + description: 'Cool new chart', + requestHandler: myRequestHandler + }); +} + +VisTypesRegistryProvider.register(MyNewVisType); +----------- + +[[development-visualization-response-handlers]] +=== Visualization Response Handlers +The response handler is a function that receives the data from a request handler, as well as an instance of Vis object. +Its job is to convert the data to a format visualization can use. By default 'default' request handler is used +which produces a table representation of the data. The data object will then be passed to visualization. +This response matches the visData property of the directive. + +[[development-default-response-handler]] +==== default response handler +Default response handler will convert pure elasticsearch response to tabular format. + +[[development-none-response-handler]] +==== none response handler +None response handler is an identity function, which will return the same data it receives. + +[[development-custom-response-handler]] +==== custom response handler +You can define your custom response handler by providing a function with the following definition: +'function (vis, response) { ... }'. + +Function should return the transformed data object that visualization can consume. + +["source","js"] +----------- +import { VisFactoryProvider } from 'ui/vis/vis_factory'; + +const myResponseHandler = (vis, response) => { + // transform the response (based on vis object?) + const resposne = ... transform data ...; + return response; +}; + +const MyNewVisType(Private) => { + const VisFactory = Private(VisFactoryProvider); + + return VisFactory.createAngularVisualization({ + name: 'my_new_vis', + title: 'My New Vis', + icon: 'my_icon', + description: 'Cool new chart', + responseHandler: myResponseHandler + }); +} + +VisTypesRegistryProvider.register(MyNewVisType); +----------- + +[[development-vis-object]] +=== Vis object +The `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.forceReload()*: forces whole cycle (request handler gets called) +- *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 + +The visualization gets all its parameters in `vis.params`, which are default values merged with the current state. +If the visualization needs to update the current state, it should update the `vis.params` and call `vis.updateState()` +which will inform about the change, which will call request and response handler and then your +visualization's render method. + +For the parameters that should not be saved with the visualization you should use the UI state. +These hold viewer-specific state, such as popup open/closed, custom colors applied to the series etc. + +You can access filter bar and time picker through the objects defined on `vis.API` + +[[development-vis-timefilter]] +==== timeFilter + +Update the timefilter time values and call update() method on it to update time picker +["source","js"] +----------- + timefilter.time.from = moment(ranges.xaxis.from); + timefilter.time.to = moment(ranges.xaxis.to); + timefilter.time.mode = 'absolute'; + timefilter.update(); +----------- \ No newline at end of file diff --git a/docs/development/visualize/development-embedding-visualizations.asciidoc b/docs/development/visualize/development-embedding-visualizations.asciidoc new file mode 100644 index 000000000000..bb541cc21be3 --- /dev/null +++ b/docs/development/visualize/development-embedding-visualizations.asciidoc @@ -0,0 +1,106 @@ +[[development-embedding-visualizations]] +=== Embedding Visualizations + +There are two different angular directives you can use to insert a visualization in your page. +To display an already saved visualization, use the `` directive. +To reuse an existing Visualization implementation for a more custom purpose, use the `` directive instead. + +==== `` directive +The `` directive takes care of loading data, parsing data, rendering the editor +(if the Visualization is in edit mode) and rendering the visualization. +The directive takes a savedVis object for its configuration. +It is the easiest way to add visualization to your page under the assumption that +the visualization you are trying to display is saved in kibana. +If that is not the case, take a look at using `` directive. + +The simplest way is to just pass `saved-id` to ``: + +`` + +For the above to work with time based visualizations time picker must be present (enabled) on the page. For scenarios +where timepicker is not available time range can be passed in as additional parameter: + +`` + +Once is done rendering the element will emit `renderComplete` event. + +When more control is required over the visualization you may prefer to load the saved object yourself and then pass it +to `` + +`` where + +`savedVis` is an instance of savedVisualization object, which can be retrieved using `savedVisualizations` service +which is explained later in this documentation. + +`appState` is an instance of `AppState`. is expecting two keys defined on AppState: + +- `filters` which is an instance of searchSource filter object and +- `query` which is an instance of searchSource query object + +If `appState` is not provided, `` will not monitor the `AppState`. + +`uiState` should be an instance of `PersistedState`. if not provided visualize will generate one, +but you will have no access to it. It is used to store viewer specific information like whether the legend is toggled on or off. + +`editor-mode` defines if should render in editor or in view mode. + +*code example: Showing a saved visualization, without linking to querybar or filterbar.* +["source","html"] +----------- +
+ +
+----------- +["source","js"] +----------- +import { uiModules } from 'ui/modules'; + +uiModules.get('kibana') +.controller('KbnTestController', function ($scope, AppState, savedVisualizations) { + const visId = 'enter_your_vis_id'; + savedVisualizations.get(visId).then(savedVis => $scope.savedObj = savedVis); +}); +----------- + +When is ready it will emit `ready:vis` event on the root scope. +When is done rendering it will emit `renderComplete` event on the element. + +==== `` directive +The `` directive takes a visualization configuration and data. + +`` where + +`vis` is an instance of `Vis` object. The constructor takes 3 parameters: + +- `indexPattern` : the indexPattern you want to pass to the visualization +- `visConfig` : the configuration object +- `uiState` : uiState object you want to pass to Vis. If not provided Vis will create its own. + +`visData` is the data object. Each visualization defines a `responseHandler`, which defines the format of this object. + +`uiState` is an instance of PersistedState. Visualizations use it to keep track of their current state. If not provided +`` will create its own (but you won't be able to check its values) + +*code example: create single metric visualization* +["source","html"] +----------- +
+ +
+----------- +["source","js"] +----------- +import { uiModules } from 'ui/modules'; + +uiModules.get('kibana') +.controller('KbnTestController', function ($scope) { + const visConfig = { + type: 'metric' + }; + $scope.vis = new Vis('.logstash*', visConfig); + $scope.visData = [{ columns: [{ title: 'Count' }], rows: [[ 1024 ], [ 256 ]] }]; +}); +----------- + + will trigger `renderComplete` event on the element once it's done rendering. \ No newline at end of file diff --git a/docs/development/visualize/development-visualize-index.asciidoc b/docs/development/visualize/development-visualize-index.asciidoc new file mode 100644 index 000000000000..1cdeac7540ce --- /dev/null +++ b/docs/development/visualize/development-visualize-index.asciidoc @@ -0,0 +1,21 @@ +[[development-visualize-index]] +== Developing Visualizations + +Kibana Visualizations are the easiest way to add additional functionality to Kibana. +This part of documentation is split into two parts. +The first part tells you all you need to know on how to embed existing Kibana Visualizations in your plugin. +The second step explains how to create your own custom visualization. + +[IMPORTANT] +============================================== +These pages document internal APIs and are not guaranteed to be supported across future versions of Kibana. +However, these docs will be kept up-to-date to reflect the current implementation of Visualization plugins in Kibana. +============================================== + +* <> +* <> + + +include::development-embedding-visualizations.asciidoc[] + +include::development-create-visualization.asciidoc[] \ No newline at end of file diff --git a/docs/images/visualize-flow.png b/docs/images/visualize-flow.png new file mode 100644 index 000000000000..bc00ff52a8d6 Binary files /dev/null and b/docs/images/visualize-flow.png differ