MM-30970 Add Basic unreadMentions support for collapsed threads (#16407)
Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
1bd7dc41bd
Коммит
c2036f614e
@@ -5544,6 +5544,80 @@ func TestFollowThreads(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func postAndCheck(t *testing.T, client *model.Client4, post *model.Post) (*model.Post, *model.Response) {
|
||||
p, resp := client.CreatePost(post)
|
||||
CheckNoError(t, resp)
|
||||
CheckCreatedStatus(t, resp)
|
||||
return p, resp
|
||||
}
|
||||
|
||||
func TestMaintainUnreadMentionsInThread(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
Client := th.Client
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.ThreadAutoFollow = true
|
||||
*cfg.ServiceSettings.CollapsedThreads = model.COLLAPSED_THREADS_DEFAULT_ON
|
||||
})
|
||||
|
||||
checkThreadList := func(client *model.Client4, userId string, expectedMentions, expectedThreads int) (*model.Threads, *model.Response) {
|
||||
uss, resp := client.GetUserThreads(userId, model.GetUserThreadsOpts{
|
||||
Page: 0,
|
||||
PageSize: 30,
|
||||
Deleted: false,
|
||||
})
|
||||
CheckNoError(t, resp)
|
||||
require.Len(t, uss.Threads, expectedThreads)
|
||||
|
||||
// validate amount of mentions via store. once GetUserThreads starts returning mentions - update
|
||||
memberships, err := th.App.Srv().Store.Thread().GetMembershipsForUser(userId)
|
||||
require.NoError(t, err)
|
||||
sum := int64(0)
|
||||
for _, membership := range memberships {
|
||||
sum += membership.UnreadMentions
|
||||
}
|
||||
require.EqualValues(t, expectedMentions, sum)
|
||||
return uss, resp
|
||||
}
|
||||
|
||||
// create regular post
|
||||
rpost, _ := postAndCheck(t, Client, &model.Post{ChannelId: th.BasicChannel.Id, Message: "testMsg"})
|
||||
// create reply and mention the original poster and another user
|
||||
postAndCheck(t, th.SystemAdminClient, &model.Post{ChannelId: th.BasicChannel.Id, Message: "testReply @" + th.BasicUser.Username + " and @" + th.BasicUser2.Username, RootId: rpost.Id})
|
||||
defer th.App.Srv().Store.Post().PermanentDeleteByUser(th.BasicUser.Id)
|
||||
defer th.App.Srv().Store.Post().PermanentDeleteByUser(th.SystemAdminUser.Id)
|
||||
|
||||
// basic user 1 was mentioned 1 time
|
||||
checkThreadList(th.Client, th.BasicUser.Id, 1, 1)
|
||||
// basic user 2 was mentioned 1 time
|
||||
checkThreadList(th.SystemAdminClient, th.BasicUser2.Id, 1, 1)
|
||||
|
||||
// test self mention, shouldn't increase mention count
|
||||
postAndCheck(t, Client, &model.Post{ChannelId: th.BasicChannel.Id, Message: "testReply @" + th.BasicUser.Username, RootId: rpost.Id})
|
||||
// count should increase
|
||||
checkThreadList(th.Client, th.BasicUser.Id, 1, 1)
|
||||
|
||||
// test DM
|
||||
dm := th.CreateDmChannel(th.SystemAdminUser)
|
||||
dm_root_post, _ := postAndCheck(t, Client, &model.Post{ChannelId: dm.Id, Message: "hi @" + th.SystemAdminUser.Username})
|
||||
|
||||
// no changes
|
||||
checkThreadList(th.Client, th.BasicUser.Id, 1, 1)
|
||||
|
||||
// post reply by the same user
|
||||
postAndCheck(t, Client, &model.Post{ChannelId: dm.Id, Message: "how are you", RootId: dm_root_post.Id})
|
||||
|
||||
// thread created
|
||||
checkThreadList(th.Client, th.BasicUser.Id, 1, 2)
|
||||
|
||||
// post two replies by another user, without mentions. mention count should still increase since this is a DM
|
||||
postAndCheck(t, th.SystemAdminClient, &model.Post{ChannelId: dm.Id, Message: "msg1", RootId: dm_root_post.Id})
|
||||
postAndCheck(t, th.SystemAdminClient, &model.Post{ChannelId: dm.Id, Message: "msg2", RootId: dm_root_post.Id})
|
||||
// expect increment by two mentions
|
||||
checkThreadList(th.Client, th.BasicUser.Id, 3, 2)
|
||||
|
||||
}
|
||||
|
||||
func TestReadThreads(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
@@ -2337,6 +2337,24 @@ func (a *App) MarkChannelAsUnreadFromPost(postID string, userID string) (*model.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if *a.Config().ServiceSettings.ThreadAutoFollow && post.RootId != "" {
|
||||
threadMembership, _ := a.Srv().Store.Thread().GetMembershipForUser(user.Id, post.RootId)
|
||||
if threadMembership != nil {
|
||||
channel, nErr := a.Srv().Store.Channel().Get(post.ChannelId, true)
|
||||
if nErr != nil {
|
||||
return nil, model.NewAppError("MarkChannelAsUnreadFromPost", "app.channel.update_last_viewed_at_post.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
threadMembership.UnreadMentions, err = a.countThreadMentions(user, post, channel.TeamId, post.UpdateAt-1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, nErr = a.Srv().Store.Thread().UpdateMembership(threadMembership)
|
||||
if nErr != nil {
|
||||
return nil, model.NewAppError("MarkChannelAsUnreadFromPost", "app.channel.update_last_viewed_at_post.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
channelUnread, nErr := a.Srv().Store.Channel().UpdateLastViewedAtPost(post, userID, unreadMentions, *a.Config().ServiceSettings.ThreadAutoFollow)
|
||||
if nErr != nil {
|
||||
return channelUnread, model.NewAppError("MarkChannelAsUnreadFromPost", "app.channel.update_last_viewed_at_post.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
||||
|
||||
@@ -162,21 +162,27 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod
|
||||
mentionedUsersList := make([]string, 0, len(mentions.Mentions))
|
||||
updateMentionChans := []chan *model.AppError{}
|
||||
mentionAutofollowChans := []chan *model.AppError{}
|
||||
threadParticipants := []string{post.UserId}
|
||||
threadParticipants := map[string]bool{post.UserId: true}
|
||||
if *a.Config().ServiceSettings.ThreadAutoFollow && post.RootId != "" {
|
||||
if parentPostList != nil {
|
||||
threadParticipants = append(threadParticipants, parentPostList.Posts[parentPostList.Order[0]].UserId)
|
||||
threadParticipants[parentPostList.Posts[parentPostList.Order[0]].UserId] = true
|
||||
}
|
||||
for id := range mentions.Mentions {
|
||||
threadParticipants = append(threadParticipants, id)
|
||||
threadParticipants[id] = true
|
||||
}
|
||||
// for each mention, make sure to update thread autofollow
|
||||
for _, id := range threadParticipants {
|
||||
// for each mention, make sure to update thread autofollow (if enabled) and update increment mention count
|
||||
for id := range threadParticipants {
|
||||
mac := make(chan *model.AppError, 1)
|
||||
go func(userId string) {
|
||||
defer close(mac)
|
||||
|
||||
nErr := a.Srv().Store.Thread().CreateMembershipIfNeeded(userId, post.RootId, true)
|
||||
incrementMentions := false
|
||||
for mid := range mentions.Mentions {
|
||||
if userId == mid {
|
||||
incrementMentions = true
|
||||
break
|
||||
}
|
||||
}
|
||||
nErr := a.Srv().Store.Thread().CreateMembershipIfNeeded(userId, post.RootId, true, incrementMentions, *a.Config().ServiceSettings.ThreadAutoFollow)
|
||||
if nErr != nil {
|
||||
mac <- model.NewAppError("SendNotifications", "app.channel.autofollow.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
|
||||
60
app/post.go
60
app/post.go
@@ -458,7 +458,7 @@ func (a *App) handlePostEvents(post *model.Post, user *model.User, channel *mode
|
||||
}
|
||||
|
||||
if *a.Config().ServiceSettings.ThreadAutoFollow && post.RootId != "" {
|
||||
if err := a.Srv().Store.Thread().CreateMembershipIfNeeded(post.UserId, post.RootId, true); err != nil {
|
||||
if err := a.Srv().Store.Thread().CreateMembershipIfNeeded(post.UserId, post.RootId, true, false, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -1340,6 +1340,64 @@ func (a *App) MaxPostSize() int {
|
||||
return a.Srv().MaxPostSize()
|
||||
}
|
||||
|
||||
// countThreadMentions returns the number of times the user is mentioned in a specified thread after the timestamp.
|
||||
func (a *App) countThreadMentions(user *model.User, post *model.Post, teamId string, timestamp int64) (int64, *model.AppError) {
|
||||
team, err := a.GetTeam(teamId)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
channel, err := a.GetChannel(post.ChannelId)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
keywords := addMentionKeywordsForUser(
|
||||
map[string][]string{},
|
||||
user,
|
||||
map[string]string{},
|
||||
&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
|
||||
)
|
||||
|
||||
posts, nErr := a.Srv().Store.Thread().GetPosts(post.Id, timestamp)
|
||||
if nErr != nil {
|
||||
return 0, model.NewAppError("countMentionsFromPost", "app.channel.count_posts_since.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
count := 0
|
||||
|
||||
if channel.Type == model.CHANNEL_DIRECT {
|
||||
// In a DM channel, every post made by the other user is a mention
|
||||
otherId := channel.GetOtherUserIdForDM(user.Id)
|
||||
for _, p := range posts {
|
||||
if p.UserId == otherId {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
return int64(count), nil
|
||||
}
|
||||
|
||||
groups, nErr := a.getGroupsAllowedForReferenceInChannel(channel, team)
|
||||
if nErr != nil {
|
||||
return 0, model.NewAppError("countMentionsFromPost", "app.channel.count_posts_since.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
mentions := getExplicitMentions(post, keywords, groups)
|
||||
if _, ok := mentions.Mentions[user.Id]; ok {
|
||||
count += 1
|
||||
}
|
||||
|
||||
for _, p := range posts {
|
||||
mentions = getExplicitMentions(p, keywords, groups)
|
||||
if _, ok := mentions.Mentions[user.Id]; ok {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
|
||||
return int64(count), nil
|
||||
}
|
||||
|
||||
// countMentionsFromPost returns the number of posts in the post's channel that mention the user after and including the
|
||||
// given post.
|
||||
func (a *App) countMentionsFromPost(user *model.User, post *model.Post) (int, *model.AppError) {
|
||||
|
||||
14
app/user.go
14
app/user.go
@@ -2384,9 +2384,9 @@ func (a *App) GetThreadsForUser(userId string, options model.GetUserThreadsOpts)
|
||||
}
|
||||
|
||||
func (a *App) UpdateThreadsReadForUser(userId string, timestamp int64) *model.AppError {
|
||||
err := a.Srv().Store.Thread().MarkAllAsRead(userId, timestamp)
|
||||
if err != nil {
|
||||
return model.NewAppError("UpdateThreadsReadForUser", "app.user.update_threads_read_for_user.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
nErr := a.Srv().Store.Thread().MarkAllAsRead(userId, timestamp)
|
||||
if nErr != nil {
|
||||
return model.NewAppError("UpdateThreadsReadForUser", "app.user.update_threads_read_for_user.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_THREAD_READ_CHANGED, "", "", userId, nil)
|
||||
message.Add("timestamp", timestamp)
|
||||
@@ -2395,7 +2395,7 @@ func (a *App) UpdateThreadsReadForUser(userId string, timestamp int64) *model.Ap
|
||||
}
|
||||
|
||||
func (a *App) UpdateThreadFollowForUser(userId, threadId string, state bool) *model.AppError {
|
||||
err := a.Srv().Store.Thread().CreateMembershipIfNeeded(userId, threadId, state)
|
||||
err := a.Srv().Store.Thread().CreateMembershipIfNeeded(userId, threadId, state, false, true)
|
||||
if err != nil {
|
||||
return model.NewAppError("UpdateThreadFollowForUser", "app.user.update_thread_follow_for_user.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
@@ -2407,9 +2407,9 @@ func (a *App) UpdateThreadFollowForUser(userId, threadId string, state bool) *mo
|
||||
}
|
||||
|
||||
func (a *App) UpdateThreadReadForUser(userId, threadId string, timestamp int64) *model.AppError {
|
||||
err := a.Srv().Store.Thread().MarkAsRead(userId, threadId, timestamp)
|
||||
if err != nil {
|
||||
return model.NewAppError("UpdateThreadReadForUser", "app.user.update_thread_read_for_user.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
nErr := a.Srv().Store.Thread().MarkAsRead(userId, threadId, timestamp)
|
||||
if nErr != nil {
|
||||
return model.NewAppError("UpdateThreadReadForUser", "app.user.update_thread_read_for_user.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_THREAD_READ_CHANGED, "", "", userId, nil)
|
||||
message.Add("thread_id", threadId)
|
||||
|
||||
@@ -67,11 +67,12 @@ func (o *Thread) Etag() string {
|
||||
}
|
||||
|
||||
type ThreadMembership struct {
|
||||
PostId string `json:"post_id"`
|
||||
UserId string `json:"user_id"`
|
||||
Following bool `json:"following"`
|
||||
LastViewed int64 `json:"last_view_at"`
|
||||
LastUpdated int64 `json:"last_update_at"`
|
||||
PostId string `json:"post_id"`
|
||||
UserId string `json:"user_id"`
|
||||
Following bool `json:"following"`
|
||||
LastViewed int64 `json:"last_view_at"`
|
||||
LastUpdated int64 `json:"last_update_at"`
|
||||
UnreadMentions int64 `json:"unread_mentions"`
|
||||
}
|
||||
|
||||
func (o *ThreadMembership) ToJson() string {
|
||||
|
||||
@@ -7666,7 +7666,7 @@ func (s *OpenTracingLayerThreadStore) CollectThreadsWithNewerReplies(userId stri
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerThreadStore) CreateMembershipIfNeeded(userId string, postId string, following bool) error {
|
||||
func (s *OpenTracingLayerThreadStore) CreateMembershipIfNeeded(userId string, postId string, following bool, incrementMentions bool, updateFollowing bool) error {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.CreateMembershipIfNeeded")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
@@ -7675,7 +7675,7 @@ func (s *OpenTracingLayerThreadStore) CreateMembershipIfNeeded(userId string, po
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
err := s.ThreadStore.CreateMembershipIfNeeded(userId, postId, following)
|
||||
err := s.ThreadStore.CreateMembershipIfNeeded(userId, postId, following, incrementMentions, updateFollowing)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
@@ -7774,6 +7774,24 @@ func (s *OpenTracingLayerThreadStore) GetMembershipsForUser(userId string) ([]*m
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerThreadStore) GetPosts(threadId string, since int64) ([]*model.Post, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.GetPosts")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.ThreadStore.GetPosts(threadId, since)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerThreadStore) GetThreadsForUser(userId string, opts model.GetUserThreadsOpts) (*model.Threads, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.GetThreadsForUser")
|
||||
@@ -7918,7 +7936,7 @@ func (s *OpenTracingLayerThreadStore) UpdateMembership(membership *model.ThreadM
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerThreadStore) UpdateUnreadsByChannel(userId string, changedThreads []string, timestamp int64) error {
|
||||
func (s *OpenTracingLayerThreadStore) UpdateUnreadsByChannel(userId string, changedThreads []string, timestamp int64, updateViewedTimestamp bool) error {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ThreadStore.UpdateUnreadsByChannel")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
@@ -7927,7 +7945,7 @@ func (s *OpenTracingLayerThreadStore) UpdateUnreadsByChannel(userId string, chan
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
err := s.ThreadStore.UpdateUnreadsByChannel(userId, changedThreads, timestamp)
|
||||
err := s.ThreadStore.UpdateUnreadsByChannel(userId, changedThreads, timestamp, updateViewedTimestamp)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
|
||||
@@ -8318,11 +8318,11 @@ func (s *RetryLayerThreadStore) CollectThreadsWithNewerReplies(userId string, ch
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerThreadStore) CreateMembershipIfNeeded(userId string, postId string, following bool) error {
|
||||
func (s *RetryLayerThreadStore) CreateMembershipIfNeeded(userId string, postId string, following bool, incrementMentions bool, updateFollowing bool) error {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
err := s.ThreadStore.CreateMembershipIfNeeded(userId, postId, following)
|
||||
err := s.ThreadStore.CreateMembershipIfNeeded(userId, postId, following, incrementMentions, updateFollowing)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -8438,6 +8438,26 @@ func (s *RetryLayerThreadStore) GetMembershipsForUser(userId string) ([]*model.T
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerThreadStore) GetPosts(threadId string, since int64) ([]*model.Post, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.ThreadStore.GetPosts(threadId, since)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerThreadStore) GetThreadsForUser(userId string, opts model.GetUserThreadsOpts) (*model.Threads, error) {
|
||||
|
||||
tries := 0
|
||||
@@ -8598,11 +8618,11 @@ func (s *RetryLayerThreadStore) UpdateMembership(membership *model.ThreadMembers
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerThreadStore) UpdateUnreadsByChannel(userId string, changedThreads []string, timestamp int64) error {
|
||||
func (s *RetryLayerThreadStore) UpdateUnreadsByChannel(userId string, changedThreads []string, timestamp int64, updateViewedTimestamp bool) error {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
err := s.ThreadStore.UpdateUnreadsByChannel(userId, changedThreads, timestamp)
|
||||
err := s.ThreadStore.UpdateUnreadsByChannel(userId, changedThreads, timestamp, updateViewedTimestamp)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2100,7 +2100,7 @@ func (s SqlChannelStore) UpdateLastViewedAt(channelIds []string, userId string,
|
||||
times[t.Id] = t.LastPostAt
|
||||
}
|
||||
if updateThreads {
|
||||
s.Thread().UpdateUnreadsByChannel(userId, threadsToUpdate, now)
|
||||
s.Thread().UpdateUnreadsByChannel(userId, threadsToUpdate, now, true)
|
||||
}
|
||||
return times, nil
|
||||
}
|
||||
@@ -2136,7 +2136,7 @@ func (s SqlChannelStore) UpdateLastViewedAt(channelIds []string, userId string,
|
||||
}
|
||||
|
||||
if updateThreads {
|
||||
s.Thread().UpdateUnreadsByChannel(userId, threadsToUpdate, now)
|
||||
s.Thread().UpdateUnreadsByChannel(userId, threadsToUpdate, now, true)
|
||||
}
|
||||
return times, nil
|
||||
}
|
||||
@@ -2252,7 +2252,7 @@ func (s SqlChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID s
|
||||
}
|
||||
|
||||
if updateThreads {
|
||||
s.Thread().UpdateUnreadsByChannel(userID, threadsToUpdate, unreadDate)
|
||||
s.Thread().UpdateUnreadsByChannel(userID, threadsToUpdate, unreadDate, true)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -2282,7 +2282,7 @@ func (s SqlChannelStore) IncrementMentionCount(channelId string, userId string,
|
||||
return errors.Wrapf(err, "failed to Update ChannelMembers with channelId=%s and userId=%s", channelId, userId)
|
||||
}
|
||||
if updateThreads {
|
||||
s.Thread().UpdateUnreadsByChannel(userId, threadsToUpdate, now)
|
||||
s.Thread().UpdateUnreadsByChannel(userId, threadsToUpdate, now, false)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ func (s *SqlThreadStore) GetThreadsForUser(userId string, opts model.GetUserThre
|
||||
var threads []*JoinedThread
|
||||
|
||||
fetchConditions := sq.And{
|
||||
sq.Eq{"Posts.UserId": userId},
|
||||
sq.Eq{"ThreadMemberships.UserId": userId},
|
||||
sq.Eq{"ThreadMemberships.Following": true},
|
||||
}
|
||||
if !opts.Deleted {
|
||||
@@ -273,13 +273,18 @@ func (s *SqlThreadStore) DeleteMembershipForUser(userId string, postId string) e
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SqlThreadStore) CreateMembershipIfNeeded(userId, postId string, following bool) error {
|
||||
func (s *SqlThreadStore) CreateMembershipIfNeeded(userId, postId string, following, incrementMentions, updateFollowing bool) error {
|
||||
membership, err := s.GetMembershipForUser(userId, postId)
|
||||
now := utils.MillisFromTime(time.Now())
|
||||
if err == nil {
|
||||
if !membership.Following || membership.Following != following {
|
||||
membership.Following = following
|
||||
if (updateFollowing && !membership.Following || membership.Following != following) || incrementMentions {
|
||||
if updateFollowing {
|
||||
membership.Following = following
|
||||
}
|
||||
membership.LastUpdated = now
|
||||
if incrementMentions {
|
||||
membership.UnreadMentions += 1
|
||||
}
|
||||
_, err = s.UpdateMembership(membership)
|
||||
}
|
||||
return err
|
||||
@@ -290,12 +295,17 @@ func (s *SqlThreadStore) CreateMembershipIfNeeded(userId, postId string, followi
|
||||
if !errors.As(err, &nfErr) {
|
||||
return errors.Wrap(err, "failed to get thread membership")
|
||||
}
|
||||
mentions := 0
|
||||
if incrementMentions {
|
||||
mentions = 1
|
||||
}
|
||||
_, err = s.SaveMembership(&model.ThreadMembership{
|
||||
PostId: postId,
|
||||
UserId: userId,
|
||||
Following: following,
|
||||
LastViewed: 0,
|
||||
LastUpdated: now,
|
||||
PostId: postId,
|
||||
UserId: userId,
|
||||
Following: following,
|
||||
LastViewed: 0,
|
||||
LastUpdated: now,
|
||||
UnreadMentions: int64(mentions),
|
||||
})
|
||||
return err
|
||||
}
|
||||
@@ -321,19 +331,38 @@ func (s *SqlThreadStore) CollectThreadsWithNewerReplies(userId string, channelId
|
||||
return changedThreads, nil
|
||||
}
|
||||
|
||||
func (s *SqlThreadStore) UpdateUnreadsByChannel(userId string, changedThreads []string, timestamp int64) error {
|
||||
func (s *SqlThreadStore) UpdateUnreadsByChannel(userId string, changedThreads []string, timestamp int64, updateViewedTimestamp bool) error {
|
||||
if len(changedThreads) == 0 {
|
||||
return nil
|
||||
}
|
||||
updateQuery, updateArgs, _ := s.getQueryBuilder().
|
||||
|
||||
qb := s.getQueryBuilder().
|
||||
Update("ThreadMemberships").
|
||||
Where(sq.Eq{"UserId": userId, "PostId": changedThreads}).
|
||||
Set("LastUpdated", timestamp).
|
||||
Set("LastViewed", timestamp).
|
||||
ToSql()
|
||||
Set("LastUpdated", timestamp)
|
||||
|
||||
if updateViewedTimestamp {
|
||||
qb = qb.Set("LastViewed", timestamp)
|
||||
}
|
||||
updateQuery, updateArgs, _ := qb.ToSql()
|
||||
|
||||
if _, err := s.GetMaster().Exec(updateQuery, updateArgs...); err != nil {
|
||||
return errors.Wrap(err, "failed to update thread membership")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SqlThreadStore) GetPosts(threadId string, since int64) ([]*model.Post, error) {
|
||||
query, args, _ := s.getQueryBuilder().
|
||||
Select("*").
|
||||
From("Posts").
|
||||
Where(sq.Eq{"RootId": threadId}).
|
||||
Where(sq.Eq{"DeleteAt": 0}).
|
||||
Where(sq.GtOrEq{"UpdateAt": since}).ToSql()
|
||||
var result []*model.Post
|
||||
if _, err := s.GetReplica().Select(&result, query, args...); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to fetch thread posts")
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -253,6 +253,7 @@ type ThreadStore interface {
|
||||
Get(id string) (*model.Thread, error)
|
||||
GetThreadsForUser(userId string, opts model.GetUserThreadsOpts) (*model.Threads, error)
|
||||
Delete(postId string) error
|
||||
GetPosts(threadId string, since int64) ([]*model.Post, error)
|
||||
|
||||
MarkAllAsRead(userId string, timestamp int64) error
|
||||
MarkAsRead(userId, threadId string, timestamp int64) error
|
||||
@@ -262,9 +263,9 @@ type ThreadStore interface {
|
||||
GetMembershipsForUser(userId string) ([]*model.ThreadMembership, error)
|
||||
GetMembershipForUser(userId, postId string) (*model.ThreadMembership, error)
|
||||
DeleteMembershipForUser(userId, postId string) error
|
||||
CreateMembershipIfNeeded(userId, postId string, following bool) error
|
||||
CreateMembershipIfNeeded(userId, postId string, following, incrementMentions, updateFollowing bool) error
|
||||
CollectThreadsWithNewerReplies(userId string, channelIds []string, timestamp int64) ([]string, error)
|
||||
UpdateUnreadsByChannel(userId string, changedThreads []string, timestamp int64) error
|
||||
UpdateUnreadsByChannel(userId string, changedThreads []string, timestamp int64, updateViewedTimestamp bool) error
|
||||
}
|
||||
|
||||
type PostStore interface {
|
||||
|
||||
@@ -37,13 +37,13 @@ func (_m *ThreadStore) CollectThreadsWithNewerReplies(userId string, channelIds
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// CreateMembershipIfNeeded provides a mock function with given fields: userId, postId, following
|
||||
func (_m *ThreadStore) CreateMembershipIfNeeded(userId string, postId string, following bool) error {
|
||||
ret := _m.Called(userId, postId, following)
|
||||
// CreateMembershipIfNeeded provides a mock function with given fields: userId, postId, following, incrementMentions, updateFollowing
|
||||
func (_m *ThreadStore) CreateMembershipIfNeeded(userId string, postId string, following bool, incrementMentions bool, updateFollowing bool) error {
|
||||
ret := _m.Called(userId, postId, following, incrementMentions, updateFollowing)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, bool) error); ok {
|
||||
r0 = rf(userId, postId, following)
|
||||
if rf, ok := ret.Get(0).(func(string, string, bool, bool, bool) error); ok {
|
||||
r0 = rf(userId, postId, following, incrementMentions, updateFollowing)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
@@ -148,6 +148,29 @@ func (_m *ThreadStore) GetMembershipsForUser(userId string) ([]*model.ThreadMemb
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetPosts provides a mock function with given fields: threadId, since
|
||||
func (_m *ThreadStore) GetPosts(threadId string, since int64) ([]*model.Post, error) {
|
||||
ret := _m.Called(threadId, since)
|
||||
|
||||
var r0 []*model.Post
|
||||
if rf, ok := ret.Get(0).(func(string, int64) []*model.Post); ok {
|
||||
r0 = rf(threadId, since)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.Post)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string, int64) error); ok {
|
||||
r1 = rf(threadId, since)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetThreadsForUser provides a mock function with given fields: userId, opts
|
||||
func (_m *ThreadStore) GetThreadsForUser(userId string, opts model.GetUserThreadsOpts) (*model.Threads, error) {
|
||||
ret := _m.Called(userId, opts)
|
||||
@@ -321,13 +344,13 @@ func (_m *ThreadStore) UpdateMembership(membership *model.ThreadMembership) (*mo
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// UpdateUnreadsByChannel provides a mock function with given fields: userId, changedThreads, timestamp
|
||||
func (_m *ThreadStore) UpdateUnreadsByChannel(userId string, changedThreads []string, timestamp int64) error {
|
||||
ret := _m.Called(userId, changedThreads, timestamp)
|
||||
// UpdateUnreadsByChannel provides a mock function with given fields: userId, changedThreads, timestamp, updateViewedTimestamp
|
||||
func (_m *ThreadStore) UpdateUnreadsByChannel(userId string, changedThreads []string, timestamp int64, updateViewedTimestamp bool) error {
|
||||
ret := _m.Called(userId, changedThreads, timestamp, updateViewedTimestamp)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, []string, int64) error); ok {
|
||||
r0 = rf(userId, changedThreads, timestamp)
|
||||
if rf, ok := ret.Get(0).(func(string, []string, int64, bool) error); ok {
|
||||
r0 = rf(userId, changedThreads, timestamp, updateViewedTimestamp)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
|
||||
t.Run("Thread last updated is changed when channel is updated after UpdateLastViewedAtPost", func(t *testing.T) {
|
||||
newPosts := makeSomePosts()
|
||||
|
||||
require.Nil(t, ss.Thread().CreateMembershipIfNeeded(newPosts[0].UserId, newPosts[0].Id, true))
|
||||
require.Nil(t, ss.Thread().CreateMembershipIfNeeded(newPosts[0].UserId, newPosts[0].Id, true, false, true))
|
||||
m, err1 := ss.Thread().GetMembershipForUser(newPosts[0].UserId, newPosts[0].Id)
|
||||
require.Nil(t, err1)
|
||||
m.LastUpdated -= 1000
|
||||
@@ -254,7 +254,7 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
|
||||
t.Run("Thread last updated is changed when channel is updated after IncrementMentionCount", func(t *testing.T) {
|
||||
newPosts := makeSomePosts()
|
||||
|
||||
require.Nil(t, ss.Thread().CreateMembershipIfNeeded(newPosts[0].UserId, newPosts[0].Id, true))
|
||||
require.Nil(t, ss.Thread().CreateMembershipIfNeeded(newPosts[0].UserId, newPosts[0].Id, true, false, true))
|
||||
m, err1 := ss.Thread().GetMembershipForUser(newPosts[0].UserId, newPosts[0].Id)
|
||||
require.Nil(t, err1)
|
||||
m.LastUpdated -= 1000
|
||||
@@ -274,7 +274,7 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
|
||||
t.Run("Thread last updated is changed when channel is updated after UpdateLastViewedAt", func(t *testing.T) {
|
||||
newPosts := makeSomePosts()
|
||||
|
||||
require.Nil(t, ss.Thread().CreateMembershipIfNeeded(newPosts[0].UserId, newPosts[0].Id, true))
|
||||
require.Nil(t, ss.Thread().CreateMembershipIfNeeded(newPosts[0].UserId, newPosts[0].Id, true, false, true))
|
||||
m, err1 := ss.Thread().GetMembershipForUser(newPosts[0].UserId, newPosts[0].Id)
|
||||
require.Nil(t, err1)
|
||||
m.LastUpdated -= 1000
|
||||
@@ -294,7 +294,7 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
|
||||
t.Run("Thread last updated is changed when channel is updated after UpdateLastViewedAtPost for mark unread", func(t *testing.T) {
|
||||
newPosts := makeSomePosts()
|
||||
|
||||
require.Nil(t, ss.Thread().CreateMembershipIfNeeded(newPosts[0].UserId, newPosts[0].Id, true))
|
||||
require.Nil(t, ss.Thread().CreateMembershipIfNeeded(newPosts[0].UserId, newPosts[0].Id, true, false, true))
|
||||
m, err1 := ss.Thread().GetMembershipForUser(newPosts[0].UserId, newPosts[0].Id)
|
||||
require.Nil(t, err1)
|
||||
m.LastUpdated += 1000
|
||||
|
||||
@@ -6920,10 +6920,10 @@ func (s *TimerLayerThreadStore) CollectThreadsWithNewerReplies(userId string, ch
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerThreadStore) CreateMembershipIfNeeded(userId string, postId string, following bool) error {
|
||||
func (s *TimerLayerThreadStore) CreateMembershipIfNeeded(userId string, postId string, following bool, incrementMentions bool, updateFollowing bool) error {
|
||||
start := timemodule.Now()
|
||||
|
||||
err := s.ThreadStore.CreateMembershipIfNeeded(userId, postId, following)
|
||||
err := s.ThreadStore.CreateMembershipIfNeeded(userId, postId, following, incrementMentions, updateFollowing)
|
||||
|
||||
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
@@ -7016,6 +7016,22 @@ func (s *TimerLayerThreadStore) GetMembershipsForUser(userId string) ([]*model.T
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerThreadStore) GetPosts(threadId string, since int64) ([]*model.Post, error) {
|
||||
start := timemodule.Now()
|
||||
|
||||
result, err := s.ThreadStore.GetPosts(threadId, since)
|
||||
|
||||
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("ThreadStore.GetPosts", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerThreadStore) GetThreadsForUser(userId string, opts model.GetUserThreadsOpts) (*model.Threads, error) {
|
||||
start := timemodule.Now()
|
||||
|
||||
@@ -7144,10 +7160,10 @@ func (s *TimerLayerThreadStore) UpdateMembership(membership *model.ThreadMembers
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerThreadStore) UpdateUnreadsByChannel(userId string, changedThreads []string, timestamp int64) error {
|
||||
func (s *TimerLayerThreadStore) UpdateUnreadsByChannel(userId string, changedThreads []string, timestamp int64, updateViewedTimestamp bool) error {
|
||||
start := timemodule.Now()
|
||||
|
||||
err := s.ThreadStore.UpdateUnreadsByChannel(userId, changedThreads, timestamp)
|
||||
err := s.ThreadStore.UpdateUnreadsByChannel(userId, changedThreads, timestamp, updateViewedTimestamp)
|
||||
|
||||
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
|
||||
Ссылка в новой задаче
Block a user