[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
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
3428cd15b6
Коммит
d58b048965
@@ -3344,6 +3344,14 @@ func setUnreadThreadByPostId(c *Context, w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
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)
|
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 {
|
if err != nil {
|
||||||
c.Err = err
|
c.Err = err
|
||||||
|
|||||||
@@ -6855,29 +6855,6 @@ func TestThreadSocketEvents(t *testing.T) {
|
|||||||
require.Truef(t, caught, "User should have received %s event", model.WebsocketEventThreadUpdated)
|
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)
|
_, resp, err = th.Client.UpdateThreadReadForUser(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, rpost.Id, replyPost.CreateAt+1)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
CheckOKStatus(t, resp)
|
CheckOKStatus(t, resp)
|
||||||
@@ -6907,6 +6884,31 @@ func TestThreadSocketEvents(t *testing.T) {
|
|||||||
require.Truef(t, caught, "User should have received %s event", model.WebsocketEventThreadReadChanged)
|
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)
|
_, resp, err = th.Client.SetThreadUnreadByPostId(context.Background(), th.BasicUser.Id, th.BasicTeam.Id, rpost.Id, rpost.Id)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
CheckOKStatus(t, resp)
|
CheckOKStatus(t, resp)
|
||||||
|
|||||||
@@ -2831,13 +2831,10 @@ func (a *App) UpdateThreadReadForUser(c request.CTX, currentSessionId, userID, t
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := store.ThreadMembershipOpts{
|
// If the thread doesn't have a membership, we shouldn't try to mark it as unread
|
||||||
Following: true,
|
membership, err := a.GetThreadMembershipForUser(userID, threadID)
|
||||||
UpdateFollowing: true,
|
if err != nil {
|
||||||
}
|
return nil, err
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
previousUnreadMentions := membership.UnreadMentions
|
previousUnreadMentions := membership.UnreadMentions
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -1930,7 +1929,7 @@ func TestPatchUser(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateThreadReadForUser(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()
|
th := Setup(t).InitBasic()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||||
@@ -1947,48 +1946,13 @@ func TestUpdateThreadReadForUser(t *testing.T) {
|
|||||||
require.Zero(t, threads.Total)
|
require.Zero(t, threads.Total)
|
||||||
|
|
||||||
_, appErr = th.App.UpdateThreadReadForUser(th.Context, "currentSessionId", th.BasicUser.Id, th.BasicChannel.TeamId, rootPost.Id, replyPost.CreateAt)
|
_, 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)
|
require.NotNil(t, appErr)
|
||||||
assert.Equal(t, http.StatusNotFound, appErr.StatusCode)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Ensure no panic on error", func(t *testing.T) {
|
_, err := th.App.Srv().Store().Thread().MaintainMembership(th.BasicUser.Id, rootPost.Id, store.ThreadMembershipOpts{Following: true, UpdateFollowing: true})
|
||||||
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,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
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)
|
_, appErr = th.App.UpdateThreadReadForUser(th.Context, "currentSessionId", th.BasicUser.Id, th.BasicChannel.TeamId, rootPost.Id, replyPost.CreateAt)
|
||||||
require.Error(t, err)
|
require.Nil(t, appErr)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {batchActions} from 'redux-batched-actions';
|
|||||||
|
|
||||||
import {updateThreadRead} from 'mattermost-redux/actions/threads';
|
import {updateThreadRead} from 'mattermost-redux/actions/threads';
|
||||||
import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
|
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 {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
||||||
import type {ThunkActionFunc} from 'mattermost-redux/types/actions';
|
import type {ThunkActionFunc} from 'mattermost-redux/types/actions';
|
||||||
|
|
||||||
@@ -56,8 +57,9 @@ export function markThreadAsRead(threadId: string): ThunkActionFunc<void, Global
|
|||||||
const state = getState();
|
const state = getState();
|
||||||
const currentUserId = getCurrentUserId(state);
|
const currentUserId = getCurrentUserId(state);
|
||||||
const currentTeamId = getCurrentTeamId(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
|
// mark thread as read on the server
|
||||||
dispatch(updateThreadRead(currentUserId, currentTeamId, threadId, Date.now()));
|
dispatch(updateThreadRead(currentUserId, currentTeamId, threadId, Date.now()));
|
||||||
}
|
}
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user