-
Notifications
You must be signed in to change notification settings - Fork 74
Exporting ajaxWrapper and config to UI plugins #813
Exporting ajaxWrapper and config to UI plugins #813
Conversation
UI plugins should be able to call the backend. Exporting ajaxWrapper and config makes it possible to a plugin to make such a call: ``` const { ajaxWrapper, config } = window.marathonPluginInterface; this.http({ url: `${config.apiURL}v2/plugins` }).success(...).error(...); ```
Hey @daltonmatos, I very much like the idea of enabling plugins to fetch data from Marathon directly to support the creation of feature rich plugins. Sharing the complete config is a good idea, and I can't think of a reason to not share the config, but we have to make sure the config is not writable nor configurable ( |
Hello @orlandohohmeier. Totally agree! Didn't know about the About the Also agree on the MararthonService.request({}, success_callback, error_callback) Or should it be the builder style (as it is on MararthonService.request({})
.success(function(){})
.error(function(){}) Thinking about the code, it would be a direct call to Does that make sense? Let me know what you think and I will write the code. In the mean time, I will adjust this PR with the changes we already agreed on. Thanks, |
The config object already has the value of `UIVersion`, so there's no need to have the same value with different exported with names.
Hello @orlandohohmeier, any thoughts on the Thanks, |
Hello @orlandohohmeier, how about removing Yet, how do we properly document this breaking change about removing Thanks, |
Hi @daltonmatos, please excuse the late response, I haven't had the time to think about your proposal any sooner. I really like where the As propose I'd probably expose an interface very similar to the Example // fetching groups with defaults
MararthonService.request({
resouce: '/groups'
})
.success(function(response) {})
.error(function(response) {})
// fetching groups data that is no than 500ms
MararthonService.request({
resouce: '/groups',
age: 500
})
.success(function(response) {})
.error(function(response) {})
// deploying a new app
MararthonService.request({
resouce: '/apps'
data: {new_app},
method: 'POST',
age: 500
})
.success(function(response) {})
.error(function(response) {}) Possible Options
Next Steps
What do you think? /cc @mesosphere/frontend |
That's indeed a great idea @orlandohohmeier. I have a doubt about the I was thinking about this
MarathonService.createApp({
data: {app_data},
otherOption: ...,
...
})
.success(function(response) {})
.error( function(response) {}) This gives room to changes to backend API without breaking plugins. Also a plugin developer does not have to worry about the details of the underlying API, it would be able to always call
MarathonService.dialogs.alert([{
title: "Dialog Title",
message: "Dialog Message",
otherOptions: ...
}]); Of course, And this goes on and on, this object could have methods to let plugins subscribe to internal UI events, dispatch UI events, mount new components, modify requests that the UI is about to perform, receive keystroke events (think about a plugin that adds new keyboard shortcuts), and everything we want to let a plugin do. If you think this is a good idea, we can already namespace this Obviously this is a experimental code that would be evolved in future releases. It's also perfectly possible to have two PluginAPIs exposed by the UI, while the old API is correctly deprecated. |
Thanks for your feedback @daltonmatos! You're right I like the idea of creating a rich We actually planned to deliver an independently versioned interface but decided to run with an integrated version till we see the need for a separate one. It might be the right time to reactive the repository. What do you think? https://github.com/mesosphere/marathon-ui-plugin-sdk/tree/feature/pull-3107-plugin-interface I don't see a need for the How would you like to proceed? |
Hello @orlandohohmeier, All fine about the Being agreed about the implementation of a more cohesive interface (instead of a strictly "raw" implementation) I think we should start writing it as a first iteration of this new plugin interface. I totally agree about the extracted plugin SDK, but I think that doing it right now would be a bit too much, after all, we are creating something very experimental that we believe will become the new standard for Marathon UI Plugins in the future, so I would think that we should first somewhat confirm that what we are choosing now will work and then we extract the SDK. This will make our lives easier during this early development, and as a bonus if we maintain the same methods, namespaces, etc. when extracting the SDK, no plugin will need to be changed. 😉 About the So a plugin could use this "raw" http interface until the Plugin Interface is updated with more suitable methods that helps this plugin achieve what it wants, and then it could switch from the raw to the high level methods. Another usefulness of this raw API would be to call a plugins' backend http endpoint(s). The UI Core and the exposed Plugin Interface don't know about which endpoints a plugin can or will implement, so having a raw http interface would be useful for plugin developers to call its backend when necessary. Of course that All said, I think we should start writing the What do you think? Give me a green signal and I will start writing this initial wrapper. What do you think about a new issue on the SDK repo so we can talk about more broad things related to this new interface? We could talk about what will be exposed and how it will be implemented (Plugin mountpoints Vs. Dispatched events, etc). I have some doubts about the code on this branch you linked. Thanks a lot and sorry for the very long posts. 😔 [1] We can begin with this name and change later, no problem. But we should change it fast as this will be a breaking change. |
Hello @orlandohohmeier, any thoughts about this? I was thinking about starting to write some code during the weekend, what do you think? Thanks, |
Hi @daltonmatos! I like the idea of providing a low-level-i-know-what-i-am-doing interface to interact with Marathon. I'd propose the following namespaces on the plugin interface:
I've outlined the respective modules in the following: MarathonServiceThis service provides an interface to request Marathon resources. Plugins will need to use the standard XHR or fetch interface to perform arbitrary requests. ℹ️ This service enables plugins to load files that are served s using the Examples// fetching groups with defaults
MararthonService.request({
resouce: '/groups'
})
.then(function success(response) {}, function error(response) {})
// deploying a new app
MararthonService.request({
resouce: '/apps'
data: {new_app},
method: 'POST'
})
.then(function success(response) {}, function error(response) {}) MarathonActionsThese actions will use the ExamplesMarathonActions.getGroups()
.then(function success(response) {}, function error(response) {})
MarathonActions.createApp().
.then(function success(response) {}, function error(response) {}) Please let me know if this is sufficient and if you need anything else to get started. |
Hello @orlandohohmeier , this is sufficient to begin coding. The only thing I would like to suggest changing (but this doesn't prevent me to start coding) is: Change the MarathonActions.getGroups()
.success(function success(response) {})
.error(function error(response) {}) I think this approach more pleasant to have. What do you think? I will try to code something this weekend. Hope I have some free time. |
@daltonmatos please excuse the late response. I'm fine with have |
No problem @orlandohohmeier. I will try to write something and post here. Thanks. |
Hello @orlandohohmeier , here is a Proof of Concept about what we talked about. About the I also decided to have all methods inside The same happend when I think that, if what we are doing works, pluginWindow.marathonPluginInterface = Object.freeze({
pluginId: pluginId,
PluginAPI: PluginAPI
}); and all other currently exported objects would be migrated to namespaces inside Let me know what do you think about this. I implemented a sample plugin that uses this new interface and it works as expected. Thanks a lot. |
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.
💜 Thanks a lot for putting this together. This is great and it will enable lots of new cool plugins.
I added a few comments to further improve the code and make this as reliable as possible. Once again, thank you.
|
||
export default class MarathonService { | ||
|
||
request(opts = {}) { |
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.
Let's make request static so that you don't need to create an instance of MarathonService
.
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.
👍
|
||
export default class MarathonActions { | ||
|
||
getDeployments() { |
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.
Let's make these "actions" static so that we don't need to create and instance of MarathonActions
.
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.
👍
} | ||
|
||
getGroup(group) { | ||
/* ${group} already has the leading '/' */ |
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'd add a check to make sure group
actually starts with a slash /
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.
👍
method = opts.method; | ||
} | ||
|
||
return ajaxWrapper({ |
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 wonder if it makes sense to always allow additional option to pass embed
flags.
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.
Currently it is possible to pass all needed embeds directly on the resource
attribute, like this:
MarathonService.request({
resource: "/v2/groups?embeds=...&embeds=..."
});
Do you think we need a new attibute specific for this on the object passed to request()
?
Maybe this makes more sense on MarathonActions.getGroup()
, since there we have a higher level API.
What do you think?
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.
Fair point. Let's add these flags to the higher level API.
src/js/plugin/PluginLoader.js
Outdated
@@ -45,7 +48,9 @@ const PluginLoader = { | |||
PluginMountPoints: PluginMountPoints, | |||
pluginId: pluginId, | |||
React: React, | |||
UIVersion: config.version | |||
ajaxWrapper: ajaxWrapper, |
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.
Let's not share the ajaxWrapper
.
src/js/plugin/PluginLoader.js
Outdated
UIVersion: config.version | ||
ajaxWrapper: ajaxWrapper, | ||
config: Object.freeze(config), | ||
PluginAPI: PluginAPI |
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 we really need to wrap these into a dedicate PluginAPI
namespace? Shouldn't it be enough to simple provide MarathonService
and MarathonActions
?
About the possibility to export all namespaces individually and directly on The ideas behind what I did are:
This is what I think about what would be a PluginSDK (on a separated repository).
import PluginAPI from "marathon-ui-plugin-sdk";
With the setup like this, Marathon UI would always be shipped with a specific version of the plugin-sdk. And all this would be done without breaking any plugin code, because the interface is the same, there's only one object for plugins to import: Also the plugin code would become simpler: const {PluginAPI} = window.marathonPluginInterface; and all further interactions will be done with What do you think? |
Proof of concept of the new PluginAPI that will be exported to Marathon UI plugins.
Also implementing a Utils class with common code, useful for both namespaces.
Hello @orlandohohmeier, any thoughts? Thanks, |
|
||
export default class MarathonService { | ||
|
||
static request(opts = {}) { |
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.
Definitely not a blocker but you could use destructing to extract resource
, method
as well as the data
as illustrated in the following snippet:
static request({resource, method="GET", data}) {
// ...
return ajaxWrapper({
url: `${config.apiURL}${resource}`,
method: method,
data: opts.data
});
}
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 is awesome! Will do it. This will make the code much simpler!
|
||
static request(opts = {}) { | ||
let method = "GET"; | ||
let resource = Utils.addLeadingSlashIfNedded(opts.resource); |
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 not sure if we need a dedicated util to prepend a slash. Applying a simple RegExp
like the following one should do the trick.
resource.replace(/\/?(.*)/,"/$1")
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.
Perfect. Will change to use a simple regex.
src/js/plugin/sdk/utils.js
Outdated
|
||
export default class Utils { | ||
|
||
static addLeadingSlashIfNedded(value) { |
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.
Let's not create a new util only to add a slash.
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.
Sure!
method = opts.method; | ||
} | ||
|
||
return ajaxWrapper({ |
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.
Fair point. Let's add these flags to the higher level API.
I'm still don't think we should add a Example pluginWindow.marathonPluginInterface = new MarathonPluginInterface({
PluginMountPoints: PluginMountPoints,
pluginId: pluginId,
React: React,
UIVersion: config.version
}); N.B. The plugin loader probably needs to inject a few dependencies to provide access to the respective Marathon UI bits. We should look into different IOC containers to abstract the the injections to properly decouple the loader and the interface. |
About the |
All done @orlandohohmeier. Waiting for your review. |
@daltonmatos this looks great! Thanks a lot for your contribution and efforts to extend the plugin interface to enable new use cases. 🙇 |
Hello all,
this PR aims to make it possible to write more useful plugins. Plugins that are able to request info from the backend API. Let me know what you think and if I need to adjust anything.
UI plugins should be able to call the backend. Exporting ajaxWrapper and
config makes it possible to a plugin to make such a call: