MM-46410: adds urgency on mention counts (#20999)

* MM-46410: adds urgency on mention counts

We have introduced priority for posts in
https://github.com/mattermost/mattermost-webapp/pull/10951.
We do need to color the mention badges in the webapp with a prominent
color when a mention is posted in an urgent message.
A thread has urgent mentions if the root post is marked as urgent, and
the replies contain mentions to the user viewing the thread.

This PR adds a column, urgentmentioncount, in channelmembers.
Furthermore when asking for team/thread mention counts, we also return
urgent mention counts for the user.

Adds a new table to hold posts priorities
Refactors priority out of the props and into the new table

We are nilifying Metadata when post.ForPlugin(), which didn't save Priority
for a post when Boards was enabled.
This commit copies metadata again to the post, so metadata are
reinstated.

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Co-authored-by: Vishal Choudhary <vish9812@gmail.com>
Этот коммит содержится в:
Kyriakos Z
2022-11-23 21:08:21 +02:00
коммит произвёл GitHub
родитель ff1ea0599e
Коммит c44d37629a
47 изменённых файлов: 1676 добавлений и 343 удалений

Просмотреть файл

@@ -141,7 +141,7 @@ func createEphemeralPost(c *Context, w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusCreated)
rp = model.AddPostActionCookies(rp, c.App.PostActionCookieSecret())
rp = c.App.PreparePostForClientWithEmbedsAndImages(c.AppContext, rp, true, false)
rp = c.App.PreparePostForClientWithEmbedsAndImages(c.AppContext, rp, true, false, true)
rp, err := c.App.SanitizePostMetadataForUser(c.AppContext, rp, c.AppContext.Session().UserId)
if err != nil {
c.Err = err
@@ -420,7 +420,7 @@ func getPost(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
post = c.App.PreparePostForClientWithEmbedsAndImages(c.AppContext, post, false, false)
post = c.App.PreparePostForClientWithEmbedsAndImages(c.AppContext, post, false, false, true)
post, err = c.App.SanitizePostMetadataForUser(c.AppContext, post, c.AppContext.Session().UserId)
if err != nil {
c.Err = err
@@ -479,7 +479,7 @@ func getPostsByIds(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
post = c.App.PreparePostForClient(c.AppContext, post, false, false)
post = c.App.PreparePostForClient(c.AppContext, post, false, false, true)
post.StripActionIntegrations()
posts = append(posts, post)
}

Просмотреть файл

@@ -66,6 +66,7 @@ func TestGraphQLChannelMembers(t *testing.T) {
MsgCount float64 `json:"msgCount"`
MentionCount float64 `json:"mentionCount"`
MentionCountRoot float64 `json:"mentionCountRoot"`
UrgentMentionCount float64 `json:"urgentMentionCount"`
MsgCountRoot float64 `json:"msgCountRoot"`
NotifyProps model.StringMap `json:"notifyProps"`
SchemeGuest bool `json:"schemeGuest"`
@@ -101,6 +102,7 @@ func TestGraphQLChannelMembers(t *testing.T) {
msgCount
mentionCount
mentionCountRoot
urgentMentionCount
msgCountRoot
schemeGuest
schemeUser
@@ -181,6 +183,7 @@ func TestGraphQLChannelMembers(t *testing.T) {
msgCount
mentionCount
mentionCountRoot
urgentMentionCount
}
}
`,

Просмотреть файл

@@ -75,6 +75,7 @@ type ChannelMember {
lastViewedAt : Float!
msgCount : Float!
mentionCount : Float!
urgentMentionCount: Float!
mentionCountRoot : Float!
msgCountRoot : Float!
notifyProps : StringMap!

Просмотреть файл

@@ -5720,10 +5720,12 @@ func TestUpdatePassword(t *testing.T) {
}
func TestGetThreadsForUser(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
os.Setenv("MM_FEATUREFLAGS_POSTPRIORITY", "true")
defer os.Unsetenv("MM_FEATUREFLAGS_POSTPRIORITY")
os.Setenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS", "true")
defer os.Unsetenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS")
th := Setup(t).InitBasic()
defer th.TearDown()
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.ThreadAutoFollow = true
@@ -5820,7 +5822,49 @@ func TestGetThreadsForUser(t *testing.T) {
require.NoError(t, err)
require.Len(t, uss.Threads, 1)
require.Greater(t, uss.Threads[0].Post.DeleteAt, int64(0))
})
t.Run("isUrgent, 1 thread", func(t *testing.T) {
testCases := []struct {
featureEnabled bool
expected bool
}{
{featureEnabled: true, expected: true},
{featureEnabled: false, expected: false},
}
for _, tc := range testCases {
func() {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.PostPriority = tc.featureEnabled
cfg.FeatureFlags.PostPriority = true
})
client := th.Client
rpost, resp, err := client.CreatePost(&model.Post{
ChannelId: th.BasicChannel.Id,
Message: "testMsg",
Metadata: &model.PostMetadata{
Priority: &model.PostPriority{
Priority: model.NewString(model.PostPriorityUrgent),
},
},
})
require.NoError(t, err)
CheckCreatedStatus(t, resp)
_, resp, err = client.CreatePost(&model.Post{ChannelId: th.BasicChannel.Id, Message: "testReply", RootId: rpost.Id})
require.NoError(t, err)
CheckCreatedStatus(t, resp)
defer th.App.Srv().Store().Post().PermanentDeleteByUser(th.BasicUser.Id)
uss, _, err := th.Client.GetUserThreads(th.BasicUser.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{})
require.NoError(t, err)
require.Len(t, uss.Threads, 1)
require.Equal(t, uss.Threads[0].IsUrgent, tc.expected)
}()
}
})
t.Run("paged, 30 threads", func(t *testing.T) {
@@ -6515,13 +6559,19 @@ func TestThreadCounts(t *testing.T) {
}
func TestSingleThreadGet(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
os.Setenv("MM_FEATUREFLAGS_POSTPRIORITY", "true")
defer os.Unsetenv("MM_FEATUREFLAGS_POSTPRIORITY")
os.Setenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS", "true")
defer os.Unsetenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS")
th := Setup(t).InitBasic()
defer th.TearDown()
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.ThreadAutoFollow = true
*cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOn
*cfg.ServiceSettings.PostPriority = true
cfg.FeatureFlags.PostPriority = true
})
client := th.Client
@@ -6534,7 +6584,15 @@ func TestSingleThreadGet(t *testing.T) {
postAndCheck(t, th.SystemAdminClient, &model.Post{ChannelId: th.BasicChannel.Id, Message: "testReply", RootId: rpost.Id})
// create another thread to check that we are not returning it by mistake
rpost2, _ := postAndCheck(t, client, &model.Post{ChannelId: th.BasicChannel2.Id, Message: "testMsg2"})
rpost2, _ := postAndCheck(t, client, &model.Post{
ChannelId: th.BasicChannel2.Id,
Message: "testMsg2",
Metadata: &model.PostMetadata{
Priority: &model.PostPriority{
Priority: model.NewString(model.PostPriorityUrgent),
},
},
})
postAndCheck(t, th.SystemAdminClient, &model.Post{ChannelId: th.BasicChannel2.Id, Message: "testReply", RootId: rpost2.Id})
// regular user should have two threads with 3 replies total
@@ -6546,9 +6604,22 @@ func TestSingleThreadGet(t *testing.T) {
require.Equal(t, threads.Threads[0].PostId, tr.PostId)
require.Empty(t, tr.Participants[0].Username)
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.PostPriority = false
})
tr, _, err = th.Client.GetUserThread(th.BasicUser.Id, th.BasicTeam.Id, threads.Threads[0].PostId, true)
require.NoError(t, err)
require.NotEmpty(t, tr.Participants[0].Username)
require.Equal(t, false, tr.IsUrgent)
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.PostPriority = true
})
tr, _, err = th.Client.GetUserThread(th.BasicUser.Id, th.BasicTeam.Id, threads.Threads[0].PostId, true)
require.NoError(t, err)
require.Equal(t, true, tr.IsUrgent)
}
func TestMaintainUnreadMentionsInThread(t *testing.T) {

Просмотреть файл

@@ -716,6 +716,8 @@ type AppIface interface {
GetPreferenceByCategoryForUser(userID string, category string) (model.Preferences, *model.AppError)
GetPreferencesForUser(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)
GetPrivateChannelsForTeam(c request.CTX, teamID string, offset int, limit int) (model.ChannelList, *model.AppError)
GetProfileImage(user *model.User) ([]byte, bool, *model.AppError)
GetPublicChannelsByIdsForTeam(c request.CTX, teamID string, channelIDs []string) (model.ChannelList, *model.AppError)
@@ -927,8 +929,8 @@ type AppIface interface {
PostUpdateChannelPurposeMessage(c request.CTX, userID string, channel *model.Channel, oldChannelPurpose string, newChannelPurpose string) *model.AppError
PostWithProxyAddedToImageURLs(post *model.Post) *model.Post
PostWithProxyRemovedFromImageURLs(post *model.Post) *model.Post
PreparePostForClient(c request.CTX, originalPost *model.Post, isNewPost, isEditPost bool) *model.Post
PreparePostForClientWithEmbedsAndImages(c request.CTX, originalPost *model.Post, isNewPost, isEditPost bool) *model.Post
PreparePostForClient(c request.CTX, originalPost *model.Post, isNewPost, isEditPost, includePriority bool) *model.Post
PreparePostForClientWithEmbedsAndImages(c request.CTX, originalPost *model.Post, isNewPost, isEditPost, includePriority bool) *model.Post
PreparePostListForClient(c request.CTX, originalList *model.PostList) *model.PostList
ProcessSlackText(text string) string
Publish(message *model.WebSocketEvent)

Просмотреть файл

@@ -2609,12 +2609,12 @@ func (a *App) MarkChannelAsUnreadFromPost(c request.CTX, postID string, userID s
return nil, err
}
unreadMentions, unreadMentionsRoot, err := a.countMentionsFromPost(c, user, post)
unreadMentions, unreadMentionsRoot, urgentMentions, err := a.countMentionsFromPost(c, user, post)
if err != nil {
return nil, err
}
channelUnread, nErr := a.Srv().Store().Channel().UpdateLastViewedAtPost(post, userID, unreadMentions, unreadMentionsRoot, true)
channelUnread, nErr := a.Srv().Store().Channel().UpdateLastViewedAtPost(post, userID, unreadMentions, unreadMentionsRoot, urgentMentions, true)
if nErr != nil {
return channelUnread, model.NewAppError("MarkChannelAsUnreadFromPost", "app.channel.update_last_viewed_at_post.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
}
@@ -2641,7 +2641,7 @@ func (a *App) markChannelAsUnreadFromPostCRTUnsupported(c request.CTX, postID st
threadId = post.Id
}
unreadMentions, unreadMentionsRoot, appErr := a.countMentionsFromPost(c, user, post)
unreadMentions, unreadMentionsRoot, urgentMentions, appErr := a.countMentionsFromPost(c, user, post)
if appErr != nil {
return nil, appErr
}
@@ -2650,7 +2650,7 @@ func (a *App) markChannelAsUnreadFromPostCRTUnsupported(c request.CTX, postID st
// In CRT Supported Client: badge on channel only sums mentions in root posts including and below the post that was marked.
// In CRT Unsupported Client: badge on channel sums mentions in all posts (root & replies) including and below the post that was marked unread.
if post.RootId == "" {
channelUnread, nErr := a.Srv().Store().Channel().UpdateLastViewedAtPost(post, userID, unreadMentions, unreadMentionsRoot, true)
channelUnread, nErr := a.Srv().Store().Channel().UpdateLastViewedAtPost(post, userID, unreadMentions, unreadMentionsRoot, urgentMentions, true)
if nErr != nil {
return channelUnread, model.NewAppError("MarkChannelAsUnreadFromPost", "app.channel.update_last_viewed_at_post.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
}
@@ -2706,7 +2706,7 @@ func (a *App) markChannelAsUnreadFromPostCRTUnsupported(c request.CTX, postID st
if mErr != nil {
return nil, model.NewAppError("MarkChannelAsUnreadFromPost", "app.channel.update_last_viewed_at_post.app_error", nil, "", http.StatusInternalServerError).Wrap(mErr)
}
thread, mErr := a.Srv().Store().Thread().GetThreadForUser(threadMembership, true)
thread, mErr := a.Srv().Store().Thread().GetThreadForUser(threadMembership, true, a.isPostPriorityEnabled())
if mErr != nil {
return nil, model.NewAppError("MarkChannelAsUnreadFromPost", "app.channel.update_last_viewed_at_post.app_error", nil, "", http.StatusInternalServerError).Wrap(mErr)
}
@@ -2724,7 +2724,7 @@ func (a *App) markChannelAsUnreadFromPostCRTUnsupported(c request.CTX, postID st
}
}
channelUnread, nErr := a.Srv().Store().Channel().UpdateLastViewedAtPost(post, userID, unreadMentions, 0, false)
channelUnread, nErr := a.Srv().Store().Channel().UpdateLastViewedAtPost(post, userID, unreadMentions, 0, 0, false)
if nErr != nil {
return channelUnread, model.NewAppError("MarkChannelAsUnreadFromPost", "app.channel.update_last_viewed_at_post.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
}
@@ -2741,6 +2741,7 @@ func (a *App) sendWebSocketPostUnreadEvent(c request.CTX, channelUnread *model.C
}
message.Add("mention_count", channelUnread.MentionCount)
message.Add("mention_count_root", channelUnread.MentionCountRoot)
message.Add("urgent_mention_count", channelUnread.UrgentMentionCount)
message.Add("last_viewed_at", channelUnread.LastViewedAt)
message.Add("post_id", postID)
a.Publish(message)

Просмотреть файл

@@ -305,7 +305,8 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
mentionedUsersList = append(mentionedUsersList, id)
}
nErr := a.Srv().Store().Channel().IncrementMentionCount(post.ChannelId, mentionedUsersList, post.RootId == "")
nErr := a.Srv().Store().Channel().IncrementMentionCount(post.ChannelId, mentionedUsersList, post.RootId == "", post.IsUrgent())
if nErr != nil {
mlog.Warn(
"Failed to update mention count",
@@ -596,7 +597,7 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
}
threadMembership = tm
}
userThread, err := a.Srv().Store().Thread().GetThreadForUser(threadMembership, true)
userThread, err := a.Srv().Store().Thread().GetThreadForUser(threadMembership, true, a.isPostPriorityEnabled())
if err != nil {
return nil, errors.Wrapf(err, "cannot get thread %q for user %q", post.RootId, uid)
}

Просмотреть файл

@@ -8118,6 +8118,50 @@ func (a *OpenTracingAppLayer) GetPrevPostIdFromPostList(postList *model.PostList
return resultVar0
}
func (a *OpenTracingAppLayer) GetPriorityForPost(postId string) (*model.PostPriority, *model.AppError) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetPriorityForPost")
a.ctx = newCtx
a.app.Srv().Store().SetContext(newCtx)
defer func() {
a.app.Srv().Store().SetContext(origCtx)
a.ctx = origCtx
}()
defer span.Finish()
resultVar0, resultVar1 := a.app.GetPriorityForPost(postId)
if resultVar1 != nil {
span.LogFields(spanlog.Error(resultVar1))
ext.Error.Set(span, true)
}
return resultVar0, resultVar1
}
func (a *OpenTracingAppLayer) GetPriorityForPostList(list *model.PostList) (map[string]*model.PostPriority, *model.AppError) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetPriorityForPostList")
a.ctx = newCtx
a.app.Srv().Store().SetContext(newCtx)
defer func() {
a.app.Srv().Store().SetContext(origCtx)
a.ctx = origCtx
}()
defer span.Finish()
resultVar0, resultVar1 := a.app.GetPriorityForPostList(list)
if resultVar1 != nil {
span.LogFields(spanlog.Error(resultVar1))
ext.Error.Set(span, true)
}
return resultVar0, resultVar1
}
func (a *OpenTracingAppLayer) GetPrivateChannelsForTeam(c request.CTX, teamID string, offset int, limit int) (model.ChannelList, *model.AppError) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetPrivateChannelsForTeam")
@@ -13023,7 +13067,7 @@ func (a *OpenTracingAppLayer) PostWithProxyRemovedFromImageURLs(post *model.Post
return resultVar0
}
func (a *OpenTracingAppLayer) PreparePostForClient(c request.CTX, originalPost *model.Post, isNewPost bool, isEditPost bool) *model.Post {
func (a *OpenTracingAppLayer) PreparePostForClient(c request.CTX, originalPost *model.Post, isNewPost bool, isEditPost bool, includePriority bool) *model.Post {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.PreparePostForClient")
@@ -13035,12 +13079,12 @@ func (a *OpenTracingAppLayer) PreparePostForClient(c request.CTX, originalPost *
}()
defer span.Finish()
resultVar0 := a.app.PreparePostForClient(c, originalPost, isNewPost, isEditPost)
resultVar0 := a.app.PreparePostForClient(c, originalPost, isNewPost, isEditPost, includePriority)
return resultVar0
}
func (a *OpenTracingAppLayer) PreparePostForClientWithEmbedsAndImages(c request.CTX, originalPost *model.Post, isNewPost bool, isEditPost bool) *model.Post {
func (a *OpenTracingAppLayer) PreparePostForClientWithEmbedsAndImages(c request.CTX, originalPost *model.Post, isNewPost bool, isEditPost bool, includePriority bool) *model.Post {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.PreparePostForClientWithEmbedsAndImages")
@@ -13052,7 +13096,7 @@ func (a *OpenTracingAppLayer) PreparePostForClientWithEmbedsAndImages(c request.
}()
defer span.Finish()
resultVar0 := a.app.PreparePostForClientWithEmbedsAndImages(c, originalPost, isNewPost, isEditPost)
resultVar0 := a.app.PreparePostForClientWithEmbedsAndImages(c, originalPost, isNewPost, isEditPost, includePriority)
return resultVar0
}

Просмотреть файл

@@ -259,7 +259,15 @@ func (a *App) CreatePost(c request.CTX, post *model.Post, channel *model.Channel
}
}
if !a.isPostPriorityEnabled() && post.GetPriority() != nil {
post.Metadata.Priority = nil
}
if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil {
var metadata *model.PostMetadata
if post.Metadata != nil {
metadata = post.Metadata.Copy()
}
var rejectionError *model.AppError
pluginContext := pluginContext(c)
pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
@@ -273,8 +281,12 @@ func (a *App) CreatePost(c request.CTX, post *model.Post, channel *model.Channel
return false
}
if replacementPost != nil {
// the original post's metadata (if there ever was any) is lost, and will be rebuilt.
post = replacementPost
if post.Metadata != nil && metadata != nil {
post.Metadata.Priority = metadata.Priority
} else {
post.Metadata = metadata
}
}
return true
@@ -343,7 +355,9 @@ func (a *App) CreatePost(c request.CTX, post *model.Post, channel *model.Channel
// Normally, we would let the API layer call PreparePostForClient, but we do it here since it also needs
// to be done when we send the post over the websocket in handlePostEvents
rpost = a.PreparePostForClient(c, rpost, true, false)
// PS: we don't want to include PostPriority from the db to avoid the replica lag,
// so we just return the one that was passed with post
rpost = a.PreparePostForClient(c, rpost, true, false, false)
// Make sure poster is following the thread
if *a.Config().ServiceSettings.ThreadAutoFollow && rpost.RootId != "" {
@@ -515,7 +529,7 @@ func (a *App) SendEphemeralPost(c request.CTX, userID string, post *model.Post)
post.GenerateActionIds()
message := model.NewWebSocketEvent(model.WebsocketEventEphemeralMessage, "", post.ChannelId, userID, nil, "")
post = a.PreparePostForClientWithEmbedsAndImages(c, post, true, false)
post = a.PreparePostForClientWithEmbedsAndImages(c, post, true, false, true)
post = model.AddPostActionCookies(post, a.PostActionCookieSecret())
postJSON, jsonErr := post.ToJSON()
@@ -538,7 +552,7 @@ func (a *App) UpdateEphemeralPost(c request.CTX, userID string, post *model.Post
post.GenerateActionIds()
message := model.NewWebSocketEvent(model.WebsocketEventPostEdited, "", post.ChannelId, userID, nil, "")
post = a.PreparePostForClientWithEmbedsAndImages(c, post, true, false)
post = a.PreparePostForClientWithEmbedsAndImages(c, post, true, false, true)
post = model.AddPostActionCookies(post, a.PostActionCookieSecret())
postJSON, jsonErr := post.ToJSON()
if jsonErr != nil {
@@ -682,7 +696,7 @@ func (a *App) UpdatePost(c *request.Context, post *model.Post, safeUpdate bool)
})
}
rpost = a.PreparePostForClientWithEmbedsAndImages(c, rpost, false, true)
rpost = a.PreparePostForClientWithEmbedsAndImages(c, rpost, false, true, true)
// Ensure IsFollowing is nil since this updated post will be broadcast to all users
// and we don't want to have to populate it for every single user and broadcast to each
@@ -1705,7 +1719,7 @@ func (a *App) countThreadMentions(c request.CTX, user *model.User, post *model.P
posts, nErr := a.Srv().Store().Post().GetPostsByThread(post.Id, timestamp)
if nErr != nil {
return 0, model.NewAppError("countMentionsFromPost", "app.channel.count_posts_since.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
return 0, model.NewAppError("countThreadMentions", "app.channel.count_posts_since.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
}
count := 0
@@ -1732,7 +1746,7 @@ func (a *App) countThreadMentions(c request.CTX, user *model.User, post *model.P
groups, nErr := a.getGroupsAllowedForReferenceInChannel(channel, team)
if nErr != nil {
return 0, model.NewAppError("countMentionsFromPost", "app.channel.count_posts_since.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
return 0, model.NewAppError("countThreadMentions", "app.channel.count_posts_since.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
}
for _, p := range posts {
@@ -1749,25 +1763,33 @@ func (a *App) countThreadMentions(c request.CTX, user *model.User, post *model.P
// countMentionsFromPost returns the number of posts in the post's channel that mention the user after and including the
// given post.
func (a *App) countMentionsFromPost(c request.CTX, user *model.User, post *model.Post) (int, int, *model.AppError) {
func (a *App) countMentionsFromPost(c request.CTX, user *model.User, post *model.Post) (int, int, int, *model.AppError) {
channel, err := a.GetChannel(c, post.ChannelId)
if err != nil {
return 0, 0, err
return 0, 0, 0, err
}
if channel.Type == model.ChannelTypeDirect {
// In a DM channel, every post made by the other user is a mention
count, countRoot, nErr := a.Srv().Store().Channel().CountPostsAfter(post.ChannelId, post.CreateAt-1, channel.GetOtherUserIdForDM(user.Id))
if nErr != nil {
return 0, 0, model.NewAppError("countMentionsFromPost", "app.channel.count_posts_since.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
return 0, 0, 0, model.NewAppError("countMentionsFromPost", "app.channel.count_posts_since.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
}
return count, countRoot, nil
var urgentCount int
if a.isPostPriorityEnabled() {
urgentCount, nErr = a.Srv().Store().Channel().CountUrgentPostsAfter(post.ChannelId, post.CreateAt-1, channel.GetOtherUserIdForDM(user.Id))
if nErr != nil {
return 0, 0, 0, model.NewAppError("countMentionsFromPost", "app.channel.count_urgent_posts_since.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
}
}
return count, countRoot, urgentCount, nil
}
channelMember, err := a.GetChannelMember(c, channel.Id, user.Id)
if err != nil {
return 0, 0, err
return 0, 0, 0, err
}
keywords := addMentionKeywordsForUser(
@@ -1785,15 +1807,25 @@ func (a *App) countMentionsFromPost(c request.CTX, user *model.User, post *model
thread, err := a.GetPostThread(post.Id, model.GetPostsOptions{}, user.Id)
if err != nil {
return 0, 0, err
return 0, 0, 0, err
}
count := 0
countRoot := 0
urgentCount := 0
if isPostMention(user, post, keywords, thread.Posts, mentionedByThread, checkForCommentMentions) {
count += 1
if post.RootId == "" {
countRoot += 1
if a.isPostPriorityEnabled() {
priority, err := a.GetPriorityForPost(post.Id)
if err != nil {
return 0, 0, 0, err
}
if priority != nil && *priority.Priority == model.PostPriorityUrgent {
urgentCount += 1
}
}
}
}
@@ -1807,18 +1839,32 @@ func (a *App) countMentionsFromPost(c request.CTX, user *model.User, post *model
PerPage: perPage,
})
if err != nil {
return 0, 0, err
return 0, 0, 0, err
}
mentionPostIds := make([]string, 0)
for _, postID := range postList.Order {
if isPostMention(user, postList.Posts[postID], keywords, postList.Posts, mentionedByThread, checkForCommentMentions) {
count += 1
if postList.Posts[postID].RootId == "" {
mentionPostIds = append(mentionPostIds, postID)
countRoot += 1
}
}
}
if a.isPostPriorityEnabled() {
priorityList, nErr := a.Srv().Store().PostPriority().GetForPosts(mentionPostIds)
if err != nil {
return 0, 0, 0, model.NewAppError("countMentionsFromPost", "app.channel.get_priority_for_posts.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
}
for _, priority := range priorityList {
if *priority.Priority == model.PostPriorityUrgent {
urgentCount += 1
}
}
}
if len(postList.Order) < perPage {
break
}
@@ -1826,7 +1872,7 @@ func (a *App) countMentionsFromPost(c request.CTX, user *model.User, post *model
page += 1
}
return count, countRoot, nil
return count, countRoot, urgentCount, nil
}
func isCommentMention(user *model.User, post *model.Post, otherPosts map[string]*model.Post, mentionedByThread map[string]bool) bool {
@@ -2025,7 +2071,7 @@ func (a *App) SetPostReminder(postID, userID string, targetTime int64) *model.Ap
}
message := model.NewWebSocketEvent(model.WebsocketEventEphemeralMessage, "", ephemeralPost.ChannelId, userID, nil, "")
ephemeralPost = a.PreparePostForClientWithEmbedsAndImages(request.EmptyContext(a.Log()), ephemeralPost, true, false)
ephemeralPost = a.PreparePostForClientWithEmbedsAndImages(request.EmptyContext(a.Log()), ephemeralPost, true, false, true)
ephemeralPost = model.AddPostActionCookies(ephemeralPost, a.PostActionCookieSecret())
postJSON, jsonErr := ephemeralPost.ToJSON()
@@ -2107,7 +2153,7 @@ func (a *App) CheckPostReminders() {
func includeEmbedsAndImages(a *App, c request.CTX, topThreadList *model.TopThreadList, userID string) (*model.TopThreadList, error) {
for _, topThread := range topThreadList.Items {
topThread.Post = a.PreparePostForClientWithEmbedsAndImages(c, topThread.Post, false, false)
topThread.Post = a.PreparePostForClientWithEmbedsAndImages(c, topThread.Post, false, false, true)
sanitizedPost, err := a.SanitizePostMetadataForUser(c, topThread.Post, userID)
if err != nil {
return nil, err
@@ -2116,3 +2162,7 @@ func includeEmbedsAndImages(a *App, c request.CTX, topThreadList *model.TopThrea
}
return topThreadList, nil
}
func (a *App) isPostPriorityEnabled() bool {
return a.Config().FeatureFlags.PostPriority && *a.Config().ServiceSettings.PostPriority
}

Просмотреть файл

@@ -56,11 +56,20 @@ func (a *App) PreparePostListForClient(c request.CTX, originalList *model.PostLi
}
for id, originalPost := range originalList.Posts {
post := a.PreparePostForClientWithEmbedsAndImages(c, originalPost, false, false)
post := a.PreparePostForClientWithEmbedsAndImages(c, originalPost, false, false, false)
list.Posts[id] = post
}
if a.isPostPriorityEnabled() {
priority, _ := a.GetPriorityForPostList(list)
for _, id := range list.Order {
if _, ok := priority[id]; ok {
list.Posts[id].Metadata.Priority = priority[id]
}
}
}
return list
}
@@ -90,7 +99,7 @@ func (a *App) OverrideIconURLIfEmoji(c request.CTX, post *model.Post) {
}
}
func (a *App) PreparePostForClient(c request.CTX, originalPost *model.Post, isNewPost, isEditPost bool) *model.Post {
func (a *App) PreparePostForClient(c request.CTX, originalPost *model.Post, isNewPost, isEditPost, includePriority bool) *model.Post {
post := originalPost.Clone()
// Proxy image links before constructing metadata so that requests go through the proxy
@@ -123,11 +132,20 @@ func (a *App) PreparePostForClient(c request.CTX, originalPost *model.Post, isNe
post.Metadata.Files = fileInfos
}
if includePriority && a.isPostPriorityEnabled() && post.RootId == "" {
// Post's Priority if any
if priority, err := a.GetPriorityForPost(post.Id); err != nil {
mlog.Warn("Failed to get post priority for a post", mlog.String("post_id", post.Id), mlog.Err(err))
} else {
post.Metadata.Priority = priority
}
}
return post
}
func (a *App) PreparePostForClientWithEmbedsAndImages(c request.CTX, originalPost *model.Post, isNewPost, isEditPost bool) *model.Post {
post := a.PreparePostForClient(c, originalPost, isNewPost, isEditPost)
func (a *App) PreparePostForClientWithEmbedsAndImages(c request.CTX, originalPost *model.Post, isNewPost, isEditPost, includePriority bool) *model.Post {
post := a.PreparePostForClient(c, originalPost, isNewPost, isEditPost, includePriority)
post = a.getEmbedsAndImages(c, post, isNewPost)
return post
}
@@ -562,7 +580,7 @@ func (a *App) getLinkMetadata(c request.CTX, requestURL string, timestamp int64,
permalink = &model.Permalink{PreviewPost: model.NewPreviewPost(referencedPost, referencedTeam, referencedChannel)}
} else {
// referencedPost does not contain a permalink: we get its metadata
referencedPostWithMetadata := a.PreparePostForClientWithEmbedsAndImages(c, referencedPost, false, false)
referencedPostWithMetadata := a.PreparePostForClientWithEmbedsAndImages(c, referencedPost, false, false, false)
permalink = &model.Permalink{PreviewPost: model.NewPreviewPost(referencedPostWithMetadata, referencedTeam, referencedChannel)}
}
} else {

Просмотреть файл

@@ -125,7 +125,7 @@ func TestPreparePostForClient(t *testing.T) {
Message: message,
}
clientPost := th.App.PreparePostForClient(th.Context, post, false, true)
clientPost := th.App.PreparePostForClient(th.Context, post, false, true, false)
t.Run("doesn't mutate provided post", func(t *testing.T) {
assert.NotEqual(t, clientPost, post, "should've returned a new post")
@@ -151,7 +151,7 @@ func TestPreparePostForClient(t *testing.T) {
post := th.CreatePost(th.BasicChannel)
clientPost := th.App.PreparePostForClient(th.Context, post, false, false)
clientPost := th.App.PreparePostForClient(th.Context, post, false, false, false)
assert.False(t, clientPost == post, "should've returned a new post")
assert.Equal(t, clientPost, post, "shouldn't have changed any metadata")
@@ -167,7 +167,7 @@ func TestPreparePostForClient(t *testing.T) {
reaction3 := th.AddReactionToPost(post, th.BasicUser2, "ice_cream")
post.HasReactions = true
clientPost := th.App.PreparePostForClient(th.Context, post, false, false)
clientPost := th.App.PreparePostForClient(th.Context, post, false, false, false)
assert.Len(t, clientPost.Metadata.Reactions, 3, "should've populated Reactions")
assert.Equal(t, reaction1, clientPost.Metadata.Reactions[0], "first reaction is incorrect")
@@ -194,7 +194,7 @@ func TestPreparePostForClient(t *testing.T) {
var clientPost *model.Post
assert.Eventually(t, func() bool {
clientPost = th.App.PreparePostForClient(th.Context, post, false, false)
clientPost = th.App.PreparePostForClient(th.Context, post, false, false, false)
return assert.ObjectsAreEqual([]*model.FileInfo{fileInfo}, clientPost.Metadata.Files)
}, time.Second, 10*time.Millisecond)
@@ -230,7 +230,7 @@ func TestPreparePostForClient(t *testing.T) {
th.AddReactionToPost(post, th.BasicUser2, "angry")
post.HasReactions = true
clientPost := th.App.PreparePostForClient(th.Context, post, false, false)
clientPost := th.App.PreparePostForClient(th.Context, post, false, false, false)
t.Run("populates emojis", func(t *testing.T) {
assert.ElementsMatch(t, []*model.Emoji{}, clientPost.Metadata.Emojis, "should've populated empty Emojis")
@@ -275,7 +275,7 @@ func TestPreparePostForClient(t *testing.T) {
th.AddReactionToPost(post, th.BasicUser2, "angry")
post.HasReactions = true
clientPost := th.App.PreparePostForClient(th.Context, post, false, false)
clientPost := th.App.PreparePostForClient(th.Context, post, false, false, false)
t.Run("populates emojis", func(t *testing.T) {
assert.ElementsMatch(t, []*model.Emoji{emoji1, emoji2, emoji3, emoji4}, clientPost.Metadata.Emojis, "should've populated post.Emojis")
@@ -307,7 +307,7 @@ func TestPreparePostForClient(t *testing.T) {
post.AddProp(model.PostPropsOverrideIconURL, url)
post.AddProp(model.PostPropsOverrideIconEmoji, emoji)
return th.App.PreparePostForClient(th.Context, post, false, false)
return th.App.PreparePostForClient(th.Context, post, false, false, false)
}
emoji := "basketball"
@@ -361,7 +361,7 @@ func TestPreparePostForClient(t *testing.T) {
}, th.BasicChannel, false, true)
require.Nil(t, err)
clientPost := th.App.PreparePostForClient(th.Context, post, false, false)
clientPost := th.App.PreparePostForClient(th.Context, post, false, false, false)
t.Run("populates image dimensions", func(t *testing.T) {
imageDimensions := clientPost.Metadata.Images
@@ -394,7 +394,7 @@ func TestPreparePostForClient(t *testing.T) {
post.AddProp(model.PostPropsOverrideIconEmoji, true)
require.NotPanics(t, func() {
_ = th.App.PreparePostForClient(th.Context, post, false, false)
_ = th.App.PreparePostForClient(th.Context, post, false, false, false)
})
})
@@ -424,7 +424,7 @@ func TestPreparePostForClient(t *testing.T) {
}, th.BasicChannel, false, true)
require.Nil(t, err)
post.Metadata.Embeds = nil
clientPost := th.App.PreparePostForClientWithEmbedsAndImages(th.Context, post, false, false)
clientPost := th.App.PreparePostForClientWithEmbedsAndImages(th.Context, post, false, false, false)
// Reminder that only the first link gets an embed and dimensions
@@ -459,7 +459,7 @@ func TestPreparePostForClient(t *testing.T) {
}, th.BasicChannel, false, true)
require.Nil(t, err)
clientPost := th.App.PreparePostForClient(th.Context, post, false, false)
clientPost := th.App.PreparePostForClient(th.Context, post, false, false, false)
firstEmbed := clientPost.Metadata.Embeds[0]
ogData := firstEmbed.Data.(*opengraph.OpenGraph)
@@ -502,7 +502,7 @@ func TestPreparePostForClient(t *testing.T) {
}, th.BasicChannel, false, true)
require.Nil(t, err)
post.Metadata.Embeds = nil
clientPost := th.App.PreparePostForClientWithEmbedsAndImages(th.Context, post, false, false)
clientPost := th.App.PreparePostForClientWithEmbedsAndImages(th.Context, post, false, false, false)
t.Run("populates embeds", func(t *testing.T) {
assert.ElementsMatch(t, []*model.PostEmbed{
@@ -547,7 +547,7 @@ func TestPreparePostForClient(t *testing.T) {
// DeleteAt isn't set on the post returned by App.DeletePost
post.DeleteAt = model.GetMillis()
clientPost := th.App.PreparePostForClient(th.Context, post, false, false)
clientPost := th.App.PreparePostForClient(th.Context, post, false, false, false)
assert.NotEqual(t, nil, clientPost.Metadata, "should've populated Metadata“")
assert.Equal(t, "", clientPost.Message, "should've cleaned post content")
@@ -582,7 +582,7 @@ func TestPreparePostForClient(t *testing.T) {
}, th.BasicChannel, false, true)
require.Nil(t, err)
previewPost.Metadata.Embeds = nil
clientPost := th.App.PreparePostForClientWithEmbedsAndImages(th.Context, previewPost, false, false)
clientPost := th.App.PreparePostForClientWithEmbedsAndImages(th.Context, previewPost, false, false, false)
firstEmbed := clientPost.Metadata.Embeds[0]
preview := firstEmbed.Data.(*model.PreviewPost)
require.Equal(t, referencedPost.Id, preview.PostID)
@@ -641,7 +641,7 @@ func TestPreparePostForClient(t *testing.T) {
require.Nil(t, err)
previewPost.Metadata.Embeds = nil
clientPost := th.App.PreparePostForClientWithEmbedsAndImages(th.Context, previewPost, false, false)
clientPost := th.App.PreparePostForClientWithEmbedsAndImages(th.Context, previewPost, false, false, false)
firstEmbed := clientPost.Metadata.Embeds[0]
preview := firstEmbed.Data.(*model.PreviewPost)
@@ -679,7 +679,7 @@ func TestPreparePostForClient(t *testing.T) {
require.Nil(t, err)
previewPost.Metadata.Embeds = nil
clientPost := th.App.PreparePostForClientWithEmbedsAndImages(th.Context, previewPost, false, false)
clientPost := th.App.PreparePostForClientWithEmbedsAndImages(th.Context, previewPost, false, false, false)
firstEmbed := clientPost.Metadata.Embeds[0]
preview := firstEmbed.Data.(*model.PreviewPost)
referencedPostFirstEmbed := preview.Post.Metadata.Embeds[0]
@@ -726,7 +726,7 @@ func TestPreparePostForClient(t *testing.T) {
require.Nil(t, err)
previewPost.Metadata.Embeds = nil
clientPost := th.App.PreparePostForClientWithEmbedsAndImages(th.Context, previewPost, false, false)
clientPost := th.App.PreparePostForClientWithEmbedsAndImages(th.Context, previewPost, false, false, false)
firstEmbed := clientPost.Metadata.Embeds[0]
preview := firstEmbed.Data.(*model.PreviewPost)
referencedPostMetadata := preview.Post.Metadata
@@ -761,7 +761,7 @@ func TestPreparePostForClient(t *testing.T) {
}, th.BasicChannel, false, true)
require.Nil(t, err)
clientPost := th.App.PreparePostForClient(th.Context, previewPost, false, false)
clientPost := th.App.PreparePostForClient(th.Context, previewPost, false, false, false)
firstEmbed := clientPost.Metadata.Embeds[0]
preview := firstEmbed.Data.(*model.PreviewPost)
require.Equal(t, referencedPost.Id, preview.PostID)
@@ -770,13 +770,13 @@ func TestPreparePostForClient(t *testing.T) {
*cfg.ServiceSettings.EnablePermalinkPreviews = false
})
th.App.PreparePostForClient(th.Context, previewPost, false, false)
th.App.PreparePostForClient(th.Context, previewPost, false, false, false)
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.EnablePermalinkPreviews = true
})
clientPost2 := th.App.PreparePostForClient(th.Context, previewPost, false, false)
clientPost2 := th.App.PreparePostForClient(th.Context, previewPost, false, false, false)
firstEmbed2 := clientPost2.Metadata.Embeds[0]
preview2 := firstEmbed2.Data.(*model.PreviewPost)
require.Equal(t, referencedPost.Id, preview2.PostID)
@@ -828,7 +828,7 @@ func testProxyLinkedImage(t *testing.T, th *TestHelper, shouldProxy bool) {
Message: fmt.Sprintf(postTemplate, imageURL),
}
clientPost := th.App.PreparePostForClient(th.Context, post, false, false)
clientPost := th.App.PreparePostForClient(th.Context, post, false, false, false)
if shouldProxy {
assert.Equal(t, fmt.Sprintf(postTemplate, imageURL), post.Message, "should not have mutated original post")
@@ -876,7 +876,7 @@ func testProxyOpenGraphImage(t *testing.T, th *TestHelper, shouldProxy bool) {
require.Nil(t, err)
post.Metadata.Embeds = nil
embeds := th.App.PreparePostForClientWithEmbedsAndImages(th.Context, post, false, false).Metadata.Embeds
embeds := th.App.PreparePostForClientWithEmbedsAndImages(th.Context, post, false, false, false).Metadata.Embeds
require.Len(t, embeds, 1, "should have one embed")
embed := embeds[0]

34
app/post_priority.go Обычный файл
Просмотреть файл

@@ -0,0 +1,34 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package app
import (
"database/sql"
"net/http"
"github.com/mattermost/mattermost-server/v6/model"
)
func (a *App) GetPriorityForPost(postId string) (*model.PostPriority, *model.AppError) {
priority, err := a.Srv().Store().PostPriority().GetForPost(postId)
if err != nil && err != sql.ErrNoRows {
return nil, model.NewAppError("GetPriorityForPost", "app.post_prority.get_for_post.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
return priority, nil
}
func (a *App) GetPriorityForPostList(list *model.PostList) (map[string]*model.PostPriority, *model.AppError) {
priority, err := a.Srv().Store().PostPriority().GetForPosts(list.Order)
if err != nil {
return nil, model.NewAppError("GetPriorityForPost", "app.post_prority.get_for_post.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
priorityMap := make(map[string]*model.PostPriority)
for _, p := range priority {
priorityMap[p.PostId] = p
}
return priorityMap, nil
}

Просмотреть файл

@@ -1598,7 +1598,7 @@ func TestCountMentionsFromPost(t *testing.T) {
}, channel, false, true)
require.Nil(t, err)
count, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
assert.Nil(t, err)
assert.Equal(t, 0, count)
@@ -1637,7 +1637,7 @@ func TestCountMentionsFromPost(t *testing.T) {
// post1 and post3 should mention the user
count, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
assert.Nil(t, err)
assert.Equal(t, 2, count)
@@ -1676,7 +1676,7 @@ func TestCountMentionsFromPost(t *testing.T) {
// post2 and post3 should mention the user
count, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
assert.Nil(t, err)
assert.Equal(t, 2, count)
@@ -1713,7 +1713,7 @@ func TestCountMentionsFromPost(t *testing.T) {
}, channel, false, true)
require.Nil(t, err)
count, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
assert.Nil(t, err)
assert.Equal(t, 0, count)
@@ -1755,7 +1755,7 @@ func TestCountMentionsFromPost(t *testing.T) {
}, channel, false, true)
require.Nil(t, err)
count, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
assert.Nil(t, err)
assert.Equal(t, 0, count)
@@ -1809,7 +1809,7 @@ func TestCountMentionsFromPost(t *testing.T) {
// post2 should mention the user
count, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
assert.Nil(t, err)
assert.Equal(t, 1, count)
@@ -1863,7 +1863,7 @@ func TestCountMentionsFromPost(t *testing.T) {
// post2 and post5 should mention the user
count, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
assert.Nil(t, err)
assert.Equal(t, 2, count)
@@ -1912,7 +1912,7 @@ func TestCountMentionsFromPost(t *testing.T) {
// should be mentioned by post2 and post3
count, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
assert.Nil(t, err)
assert.Equal(t, 2, count)
@@ -1942,12 +1942,12 @@ func TestCountMentionsFromPost(t *testing.T) {
}, channel, false, true)
require.Nil(t, err)
count, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
assert.Nil(t, err)
assert.Equal(t, 2, count)
count, _, err = th.App.countMentionsFromPost(th.Context, user1, post1)
count, _, _, err = th.App.countMentionsFromPost(th.Context, user1, post1)
assert.Nil(t, err)
assert.Equal(t, 0, count)
@@ -1984,7 +1984,7 @@ func TestCountMentionsFromPost(t *testing.T) {
// post1 and post3 should mention the user, but we only count post3
count, _, err := th.App.countMentionsFromPost(th.Context, user2, post2)
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post2)
assert.Nil(t, err)
assert.Equal(t, 1, count)
@@ -2015,7 +2015,7 @@ func TestCountMentionsFromPost(t *testing.T) {
// post2 should mention the user
count, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
assert.Nil(t, err)
assert.Equal(t, 1, count)
@@ -2062,7 +2062,7 @@ func TestCountMentionsFromPost(t *testing.T) {
// post4 should mention the user
count, _, err := th.App.countMentionsFromPost(th.Context, user2, post3)
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post3)
assert.Nil(t, err)
assert.Equal(t, 1, count)
@@ -2102,7 +2102,7 @@ func TestCountMentionsFromPost(t *testing.T) {
// post3 should mention the user
count, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
assert.Nil(t, err)
assert.Equal(t, 1, count)
@@ -2138,11 +2138,70 @@ func TestCountMentionsFromPost(t *testing.T) {
// Every post should mention the user
count, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
assert.Nil(t, err)
assert.Equal(t, numPosts, count)
})
t.Run("should count urgent mentions", func(t *testing.T) {
os.Setenv("MM_FEATUREFLAGS_POSTPRIORITY", "true")
defer os.Unsetenv("MM_FEATUREFLAGS_POSTPRIORITY")
th := Setup(t).InitBasic()
defer th.TearDown()
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.PostPriority = true
cfg.FeatureFlags.PostPriority = true
})
user1 := th.BasicUser
user2 := th.BasicUser2
channel := th.CreateChannel(th.Context, th.BasicTeam)
th.AddUserToChannel(user2, channel)
user2.NotifyProps[model.MentionKeysNotifyProp] = "apple"
post1, err := th.App.CreatePost(th.Context, &model.Post{
UserId: user1.Id,
ChannelId: channel.Id,
Message: fmt.Sprintf("@%s", user2.Username),
Metadata: &model.PostMetadata{
Priority: &model.PostPriority{
Priority: model.NewString(model.PostPriorityUrgent),
},
},
}, channel, false, true)
require.Nil(t, err)
_, err = th.App.CreatePost(th.Context, &model.Post{
UserId: user1.Id,
ChannelId: channel.Id,
Message: fmt.Sprintf("@%s", user2.Username),
}, channel, false, true)
require.Nil(t, err)
_, err = th.App.CreatePost(th.Context, &model.Post{
UserId: user1.Id,
ChannelId: channel.Id,
Message: "apple",
Metadata: &model.PostMetadata{
Priority: &model.PostPriority{
Priority: model.NewString(model.PostPriorityUrgent),
},
},
}, channel, false, true)
require.Nil(t, err)
// all posts mention the user but only post1, post3 are urgent
_, _, count, err := th.App.countMentionsFromPost(th.Context, user2, post1)
assert.Nil(t, err)
assert.Equal(t, 2, count)
})
}
func TestFillInPostProps(t *testing.T) {

Просмотреть файл

@@ -1747,6 +1747,7 @@ func (a *App) GetTeamsUnreadForUser(excludeTeamId string, userID string, include
MsgCountRoot: 0,
ThreadCount: 0,
ThreadMentionCount: 0,
ThreadUrgentMentionCount: 0,
TeamId: id,
})
}
@@ -1755,7 +1756,7 @@ func (a *App) GetTeamsUnreadForUser(excludeTeamId string, userID string, include
includeCollapsedThreads = includeCollapsedThreads && *a.Config().ServiceSettings.CollapsedThreads != model.CollapsedThreadsDisabled
if includeCollapsedThreads {
teamUnreads, err := a.Srv().Store().Thread().GetTeamsUnreadForUser(userID, teamIDs)
teamUnreads, err := a.Srv().Store().Thread().GetTeamsUnreadForUser(userID, teamIDs, a.isPostPriorityEnabled())
if err != nil {
return nil, model.NewAppError("GetTeamsUnreadForUser", "app.team.get_unread.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
@@ -1763,6 +1764,7 @@ func (a *App) GetTeamsUnreadForUser(excludeTeamId string, userID string, include
if _, ok := teamUnreads[teamID]; ok {
member.ThreadCount = teamUnreads[teamID].ThreadCount
member.ThreadMentionCount = teamUnreads[teamID].ThreadMentionCount
member.ThreadUrgentMentionCount = teamUnreads[teamID].ThreadUrgentMentionCount
}
}
}

Просмотреть файл

@@ -2391,6 +2391,10 @@ func (a *App) ConvertBotToUser(c request.CTX, bot *model.Bot, userPatch *model.U
func (a *App) GetThreadsForUser(userID, teamID string, options model.GetUserThreadsOpts) (*model.Threads, *model.AppError) {
var result model.Threads
var eg errgroup.Group
postPriorityIsEnabled := a.isPostPriorityEnabled()
if postPriorityIsEnabled {
options.IncludeIsUrgent = true
}
if !options.ThreadsOnly {
eg.Go(func() error {
@@ -2427,6 +2431,18 @@ func (a *App) GetThreadsForUser(userID, teamID string, options model.GetUserThre
return nil
})
if postPriorityIsEnabled {
eg.Go(func() error {
totalUnreadUrgentMentions, err := a.Srv().Store().Thread().GetTotalUnreadUrgentMentions(userID, teamID, options)
if err != nil {
return errors.Wrapf(err, "failed to count urgent mentioned threads for user id=%s", userID)
}
result.TotalUnreadUrgentMentions = totalUnreadUrgentMentions
return nil
})
}
}
if !options.TotalsOnly {
@@ -2469,7 +2485,7 @@ func (a *App) GetThreadMembershipForUser(userId, threadId string) (*model.Thread
}
func (a *App) GetThreadForUser(threadMembership *model.ThreadMembership, extended bool) (*model.ThreadResponse, *model.AppError) {
thread, err := a.Srv().Store().Thread().GetThreadForUser(threadMembership, extended)
thread, err := a.Srv().Store().Thread().GetThreadForUser(threadMembership, extended, a.isPostPriorityEnabled())
if err != nil {
return nil, model.NewAppError("GetThreadForUser", "app.user.get_threads_for_user.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
@@ -2551,7 +2567,7 @@ func (a *App) UpdateThreadFollowForUserFromChannelAdd(c request.CTX, userID, tea
}
message := model.NewWebSocketEvent(model.WebsocketEventThreadUpdated, teamID, "", userID, nil, "")
userThread, err := a.Srv().Store().Thread().GetThreadForUser(tm, true)
userThread, err := a.Srv().Store().Thread().GetThreadForUser(tm, true, a.isPostPriorityEnabled())
if err != nil {
var errNotFound *store.ErrNotFound

Просмотреть файл

@@ -192,6 +192,8 @@ db/migrations/mysql/000095_remove_posts_parentid.down.sql
db/migrations/mysql/000095_remove_posts_parentid.up.sql
db/migrations/mysql/000096_threads_threadteamid.down.sql
db/migrations/mysql/000096_threads_threadteamid.up.sql
db/migrations/mysql/000097_create_posts_priority.down.sql
db/migrations/mysql/000097_create_posts_priority.up.sql
db/migrations/postgres/000001_create_teams.down.sql
db/migrations/postgres/000001_create_teams.up.sql
db/migrations/postgres/000002_create_team_members.down.sql
@@ -384,3 +386,5 @@ db/migrations/postgres/000095_remove_posts_parentid.down.sql
db/migrations/postgres/000095_remove_posts_parentid.up.sql
db/migrations/postgres/000096_threads_threadteamid.down.sql
db/migrations/postgres/000096_threads_threadteamid.up.sql
db/migrations/postgres/000097_create_posts_priority.down.sql
db/migrations/postgres/000097_create_posts_priority.up.sql

Просмотреть файл

@@ -0,0 +1,16 @@
DROP TABLE IF EXISTS PostsPriority;
SET @preparedStatement = (SELECT IF(
(
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = 'ChannelMembers'
AND table_schema = DATABASE()
AND column_name = 'UrgentMentionCount'
) > 0,
'ALTER TABLE ChannelMembers DROP COLUMN UrgentMentionCount;',
'SELECT 1'
));
PREPARE alterIfExists FROM @preparedStatement;
EXECUTE alterIfExists;
DEALLOCATE PREPARE alterIfExists;

Просмотреть файл

@@ -0,0 +1,23 @@
CREATE TABLE IF NOT EXISTS PostsPriority (
PostId varchar(26) NOT NULL,
ChannelId varchar(26) NOT NULL,
Priority varchar(32) NOT NULL,
RequestedAck tinyint(1),
PersistentNotifications tinyint(1),
PRIMARY KEY (PostId)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
SET @preparedStatement = (SELECT IF(
NOT EXISTS(
SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = 'ChannelMembers'
AND table_schema = DATABASE()
AND column_name = 'UrgentMentionCount'
),
'ALTER TABLE ChannelMembers ADD COLUMN UrgentMentionCount bigint(20);',
'SELECT 1;'
));
PREPARE alterIfNotExists FROM @preparedStatement;
EXECUTE alterIfNotExists;
DEALLOCATE PREPARE alterIfNotExists;

Просмотреть файл

@@ -0,0 +1,3 @@
DROP TABLE IF EXISTS postspriority;
ALTER TABLE channelmembers DROP COLUMN IF EXISTS urgentmentioncount;

Просмотреть файл

@@ -0,0 +1,9 @@
CREATE TABLE IF NOT EXISTS postspriority (
postid VARCHAR(26) PRIMARY KEY,
channelid VARCHAR(26) NOT NULL,
priority VARCHAR(32) NOT NULL,
requestedack boolean,
persistentnotifications boolean
);
ALTER TABLE channelmembers ADD COLUMN IF NOT EXISTS urgentmentioncount bigint;

Просмотреть файл

@@ -4551,6 +4551,10 @@
"id": "app.channel.count_posts_since.app_error",
"translation": "Unable to count messages since given date."
},
{
"id": "app.channel.count_urgent_posts_since.app_error",
"translation": "Unable to count urgent posts since given date."
},
{
"id": "app.channel.create_channel.internal_error",
"translation": "Unable to save channel."
@@ -4679,6 +4683,10 @@
"id": "app.channel.get_pinnedpost_count.app_error",
"translation": "Unable to get the channel pinned post count."
},
{
"id": "app.channel.get_priority_for_posts.app_error",
"translation": "Unable to get the priority for posts"
},
{
"id": "app.channel.get_private_channels.get.app_error",
"translation": "Unable to get private channels."
@@ -6111,6 +6119,10 @@
"id": "app.post.update.app_error",
"translation": "Unable to update the Post."
},
{
"id": "app.post_prority.get_for_post.app_error",
"translation": "Unable to get postpriority for post"
},
{
"id": "app.post_reminder_dm",
"translation": "Hi there, here's your reminder about this message from @{{.Username}}: {{.SiteURL}}/{{.TeamName}}/pl/{{.PostId}}"

Просмотреть файл

@@ -27,6 +27,7 @@ type ChannelUnread struct {
MsgCount int64 `json:"msg_count"`
MentionCount int64 `json:"mention_count"`
MentionCountRoot int64 `json:"mention_count_root"`
UrgentMentionCount int64 `json:"urgent_mention_count"`
MsgCountRoot int64 `json:"msg_count_root"`
NotifyProps StringMap `json:"-"`
}
@@ -38,6 +39,7 @@ type ChannelUnreadAt struct {
MsgCount int64 `json:"msg_count"`
MentionCount int64 `json:"mention_count"`
MentionCountRoot int64 `json:"mention_count_root"`
UrgentMentionCount int64 `json:"urgent_mention_count"`
MsgCountRoot int64 `json:"msg_count_root"`
LastViewedAt int64 `json:"last_viewed_at"`
NotifyProps StringMap `json:"-"`
@@ -51,6 +53,7 @@ type ChannelMember struct {
MsgCount int64 `json:"msg_count"`
MentionCount int64 `json:"mention_count"`
MentionCountRoot int64 `json:"mention_count_root"`
UrgentMentionCount int64 `json:"urgent_mention_count"`
MsgCountRoot int64 `json:"msg_count_root"`
NotifyProps StringMap `json:"notify_props"`
LastUpdateAt int64 `json:"last_update_at"`
@@ -69,6 +72,7 @@ func (o *ChannelMember) Auditable() map[string]interface{} {
"msg_count": o.MsgCount,
"mention_count": o.MentionCount,
"mention_count_root": o.MentionCountRoot,
"urgent_mention_count": o.UrgentMentionCount,
"msg_count_root": o.MsgCountRoot,
"notify_props": o.NotifyProps,
"last_update_at": o.LastUpdateAt,
@@ -100,6 +104,10 @@ func (o *ChannelMember) MentionCountRoot_() float64 {
return float64(o.MentionCountRoot)
}
func (o *ChannelMember) UrgentMentionCount_() float64 {
return float64(o.UrgentMentionCount)
}
func (o *ChannelMember) MsgCountRoot_() float64 {
return float64(o.MsgCountRoot)
}

Просмотреть файл

@@ -71,6 +71,10 @@ const (
PostPropsGroupHighlightDisabled = "disable_group_highlight"
PostPropsPreviewedPost = "previewed_post"
PostPriorityUrgent = "urgent"
PostPropsRequestedAck = "requested_ack"
PostPropsPersistentNotifications = "persistent_notifications"
)
const (
@@ -158,6 +162,15 @@ type PostReminder struct {
UserId string `json:",omitempty"`
}
type PostPriority struct {
Priority *string `json:"priority"`
RequestedAck *bool `json:"requested_ack"`
PersistentNotifications *bool `json:"persistent_notifications"`
// These fields are only used internally for interacting with DB.
PostId string `json:",omitempty"`
ChannelId string `json:",omitempty"`
}
type SearchParameter struct {
Terms *string `json:"terms"`
IsOrSearch *bool `json:"is_or_search"`
@@ -306,6 +319,7 @@ type GetPostsOptions struct {
FromCreateAt int64 // CreateAt after which to send the items
Direction string // Only accepts up|down. Indicates the order in which to send the items.
IncludeDeleted bool
IncludePostPriority bool
}
type PostCountOptions struct {
@@ -770,3 +784,20 @@ func (o *Post) GetPreviewedPostProp() string {
}
return ""
}
func (o *Post) GetPriority() *PostPriority {
if o.Metadata != nil && o.Metadata.Priority != nil {
return o.Metadata.Priority
}
return nil
}
func (o *Post) IsUrgent() bool {
postPriority := o.GetPriority()
if postPriority == nil {
return false
}
return *postPriority.Priority == PostPriorityUrgent
}

Просмотреть файл

@@ -22,6 +22,9 @@ type PostMetadata struct {
// Reactions holds reactions made to the post.
Reactions []*Reaction `json:"reactions,omitempty"`
// Reactions holds reactions made to the post.
Priority *PostPriority `json:"priority,omitempty"`
}
type PostImage struct {
@@ -54,11 +57,23 @@ func (p *PostMetadata) Copy() *PostMetadata {
reactionsCopy := make([]*Reaction, len(p.Reactions))
copy(reactionsCopy, p.Reactions)
var postPriorityCopy *PostPriority
if p.Priority != nil {
postPriorityCopy = &PostPriority{
Priority: p.Priority.Priority,
RequestedAck: p.Priority.RequestedAck,
PersistentNotifications: p.Priority.PersistentNotifications,
PostId: p.Priority.PostId,
ChannelId: p.Priority.ChannelId,
}
}
return &PostMetadata{
Embeds: embedsCopy,
Emojis: emojisCopy,
Files: filesCopy,
Images: imagesCopy,
Reactions: reactionsCopy,
Priority: postPriorityCopy,
}
}

Просмотреть файл

@@ -52,6 +52,7 @@ type TeamUnread struct {
MsgCountRoot int64 `json:"msg_count_root"`
ThreadCount int64 `json:"thread_count"`
ThreadMentionCount int64 `json:"thread_mention_count"`
ThreadUrgentMentionCount int64 `json:"thread_urgent_mention_count"`
}
//msgp:ignore TeamMemberForExport

Просмотреть файл

@@ -41,6 +41,7 @@ type ThreadResponse struct {
Post *Post `json:"post"`
UnreadReplies int64 `json:"unread_replies"`
UnreadMentions int64 `json:"unread_mentions"`
IsUrgent bool `json:"is_urgent"`
DeleteAt int64 `json:"delete_at"`
}
@@ -48,6 +49,7 @@ type Threads struct {
Total int64 `json:"total"`
TotalUnreadThreads int64 `json:"total_unread_threads"`
TotalUnreadMentions int64 `json:"total_unread_mentions"`
TotalUnreadUrgentMentions int64 `json:"total_unread_urgent_mentions"`
Threads []*ThreadResponse `json:"threads"`
}
@@ -81,6 +83,9 @@ type GetUserThreadsOpts struct {
// TeamOnly will only fetch threads and unreads for the specified team and excludes DMs/GMs
TeamOnly bool
// IncludeIsUrgent will return IsUrgent field as well to assert is the thread is urgent or not
IncludeIsUrgent bool
}
func (o *Thread) Etag() string {

Просмотреть файл

@@ -37,6 +37,7 @@ type OpenTracingLayer struct {
OAuthStore store.OAuthStore
PluginStore store.PluginStore
PostStore store.PostStore
PostPriorityStore store.PostPriorityStore
PreferenceStore store.PreferenceStore
ProductNoticesStore store.ProductNoticesStore
ReactionStore store.ReactionStore
@@ -131,6 +132,10 @@ func (s *OpenTracingLayer) Post() store.PostStore {
return s.PostStore
}
func (s *OpenTracingLayer) PostPriority() store.PostPriorityStore {
return s.PostPriorityStore
}
func (s *OpenTracingLayer) Preference() store.PreferenceStore {
return s.PreferenceStore
}
@@ -301,6 +306,11 @@ type OpenTracingLayerPostStore struct {
Root *OpenTracingLayer
}
type OpenTracingLayerPostPriorityStore struct {
store.PostPriorityStore
Root *OpenTracingLayer
}
type OpenTracingLayerPreferenceStore struct {
store.PreferenceStore
Root *OpenTracingLayer
@@ -702,6 +712,24 @@ func (s *OpenTracingLayerChannelStore) CountPostsAfter(channelID string, timesta
return result, resultVar1, err
}
func (s *OpenTracingLayerChannelStore) CountUrgentPostsAfter(channelID string, timestamp int64, userID string) (int, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.CountUrgentPostsAfter")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
result, err := s.ChannelStore.CountUrgentPostsAfter(channelID, timestamp, userID)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return result, err
}
func (s *OpenTracingLayerChannelStore) CreateDirectChannel(userID *model.User, otherUserID *model.User, channelOptions ...model.ChannelOption) (*model.Channel, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.CreateDirectChannel")
@@ -1867,7 +1895,7 @@ func (s *OpenTracingLayerChannelStore) GroupSyncedChannelCount() (int64, error)
return result, err
}
func (s *OpenTracingLayerChannelStore) IncrementMentionCount(channelID string, userIDs []string, isRoot bool) error {
func (s *OpenTracingLayerChannelStore) IncrementMentionCount(channelID string, userIDs []string, isRoot bool, isUrgent bool) error {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.IncrementMentionCount")
s.Root.Store.SetContext(newCtx)
@@ -1876,7 +1904,7 @@ func (s *OpenTracingLayerChannelStore) IncrementMentionCount(channelID string, u
}()
defer span.Finish()
err := s.ChannelStore.IncrementMentionCount(channelID, userIDs, isRoot)
err := s.ChannelStore.IncrementMentionCount(channelID, userIDs, isRoot, isUrgent)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
@@ -2439,7 +2467,7 @@ func (s *OpenTracingLayerChannelStore) UpdateLastViewedAt(channelIds []string, u
return result, err
}
func (s *OpenTracingLayerChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount int, mentionCountRoot int, setUnreadCountRoot bool) (*model.ChannelUnreadAt, error) {
func (s *OpenTracingLayerChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount int, mentionCountRoot int, urgentMentionCount int, setUnreadCountRoot bool) (*model.ChannelUnreadAt, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.UpdateLastViewedAtPost")
s.Root.Store.SetContext(newCtx)
@@ -2448,7 +2476,7 @@ func (s *OpenTracingLayerChannelStore) UpdateLastViewedAtPost(unreadPost *model.
}()
defer span.Finish()
result, err := s.ChannelStore.UpdateLastViewedAtPost(unreadPost, userID, mentionCount, mentionCountRoot, setUnreadCountRoot)
result, err := s.ChannelStore.UpdateLastViewedAtPost(unreadPost, userID, mentionCount, mentionCountRoot, urgentMentionCount, setUnreadCountRoot)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
@@ -6516,6 +6544,42 @@ func (s *OpenTracingLayerPostStore) Update(newPost *model.Post, oldPost *model.P
return result, err
}
func (s *OpenTracingLayerPostPriorityStore) GetForPost(postId string) (*model.PostPriority, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostPriorityStore.GetForPost")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
result, err := s.PostPriorityStore.GetForPost(postId)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return result, err
}
func (s *OpenTracingLayerPostPriorityStore) GetForPosts(ids []string) ([]*model.PostPriority, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostPriorityStore.GetForPosts")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
result, err := s.PostPriorityStore.GetForPosts(ids)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return result, err
}
func (s *OpenTracingLayerPreferenceStore) CleanupFlagsBatch(limit int64) (int64, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PreferenceStore.CleanupFlagsBatch")
@@ -9872,7 +9936,7 @@ func (s *OpenTracingLayerThreadStore) GetMembershipsForUser(userId string, teamI
return result, err
}
func (s *OpenTracingLayerThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string) (map[string]*model.TeamUnread, error) {
func (s *OpenTracingLayerThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string, includeUrgentMentionCount bool) (map[string]*model.TeamUnread, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.GetTeamsUnreadForUser")
s.Root.Store.SetContext(newCtx)
@@ -9881,7 +9945,7 @@ func (s *OpenTracingLayerThreadStore) GetTeamsUnreadForUser(userID string, teamI
}()
defer span.Finish()
result, err := s.ThreadStore.GetTeamsUnreadForUser(userID, teamIDs)
result, err := s.ThreadStore.GetTeamsUnreadForUser(userID, teamIDs, includeUrgentMentionCount)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
@@ -9908,7 +9972,7 @@ func (s *OpenTracingLayerThreadStore) GetThreadFollowers(threadID string, fetchO
return result, err
}
func (s *OpenTracingLayerThreadStore) GetThreadForUser(threadMembership *model.ThreadMembership, extended bool) (*model.ThreadResponse, error) {
func (s *OpenTracingLayerThreadStore) GetThreadForUser(threadMembership *model.ThreadMembership, extended bool, postPriorityIsEnabled bool) (*model.ThreadResponse, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.GetThreadForUser")
s.Root.Store.SetContext(newCtx)
@@ -9917,7 +9981,7 @@ func (s *OpenTracingLayerThreadStore) GetThreadForUser(threadMembership *model.T
}()
defer span.Finish()
result, err := s.ThreadStore.GetThreadForUser(threadMembership, extended)
result, err := s.ThreadStore.GetThreadForUser(threadMembership, extended, postPriorityIsEnabled)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
@@ -10052,6 +10116,24 @@ func (s *OpenTracingLayerThreadStore) GetTotalUnreadThreads(userId string, teamI
return result, err
}
func (s *OpenTracingLayerThreadStore) GetTotalUnreadUrgentMentions(userId string, teamID string, opts model.GetUserThreadsOpts) (int64, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.GetTotalUnreadUrgentMentions")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
result, err := s.ThreadStore.GetTotalUnreadUrgentMentions(userId, teamID, opts)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return result, err
}
func (s *OpenTracingLayerThreadStore) MaintainMembership(userID string, postID string, opts store.ThreadMembershipOpts) (*model.ThreadMembership, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.MaintainMembership")
@@ -12509,6 +12591,7 @@ func New(childStore store.Store, ctx context.Context) *OpenTracingLayer {
newStore.OAuthStore = &OpenTracingLayerOAuthStore{OAuthStore: childStore.OAuth(), Root: &newStore}
newStore.PluginStore = &OpenTracingLayerPluginStore{PluginStore: childStore.Plugin(), Root: &newStore}
newStore.PostStore = &OpenTracingLayerPostStore{PostStore: childStore.Post(), Root: &newStore}
newStore.PostPriorityStore = &OpenTracingLayerPostPriorityStore{PostPriorityStore: childStore.PostPriority(), Root: &newStore}
newStore.PreferenceStore = &OpenTracingLayerPreferenceStore{PreferenceStore: childStore.Preference(), Root: &newStore}
newStore.ProductNoticesStore = &OpenTracingLayerProductNoticesStore{ProductNoticesStore: childStore.ProductNotices(), Root: &newStore}
newStore.ReactionStore = &OpenTracingLayerReactionStore{ReactionStore: childStore.Reaction(), Root: &newStore}

Просмотреть файл

@@ -40,6 +40,7 @@ type RetryLayer struct {
OAuthStore store.OAuthStore
PluginStore store.PluginStore
PostStore store.PostStore
PostPriorityStore store.PostPriorityStore
PreferenceStore store.PreferenceStore
ProductNoticesStore store.ProductNoticesStore
ReactionStore store.ReactionStore
@@ -134,6 +135,10 @@ func (s *RetryLayer) Post() store.PostStore {
return s.PostStore
}
func (s *RetryLayer) PostPriority() store.PostPriorityStore {
return s.PostPriorityStore
}
func (s *RetryLayer) Preference() store.PreferenceStore {
return s.PreferenceStore
}
@@ -304,6 +309,11 @@ type RetryLayerPostStore struct {
Root *RetryLayer
}
type RetryLayerPostPriorityStore struct {
store.PostPriorityStore
Root *RetryLayer
}
type RetryLayerPreferenceStore struct {
store.PreferenceStore
Root *RetryLayer
@@ -762,6 +772,27 @@ func (s *RetryLayerChannelStore) CountPostsAfter(channelID string, timestamp int
}
func (s *RetryLayerChannelStore) CountUrgentPostsAfter(channelID string, timestamp int64, userID string) (int, error) {
tries := 0
for {
result, err := s.ChannelStore.CountUrgentPostsAfter(channelID, timestamp, userID)
if err == nil {
return result, nil
}
if !isRepeatableError(err) {
return result, err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return result, err
}
timepkg.Sleep(100 * timepkg.Millisecond)
}
}
func (s *RetryLayerChannelStore) CreateDirectChannel(userID *model.User, otherUserID *model.User, channelOptions ...model.ChannelOption) (*model.Channel, error) {
tries := 0
@@ -2112,11 +2143,11 @@ func (s *RetryLayerChannelStore) GroupSyncedChannelCount() (int64, error) {
}
func (s *RetryLayerChannelStore) IncrementMentionCount(channelID string, userIDs []string, isRoot bool) error {
func (s *RetryLayerChannelStore) IncrementMentionCount(channelID string, userIDs []string, isRoot bool, isUrgent bool) error {
tries := 0
for {
err := s.ChannelStore.IncrementMentionCount(channelID, userIDs, isRoot)
err := s.ChannelStore.IncrementMentionCount(channelID, userIDs, isRoot, isUrgent)
if err == nil {
return nil
}
@@ -2706,11 +2737,11 @@ func (s *RetryLayerChannelStore) UpdateLastViewedAt(channelIds []string, userID
}
func (s *RetryLayerChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount int, mentionCountRoot int, setUnreadCountRoot bool) (*model.ChannelUnreadAt, error) {
func (s *RetryLayerChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount int, mentionCountRoot int, urgentMentionCount int, setUnreadCountRoot bool) (*model.ChannelUnreadAt, error) {
tries := 0
for {
result, err := s.ChannelStore.UpdateLastViewedAtPost(unreadPost, userID, mentionCount, mentionCountRoot, setUnreadCountRoot)
result, err := s.ChannelStore.UpdateLastViewedAtPost(unreadPost, userID, mentionCount, mentionCountRoot, urgentMentionCount, setUnreadCountRoot)
if err == nil {
return result, nil
}
@@ -7389,6 +7420,48 @@ func (s *RetryLayerPostStore) Update(newPost *model.Post, oldPost *model.Post) (
}
func (s *RetryLayerPostPriorityStore) GetForPost(postId string) (*model.PostPriority, error) {
tries := 0
for {
result, err := s.PostPriorityStore.GetForPost(postId)
if err == nil {
return result, nil
}
if !isRepeatableError(err) {
return result, err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return result, err
}
timepkg.Sleep(100 * timepkg.Millisecond)
}
}
func (s *RetryLayerPostPriorityStore) GetForPosts(ids []string) ([]*model.PostPriority, error) {
tries := 0
for {
result, err := s.PostPriorityStore.GetForPosts(ids)
if err == nil {
return result, nil
}
if !isRepeatableError(err) {
return result, err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return result, err
}
timepkg.Sleep(100 * timepkg.Millisecond)
}
}
func (s *RetryLayerPreferenceStore) CleanupFlagsBatch(limit int64) (int64, error) {
tries := 0
@@ -11286,11 +11359,11 @@ func (s *RetryLayerThreadStore) GetMembershipsForUser(userId string, teamID stri
}
func (s *RetryLayerThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string) (map[string]*model.TeamUnread, error) {
func (s *RetryLayerThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string, includeUrgentMentionCount bool) (map[string]*model.TeamUnread, error) {
tries := 0
for {
result, err := s.ThreadStore.GetTeamsUnreadForUser(userID, teamIDs)
result, err := s.ThreadStore.GetTeamsUnreadForUser(userID, teamIDs, includeUrgentMentionCount)
if err == nil {
return result, nil
}
@@ -11328,11 +11401,11 @@ func (s *RetryLayerThreadStore) GetThreadFollowers(threadID string, fetchOnlyAct
}
func (s *RetryLayerThreadStore) GetThreadForUser(threadMembership *model.ThreadMembership, extended bool) (*model.ThreadResponse, error) {
func (s *RetryLayerThreadStore) GetThreadForUser(threadMembership *model.ThreadMembership, extended bool, postPriorityIsEnabled bool) (*model.ThreadResponse, error) {
tries := 0
for {
result, err := s.ThreadStore.GetThreadForUser(threadMembership, extended)
result, err := s.ThreadStore.GetThreadForUser(threadMembership, extended, postPriorityIsEnabled)
if err == nil {
return result, nil
}
@@ -11496,6 +11569,27 @@ func (s *RetryLayerThreadStore) GetTotalUnreadThreads(userId string, teamID stri
}
func (s *RetryLayerThreadStore) GetTotalUnreadUrgentMentions(userId string, teamID string, opts model.GetUserThreadsOpts) (int64, error) {
tries := 0
for {
result, err := s.ThreadStore.GetTotalUnreadUrgentMentions(userId, teamID, opts)
if err == nil {
return result, nil
}
if !isRepeatableError(err) {
return result, err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return result, err
}
timepkg.Sleep(100 * timepkg.Millisecond)
}
}
func (s *RetryLayerThreadStore) MaintainMembership(userID string, postID string, opts store.ThreadMembershipOpts) (*model.ThreadMembership, error) {
tries := 0
@@ -14261,6 +14355,7 @@ func New(childStore store.Store) *RetryLayer {
newStore.OAuthStore = &RetryLayerOAuthStore{OAuthStore: childStore.OAuth(), Root: &newStore}
newStore.PluginStore = &RetryLayerPluginStore{PluginStore: childStore.Plugin(), Root: &newStore}
newStore.PostStore = &RetryLayerPostStore{PostStore: childStore.Post(), Root: &newStore}
newStore.PostPriorityStore = &RetryLayerPostPriorityStore{PostPriorityStore: childStore.PostPriority(), Root: &newStore}
newStore.PreferenceStore = &RetryLayerPreferenceStore{PreferenceStore: childStore.Preference(), Root: &newStore}
newStore.ProductNoticesStore = &RetryLayerProductNoticesStore{ProductNoticesStore: childStore.ProductNotices(), Root: &newStore}
newStore.ReactionStore = &RetryLayerReactionStore{ReactionStore: childStore.Reaction(), Root: &newStore}

Просмотреть файл

@@ -54,6 +54,7 @@ func genStore() *mocks.Store {
mock.On("UserTermsOfService").Return(&mocks.UserTermsOfServiceStore{})
mock.On("Webhook").Return(&mocks.WebhookStore{})
mock.On("NotifyAdmin").Return(&mocks.NotifyAdminStore{})
mock.On("PostPriority").Return(&mocks.PostPriorityStore{})
return mock
}

Просмотреть файл

@@ -47,6 +47,7 @@ type channelMember struct {
LastViewedAt int64
MsgCount int64
MentionCount int64
UrgentMentionCount int64
NotifyProps model.StringMap
LastUpdateAt int64
SchemeUser sql.NullBool
@@ -65,6 +66,7 @@ func NewMapFromChannelMemberModel(cm *model.ChannelMember) map[string]any {
"MsgCount": cm.MsgCount,
"MentionCount": cm.MentionCount,
"MentionCountRoot": cm.MentionCountRoot,
"UrgentMentionCount": cm.UrgentMentionCount,
"MsgCountRoot": cm.MsgCountRoot,
"NotifyProps": cm.NotifyProps,
"LastUpdateAt": cm.LastUpdateAt,
@@ -82,6 +84,7 @@ type channelMemberWithSchemeRoles struct {
MsgCount int64
MentionCount int64
MentionCountRoot int64
UrgentMentionCount int64
NotifyProps model.StringMap
LastUpdateAt int64
SchemeGuest sql.NullBool
@@ -106,7 +109,7 @@ type channelMemberWithTeamWithSchemeRoles struct {
type channelMemberWithTeamWithSchemeRolesList []channelMemberWithTeamWithSchemeRoles
func channelMemberSliceColumns() []string {
return []string{"ChannelId", "UserId", "Roles", "LastViewedAt", "MsgCount", "MsgCountRoot", "MentionCount", "MentionCountRoot", "NotifyProps", "LastUpdateAt", "SchemeUser", "SchemeAdmin", "SchemeGuest"}
return []string{"ChannelId", "UserId", "Roles", "LastViewedAt", "MsgCount", "MsgCountRoot", "MentionCount", "MentionCountRoot", "UrgentMentionCount", "NotifyProps", "LastUpdateAt", "SchemeUser", "SchemeAdmin", "SchemeGuest"}
}
func channelMemberToSlice(member *model.ChannelMember) []any {
@@ -119,6 +122,7 @@ func channelMemberToSlice(member *model.ChannelMember) []any {
resultSlice = append(resultSlice, member.MsgCountRoot)
resultSlice = append(resultSlice, member.MentionCount)
resultSlice = append(resultSlice, member.MentionCountRoot)
resultSlice = append(resultSlice, member.UrgentMentionCount)
resultSlice = append(resultSlice, model.MapToJSON(member.NotifyProps))
resultSlice = append(resultSlice, member.LastUpdateAt)
resultSlice = append(resultSlice, member.SchemeUser)
@@ -252,6 +256,7 @@ func (db channelMemberWithSchemeRoles) ToModel() *model.ChannelMember {
MsgCountRoot: db.MsgCountRoot,
MentionCount: db.MentionCount,
MentionCountRoot: db.MentionCountRoot,
UrgentMentionCount: db.UrgentMentionCount,
NotifyProps: db.NotifyProps,
LastUpdateAt: db.LastUpdateAt,
SchemeAdmin: rolesResult.schemeAdmin,
@@ -315,6 +320,7 @@ func (db channelMemberWithTeamWithSchemeRoles) ToModel() *model.ChannelMemberWit
MsgCountRoot: db.MsgCountRoot,
MentionCount: db.MentionCount,
MentionCountRoot: db.MentionCountRoot,
UrgentMentionCount: db.UrgentMentionCount,
NotifyProps: db.NotifyProps,
LastUpdateAt: db.LastUpdateAt,
SchemeAdmin: rolesResult.schemeAdmin,
@@ -471,7 +477,20 @@ func newSqlChannelStore(sqlStore *SqlStore, metrics einterfaces.MetricsInterface
func (s *SqlChannelStore) initializeQueries() {
s.channelMembersForTeamWithSchemeSelectQuery = s.getQueryBuilder().
Select(
"ChannelMembers.*",
"ChannelMembers.ChannelId",
"ChannelMembers.UserId",
"ChannelMembers.Roles",
"ChannelMembers.LastViewedAt",
"ChannelMembers.MsgCount",
"ChannelMembers.MentionCount",
"ChannelMembers.MentionCountRoot",
"COALESCE(ChannelMembers.UrgentMentionCount, 0) AS UrgentMentionCount",
"ChannelMembers.MsgCountRoot",
"ChannelMembers.NotifyProps",
"ChannelMembers.LastUpdateAt",
"ChannelMembers.SchemeUser",
"ChannelMembers.SchemeAdmin",
"ChannelMembers.SchemeGuest",
"TeamScheme.DefaultChannelGuestRole TeamSchemeDefaultGuestRole",
"TeamScheme.DefaultChannelUserRole TeamSchemeDefaultUserRole",
"TeamScheme.DefaultChannelAdminRole TeamSchemeDefaultAdminRole",
@@ -779,7 +798,7 @@ func (s SqlChannelStore) GetChannelUnread(channelId, userId string) (*model.Chan
var unreadChannel model.ChannelUnread
err := s.GetReplicaX().Get(&unreadChannel,
`SELECT
Channels.TeamId TeamId, Channels.Id ChannelId, (Channels.TotalMsgCount - ChannelMembers.MsgCount) MsgCount, (Channels.TotalMsgCountRoot - ChannelMembers.MsgCountRoot) MsgCountRoot, ChannelMembers.MentionCount MentionCount, ChannelMembers.MentionCountRoot MentionCountRoot, ChannelMembers.NotifyProps NotifyProps
Channels.TeamId TeamId, Channels.Id ChannelId, (Channels.TotalMsgCount - ChannelMembers.MsgCount) MsgCount, (Channels.TotalMsgCountRoot - ChannelMembers.MsgCountRoot) MsgCountRoot, ChannelMembers.MentionCount MentionCount, ChannelMembers.MentionCountRoot MentionCountRoot, COALESCE(ChannelMembers.UrgentMentionCount, 0) UrgentMentionCount, ChannelMembers.NotifyProps NotifyProps
FROM
Channels, ChannelMembers
WHERE
@@ -1612,7 +1631,20 @@ func (s SqlChannelStore) GetDeleted(teamId string, offset int, limit int, userId
var channelMembersWithSchemeSelectQuery = `
SELECT
ChannelMembers.*,
ChannelMembers.ChannelId,
ChannelMembers.UserId,
ChannelMembers.Roles,
ChannelMembers.LastViewedAt,
ChannelMembers.MsgCount,
ChannelMembers.MentionCount,
ChannelMembers.MentionCountRoot,
COALESCE(ChannelMembers.UrgentMentionCount, 0) AS UrgentMentionCount,
ChannelMembers.MsgCountRoot,
ChannelMembers.NotifyProps,
ChannelMembers.LastUpdateAt,
ChannelMembers.SchemeUser,
ChannelMembers.SchemeAdmin,
ChannelMembers.SchemeGuest,
COALESCE(Teams.DisplayName, '') TeamDisplayName,
COALESCE(Teams.Name, '') TeamName,
COALESCE(Teams.UpdateAt, 0) TeamUpdateAt,
@@ -2048,7 +2080,20 @@ func (s SqlChannelStore) GetMemberForPost(postId string, userId string) (*model.
var dbMember channelMemberWithSchemeRoles
query := `
SELECT
ChannelMembers.*,
ChannelMembers.ChannelId,
ChannelMembers.UserId,
ChannelMembers.Roles,
ChannelMembers.LastViewedAt,
ChannelMembers.MsgCount,
ChannelMembers.MentionCount,
ChannelMembers.MentionCountRoot,
COALESCE(ChannelMembers.UrgentMentionCount, 0) AS UrgentMentionCount,
ChannelMembers.MsgCountRoot,
ChannelMembers.NotifyProps,
ChannelMembers.LastUpdateAt,
ChannelMembers.SchemeUser,
ChannelMembers.SchemeAdmin,
ChannelMembers.SchemeGuest,
TeamScheme.DefaultChannelGuestRole TeamSchemeDefaultGuestRole,
TeamScheme.DefaultChannelUserRole TeamSchemeDefaultUserRole,
TeamScheme.DefaultChannelAdminRole TeamSchemeDefaultAdminRole,
@@ -2438,6 +2483,7 @@ func (s SqlChannelStore) UpdateLastViewedAt(channelIds []string, userId string)
Update("ChannelMembers cm").
Set("MentionCount", 0).
Set("MentionCountRoot", 0).
Set("UrgentMentionCount", 0).
Set("MsgCount", sq.Expr("greatest(cm.MsgCount, c.TotalMsgCount)")).
Set("MsgCountRoot", sq.Expr("greatest(cm.MsgCountRoot, c.TotalMsgCountRoot)")).
Set("LastViewedAt", sq.Expr("greatest(cm.LastViewedAt, c.LastPostAt)")).
@@ -2497,6 +2543,7 @@ func (s SqlChannelStore) UpdateLastViewedAt(channelIds []string, userId string)
updateQuery := s.getQueryBuilder().Update("ChannelMembers").
Set("MentionCount", 0).
Set("MentionCountRoot", 0).
Set("UrgentMentionCount", 0).
Set("MsgCount", msgCountQuery).
Set("MsgCountRoot", msgCountQueryRoot).
Set("LastViewedAt", lastViewedQuery).
@@ -2518,6 +2565,31 @@ func (s SqlChannelStore) UpdateLastViewedAt(channelIds []string, userId string)
return times, nil
}
func (s SqlChannelStore) CountUrgentPostsAfter(channelId string, timestamp int64, userId string) (int, error) {
query := s.getQueryBuilder().
Select("count(*)").
From("PostsPriority").
Join("Posts ON Posts.Id = PostsPriority.PostId").
Where(sq.And{
sq.Eq{"PostsPriority.Priority": model.PostPriorityUrgent},
sq.Eq{"Posts.ChannelId": channelId},
sq.Gt{"Posts.CreateAt": timestamp},
sq.Eq{"Posts.DeleteAt": 0},
})
if userId != "" {
query = query.Where(sq.Eq{"Posts.UserId": userId})
}
var urgent int64
err := s.GetReplicaX().GetBuilder(&urgent, query)
if err != nil {
return 0, errors.Wrap(err, "failed to count urgent Posts")
}
return int(urgent), nil
}
// CountPostsAfter returns the number of posts in the given channel created after but not including the given timestamp. If given a non-empty user ID, only counts posts made by that user.
func (s SqlChannelStore) CountPostsAfter(channelId string, timestamp int64, userId string) (int, int, error) {
joinLeavePostTypes := []string{
@@ -2566,13 +2638,14 @@ func (s SqlChannelStore) CountPostsAfter(channelId string, timestamp int64, user
if err != nil {
return 0, 0, errors.Wrap(err, "failed to count root Posts")
}
return int(unread), int(unreadRoot), nil
}
// UpdateLastViewedAtPost updates a ChannelMember as if the user last read the channel at the time of the given post.
// If the provided mentionCount is -1, the given post and all posts after it are considered to be mentions. Returns
// an updated model.ChannelUnreadAt that can be returned to the client.
func (s SqlChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount, mentionCountRoot int, setUnreadCountRoot bool) (*model.ChannelUnreadAt, error) {
func (s SqlChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount, mentionCountRoot, urgentMentionCount int, setUnreadCountRoot bool) (*model.ChannelUnreadAt, error) {
unreadDate := unreadPost.CreateAt - 1
unread, unreadRoot, err := s.CountPostsAfter(unreadPost.ChannelId, unreadDate, "")
@@ -2587,6 +2660,7 @@ func (s SqlChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID s
params := map[string]any{
"mentions": mentionCount,
"mentionsroot": mentionCountRoot,
"urgentmentions": urgentMentionCount,
"unreadcount": unread,
"unreadcountroot": unreadRoot,
"lastviewedat": unreadDate,
@@ -2603,6 +2677,7 @@ func (s SqlChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID s
SET
MentionCount = :mentions,
MentionCountRoot = :mentionsroot,
UrgentMentionCount = :urgentmentions,
MsgCount = (SELECT TotalMsgCount FROM Channels WHERE ID = :channelid) - :unreadcount,
MsgCountRoot = (SELECT TotalMsgCountRoot FROM Channels WHERE ID = :channelid) - :unreadcountroot,
LastViewedAt = :lastviewedat,
@@ -2625,6 +2700,7 @@ func (s SqlChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID s
cm.MsgCountRoot MsgCountRoot,
cm.MentionCount MentionCount,
cm.MentionCountRoot MentionCountRoot,
COALESCE(cm.UrgentMentionCount, 0) UrgentMentionCount,
cm.LastViewedAt LastViewedAt,
cm.NotifyProps NotifyProps
FROM
@@ -2643,7 +2719,7 @@ func (s SqlChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID s
return result, nil
}
func (s SqlChannelStore) IncrementMentionCount(channelId string, userIDs []string, isRoot bool) error {
func (s SqlChannelStore) IncrementMentionCount(channelId string, userIDs []string, isRoot bool, isUrgent bool) error {
now := model.GetMillis()
rootInc := 0
@@ -2651,10 +2727,16 @@ func (s SqlChannelStore) IncrementMentionCount(channelId string, userIDs []strin
rootInc = 1
}
urgentInc := 0
if isUrgent {
urgentInc = 1
}
sql, args, err := s.getQueryBuilder().
Update("ChannelMembers").
Set("MentionCount", sq.Expr("MentionCount + 1")).
Set("MentionCountRoot", sq.Expr("MentionCountRoot + ?", rootInc)).
Set("UrgentMentionCount", sq.Expr("UrgentMentionCount + ?", urgentInc)).
Set("LastUpdateAt", now).
Where(sq.Eq{
"UserId": userIDs,
@@ -2832,7 +2914,21 @@ func (s SqlChannelStore) GetMembersForUser(teamID string, userID string) (model.
func (s SqlChannelStore) GetMembersForUserWithCursor(userID, teamID string, opts *store.ChannelMemberGraphQLSearchOpts) (model.ChannelMembers, error) {
query := s.getQueryBuilder().
Select("ChannelMembers.*",
Select(
"ChannelMembers.ChannelId",
"ChannelMembers.UserId",
"ChannelMembers.Roles",
"ChannelMembers.LastViewedAt",
"ChannelMembers.MsgCount",
"ChannelMembers.MentionCount",
"ChannelMembers.MentionCountRoot",
"COALESCE(ChannelMembers.UrgentMentionCount, 0) AS UrgentMentionCount",
"ChannelMembers.MsgCountRoot",
"ChannelMembers.NotifyProps",
"ChannelMembers.LastUpdateAt",
"ChannelMembers.SchemeUser",
"ChannelMembers.SchemeAdmin",
"ChannelMembers.SchemeGuest",
"TeamScheme.DefaultChannelGuestRole TeamSchemeDefaultGuestRole",
"TeamScheme.DefaultChannelUserRole TeamSchemeDefaultUserRole",
"TeamScheme.DefaultChannelAdminRole TeamSchemeDefaultAdminRole",
@@ -3790,6 +3886,7 @@ func (s SqlChannelStore) MigrateChannelMembers(fromChannelId string, fromUserId
LastViewedAt=:LastViewedAt,
MsgCount=:MsgCount,
MentionCount=:MentionCount,
UrgentMentionCount=:UrgentMentionCount,
NotifyProps=:NotifyProps,
LastUpdateAt=:LastUpdateAt,
SchemeUser=:SchemeUser,
@@ -3932,6 +4029,7 @@ func (s SqlChannelStore) GetChannelMembersForExport(userId string, teamId string
ChannelMembers.MsgCount,
ChannelMembers.MentionCount,
ChannelMembers.MentionCountRoot,
COALESCE(ChannelMembers.UrgentMentionCount, 0) AS UrgentMentionCount,
ChannelMembers.NotifyProps,
ChannelMembers.LastUpdateAt,
ChannelMembers.SchemeUser,
@@ -3981,7 +4079,7 @@ func (s SqlChannelStore) GetAllDirectChannelsForExportAfter(limit int, afterId s
channelIds = append(channelIds, channel.Id)
}
query = s.getQueryBuilder().
Select("u.Username as Username, ChannelId, UserId, cm.Roles as Roles, LastViewedAt, MsgCount, MentionCount, MentionCountRoot, cm.NotifyProps as NotifyProps, LastUpdateAt, SchemeUser, SchemeAdmin, (SchemeGuest IS NOT NULL AND SchemeGuest) as SchemeGuest").
Select("u.Username as Username, ChannelId, UserId, cm.Roles as Roles, LastViewedAt, MsgCount, MentionCount, MentionCountRoot, COALESCE(UrgentMentionCount, 0) UrgentMentionCount, cm.NotifyProps as NotifyProps, LastUpdateAt, SchemeUser, SchemeAdmin, (SchemeGuest IS NOT NULL AND SchemeGuest) as SchemeGuest").
From("ChannelMembers cm").
Join("Users u ON ( u.Id = cm.UserId )").
Where(sq.And{

63
store/sqlstore/post_priority_store.go Обычный файл
Просмотреть файл

@@ -0,0 +1,63 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package sqlstore
import (
"github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/store"
sq "github.com/mattermost/squirrel"
)
type SqlPostPriorityStore struct {
*SqlStore
}
func newSqlPostPriorityStore(sqlStore *SqlStore) store.PostPriorityStore {
return &SqlPostPriorityStore{
SqlStore: sqlStore,
}
}
func (s *SqlPostPriorityStore) GetForPost(postId string) (*model.PostPriority, error) {
query := s.getQueryBuilder().
Select("Priority", "RequestedAck", "PersistentNotifications").
From("PostsPriority").
Where(sq.Eq{"PostId": postId})
var postPriority model.PostPriority
err := s.GetReplicaX().GetBuilder(&postPriority, query)
if err != nil {
return nil, err
}
return &postPriority, nil
}
func (s *SqlPostPriorityStore) GetForPosts(postIds []string) ([]*model.PostPriority, error) {
var priority []*model.PostPriority
perPage := 200
for i := 0; i < len(postIds); i += perPage {
j := i + perPage
if len(postIds) < j {
j = len(postIds)
}
query := s.getQueryBuilder().
Select("PostId", "Priority", "RequestedAck", "PersistentNotifications").
From("PostsPriority").
Where(sq.Eq{"PostId": postIds[i:j]})
var priorityBatch []*model.PostPriority
err := s.GetReplicaX().SelectBuilder(&priority, query)
if err != nil {
return nil, err
}
priority = append(priority, priorityBatch...)
}
return priority, nil
}

14
store/sqlstore/post_priority_store_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package sqlstore
import (
"testing"
"github.com/mattermost/mattermost-server/v6/store/storetest"
)
func TestPostPriorityStore(t *testing.T) {
StoreTestWithSqlStore(t, storetest.TestPostPriorityStore)
}

Просмотреть файл

@@ -219,6 +219,10 @@ func (s *SqlPostStore) SaveMultiple(posts []*model.Post) ([]*model.Post, int, er
return nil, -1, errors.Wrap(err, "update thread from posts failed")
}
if err = s.savePostsPriority(transaction, posts); err != nil {
return nil, -1, errors.Wrap(err, "failed to save PostPriority")
}
if err = transaction.Commit(); err != nil {
// don't need to rollback here since the transaction is already closed
return posts, -1, errors.Wrap(err, "commit_transaction")
@@ -2920,6 +2924,24 @@ func (s *SqlPostStore) updateThreadAfterReplyDeletion(transaction *sqlxTxWrapper
return nil
}
func (s *SqlPostStore) savePostsPriority(transaction *sqlxTxWrapper, posts []*model.Post) error {
for _, post := range posts {
if post.GetPriority() != nil {
postPriority := &model.PostPriority{
PostId: post.Id,
ChannelId: post.ChannelId,
Priority: post.Metadata.Priority.Priority,
RequestedAck: post.Metadata.Priority.RequestedAck,
PersistentNotifications: post.Metadata.Priority.PersistentNotifications,
}
if _, err := transaction.NamedExec(`INSERT INTO PostsPriority (PostId, ChannelId, Priority, RequestedAck, PersistentNotifications) VALUES (:PostId, :ChannelId, :Priority, :RequestedAck, :PersistentNotifications)`, postPriority); err != nil {
return err
}
}
}
return nil
}
func (s *SqlPostStore) updateThreadsFromPosts(transaction *sqlxTxWrapper, posts []*model.Post) error {
postsByRoot := map[string][]*model.Post{}
var rootIds []string

Просмотреть файл

@@ -109,6 +109,7 @@ type SqlStoreStores struct {
linkMetadata store.LinkMetadataStore
sharedchannel store.SharedChannelStore
notifyAdmin store.NotifyAdminStore
postPriority store.PostPriorityStore
}
type SqlStore struct {
@@ -214,6 +215,7 @@ func New(settings model.SqlSettings, metrics einterfaces.MetricsInterface) *SqlS
store.stores.group = newSqlGroupStore(store)
store.stores.productNotices = newSqlProductNoticesStore(store)
store.stores.notifyAdmin = newSqlNotifyAdminStore(store)
store.stores.postPriority = newSqlPostPriorityStore(store)
store.stores.preference.(*SqlPreferenceStore).deleteUnusedFeatures()
@@ -955,6 +957,10 @@ func (ss *SqlStore) SharedChannel() store.SharedChannelStore {
return ss.stores.sharedchannel
}
func (ss *SqlStore) PostPriority() store.PostPriorityStore {
return ss.stores.postPriority
}
func (ss *SqlStore) DropAllTables() {
if ss.DriverName() == model.DatabaseDriverPostgres {
ss.masterX.Exec(`DO

Просмотреть файл

@@ -7,11 +7,11 @@ import (
"context"
"database/sql"
"strconv"
"sync"
"time"
sq "github.com/mattermost/squirrel"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
"github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/store"
@@ -30,6 +30,7 @@ type JoinedThread struct {
Participants model.StringArray
ThreadDeleteAt int64
TeamId string
IsUrgent bool
model.Post
}
@@ -51,6 +52,7 @@ func (thread *JoinedThread) toThreadResponse(users map[string]*model.User) *mode
Participants: threadParticipants,
Post: thread.Post.ToNilIfInvalid(),
DeleteAt: thread.ThreadDeleteAt,
IsUrgent: thread.IsUrgent,
}
}
@@ -213,6 +215,46 @@ func (s *SqlThreadStore) GetTotalUnreadMentions(userId, teamId string, opts mode
return totalUnreadMentions, nil
}
// GetTotalUnreadUrgentMentions counts the number of unread mentions for the given user, optionally
// constrained to the given team + DMs/GMs.
func (s *SqlThreadStore) GetTotalUnreadUrgentMentions(userId, teamId string, opts model.GetUserThreadsOpts) (int64, error) {
var totalUnreadUrgentMentions int64
query := s.getQueryBuilder().
Select("COALESCE(SUM(ThreadMemberships.UnreadMentions),0)").
From("ThreadMemberships").
Join("PostsPriority ON PostsPriority.PostId = ThreadMemberships.PostId").
Where(sq.Eq{
"ThreadMemberships.UserId": userId,
"ThreadMemberships.Following": true,
"PostsPriority.Priority": model.PostPriorityUrgent,
})
if teamId != "" || !opts.Deleted {
query = query.Join("Threads ON Threads.PostId = ThreadMemberships.PostId")
}
if teamId != "" {
query = query.
Where(sq.Or{
sq.Eq{"Threads.ThreadTeamId": teamId},
sq.Eq{"Threads.ThreadTeamId": ""},
})
}
if !opts.Deleted {
query = query.
Where(sq.Eq{"COALESCE(Threads.ThreadDeleteAt, 0)": 0})
}
err := s.GetReplicaX().GetBuilder(&totalUnreadUrgentMentions, query)
if err != nil {
return 0, errors.Wrapf(err, "failed to count unread urgent mentions for user id=%s", userId)
}
return totalUnreadUrgentMentions, nil
}
func (s *SqlThreadStore) GetThreadsForUser(userId, teamId string, opts model.GetUserThreadsOpts) ([]*model.ThreadResponse, error) {
pageSize := uint64(30)
if opts.PageSize != 0 {
@@ -243,6 +285,17 @@ func (s *SqlThreadStore) GetThreadsForUser(userId, teamId string, opts model.Get
Where(sq.Eq{"ThreadMemberships.UserId": userId}).
Where(sq.Eq{"ThreadMemberships.Following": true})
if opts.IncludeIsUrgent {
urgencyCase := sq.
Case().
When(sq.Eq{"PostsPriority.Priority": model.PostPriorityUrgent}, "true").
Else("false")
query = query.
Column(sq.Alias(urgencyCase, "IsUrgent")).
LeftJoin("PostsPriority ON PostsPriority.PostId = Threads.PostId")
}
// If a team is specified, constrain to channels in that team or DMs/GMs without
// a team at all.
if teamId != "" {
@@ -322,7 +375,7 @@ func (s *SqlThreadStore) GetThreadsForUser(userId, teamId string, opts model.Get
// GetTeamsUnreadForUser returns the total unread threads and unread mentions
// for a user from all teams.
func (s *SqlThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string) (map[string]*model.TeamUnread, error) {
func (s *SqlThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string, includeUrgentMentionCount bool) (map[string]*model.TeamUnread, error) {
fetchConditions := sq.And{
sq.Eq{"ThreadMemberships.UserId": userID},
sq.Eq{"ThreadMemberships.Following": true},
@@ -330,8 +383,7 @@ func (s *SqlThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string)
sq.Eq{"COALESCE(Threads.ThreadDeleteAt, 0)": 0},
}
var wg sync.WaitGroup
var err1, err2 error
var eg errgroup.Group
unreadThreads := []struct {
Count int64
@@ -341,13 +393,15 @@ func (s *SqlThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string)
Count int64
TeamId string
}{}
unreadUrgentMentions := []struct {
Count int64
TeamId string
}{}
// Running these concurrently hasn't shown any major downside
// than running them serially. So using a bit of perf boost.
// In any case, they will be replaced by computed columns later.
wg.Add(1)
go func() {
defer wg.Done()
eg.Go(func() error {
repliesQuery := s.getQueryBuilder().
Select("COUNT(Threads.PostId) AS Count, ThreadTeamId AS TeamId").
From("Threads").
@@ -356,15 +410,10 @@ func (s *SqlThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string)
Where("Threads.LastReplyAt > ThreadMemberships.LastViewed").
GroupBy("Threads.ThreadTeamId")
err := s.GetReplicaX().SelectBuilder(&unreadThreads, repliesQuery)
if err != nil {
err1 = errors.Wrap(err, "failed to get total unread threads")
}
}()
return errors.Wrap(s.GetReplicaX().SelectBuilder(&unreadThreads, repliesQuery), "failed to get total unread threads")
})
wg.Add(1)
go func() {
defer wg.Done()
eg.Go(func() error {
mentionsQuery := s.getQueryBuilder().
Select("COALESCE(SUM(ThreadMemberships.UnreadMentions),0) AS Count, ThreadTeamId AS TeamId").
From("ThreadMemberships").
@@ -372,20 +421,27 @@ func (s *SqlThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string)
Where(fetchConditions).
GroupBy("Threads.ThreadTeamId")
err := s.GetReplicaX().SelectBuilder(&unreadMentions, mentionsQuery)
if err != nil {
err2 = errors.Wrap(err, "failed to get total unread mentions")
return errors.Wrap(s.GetReplicaX().SelectBuilder(&unreadMentions, mentionsQuery), "failed to get total unread mentions")
})
if includeUrgentMentionCount {
eg.Go(func() error {
urgentMentionsQuery := s.getQueryBuilder().
Select("COALESCE(SUM(ThreadMemberships.UnreadMentions),0) AS Count, ThreadTeamId AS TeamId").
From("ThreadMemberships").
LeftJoin("Threads ON Threads.PostId = ThreadMemberships.PostId").
Join("PostsPriority ON PostsPriority.PostId = ThreadMemberships.PostId").
Where(sq.Eq{"PostsPriority.Priority": model.PostPriorityUrgent}).
Where(fetchConditions).
GroupBy("Threads.ThreadTeamId")
return errors.Wrap(s.GetReplicaX().SelectBuilder(&unreadUrgentMentions, urgentMentionsQuery), "failed to get total unread urgent mentions")
})
}
}()
// Wait for them to be over
wg.Wait()
if err1 != nil {
return nil, err1
}
if err2 != nil {
return nil, err2
if err := eg.Wait(); err != nil {
return nil, err
}
res := make(map[string]*model.TeamUnread)
@@ -405,6 +461,15 @@ func (s *SqlThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string)
}
}
}
for _, item := range unreadUrgentMentions {
if _, ok := res[item.TeamId]; ok {
res[item.TeamId].ThreadUrgentMentionCount = item.Count
} else {
res[item.TeamId] = &model.TeamUnread{
ThreadUrgentMentionCount: item.Count,
}
}
}
return res, nil
}
@@ -436,7 +501,7 @@ func (s *SqlThreadStore) GetThreadFollowers(threadID string, fetchOnlyActive boo
return users, nil
}
func (s *SqlThreadStore) GetThreadForUser(threadMembership *model.ThreadMembership, extended bool) (*model.ThreadResponse, error) {
func (s *SqlThreadStore) GetThreadForUser(threadMembership *model.ThreadMembership, extended, postPriorityEnabled bool) (*model.ThreadResponse, error) {
if !threadMembership.Following {
return nil, nil // in case the thread is not followed anymore - return nil error to be interpreted as 404
}
@@ -462,6 +527,17 @@ func (s *SqlThreadStore) GetThreadForUser(threadMembership *model.ThreadMembersh
LeftJoin("Posts ON Posts.Id = Threads.PostId").
Where(sq.Eq{"Threads.PostId": threadMembership.PostId})
if postPriorityEnabled {
urgencyCase := sq.
Case().
When(sq.Eq{"PostsPriority.Priority": model.PostPriorityUrgent}, "true").
Else("false")
query = query.
Column(sq.Alias(urgencyCase, "IsUrgent")).
LeftJoin("PostsPriority ON PostsPriority.PostId = Threads.PostId")
}
err := s.GetReplicaX().GetBuilder(&thread, query)
if err != nil {
if err == sql.ErrNoRows {

Просмотреть файл

@@ -84,6 +84,7 @@ type Store interface {
SetContext(context context.Context)
Context() context.Context
NotifyAdmin() NotifyAdminStore
PostPriority() PostPriorityStore
}
type RetentionPolicyStore interface {
@@ -240,9 +241,10 @@ type ChannelStore interface {
PermanentDeleteMembersByUser(userID string) error
PermanentDeleteMembersByChannel(channelID string) error
UpdateLastViewedAt(channelIds []string, userID string) (map[string]int64, error)
UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount, mentionCountRoot int, setUnreadCountRoot bool) (*model.ChannelUnreadAt, error)
UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount, mentionCountRoot, urgentMentionCount int, setUnreadCountRoot bool) (*model.ChannelUnreadAt, error)
CountPostsAfter(channelID string, timestamp int64, userID string) (int, int, error)
IncrementMentionCount(channelID string, userIDs []string, isRoot bool) error
CountUrgentPostsAfter(channelID string, timestamp int64, userID string) (int, error)
IncrementMentionCount(channelID string, userIDs []string, isRoot, isUrgent bool) error
AnalyticsTypeCount(teamID string, channelType model.ChannelType) (int64, error)
GetMembersForUser(teamID string, userID string) (model.ChannelMembers, error)
GetTeamMembersForChannel(channelID string) ([]string, error)
@@ -322,9 +324,10 @@ type ThreadStore interface {
GetTotalUnreadThreads(userId, teamID string, opts model.GetUserThreadsOpts) (int64, error)
GetTotalThreads(userId, teamID string, opts model.GetUserThreadsOpts) (int64, error)
GetTotalUnreadMentions(userId, teamID string, opts model.GetUserThreadsOpts) (int64, error)
GetTotalUnreadUrgentMentions(userId, teamID string, opts model.GetUserThreadsOpts) (int64, error)
GetThreadsForUser(userId, teamID string, opts model.GetUserThreadsOpts) ([]*model.ThreadResponse, error)
GetThreadForUser(threadMembership *model.ThreadMembership, extended bool) (*model.ThreadResponse, error)
GetTeamsUnreadForUser(userID string, teamIDs []string) (map[string]*model.TeamUnread, error)
GetThreadForUser(threadMembership *model.ThreadMembership, extended, postPriorityIsEnabled bool) (*model.ThreadResponse, error)
GetTeamsUnreadForUser(userID string, teamIDs []string, includeUrgentMentionCount bool) (map[string]*model.TeamUnread, error)
MarkAllAsRead(userID string, threadIds []string) error
MarkAllAsReadByTeam(userID, teamID string) error
@@ -970,6 +973,11 @@ type SharedChannelStore interface {
UpdateAttachmentLastSyncAt(id string, syncTime int64) error
}
type PostPriorityStore interface {
GetForPost(postId string) (*model.PostPriority, error)
GetForPosts(ids []string) ([]*model.PostPriority, error)
}
// ChannelSearchOpts contains options for searching channels.
//
// NotAssociatedToGroup will exclude channels that have associated, active GroupChannels records.

Просмотреть файл

@@ -104,6 +104,7 @@ func TestChannelStore(t *testing.T, ss store.Store, s SqlStore) {
t.Run("GetMembersForUserWithCursor", func(t *testing.T) { testChannelStoreGetMembersForUserWithCursor(t, ss) })
t.Run("GetMembersForUserWithPagination", func(t *testing.T) { testChannelStoreGetMembersForUserWithPagination(t, ss) })
t.Run("CountPostsAfter", func(t *testing.T) { testCountPostsAfter(t, ss) })
t.Run("CountUrgentPostsAfter", func(t *testing.T) { testCountUrgentPostsAfter(t, ss) })
t.Run("UpdateLastViewedAt", func(t *testing.T) { testChannelStoreUpdateLastViewedAt(t, ss) })
t.Run("IncrementMentionCount", func(t *testing.T) { testChannelStoreIncrementMentionCount(t, ss) })
t.Run("UpdateChannelMember", func(t *testing.T) { testUpdateChannelMember(t, ss) })
@@ -4833,6 +4834,66 @@ func testCountPostsAfter(t *testing.T, ss store.Store) {
})
}
func testCountUrgentPostsAfter(t *testing.T, ss store.Store) {
t.Run("should count all posts with or without the given user ID", func(t *testing.T) {
userId1 := model.NewId()
userId2 := model.NewId()
channelId := model.NewId()
p1, err := ss.Post().Save(&model.Post{
UserId: userId1,
ChannelId: channelId,
CreateAt: 1000,
Metadata: &model.PostMetadata{
Priority: &model.PostPriority{
Priority: model.NewString(model.PostPriorityUrgent),
RequestedAck: model.NewBool(false),
PersistentNotifications: model.NewBool(false),
},
},
})
require.NoError(t, err)
_, err = ss.Post().Save(&model.Post{
UserId: userId1,
ChannelId: channelId,
CreateAt: 1001,
Metadata: &model.PostMetadata{
Priority: &model.PostPriority{
Priority: model.NewString("important"),
RequestedAck: model.NewBool(false),
PersistentNotifications: model.NewBool(false),
},
},
})
require.NoError(t, err)
_, err = ss.Post().Save(&model.Post{
UserId: userId2,
ChannelId: channelId,
CreateAt: 1002,
})
require.NoError(t, err)
count, err := ss.Channel().CountUrgentPostsAfter(channelId, p1.CreateAt-1, "")
require.NoError(t, err)
assert.Equal(t, 1, count)
count, err = ss.Channel().CountUrgentPostsAfter(channelId, p1.CreateAt, "")
require.NoError(t, err)
assert.Equal(t, 0, count)
count, err = ss.Channel().CountUrgentPostsAfter(channelId, p1.CreateAt-1, userId1)
require.NoError(t, err)
assert.Equal(t, 1, count)
count, err = ss.Channel().CountUrgentPostsAfter(channelId, p1.CreateAt, userId1)
require.NoError(t, err)
assert.Equal(t, 0, count)
})
}
func testChannelStoreUpdateLastViewedAt(t *testing.T, ss store.Store) {
o1 := model.Channel{}
o1.TeamId = model.NewId()
@@ -4912,16 +4973,16 @@ func testChannelStoreIncrementMentionCount(t *testing.T, ss store.Store) {
_, err := ss.Channel().SaveMember(&m1)
require.NoError(t, err)
err = ss.Channel().IncrementMentionCount(m1.ChannelId, []string{m1.UserId}, false)
err = ss.Channel().IncrementMentionCount(m1.ChannelId, []string{m1.UserId}, false, false)
require.NoError(t, err, "failed to update")
err = ss.Channel().IncrementMentionCount(m1.ChannelId, []string{"missing id"}, false)
err = ss.Channel().IncrementMentionCount(m1.ChannelId, []string{"missing id"}, false, false)
require.NoError(t, err, "failed to update")
err = ss.Channel().IncrementMentionCount("missing id", []string{m1.UserId}, false)
err = ss.Channel().IncrementMentionCount("missing id", []string{m1.UserId}, false, false)
require.NoError(t, err, "failed to update")
err = ss.Channel().IncrementMentionCount("missing id", []string{"missing id"}, false)
err = ss.Channel().IncrementMentionCount("missing id", []string{"missing id"}, false, false)
require.NoError(t, err, "failed to update")
}

Просмотреть файл

@@ -192,6 +192,27 @@ func (_m *ChannelStore) CountPostsAfter(channelID string, timestamp int64, userI
return r0, r1, r2
}
// CountUrgentPostsAfter provides a mock function with given fields: channelID, timestamp, userID
func (_m *ChannelStore) CountUrgentPostsAfter(channelID string, timestamp int64, userID string) (int, error) {
ret := _m.Called(channelID, timestamp, userID)
var r0 int
if rf, ok := ret.Get(0).(func(string, int64, string) int); ok {
r0 = rf(channelID, timestamp, userID)
} else {
r0 = ret.Get(0).(int)
}
var r1 error
if rf, ok := ret.Get(1).(func(string, int64, string) error); ok {
r1 = rf(channelID, timestamp, userID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CreateDirectChannel provides a mock function with given fields: userID, otherUserID, channelOptions
func (_m *ChannelStore) CreateDirectChannel(userID *model.User, otherUserID *model.User, channelOptions ...model.ChannelOption) (*model.Channel, error) {
_va := make([]interface{}, len(channelOptions))
@@ -1646,13 +1667,13 @@ func (_m *ChannelStore) GroupSyncedChannelCount() (int64, error) {
return r0, r1
}
// IncrementMentionCount provides a mock function with given fields: channelID, userIDs, isRoot
func (_m *ChannelStore) IncrementMentionCount(channelID string, userIDs []string, isRoot bool) error {
ret := _m.Called(channelID, userIDs, isRoot)
// IncrementMentionCount provides a mock function with given fields: channelID, userIDs, isRoot, isUrgent
func (_m *ChannelStore) IncrementMentionCount(channelID string, userIDs []string, isRoot bool, isUrgent bool) error {
ret := _m.Called(channelID, userIDs, isRoot, isUrgent)
var r0 error
if rf, ok := ret.Get(0).(func(string, []string, bool) error); ok {
r0 = rf(channelID, userIDs, isRoot)
if rf, ok := ret.Get(0).(func(string, []string, bool, bool) error); ok {
r0 = rf(channelID, userIDs, isRoot, isUrgent)
} else {
r0 = ret.Error(0)
}
@@ -2192,13 +2213,13 @@ func (_m *ChannelStore) UpdateLastViewedAt(channelIds []string, userID string) (
return r0, r1
}
// UpdateLastViewedAtPost provides a mock function with given fields: unreadPost, userID, mentionCount, mentionCountRoot, setUnreadCountRoot
func (_m *ChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount int, mentionCountRoot int, setUnreadCountRoot bool) (*model.ChannelUnreadAt, error) {
ret := _m.Called(unreadPost, userID, mentionCount, mentionCountRoot, setUnreadCountRoot)
// UpdateLastViewedAtPost provides a mock function with given fields: unreadPost, userID, mentionCount, mentionCountRoot, urgentMentionCount, setUnreadCountRoot
func (_m *ChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount int, mentionCountRoot int, urgentMentionCount int, setUnreadCountRoot bool) (*model.ChannelUnreadAt, error) {
ret := _m.Called(unreadPost, userID, mentionCount, mentionCountRoot, urgentMentionCount, setUnreadCountRoot)
var r0 *model.ChannelUnreadAt
if rf, ok := ret.Get(0).(func(*model.Post, string, int, int, bool) *model.ChannelUnreadAt); ok {
r0 = rf(unreadPost, userID, mentionCount, mentionCountRoot, setUnreadCountRoot)
if rf, ok := ret.Get(0).(func(*model.Post, string, int, int, int, bool) *model.ChannelUnreadAt); ok {
r0 = rf(unreadPost, userID, mentionCount, mentionCountRoot, urgentMentionCount, setUnreadCountRoot)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.ChannelUnreadAt)
@@ -2206,8 +2227,8 @@ func (_m *ChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID st
}
var r1 error
if rf, ok := ret.Get(1).(func(*model.Post, string, int, int, bool) error); ok {
r1 = rf(unreadPost, userID, mentionCount, mentionCountRoot, setUnreadCountRoot)
if rf, ok := ret.Get(1).(func(*model.Post, string, int, int, int, bool) error); ok {
r1 = rf(unreadPost, userID, mentionCount, mentionCountRoot, urgentMentionCount, setUnreadCountRoot)
} else {
r1 = ret.Error(1)
}

61
store/storetest/mocks/PostPriorityStore.go Обычный файл
Просмотреть файл

@@ -0,0 +1,61 @@
// Code generated by mockery v2.10.4. DO NOT EDIT.
// Regenerate this file using `make store-mocks`.
package mocks
import (
model "github.com/mattermost/mattermost-server/v6/model"
mock "github.com/stretchr/testify/mock"
)
// PostPriorityStore is an autogenerated mock type for the PostPriorityStore type
type PostPriorityStore struct {
mock.Mock
}
// GetForPost provides a mock function with given fields: postId
func (_m *PostPriorityStore) GetForPost(postId string) (*model.PostPriority, error) {
ret := _m.Called(postId)
var r0 *model.PostPriority
if rf, ok := ret.Get(0).(func(string) *model.PostPriority); ok {
r0 = rf(postId)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.PostPriority)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(postId)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetForPosts provides a mock function with given fields: ids
func (_m *PostPriorityStore) GetForPosts(ids []string) ([]*model.PostPriority, error) {
ret := _m.Called(ids)
var r0 []*model.PostPriority
if rf, ok := ret.Get(0).(func([]string) []*model.PostPriority); ok {
r0 = rf(ids)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.PostPriority)
}
}
var r1 error
if rf, ok := ret.Get(1).(func([]string) error); ok {
r1 = rf(ids)
} else {
r1 = ret.Error(1)
}
return r0, r1
}

Просмотреть файл

@@ -475,6 +475,22 @@ func (_m *Store) Post() store.PostStore {
return r0
}
// PostPriority provides a mock function with given fields:
func (_m *Store) PostPriority() store.PostPriorityStore {
ret := _m.Called()
var r0 store.PostPriorityStore
if rf, ok := ret.Get(0).(func() store.PostPriorityStore); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.PostPriorityStore)
}
}
return r0
}
// Preference provides a mock function with given fields:
func (_m *Store) Preference() store.PreferenceStore {
ret := _m.Called()

Просмотреть файл

@@ -119,13 +119,13 @@ func (_m *ThreadStore) GetMembershipsForUser(userId string, teamID string) ([]*m
return r0, r1
}
// GetTeamsUnreadForUser provides a mock function with given fields: userID, teamIDs
func (_m *ThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string) (map[string]*model.TeamUnread, error) {
ret := _m.Called(userID, teamIDs)
// GetTeamsUnreadForUser provides a mock function with given fields: userID, teamIDs, includeUrgentMentionCount
func (_m *ThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string, includeUrgentMentionCount bool) (map[string]*model.TeamUnread, error) {
ret := _m.Called(userID, teamIDs, includeUrgentMentionCount)
var r0 map[string]*model.TeamUnread
if rf, ok := ret.Get(0).(func(string, []string) map[string]*model.TeamUnread); ok {
r0 = rf(userID, teamIDs)
if rf, ok := ret.Get(0).(func(string, []string, bool) map[string]*model.TeamUnread); ok {
r0 = rf(userID, teamIDs, includeUrgentMentionCount)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(map[string]*model.TeamUnread)
@@ -133,8 +133,8 @@ func (_m *ThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string) (m
}
var r1 error
if rf, ok := ret.Get(1).(func(string, []string) error); ok {
r1 = rf(userID, teamIDs)
if rf, ok := ret.Get(1).(func(string, []string, bool) error); ok {
r1 = rf(userID, teamIDs, includeUrgentMentionCount)
} else {
r1 = ret.Error(1)
}
@@ -165,13 +165,13 @@ func (_m *ThreadStore) GetThreadFollowers(threadID string, fetchOnlyActive bool)
return r0, r1
}
// GetThreadForUser provides a mock function with given fields: threadMembership, extended
func (_m *ThreadStore) GetThreadForUser(threadMembership *model.ThreadMembership, extended bool) (*model.ThreadResponse, error) {
ret := _m.Called(threadMembership, extended)
// GetThreadForUser provides a mock function with given fields: threadMembership, extended, postPriorityIsEnabled
func (_m *ThreadStore) GetThreadForUser(threadMembership *model.ThreadMembership, extended bool, postPriorityIsEnabled bool) (*model.ThreadResponse, error) {
ret := _m.Called(threadMembership, extended, postPriorityIsEnabled)
var r0 *model.ThreadResponse
if rf, ok := ret.Get(0).(func(*model.ThreadMembership, bool) *model.ThreadResponse); ok {
r0 = rf(threadMembership, extended)
if rf, ok := ret.Get(0).(func(*model.ThreadMembership, bool, bool) *model.ThreadResponse); ok {
r0 = rf(threadMembership, extended, postPriorityIsEnabled)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.ThreadResponse)
@@ -179,8 +179,8 @@ func (_m *ThreadStore) GetThreadForUser(threadMembership *model.ThreadMembership
}
var r1 error
if rf, ok := ret.Get(1).(func(*model.ThreadMembership, bool) error); ok {
r1 = rf(threadMembership, extended)
if rf, ok := ret.Get(1).(func(*model.ThreadMembership, bool, bool) error); ok {
r1 = rf(threadMembership, extended, postPriorityIsEnabled)
} else {
r1 = ret.Error(1)
}
@@ -341,6 +341,27 @@ func (_m *ThreadStore) GetTotalUnreadThreads(userId string, teamID string, opts
return r0, r1
}
// GetTotalUnreadUrgentMentions provides a mock function with given fields: userId, teamID, opts
func (_m *ThreadStore) GetTotalUnreadUrgentMentions(userId string, teamID string, opts model.GetUserThreadsOpts) (int64, error) {
ret := _m.Called(userId, teamID, opts)
var r0 int64
if rf, ok := ret.Get(0).(func(string, string, model.GetUserThreadsOpts) int64); ok {
r0 = rf(userId, teamID, opts)
} else {
r0 = ret.Get(0).(int64)
}
var r1 error
if rf, ok := ret.Get(1).(func(string, string, model.GetUserThreadsOpts) error); ok {
r1 = rf(userId, teamID, opts)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MaintainMembership provides a mock function with given fields: userID, postID, opts
func (_m *ThreadStore) MaintainMembership(userID string, postID string, opts store.ThreadMembershipOpts) (*model.ThreadMembership, error) {
ret := _m.Called(userID, postID, opts)

72
store/storetest/post_priority_store.go Обычный файл
Просмотреть файл

@@ -0,0 +1,72 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package storetest
import (
"database/sql"
"errors"
"testing"
"github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/store"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPostPriorityStore(t *testing.T, ss store.Store, s SqlStore) {
t.Run("GetForPost", func(t *testing.T) { testPostPriorityStoreGetForPost(t, ss) })
}
func testPostPriorityStoreGetForPost(t *testing.T, ss store.Store) {
t.Run("Save post priority when in post's metadata", func(t *testing.T) {
p1 := model.Post{}
p1.ChannelId = model.NewId()
p1.UserId = model.NewId()
p1.Message = NewTestId()
p1.Metadata = &model.PostMetadata{
Priority: &model.PostPriority{
Priority: model.NewString("important"),
RequestedAck: model.NewBool(true),
PersistentNotifications: model.NewBool(false),
},
}
p2 := model.Post{}
p2.ChannelId = model.NewId()
p2.UserId = model.NewId()
p2.Message = NewTestId()
p2.Metadata = &model.PostMetadata{
Priority: &model.PostPriority{
Priority: model.NewString(model.PostPriorityUrgent),
RequestedAck: model.NewBool(false),
PersistentNotifications: model.NewBool(true),
},
}
p3 := model.Post{}
p3.ChannelId = model.NewId()
p3.UserId = model.NewId()
p3.Message = NewTestId()
_, errIdx, err := ss.Post().SaveMultiple([]*model.Post{&p1, &p2, &p3})
require.NoError(t, err)
require.Equal(t, -1, errIdx)
pp1, err := ss.PostPriority().GetForPost(p1.Id)
require.NoError(t, err)
assert.Equal(t, "important", *pp1.Priority)
assert.Equal(t, true, *pp1.RequestedAck)
assert.Equal(t, false, *pp1.PersistentNotifications)
pp2, err := ss.PostPriority().GetForPost(p2.Id)
require.NoError(t, err)
assert.Equal(t, model.PostPriorityUrgent, *pp2.Priority)
assert.Equal(t, false, *pp2.RequestedAck)
assert.Equal(t, true, *pp2.PersistentNotifications)
_, err = ss.PostPriority().GetForPost(p3.Id)
assert.True(t, errors.Is(err, sql.ErrNoRows))
})
}

Просмотреть файл

@@ -238,6 +238,31 @@ func testPostStoreSave(t *testing.T, ss store.Store) {
assert.Greater(t, rchannel3.LastPostAt, rchannel2.LastPostAt)
assert.Equal(t, int64(3), rchannel3.TotalMsgCount)
})
t.Run("Save post with priority metadata set", func(t *testing.T) {
o1 := model.Post{}
o1.ChannelId = model.NewId()
o1.UserId = model.NewId()
o1.Message = NewTestId()
o1.Metadata = &model.PostMetadata{
Priority: &model.PostPriority{
Priority: model.NewString("important"),
RequestedAck: model.NewBool(true),
PersistentNotifications: model.NewBool(false),
},
}
p, err := ss.Post().Save(&o1)
require.NoError(t, err, "couldn't save item")
assert.Equal(t, int64(0), p.ReplyCount)
pp, err := ss.PostPriority().GetForPost(p.Id)
require.NoError(t, err, "couldn't save item")
assert.Equal(t, "important", *pp.Priority)
assert.Equal(t, true, *pp.RequestedAck)
assert.Equal(t, false, *pp.PersistentNotifications)
})
}
func testPostStoreSaveMultiple(t *testing.T, ss store.Store) {

Просмотреть файл

@@ -56,6 +56,7 @@ type Store struct {
ProductNoticesStore mocks.ProductNoticesStore
context context.Context
NotifyAdminStore mocks.NotifyAdminStore
PostPriorityStore mocks.PostPriorityStore
}
func (s *Store) SetContext(context context.Context) { s.context = context }
@@ -100,6 +101,7 @@ func (s *Store) NotifyAdmin() store.NotifyAdminStore { return &s.NotifyAdmin
func (s *Store) Group() store.GroupStore { return &s.GroupStore }
func (s *Store) LinkMetadata() store.LinkMetadataStore { return &s.LinkMetadataStore }
func (s *Store) SharedChannel() store.SharedChannelStore { return &s.SharedChannelStore }
func (s *Store) PostPriority() store.PostPriorityStore { return &s.PostPriorityStore }
func (s *Store) MarkSystemRanUnitTests() { /* do nothing */ }
func (s *Store) Close() { /* do nothing */ }
func (s *Store) LockToMaster() { /* do nothing */ }
@@ -158,5 +160,6 @@ func (s *Store) AssertExpectations(t mock.TestingT) bool {
&s.ProductNoticesStore,
&s.SharedChannelStore,
&s.NotifyAdminStore,
&s.PostPriorityStore,
)
}

Просмотреть файл

@@ -32,7 +32,7 @@ func TestThreadStore(t *testing.T, ss store.Store, s SqlStore) {
}
func testThreadStorePopulation(t *testing.T, ss store.Store) {
makeSomePosts := func() []*model.Post {
makeSomePosts := func(urgent bool) []*model.Post {
u1 := model.User{
Email: MakeEmail(),
@@ -61,6 +61,16 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
o.UserId = u.Id
o.Message = NewTestId()
if urgent {
o.Metadata = &model.PostMetadata{
Priority: &model.PostPriority{
Priority: model.NewString(model.PostPriorityUrgent),
RequestedAck: model.NewBool(false),
PersistentNotifications: model.NewBool(false),
},
}
}
otmp, err3 := ss.Post().Save(&o)
require.NoError(t, err3)
o2 := model.Post{}
@@ -100,7 +110,7 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
return newPosts
}
t.Run("Save replies creates a thread", func(t *testing.T) {
newPosts := makeSomePosts()
newPosts := makeSomePosts(false)
thread, err := ss.Thread().Get(newPosts[0].Id)
require.NoError(t, err, "couldn't get thread")
require.NotNil(t, thread)
@@ -133,7 +143,7 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
})
t.Run("Delete a reply updates count on a thread", func(t *testing.T) {
newPosts := makeSomePosts()
newPosts := makeSomePosts(false)
thread, err := ss.Thread().Get(newPosts[0].Id)
require.NoError(t, err, "couldn't get thread")
require.NotNil(t, thread)
@@ -307,7 +317,7 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
})
t.Run("Thread membership 'viewed' timestamp is updated properly", func(t *testing.T) {
newPosts := makeSomePosts()
newPosts := makeSomePosts(false)
opts := store.ThreadMembershipOpts{
Following: true,
@@ -341,7 +351,7 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
})
t.Run("Thread membership 'viewed' timestamp is updated properly for new membership", func(t *testing.T) {
newPosts := makeSomePosts()
newPosts := makeSomePosts(false)
opts := store.ThreadMembershipOpts{
Following: true,
@@ -356,7 +366,7 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
})
t.Run("Updating post does not make thread unread", func(t *testing.T) {
newPosts := makeSomePosts()
newPosts := makeSomePosts(false)
opts := store.ThreadMembershipOpts{
Following: true,
IncrementMentions: false,
@@ -366,14 +376,14 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
}
m, err := ss.Thread().MaintainMembership(newPosts[0].UserId, newPosts[0].Id, opts)
require.NoError(t, err)
th, err := ss.Thread().GetThreadForUser(m, false)
th, err := ss.Thread().GetThreadForUser(m, false, false)
require.NoError(t, err)
require.Equal(t, int64(2), th.UnreadReplies)
m.LastViewed = newPosts[2].UpdateAt + 1
_, err = ss.Thread().UpdateMembership(m)
require.NoError(t, err)
th, err = ss.Thread().GetThreadForUser(m, false)
th, err = ss.Thread().GetThreadForUser(m, false, false)
require.NoError(t, err)
require.Equal(t, int64(0), th.UnreadReplies)
@@ -382,13 +392,13 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
_, err = ss.Post().Update(editedPost, newPosts[2])
require.NoError(t, err)
th, err = ss.Thread().GetThreadForUser(m, false)
th, err = ss.Thread().GetThreadForUser(m, false, false)
require.NoError(t, err)
require.Equal(t, int64(0), th.UnreadReplies)
})
t.Run("Empty participantID should not appear in thread response", func(t *testing.T) {
newPosts := makeSomePosts()
newPosts := makeSomePosts(false)
opts := store.ThreadMembershipOpts{
Following: true,
IncrementMentions: false,
@@ -399,7 +409,7 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
m, err := ss.Thread().MaintainMembership("", newPosts[0].Id, opts)
require.NoError(t, err)
m.UserId = newPosts[0].UserId
th, err := ss.Thread().GetThreadForUser(m, true)
th, err := ss.Thread().GetThreadForUser(m, true, false)
require.NoError(t, err)
for _, user := range th.Participants {
require.NotNil(t, user)
@@ -407,7 +417,7 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
})
t.Run("Get unread reply counts for thread", func(t *testing.T) {
t.Skip("MM-41797")
newPosts := makeSomePosts()
newPosts := makeSomePosts(false)
opts := store.ThreadMembershipOpts{
Following: true,
IncrementMentions: false,
@@ -435,6 +445,36 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
require.NoError(t, err)
require.Equal(t, int64(2), unreads)
})
testCases := []bool{true, false}
for _, isUrgent := range testCases {
t.Run("Return is urgent for user thread/s", func(t *testing.T) {
newPosts := makeSomePosts(isUrgent)
opts := store.ThreadMembershipOpts{
Following: true,
IncrementMentions: false,
UpdateFollowing: true,
UpdateViewedTimestamp: true,
UpdateParticipants: false,
}
userID := newPosts[0].UserId
_, e := ss.Thread().MaintainMembership(userID, newPosts[0].Id, opts)
require.NoError(t, e)
m, e := ss.Thread().GetMembershipForUser(userID, newPosts[0].Id)
require.NoError(t, e)
th, e := ss.Thread().GetThreadForUser(m, false, true)
require.NoError(t, e)
require.Equal(t, isUrgent, th.IsUrgent)
threads, e := ss.Thread().GetThreadsForUser(userID, "", model.GetUserThreadsOpts{IncludeIsUrgent: true})
require.NoError(t, e)
require.Equal(t, isUrgent, threads[0].IsUrgent)
})
}
}
func threadStoreCreateReply(t *testing.T, ss store.Store, channelID, postID, userID string, createAt int64) *model.Post {
@@ -660,7 +700,7 @@ func testGetTeamsUnreadForUser(t *testing.T, ss store.Store) {
threadStoreCreateReply(t, ss, channel1.Id, post.Id, post.UserId, model.GetMillis())
createThreadMembership(userID, post.Id)
teamsUnread, err := ss.Thread().GetTeamsUnreadForUser(userID, []string{team1.Id})
teamsUnread, err := ss.Thread().GetTeamsUnreadForUser(userID, []string{team1.Id}, true)
require.NoError(t, err)
assert.Len(t, teamsUnread, 1)
assert.Equal(t, int64(1), teamsUnread[team1.Id].ThreadCount)
@@ -674,7 +714,7 @@ func testGetTeamsUnreadForUser(t *testing.T, ss store.Store) {
threadStoreCreateReply(t, ss, channel1.Id, post.Id, post.UserId, model.GetMillis())
createThreadMembership(userID, post.Id)
teamsUnread, err = ss.Thread().GetTeamsUnreadForUser(userID, []string{team1.Id})
teamsUnread, err = ss.Thread().GetTeamsUnreadForUser(userID, []string{team1.Id}, true)
require.NoError(t, err)
assert.Len(t, teamsUnread, 1)
assert.Equal(t, int64(2), teamsUnread[team1.Id].ThreadCount)
@@ -693,16 +733,24 @@ func testGetTeamsUnreadForUser(t *testing.T, ss store.Store) {
Type: model.ChannelTypeOpen,
}, -1)
require.NoError(t, err)
post2, err := ss.Post().Save(&model.Post{
ChannelId: channel2.Id,
UserId: userID,
Message: model.NewRandomString(10),
Metadata: &model.PostMetadata{
Priority: &model.PostPriority{
Priority: model.NewString(model.PostPriorityUrgent),
RequestedAck: model.NewBool(false),
PersistentNotifications: model.NewBool(false),
},
},
})
require.NoError(t, err)
threadStoreCreateReply(t, ss, channel2.Id, post2.Id, post2.UserId, model.GetMillis())
createThreadMembership(userID, post2.Id)
teamsUnread, err = ss.Thread().GetTeamsUnreadForUser(userID, []string{team1.Id, team2.Id})
teamsUnread, err = ss.Thread().GetTeamsUnreadForUser(userID, []string{team1.Id, team2.Id}, true)
require.NoError(t, err)
assert.Len(t, teamsUnread, 2)
assert.Equal(t, int64(2), teamsUnread[team1.Id].ThreadCount)
@@ -715,11 +763,12 @@ func testGetTeamsUnreadForUser(t *testing.T, ss store.Store) {
_, err = ss.Thread().MaintainMembership(userID, post2.Id, opts)
require.NoError(t, err)
teamsUnread, err = ss.Thread().GetTeamsUnreadForUser(userID, []string{team2.Id})
teamsUnread, err = ss.Thread().GetTeamsUnreadForUser(userID, []string{team2.Id}, true)
require.NoError(t, err)
assert.Len(t, teamsUnread, 1)
assert.Equal(t, int64(1), teamsUnread[team2.Id].ThreadCount)
assert.Equal(t, int64(1), teamsUnread[team2.Id].ThreadMentionCount)
assert.Equal(t, int64(1), teamsUnread[team2.Id].ThreadUrgentMentionCount)
}
type byPostId []*model.Post
@@ -831,6 +880,13 @@ func testVarious(t *testing.T, ss store.Store) {
ChannelId: team1channel1.Id,
UserId: user1ID,
Message: model.NewRandomString(10),
Metadata: &model.PostMetadata{
Priority: &model.PostPriority{
Priority: model.NewString(model.PostPriorityUrgent),
RequestedAck: model.NewBool(false),
PersistentNotifications: model.NewBool(false),
},
},
})
require.NoError(t, err)
@@ -1032,6 +1088,33 @@ func testVarious(t *testing.T, ss store.Store) {
}
})
t.Run("GetTotalUnreadUrgentMentions", func(t *testing.T) {
testCases := []struct {
Description string
UserID string
TeamID string
Options model.GetUserThreadsOpts
ExpectedThreads []*model.Post
}{
{"all teams, user1", user1ID, "", model.GetUserThreadsOpts{}, []*model.Post{
team1channel1post3,
}},
{"team1, user1", user1ID, team1.Id, model.GetUserThreadsOpts{}, []*model.Post{
team1channel1post3,
}},
{"team2, user1", user1ID, team2.Id, model.GetUserThreadsOpts{}, []*model.Post{}},
}
for _, testCase := range testCases {
t.Run(testCase.Description, func(t *testing.T) {
totalUnreadUrgentMentions, err := ss.Thread().GetTotalUnreadUrgentMentions(testCase.UserID, testCase.TeamID, testCase.Options)
require.NoError(t, err)
assert.EqualValues(t, int64(len(testCase.ExpectedThreads)), totalUnreadUrgentMentions)
})
}
})
assertThreadPosts := func(t *testing.T, threads []*model.ThreadResponse, expectedPosts []*model.Post) {
t.Helper()
@@ -1166,7 +1249,7 @@ func testMarkAllAsReadByChannels(t *testing.T, ss store.Store) {
assertThreadReplyCount := func(t *testing.T, userID string, count int64) {
t.Helper()
teamsUnread, err := ss.Thread().GetTeamsUnreadForUser(userID, []string{team1.Id})
teamsUnread, err := ss.Thread().GetTeamsUnreadForUser(userID, []string{team1.Id}, false)
require.NoError(t, err)
require.Len(t, teamsUnread, 1, "unexpected unread teams count")
assert.Equal(t, count, teamsUnread[team1.Id].ThreadCount, "unexpected thread count")
@@ -1623,7 +1706,7 @@ func testMarkAllAsReadByTeam(t *testing.T, ss store.Store) {
assertThreadReplyCount := func(t *testing.T, userID, teamID string, count int64, message string) {
t.Helper()
teamsUnread, err := ss.Thread().GetTeamsUnreadForUser(userID, []string{teamID})
teamsUnread, err := ss.Thread().GetTeamsUnreadForUser(userID, []string{teamID}, true)
require.NoError(t, err)
require.Lenf(t, teamsUnread, 1, "unexpected unread teams count: %s", message)
assert.Equalf(t, count, teamsUnread[teamID].ThreadCount, "unexpected thread count: %s", message)

Просмотреть файл

@@ -2468,7 +2468,7 @@ func testUserUnreadCount(t *testing.T, ss store.Store) {
// Post one message with mention to open channel
_, nErr = ss.Post().Save(&p1)
require.NoError(t, nErr)
nErr = ss.Channel().IncrementMentionCount(c1.Id, []string{u2.Id, u3.Id}, false)
nErr = ss.Channel().IncrementMentionCount(c1.Id, []string{u2.Id, u3.Id}, false, false)
require.NoError(t, nErr)
// Post 2 messages without mention to direct channel
@@ -2479,7 +2479,7 @@ func testUserUnreadCount(t *testing.T, ss store.Store) {
_, nErr = ss.Post().Save(&p2)
require.NoError(t, nErr)
nErr = ss.Channel().IncrementMentionCount(c2.Id, []string{u2.Id}, false)
nErr = ss.Channel().IncrementMentionCount(c2.Id, []string{u2.Id}, false, false)
require.NoError(t, nErr)
p3 := model.Post{}
@@ -2489,7 +2489,7 @@ func testUserUnreadCount(t *testing.T, ss store.Store) {
_, nErr = ss.Post().Save(&p3)
require.NoError(t, nErr)
nErr = ss.Channel().IncrementMentionCount(c2.Id, []string{u2.Id}, false)
nErr = ss.Channel().IncrementMentionCount(c2.Id, []string{u2.Id}, false, false)
require.NoError(t, nErr)
badge, unreadCountErr := ss.User().GetUnreadCount(u2.Id, false)
@@ -2501,7 +2501,7 @@ func testUserUnreadCount(t *testing.T, ss store.Store) {
require.Equal(t, int64(1), badge, "should have 1 unread message")
// Increment root mentions by 1
nErr = ss.Channel().IncrementMentionCount(c1.Id, []string{u3.Id}, true)
nErr = ss.Channel().IncrementMentionCount(c1.Id, []string{u3.Id}, true, false)
require.NoError(t, nErr)
// CRT is enabled, only root mentions are counted

Просмотреть файл

@@ -36,6 +36,7 @@ type TimerLayer struct {
OAuthStore store.OAuthStore
PluginStore store.PluginStore
PostStore store.PostStore
PostPriorityStore store.PostPriorityStore
PreferenceStore store.PreferenceStore
ProductNoticesStore store.ProductNoticesStore
ReactionStore store.ReactionStore
@@ -130,6 +131,10 @@ func (s *TimerLayer) Post() store.PostStore {
return s.PostStore
}
func (s *TimerLayer) PostPriority() store.PostPriorityStore {
return s.PostPriorityStore
}
func (s *TimerLayer) Preference() store.PreferenceStore {
return s.PreferenceStore
}
@@ -300,6 +305,11 @@ type TimerLayerPostStore struct {
Root *TimerLayer
}
type TimerLayerPostPriorityStore struct {
store.PostPriorityStore
Root *TimerLayer
}
type TimerLayerPreferenceStore struct {
store.PreferenceStore
Root *TimerLayer
@@ -671,6 +681,22 @@ func (s *TimerLayerChannelStore) CountPostsAfter(channelID string, timestamp int
return result, resultVar1, err
}
func (s *TimerLayerChannelStore) CountUrgentPostsAfter(channelID string, timestamp int64, userID string) (int, error) {
start := time.Now()
result, err := s.ChannelStore.CountUrgentPostsAfter(channelID, timestamp, userID)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("ChannelStore.CountUrgentPostsAfter", success, elapsed)
}
return result, err
}
func (s *TimerLayerChannelStore) CreateDirectChannel(userID *model.User, otherUserID *model.User, channelOptions ...model.ChannelOption) (*model.Channel, error) {
start := time.Now()
@@ -1711,10 +1737,10 @@ func (s *TimerLayerChannelStore) GroupSyncedChannelCount() (int64, error) {
return result, err
}
func (s *TimerLayerChannelStore) IncrementMentionCount(channelID string, userIDs []string, isRoot bool) error {
func (s *TimerLayerChannelStore) IncrementMentionCount(channelID string, userIDs []string, isRoot bool, isUrgent bool) error {
start := time.Now()
err := s.ChannelStore.IncrementMentionCount(channelID, userIDs, isRoot)
err := s.ChannelStore.IncrementMentionCount(channelID, userIDs, isRoot, isUrgent)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {
@@ -2248,10 +2274,10 @@ func (s *TimerLayerChannelStore) UpdateLastViewedAt(channelIds []string, userID
return result, err
}
func (s *TimerLayerChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount int, mentionCountRoot int, setUnreadCountRoot bool) (*model.ChannelUnreadAt, error) {
func (s *TimerLayerChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount int, mentionCountRoot int, urgentMentionCount int, setUnreadCountRoot bool) (*model.ChannelUnreadAt, error) {
start := time.Now()
result, err := s.ChannelStore.UpdateLastViewedAtPost(unreadPost, userID, mentionCount, mentionCountRoot, setUnreadCountRoot)
result, err := s.ChannelStore.UpdateLastViewedAtPost(unreadPost, userID, mentionCount, mentionCountRoot, urgentMentionCount, setUnreadCountRoot)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {
@@ -5891,6 +5917,38 @@ func (s *TimerLayerPostStore) Update(newPost *model.Post, oldPost *model.Post) (
return result, err
}
func (s *TimerLayerPostPriorityStore) GetForPost(postId string) (*model.PostPriority, error) {
start := time.Now()
result, err := s.PostPriorityStore.GetForPost(postId)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("PostPriorityStore.GetForPost", success, elapsed)
}
return result, err
}
func (s *TimerLayerPostPriorityStore) GetForPosts(ids []string) ([]*model.PostPriority, error) {
start := time.Now()
result, err := s.PostPriorityStore.GetForPosts(ids)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("PostPriorityStore.GetForPosts", success, elapsed)
}
return result, err
}
func (s *TimerLayerPreferenceStore) CleanupFlagsBatch(limit int64) (int64, error) {
start := time.Now()
@@ -8881,10 +8939,10 @@ func (s *TimerLayerThreadStore) GetMembershipsForUser(userId string, teamID stri
return result, err
}
func (s *TimerLayerThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string) (map[string]*model.TeamUnread, error) {
func (s *TimerLayerThreadStore) GetTeamsUnreadForUser(userID string, teamIDs []string, includeUrgentMentionCount bool) (map[string]*model.TeamUnread, error) {
start := time.Now()
result, err := s.ThreadStore.GetTeamsUnreadForUser(userID, teamIDs)
result, err := s.ThreadStore.GetTeamsUnreadForUser(userID, teamIDs, includeUrgentMentionCount)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {
@@ -8913,10 +8971,10 @@ func (s *TimerLayerThreadStore) GetThreadFollowers(threadID string, fetchOnlyAct
return result, err
}
func (s *TimerLayerThreadStore) GetThreadForUser(threadMembership *model.ThreadMembership, extended bool) (*model.ThreadResponse, error) {
func (s *TimerLayerThreadStore) GetThreadForUser(threadMembership *model.ThreadMembership, extended bool, postPriorityIsEnabled bool) (*model.ThreadResponse, error) {
start := time.Now()
result, err := s.ThreadStore.GetThreadForUser(threadMembership, extended)
result, err := s.ThreadStore.GetThreadForUser(threadMembership, extended, postPriorityIsEnabled)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {
@@ -9041,6 +9099,22 @@ func (s *TimerLayerThreadStore) GetTotalUnreadThreads(userId string, teamID stri
return result, err
}
func (s *TimerLayerThreadStore) GetTotalUnreadUrgentMentions(userId string, teamID string, opts model.GetUserThreadsOpts) (int64, error) {
start := time.Now()
result, err := s.ThreadStore.GetTotalUnreadUrgentMentions(userId, teamID, opts)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("ThreadStore.GetTotalUnreadUrgentMentions", success, elapsed)
}
return result, err
}
func (s *TimerLayerThreadStore) MaintainMembership(userID string, postID string, opts store.ThreadMembershipOpts) (*model.ThreadMembership, error) {
start := time.Now()
@@ -11270,6 +11344,7 @@ func New(childStore store.Store, metrics einterfaces.MetricsInterface) *TimerLay
newStore.OAuthStore = &TimerLayerOAuthStore{OAuthStore: childStore.OAuth(), Root: &newStore}
newStore.PluginStore = &TimerLayerPluginStore{PluginStore: childStore.Plugin(), Root: &newStore}
newStore.PostStore = &TimerLayerPostStore{PostStore: childStore.Post(), Root: &newStore}
newStore.PostPriorityStore = &TimerLayerPostPriorityStore{PostPriorityStore: childStore.PostPriority(), Root: &newStore}
newStore.PreferenceStore = &TimerLayerPreferenceStore{PreferenceStore: childStore.Preference(), Root: &newStore}
newStore.ProductNoticesStore = &TimerLayerProductNoticesStore{ProductNoticesStore: childStore.ProductNotices(), Root: &newStore}
newStore.ReactionStore = &TimerLayerReactionStore{ReactionStore: childStore.Reaction(), Root: &newStore}