-
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
Create the concept of embeddableHandlers #12146
Create the concept of embeddableHandlers #12146
Conversation
b30fafe
to
1602e2e
Compare
713fca2
to
2847207
Compare
jenkins, test this |
@thomasneirynck @kjbekkelund, do you guys think you will have a chance to review this over the next week or so? It's not a big rush but would help move things along if I could get it checked in. |
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.
This approach looks sane to me, and it all seems to work as expected.
Should probably be reviewed by someone with more Angular skills than me, though, to double-check the Angular-isms.
Should there be tests for each of the embeddable handlers too?
I had a tiny nit, but it all LGTM.
} | ||
const type = $scope.panel.type; | ||
const id = $scope.panel.id; | ||
const service = _.find(services, { type: 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.
nitpick (and I know it's just copy/pasted from the old code): If we're still on the "use less lodash", this can be services.find(service => service.type === type)
.
@spalger - do you have interest in giving this a look over? |
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 genuinely like the idea. I agree that this lower-level of abstraction is a better fit for all the possible dashboard panels, given what we have in Kibana now. The visualize refactor can for now work in isolation just for the Visualize components and doesn't need to accomodate saved searches.
As for this PR. I looked at it only briefly so far, but nothing major jumped out.
I would consider creating an abstract class of an EmbeddableHandler that defines the shape of the type. All children (Visualize/Search/...) then subclass from it. The duck-typing of the EmbedablleHandler-type now works, but having the inheritance would help navigability and would be a form of documentation too.
I'd also add, beyond the 'render', `getEditPath', ... two new methods:
- a
destroy
method, that gets called for all when people navigate away from the dashboard. - a resize(), that instructs an embeddalbe to fit to the dimension of the new surrounding container.
This would match what we're working towards for the shape of the visualization-object in "Visualize". I think these are generic enough to apply to any UI-component.
Do we have to pass in the panel
object in getTitlefor/getEditPath. Why not just the 'panel-id'?
For each EmbeddableHandler, consider making name
and title
constants in the module-scope. These are immutable properties, so they don't need to be instance-properties assigned in the cosntructor.
Embedding a Coordinate Map on the dashboard causes with a TypeError. Cannot read get of undefined
on this line https://github.com/thomasneirynck/kibana/blob/262bcb411acc6269dbd0fba519bbee3f96b0e363/src/ui/public/vis_maps/maps_renderbot.js#L58-L58.
I applied these changes to the feature/visualize
branch, but there were quite a few merge conflicts, which I didn't have the time to fully resolve. Before we merge either, we should ensure that both PRs will work (this can be merged first no problem of course, but we should check if the feature/visualize branch is compatible)
@thomasneirynck - I don't think we have any precedent for this in our code base currently. I'm personally a fan of it, and I used interfaces with typescript at a prior company, but when I've done that type of thing here, in PRs past, it's generally been unfavorable viewed, and the comprise decided on was to use JSDoc to document the interface. The downside being that you need a common place to put the interface and there isn't a good common spot right now. I suppose I could put it in the embeddable_handler_registery. What do you think of that approach? cc @weltenwort since I think most of the conversations I had regarding this topic were with you, can you confirm that I'm stating that correctly and you prefer JSDoc interface definitions over abstract classes? Perhaps we should bring this up as a new issue for team wide votes so we can have the decision written down and everyones input. |
Right now each embeddable handles destroying itself when that happens:
If we wanted to extract it out into a new function in the handler, we'd have to keep track of all panels that were rendered, and map them to their scope objects, because a destroy on a handler will need to destroy every rendered panel. Maybe what we need, in the long term, is to have and Embeddable and an EmbeddableHandler, and the Embeddable has a destroy method. But as of right now I think that would just complicate the logic. The same thing goes for the resize function - you'd need a context of all the panels the handler rendered, which right now, it doesn't need to store. The resizing all works naturally, so I'm not even sure when I would call it in dashboard. |
I got rid of title for now, it wasn't being used. I'll bring it back when it is. As for name, I'm pretty sure I need to leave it as is for the registry handlers to work correctly: |
…onality into a new class
@spalger -- any more feedback on your end? |
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.
Looking good, some more feedback
@@ -0,0 +1,9 @@ | |||
import { uiRegistry } from 'ui/registry/_registry'; |
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.
Can we put this in ui/registry
with the rest of the registries? I'm not a huge fan of the decision to go that route but we should probably stay consistent.
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 are already starting to deviate elsewhere - savedObjectsRegistery. Though yes most are in the ui/registry folder. If you feel strongly about it I'll move it over to ui/registry, though I'd rather not. :)
* @param operator | ||
* @param index | ||
*/ | ||
onFilter(/*field, value, operator, index */) { |
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.
This should probably be addFilter()
right?
* @param uiState {Object} - the uiState for the child. | ||
* @returns {Object} | ||
*/ | ||
createChildUiState(/* path, uiState */) { |
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.
Can we call this initalState
or something that disambiguates it from the PersistedState
instances we usually call uiState
? If it was a PersistedState
then we would just be able to call createChild()
on it.
/** | ||
* The ContainerAPI is an interface for embeddable objects to interact with the container they are embedded within. | ||
*/ | ||
export class ContainerAPI { |
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.
👍
return savedObject.title; | ||
} | ||
|
||
async render(domNode, panel, container) { |
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.
Unfortunately there are a lot of unexpected and hard-to-debug issues that pop-up when native promises or async/await with Angular. I hate to say it but please convert any async/await
or native promise usage that is interacting with Angular to use Angular 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.
so saaaaaaad :(
* from panel.id because you can have the same object rendered multiple times in two different panels. | ||
* @param {Promise.<void>} A promise that resolves when the object is finished rendering. | ||
*/ | ||
async render(/* domNode, panel, container */) { |
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.
Looks like the params here are now domNode, panel, containerApi
? jsdocs need updates.
} | ||
|
||
async getEditPath(panelId) { | ||
return await this.searchLoader.urlFor(panelId); |
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.
If an embeddable had controls for editing itself it would need to be able to tell consumers that the title/path/etc. have changed, any idea how that would work?
I assume we couldn't just replace these methods with something like ContainerAPI#setEditPath()
and ContainerAPI#setTitle()
because we want to show the edit path and title for a panel without rendering it... Maybe the container API could expose a method to invalidate the editPath and titles that are cached by containers?
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.
Yea, that's a good point. Not sure how I feel about adding that functionality now though when it's not needed by anything we build. Yes, we want this to be a flexible embeddable system for others to easily write panels in, but I think during this initial phase, it's okay to limit what the container api exposes. We should probably add some kind of forceRefresh functionality that forces a rebuild of the whole panel at some point. Just not sure I want to go down this path without an easy way to test it works. Thoughts?
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, without a use case we probably shouldn't design a solution, I'm fine with punting for now. We can figure out how to make it work when we need it (hint: I'm thinking about observables)
- Rename onFilter to addFilter - Use angular promises instead of async/await - fix jsdoc - rename createChildUiState to getInitialState
return this.dashboardState.getIsViewMode(); | ||
} | ||
|
||
getInitialState(path, uiState) { |
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.
Haha, I wasn't very clear, I was asking for createChildUiState(path, initialState)
This method returns an instance of PersistedState
, which we usually refer to as uiState
. This method also takes an argument which it calls uiState
, but it's actually just a plain object that is used as the initial state for the uiState
that is returned.
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.
hah, okay, yea, this function is carried over from the past and I never understood much of it. Will switch to your intended version. :)
* @return {Promise.<string>} a promise that resolves with the path that dictates where the user will be navigated to | ||
* when they click the edit icon. | ||
*/ | ||
async getEditPath(/* panelId */) { |
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.
No need for async
(or Promise.reject
below)
Looks like spy panel shows up on visualizations that are added, sounds like a two-way binding is being broken |
Nice find. I see where it's coming from. Strange though because I have a test that verifies the spyPaneToggle button exists, and works, and that should have caught it. Will investigate. |
There is still a bit of a bug here as mentioned in elastic#13340 but it will be fixed separately as it’s also an issue in master
Added a test that would have caught it and fixed up the logic. The binding part is still an issue, but it's an issue in master as well and I filed #13340 to keep track. It's just much less obvious of a bug when the logic is correct because it only hides the spy pane in embed mode and you aren't supposed to leave embed mode without a hard refresh. |
* Move dashboard panel rendering logic to each registered type. * Remove dashboard knowledge of click and brush handlers for visualizations Move it to the VisualizeEmbeddableHandler. * merge master with manual changes * No need to use lodash * Add EmbeddableHandler base class * Use correct path to embeddable_handlers_registry * clean up * Set visualize scope uiState that is of type PersistedState, otherwise it won't actually be set. * add retry to loading saved search data * Fix handleError param and remove unnecessary private param * Rename savePanelState updatePanel and return the new object rather than mutating the original * Make ContainerAPI a base class and move the dashboard specific functionality into a new class * Make api's async and clean up documentation * Fix panel tests * Fix bug which broke tests - need to pass container-api to dashboard-panel * Address code comments - Rename onFilter to addFilter - Use angular promises instead of async/await - fix jsdoc - rename createChildUiState to getInitialState * changed the wrong variable name * no need for async or Promise.reject on interface * add tests that will fail due to spy pane issue in this PR * Fix logic with spy pane toggle There is still a bit of a bug here as mentioned in elastic#13340 but it will be fixed separately as it’s also an issue in master * Fix failing test
* Move dashboard panel rendering logic to each registered type. * Remove dashboard knowledge of click and brush handlers for visualizations Move it to the VisualizeEmbeddableHandler. * merge master with manual changes * No need to use lodash * Add EmbeddableHandler base class * Use correct path to embeddable_handlers_registry * clean up * Set visualize scope uiState that is of type PersistedState, otherwise it won't actually be set. * add retry to loading saved search data * Fix handleError param and remove unnecessary private param * Rename savePanelState updatePanel and return the new object rather than mutating the original * Make ContainerAPI a base class and move the dashboard specific functionality into a new class * Make api's async and clean up documentation * Fix panel tests * Fix bug which broke tests - need to pass container-api to dashboard-panel * Address code comments - Rename onFilter to addFilter - Use angular promises instead of async/await - fix jsdoc - rename createChildUiState to getInitialState * changed the wrong variable name * no need for async or Promise.reject on interface * add tests that will fail due to spy pane issue in this PR * Fix logic with spy pane toggle There is still a bit of a bug here as mentioned in #13340 but it will be fixed separately as it’s also an issue in master * Fix failing test
Register embeddable handlers to push rendering logic outside of dashboard.
As dashboard attempts to render each panel, it will use panel type to grab the corresponding embeddable handler. The actual rendering logic is inside of the embeddableHandler which gets passed a dom node, along with some actions.