Skip to content
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

Events for plugin communication #13211

Closed
wants to merge 1 commit into from

Conversation

enoldev
Copy link

@enoldev enoldev commented Nov 25, 2019

This an implementation proposal on how can we handle plugin to plugin communication in an easier way. The concept uses "events" to which plugins can subscribe and it is implemented using the current RPC communication that mattermost-server uses to handle plugin communication.

This way, any plugin that is subscribed to an event will get the message without any other necessary action.

- Plugin API:
SubscribePluginEvent: allows to subscribe to an specific event
SendPluginEvent: allows you to send an event

- Hooks:
ReceivePluginEvent: allows you to handle events to which you have subscribed.

Example:

  1. Plugin A gets subscribe to "Test" event
  2. Plugin B sends a "Test" event
  3. Plugin A gets the "Test" event

A simple code snippet:

Plugin A

// We can use the OnActivate hook in order to get subscribe to events
func (p *Plugin) OnActivate() {
	p.API.SubscribePluginEvent("Test")
}

// We get all events here
func (p *Plugin) ReceivePluginEvent(event string, payload interface{}) {
	
	switch event {
	case "Test":
		testEventMessage := payload.(string)
	}
}

Plugin B

func (p *Plugin) SomeMethod() {
	p.API.SendPluginEvent("Test", "test message")
}

Tests are still missing and any sort of comments on this would be appreciated.

Thanks a lot!

@enoldev
Copy link
Author

enoldev commented Nov 25, 2019

Maybe we can set a "Work in progress" tag?

@hanzei hanzei added the Work In Progress Not yet ready for review label Nov 25, 2019
@mickmister mickmister self-requested a review November 26, 2019 09:41
Copy link
Member

@mickmister mickmister left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@enolal826 Great job! I have a few suggestions and design questions.

@crspeller I'd like to get your thoughts on this proposal.

errorList := []string{}

for _, subscribedPlugin := range subscriptionsForEvent {
error := api.app.servePluginEvent(event, payload, subscribedPlugin, api.manifest.Id)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In programs written in Go, it is common to use the variable name err when assigning a temporary error.

Suggested change
error := api.app.servePluginEvent(event, payload, subscribedPlugin, api.manifest.Id)
err := api.app.servePluginEvent(event, payload, subscribedPlugin, api.manifest.Id)

error := api.app.servePluginEvent(event, payload, subscribedPlugin, api.manifest.Id)

if error != nil {
errorList = append(errorList, subscribedPlugin)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to returning the aggregate error, I think the errors should be logged individually here, as the error messages may be different per plugin.


SendPluginEvent(event string, payload interface{}) *model.AppError

SubscribePluginEvent(event string) *model.AppError
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should have an UnsubscribePluginEvent as well, so a plugin can dynamically unsubscribe to the event.

pluginSubscriptionList := pluginSubscription.([]string)
pluginSubscription = append(pluginSubscriptionList, plugin)
} else {
pluginSubscription = []string{plugin}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if we want to make this into a defined []struct{}. We only have a use case for one field right now so it is fine as a string, as we can easily change later since the map is ephemeral.

We will want to avoid duplicate plugin ids in the subscription map. I'm thinking that a given plugin would call SubscribePluginEvent() in its OnActivate() hook method. This may cause problems if the plugin is disabled/re-enabled and is already subscribed to the event. With that in mind I think we should be removing the subscription whenever a subscribed plugin is deactivated.

@@ -155,4 +156,6 @@ type Hooks interface {
// Note that this method will be called for files uploaded by plugins, including the plugin that uploaded the post.
// FileInfo.Size will be automatically set properly if you modify the file.
FileWillBeUploaded(c *Context, info *model.FileInfo, file io.Reader, output io.Writer) (*model.FileInfo, string)

ReceivePluginEvent(c *Context, event string, payload interface{})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think something along the lines of OnPluginEvent may be more fitting. Looking at the pattern of naming, it seems appropriate to me. A comment needs to be placed here as well. Something like

OnPluginEvent is invoked on each subscribed plugin when a PluginEvent is dispatched.

Also, I think we should define a PluginEvent struct to represent the event, with the actual data payload as an interface{} inside of the event.

@mattermod
Copy link
Contributor

This issue has been automatically labelled "stale" because it hasn't had recent activity.
A core team member will check in on the status of the PR to help with questions.
Thank you for your contribution!

/cc @jasonblais @hanzei

@jasonblais
Copy link
Contributor

@aaronrothschild / @crspeller / @lieut-data I'm not sure if this is something that would be scheduled for review with other Toolkit related work, or related to the interplay between plugins that ChrisS is working on?

This was a November hackathon contribution from @enolal826 :)

@aaronrothschild
Copy link
Contributor

@aaronrothschild / @crspeller / @lieut-data I'm not sure if this is something that would be scheduled for review with other Toolkit related work, or related to the interplay between plugins that ChrisS is working on?

This was a November hackathon contribution from @enolal826 :)

I have it on my spreadsheet for tracking, but would look to @crspeller and @lieut-data to help prioritise this work.

  • Is this an architectural framework we want to build on top of?
  • What are the use cases we would try to address with this approach?
  • What are the shortcomings/challenges of this approach vs others we've considered?
    etc.

@lieut-data lieut-data self-requested a review January 8, 2020 21:09
Copy link
Member

@lieut-data lieut-data left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the possible exception of a few naming tweaks, I think the code is fine. We actually discussed something similar a while back: https://community-release.mattermost.com/core/pl/ee9yun66ybbgzpjney5eh4a1ra, but we eventually pivoted towards directly communicating between plugins via HTTP.

@aaronrothschild, in terms of prioritization, I don't know of any specific (internal or external) plans that would require a bus like this. It may not make sense to include this in core, /but/ it might be interesting to expose this as a plugin itself -- using the existing HTTP communication, and exposing a Go package for a plugin to subscribe and publish. No changes to core required, but about twice as many hook calls.

Thoughts?

@teresa-novoa-zz
Copy link

@lieut-data @aaronrothschild where are we with this PR? Thanks!

@lieut-data
Copy link
Member

Thanks for this effort, @enolal826! We are taking the plugin framework in a different direction right now, and so will decline these changes "as-is". I note above that I still think this could be interesting to expose as a plugin itself, requiring no server changes, but for now we won't be proceeding as-is.

(Thanks, @teresa-novoa, for the poke!)

@lieut-data lieut-data closed this May 12, 2020
@mattermod mattermod removed the Work In Progress Not yet ready for review label May 12, 2020
@teresa-novoa-zz
Copy link

great, thanks @lieut-data!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants