MM-56082 Add PreferencesHaveChanged plugin hook (#25659)

* 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
This commit is contained in:
Harrison Healey 2024-01-03 12:25:53 -05:00 committed by GitHub
parent 18f0d8d88f
commit 502cd6ef7d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 226 additions and 64 deletions

View file

@ -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
@ -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
@ -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
@ -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
}
@ -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
}

View file

@ -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
}

View file

@ -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) {
@ -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) {

View file

@ -555,7 +555,7 @@ type AppIface interface {
DeleteOutgoingWebhook(hookID string) *model.AppError
DeletePluginKey(pluginID string, key string) *model.AppError
DeletePost(c request.CTX, postID, deleteByID string) (*model.Post, *model.AppError)
DeletePreferences(userID string, preferences model.Preferences) *model.AppError
DeletePreferences(c request.CTX, userID string, preferences model.Preferences) *model.AppError
DeleteReactionForPost(c request.CTX, reaction *model.Reaction) *model.AppError
DeleteRemoteCluster(remoteClusterId string) (bool, *model.AppError)
DeleteRetentionPolicy(policyID string) *model.AppError
@ -750,9 +750,9 @@ type AppIface interface {
GetPostsForChannelAroundLastUnread(c request.CTX, channelID, userID string, limitBefore, limitAfter int, skipFetchThreads bool, collapsedThreads, collapsedThreadsExtended bool) (*model.PostList, *model.AppError)
GetPostsPage(options model.GetPostsOptions) (*model.PostList, *model.AppError)
GetPostsSince(options model.GetPostsSinceOptions) (*model.PostList, *model.AppError)
GetPreferenceByCategoryAndNameForUser(userID string, category string, preferenceName string) (*model.Preference, *model.AppError)
GetPreferenceByCategoryForUser(userID string, category string) (model.Preferences, *model.AppError)
GetPreferencesForUser(userID string) (model.Preferences, *model.AppError)
GetPreferenceByCategoryAndNameForUser(c request.CTX, userID string, category string, preferenceName string) (*model.Preference, *model.AppError)
GetPreferenceByCategoryForUser(c request.CTX, userID string, category string) (model.Preferences, *model.AppError)
GetPreferencesForUser(c request.CTX, userID string) (model.Preferences, *model.AppError)
GetPrevPostIdFromPostList(postList *model.PostList, collapsedThreads bool) string
GetPriorityForPost(postId string) (*model.PostPriority, *model.AppError)
GetPriorityForPostList(list *model.PostList) (map[string]*model.PostPriority, *model.AppError)
@ -994,7 +994,7 @@ type AppIface interface {
RemoveLdapPrivateCertificate() *model.AppError
RemoveLdapPublicCertificate() *model.AppError
RemoveNotifications(c request.CTX, post *model.Post, channel *model.Channel) error
RemoveRecentCustomStatus(userID string, status *model.CustomStatus) *model.AppError
RemoveRecentCustomStatus(c request.CTX, userID string, status *model.CustomStatus) *model.AppError
RemoveSamlIdpCertificate() *model.AppError
RemoveSamlPrivateCertificate() *model.AppError
RemoveSamlPublicCertificate() *model.AppError
@ -1157,7 +1157,7 @@ type AppIface interface {
UpdatePasswordByUserIdSendEmail(c request.CTX, userID, newPassword, method string) *model.AppError
UpdatePasswordSendEmail(c request.CTX, user *model.User, newPassword, method string) *model.AppError
UpdatePost(c request.CTX, receivedUpdatedPost *model.Post, safeUpdate bool) (*model.Post, *model.AppError)
UpdatePreferences(userID string, preferences model.Preferences) *model.AppError
UpdatePreferences(c request.CTX, userID string, preferences model.Preferences) *model.AppError
UpdateRemoteCluster(rc *model.RemoteCluster) (*model.RemoteCluster, *model.AppError)
UpdateRemoteClusterTopics(remoteClusterId string, topics string) (*model.RemoteCluster, *model.AppError)
UpdateRole(role *model.Role) (*model.Role, *model.AppError)

View file

@ -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
}
@ -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
}
@ -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)
@ -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
}
@ -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
}

View file

@ -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 {

View file

@ -3394,7 +3394,7 @@ func (a *OpenTracingAppLayer) DeletePost(c request.CTX, postID string, deleteByI
return resultVar0, resultVar1
}
func (a *OpenTracingAppLayer) DeletePreferences(userID string, preferences model.Preferences) *model.AppError {
func (a *OpenTracingAppLayer) DeletePreferences(c request.CTX, userID string, preferences model.Preferences) *model.AppError {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.DeletePreferences")
@ -3406,7 +3406,7 @@ func (a *OpenTracingAppLayer) DeletePreferences(userID string, preferences model
}()
defer span.Finish()
resultVar0 := a.app.DeletePreferences(userID, preferences)
resultVar0 := a.app.DeletePreferences(c, userID, preferences)
if resultVar0 != nil {
span.LogFields(spanlog.Error(resultVar0))
@ -8395,7 +8395,7 @@ func (a *OpenTracingAppLayer) GetPostsUsage() (int64, *model.AppError) {
return resultVar0, resultVar1
}
func (a *OpenTracingAppLayer) GetPreferenceByCategoryAndNameForUser(userID string, category string, preferenceName string) (*model.Preference, *model.AppError) {
func (a *OpenTracingAppLayer) GetPreferenceByCategoryAndNameForUser(c request.CTX, userID string, category string, preferenceName string) (*model.Preference, *model.AppError) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetPreferenceByCategoryAndNameForUser")
@ -8407,7 +8407,7 @@ func (a *OpenTracingAppLayer) GetPreferenceByCategoryAndNameForUser(userID strin
}()
defer span.Finish()
resultVar0, resultVar1 := a.app.GetPreferenceByCategoryAndNameForUser(userID, category, preferenceName)
resultVar0, resultVar1 := a.app.GetPreferenceByCategoryAndNameForUser(c, userID, category, preferenceName)
if resultVar1 != nil {
span.LogFields(spanlog.Error(resultVar1))
@ -8417,7 +8417,7 @@ func (a *OpenTracingAppLayer) GetPreferenceByCategoryAndNameForUser(userID strin
return resultVar0, resultVar1
}
func (a *OpenTracingAppLayer) GetPreferenceByCategoryForUser(userID string, category string) (model.Preferences, *model.AppError) {
func (a *OpenTracingAppLayer) GetPreferenceByCategoryForUser(c request.CTX, userID string, category string) (model.Preferences, *model.AppError) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetPreferenceByCategoryForUser")
@ -8429,7 +8429,7 @@ func (a *OpenTracingAppLayer) GetPreferenceByCategoryForUser(userID string, cate
}()
defer span.Finish()
resultVar0, resultVar1 := a.app.GetPreferenceByCategoryForUser(userID, category)
resultVar0, resultVar1 := a.app.GetPreferenceByCategoryForUser(c, userID, category)
if resultVar1 != nil {
span.LogFields(spanlog.Error(resultVar1))
@ -8439,7 +8439,7 @@ func (a *OpenTracingAppLayer) GetPreferenceByCategoryForUser(userID string, cate
return resultVar0, resultVar1
}
func (a *OpenTracingAppLayer) GetPreferencesForUser(userID string) (model.Preferences, *model.AppError) {
func (a *OpenTracingAppLayer) GetPreferencesForUser(c request.CTX, userID string) (model.Preferences, *model.AppError) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetPreferencesForUser")
@ -8451,7 +8451,7 @@ func (a *OpenTracingAppLayer) GetPreferencesForUser(userID string) (model.Prefer
}()
defer span.Finish()
resultVar0, resultVar1 := a.app.GetPreferencesForUser(userID)
resultVar0, resultVar1 := a.app.GetPreferencesForUser(c, userID)
if resultVar1 != nil {
span.LogFields(spanlog.Error(resultVar1))
@ -14031,7 +14031,7 @@ func (a *OpenTracingAppLayer) RemoveNotifications(c request.CTX, post *model.Pos
return resultVar0
}
func (a *OpenTracingAppLayer) RemoveRecentCustomStatus(userID string, status *model.CustomStatus) *model.AppError {
func (a *OpenTracingAppLayer) RemoveRecentCustomStatus(c request.CTX, userID string, status *model.CustomStatus) *model.AppError {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.RemoveRecentCustomStatus")
@ -14043,7 +14043,7 @@ func (a *OpenTracingAppLayer) RemoveRecentCustomStatus(userID string, status *mo
}()
defer span.Finish()
resultVar0 := a.app.RemoveRecentCustomStatus(userID, status)
resultVar0 := a.app.RemoveRecentCustomStatus(c, userID, status)
if resultVar0 != nil {
span.LogFields(spanlog.Error(resultVar0))
@ -17878,7 +17878,7 @@ func (a *OpenTracingAppLayer) UpdatePost(c request.CTX, receivedUpdatedPost *mod
return resultVar0, resultVar1
}
func (a *OpenTracingAppLayer) UpdatePreferences(userID string, preferences model.Preferences) *model.AppError {
func (a *OpenTracingAppLayer) UpdatePreferences(c request.CTX, userID string, preferences model.Preferences) *model.AppError {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UpdatePreferences")
@ -17890,7 +17890,7 @@ func (a *OpenTracingAppLayer) UpdatePreferences(userID string, preferences model
}()
defer span.Finish()
resultVar0 := a.app.UpdatePreferences(userID, preferences)
resultVar0 := a.app.UpdatePreferences(c, userID, preferences)
if resultVar0 != nil {
span.LogFields(spanlog.Error(resultVar0))

View file

@ -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) {

View file

@ -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) {
@ -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")
})
}

View file

@ -9,6 +9,8 @@ import (
"net/http"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/plugin"
"github.com/mattermost/mattermost/server/public/shared/request"
"github.com/mattermost/mattermost/server/v8/channels/product"
)
@ -20,19 +22,19 @@ type preferencesServiceWrapper struct {
app AppIface
}
func (w *preferencesServiceWrapper) GetPreferencesForUser(userID string) (model.Preferences, *model.AppError) {
return w.app.GetPreferencesForUser(userID)
func (w *preferencesServiceWrapper) GetPreferencesForUser(c request.CTX, userID string) (model.Preferences, *model.AppError) {
return w.app.GetPreferencesForUser(c, userID)
}
func (w *preferencesServiceWrapper) UpdatePreferencesForUser(userID string, preferences model.Preferences) *model.AppError {
return w.app.UpdatePreferences(userID, preferences)
func (w *preferencesServiceWrapper) UpdatePreferencesForUser(c request.CTX, userID string, preferences model.Preferences) *model.AppError {
return w.app.UpdatePreferences(c, userID, preferences)
}
func (w *preferencesServiceWrapper) DeletePreferencesForUser(userID string, preferences model.Preferences) *model.AppError {
return w.app.DeletePreferences(userID, preferences)
func (w *preferencesServiceWrapper) DeletePreferencesForUser(c request.CTX, userID string, preferences model.Preferences) *model.AppError {
return w.app.DeletePreferences(c, userID, preferences)
}
func (a *App) GetPreferencesForUser(userID string) (model.Preferences, *model.AppError) {
func (a *App) GetPreferencesForUser(c request.CTX, userID string) (model.Preferences, *model.AppError) {
preferences, err := a.Srv().Store().Preference().GetAll(userID)
if err != nil {
return nil, model.NewAppError("GetPreferencesForUser", "app.preference.get_all.app_error", nil, "", http.StatusBadRequest).Wrap(err)
@ -40,7 +42,7 @@ func (a *App) GetPreferencesForUser(userID string) (model.Preferences, *model.Ap
return preferences, nil
}
func (a *App) GetPreferenceByCategoryForUser(userID string, category string) (model.Preferences, *model.AppError) {
func (a *App) GetPreferenceByCategoryForUser(c request.CTX, userID string, category string) (model.Preferences, *model.AppError) {
preferences, err := a.Srv().Store().Preference().GetCategory(userID, category)
if err != nil {
return nil, model.NewAppError("GetPreferenceByCategoryForUser", "app.preference.get_category.app_error", nil, "", http.StatusBadRequest).Wrap(err)
@ -52,7 +54,7 @@ func (a *App) GetPreferenceByCategoryForUser(userID string, category string) (mo
return preferences, nil
}
func (a *App) GetPreferenceByCategoryAndNameForUser(userID string, category string, preferenceName string) (*model.Preference, *model.AppError) {
func (a *App) GetPreferenceByCategoryAndNameForUser(c request.CTX, userID string, category string, preferenceName string) (*model.Preference, *model.AppError) {
res, err := a.Srv().Store().Preference().Get(userID, category, preferenceName)
if err != nil {
return nil, model.NewAppError("GetPreferenceByCategoryAndNameForUser", "app.preference.get.app_error", nil, "", http.StatusBadRequest).Wrap(err)
@ -60,7 +62,7 @@ func (a *App) GetPreferenceByCategoryAndNameForUser(userID string, category stri
return res, nil
}
func (a *App) UpdatePreferences(userID string, preferences model.Preferences) *model.AppError {
func (a *App) UpdatePreferences(c request.CTX, userID string, preferences model.Preferences) *model.AppError {
for _, preference := range preferences {
if userID != preference.UserId {
return model.NewAppError("savePreferences", "api.preference.update_preferences.set.app_error", nil,
@ -94,10 +96,18 @@ func (a *App) UpdatePreferences(userID string, preferences model.Preferences) *m
message.Add("preferences", string(prefsJSON))
a.Publish(message)
pluginContext := pluginContext(c)
a.Srv().Go(func() {
a.ch.RunMultiHook(func(hooks plugin.Hooks) bool {
hooks.PreferencesHaveChanged(pluginContext, preferences)
return true
}, plugin.PreferencesHaveChangedID)
})
return nil
}
func (a *App) DeletePreferences(userID string, preferences model.Preferences) *model.AppError {
func (a *App) DeletePreferences(c request.CTX, userID string, preferences model.Preferences) *model.AppError {
for _, preference := range preferences {
if userID != preference.UserId {
err := model.NewAppError("DeletePreferences", "api.preference.delete_preferences.delete.app_error", nil,

View file

@ -95,7 +95,7 @@ func (a *App) SetCustomStatus(c request.CTX, userID string, cs *model.CustomStat
return updateErr
}
if err := a.addRecentCustomStatus(userID, cs); err != nil {
if err := a.addRecentCustomStatus(c, userID, cs); err != nil {
c.Logger().Error("Can't add recent custom status for", mlog.String("userID", userID), mlog.Err(err))
}
@ -126,10 +126,10 @@ func (a *App) GetCustomStatus(userID string) (*model.CustomStatus, *model.AppErr
return user.GetCustomStatus(), nil
}
func (a *App) addRecentCustomStatus(userID string, status *model.CustomStatus) *model.AppError {
func (a *App) addRecentCustomStatus(c request.CTX, userID string, status *model.CustomStatus) *model.AppError {
var newRCS model.RecentCustomStatuses
pref, appErr := a.GetPreferenceByCategoryAndNameForUser(userID, model.PreferenceCategoryCustomStatus, model.PreferenceNameRecentCustomStatuses)
pref, appErr := a.GetPreferenceByCategoryAndNameForUser(c, userID, model.PreferenceCategoryCustomStatus, model.PreferenceNameRecentCustomStatuses)
if appErr != nil || pref.Value == "" {
newRCS = model.RecentCustomStatuses{*status}
} else {
@ -150,15 +150,15 @@ func (a *App) addRecentCustomStatus(userID string, status *model.CustomStatus) *
Name: model.PreferenceNameRecentCustomStatuses,
Value: string(newRCSJSON),
}
if appErr := a.UpdatePreferences(userID, model.Preferences{*pref}); appErr != nil {
if appErr := a.UpdatePreferences(c, userID, model.Preferences{*pref}); appErr != nil {
return appErr
}
return nil
}
func (a *App) RemoveRecentCustomStatus(userID string, status *model.CustomStatus) *model.AppError {
pref, appErr := a.GetPreferenceByCategoryAndNameForUser(userID, model.PreferenceCategoryCustomStatus, model.PreferenceNameRecentCustomStatuses)
func (a *App) RemoveRecentCustomStatus(c request.CTX, userID string, status *model.CustomStatus) *model.AppError {
pref, appErr := a.GetPreferenceByCategoryAndNameForUser(c, userID, model.PreferenceCategoryCustomStatus, model.PreferenceNameRecentCustomStatuses)
if appErr != nil {
return appErr
}
@ -186,7 +186,7 @@ func (a *App) RemoveRecentCustomStatus(userID string, status *model.CustomStatus
return model.NewAppError("RemoveRecentCustomStatus", "api.marshal_error", nil, "", http.StatusBadRequest).Wrap(err)
}
pref.Value = string(newRCSJSON)
if appErr := a.UpdatePreferences(userID, model.Preferences{*pref}); appErr != nil {
if appErr := a.UpdatePreferences(c, userID, model.Preferences{*pref}); appErr != nil {
return appErr
}

View file

@ -1813,17 +1813,17 @@ func TestCreateUserWithInitialPreferences(t *testing.T) {
testUser := th.CreateUser()
defer th.App.PermanentDeleteUser(th.Context, testUser)
tutorialStepPref, appErr := th.App.GetPreferenceByCategoryAndNameForUser(testUser.Id, model.PreferenceCategoryTutorialSteps, testUser.Id)
tutorialStepPref, appErr := th.App.GetPreferenceByCategoryAndNameForUser(th.Context, testUser.Id, model.PreferenceCategoryTutorialSteps, testUser.Id)
require.Nil(t, appErr)
assert.Equal(t, testUser.Id, tutorialStepPref.Name)
recommendedNextStepsPref, appErr := th.App.GetPreferenceByCategoryForUser(testUser.Id, model.PreferenceRecommendedNextSteps)
recommendedNextStepsPref, appErr := th.App.GetPreferenceByCategoryForUser(th.Context, testUser.Id, model.PreferenceRecommendedNextSteps)
require.Nil(t, appErr)
assert.Equal(t, model.PreferenceRecommendedNextSteps, recommendedNextStepsPref[0].Category)
assert.Equal(t, "hide", recommendedNextStepsPref[0].Name)
assert.Equal(t, "false", recommendedNextStepsPref[0].Value)
gmASdmNoticeViewedPref, appErr := th.App.GetPreferenceByCategoryAndNameForUser(testUser.Id, model.PreferenceCategorySystemNotice, "GMasDM")
gmASdmNoticeViewedPref, appErr := th.App.GetPreferenceByCategoryAndNameForUser(th.Context, testUser.Id, model.PreferenceCategorySystemNotice, "GMasDM")
require.Nil(t, appErr)
assert.Equal(t, "GMasDM", gmASdmNoticeViewedPref.Name)
assert.Equal(t, "true", gmASdmNoticeViewedPref.Value)
@ -1835,17 +1835,17 @@ func TestCreateUserWithInitialPreferences(t *testing.T) {
testUser := th.CreateGuest()
defer th.App.PermanentDeleteUser(th.Context, testUser)
tutorialStepPref, appErr := th.App.GetPreferenceByCategoryAndNameForUser(testUser.Id, model.PreferenceCategoryTutorialSteps, testUser.Id)
tutorialStepPref, appErr := th.App.GetPreferenceByCategoryAndNameForUser(th.Context, testUser.Id, model.PreferenceCategoryTutorialSteps, testUser.Id)
require.Nil(t, appErr)
assert.Equal(t, testUser.Id, tutorialStepPref.Name)
recommendedNextStepsPref, appErr := th.App.GetPreferenceByCategoryForUser(testUser.Id, model.PreferenceRecommendedNextSteps)
recommendedNextStepsPref, appErr := th.App.GetPreferenceByCategoryForUser(th.Context, testUser.Id, model.PreferenceRecommendedNextSteps)
require.Nil(t, appErr)
assert.Equal(t, model.PreferenceRecommendedNextSteps, recommendedNextStepsPref[0].Category)
assert.Equal(t, "hide", recommendedNextStepsPref[0].Name)
assert.Equal(t, "false", recommendedNextStepsPref[0].Value)
gmASdmNoticeViewedPref, appErr := th.App.GetPreferenceByCategoryAndNameForUser(testUser.Id, model.PreferenceCategorySystemNotice, "GMasDM")
gmASdmNoticeViewedPref, appErr := th.App.GetPreferenceByCategoryAndNameForUser(th.Context, testUser.Id, model.PreferenceCategorySystemNotice, "GMasDM")
require.Nil(t, appErr)
assert.Equal(t, "GMasDM", gmASdmNoticeViewedPref.Name)
assert.Equal(t, "true", gmASdmNoticeViewedPref.Value)

View file

@ -207,9 +207,9 @@ type SystemService interface {
//
// The service shall be registered via app.PreferencesKey service key.
type PreferencesService interface {
GetPreferencesForUser(userID string) (model.Preferences, *model.AppError)
UpdatePreferencesForUser(userID string, preferences model.Preferences) *model.AppError
DeletePreferencesForUser(userID string, preferences model.Preferences) *model.AppError
GetPreferencesForUser(c request.CTX, userID string) (model.Preferences, *model.AppError)
UpdatePreferencesForUser(c request.CTX, userID string, preferences model.Preferences) *model.AppError
DeletePreferencesForUser(c request.CTX, userID string, preferences model.Preferences) *model.AppError
}
// SessionService is the API for accessing the session.

View file

@ -159,7 +159,7 @@ func authorizeOAuthPage(c *Context, w http.ResponseWriter, r *http.Request) {
isAuthorized := false
if _, err := c.App.GetPreferenceByCategoryAndNameForUser(c.AppContext.Session().UserId, model.PreferenceCategoryAuthorizedOAuthApp, authRequest.ClientId); err == nil {
if _, err := c.App.GetPreferenceByCategoryAndNameForUser(c.AppContext, c.AppContext.Session().UserId, model.PreferenceCategoryAuthorizedOAuthApp, authRequest.ClientId); err == nil {
// when we support scopes we should check if the scopes match
isAuthorized = true
}

View file

@ -1018,6 +1018,40 @@ func (s *hooksRPCServer) OnSharedChannelsPing(args *Z_OnSharedChannelsPingArgs,
return nil
}
func init() {
hookNameToId["PreferencesHaveChanged"] = PreferencesHaveChangedID
}
type Z_PreferencesHaveChangedArgs struct {
A *Context
B []model.Preference
}
type Z_PreferencesHaveChangedReturns struct {
}
func (g *hooksRPCClient) PreferencesHaveChanged(c *Context, preferences []model.Preference) {
_args := &Z_PreferencesHaveChangedArgs{c, preferences}
_returns := &Z_PreferencesHaveChangedReturns{}
if g.implemented[PreferencesHaveChangedID] {
if err := g.client.Call("Plugin.PreferencesHaveChanged", _args, _returns); err != nil {
g.log.Error("RPC call PreferencesHaveChanged to plugin failed.", mlog.Err(err))
}
}
}
func (s *hooksRPCServer) PreferencesHaveChanged(args *Z_PreferencesHaveChangedArgs, returns *Z_PreferencesHaveChangedReturns) error {
if hook, ok := s.impl.(interface {
PreferencesHaveChanged(c *Context, preferences []model.Preference)
}); ok {
hook.PreferencesHaveChanged(args.A, args.B)
} else {
return encodableError(fmt.Errorf("Hook PreferencesHaveChanged called but not implemented."))
}
return nil
}
type Z_RegisterCommandArgs struct {
A *model.Command
}

View file

@ -57,6 +57,7 @@ const (
ServeMetricsID = 39
OnSharedChannelsSyncMsgID = 40
OnSharedChannelsPingID = 41
PreferencesHaveChangedID = 42
TotalHooksID = iota
)
@ -354,4 +355,11 @@ type Hooks interface {
//
// Minimum server version: 9.5
OnSharedChannelsPing(rc *model.RemoteCluster) bool
// PreferencesHaveChanged is invoked after one or more of a user's preferences have changed.
// Note that this method will be called for preferences changed by plugins, including the plugin that changed
// the preferences.
//
// Minimum server version: 9.5
PreferencesHaveChanged(c *Context, preferences []model.Preference)
}

View file

@ -264,3 +264,9 @@ func (hooks *hooksTimerLayer) OnSharedChannelsPing(rc *model.RemoteCluster) bool
hooks.recordTime(startTime, "OnSharedChannelsPing", true)
return _returnsA
}
func (hooks *hooksTimerLayer) PreferencesHaveChanged(c *Context, preferences []model.Preference) {
startTime := timePkg.Now()
hooks.hooksImpl.PreferencesHaveChanged(c, preferences)
hooks.recordTime(startTime, "PreferencesHaveChanged", true)
}

View file

@ -359,6 +359,11 @@ func (_m *Hooks) OnWebSocketDisconnect(webConnID string, userID string) {
_m.Called(webConnID, userID)
}
// PreferencesHaveChanged provides a mock function with given fields: c, preferences
func (_m *Hooks) PreferencesHaveChanged(c *plugin.Context, preferences []model.Preference) {
_m.Called(c, preferences)
}
// ReactionHasBeenAdded provides a mock function with given fields: c, reaction
func (_m *Hooks) ReactionHasBeenAdded(c *plugin.Context, reaction *model.Reaction) {
_m.Called(c, reaction)

View file

@ -147,6 +147,10 @@ type OnSharedChannelsPingIFace interface {
OnSharedChannelsPing(rc *model.RemoteCluster) bool
}
type PreferencesHaveChangedIFace interface {
PreferencesHaveChanged(c *Context, preferences []model.Preference)
}
type HooksAdapter struct {
implemented map[int]struct{}
productHooks any
@ -457,6 +461,15 @@ func NewAdapter(productHooks any) (*HooksAdapter, error) {
return nil, errors.New("hook has OnSharedChannelsPing method but does not implement plugin.OnSharedChannelsPing interface")
}
// Assessing the type of the productHooks if it individually implements PreferencesHaveChanged interface.
tt = reflect.TypeOf((*PreferencesHaveChangedIFace)(nil)).Elem()
if ft.Implements(tt) {
a.implemented[PreferencesHaveChangedID] = struct{}{}
} else if _, ok := ft.MethodByName("PreferencesHaveChanged"); ok {
return nil, errors.New("hook has PreferencesHaveChanged method but does not implement plugin.PreferencesHaveChanged interface")
}
return a, nil
}
@ -756,3 +769,12 @@ func (a *HooksAdapter) OnSharedChannelsPing(rc *model.RemoteCluster) bool {
return a.productHooks.(OnSharedChannelsPingIFace).OnSharedChannelsPing(rc)
}
func (a *HooksAdapter) PreferencesHaveChanged(c *Context, preferences []model.Preference) {
if _, ok := a.implemented[PreferencesHaveChangedID]; !ok {
panic("product hooks must implement PreferencesHaveChanged")
}
a.productHooks.(PreferencesHaveChangedIFace).PreferencesHaveChanged(c, preferences)
}