MM-34758 Collapsed Reply Threads without mobile support (#17424)
Summary added support for legacy clients accessing server added collapsed_threads_supported param to viewChannel API and setPostUnread API Ticket Link https://mattermost.atlassian.net/browse/MM-34758 Related Webapp PR mattermost/mattermost-webapp#7933
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
ebed0c67f7
Коммит
46649292f8
@@ -1330,7 +1330,7 @@ func viewChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
times, err := c.App.ViewChannel(view, c.Params.UserId, c.AppContext.Session().Id)
|
||||
times, err := c.App.ViewChannel(view, c.Params.UserId, c.AppContext.Session().Id, view.CollapsedThreadsSupported)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -4178,6 +4179,7 @@ func TestMoveChannel(t *testing.T) {
|
||||
func TestRootMentionsCount(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
Client := th.Client
|
||||
user := th.BasicUser
|
||||
channel := th.BasicChannel
|
||||
@@ -4214,3 +4216,44 @@ func TestRootMentionsCount(t *testing.T) {
|
||||
require.Equal(t, int64(1), counts.MentionCountRoot)
|
||||
require.Equal(t, int64(2), counts.MentionCount)
|
||||
}
|
||||
|
||||
func TestViewChannelWithoutCollapsedThreads(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
os.Setenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS", "true")
|
||||
defer os.Unsetenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS")
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.ThreadAutoFollow = true
|
||||
*cfg.ServiceSettings.CollapsedThreads = model.COLLAPSED_THREADS_DEFAULT_ON
|
||||
})
|
||||
|
||||
Client := th.Client
|
||||
user := th.BasicUser
|
||||
team := th.BasicTeam
|
||||
channel := th.BasicChannel
|
||||
|
||||
// mention the user in a root post
|
||||
post1, resp := th.SystemAdminClient.CreatePost(&model.Post{ChannelId: channel.Id, Message: "hey @" + user.Username})
|
||||
CheckNoError(t, resp)
|
||||
// mention the user in a reply post
|
||||
post2 := &model.Post{ChannelId: channel.Id, Message: "reply at @" + user.Username, RootId: post1.Id}
|
||||
_, resp = th.SystemAdminClient.CreatePost(post2)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
threads, resp := Client.GetUserThreads(user.Id, team.Id, model.GetUserThreadsOpts{})
|
||||
CheckNoError(t, resp)
|
||||
require.EqualValues(t, int64(1), threads.TotalUnreadMentions)
|
||||
|
||||
// simulate opening the channel from an old client
|
||||
_, resp = Client.ViewChannel(user.Id, &model.ChannelView{
|
||||
ChannelId: channel.Id,
|
||||
PrevChannelId: "",
|
||||
CollapsedThreadsSupported: false,
|
||||
})
|
||||
CheckNoError(t, resp)
|
||||
|
||||
threads, resp = Client.GetUserThreads(user.Id, team.Id, model.GetUserThreadsOpts{})
|
||||
CheckNoError(t, resp)
|
||||
require.Zero(t, threads.TotalUnreadMentions)
|
||||
}
|
||||
|
||||
@@ -639,11 +639,15 @@ func patchPost(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(patchedPost.ToJson()))
|
||||
}
|
||||
|
||||
func setPostUnread(c *Context, w http.ResponseWriter, _ *http.Request) {
|
||||
func setPostUnread(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequirePostId().RequireUserId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
props := model.MapBoolFromJson(r.Body)
|
||||
collapsedThreadsSupported := props["collapsed_threads_supported"]
|
||||
|
||||
if c.AppContext.Session().UserId != c.Params.UserId && !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), c.Params.UserId) {
|
||||
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
|
||||
return
|
||||
@@ -653,7 +657,7 @@ func setPostUnread(c *Context, w http.ResponseWriter, _ *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
state, err := c.App.MarkChannelAsUnreadFromPost(c.Params.PostId, c.Params.UserId)
|
||||
state, err := c.App.MarkChannelAsUnreadFromPost(c.Params.PostId, c.Params.UserId, collapsedThreadsSupported)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
|
||||
@@ -2493,14 +2493,14 @@ func TestSetChannelUnread(t *testing.T) {
|
||||
unread, err = th.App.GetChannelUnread(c1.Id, u2.Id)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, int64(4), unread.MsgCount)
|
||||
_, err = th.App.ViewChannel(c1toc2, u2.Id, s2.Id)
|
||||
_, err = th.App.ViewChannel(c1toc2, u2.Id, s2.Id, false)
|
||||
require.Nil(t, err)
|
||||
unread, err = th.App.GetChannelUnread(c1.Id, u2.Id)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, int64(0), unread.MsgCount)
|
||||
|
||||
t.Run("Unread last one", func(t *testing.T) {
|
||||
r := th.Client.SetPostUnread(u1.Id, p2.Id)
|
||||
r := th.Client.SetPostUnread(u1.Id, p2.Id, true)
|
||||
checkHTTPStatus(t, r, 200, false)
|
||||
unread, err := th.App.GetChannelUnread(c1.Id, u1.Id)
|
||||
require.Nil(t, err)
|
||||
@@ -2508,12 +2508,12 @@ func TestSetChannelUnread(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Unread on a private channel", func(t *testing.T) {
|
||||
r := th.Client.SetPostUnread(u1.Id, pp2.Id)
|
||||
r := th.Client.SetPostUnread(u1.Id, pp2.Id, true)
|
||||
assert.Equal(t, 200, r.StatusCode)
|
||||
unread, err := th.App.GetChannelUnread(th.BasicPrivateChannel.Id, u1.Id)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, int64(1), unread.MsgCount)
|
||||
r = th.Client.SetPostUnread(u1.Id, pp1.Id)
|
||||
r = th.Client.SetPostUnread(u1.Id, pp1.Id, true)
|
||||
assert.Equal(t, 200, r.StatusCode)
|
||||
unread, err = th.App.GetChannelUnread(th.BasicPrivateChannel.Id, u1.Id)
|
||||
require.Nil(t, err)
|
||||
@@ -2521,7 +2521,7 @@ func TestSetChannelUnread(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Can't unread an imaginary post", func(t *testing.T) {
|
||||
r := th.Client.SetPostUnread(u1.Id, "invalid4ofngungryquinj976y")
|
||||
r := th.Client.SetPostUnread(u1.Id, "invalid4ofngungryquinj976y", true)
|
||||
assert.Equal(t, http.StatusForbidden, r.StatusCode)
|
||||
})
|
||||
|
||||
@@ -2531,18 +2531,18 @@ func TestSetChannelUnread(t *testing.T) {
|
||||
c3.Login(u3.Email, u3.Password)
|
||||
|
||||
t.Run("Can't unread channels you don't belong to", func(t *testing.T) {
|
||||
r := c3.SetPostUnread(u3.Id, pp1.Id)
|
||||
r := c3.SetPostUnread(u3.Id, pp1.Id, true)
|
||||
assert.Equal(t, http.StatusForbidden, r.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("Can't unread users you don't have permission to edit", func(t *testing.T) {
|
||||
r := c3.SetPostUnread(u1.Id, pp1.Id)
|
||||
r := c3.SetPostUnread(u1.Id, pp1.Id, true)
|
||||
assert.Equal(t, http.StatusForbidden, r.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("Can't unread if user is not logged in", func(t *testing.T) {
|
||||
th.Client.Logout()
|
||||
response := th.Client.SetPostUnread(u1.Id, p2.Id)
|
||||
response := th.Client.SetPostUnread(u1.Id, p2.Id, true)
|
||||
checkHTTPStatus(t, response, http.StatusUnauthorized, true)
|
||||
})
|
||||
}
|
||||
@@ -2565,7 +2565,7 @@ func TestMarkUnreadCausesAutofollow(t *testing.T) {
|
||||
require.Nil(t, appErr)
|
||||
require.Zero(t, threads.Total)
|
||||
|
||||
_, appErr = th.App.MarkChannelAsUnreadFromPost(replyPost.Id, th.BasicUser.Id)
|
||||
_, appErr = th.App.MarkChannelAsUnreadFromPost(replyPost.Id, th.BasicUser.Id, true)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
threads, appErr = th.App.GetThreadsForUser(th.BasicUser.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{})
|
||||
@@ -2573,3 +2573,70 @@ func TestMarkUnreadCausesAutofollow(t *testing.T) {
|
||||
require.NotZero(t, threads.Total)
|
||||
|
||||
}
|
||||
|
||||
func TestSetPostUnreadWithoutCollapsedThreads(t *testing.T) {
|
||||
os.Setenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS", "true")
|
||||
defer os.Unsetenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS")
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.ThreadAutoFollow = true
|
||||
*cfg.ServiceSettings.CollapsedThreads = model.COLLAPSED_THREADS_DEFAULT_ON
|
||||
})
|
||||
|
||||
// user2: first root mention @user1
|
||||
// - user1: hello
|
||||
// - user2: mention @u1
|
||||
// - user1: another repoy
|
||||
// - user2: another mention @u1
|
||||
// user1: a root post
|
||||
// user2: Another root mention @u1
|
||||
user1Mention := " @" + th.BasicUser.Username
|
||||
rootPost1, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "first root mention" + user1Mention}, th.BasicChannel, false, false)
|
||||
require.Nil(t, appErr)
|
||||
_, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost1.Id, UserId: th.BasicUser.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hello"}, th.BasicChannel, false, false)
|
||||
require.Nil(t, appErr)
|
||||
replyPost1, appErr := th.App.CreatePost(th.Context, &model.Post{RootId: rootPost1.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "mention" + user1Mention}, th.BasicChannel, false, false)
|
||||
require.Nil(t, appErr)
|
||||
_, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost1.Id, UserId: th.BasicUser.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "another reply"}, th.BasicChannel, false, false)
|
||||
require.Nil(t, appErr)
|
||||
_, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost1.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "another mention" + user1Mention}, th.BasicChannel, false, false)
|
||||
require.Nil(t, appErr)
|
||||
_, appErr = th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "a root post"}, th.BasicChannel, false, false)
|
||||
require.Nil(t, appErr)
|
||||
_, appErr = th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "another root mention" + user1Mention}, th.BasicChannel, false, false)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
t.Run("Mark reply post as unread", func(t *testing.T) {
|
||||
resp := th.Client.SetPostUnread(th.BasicUser.Id, replyPost1.Id, false)
|
||||
CheckNoError(t, resp)
|
||||
channelUnread, appErr := th.App.GetChannelUnread(th.BasicChannel.Id, th.BasicUser.Id)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
require.Equal(t, int64(3), channelUnread.MentionCount)
|
||||
// MentionCountRoot should be zero so that supported clients don't show a mention badge for the channel
|
||||
require.Equal(t, int64(0), channelUnread.MentionCountRoot)
|
||||
|
||||
require.Equal(t, int64(5), channelUnread.MsgCount)
|
||||
// MentionCountRoot should be zero so that supported clients don't show the channel as unread
|
||||
require.Equal(t, channelUnread.MsgCountRoot, int64(0))
|
||||
|
||||
thread, err := th.App.GetThreadForUser(th.BasicUser.Id, th.BasicTeam.Id, rootPost1.Id, false)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, int64(2), thread.UnreadMentions)
|
||||
require.Equal(t, int64(3), thread.UnreadReplies)
|
||||
})
|
||||
|
||||
t.Run("Mark root post as unread", func(t *testing.T) {
|
||||
resp := th.Client.SetPostUnread(th.BasicUser.Id, rootPost1.Id, false)
|
||||
CheckNoError(t, resp)
|
||||
channelUnread, appErr := th.App.GetChannelUnread(th.BasicChannel.Id, th.BasicUser.Id)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
require.Equal(t, int64(4), channelUnread.MentionCount)
|
||||
require.Equal(t, int64(2), channelUnread.MentionCountRoot)
|
||||
|
||||
require.Equal(t, int64(7), channelUnread.MsgCount)
|
||||
require.Equal(t, int64(3), channelUnread.MsgCountRoot)
|
||||
})
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user