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>
Этот коммит содержится в:
@@ -41,36 +41,38 @@ type SqlChannelStore struct {
|
||||
}
|
||||
|
||||
type channelMember struct {
|
||||
ChannelId string
|
||||
UserId string
|
||||
Roles string
|
||||
LastViewedAt int64
|
||||
MsgCount int64
|
||||
MentionCount int64
|
||||
NotifyProps model.StringMap
|
||||
LastUpdateAt int64
|
||||
SchemeUser sql.NullBool
|
||||
SchemeAdmin sql.NullBool
|
||||
SchemeGuest sql.NullBool
|
||||
MentionCountRoot int64
|
||||
MsgCountRoot int64
|
||||
ChannelId string
|
||||
UserId string
|
||||
Roles string
|
||||
LastViewedAt int64
|
||||
MsgCount int64
|
||||
MentionCount int64
|
||||
UrgentMentionCount int64
|
||||
NotifyProps model.StringMap
|
||||
LastUpdateAt int64
|
||||
SchemeUser sql.NullBool
|
||||
SchemeAdmin sql.NullBool
|
||||
SchemeGuest sql.NullBool
|
||||
MentionCountRoot int64
|
||||
MsgCountRoot int64
|
||||
}
|
||||
|
||||
func NewMapFromChannelMemberModel(cm *model.ChannelMember) map[string]any {
|
||||
return map[string]any{
|
||||
"ChannelId": cm.ChannelId,
|
||||
"UserId": cm.UserId,
|
||||
"Roles": cm.ExplicitRoles,
|
||||
"LastViewedAt": cm.LastViewedAt,
|
||||
"MsgCount": cm.MsgCount,
|
||||
"MentionCount": cm.MentionCount,
|
||||
"MentionCountRoot": cm.MentionCountRoot,
|
||||
"MsgCountRoot": cm.MsgCountRoot,
|
||||
"NotifyProps": cm.NotifyProps,
|
||||
"LastUpdateAt": cm.LastUpdateAt,
|
||||
"SchemeGuest": sql.NullBool{Valid: true, Bool: cm.SchemeGuest},
|
||||
"SchemeUser": sql.NullBool{Valid: true, Bool: cm.SchemeUser},
|
||||
"SchemeAdmin": sql.NullBool{Valid: true, Bool: cm.SchemeAdmin},
|
||||
"ChannelId": cm.ChannelId,
|
||||
"UserId": cm.UserId,
|
||||
"Roles": cm.ExplicitRoles,
|
||||
"LastViewedAt": cm.LastViewedAt,
|
||||
"MsgCount": cm.MsgCount,
|
||||
"MentionCount": cm.MentionCount,
|
||||
"MentionCountRoot": cm.MentionCountRoot,
|
||||
"UrgentMentionCount": cm.UrgentMentionCount,
|
||||
"MsgCountRoot": cm.MsgCountRoot,
|
||||
"NotifyProps": cm.NotifyProps,
|
||||
"LastUpdateAt": cm.LastUpdateAt,
|
||||
"SchemeGuest": sql.NullBool{Valid: true, Bool: cm.SchemeGuest},
|
||||
"SchemeUser": sql.NullBool{Valid: true, Bool: cm.SchemeUser},
|
||||
"SchemeAdmin": sql.NullBool{Valid: true, Bool: cm.SchemeAdmin},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -244,20 +248,21 @@ func (db channelMemberWithSchemeRoles) ToModel() *model.ChannelMember {
|
||||
strings.Fields(db.Roles),
|
||||
)
|
||||
return &model.ChannelMember{
|
||||
ChannelId: db.ChannelId,
|
||||
UserId: db.UserId,
|
||||
Roles: strings.Join(rolesResult.roles, " "),
|
||||
LastViewedAt: db.LastViewedAt,
|
||||
MsgCount: db.MsgCount,
|
||||
MsgCountRoot: db.MsgCountRoot,
|
||||
MentionCount: db.MentionCount,
|
||||
MentionCountRoot: db.MentionCountRoot,
|
||||
NotifyProps: db.NotifyProps,
|
||||
LastUpdateAt: db.LastUpdateAt,
|
||||
SchemeAdmin: rolesResult.schemeAdmin,
|
||||
SchemeUser: rolesResult.schemeUser,
|
||||
SchemeGuest: rolesResult.schemeGuest,
|
||||
ExplicitRoles: strings.Join(rolesResult.explicitRoles, " "),
|
||||
ChannelId: db.ChannelId,
|
||||
UserId: db.UserId,
|
||||
Roles: strings.Join(rolesResult.roles, " "),
|
||||
LastViewedAt: db.LastViewedAt,
|
||||
MsgCount: db.MsgCount,
|
||||
MsgCountRoot: db.MsgCountRoot,
|
||||
MentionCount: db.MentionCount,
|
||||
MentionCountRoot: db.MentionCountRoot,
|
||||
UrgentMentionCount: db.UrgentMentionCount,
|
||||
NotifyProps: db.NotifyProps,
|
||||
LastUpdateAt: db.LastUpdateAt,
|
||||
SchemeAdmin: rolesResult.schemeAdmin,
|
||||
SchemeUser: rolesResult.schemeUser,
|
||||
SchemeGuest: rolesResult.schemeGuest,
|
||||
ExplicitRoles: strings.Join(rolesResult.explicitRoles, " "),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,20 +312,21 @@ func (db channelMemberWithTeamWithSchemeRoles) ToModel() *model.ChannelMemberWit
|
||||
)
|
||||
return &model.ChannelMemberWithTeamData{
|
||||
ChannelMember: model.ChannelMember{
|
||||
ChannelId: db.ChannelId,
|
||||
UserId: db.UserId,
|
||||
Roles: strings.Join(rolesResult.roles, " "),
|
||||
LastViewedAt: db.LastViewedAt,
|
||||
MsgCount: db.MsgCount,
|
||||
MsgCountRoot: db.MsgCountRoot,
|
||||
MentionCount: db.MentionCount,
|
||||
MentionCountRoot: db.MentionCountRoot,
|
||||
NotifyProps: db.NotifyProps,
|
||||
LastUpdateAt: db.LastUpdateAt,
|
||||
SchemeAdmin: rolesResult.schemeAdmin,
|
||||
SchemeUser: rolesResult.schemeUser,
|
||||
SchemeGuest: rolesResult.schemeGuest,
|
||||
ExplicitRoles: strings.Join(rolesResult.explicitRoles, " "),
|
||||
ChannelId: db.ChannelId,
|
||||
UserId: db.UserId,
|
||||
Roles: strings.Join(rolesResult.roles, " "),
|
||||
LastViewedAt: db.LastViewedAt,
|
||||
MsgCount: db.MsgCount,
|
||||
MsgCountRoot: db.MsgCountRoot,
|
||||
MentionCount: db.MentionCount,
|
||||
MentionCountRoot: db.MentionCountRoot,
|
||||
UrgentMentionCount: db.UrgentMentionCount,
|
||||
NotifyProps: db.NotifyProps,
|
||||
LastUpdateAt: db.LastUpdateAt,
|
||||
SchemeAdmin: rolesResult.schemeAdmin,
|
||||
SchemeUser: rolesResult.schemeUser,
|
||||
SchemeGuest: rolesResult.schemeGuest,
|
||||
ExplicitRoles: strings.Join(rolesResult.explicitRoles, " "),
|
||||
},
|
||||
TeamName: db.TeamName,
|
||||
TeamDisplayName: db.TeamDisplayName,
|
||||
@@ -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
Обычный файл
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
Обычный файл
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 {
|
||||
|
||||
Ссылка в новой задаче
Block a user