MM-36429 Do not increment mention counts when marking root posts with no replies as unread (#17826)

Automatic Merge
Этот коммит содержится в:
Joram Wilander
2021-06-24 16:35:03 -04:00
коммит произвёл GitHub
родитель 33e2ae4775
Коммит e08ebf3871
5 изменённых файлов: 140 добавлений и 36 удалений

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

@@ -2503,7 +2503,7 @@ func (a *App) MarkChannelAsUnreadFromPost(postID string, userID string, collapse
if threadMembership == nil { if threadMembership == nil {
opts := store.ThreadMembershipOpts{ opts := store.ThreadMembershipOpts{
Following: followThread, Following: followThread,
IncrementMentions: true, IncrementMentions: false,
UpdateFollowing: true, UpdateFollowing: true,
UpdateViewedTimestamp: true, UpdateViewedTimestamp: true,
UpdateParticipants: false, UpdateParticipants: false,

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

@@ -2212,37 +2212,136 @@ func TestMarkUnreadWithThreads(t *testing.T) {
*cfg.ServiceSettings.CollapsedThreads = model.COLLAPSED_THREADS_DEFAULT_ON *cfg.ServiceSettings.CollapsedThreads = model.COLLAPSED_THREADS_DEFAULT_ON
}) })
rootPost, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi"}, th.BasicChannel, false, false) t.Run("Follow threads only if specified", func(t *testing.T) {
require.Nil(t, appErr) rootPost, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi"}, th.BasicChannel, false, false)
replyPost, appErr := th.App.CreatePost(th.Context, &model.Post{RootId: rootPost.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi"}, th.BasicChannel, false, false) require.Nil(t, appErr)
require.Nil(t, appErr) replyPost, appErr := th.App.CreatePost(th.Context, &model.Post{RootId: rootPost.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi"}, th.BasicChannel, false, false)
threads, appErr := th.App.GetThreadsForUser(th.BasicUser.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{}) require.Nil(t, appErr)
require.Nil(t, appErr) threads, appErr := th.App.GetThreadsForUser(th.BasicUser.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{})
require.Zero(t, threads.Total) require.Nil(t, appErr)
require.Zero(t, threads.Total)
_, appErr = th.App.MarkChannelAsUnreadFromPost(replyPost.Id, th.BasicUser.Id, true, true) _, appErr = th.App.MarkChannelAsUnreadFromPost(replyPost.Id, th.BasicUser.Id, true, true)
require.Nil(t, appErr) require.Nil(t, appErr)
threads, appErr = th.App.GetThreadsForUser(th.BasicUser.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{}) threads, appErr = th.App.GetThreadsForUser(th.BasicUser.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{})
require.Nil(t, appErr) require.Nil(t, appErr)
require.NotZero(t, threads.Total) require.NotZero(t, threads.Total)
threadMembership, appErr := th.App.GetThreadMembershipForUser(th.BasicUser.Id, replyPost.RootId) threadMembership, appErr := th.App.GetThreadMembershipForUser(th.BasicUser.Id, replyPost.RootId)
require.Nil(t, appErr) require.Nil(t, appErr)
require.NotNil(t, threadMembership) require.NotNil(t, threadMembership)
assert.True(t, threadMembership.Following) assert.True(t, threadMembership.Following)
// Create a new thread // Create a new thread
rootPost, appErr = th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi2"}, th.BasicChannel, false, false) rootPost, appErr = th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi2"}, th.BasicChannel, false, false)
require.Nil(t, appErr) require.Nil(t, appErr)
replyPost, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi2"}, th.BasicChannel, false, false) replyPost, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi2"}, th.BasicChannel, false, false)
require.Nil(t, appErr) require.Nil(t, appErr)
_, appErr = th.App.MarkChannelAsUnreadFromPost(replyPost.Id, th.BasicUser.Id, true, false) _, appErr = th.App.MarkChannelAsUnreadFromPost(replyPost.Id, th.BasicUser.Id, true, false)
require.Nil(t, appErr) require.Nil(t, appErr)
threadMembership, appErr = th.App.GetThreadMembershipForUser(th.BasicUser.Id, replyPost.RootId) threadMembership, appErr = th.App.GetThreadMembershipForUser(th.BasicUser.Id, replyPost.RootId)
require.Nil(t, appErr) require.Nil(t, appErr)
require.NotNil(t, threadMembership) require.NotNil(t, threadMembership)
assert.False(t, threadMembership.Following) assert.False(t, threadMembership.Following)
})
t.Run("Set unread mentions correctly", func(t *testing.T) {
t.Run("Never followed root post with no replies or mentions", func(t *testing.T) {
rootPost, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi"}, th.BasicChannel, false, false)
require.Nil(t, appErr)
_, appErr = th.App.MarkChannelAsUnreadFromPost(rootPost.Id, th.BasicUser.Id, true, true)
require.Nil(t, appErr)
threadMembership, appErr := th.App.GetThreadMembershipForUser(th.BasicUser.Id, rootPost.Id)
require.Nil(t, appErr)
require.NotNil(t, threadMembership)
assert.Zero(t, threadMembership.UnreadMentions)
})
t.Run("Never followed root post with replies and no mentions", func(t *testing.T) {
rootPost, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi"}, th.BasicChannel, false, false)
require.Nil(t, appErr)
_, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi"}, th.BasicChannel, false, false)
require.Nil(t, appErr)
_, appErr = th.App.MarkChannelAsUnreadFromPost(rootPost.Id, th.BasicUser.Id, true, true)
require.Nil(t, appErr)
threadMembership, appErr := th.App.GetThreadMembershipForUser(th.BasicUser.Id, rootPost.Id)
require.Nil(t, appErr)
require.NotNil(t, threadMembership)
assert.Zero(t, threadMembership.UnreadMentions)
})
t.Run("Never followed root post with replies and mentions", func(t *testing.T) {
rootPost, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi"}, th.BasicChannel, false, false)
require.Nil(t, appErr)
_, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi @" + th.BasicUser.Username}, th.BasicChannel, false, false)
require.Nil(t, appErr)
_, appErr = th.App.MarkChannelAsUnreadFromPost(rootPost.Id, th.BasicUser.Id, true, true)
require.Nil(t, appErr)
threadMembership, appErr := th.App.GetThreadMembershipForUser(th.BasicUser.Id, rootPost.Id)
require.Nil(t, appErr)
require.NotNil(t, threadMembership)
assert.Equal(t, int64(1), threadMembership.UnreadMentions)
})
t.Run("Previously followed root post with no replies or mentions", func(t *testing.T) {
rootPost, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi"}, th.BasicChannel, false, false)
require.Nil(t, appErr)
appErr = th.App.UpdateThreadFollowForUser(th.BasicUser.Id, th.BasicTeam.Id, rootPost.Id, true)
require.Nil(t, appErr)
appErr = th.App.UpdateThreadFollowForUser(th.BasicUser.Id, th.BasicTeam.Id, rootPost.Id, false)
require.Nil(t, appErr)
_, appErr = th.App.MarkChannelAsUnreadFromPost(rootPost.Id, th.BasicUser.Id, true, true)
require.Nil(t, appErr)
threadMembership, appErr := th.App.GetThreadMembershipForUser(th.BasicUser.Id, rootPost.Id)
require.Nil(t, appErr)
require.NotNil(t, threadMembership)
assert.Zero(t, threadMembership.UnreadMentions)
})
t.Run("Previously followed root post with replies and no mentions", func(t *testing.T) {
rootPost, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi"}, th.BasicChannel, false, false)
require.Nil(t, appErr)
_, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi"}, th.BasicChannel, false, false)
require.Nil(t, appErr)
appErr = th.App.UpdateThreadFollowForUser(th.BasicUser.Id, th.BasicTeam.Id, rootPost.Id, true)
require.Nil(t, appErr)
appErr = th.App.UpdateThreadFollowForUser(th.BasicUser.Id, th.BasicTeam.Id, rootPost.Id, false)
require.Nil(t, appErr)
_, appErr = th.App.MarkChannelAsUnreadFromPost(rootPost.Id, th.BasicUser.Id, true, true)
require.Nil(t, appErr)
threadMembership, appErr := th.App.GetThreadMembershipForUser(th.BasicUser.Id, rootPost.Id)
require.Nil(t, appErr)
require.NotNil(t, threadMembership)
assert.Zero(t, threadMembership.UnreadMentions)
})
t.Run("Previously followed root post with replies and mentions", func(t *testing.T) {
rootPost, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi"}, th.BasicChannel, false, false)
require.Nil(t, appErr)
_, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi @" + th.BasicUser.Username}, th.BasicChannel, false, false)
require.Nil(t, appErr)
appErr = th.App.UpdateThreadFollowForUser(th.BasicUser.Id, th.BasicTeam.Id, rootPost.Id, true)
require.Nil(t, appErr)
appErr = th.App.UpdateThreadFollowForUser(th.BasicUser.Id, th.BasicTeam.Id, rootPost.Id, false)
require.Nil(t, appErr)
_, appErr = th.App.MarkChannelAsUnreadFromPost(rootPost.Id, th.BasicUser.Id, true, true)
require.Nil(t, appErr)
threadMembership, appErr := th.App.GetThreadMembershipForUser(th.BasicUser.Id, rootPost.Id)
require.Nil(t, appErr)
require.NotNil(t, threadMembership)
assert.Equal(t, int64(1), threadMembership.UnreadMentions)
})
})
} }

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

@@ -2228,14 +2228,17 @@ func (a *App) UpdateThreadFollowForUser(userID, teamID, threadID string, state b
return model.NewAppError("UpdateThreadFollowForUser", "app.user.update_thread_follow_for_user.app_error", nil, err.Error(), http.StatusInternalServerError) return model.NewAppError("UpdateThreadFollowForUser", "app.user.update_thread_follow_for_user.app_error", nil, err.Error(), http.StatusInternalServerError)
} }
thread, err := a.Srv().Store.Thread().Get(threadID) thread, err := a.Srv().Store.Thread().Get(threadID)
if err != nil { if err != nil {
return model.NewAppError("UpdateThreadFollowForUser", "app.user.update_thread_follow_for_user.app_error", nil, err.Error(), http.StatusInternalServerError) return model.NewAppError("UpdateThreadFollowForUser", "app.user.update_thread_follow_for_user.app_error", nil, err.Error(), http.StatusInternalServerError)
} }
replyCount := int64(0)
if thread != nil {
replyCount = thread.ReplyCount
}
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_THREAD_FOLLOW_CHANGED, teamID, "", userID, nil) message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_THREAD_FOLLOW_CHANGED, teamID, "", userID, nil)
message.Add("thread_id", threadID) message.Add("thread_id", threadID)
message.Add("state", state) message.Add("state", state)
message.Add("reply_count", thread.ReplyCount) message.Add("reply_count", replyCount)
a.Publish(message) a.Publish(message)
return nil return nil
} }

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

@@ -110,7 +110,7 @@ func (s *SqlThreadStore) get(ex gorp.SqlExecutor, id string) (*model.Thread, err
err := ex.SelectOne(&thread, query, args...) err := ex.SelectOne(&thread, query, args...)
if err != nil { if err != nil {
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return nil, store.NewErrNotFound("Thread", id) return nil, nil
} }
return nil, errors.Wrapf(err, "failed to get thread with id=%s", id) return nil, errors.Wrapf(err, "failed to get thread with id=%s", id)
@@ -626,7 +626,7 @@ func (s *SqlThreadStore) MaintainMembership(userId, postId string, opts store.Th
if getErr != nil { if getErr != nil {
return nil, getErr return nil, getErr
} }
if !thread.Participants.Contains(userId) { if thread != nil && !thread.Participants.Contains(userId) {
thread.Participants = append(thread.Participants, userId) thread.Participants = append(thread.Participants, userId)
if _, err = s.update(trx, thread); err != nil { if _, err = s.update(trx, thread); err != nil {
return nil, err return nil, err

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

@@ -469,8 +469,9 @@ func testThreadStorePermanentDeleteBatchForRetentionPolicies(t *testing.T, ss st
nowMillis := thread.LastReplyAt + *channelPolicy.PostDuration*24*60*60*1000 + 1 nowMillis := thread.LastReplyAt + *channelPolicy.PostDuration*24*60*60*1000 + 1
_, _, err = ss.Thread().PermanentDeleteBatchForRetentionPolicies(nowMillis, 0, limit, model.RetentionPolicyCursor{}) _, _, err = ss.Thread().PermanentDeleteBatchForRetentionPolicies(nowMillis, 0, limit, model.RetentionPolicyCursor{})
require.NoError(t, err) require.NoError(t, err)
_, err = ss.Thread().Get(post.Id) thread, err = ss.Thread().Get(post.Id)
require.Error(t, err, "thread should have been deleted by channel policy") assert.NoError(t, err)
assert.Nil(t, thread, "thread should have been deleted by channel policy")
// create a new thread // create a new thread
threadStoreCreateReply(t, ss, channel.Id, post.Id, 2000) threadStoreCreateReply(t, ss, channel.Id, post.Id, 2000)
@@ -498,8 +499,9 @@ func testThreadStorePermanentDeleteBatchForRetentionPolicies(t *testing.T, ss st
require.NoError(t, err) require.NoError(t, err)
_, _, err = ss.Thread().PermanentDeleteBatchForRetentionPolicies(nowMillis, 0, limit, model.RetentionPolicyCursor{}) _, _, err = ss.Thread().PermanentDeleteBatchForRetentionPolicies(nowMillis, 0, limit, model.RetentionPolicyCursor{})
require.NoError(t, err) require.NoError(t, err)
_, err = ss.Thread().Get(post.Id) thread, err = ss.Thread().Get(post.Id)
require.Error(t, err, "thread should have been deleted by team policy") assert.NoError(t, err)
assert.Nil(t, thread, "thread should have been deleted by team policy")
} }
func testThreadStorePermanentDeleteBatchThreadMembershipsForRetentionPolicies(t *testing.T, ss store.Store) { func testThreadStorePermanentDeleteBatchThreadMembershipsForRetentionPolicies(t *testing.T, ss store.Store) {