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>
Этот коммит содержится в:
@@ -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 {
|
||||
|
||||
Ссылка в новой задаче
Block a user