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 удалений

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

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

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

@@ -1741,13 +1741,14 @@ func (a *App) GetTeamsUnreadForUser(excludeTeamId string, userID string, include
} else {
teamIDs = append(teamIDs, id)
membersMap[id] = unreads(data[i], &model.TeamUnread{
MsgCount: 0,
MentionCount: 0,
MentionCountRoot: 0,
MsgCountRoot: 0,
ThreadCount: 0,
ThreadMentionCount: 0,
TeamId: id,
MsgCount: 0,
MentionCount: 0,
MentionCountRoot: 0,
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