From 3515b4d6c98072a76445f42d85cc5e8965d753f9 Mon Sep 17 00:00:00 2001 From: Jared Shields Date: Sat, 31 Oct 2020 02:06:43 -0400 Subject: [PATCH] GH-15624: Added plugin hooks for `ReationHasBeenAdded` and `ReactionHasBeenRemoved` (#15765) --- app/plugin_hooks_test.go | 86 +++++++++++++++++++++++++++ app/reaction.go | 21 +++++++ plugin/client_rpc_generated.go | 68 +++++++++++++++++++++ plugin/hooks.go | 56 +++++++++++------ plugin/hooks_timer_layer_generated.go | 12 ++++ plugin/plugintest/hooks.go | 10 ++++ 6 files changed, 234 insertions(+), 19 deletions(-) diff --git a/app/plugin_hooks_test.go b/app/plugin_hooks_test.go index 5c34e16758..39b96d22ce 100644 --- a/app/plugin_hooks_test.go +++ b/app/plugin_hooks_test.go @@ -1131,3 +1131,89 @@ func TestHookMetrics(t *testing.T) { metricsMock.AssertExpectations(t) }) } + +func TestHookReactionHasBeenAdded(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + + var mockAPI plugintest.API + mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil) + mockAPI.On("LogDebug", "smile").Return(nil) + + tearDown, _, _ := SetAppEnvironmentWithPlugins(t, + []string{ + ` + package main + + import ( + "github.com/mattermost/mattermost-server/v5/plugin" + "github.com/mattermost/mattermost-server/v5/model" + ) + + type MyPlugin struct { + plugin.MattermostPlugin + } + + func (p *MyPlugin) ReactionHasBeenAdded(c *plugin.Context, reaction *model.Reaction) { + p.API.LogDebug(reaction.EmojiName) + } + + func main() { + plugin.ClientMain(&MyPlugin{}) + } + `}, th.App, func(*model.Manifest) plugin.API { return &mockAPI }) + defer tearDown() + + reaction := &model.Reaction{ + UserId: th.BasicUser.Id, + PostId: th.BasicPost.Id, + EmojiName: "smile", + CreateAt: model.GetMillis() - 10000, + } + _, err := th.App.SaveReactionForPost(reaction) + require.Nil(t, err) +} + +func TestHookReactionHasBeenRemoved(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + + var mockAPI plugintest.API + mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil) + mockAPI.On("LogDebug", "star").Return(nil) + + tearDown, _, _ := SetAppEnvironmentWithPlugins(t, + []string{ + ` + package main + + import ( + "github.com/mattermost/mattermost-server/v5/plugin" + "github.com/mattermost/mattermost-server/v5/model" + ) + + type MyPlugin struct { + plugin.MattermostPlugin + } + + func (p *MyPlugin) ReactionHasBeenRemoved(c *plugin.Context, reaction *model.Reaction) { + p.API.LogDebug(reaction.EmojiName) + } + + func main() { + plugin.ClientMain(&MyPlugin{}) + } + `}, th.App, func(*model.Manifest) plugin.API { return &mockAPI }) + defer tearDown() + + reaction := &model.Reaction{ + UserId: th.BasicUser.Id, + PostId: th.BasicPost.Id, + EmojiName: "star", + CreateAt: model.GetMillis() - 10000, + } + + err := th.App.DeleteReactionForPost(reaction) + + require.Nil(t, err) +} diff --git a/app/reaction.go b/app/reaction.go index 2b2fde21f9..57c6f72eaa 100644 --- a/app/reaction.go +++ b/app/reaction.go @@ -8,6 +8,7 @@ import ( "net/http" "github.com/mattermost/mattermost-server/v5/model" + "github.com/mattermost/mattermost-server/v5/plugin" ) func (a *App) SaveReactionForPost(reaction *model.Reaction) (*model.Reaction, *model.AppError) { @@ -51,6 +52,16 @@ func (a *App) SaveReactionForPost(reaction *model.Reaction) (*model.Reaction, *m // The post is always modified since the UpdateAt always changes a.invalidateCacheForChannelPosts(post.ChannelId) + if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil { + a.Srv().Go(func() { + pluginContext := a.PluginContext() + pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool { + hooks.ReactionHasBeenAdded(pluginContext, reaction) + return true + }, plugin.ReactionHasBeenAddedId) + }) + } + a.Srv().Go(func() { a.sendReactionEvent(model.WEBSOCKET_EVENT_REACTION_ADDED, reaction, post, true) }) @@ -132,6 +143,16 @@ func (a *App) DeleteReactionForPost(reaction *model.Reaction) *model.AppError { // The post is always modified since the UpdateAt always changes a.invalidateCacheForChannelPosts(post.ChannelId) + if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil { + a.Srv().Go(func() { + pluginContext := a.PluginContext() + pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool { + hooks.ReactionHasBeenRemoved(pluginContext, reaction) + return true + }, plugin.ReactionHasBeenRemovedId) + }) + } + a.Srv().Go(func() { a.sendReactionEvent(model.WEBSOCKET_EVENT_REACTION_REMOVED, reaction, post, hasReactions) }) diff --git a/plugin/client_rpc_generated.go b/plugin/client_rpc_generated.go index b26b74d259..a57d4a3c7b 100644 --- a/plugin/client_rpc_generated.go +++ b/plugin/client_rpc_generated.go @@ -464,6 +464,74 @@ func (s *hooksRPCServer) UserHasLeftTeam(args *Z_UserHasLeftTeamArgs, returns *Z return nil } +func init() { + hookNameToId["ReactionHasBeenAdded"] = ReactionHasBeenAddedId +} + +type Z_ReactionHasBeenAddedArgs struct { + A *Context + B *model.Reaction +} + +type Z_ReactionHasBeenAddedReturns struct { +} + +func (g *hooksRPCClient) ReactionHasBeenAdded(c *Context, reaction *model.Reaction) { + _args := &Z_ReactionHasBeenAddedArgs{c, reaction} + _returns := &Z_ReactionHasBeenAddedReturns{} + if g.implemented[ReactionHasBeenAddedId] { + if err := g.client.Call("Plugin.ReactionHasBeenAdded", _args, _returns); err != nil { + g.log.Error("RPC call ReactionHasBeenAdded to plugin failed.", mlog.Err(err)) + } + } + +} + +func (s *hooksRPCServer) ReactionHasBeenAdded(args *Z_ReactionHasBeenAddedArgs, returns *Z_ReactionHasBeenAddedReturns) error { + if hook, ok := s.impl.(interface { + ReactionHasBeenAdded(c *Context, reaction *model.Reaction) + }); ok { + hook.ReactionHasBeenAdded(args.A, args.B) + } else { + return encodableError(fmt.Errorf("Hook ReactionHasBeenAdded called but not implemented.")) + } + return nil +} + +func init() { + hookNameToId["ReactionHasBeenRemoved"] = ReactionHasBeenRemovedId +} + +type Z_ReactionHasBeenRemovedArgs struct { + A *Context + B *model.Reaction +} + +type Z_ReactionHasBeenRemovedReturns struct { +} + +func (g *hooksRPCClient) ReactionHasBeenRemoved(c *Context, reaction *model.Reaction) { + _args := &Z_ReactionHasBeenRemovedArgs{c, reaction} + _returns := &Z_ReactionHasBeenRemovedReturns{} + if g.implemented[ReactionHasBeenRemovedId] { + if err := g.client.Call("Plugin.ReactionHasBeenRemoved", _args, _returns); err != nil { + g.log.Error("RPC call ReactionHasBeenRemoved to plugin failed.", mlog.Err(err)) + } + } + +} + +func (s *hooksRPCServer) ReactionHasBeenRemoved(args *Z_ReactionHasBeenRemovedArgs, returns *Z_ReactionHasBeenRemovedReturns) error { + if hook, ok := s.impl.(interface { + ReactionHasBeenRemoved(c *Context, reaction *model.Reaction) + }); ok { + hook.ReactionHasBeenRemoved(args.A, args.B) + } else { + return encodableError(fmt.Errorf("Hook ReactionHasBeenRemoved called but not implemented.")) + } + return nil +} + type Z_RegisterCommandArgs struct { A *model.Command } diff --git a/plugin/hooks.go b/plugin/hooks.go index f3830a8079..1e85a66ff2 100644 --- a/plugin/hooks.go +++ b/plugin/hooks.go @@ -15,25 +15,27 @@ import ( // Feel free to add more, but do not change existing assignments. Follow the naming convention of // Id as the autogenerated glue code depends on that. const ( - OnActivateId = 0 - OnDeactivateId = 1 - ServeHTTPId = 2 - OnConfigurationChangeId = 3 - ExecuteCommandId = 4 - MessageWillBePostedId = 5 - MessageWillBeUpdatedId = 6 - MessageHasBeenPostedId = 7 - MessageHasBeenUpdatedId = 8 - UserHasJoinedChannelId = 9 - UserHasLeftChannelId = 10 - UserHasJoinedTeamId = 11 - UserHasLeftTeamId = 12 - ChannelHasBeenCreatedId = 13 - FileWillBeUploadedId = 14 - UserWillLogInId = 15 - UserHasLoggedInId = 16 - UserHasBeenCreatedId = 17 - TotalHooksId = iota + OnActivateId = 0 + OnDeactivateId = 1 + ServeHTTPId = 2 + OnConfigurationChangeId = 3 + ExecuteCommandId = 4 + MessageWillBePostedId = 5 + MessageWillBeUpdatedId = 6 + MessageHasBeenPostedId = 7 + MessageHasBeenUpdatedId = 8 + UserHasJoinedChannelId = 9 + UserHasLeftChannelId = 10 + UserHasJoinedTeamId = 11 + UserHasLeftTeamId = 12 + ChannelHasBeenCreatedId = 13 + FileWillBeUploadedId = 14 + UserWillLogInId = 15 + UserHasLoggedInId = 16 + UserHasBeenCreatedId = 17 + ReactionHasBeenAddedId = 18 + ReactionHasBeenRemovedId = 19 + TotalHooksId = iota ) const ( @@ -191,4 +193,20 @@ type Hooks interface { // // Minimum server version: 5.2 FileWillBeUploaded(c *Context, info *model.FileInfo, file io.Reader, output io.Writer) (*model.FileInfo, string) + + // ReactionHasBeenAdded is invoked after the reaction has been committed to the database. + // + // Note that this method will be called for reactions added by plugins, including the plugin that + // added the reaction. + // + // Minimum server version: 5.30 + ReactionHasBeenAdded(c *Context, reaction *model.Reaction) + + // ReactionHasBeenRemoved is invoked after the removal of the reaction has been committed to the database. + // + // Note that this method will be called for reactions removed by plugins, including the plugin that + // removed the reaction. + // + // Minimum server version: 5.30 + ReactionHasBeenRemoved(c *Context, reaction *model.Reaction) } diff --git a/plugin/hooks_timer_layer_generated.go b/plugin/hooks_timer_layer_generated.go index b2e98719d2..05a0a4bcaf 100644 --- a/plugin/hooks_timer_layer_generated.go +++ b/plugin/hooks_timer_layer_generated.go @@ -150,3 +150,15 @@ func (hooks *hooksTimerLayer) FileWillBeUploaded(c *Context, info *model.FileInf hooks.recordTime(startTime, "FileWillBeUploaded", true) return _returnsA, _returnsB } + +func (hooks *hooksTimerLayer) ReactionHasBeenAdded(c *Context, reaction *model.Reaction) { + startTime := timePkg.Now() + hooks.hooksImpl.ReactionHasBeenAdded(c, reaction) + hooks.recordTime(startTime, "ReactionHasBeenAdded", true) +} + +func (hooks *hooksTimerLayer) ReactionHasBeenRemoved(c *Context, reaction *model.Reaction) { + startTime := timePkg.Now() + hooks.hooksImpl.ReactionHasBeenRemoved(c, reaction) + hooks.recordTime(startTime, "ReactionHasBeenRemoved", true) +} diff --git a/plugin/plugintest/hooks.go b/plugin/plugintest/hooks.go index 51648c5517..56842adf99 100644 --- a/plugin/plugintest/hooks.go +++ b/plugin/plugintest/hooks.go @@ -194,6 +194,16 @@ func (_m *Hooks) OnDeactivate() error { return r0 } +// ReactionHasBeenAdded provides a mock function with given fields: c, reaction +func (_m *Hooks) ReactionHasBeenAdded(c *plugin.Context, reaction *model.Reaction) { + _m.Called(c, reaction) +} + +// ReactionHasBeenRemoved provides a mock function with given fields: c, reaction +func (_m *Hooks) ReactionHasBeenRemoved(c *plugin.Context, reaction *model.Reaction) { + _m.Called(c, reaction) +} + // ServeHTTP provides a mock function with given fields: c, w, r func (_m *Hooks) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) { _m.Called(c, w, r)