-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
visualizations emit 'renderComplete' event when done rendering #8914
Conversation
@@ -15,8 +15,8 @@ export default function TemplateVisTypeFactory(Private) { | |||
} | |||
} | |||
|
|||
TemplateVisType.prototype.createRenderbot = function (vis, $el, uiState) { | |||
return new TemplateRenderbot(vis, $el, uiState); | |||
TemplateVisType.prototype.createRenderbot = function (vis, $el, uiState, doneCallback) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or should we go for promises ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would that work? createRenderBot is only called at setup time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you are right
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this change overall. It's pretty clear at first glance, and user-friendly.
- I haven't tested this yet with the Reporting-module (so we should do that ;)).
- I used this API in the Tagcloud-proposal for now Tagcloud #8104).
- The rendered events are not fired when there is a change in the vis.params (and not the data). I don't think that is a problem in light of its purpose (supporting Reported), but this event (or something similar) should fire for any change in the visualization. This is also just a problem with the current implementations of e.g. MetricVis, that doesn't handle options changes explicitly but lets angular handle them transparently.
- Generally speaking, I think we should try and divorce this event from angular. But i guess that's long term. A specific issue right now with this approach is that we use the
scope
both to propagate model-changes (data, options), but also for the view to publish changes. That feels a little 'crooked', as scope should really only be the model-state. I think we can justify this as long as it is well documented an its use is only internal. But an event like this belongs more in the "view" (e.g. as an event of TemplateVisType). - I am not sure if 'rendered' should be an event to begin with. Essentially, we want a hook for a client to know when a visualization is ready. But the ready-state of a visualization also depends on the model-state and any other changes to the view. So we want a hook for when a visualization is ready for a particular configuration. From a client-perspective, when using events, you get into the complexity where you have to unhook your existing listeners if there are any state changes (since you might respond to events from older states). Another approach may be giving the visualizations a
whenReady
method that returns a promise. When this promise resolves, the visualization is ready at the moment it resolves and there are no outstanding model/config changes that are still being resolved.
I did found it easy to use in Tagcloud, but I think this API probably won't work long-term. So I guess we need to work out if this goes in as a sort of temporary improvement.
@@ -15,8 +15,8 @@ export default function TemplateVisTypeFactory(Private) { | |||
} | |||
} | |||
|
|||
TemplateVisType.prototype.createRenderbot = function (vis, $el, uiState) { | |||
return new TemplateRenderbot(vis, $el, uiState); | |||
TemplateVisType.prototype.createRenderbot = function (vis, $el, uiState, doneCallback) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would that work? createRenderBot is only called at setup time.
|
2eb664d
to
1b7f87a
Compare
i updated it to use SimpleEmitter (its using this one in vislib) in the TemplateVisType (instead of relying on angular $scope) |
18ffaa6
to
7013951
Compare
jenkins, test this |
7013951
to
ae0f782
Compare
d54a61e
to
c39c606
Compare
There are quite a few plugins that have visualization addons. It would be nice if this functionality did not require the plugin authors to add it into the plugin. |
@@ -11,6 +11,10 @@ export default function TemplateRenderbotFactory(Private, $compile, $rootScope) | |||
this.$scope.vis = vis; | |||
this.$scope.uiState = uiState; | |||
|
|||
if (this.vis.listeners.renderComplete) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like these instance checks. We should give all visualizations the renderComplete API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the simple publishing of an event on the scope (although perhaps we could leave the hook in vis/vis
and have Reporting hook into it directly).
I would propose using simple pub/sub with emit/on iso. having clients conditionally overwrite a method on .listeners.
if (vis) { | ||
vis.listeners.renderComplete = function () { | ||
$scope.$emit('renderComplete'); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't have an external component be responsible for implementing functionality of the vis-type. We should try and decouple pub/sub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
its not necessary to define this function, if component using Vis needs it it can define it ...
VisType.prototype.createRenderbot = function (vis, $el, uiState) { | ||
throw new Error('not implemented'); | ||
}; | ||
|
||
return VisType; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not a 100% sure if we need to broker this with the renderbot at all, but maybe I miss something major.
Since every externally accessible visualization (instance of vis
) needs to publish a 'renderComplete' event, I would propose modeling on the Vis-level.
E.g.:
- We make
ui/vis/vis
support pub/sub (perhaps do the same forui/vis/vis_type
in fact)
function Vis(indexPattern, state, uiState) {
EventEmitter.call(this);
... }
Vis.prototype = Object.create(EventEmitter.prototype);
- visualization implementations publish their readiness (templated vis, vislib vis)
e.g. in metric_vis_controller
, (but also table_vis_controller
, `vislib, ...)
$scope.processTableGroups(tabifyAggResponse($scope.vis, resp));
$scope.vis.emit('renderComplete');
}
- listeners can just subscribe to this events
e.g. inui/visualize/visualize
, (but also Reporting...)
if (oldVis) $scope.renderbot = null;
if (vis) {
vis.on('renderComplete', () => {
//...here we could propage on the correct scope with $scope.$emit('renderComplete');
// ...but perhaps Reporting could just listen to it directly
});
}
I think this will have several advantages:
- the implementation remains a little cleaner
- we don't have clients conditionally implement a method. They can just use
on
. - publishers can just use emit() to publish, and don't have to worry if there is a registered listener or not
- we don't have clients conditionally implement a method. They can just use
- Thinking longer ahead, this will be easier to document in JSDoc as well since @events are a core-type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
regarding VisType.prototype.createRenderbot ... VisType must implement this method, thats why i added an implementation to the base type which will throw an error if this method is not implemented on vis subtype.
Regarding Vis extending EventEmitter ... i am generally for that. But i don't feel that having every new functionalty achieve things in different way is a good way forward. The listeners object is currently used to hook to events in vis ... so i followed the same pattern. If we would change that i would be for changing that for all the events.
i agree implementation would be cleaner (template_renderbot doesnt need to do anything)
- but nothing much would change for publishers (they can emit now as well, and even in this case with no registered listener nothing happens ...) so nothing changes for them
- clients (like visualize) would still need to define a listener (setting an object property or calling on doesnt make that much of a difference)
i think the biggest benefit of this would be not having to rely on angular (which i hear we are moving away from)
@spalger do you maybe know why current implementation is using listeners object with functions which are then attached to events instead of just having vis extend EventEmitter ?
another thing i would like to understand is the use of all the different event objects (utils/simple_emitter, public/events) ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do you maybe know why current implementation is using listeners object with functions which are then attached to events instead of just having vis extend EventEmitter ?
I'm 99% sure that is was just the way that the original implementor wanted to do it. The listeners object though was intended to be a shortcut for situations like this but I totally agree that it would be best to have one way of doing that (and I prefer vis.on(...)
)
i would like to understand is the use of all the different event objects
The SimpleEmitter
class is a standard event emitter, very similar to the event emitter in node.js. The Events
class in ui/events
is an angular compatible version that wraps handlers in promises so that they properly trigger angular digest cycles and bind to the correct angular scope when listeners are added. If the vislib uses the SimpleEmitter
then any angular consumer has to wrap their event handlers in $scope.apply()
or something similar to ensure that an angular digest cycle is triggered after running, but if it uses the ui/events
Events
class then angular consumers will just be able to consume them like normal.
@trevan this change will not require external add-ons to implement this. We need this event to support screenshot functionality (e.g. for testing, reporting). But this is not planned to be a breaking change. Over time, I do see a use for visualizations in Kibana having to advertise when they are done rendering (since there is no guarantee visualizations must render synchronously), but we're not at the point yet of making this an API requirement. |
@thomasneirynck, but doesn't an external add-on that wants to work with reporting not need this change? |
i agree with @trevan, 3rd party plugins will need to implement this in order to work with reporting. |
@trevan yup, they would have to, but they don't necessarily work with Reporting right now either (all depends if they render async or not). FWIW, Kibana needs a public API for Visualizations, one that is documented and stable across releases. One of the requirements of that API is that it must support visualizations that render async (e.g. they might have animations, render on a requestAnimationFrame-loop, etc....). But that's an effort down the road. This PR is addressing a specific issue right now, but we don't want to enforce it yet for external add-ons. |
@@ -11,7 +11,11 @@ marked.setOptions({ | |||
const module = uiModules.get('kibana/markdown_vis', ['kibana', 'ngSanitize']); | |||
module.controller('KbnMarkdownVisController', function ($scope) { | |||
$scope.$watch('vis.params.markdown', function (html) { | |||
if (!html) return; | |||
if (!html) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something like this would be cleaner:
if (html) { $scope.html = marked(html); }
$scope.$emit('renderComplete');
Plus, then there's no potential of forgetting to emit if other conditions are added.
c39c606
to
7e8a2bf
Compare
@thomasneirynck i think this is ready for another review. |
this.render(this.data, this.uiState); | ||
} | ||
}; | ||
uiState.on('change', this._uiStateChangeHandler); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for some reason uiStateChangeHandler gets called 2-3 times AFTER vis.destroy() was called (which removes the event handler) when we open a new dashboard for example. multiple renderComplete would be thrown (for each render call) when opening a new dashboard. i was not able to identify why this is happening so this is a short term workaround (check if the element is still in DOM).
|
||
self.vislibParams = self._getVislibParams(); | ||
self.vislibVis = new vislib.Vis(self.$el[0], self.vislibParams); | ||
_.each(this.vis.listeners, (listener, event) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in vislib we still depend on listeners object. as this would require many changes i suggest to do it in a follow up PR.
if (vis) { | ||
vis.on('renderComplete', () => { | ||
$scope.$emit('renderComplete'); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@w33ble are you ok with removing emiting renderComplete on scope (so you would need to subscribe to $visualizeScope.vis.on instead of $visualizeScope.on ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that's fine with me. I just need something to listen to, I don't care what that something is.
Latest changes, with the implementsRenderComplete() helper, look good. However, I'm running into another problem. The visualization is pretty quick to render the vis on my machine, and the listener is attached after it's already done. I think wrapping up the listener in a helper as well, and firing it immediately if the event has already emitted, is the way to go. Something like |
And here's an example using a Tilemap, where the tiles take a few seconds to download it delays the Successfully generated a Dashboard with every vis type, and even removed the event from Timelion to test without the listener. Functionally LGTM! |
@thomasneirynck there's been a few small commits since you last looked at this I think. Once you've taken a look through them, and assuming you approve, let's get this merged. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
@w33ble Looked at the last API changes. Looks good. I like this approach. |
Cool. I think this is good to merge then. I'll let @ppisljar do the honors. |
Backports PR #8914 **Commit 1:** visualize emits renderComplete event once its done rendering * Original sha: 2195692 * Authored by ppisljar <[email protected]> on 2016-11-04T09:32:29Z **Commit 2:** fixing test * Original sha: 97a390a * Authored by ppisljar <[email protected]> on 2016-11-04T09:48:23Z **Commit 3:** adding renderComplete event to timelion * Original sha: b833873 * Authored by ppisljar <[email protected]> on 2016-11-10T15:14:16Z **Commit 4:** adding renderComplete event to timelion * Original sha: ab94eef * Authored by ppisljar <[email protected]> on 2016-11-11T09:20:32Z **Commit 5:** adding implementsRenderComplete property to vis * Original sha: b2ff874 * Authored by ppisljar <[email protected]> on 2016-11-16T06:50:19Z **Commit 6:** adding helper method on vis * Original sha: fc78562 * Authored by ppisljar <[email protected]> on 2016-11-16T18:21:46Z **Commit 7:** adding onRenderComplete method * Original sha: 451ab9a * Authored by ppisljar <[email protected]> on 2016-11-16T18:48:48Z
Backports PR #8914 **Commit 1:** visualize emits renderComplete event once its done rendering * Original sha: 2195692 * Authored by ppisljar <[email protected]> on 2016-11-04T09:32:29Z **Commit 2:** fixing test * Original sha: 97a390a * Authored by ppisljar <[email protected]> on 2016-11-04T09:48:23Z **Commit 3:** adding renderComplete event to timelion * Original sha: b833873 * Authored by ppisljar <[email protected]> on 2016-11-10T15:14:16Z **Commit 4:** adding renderComplete event to timelion * Original sha: ab94eef * Authored by ppisljar <[email protected]> on 2016-11-11T09:20:32Z **Commit 5:** adding implementsRenderComplete property to vis * Original sha: b2ff874 * Authored by ppisljar <[email protected]> on 2016-11-16T06:50:19Z **Commit 6:** adding helper method on vis * Original sha: fc78562 * Authored by ppisljar <[email protected]> on 2016-11-16T18:21:46Z **Commit 7:** adding onRenderComplete method * Original sha: 451ab9a * Authored by ppisljar <[email protected]> on 2016-11-16T18:48:48Z
…ic#9113) Backports PR elastic#8914 **Commit 1:** visualize emits renderComplete event once its done rendering * Original sha: 2195692 * Authored by ppisljar <[email protected]> on 2016-11-04T09:32:29Z **Commit 2:** fixing test * Original sha: 97a390a * Authored by ppisljar <[email protected]> on 2016-11-04T09:48:23Z **Commit 3:** adding renderComplete event to timelion * Original sha: b833873 * Authored by ppisljar <[email protected]> on 2016-11-10T15:14:16Z **Commit 4:** adding renderComplete event to timelion * Original sha: ab94eef * Authored by ppisljar <[email protected]> on 2016-11-11T09:20:32Z **Commit 5:** adding implementsRenderComplete property to vis * Original sha: b2ff874 * Authored by ppisljar <[email protected]> on 2016-11-16T06:50:19Z **Commit 6:** adding helper method on vis * Original sha: fc78562 * Authored by ppisljar <[email protected]> on 2016-11-16T18:21:46Z **Commit 7:** adding onRenderComplete method * Original sha: 451ab9a * Authored by ppisljar <[email protected]> on 2016-11-16T18:48:48Z Former-commit-id: 2393329
Hi @ppisljar, |
visualization would fire renderComplete in dashboard context as well. I don't think dashboard itself fires any event once all the visualizations are rendered @stacey-gammon ? |
Hi again @ppisljar , |
As far as I know, the only way to check for this on the dashboard is to count the number of visualizations in that dashboard and wait for the |
yup, what w33ble said, there is no event on the dashbboard level that would notify you of rendering being done ... right @stacey-gammon ? would it make sense to introduce one ? (for reporting as well) |
Not currently, but one could pretty easily be added with the embeddable stuff. I could see it being added in once the embeddable stuff is more fleshed out, but it's not really something we want to rush to throw in there, esp if there are no current internal use cases. If we have an internal use case for it (maybe with reporting?), then it would likely get done sooner, since we'd be able to verify it all worked as expected. |
Yeah right now reporting needs to figure that out on its own, I think dashboard should be handling that |
Vis emits 'renderComplete' event once its done rendering. This allows reporting to know when to make screenshots.
the event will also be emitted in case: