MM-17071 Add mention counting when marking a post as unread (#11966)
* Add different types for different mentions * Remove redundant THREAD_ANY and THREAD_ROOT constants * Make PostStore.Get return thread in order * MM-17071 Add initial version of countMentionsFromPost * MM-17071 Refactor comment mention counting * MM-17071 Use mention counting when marking post as unread * Fix shadowing in tests * Remove repeated check of user count * Refactor code using MentionType * Update comments around -1 return value * Move inner functions out of countMentionsFromPost * Remove preconditions check as separate test case * Update comments * Add User.GetMentionKeys * Revert "Make PostStore.Get return thread in order" This reverts commit 22aa010cee359655fe75e1dc899cf0ffb0943c2a. * Fix tests * Fix merge conflict * Add store.MentionAllPosts
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
5f28ce9de0
Коммит
e6f67c664c
@@ -1787,13 +1787,21 @@ func (a *App) UpdateChannelLastViewedAt(channelIds []string, userId string) *mod
|
||||
|
||||
// MarkChanelAsUnreadFromPost will take a post and set the channel as unread from that one.
|
||||
func (a *App) MarkChannelAsUnreadFromPost(postID string, userID string) (*model.ChannelUnreadAt, *model.AppError) {
|
||||
|
||||
post, err := a.GetSinglePost(postID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unreadMentions := 0 // TODO: calculate this value, setting it to zero for now.
|
||||
user, err := a.GetUser(userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unreadMentions, err := a.countMentionsFromPost(user, post)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return a.Srv.Store.Channel().UpdateLastViewedAtPost(post, userID, unreadMentions)
|
||||
}
|
||||
|
||||
|
||||
@@ -1128,7 +1128,6 @@ func TestMarkChannelAsUnreadFromPost(t *testing.T) {
|
||||
unread, err := th.App.GetChannelUnread(c1.Id, u2.Id)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, int64(0), unread.MsgCount)
|
||||
|
||||
})
|
||||
|
||||
t.Run("Unread on a private channel", func(t *testing.T) {
|
||||
@@ -1150,6 +1149,48 @@ func TestMarkChannelAsUnreadFromPost(t *testing.T) {
|
||||
assert.Equal(t, pp2.CreateAt-1, response.LastViewedAt)
|
||||
})
|
||||
|
||||
t.Run("Unread with mentions", func(t *testing.T) {
|
||||
c2 := th.CreateChannel(th.BasicTeam)
|
||||
_, err := th.App.AddUserToChannel(u2, c2)
|
||||
require.Nil(t, err)
|
||||
|
||||
p4, err := th.App.CreatePost(&model.Post{
|
||||
UserId: u2.Id,
|
||||
ChannelId: c2.Id,
|
||||
Message: "@" + u1.Username,
|
||||
}, c2, false)
|
||||
require.Nil(t, err)
|
||||
th.CreatePost(c2)
|
||||
|
||||
response, err := th.App.MarkChannelAsUnreadFromPost(p4.Id, u1.Id)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(1), response.MsgCount)
|
||||
assert.Equal(t, int64(1), response.MentionCount)
|
||||
|
||||
unread, err := th.App.GetChannelUnread(c2.Id, u1.Id)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, int64(1), unread.MsgCount)
|
||||
assert.Equal(t, int64(1), unread.MentionCount)
|
||||
})
|
||||
|
||||
t.Run("Unread on a DM channel", func(t *testing.T) {
|
||||
dc := th.CreateDmChannel(u2)
|
||||
|
||||
dm1 := th.CreatePost(dc)
|
||||
th.CreatePost(dc)
|
||||
th.CreatePost(dc)
|
||||
|
||||
response, err := th.App.MarkChannelAsUnreadFromPost(dm1.Id, u1.Id)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(0), response.MsgCount)
|
||||
assert.Equal(t, int64(3), response.MentionCount)
|
||||
|
||||
unread, err := th.App.GetChannelUnread(dc.Id, u1.Id)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, int64(3), unread.MsgCount)
|
||||
assert.Equal(t, int64(3), unread.MentionCount)
|
||||
})
|
||||
|
||||
t.Run("Can't unread an imaginary post", func(t *testing.T) {
|
||||
response, err := th.App.MarkChannelAsUnreadFromPost("invalid4ofngungryquinj976y", u1.Id)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
@@ -16,11 +16,6 @@ import (
|
||||
"github.com/mattermost/mattermost-server/utils/markdown"
|
||||
)
|
||||
|
||||
const (
|
||||
THREAD_ANY = "any"
|
||||
THREAD_ROOT = "root"
|
||||
)
|
||||
|
||||
func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *model.Channel, sender *model.User, parentPostList *model.PostList) ([]string, error) {
|
||||
// Do not send notifications in archived channels
|
||||
if channel.DeleteAt > 0 {
|
||||
@@ -63,67 +58,57 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod
|
||||
}
|
||||
channelMemberNotifyPropsMap := result.Data.(map[string]model.StringMap)
|
||||
|
||||
mentionedUserIds := make(map[string]bool)
|
||||
threadMentionedUserIds := make(map[string]string)
|
||||
mentions := &ExplicitMentions{}
|
||||
allActivityPushUserIds := []string{}
|
||||
hereNotification := false
|
||||
channelNotification := false
|
||||
allNotification := false
|
||||
updateMentionChans := []chan *model.AppError{}
|
||||
|
||||
if channel.Type == model.CHANNEL_DIRECT {
|
||||
otherUserId := channel.GetOtherUserIdForDM(post.UserId)
|
||||
|
||||
_, ok := profileMap[otherUserId]
|
||||
if ok {
|
||||
mentionedUserIds[otherUserId] = true
|
||||
mentions.addMention(otherUserId, DMMention)
|
||||
}
|
||||
|
||||
if post.Props["from_webhook"] == "true" {
|
||||
mentionedUserIds[post.UserId] = true
|
||||
mentions.addMention(post.UserId, DMMention)
|
||||
}
|
||||
} else {
|
||||
keywords := a.getMentionKeywordsInChannel(profileMap, post.Type != model.POST_HEADER_CHANGE && post.Type != model.POST_PURPOSE_CHANGE, channelMemberNotifyPropsMap)
|
||||
allowChannelMentions := a.allowChannelMentions(post, len(profileMap))
|
||||
keywords := a.getMentionKeywordsInChannel(profileMap, allowChannelMentions, channelMemberNotifyPropsMap)
|
||||
|
||||
m := getExplicitMentions(post, keywords)
|
||||
mentions = getExplicitMentions(post, keywords)
|
||||
|
||||
// Add an implicit mention when a user is added to a channel
|
||||
// even if the user has set 'username mentions' to false in account settings.
|
||||
if post.Type == model.POST_ADD_TO_CHANNEL {
|
||||
val := post.Props[model.POST_PROPS_ADDED_USER_ID]
|
||||
if val != nil {
|
||||
uid := val.(string)
|
||||
m.MentionedUserIds[uid] = true
|
||||
addedUserId, ok := post.Props[model.POST_PROPS_ADDED_USER_ID].(string)
|
||||
if ok {
|
||||
mentions.addMention(addedUserId, KeywordMention)
|
||||
}
|
||||
}
|
||||
|
||||
mentionedUserIds, hereNotification, channelNotification, allNotification = m.MentionedUserIds, m.HereMentioned, m.ChannelMentioned, m.AllMentioned
|
||||
|
||||
// get users that have comment thread mentions enabled
|
||||
if len(post.RootId) > 0 && parentPostList != nil {
|
||||
for _, threadPost := range parentPostList.Posts {
|
||||
profile := profileMap[threadPost.UserId]
|
||||
if profile != nil && (profile.NotifyProps[model.COMMENTS_NOTIFY_PROP] == THREAD_ANY || (profile.NotifyProps[model.COMMENTS_NOTIFY_PROP] == THREAD_ROOT && threadPost.Id == parentPostList.Order[0])) {
|
||||
if profile != nil && (profile.NotifyProps[model.COMMENTS_NOTIFY_PROP] == model.COMMENTS_NOTIFY_ANY || (profile.NotifyProps[model.COMMENTS_NOTIFY_PROP] == model.COMMENTS_NOTIFY_ROOT && threadPost.Id == parentPostList.Order[0])) {
|
||||
mentionType := ThreadMention
|
||||
if threadPost.Id == parentPostList.Order[0] {
|
||||
threadMentionedUserIds[threadPost.UserId] = THREAD_ROOT
|
||||
} else {
|
||||
threadMentionedUserIds[threadPost.UserId] = THREAD_ANY
|
||||
mentionType = CommentMention
|
||||
}
|
||||
|
||||
if _, ok := mentionedUserIds[threadPost.UserId]; !ok {
|
||||
mentionedUserIds[threadPost.UserId] = false
|
||||
}
|
||||
mentions.addMention(threadPost.UserId, mentionType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// prevent the user from mentioning themselves
|
||||
if post.Props["from_webhook"] != "true" {
|
||||
delete(mentionedUserIds, post.UserId)
|
||||
mentions.removeMention(post.UserId)
|
||||
}
|
||||
|
||||
go func() {
|
||||
_, err := a.sendOutOfChannelMentions(sender, post, channel, m.OtherPotentialMentions)
|
||||
_, err := a.sendOutOfChannelMentions(sender, post, channel, mentions.OtherPotentialMentions)
|
||||
if err != nil {
|
||||
mlog.Error("Failed to send warning for out of channel mentions", mlog.String("user_id", sender.Id), mlog.String("post_id", post.Id), mlog.Err(err))
|
||||
}
|
||||
@@ -140,9 +125,12 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod
|
||||
}
|
||||
}
|
||||
|
||||
mentionedUsersList := make([]string, 0, len(mentionedUserIds))
|
||||
for id := range mentionedUserIds {
|
||||
mentionedUsersList := make([]string, 0, len(mentions.Mentions))
|
||||
updateMentionChans := []chan *model.AppError{}
|
||||
|
||||
for id := range mentions.Mentions {
|
||||
mentionedUsersList = append(mentionedUsersList, id)
|
||||
|
||||
umc := make(chan *model.AppError, 1)
|
||||
go func(userId string) {
|
||||
umc <- a.Srv.Store.Channel().IncrementMentionCount(post.ChannelId, userId)
|
||||
@@ -205,43 +193,42 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod
|
||||
}
|
||||
}
|
||||
|
||||
T := utils.GetUserTranslations(sender.Locale)
|
||||
// Check for channel-wide mentions in channels that have too many members for those to work
|
||||
if int64(len(profileMap)) > *a.Config().TeamSettings.MaxNotificationsPerChannel {
|
||||
T := utils.GetUserTranslations(sender.Locale)
|
||||
|
||||
// If the channel has more than 1K users then @here is disabled
|
||||
if hereNotification && int64(len(profileMap)) > *a.Config().TeamSettings.MaxNotificationsPerChannel {
|
||||
hereNotification = false
|
||||
a.SendEphemeralPost(
|
||||
post.UserId,
|
||||
&model.Post{
|
||||
ChannelId: post.ChannelId,
|
||||
Message: T("api.post.disabled_here", map[string]interface{}{"Users": *a.Config().TeamSettings.MaxNotificationsPerChannel}),
|
||||
CreateAt: post.CreateAt + 1,
|
||||
},
|
||||
)
|
||||
}
|
||||
if mentions.HereMentioned {
|
||||
a.SendEphemeralPost(
|
||||
post.UserId,
|
||||
&model.Post{
|
||||
ChannelId: post.ChannelId,
|
||||
Message: T("api.post.disabled_here", map[string]interface{}{"Users": *a.Config().TeamSettings.MaxNotificationsPerChannel}),
|
||||
CreateAt: post.CreateAt + 1,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// If the channel has more than 1K users then @channel is disabled
|
||||
if channelNotification && int64(len(profileMap)) > *a.Config().TeamSettings.MaxNotificationsPerChannel {
|
||||
a.SendEphemeralPost(
|
||||
post.UserId,
|
||||
&model.Post{
|
||||
ChannelId: post.ChannelId,
|
||||
Message: T("api.post.disabled_channel", map[string]interface{}{"Users": *a.Config().TeamSettings.MaxNotificationsPerChannel}),
|
||||
CreateAt: post.CreateAt + 1,
|
||||
},
|
||||
)
|
||||
}
|
||||
if mentions.ChannelMentioned {
|
||||
a.SendEphemeralPost(
|
||||
post.UserId,
|
||||
&model.Post{
|
||||
ChannelId: post.ChannelId,
|
||||
Message: T("api.post.disabled_channel", map[string]interface{}{"Users": *a.Config().TeamSettings.MaxNotificationsPerChannel}),
|
||||
CreateAt: post.CreateAt + 1,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// If the channel has more than 1K users then @all is disabled
|
||||
if allNotification && int64(len(profileMap)) > *a.Config().TeamSettings.MaxNotificationsPerChannel {
|
||||
a.SendEphemeralPost(
|
||||
post.UserId,
|
||||
&model.Post{
|
||||
ChannelId: post.ChannelId,
|
||||
Message: T("api.post.disabled_all", map[string]interface{}{"Users": *a.Config().TeamSettings.MaxNotificationsPerChannel}),
|
||||
CreateAt: post.CreateAt + 1,
|
||||
},
|
||||
)
|
||||
if mentions.AllMentioned {
|
||||
a.SendEphemeralPost(
|
||||
post.UserId,
|
||||
&model.Post{
|
||||
ChannelId: post.ChannelId,
|
||||
Message: T("api.post.disabled_all", map[string]interface{}{"Users": *a.Config().TeamSettings.MaxNotificationsPerChannel}),
|
||||
CreateAt: post.CreateAt + 1,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure all mention updates are complete to prevent race
|
||||
@@ -282,16 +269,20 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod
|
||||
}
|
||||
|
||||
if ShouldSendPushNotification(profileMap[id], channelMemberNotifyPropsMap[id], true, status, post) {
|
||||
mentionType := mentions.Mentions[id]
|
||||
|
||||
replyToThreadType := ""
|
||||
if value, ok := threadMentionedUserIds[id]; ok {
|
||||
replyToThreadType = value
|
||||
if mentionType == ThreadMention {
|
||||
replyToThreadType = model.COMMENTS_NOTIFY_ANY
|
||||
} else if mentionType == CommentMention {
|
||||
replyToThreadType = model.COMMENTS_NOTIFY_ROOT
|
||||
}
|
||||
|
||||
a.sendPushNotification(
|
||||
notification,
|
||||
profileMap[id],
|
||||
mentionedUserIds[id],
|
||||
(channelNotification || hereNotification || allNotification),
|
||||
mentionType == KeywordMention || mentionType == ChannelMention || mentionType == DMMention,
|
||||
mentionType == ChannelMention,
|
||||
replyToThreadType,
|
||||
)
|
||||
} else {
|
||||
@@ -311,7 +302,7 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := mentionedUserIds[id]; !ok {
|
||||
if _, ok := mentions.Mentions[id]; !ok {
|
||||
var status *model.Status
|
||||
var err *model.AppError
|
||||
if status, err = a.GetStatus(id); err != nil {
|
||||
@@ -521,8 +512,8 @@ func splitAtFinal(items []string) (preliminary []string, final string) {
|
||||
}
|
||||
|
||||
type ExplicitMentions struct {
|
||||
// MentionedUserIds contains a key for each user mentioned by keyword.
|
||||
MentionedUserIds map[string]bool
|
||||
// Mentions contains the ID of each user that was mentioned and how they were mentioned.
|
||||
Mentions map[string]MentionType
|
||||
|
||||
// OtherPotentialMentions contains a list of strings that looked like mentions, but didn't have
|
||||
// a corresponding keyword.
|
||||
@@ -538,12 +529,56 @@ type ExplicitMentions struct {
|
||||
ChannelMentioned bool
|
||||
}
|
||||
|
||||
type MentionType int
|
||||
|
||||
const (
|
||||
// Different types of mentions ordered by their priority from lowest to highest
|
||||
|
||||
// A placeholder that should never be used in practice
|
||||
NoMention MentionType = iota
|
||||
|
||||
// The post is in a thread that the user has commented on
|
||||
ThreadMention
|
||||
|
||||
// The post is a comment on a thread started by the user
|
||||
CommentMention
|
||||
|
||||
// The post contains an at-channel, at-all, or at-here
|
||||
ChannelMention
|
||||
|
||||
// The post is a DM
|
||||
DMMention
|
||||
|
||||
// The post contains an at-mention for the user
|
||||
KeywordMention
|
||||
)
|
||||
|
||||
func (m *ExplicitMentions) addMention(userId string, mentionType MentionType) {
|
||||
if m.Mentions == nil {
|
||||
m.Mentions = make(map[string]MentionType)
|
||||
}
|
||||
|
||||
if currentType, ok := m.Mentions[userId]; ok && currentType >= mentionType {
|
||||
return
|
||||
}
|
||||
|
||||
m.Mentions[userId] = mentionType
|
||||
}
|
||||
|
||||
func (m *ExplicitMentions) addMentions(userIds []string, mentionType MentionType) {
|
||||
for _, userId := range userIds {
|
||||
m.addMention(userId, mentionType)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *ExplicitMentions) removeMention(userId string) {
|
||||
delete(m.Mentions, userId)
|
||||
}
|
||||
|
||||
// Given a message and a map mapping mention keywords to the users who use them, returns a map of mentioned
|
||||
// users and a slice of potential mention users not in the channel and whether or not @here was mentioned.
|
||||
func getExplicitMentions(post *model.Post, keywords map[string][]string) *ExplicitMentions {
|
||||
ret := &ExplicitMentions{
|
||||
MentionedUserIds: make(map[string]bool),
|
||||
}
|
||||
ret := &ExplicitMentions{}
|
||||
|
||||
buf := ""
|
||||
mentionsEnabledFields := getMentionsEnabledFields(post)
|
||||
@@ -582,49 +617,67 @@ func getMentionsEnabledFields(post *model.Post) model.StringArray {
|
||||
return ret
|
||||
}
|
||||
|
||||
// allowChannelMentions returns whether or not the channel mentions are allowed for the given post.
|
||||
func (a *App) allowChannelMentions(post *model.Post, numProfiles int) bool {
|
||||
if post.Type == model.POST_HEADER_CHANGE || post.Type == model.POST_PURPOSE_CHANGE {
|
||||
return false
|
||||
}
|
||||
|
||||
if int64(numProfiles) >= *a.Config().TeamSettings.MaxNotificationsPerChannel {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Given a map of user IDs to profiles, returns a list of mention
|
||||
// keywords for all users in the channel.
|
||||
func (a *App) getMentionKeywordsInChannel(profiles map[string]*model.User, lookForSpecialMentions bool, channelMemberNotifyPropsMap map[string]model.StringMap) map[string][]string {
|
||||
func (a *App) getMentionKeywordsInChannel(profiles map[string]*model.User, allowChannelMentions bool, channelMemberNotifyPropsMap map[string]model.StringMap) map[string][]string {
|
||||
keywords := make(map[string][]string)
|
||||
|
||||
for id, profile := range profiles {
|
||||
userMention := "@" + strings.ToLower(profile.Username)
|
||||
keywords[userMention] = append(keywords[userMention], id)
|
||||
for _, profile := range profiles {
|
||||
addMentionKeywordsForUser(
|
||||
keywords,
|
||||
profile,
|
||||
channelMemberNotifyPropsMap[profile.Id],
|
||||
GetStatusFromCache(profile.Id),
|
||||
allowChannelMentions,
|
||||
)
|
||||
}
|
||||
|
||||
if len(profile.NotifyProps[model.MENTION_KEYS_NOTIFY_PROP]) > 0 {
|
||||
// Add all the user's mention keys
|
||||
splitKeys := strings.Split(profile.NotifyProps[model.MENTION_KEYS_NOTIFY_PROP], ",")
|
||||
for _, k := range splitKeys {
|
||||
// note that these are made lower case so that we can do a case insensitive check for them
|
||||
key := strings.ToLower(k)
|
||||
if key != "" {
|
||||
keywords[key] = append(keywords[key], id)
|
||||
}
|
||||
}
|
||||
return keywords
|
||||
}
|
||||
|
||||
// addMentionKeywordsForUser adds the mention keywords for a given user to the given keyword map. Returns the provided keyword map.
|
||||
func addMentionKeywordsForUser(keywords map[string][]string, profile *model.User, channelNotifyProps map[string]string, status *model.Status, allowChannelMentions bool) map[string][]string {
|
||||
userMention := "@" + strings.ToLower(profile.Username)
|
||||
keywords[userMention] = append(keywords[userMention], profile.Id)
|
||||
|
||||
// Add all the user's mention keys
|
||||
for _, k := range profile.GetMentionKeys() {
|
||||
// note that these are made lower case so that we can do a case insensitive check for them
|
||||
key := strings.ToLower(k)
|
||||
|
||||
if key != "" {
|
||||
keywords[key] = append(keywords[key], profile.Id)
|
||||
}
|
||||
}
|
||||
|
||||
// If turned on, add the user's case sensitive first name
|
||||
if profile.NotifyProps[model.FIRST_NAME_NOTIFY_PROP] == "true" {
|
||||
keywords[profile.FirstName] = append(keywords[profile.FirstName], profile.Id)
|
||||
}
|
||||
// If turned on, add the user's case sensitive first name
|
||||
if profile.NotifyProps[model.FIRST_NAME_NOTIFY_PROP] == "true" {
|
||||
keywords[profile.FirstName] = append(keywords[profile.FirstName], profile.Id)
|
||||
}
|
||||
|
||||
ignoreChannelMentions := false
|
||||
if ignoreChannelMentionsNotifyProp, ok := channelMemberNotifyPropsMap[profile.Id][model.IGNORE_CHANNEL_MENTIONS_NOTIFY_PROP]; ok {
|
||||
if ignoreChannelMentionsNotifyProp == model.IGNORE_CHANNEL_MENTIONS_ON {
|
||||
ignoreChannelMentions = true
|
||||
}
|
||||
}
|
||||
// Add @channel and @all to keywords if user has them turned on and the server allows them
|
||||
if allowChannelMentions {
|
||||
ignoreChannelMentions := channelNotifyProps[model.IGNORE_CHANNEL_MENTIONS_NOTIFY_PROP] == model.IGNORE_CHANNEL_MENTIONS_ON
|
||||
|
||||
// Add @channel and @all to keywords if user has them turned on
|
||||
if lookForSpecialMentions {
|
||||
if int64(len(profiles)) <= *a.Config().TeamSettings.MaxNotificationsPerChannel && profile.NotifyProps[model.CHANNEL_MENTIONS_NOTIFY_PROP] == "true" && !ignoreChannelMentions {
|
||||
keywords["@channel"] = append(keywords["@channel"], profile.Id)
|
||||
keywords["@all"] = append(keywords["@all"], profile.Id)
|
||||
if profile.NotifyProps[model.CHANNEL_MENTIONS_NOTIFY_PROP] == "true" && !ignoreChannelMentions {
|
||||
keywords["@channel"] = append(keywords["@channel"], profile.Id)
|
||||
keywords["@all"] = append(keywords["@all"], profile.Id)
|
||||
|
||||
status := GetStatusFromCache(profile.Id)
|
||||
if status != nil && status.Status == model.STATUS_ONLINE {
|
||||
keywords["@here"] = append(keywords["@here"], profile.Id)
|
||||
}
|
||||
if status != nil && status.Status == model.STATUS_ONLINE {
|
||||
keywords["@here"] = append(keywords["@here"], profile.Id)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -679,38 +732,36 @@ func (n *postNotification) GetSenderName(userNameFormat string, overridesAllowed
|
||||
return n.sender.GetDisplayNameWithPrefix(userNameFormat, "@")
|
||||
}
|
||||
|
||||
// addMentionedUsers will add the mentioned user id in the struct's list for mentioned users
|
||||
func (e *ExplicitMentions) addMentionedUsers(ids []string) {
|
||||
for _, id := range ids {
|
||||
e.MentionedUserIds[id] = true
|
||||
}
|
||||
}
|
||||
|
||||
// checkForMention checks if there is a mention to a specific user or to the keywords here / channel / all
|
||||
func (e *ExplicitMentions) checkForMention(word string, keywords map[string][]string) bool {
|
||||
isMention := false
|
||||
var mentionType MentionType
|
||||
|
||||
switch strings.ToLower(word) {
|
||||
case "@here":
|
||||
e.HereMentioned = true
|
||||
mentionType = ChannelMention
|
||||
case "@channel":
|
||||
e.ChannelMentioned = true
|
||||
mentionType = ChannelMention
|
||||
case "@all":
|
||||
e.AllMentioned = true
|
||||
mentionType = ChannelMention
|
||||
default:
|
||||
mentionType = KeywordMention
|
||||
}
|
||||
|
||||
if ids, match := keywords[strings.ToLower(word)]; match {
|
||||
e.addMentionedUsers(ids)
|
||||
isMention = true
|
||||
e.addMentions(ids, mentionType)
|
||||
return true
|
||||
}
|
||||
|
||||
// Case-sensitive check for first name
|
||||
if ids, match := keywords[word]; match {
|
||||
e.addMentionedUsers(ids)
|
||||
isMention = true
|
||||
e.addMentions(ids, mentionType)
|
||||
return true
|
||||
}
|
||||
|
||||
return isMention
|
||||
return false
|
||||
}
|
||||
|
||||
// isKeywordMultibyte checks if a word containing a multibyte character contains a multibyte keyword
|
||||
@@ -785,8 +836,9 @@ func (e *ExplicitMentions) processText(text string, keywords map[string][]string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ids, match := isKeywordMultibyte(keywords, word); match {
|
||||
e.addMentionedUsers(ids)
|
||||
e.addMentions(ids, KeywordMention)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,11 +160,11 @@ func (a *App) getPushNotificationMessage(postMessage string, explicitMention, ch
|
||||
return senderName + userLocale("api.post.send_notifications_and_forget.push_explicit_mention")
|
||||
}
|
||||
|
||||
if replyToThreadType == THREAD_ROOT {
|
||||
if replyToThreadType == model.COMMENTS_NOTIFY_ROOT {
|
||||
return senderName + userLocale("api.post.send_notification_and_forget.push_comment_on_post")
|
||||
}
|
||||
|
||||
if replyToThreadType == THREAD_ANY {
|
||||
if replyToThreadType == model.COMMENTS_NOTIFY_ANY {
|
||||
return senderName + userLocale("api.post.send_notification_and_forget.push_comment_on_thread")
|
||||
}
|
||||
|
||||
|
||||
@@ -572,13 +572,13 @@ func TestGetPushNotificationMessage(t *testing.T) {
|
||||
},
|
||||
"full message, public channel, commented on post": {
|
||||
Message: "this is a message",
|
||||
replyToThreadType: THREAD_ROOT,
|
||||
replyToThreadType: model.COMMENTS_NOTIFY_ROOT,
|
||||
ChannelType: model.CHANNEL_OPEN,
|
||||
ExpectedMessage: "user: this is a message",
|
||||
},
|
||||
"full message, public channel, commented on thread": {
|
||||
Message: "this is a message",
|
||||
replyToThreadType: THREAD_ANY,
|
||||
replyToThreadType: model.COMMENTS_NOTIFY_ANY,
|
||||
ChannelType: model.CHANNEL_OPEN,
|
||||
ExpectedMessage: "user: this is a message",
|
||||
},
|
||||
@@ -595,13 +595,13 @@ func TestGetPushNotificationMessage(t *testing.T) {
|
||||
},
|
||||
"full message, private channel, commented on post": {
|
||||
Message: "this is a message",
|
||||
replyToThreadType: THREAD_ROOT,
|
||||
replyToThreadType: model.COMMENTS_NOTIFY_ROOT,
|
||||
ChannelType: model.CHANNEL_PRIVATE,
|
||||
ExpectedMessage: "user: this is a message",
|
||||
},
|
||||
"full message, private channel, commented on thread": {
|
||||
Message: "this is a message",
|
||||
replyToThreadType: THREAD_ANY,
|
||||
replyToThreadType: model.COMMENTS_NOTIFY_ANY,
|
||||
ChannelType: model.CHANNEL_PRIVATE,
|
||||
ExpectedMessage: "user: this is a message",
|
||||
},
|
||||
@@ -618,13 +618,13 @@ func TestGetPushNotificationMessage(t *testing.T) {
|
||||
},
|
||||
"full message, group message channel, commented on post": {
|
||||
Message: "this is a message",
|
||||
replyToThreadType: THREAD_ROOT,
|
||||
replyToThreadType: model.COMMENTS_NOTIFY_ROOT,
|
||||
ChannelType: model.CHANNEL_GROUP,
|
||||
ExpectedMessage: "user: this is a message",
|
||||
},
|
||||
"full message, group message channel, commented on thread": {
|
||||
Message: "this is a message",
|
||||
replyToThreadType: THREAD_ANY,
|
||||
replyToThreadType: model.COMMENTS_NOTIFY_ANY,
|
||||
ChannelType: model.CHANNEL_GROUP,
|
||||
ExpectedMessage: "user: this is a message",
|
||||
},
|
||||
@@ -641,13 +641,13 @@ func TestGetPushNotificationMessage(t *testing.T) {
|
||||
},
|
||||
"full message, direct message channel, commented on post": {
|
||||
Message: "this is a message",
|
||||
replyToThreadType: THREAD_ROOT,
|
||||
replyToThreadType: model.COMMENTS_NOTIFY_ROOT,
|
||||
ChannelType: model.CHANNEL_DIRECT,
|
||||
ExpectedMessage: "this is a message",
|
||||
},
|
||||
"full message, direct message channel, commented on thread": {
|
||||
Message: "this is a message",
|
||||
replyToThreadType: THREAD_ANY,
|
||||
replyToThreadType: model.COMMENTS_NOTIFY_ANY,
|
||||
ChannelType: model.CHANNEL_DIRECT,
|
||||
ExpectedMessage: "this is a message",
|
||||
},
|
||||
@@ -673,14 +673,14 @@ func TestGetPushNotificationMessage(t *testing.T) {
|
||||
},
|
||||
"generic message, public channel, commented on post": {
|
||||
Message: "this is a message",
|
||||
replyToThreadType: THREAD_ROOT,
|
||||
replyToThreadType: model.COMMENTS_NOTIFY_ROOT,
|
||||
PushNotificationContents: model.GENERIC_NOTIFICATION,
|
||||
ChannelType: model.CHANNEL_OPEN,
|
||||
ExpectedMessage: "user commented on your post.",
|
||||
},
|
||||
"generic message, public channel, commented on thread": {
|
||||
Message: "this is a message",
|
||||
replyToThreadType: THREAD_ANY,
|
||||
replyToThreadType: model.COMMENTS_NOTIFY_ANY,
|
||||
PushNotificationContents: model.GENERIC_NOTIFICATION,
|
||||
ChannelType: model.CHANNEL_OPEN,
|
||||
ExpectedMessage: "user commented on a thread you participated in.",
|
||||
@@ -707,14 +707,14 @@ func TestGetPushNotificationMessage(t *testing.T) {
|
||||
},
|
||||
"generic message, public private, commented on post": {
|
||||
Message: "this is a message",
|
||||
replyToThreadType: THREAD_ROOT,
|
||||
replyToThreadType: model.COMMENTS_NOTIFY_ROOT,
|
||||
PushNotificationContents: model.GENERIC_NOTIFICATION,
|
||||
ChannelType: model.CHANNEL_PRIVATE,
|
||||
ExpectedMessage: "user commented on your post.",
|
||||
},
|
||||
"generic message, public private, commented on thread": {
|
||||
Message: "this is a message",
|
||||
replyToThreadType: THREAD_ANY,
|
||||
replyToThreadType: model.COMMENTS_NOTIFY_ANY,
|
||||
PushNotificationContents: model.GENERIC_NOTIFICATION,
|
||||
ChannelType: model.CHANNEL_PRIVATE,
|
||||
ExpectedMessage: "user commented on a thread you participated in.",
|
||||
@@ -741,14 +741,14 @@ func TestGetPushNotificationMessage(t *testing.T) {
|
||||
},
|
||||
"generic message, group message channel, commented on post": {
|
||||
Message: "this is a message",
|
||||
replyToThreadType: THREAD_ROOT,
|
||||
replyToThreadType: model.COMMENTS_NOTIFY_ROOT,
|
||||
PushNotificationContents: model.GENERIC_NOTIFICATION,
|
||||
ChannelType: model.CHANNEL_GROUP,
|
||||
ExpectedMessage: "user commented on your post.",
|
||||
},
|
||||
"generic message, group message channel, commented on thread": {
|
||||
Message: "this is a message",
|
||||
replyToThreadType: THREAD_ANY,
|
||||
replyToThreadType: model.COMMENTS_NOTIFY_ANY,
|
||||
PushNotificationContents: model.GENERIC_NOTIFICATION,
|
||||
ChannelType: model.CHANNEL_GROUP,
|
||||
ExpectedMessage: "user commented on a thread you participated in.",
|
||||
@@ -775,14 +775,14 @@ func TestGetPushNotificationMessage(t *testing.T) {
|
||||
},
|
||||
"generic message, direct message channel, commented on post": {
|
||||
Message: "this is a message",
|
||||
replyToThreadType: THREAD_ROOT,
|
||||
replyToThreadType: model.COMMENTS_NOTIFY_ROOT,
|
||||
PushNotificationContents: model.GENERIC_NOTIFICATION,
|
||||
ChannelType: model.CHANNEL_DIRECT,
|
||||
ExpectedMessage: "sent you a message.",
|
||||
},
|
||||
"generic message, direct message channel, commented on thread": {
|
||||
Message: "this is a message",
|
||||
replyToThreadType: THREAD_ANY,
|
||||
replyToThreadType: model.COMMENTS_NOTIFY_ANY,
|
||||
PushNotificationContents: model.GENERIC_NOTIFICATION,
|
||||
ChannelType: model.CHANNEL_DIRECT,
|
||||
ExpectedMessage: "sent you a message.",
|
||||
|
||||
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
134
app/post.go
134
app/post.go
@@ -1178,3 +1178,137 @@ func (a *App) MaxPostSize() int {
|
||||
|
||||
return maxPostSize
|
||||
}
|
||||
|
||||
// countMentionsFromPost returns the number of posts in the post's channel that mention the user after and including the
|
||||
// given post. Returns the number of mentions or store.MentionAllPosts if the post is in a direct message channel.
|
||||
func (a *App) countMentionsFromPost(user *model.User, post *model.Post) (int, *model.AppError) {
|
||||
channel, err := a.GetChannel(post.ChannelId)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if channel.Type == model.CHANNEL_DIRECT {
|
||||
return store.MentionAllPosts, nil
|
||||
}
|
||||
|
||||
channelMember, err := a.GetChannelMember(channel.Id, user.Id)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
keywords := addMentionKeywordsForUser(
|
||||
map[string][]string{},
|
||||
user,
|
||||
channelMember.NotifyProps,
|
||||
&model.Status{Status: model.STATUS_ONLINE}, // Assume the user is online since they would've triggered this
|
||||
true, // Assume channel mentions are always allowed for simplicity
|
||||
)
|
||||
commentMentions := user.NotifyProps[model.COMMENTS_NOTIFY_PROP]
|
||||
checkForCommentMentions := commentMentions == model.COMMENTS_NOTIFY_ROOT || commentMentions == model.COMMENTS_NOTIFY_ANY
|
||||
|
||||
// A mapping of thread root IDs to whether or not a post in that thread mentions the user
|
||||
mentionedByThread := make(map[string]bool)
|
||||
|
||||
thread, err := a.GetPostThread(post.Id)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
count := 0
|
||||
|
||||
if isPostMention(user, post, keywords, thread.Posts, mentionedByThread, checkForCommentMentions) {
|
||||
count += 1
|
||||
}
|
||||
|
||||
page := 0
|
||||
perPage := 200
|
||||
for {
|
||||
postList, err := a.GetPostsAfterPost(model.GetPostsOptions{
|
||||
ChannelId: post.ChannelId,
|
||||
PostId: post.Id,
|
||||
Page: page,
|
||||
PerPage: perPage,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
for _, postId := range postList.Order {
|
||||
if isPostMention(user, postList.Posts[postId], keywords, postList.Posts, mentionedByThread, checkForCommentMentions) {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
|
||||
if len(postList.Order) < perPage {
|
||||
break
|
||||
}
|
||||
|
||||
page += 1
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func isCommentMention(user *model.User, post *model.Post, otherPosts map[string]*model.Post, mentionedByThread map[string]bool) bool {
|
||||
if post.RootId == "" {
|
||||
// Not a comment
|
||||
return false
|
||||
}
|
||||
|
||||
if mentioned, ok := mentionedByThread[post.RootId]; ok {
|
||||
// We've already figured out if the user was mentioned by this thread
|
||||
return mentioned
|
||||
}
|
||||
|
||||
// Whether or not the user was mentioned because they started the thread
|
||||
mentioned := otherPosts[post.RootId].UserId == user.Id
|
||||
|
||||
// Or because they commented on it before this post
|
||||
if !mentioned && user.NotifyProps[model.COMMENTS_NOTIFY_PROP] == model.COMMENTS_NOTIFY_ANY {
|
||||
for _, otherPost := range otherPosts {
|
||||
if otherPost.Id == post.Id {
|
||||
continue
|
||||
}
|
||||
|
||||
if otherPost.RootId != post.RootId {
|
||||
continue
|
||||
}
|
||||
|
||||
if otherPost.UserId == user.Id && otherPost.CreateAt < post.CreateAt {
|
||||
// Found a comment made by the user from before this post
|
||||
mentioned = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mentionedByThread[post.RootId] = mentioned
|
||||
return mentioned
|
||||
}
|
||||
|
||||
func isPostMention(user *model.User, post *model.Post, keywords map[string][]string, otherPosts map[string]*model.Post, mentionedByThread map[string]bool, checkForCommentMentions bool) bool {
|
||||
// Prevent the user from mentioning themselves
|
||||
if post.UserId == user.Id && post.Props["from_webhook"] != "true" {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check for keyword mentions
|
||||
mentions := getExplicitMentions(post, keywords)
|
||||
if _, ok := mentions.Mentions[user.Id]; ok {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check for mentions caused by being added to the channel
|
||||
if post.Type == model.POST_ADD_TO_CHANNEL {
|
||||
if addedUserId, ok := post.Props[model.POST_PROPS_ADDED_USER_ID].(string); ok && addedUserId == user.Id {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Check for comment mentions
|
||||
if checkForCommentMentions && isCommentMention(user, post, otherPosts, mentionedByThread) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
565
app/post_test.go
565
app/post_test.go
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/mattermost/mattermost-server/einterfaces/mocks"
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/plugin/plugintest/mock"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
"github.com/mattermost/mattermost-server/store/storetest"
|
||||
)
|
||||
|
||||
@@ -930,3 +931,567 @@ func TestSearchPostsInTeamForUser(t *testing.T) {
|
||||
es.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCountMentionsFromPost(t *testing.T) {
|
||||
t.Run("should not count posts without mentions", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
user1 := th.BasicUser
|
||||
user2 := th.BasicUser2
|
||||
|
||||
channel := th.CreateChannel(th.BasicTeam)
|
||||
th.AddUserToChannel(user2, channel)
|
||||
|
||||
post1, err := th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: "test",
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
_, err = th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: "test2",
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
_, err = th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: "test3",
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
|
||||
count, err := th.App.countMentionsFromPost(user2, post1)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, count)
|
||||
})
|
||||
|
||||
t.Run("should count keyword mentions", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
user1 := th.BasicUser
|
||||
user2 := th.BasicUser2
|
||||
|
||||
channel := th.CreateChannel(th.BasicTeam)
|
||||
th.AddUserToChannel(user2, channel)
|
||||
|
||||
user2.NotifyProps[model.MENTION_KEYS_NOTIFY_PROP] = "apple"
|
||||
|
||||
post1, err := th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: fmt.Sprintf("@%s", user2.Username),
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
_, err = th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: "test2",
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
_, err = th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: "apple",
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
|
||||
// post1 and post3 should mention the user
|
||||
|
||||
count, err := th.App.countMentionsFromPost(user2, post1)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, count)
|
||||
})
|
||||
|
||||
t.Run("should count channel-wide mentions when enabled", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
user1 := th.BasicUser
|
||||
user2 := th.BasicUser2
|
||||
|
||||
channel := th.CreateChannel(th.BasicTeam)
|
||||
th.AddUserToChannel(user2, channel)
|
||||
|
||||
user2.NotifyProps[model.CHANNEL_MENTIONS_NOTIFY_PROP] = "true"
|
||||
|
||||
post1, err := th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: "test",
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
_, err = th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: "@channel",
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
_, err = th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: "@all",
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
|
||||
// post2 and post3 should mention the user
|
||||
|
||||
count, err := th.App.countMentionsFromPost(user2, post1)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, count)
|
||||
})
|
||||
|
||||
t.Run("should not count channel-wide mentions when disabled for user", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
user1 := th.BasicUser
|
||||
user2 := th.BasicUser2
|
||||
|
||||
channel := th.CreateChannel(th.BasicTeam)
|
||||
th.AddUserToChannel(user2, channel)
|
||||
|
||||
user2.NotifyProps[model.CHANNEL_MENTIONS_NOTIFY_PROP] = "false"
|
||||
|
||||
post1, err := th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: "test",
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
_, err = th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: "@channel",
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
_, err = th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: "@all",
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
|
||||
count, err := th.App.countMentionsFromPost(user2, post1)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, count)
|
||||
})
|
||||
|
||||
t.Run("should not count channel-wide mentions when disabled for channel", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
user1 := th.BasicUser
|
||||
user2 := th.BasicUser2
|
||||
|
||||
channel := th.CreateChannel(th.BasicTeam)
|
||||
th.AddUserToChannel(user2, channel)
|
||||
|
||||
user2.NotifyProps[model.CHANNEL_MENTIONS_NOTIFY_PROP] = "true"
|
||||
|
||||
_, err := th.App.UpdateChannelMemberNotifyProps(map[string]string{
|
||||
model.IGNORE_CHANNEL_MENTIONS_NOTIFY_PROP: model.IGNORE_CHANNEL_MENTIONS_ON,
|
||||
}, channel.Id, user2.Id)
|
||||
require.Nil(t, err)
|
||||
|
||||
post1, err := th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: "test",
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
_, err = th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: "@channel",
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
_, err = th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: "@all",
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
|
||||
count, err := th.App.countMentionsFromPost(user2, post1)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, count)
|
||||
})
|
||||
|
||||
t.Run("should count comment mentions when using COMMENTS_NOTIFY_ROOT", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
user1 := th.BasicUser
|
||||
user2 := th.BasicUser2
|
||||
|
||||
channel := th.CreateChannel(th.BasicTeam)
|
||||
th.AddUserToChannel(user2, channel)
|
||||
|
||||
user2.NotifyProps[model.COMMENTS_NOTIFY_PROP] = model.COMMENTS_NOTIFY_ROOT
|
||||
|
||||
post1, err := th.App.CreatePost(&model.Post{
|
||||
UserId: user2.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: "test",
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
_, err = th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
RootId: post1.Id,
|
||||
Message: "test2",
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
post3, err := th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: "test3",
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
_, err = th.App.CreatePost(&model.Post{
|
||||
UserId: user2.Id,
|
||||
ChannelId: channel.Id,
|
||||
RootId: post3.Id,
|
||||
Message: "test4",
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
_, err = th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
RootId: post3.Id,
|
||||
Message: "test5",
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
|
||||
// post2 should mention the user
|
||||
|
||||
count, err := th.App.countMentionsFromPost(user2, post1)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, count)
|
||||
})
|
||||
|
||||
t.Run("should count comment mentions when using COMMENTS_NOTIFY_ANY", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
user1 := th.BasicUser
|
||||
user2 := th.BasicUser2
|
||||
|
||||
channel := th.CreateChannel(th.BasicTeam)
|
||||
th.AddUserToChannel(user2, channel)
|
||||
|
||||
user2.NotifyProps[model.COMMENTS_NOTIFY_PROP] = model.COMMENTS_NOTIFY_ANY
|
||||
|
||||
post1, err := th.App.CreatePost(&model.Post{
|
||||
UserId: user2.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: "test",
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
_, err = th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
RootId: post1.Id,
|
||||
Message: "test2",
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
post3, err := th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: "test3",
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
_, err = th.App.CreatePost(&model.Post{
|
||||
UserId: user2.Id,
|
||||
ChannelId: channel.Id,
|
||||
RootId: post3.Id,
|
||||
Message: "test4",
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
_, err = th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
RootId: post3.Id,
|
||||
Message: "test5",
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
|
||||
// post2 and post5 should mention the user
|
||||
|
||||
count, err := th.App.countMentionsFromPost(user2, post1)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, count)
|
||||
})
|
||||
|
||||
t.Run("should count mentions caused by being added to the channel", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
user1 := th.BasicUser
|
||||
user2 := th.BasicUser2
|
||||
|
||||
channel := th.CreateChannel(th.BasicTeam)
|
||||
th.AddUserToChannel(user2, channel)
|
||||
|
||||
post1, err := th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: "test",
|
||||
Type: model.POST_ADD_TO_CHANNEL,
|
||||
Props: map[string]interface{}{
|
||||
model.POST_PROPS_ADDED_USER_ID: model.NewId(),
|
||||
},
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
_, err = th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: "test2",
|
||||
Type: model.POST_ADD_TO_CHANNEL,
|
||||
Props: map[string]interface{}{
|
||||
model.POST_PROPS_ADDED_USER_ID: user2.Id,
|
||||
},
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
_, err = th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: "test3",
|
||||
Type: model.POST_ADD_TO_CHANNEL,
|
||||
Props: map[string]interface{}{
|
||||
model.POST_PROPS_ADDED_USER_ID: user2.Id,
|
||||
},
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
|
||||
// should be mentioned by post2 and post3
|
||||
|
||||
count, err := th.App.countMentionsFromPost(user2, post1)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, count)
|
||||
})
|
||||
|
||||
t.Run("should return store.MentionAllPosts for a direct channel", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
user1 := th.BasicUser
|
||||
user2 := th.BasicUser2
|
||||
|
||||
channel, err := th.App.createDirectChannel(user1.Id, user2.Id)
|
||||
require.Nil(t, err)
|
||||
|
||||
post1, err := th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: "test",
|
||||
}, channel, false)
|
||||
|
||||
count, err := th.App.countMentionsFromPost(user2, post1)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, store.MentionAllPosts, count)
|
||||
})
|
||||
|
||||
t.Run("should not count mentions from the before the given post", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
user1 := th.BasicUser
|
||||
user2 := th.BasicUser2
|
||||
|
||||
channel := th.CreateChannel(th.BasicTeam)
|
||||
th.AddUserToChannel(user2, channel)
|
||||
|
||||
_, err := th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: fmt.Sprintf("@%s", user2.Username),
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
post2, err := th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: "test2",
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
_, err = th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: fmt.Sprintf("@%s", user2.Username),
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
|
||||
// post1 and post3 should mention the user, but we only count post3
|
||||
|
||||
count, err := th.App.countMentionsFromPost(user2, post2)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, count)
|
||||
})
|
||||
|
||||
t.Run("should not count mentions from the user's own posts", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
user1 := th.BasicUser
|
||||
user2 := th.BasicUser2
|
||||
|
||||
channel := th.CreateChannel(th.BasicTeam)
|
||||
th.AddUserToChannel(user2, channel)
|
||||
|
||||
post1, err := th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: fmt.Sprintf("@%s", user2.Username),
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
_, err = th.App.CreatePost(&model.Post{
|
||||
UserId: user2.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: fmt.Sprintf("@%s", user2.Username),
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
|
||||
// post2 should mention the user
|
||||
|
||||
count, err := th.App.countMentionsFromPost(user2, post1)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, count)
|
||||
})
|
||||
|
||||
t.Run("should include comments made before the given post when counting comment mentions", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
user1 := th.BasicUser
|
||||
user2 := th.BasicUser2
|
||||
|
||||
channel := th.CreateChannel(th.BasicTeam)
|
||||
th.AddUserToChannel(user2, channel)
|
||||
|
||||
user2.NotifyProps[model.COMMENTS_NOTIFY_PROP] = model.COMMENTS_NOTIFY_ANY
|
||||
|
||||
post1, err := th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: "test1",
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
_, err = th.App.CreatePost(&model.Post{
|
||||
UserId: user2.Id,
|
||||
ChannelId: channel.Id,
|
||||
RootId: post1.Id,
|
||||
Message: "test2",
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
post3, err := th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: "test3",
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
_, err = th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
RootId: post1.Id,
|
||||
Message: "test4",
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
|
||||
// post4 should mention the user
|
||||
|
||||
count, err := th.App.countMentionsFromPost(user2, post3)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, count)
|
||||
})
|
||||
|
||||
t.Run("should count mentions from the user's webhook posts", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
user1 := th.BasicUser
|
||||
user2 := th.BasicUser2
|
||||
|
||||
channel := th.CreateChannel(th.BasicTeam)
|
||||
th.AddUserToChannel(user2, channel)
|
||||
|
||||
post1, err := th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: "test1",
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
_, err = th.App.CreatePost(&model.Post{
|
||||
UserId: user2.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: fmt.Sprintf("@%s", user2.Username),
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
_, err = th.App.CreatePost(&model.Post{
|
||||
UserId: user2.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: fmt.Sprintf("@%s", user2.Username),
|
||||
Props: map[string]interface{}{
|
||||
"from_webhook": "true",
|
||||
},
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
|
||||
// post3 should mention the user
|
||||
|
||||
count, err := th.App.countMentionsFromPost(user2, post1)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, count)
|
||||
})
|
||||
|
||||
t.Run("should count multiple pages of mentions", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
user1 := th.BasicUser
|
||||
user2 := th.BasicUser2
|
||||
|
||||
channel := th.CreateChannel(th.BasicTeam)
|
||||
th.AddUserToChannel(user2, channel)
|
||||
|
||||
numPosts := 215
|
||||
|
||||
post1, err := th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: fmt.Sprintf("@%s", user2.Username),
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
|
||||
for i := 0; i < numPosts-1; i++ {
|
||||
_, err = th.App.CreatePost(&model.Post{
|
||||
UserId: user1.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: fmt.Sprintf("@%s", user2.Username),
|
||||
}, channel, false)
|
||||
require.Nil(t, err)
|
||||
}
|
||||
|
||||
// Every post should mention the user
|
||||
|
||||
count, err := th.App.countMentionsFromPost(user2, post1)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, numPosts, count)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -397,8 +397,7 @@ func (u *User) SetDefaultNotifications() {
|
||||
|
||||
func (user *User) UpdateMentionKeysFromUsername(oldUsername string) {
|
||||
nonUsernameKeys := []string{}
|
||||
splitKeys := strings.Split(user.NotifyProps[MENTION_KEYS_NOTIFY_PROP], ",")
|
||||
for _, key := range splitKeys {
|
||||
for _, key := range user.GetMentionKeys() {
|
||||
if key != oldUsername && key != "@"+oldUsername {
|
||||
nonUsernameKeys = append(nonUsernameKeys, key)
|
||||
}
|
||||
@@ -410,6 +409,22 @@ func (user *User) UpdateMentionKeysFromUsername(oldUsername string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (user *User) GetMentionKeys() []string {
|
||||
var keys []string
|
||||
|
||||
for _, key := range strings.Split(user.NotifyProps[MENTION_KEYS_NOTIFY_PROP], ",") {
|
||||
trimmedKey := strings.TrimSpace(key)
|
||||
|
||||
if trimmedKey == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
keys = append(keys, trimmedKey)
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
func (u *User) Patch(patch *UserPatch) {
|
||||
if patch.Username != nil {
|
||||
u.Username = *patch.Username
|
||||
|
||||
@@ -1678,7 +1678,7 @@ func (s SqlChannelStore) GetPinnedPostCount(channelId string, allowFromCache boo
|
||||
FROM Posts
|
||||
WHERE
|
||||
IsPinned = true
|
||||
AND ChannelId = :ChannelId
|
||||
AND ChannelId = :ChannelId
|
||||
AND DeleteAt = 0`, map[string]interface{}{"ChannelId": channelId})
|
||||
|
||||
if err != nil {
|
||||
@@ -1880,8 +1880,8 @@ func (s SqlChannelStore) UpdateLastViewedAt(channelIds []string, userId string)
|
||||
return times, nil
|
||||
}
|
||||
|
||||
// CountPostsSince gives the number of posts in a channel created since a given date.
|
||||
func (s SqlChannelStore) CountPostsSince(channelID string, since int64) (int64, *model.AppError) {
|
||||
// countPostsAfter returns the number of posts in the given channel created after but not including the given timestamp.
|
||||
func (s SqlChannelStore) countPostsAfter(channelID string, since int64) (int64, *model.AppError) {
|
||||
countUnreadQuery := `
|
||||
SELECT count(*)
|
||||
FROM Posts
|
||||
@@ -1898,22 +1898,27 @@ func (s SqlChannelStore) CountPostsSince(channelID string, since int64) (int64,
|
||||
|
||||
unread, err := s.GetReplica().SelectInt(countUnreadQuery, countParams)
|
||||
if err != nil {
|
||||
return 0, model.NewAppError("SqlChannelStore.CountPostsSince", "store.sql_channel.count_posts_since.app_error", countParams, fmt.Sprintf("channel_id=%s, since=%d, err=%s", channelID, since, err), http.StatusInternalServerError)
|
||||
return 0, model.NewAppError("SqlChannelStore.countPostsAfter", "store.sql_channel.count_posts_since.app_error", countParams, fmt.Sprintf("channel_id=%s, since=%d, err=%s", channelID, since, err), http.StatusInternalServerError)
|
||||
}
|
||||
return unread, nil
|
||||
}
|
||||
|
||||
// UpdateLastViewedAtPost sets a channel as unread for a user at the time of the post selected and update the MentionCount
|
||||
// it returns a channelunread so redux can update the apps easily.
|
||||
// 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 int) (*model.ChannelUnreadAt, *model.AppError) {
|
||||
|
||||
unreadDate := unreadPost.CreateAt - 1
|
||||
|
||||
unread, appErr := s.CountPostsSince(unreadPost.ChannelId, unreadDate)
|
||||
unread, appErr := s.countPostsAfter(unreadPost.ChannelId, unreadDate)
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
if mentionCount == store.MentionAllPosts {
|
||||
// Treat every unread post as a mention (like in a DM channel)
|
||||
mentionCount = int(unread)
|
||||
}
|
||||
|
||||
params := map[string]interface{}{
|
||||
"mentions": mentionCount,
|
||||
"unreadCount": unread,
|
||||
@@ -1943,7 +1948,7 @@ func (s SqlChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID s
|
||||
}
|
||||
|
||||
chanUnreadQuery := `
|
||||
SELECT
|
||||
SELECT
|
||||
c.TeamId TeamId,
|
||||
cm.UserId UserId,
|
||||
cm.ChannelId ChannelId,
|
||||
@@ -1951,11 +1956,11 @@ func (s SqlChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID s
|
||||
cm.MentionCount MentionCount,
|
||||
cm.LastViewedAt LastViewedAt,
|
||||
cm.NotifyProps NotifyProps
|
||||
FROM
|
||||
FROM
|
||||
ChannelMembers cm
|
||||
LEFT JOIN Channels c ON c.Id=cm.ChannelId
|
||||
WHERE
|
||||
cm.UserId = :userId
|
||||
cm.UserId = :userId
|
||||
AND cm.channelId = :channelId
|
||||
AND c.DeleteAt = 0
|
||||
`
|
||||
|
||||
@@ -9,6 +9,10 @@ import (
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
)
|
||||
|
||||
const (
|
||||
MentionAllPosts = -1
|
||||
)
|
||||
|
||||
type StoreResult struct {
|
||||
Data interface{}
|
||||
Err *model.AppError
|
||||
|
||||
Ссылка в новой задаче
Block a user