Skip to content

Commit

Permalink
MM-56082 Add PreferencesHaveChanged plugin hook (mattermost#25659)
Browse files Browse the repository at this point in the history
* Add interface for PreferencesHaveChanged hook

* Add context to preference-related methods of App

* Implement PreferencesHaveChanged

* Re-add missing "fmt" import

* Update minimum server version for the new hook

* Remove pointers to be consistent with other preference APIs
  • Loading branch information
hmhealey authored Jan 3, 2024
1 parent 18f0d8d commit 502cd6e
Show file tree
Hide file tree
Showing 19 changed files with 226 additions and 64 deletions.
10 changes: 5 additions & 5 deletions server/channels/api4/preference.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func getPreferences(c *Context, w http.ResponseWriter, r *http.Request) {
return
}

preferences, err := c.App.GetPreferencesForUser(c.Params.UserId)
preferences, err := c.App.GetPreferencesForUser(c.AppContext, c.Params.UserId)
if err != nil {
c.Err = err
return
Expand All @@ -53,7 +53,7 @@ func getPreferencesByCategory(c *Context, w http.ResponseWriter, r *http.Request
return
}

preferences, err := c.App.GetPreferenceByCategoryForUser(c.Params.UserId, c.Params.Category)
preferences, err := c.App.GetPreferenceByCategoryForUser(c.AppContext, c.Params.UserId, c.Params.Category)
if err != nil {
c.Err = err
return
Expand All @@ -75,7 +75,7 @@ func getPreferenceByCategoryAndName(c *Context, w http.ResponseWriter, r *http.R
return
}

preferences, err := c.App.GetPreferenceByCategoryAndNameForUser(c.Params.UserId, c.Params.Category, c.Params.PreferenceName)
preferences, err := c.App.GetPreferenceByCategoryAndNameForUser(c.AppContext, c.Params.UserId, c.Params.Category, c.Params.PreferenceName)
if err != nil {
c.Err = err
return
Expand Down Expand Up @@ -125,7 +125,7 @@ func updatePreferences(c *Context, w http.ResponseWriter, r *http.Request) {
sanitizedPreferences = append(sanitizedPreferences, pref)
}

if err := c.App.UpdatePreferences(c.Params.UserId, sanitizedPreferences); err != nil {
if err := c.App.UpdatePreferences(c.AppContext, c.Params.UserId, sanitizedPreferences); err != nil {
c.Err = err
return
}
Expand Down Expand Up @@ -154,7 +154,7 @@ func deletePreferences(c *Context, w http.ResponseWriter, r *http.Request) {
return
}

if err := c.App.DeletePreferences(c.Params.UserId, preferences); err != nil {
if err := c.App.DeletePreferences(c.AppContext, c.Params.UserId, preferences); err != nil {
c.Err = err
return
}
Expand Down
2 changes: 1 addition & 1 deletion server/channels/api4/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ func removeUserRecentCustomStatus(c *Context, w http.ResponseWriter, r *http.Req
return
}

if err := c.App.RemoveRecentCustomStatus(c.Params.UserId, &recentCustomStatus); err != nil {
if err := c.App.RemoveRecentCustomStatus(c.AppContext, c.Params.UserId, &recentCustomStatus); err != nil {
c.Err = err
return
}
Expand Down
4 changes: 2 additions & 2 deletions server/channels/api4/user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3089,7 +3089,7 @@ func TestGetUsersInGroupByDisplayName(t *testing.T) {
Value: model.ShowUsername,
}

err = th.App.UpdatePreferences(th.SystemAdminUser.Id, model.Preferences{preference})
err = th.App.UpdatePreferences(th.Context, th.SystemAdminUser.Id, model.Preferences{preference})
assert.Nil(t, err)

t.Run("Returns users in group in right order for username", func(t *testing.T) {
Expand All @@ -3099,7 +3099,7 @@ func TestGetUsersInGroupByDisplayName(t *testing.T) {
})

preference.Value = model.ShowNicknameFullName
err = th.App.UpdatePreferences(th.SystemAdminUser.Id, model.Preferences{preference})
err = th.App.UpdatePreferences(th.Context, th.SystemAdminUser.Id, model.Preferences{preference})
assert.Nil(t, err)

t.Run("Returns users in group in right order for nickname", func(t *testing.T) {
Expand Down
12 changes: 6 additions & 6 deletions server/channels/app/app_iface.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions server/channels/app/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ func (a *App) exportAllUsers(ctx request.CTX, job *model.Job, writer io.Writer,

// Gathering here the exportable preferences to pass them on to ImportLineFromUser
exportedPrefs := make(map[string]*string)
allPrefs, err := a.GetPreferencesForUser(user.Id)
allPrefs, err := a.GetPreferencesForUser(ctx, user.Id)
if err != nil {
return err
}
Expand Down Expand Up @@ -319,7 +319,7 @@ func (a *App) exportAllUsers(ctx request.CTX, job *model.Job, writer io.Writer,
userLine.User.NotifyProps = a.buildUserNotifyProps(user.NotifyProps)

// Do the Team Memberships.
members, err := a.buildUserTeamAndChannelMemberships(user.Id, includeArchivedChannels)
members, err := a.buildUserTeamAndChannelMemberships(ctx, user.Id, includeArchivedChannels)
if err != nil {
return err
}
Expand All @@ -335,7 +335,7 @@ func (a *App) exportAllUsers(ctx request.CTX, job *model.Job, writer io.Writer,
return nil
}

func (a *App) buildUserTeamAndChannelMemberships(userID string, includeArchivedChannels bool) (*[]imports.UserTeamImportData, *model.AppError) {
func (a *App) buildUserTeamAndChannelMemberships(c request.CTX, userID string, includeArchivedChannels bool) (*[]imports.UserTeamImportData, *model.AppError) {
var memberships []imports.UserTeamImportData

members, err := a.Srv().Store().Team().GetTeamMembersForExport(userID)
Expand All @@ -353,7 +353,7 @@ func (a *App) buildUserTeamAndChannelMemberships(userID string, includeArchivedC
memberData := ImportUserTeamDataFromTeamMember(member)

// Do the Channel Memberships.
channelMembers, err := a.buildUserChannelMemberships(userID, member.TeamId, includeArchivedChannels)
channelMembers, err := a.buildUserChannelMemberships(c, userID, member.TeamId, includeArchivedChannels)
if err != nil {
return nil, err
}
Expand All @@ -372,14 +372,14 @@ func (a *App) buildUserTeamAndChannelMemberships(userID string, includeArchivedC
return &memberships, nil
}

func (a *App) buildUserChannelMemberships(userID string, teamID string, includeArchivedChannels bool) (*[]imports.UserChannelImportData, *model.AppError) {
func (a *App) buildUserChannelMemberships(c request.CTX, userID string, teamID string, includeArchivedChannels bool) (*[]imports.UserChannelImportData, *model.AppError) {
members, nErr := a.Srv().Store().Channel().GetChannelMembersForExport(userID, teamID, includeArchivedChannels)
if nErr != nil {
return nil, model.NewAppError("buildUserChannelMemberships", "app.channel.get_members.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
}

category := model.PreferenceCategoryFavoriteChannel
preferences, err := a.GetPreferenceByCategoryForUser(userID, category)
preferences, err := a.GetPreferenceByCategoryForUser(c, userID, category)
if err != nil && err.StatusCode != http.StatusNotFound {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion server/channels/app/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func TestExportUserChannels(t *testing.T) {
require.NoError(t, err)

th.App.UpdateChannelMemberNotifyProps(th.Context, notifyProps, channel.Id, user.Id)
exportData, appErr := th.App.buildUserChannelMemberships(user.Id, team.Id, false)
exportData, appErr := th.App.buildUserChannelMemberships(th.Context, user.Id, team.Id, false)
require.Nil(t, appErr)
assert.Equal(t, len(*exportData), 3)
for _, data := range *exportData {
Expand Down
24 changes: 12 additions & 12 deletions server/channels/app/opentracing/opentracing_layer.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions server/channels/app/plugin_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,15 +271,15 @@ func (api *PluginAPI) GetUsersInTeam(teamID string, page int, perPage int) ([]*m
}

func (api *PluginAPI) GetPreferencesForUser(userID string) ([]model.Preference, *model.AppError) {
return api.app.GetPreferencesForUser(userID)
return api.app.GetPreferencesForUser(api.ctx, userID)
}

func (api *PluginAPI) UpdatePreferencesForUser(userID string, preferences []model.Preference) *model.AppError {
return api.app.UpdatePreferences(userID, preferences)
return api.app.UpdatePreferences(api.ctx, userID, preferences)
}

func (api *PluginAPI) DeletePreferencesForUser(userID string, preferences []model.Preference) *model.AppError {
return api.app.DeletePreferences(userID, preferences)
return api.app.DeletePreferences(api.ctx, userID, preferences)
}

func (api *PluginAPI) GetSession(sessionID string) (*model.Session, *model.AppError) {
Expand Down
77 changes: 77 additions & 0 deletions server/channels/app/plugin_hooks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1294,6 +1294,10 @@ func TestHookReactionHasBeenRemoved(t *testing.T) {
err := th.App.DeleteReactionForPost(th.Context, reaction)

require.Nil(t, err)

time.Sleep(1 * time.Second)

mockAPI.AssertCalled(t, "LogDebug", "star")
}

func TestHookRunDataRetention(t *testing.T) {
Expand Down Expand Up @@ -1632,3 +1636,76 @@ func TestHookMessagesWillBeConsumed(t *testing.T) {
assert.Equal(t, "mwbc_plugin:message", post.Message)
})
}

func TestHookPreferencesHaveChanged(t *testing.T) {
t.Run("should be called when preferences are changed by non-plugin code", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()

// Setup plugin
var mockAPI plugintest.API

tearDown, pluginIDs, _ := SetAppEnvironmentWithPlugins(t, []string{`
package main
import (
"fmt"
"github.com/mattermost/mattermost/server/public/plugin"
"github.com/mattermost/mattermost/server/public/model"
)
type MyPlugin struct {
plugin.MattermostPlugin
}
func (p *MyPlugin) PreferencesHaveChanged(c *plugin.Context, preferences []model.Preference) {
for _, preference := range preferences {
p.API.LogDebug(fmt.Sprintf("category=%s name=%s value=%s", preference.Category, preference.Name, preference.Value))
}
}
func main() {
plugin.ClientMain(&MyPlugin{})
}
`}, th.App, func(*model.Manifest) plugin.API { return &mockAPI })
defer tearDown()

// Confirm plugin is actually running
require.Len(t, pluginIDs, 1)
pluginID := pluginIDs[0]

require.True(t, th.App.GetPluginsEnvironment().IsActive(pluginID))

// Setup test
preferences := model.Preferences{
{
UserId: th.BasicUser.Id,
Category: "test_category",
Name: "test_name_1",
Value: "test_value_1",
},
{
UserId: th.BasicUser.Id,
Category: "test_category",
Name: "test_name_2",
Value: "test_value_2",
},
}

mockAPI.On("LogDebug", "category=test_category name=test_name_1 value=test_value_1")
mockAPI.On("LogDebug", "category=test_category name=test_name_2 value=test_value_2")
defer mockAPI.AssertExpectations(t)

// Run test
err := th.App.UpdatePreferences(th.Context, th.BasicUser.Id, preferences)

require.Nil(t, err)

// Hooks are run in a goroutine, so wait for those to complete
time.Sleep(1 * time.Second)

mockAPI.AssertCalled(t, "LogDebug", "category=test_category name=test_name_1 value=test_value_1")
mockAPI.AssertCalled(t, "LogDebug", "category=test_category name=test_name_2 value=test_value_2")
})
}
Loading

0 comments on commit 502cd6e

Please sign in to comment.