Skip to content

Commit

Permalink
Refactor and improve Visualize Loader (#15157) (#15362)
Browse files Browse the repository at this point in the history
* Simplify promise setup logic

* Import template from own file

* Use angular.element instead of jquery

* Add documentation for loader methods

* Add params.append

* Remove params.editorMode

* Clarify when returned promise resolves

* Add element to handler

* Allow setting CSS class via loader

* Use render-counter on visualize

* Use Angular run method to get access to Private service

* Allow adding data-attributes to the vis element

* Refactor loader to return an EmbeddedVisualizeHandler instance

* Use this.destroy for previous API

* Remove fallback then method, due to bugs

* Reject promise from withId when id not found

* Add tests

* Change developer documentation

* Revert "Use Angular run method to get access to Private service"

This reverts commit 160e47d.

* Rename parameter for more clarity

* Add more documentation about appState

* Fix broken test utils

* Use chrome to get access to Angular

* Move loader to its own folder

* Use a method instead of getter for element

* Add listeners for renderComplete events

* Use typedef to document params

* Fix documentation
  • Loading branch information
timroes committed Dec 5, 2017
1 parent f4afba3 commit b24ac9c
Show file tree
Hide file tree
Showing 8 changed files with 526 additions and 144 deletions.
Original file line number Diff line number Diff line change
@@ -1,90 +1,71 @@
[[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 `<visualize>` directive.
To reuse an existing Visualization implementation for a more custom purpose, use the `<visualization>` directive instead.

==== VisualizeLoader
The `VisualizeLoader` class i the easiest way to embed a visualization into your plugin. It exposes
two methods:

- `getVisualizationList()`: which returns promise which gets resolved with list of saved visualizations
- `embedVisualizationWithId(container, savedId, params)`: which embeds visualization by id
- `embedVisualizationWithSavedObject(container, savedObject, params)`: which embeds visualization from saved object

`container` should be a dom element to which visualization should be embedded
`params` is a parameter object where the following properties can be defined:

- `timeRange`: time range to pass to `<visualize>` directive
- `uiState`: uiState to pass to `<visualize>` directive
- `appState`: appState to pass to `<visualize>` directive
- `showSpyPanel`: showSpyPanel property to pass to `<visualize>` directive
There are two different methods you can use to insert a visualization in your page.

To display an already saved visualization, use the `VisualizeLoader`.
To reuse an existing visualization implementation for a more custom purpose,
use the Angular `<visualization>` directive instead.

==== `<visualize>` directive
The `<visualize>` 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 `<visualization>` directive.

The simplest way is to just pass `saved-id` to `<visualize>`:

`<visualize saved-id="'447d2930-9eb2-11e7-a956-5558df96e706'"></visualize>`
==== VisualizeLoader

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:
The `VisualizeLoader` class is the easiest way to embed a visualization into your plugin.
It will take care of loading the data and rendering the visualization.

`<visualize saved-id="'447d2930-9eb2-11e7-a956-5558df96e706'"
time-range="{ max: '2017-09-21T21:59:59.999Z', min: '2017-09-18T22:00:00.000Z' }"></visualize>`
To get an instance of the loader, do the following:

Once <visualize> is done rendering the element will emit `renderComplete` event.
["source","js"]
-----------
import { getVisualizeLoader } from 'ui/visualize/loader';
When more control is required over the visualization you may prefer to load the saved object yourself and then pass it
to `<visualize>`
getVisualizeLoader().then((loader) => {
// You now have access to the loader
});
-----------

`<visualize saved-obj='savedVis' app-state='appState' ui-state='uiState' editor-mode='false'></visualize>` where
The loader exposes the following methods:

`savedVis` is an instance of savedVisualization object, which can be retrieved using `savedVisualizations` service
which is explained later in this documentation.
- `getVisualizationList()`: which returns promise which gets resolved with a list of saved visualizations
- `embedVisualizationWithId(container, savedId, params)`: which embeds visualization by id
- `embedVisualizationWithSavedObject(container, savedObject, params)`: which embeds visualization from saved object

`appState` is an instance of `AppState`. <visualize> is expecting two keys defined on AppState:
Depending on which embed method you are using, you either pass in the id of the
saved object for the visualization, or a `savedObject`, that you can retrieve via
the `savedVisualizations` Angular service by its id. The `savedObject` give you access
to the filter and query logic and allows you to attach listeners to the visualizations.
For a more complex use-case you usually want to use that method.

- `filters` which is an instance of searchSource filter object and
- `query` which is an instance of searchSource query object
`container` should be a DOM element (jQuery wrapped or regular DOM element) into which the visualization should be embedded
`params` is a parameter object specifying several parameters, that influence rendering.

If `appState` is not provided, `<visualize>` will not monitor the `AppState`.
You will find a detailed description of all the parameters in the inline docs
in the {repo}blob/{branch}/src/ui/public/visualize/loader/loader.js[loader source code].

`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.
Both methods return an `EmbeddedVisualizeHandler`, that gives you some access
to the visualization. The `embedVisualizationWithSavedObject` method will return
the handler immediately from the method call, whereas the `embedVisualizationWithId`
will return a promise, that resolves with the handler, as soon as the `id` could be
found. It will reject, if the `id` is invalid.

`editor-mode` defines if <visualize> should render in editor or in view mode.
The returned `EmbeddedVisualizeHandler` itself has the following methods and properties:

*code example: Showing a saved visualization, without linking to querybar or filterbar.*
["source","html"]
-----------
<div ng-controller="KbnTestController" class="test_vis">
<visualize saved-obj='savedVis'></visualize>
</div>
-----------
["source","js"]
-----------
import { uiModules } from 'ui/modules';
- `destroy()`: destroys the underlying Angualr scope of the visualization
- `getElement()`: a reference to the jQuery wrapped DOM element, that renders the visualization
- `whenFirstRenderComplete()`: will return a promise, that resolves as soon as the visualization has
finished rendering for the first time
- `addRenderCompleteListener(listener)`: will register a listener to be called whenever
a rendering of this visualization finished (not just the first one)
- `removeRenderCompleteListener(listener)`: removes an event listener from the handler again

uiModules.get('kibana')
.controller('KbnTestController', function ($scope, AppState, savedVisualizations) {
const visId = 'enter_your_vis_id';
savedVisualizations.get(visId).then(savedVis => $scope.savedObj = savedVis);
});
-----------
You can find the detailed `EmbeddedVisualizeHandler` documentation in its
{repo}blob/{branch}/src/ui/public/visualize/loader/embedded_visualize_handler.js[source code].

When <visualize> is ready it will emit `ready:vis` event on the root scope.
When <visualize> is done rendering it will emit `renderComplete` event on the element.
We recommend *not* to use the internal `<visualize>` Angular directive directly.

==== `<visualization>` directive
The `<visualization>` directive takes a visualization configuration and data.
It should be used, if you don't want to render a saved visualization, but specify
the config and data directly.

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

Expand All @@ -96,7 +77,7 @@ The `<visualization>` directive takes a visualization configuration and data.

`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
`uiState` is an instance of PersistedState. Visualizations use it to keep track of their current state. If not provided
`<visualization>` will create its own (but you won't be able to check its values)

*code example: create single metric visualization*
Expand All @@ -120,4 +101,4 @@ uiModules.get('kibana')
});
-----------

<visualization> will trigger `renderComplete` event on the element once it's done rendering.
<visualization> will trigger `renderComplete` event on the element once it's done rendering.
4 changes: 2 additions & 2 deletions src/test_utils/public/stub_get_active_injector.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import sinon from 'sinon';
* This method setups the stub for chrome.dangerouslyGetActiveInjector. You must call it in
* a place where beforeEach is allowed to be called (read: inside your describe)
* method. You must call this AFTER you've called `ngMock.module` to setup the modules,
* but BEFORE you first execute code, that uses chrome.getActiveInjector.
* but BEFORE you first execute code, that uses chrome.dangerouslyGetActiveInjector.
*/
export function setupInjectorStub() {
beforeEach(ngMock.inject(($injector) => {
Expand All @@ -30,7 +30,7 @@ export function setupInjectorStub() {
*/
export function teardownInjectorStub() {
afterEach(() => {
chrome.getActiveInjector.restore();
chrome.dangerouslyGetActiveInjector.restore();
});
}

Expand Down
75 changes: 0 additions & 75 deletions src/ui/public/visualize/loader.js

This file was deleted.

0 comments on commit b24ac9c

Please sign in to comment.