[MM-60603] Don't follow threads when marking them as read on focus (#28263)

* [MM-60603] Don't follow threads when marking them as read on focus

* Fix tests

* Fix lint

* Fix the original bug in the API call
Этот коммит содержится в:
Devin Binnie
2024-09-26 09:02:11 -04:00
коммит произвёл GitHub
родитель 3428cd15b6
Коммит d58b048965
5 изменённых файлов: 44 добавлений и 71 удалений

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

@@ -3344,6 +3344,14 @@ func setUnreadThreadByPostId(c *Context, w http.ResponseWriter, r *http.Request)
return
}
// We want to make sure the thread is followed when marking as unread
// https://mattermost.atlassian.net/browse/MM-36430
err := c.App.UpdateThreadFollowForUser(c.Params.UserId, c.Params.TeamId, c.Params.ThreadId, true)
if err != nil {
c.Err = err
return
}
thread, err := c.App.UpdateThreadReadForUserByPost(c.AppContext, c.AppContext.Session().Id, c.Params.UserId, c.Params.TeamId, c.Params.ThreadId, c.Params.PostId)
if err != nil {
c.Err = err

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

@@ -6855,29 +6855,6 @@ func TestThreadSocketEvents(t *testing.T) {
require.Truef(t, caught, "User should have received %s event", model.WebsocketEventThreadUpdated)
})
resp, err = th.Client.UpdateThreadFollowForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, rpost.Id, false)
require.NoError(t, err)
CheckOKStatus(t, resp)
t.Run("Listed for follow event", func(t *testing.T) {
var caught bool
func() {
for {
select {
case ev := <-userWSClient.EventChannel:
if ev.EventType() == model.WebsocketEventThreadFollowChanged {
caught = true
require.Equal(t, ev.GetData()["state"], false)
require.Equal(t, ev.GetData()["reply_count"], float64(1))
}
case <-time.After(2 * time.Second):
return
}
}
}()
require.Truef(t, caught, "User should have received %s event", model.WebsocketEventThreadFollowChanged)
})
_, resp, err = th.Client.UpdateThreadReadForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, rpost.Id, replyPost.CreateAt+1)
require.NoError(t, err)
CheckOKStatus(t, resp)
@@ -6907,6 +6884,31 @@ func TestThreadSocketEvents(t *testing.T) {
require.Truef(t, caught, "User should have received %s event", model.WebsocketEventThreadReadChanged)
})
resp, err = th.Client.UpdateThreadFollowForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, rpost.Id, false)
require.NoError(t, err)
CheckOKStatus(t, resp)
t.Run("Listed for follow event", func(t *testing.T) {
var caught bool
func() {
for {
select {
case ev := <-userWSClient.EventChannel:
if ev.EventType() == model.WebsocketEventThreadFollowChanged {
caught = true
require.Equal(t, ev.GetData()["state"], false)
require.Equal(t, ev.GetData()["reply_count"], float64(1))
}
case <-time.After(2 * time.Second):
return
}
}
}()
require.Truef(t, caught, "User should have received %s event", model.WebsocketEventThreadFollowChanged)
})
_, err = th.Client.UpdateThreadFollowForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, rpost.Id, true)
require.NoError(t, err)
_, resp, err = th.Client.SetThreadUnreadByPostId(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, rpost.Id, rpost.Id)
require.NoError(t, err)
CheckOKStatus(t, resp)

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

@@ -2831,13 +2831,10 @@ func (a *App) UpdateThreadReadForUser(c request.CTX, currentSessionId, userID, t
return nil, err
}
opts := store.ThreadMembershipOpts{
Following: true,
UpdateFollowing: true,
}
membership, storeErr := a.Srv().Store().Thread().MaintainMembership(userID, threadID, opts)
if storeErr != nil {
return nil, model.NewAppError("UpdateThreadReadForUser", "app.user.update_thread_read_for_user.app_error", nil, "", http.StatusInternalServerError).Wrap(storeErr)
// If the thread doesn't have a membership, we shouldn't try to mark it as unread
membership, err := a.GetThreadMembershipForUser(userID, threadID)
if err != nil {
return nil, err
}
previousUnreadMentions := membership.UnreadMentions

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

@@ -8,7 +8,6 @@ import (
"context"
"encoding/json"
"errors"
"net/http"
"path/filepath"
"strings"
"testing"
@@ -1930,7 +1929,7 @@ func TestPatchUser(t *testing.T) {
}
func TestUpdateThreadReadForUser(t *testing.T) {
t.Run("Ensure thread membership is created and followed", func(t *testing.T) {
t.Run("Ensure thread membership exists before updating read", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
th.App.UpdateConfig(func(cfg *model.Config) {
@@ -1947,48 +1946,13 @@ func TestUpdateThreadReadForUser(t *testing.T) {
require.Zero(t, threads.Total)
_, appErr = th.App.UpdateThreadReadForUser(th.Context, "currentSessionId", th.BasicUser.Id, th.BasicChannel.TeamId, rootPost.Id, replyPost.CreateAt)
require.Nil(t, appErr)
threads, appErr = th.App.GetThreadsForUser(th.BasicUser.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{})
require.Nil(t, appErr)
assert.NotZero(t, threads.Total)
threadMembership, appErr := th.App.GetThreadMembershipForUser(th.BasicUser.Id, rootPost.Id)
require.Nil(t, appErr)
require.NotNil(t, threadMembership)
assert.True(t, threadMembership.Following)
_, appErr = th.App.GetThreadMembershipForUser(th.BasicUser.Id, "notfound")
require.NotNil(t, appErr)
assert.Equal(t, http.StatusNotFound, appErr.StatusCode)
})
t.Run("Ensure no panic on error", func(t *testing.T) {
th := SetupWithStoreMock(t)
defer th.TearDown()
mockStore := th.App.Srv().Store().(*storemocks.Store)
mockUserStore := storemocks.UserStore{}
mockUserStore.On("Count", mock.Anything).Return(int64(10), nil)
mockUserStore.On("Get", mock.Anything, "user1").Return(&model.User{Id: "user1"}, nil)
mockThreadStore := storemocks.ThreadStore{}
mockThreadStore.On("MaintainMembership", "user1", "postid", mock.Anything).Return(nil, errors.New("error"))
var err error
th.App.ch.srv.userService, err = users.New(users.ServiceConfig{
UserStore: &mockUserStore,
SessionStore: &storemocks.SessionStore{},
OAuthStore: &storemocks.OAuthStore{},
ConfigFn: th.App.ch.srv.platform.Config,
LicenseFn: th.App.ch.srv.License,
})
_, err := th.App.Srv().Store().Thread().MaintainMembership(th.BasicUser.Id, rootPost.Id, store.ThreadMembershipOpts{Following: true, UpdateFollowing: true})
require.NoError(t, err)
mockStore.On("User").Return(&mockUserStore)
mockStore.On("Thread").Return(&mockThreadStore)
_, err = th.App.UpdateThreadReadForUser(th.Context, "currentSessionId", "user1", "team1", "postid", 100)
require.Error(t, err)
_, appErr = th.App.UpdateThreadReadForUser(th.Context, "currentSessionId", th.BasicUser.Id, th.BasicChannel.TeamId, rootPost.Id, replyPost.CreateAt)
require.Nil(t, appErr)
})
}

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

@@ -5,6 +5,7 @@ import {batchActions} from 'redux-batched-actions';
import {updateThreadRead} from 'mattermost-redux/actions/threads';
import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
import {getThread} from 'mattermost-redux/selectors/entities/threads';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
import type {ThunkActionFunc} from 'mattermost-redux/types/actions';
@@ -56,8 +57,9 @@ export function markThreadAsRead(threadId: string): ThunkActionFunc<void, Global
const state = getState();
const currentUserId = getCurrentUserId(state);
const currentTeamId = getCurrentTeamId(state);
const thread = getThread(state, threadId);
if (isThreadOpen(state, threadId) && window.isActive && !isThreadManuallyUnread(state, threadId)) {
if (thread && isThreadOpen(state, threadId) && window.isActive && !isThreadManuallyUnread(state, threadId)) {
// mark thread as read on the server
dispatch(updateThreadRead(currentUserId, currentTeamId, threadId, Date.now()));
}