mirror of
https://github.com/mattermost/mattermost.git
synced 2026-09-24 16:05:00 +08:00
MM-40148: threadsOnly query param for user threads (#19833)
* MM-40148: threadsOnly query param for user threads Currently we always calculate counts when fetching user threads. Those counts include total, unread replies, and unread mentions, and are potentially expensive to calculate. This commit adds a new query param 'threadsOnly' which won't calculate any counts and just return threads. Co-authored-by: koox00 <3829551+koox00@users.noreply.github.com> Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
This commit is contained in:
co-authored by
koox00
Mattermod
parent
27bd16e5f5
commit
9521797b15
+20
-10
@@ -3008,14 +3008,15 @@ func getThreadsForUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
options := model.GetUserThreadsOpts{
|
||||
Since: 0,
|
||||
Before: "",
|
||||
After: "",
|
||||
PageSize: uint64(c.Params.PerPage),
|
||||
Unread: false,
|
||||
Extended: false,
|
||||
Deleted: false,
|
||||
TotalsOnly: false,
|
||||
Since: 0,
|
||||
Before: "",
|
||||
After: "",
|
||||
PageSize: uint64(c.Params.PerPage),
|
||||
Unread: false,
|
||||
Extended: false,
|
||||
Deleted: false,
|
||||
TotalsOnly: false,
|
||||
ThreadsOnly: false,
|
||||
}
|
||||
|
||||
sinceString := r.URL.Query().Get("since")
|
||||
@@ -3030,21 +3031,30 @@ func getThreadsForUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
options.Before = r.URL.Query().Get("before")
|
||||
options.After = r.URL.Query().Get("after")
|
||||
totalsOnlyStr := r.URL.Query().Get("totalsOnly")
|
||||
threadsOnlyStr := r.URL.Query().Get("threadsOnly")
|
||||
options.TotalsOnly, _ = strconv.ParseBool(totalsOnlyStr)
|
||||
options.ThreadsOnly, _ = strconv.ParseBool(threadsOnlyStr)
|
||||
|
||||
// parameters are mutually exclusive
|
||||
if options.Before != "" && options.After != "" {
|
||||
c.Err = model.NewAppError("api.getThreadsForUser", "api.getThreadsForUser.bad_params", nil, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// parameters are mutually exclusive
|
||||
if options.TotalsOnly && options.ThreadsOnly {
|
||||
c.Err = model.NewAppError("api.getThreadsForUser", "api.getThreadsForUser.bad_only_params", nil, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
deletedStr := r.URL.Query().Get("deleted")
|
||||
unreadStr := r.URL.Query().Get("unread")
|
||||
extendedStr := r.URL.Query().Get("extended")
|
||||
totalsOnlyStr := r.URL.Query().Get("totalsOnly")
|
||||
|
||||
options.Deleted, _ = strconv.ParseBool(deletedStr)
|
||||
options.Unread, _ = strconv.ParseBool(unreadStr)
|
||||
options.Extended, _ = strconv.ParseBool(extendedStr)
|
||||
options.TotalsOnly, _ = strconv.ParseBool(totalsOnlyStr)
|
||||
|
||||
threads, err := c.App.GetThreadsForUser(c.Params.UserId, c.Params.TeamId, options)
|
||||
if err != nil {
|
||||
|
||||
@@ -5754,6 +5754,96 @@ func TestGetThreadsForUser(t *testing.T) {
|
||||
require.Len(t, uss3.Threads, 0)
|
||||
})
|
||||
|
||||
t.Run("totalsOnly param", func(t *testing.T) {
|
||||
client := th.Client
|
||||
sysadminClient := th.SystemAdminClient
|
||||
|
||||
var rootIds []*model.Post
|
||||
for i := 0; i < 10; i++ {
|
||||
rpost, resp, err := client.CreatePost(&model.Post{ChannelId: th.BasicChannel.Id, Message: "testMsg"})
|
||||
require.NoError(t, err)
|
||||
CheckCreatedStatus(t, resp)
|
||||
rootIds = append(rootIds, rpost)
|
||||
if i%2 == 0 {
|
||||
_, resp, err = client.CreatePost(&model.Post{ChannelId: th.BasicChannel.Id, Message: "testReply", RootId: rpost.Id})
|
||||
} else {
|
||||
_, resp, err = sysadminClient.CreatePost(&model.Post{ChannelId: th.BasicChannel.Id, Message: "testReply @" + th.BasicUser.Username, RootId: rpost.Id})
|
||||
}
|
||||
require.NoError(t, err)
|
||||
CheckCreatedStatus(t, resp)
|
||||
}
|
||||
|
||||
defer th.App.Srv().Store.Post().PermanentDeleteByUser(th.BasicUser.Id)
|
||||
defer th.App.Srv().Store.Post().PermanentDeleteByUser(th.SystemAdminUser.Id)
|
||||
|
||||
uss, _, err := th.Client.GetUserThreads(th.BasicUser.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{
|
||||
Deleted: false,
|
||||
TotalsOnly: true,
|
||||
PageSize: 30,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, uss.Threads, 0)
|
||||
require.Len(t, rootIds, 10)
|
||||
require.Equal(t, int64(10), uss.Total)
|
||||
require.Equal(t, int64(5), uss.TotalUnreadThreads)
|
||||
require.Equal(t, int64(5), uss.TotalUnreadMentions)
|
||||
})
|
||||
|
||||
t.Run("threadsOnly param", func(t *testing.T) {
|
||||
client := th.Client
|
||||
sysadminClient := th.SystemAdminClient
|
||||
|
||||
var rootIds []*model.Post
|
||||
for i := 0; i < 10; i++ {
|
||||
rpost, resp, err := client.CreatePost(&model.Post{ChannelId: th.BasicChannel.Id, Message: "testMsg"})
|
||||
require.NoError(t, err)
|
||||
CheckCreatedStatus(t, resp)
|
||||
rootIds = append(rootIds, rpost)
|
||||
if i%2 == 0 {
|
||||
_, resp, err = client.CreatePost(&model.Post{ChannelId: th.BasicChannel.Id, Message: "testReply", RootId: rpost.Id})
|
||||
} else {
|
||||
_, resp, err = sysadminClient.CreatePost(&model.Post{ChannelId: th.BasicChannel.Id, Message: "testReply @" + th.BasicUser.Username, RootId: rpost.Id})
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
CheckCreatedStatus(t, resp)
|
||||
}
|
||||
|
||||
defer th.App.Srv().Store.Post().PermanentDeleteByUser(th.BasicUser.Id)
|
||||
defer th.App.Srv().Store.Post().PermanentDeleteByUser(th.SystemAdminUser.Id)
|
||||
|
||||
uss, _, err := th.Client.GetUserThreads(th.BasicUser.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{
|
||||
Deleted: false,
|
||||
ThreadsOnly: true,
|
||||
PageSize: 30,
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, rootIds, 10)
|
||||
require.Len(t, uss.Threads, 10)
|
||||
require.Equal(t, int64(0), uss.Total)
|
||||
require.Equal(t, int64(0), uss.TotalUnreadThreads)
|
||||
require.Equal(t, int64(0), uss.TotalUnreadMentions)
|
||||
require.Equal(t, int64(1), uss.Threads[0].ReplyCount)
|
||||
|
||||
require.Equal(t, rootIds[9].Id, uss.Threads[0].PostId)
|
||||
require.Equal(t, th.SystemAdminUser.Id, uss.Threads[0].Participants[0].Id)
|
||||
require.Equal(t, th.BasicUser.Id, uss.Threads[1].Participants[0].Id)
|
||||
})
|
||||
|
||||
t.Run("setting both threadsOnly, and totalsOnly params is not allowed", func(t *testing.T) {
|
||||
defer th.App.Srv().Store.Post().PermanentDeleteByUser(th.BasicUser.Id)
|
||||
|
||||
_, resp, err := th.Client.GetUserThreads(th.BasicUser.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{
|
||||
ThreadsOnly: true,
|
||||
TotalsOnly: true,
|
||||
PageSize: 30,
|
||||
})
|
||||
|
||||
require.Error(t, err)
|
||||
checkHTTPStatus(t, resp, http.StatusBadRequest)
|
||||
})
|
||||
|
||||
t.Run("editing or reacting to reply post does not make thread unread", func(t *testing.T) {
|
||||
client := th.Client
|
||||
|
||||
|
||||
+26
-24
@@ -2296,35 +2296,37 @@ func (a *App) GetThreadsForUser(userID, teamID string, options model.GetUserThre
|
||||
var result model.Threads
|
||||
var eg errgroup.Group
|
||||
|
||||
eg.Go(func() error {
|
||||
totalUnreadThreads, err := a.Srv().Store.Thread().GetTotalUnreadThreads(userID, teamID, options)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to count unread threads for user id=%s", userID)
|
||||
}
|
||||
result.TotalUnreadThreads = totalUnreadThreads
|
||||
if !options.ThreadsOnly {
|
||||
eg.Go(func() error {
|
||||
totalUnreadThreads, err := a.Srv().Store.Thread().GetTotalUnreadThreads(userID, teamID, options)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to count unread threads for user id=%s", userID)
|
||||
}
|
||||
result.TotalUnreadThreads = totalUnreadThreads
|
||||
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
})
|
||||
|
||||
eg.Go(func() error {
|
||||
totalCount, err := a.Srv().Store.Thread().GetTotalThreads(userID, teamID, options)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to count threads for user id=%s", userID)
|
||||
}
|
||||
result.Total = totalCount
|
||||
eg.Go(func() error {
|
||||
totalCount, err := a.Srv().Store.Thread().GetTotalThreads(userID, teamID, options)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to count threads for user id=%s", userID)
|
||||
}
|
||||
result.Total = totalCount
|
||||
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
})
|
||||
|
||||
eg.Go(func() error {
|
||||
totalUnreadMentions, err := a.Srv().Store.Thread().GetTotalUnreadMentions(userID, teamID, options)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to count threads for user id=%s", userID)
|
||||
}
|
||||
result.TotalUnreadMentions = totalUnreadMentions
|
||||
eg.Go(func() error {
|
||||
totalUnreadMentions, err := a.Srv().Store.Thread().GetTotalUnreadMentions(userID, teamID, options)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to count threads for user id=%s", userID)
|
||||
}
|
||||
result.TotalUnreadMentions = totalUnreadMentions
|
||||
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
if !options.TotalsOnly {
|
||||
eg.Go(func() error {
|
||||
|
||||
@@ -1861,6 +1861,10 @@
|
||||
"id": "api.file.write_file.app_error",
|
||||
"translation": "Unable to write the file."
|
||||
},
|
||||
{
|
||||
"id": "api.getThreadsForUser.bad_only_params",
|
||||
"translation": "OnlyThreads and OnlyTotals parameters to getThreadsForUser are mutually exclusive"
|
||||
},
|
||||
{
|
||||
"id": "api.getThreadsForUser.bad_params",
|
||||
"translation": "Before and After parameters to getThreadsForUser are mutually exclusive"
|
||||
|
||||
@@ -7813,6 +7813,12 @@ func (c *Client4) GetUserThreads(userId, teamId string, options GetUserThreadsOp
|
||||
if options.Unread {
|
||||
v.Set("unread", "true")
|
||||
}
|
||||
if options.ThreadsOnly {
|
||||
v.Set("threadsOnly", "true")
|
||||
}
|
||||
if options.TotalsOnly {
|
||||
v.Set("totalsOnly", "true")
|
||||
}
|
||||
url := c.userThreadsRoute(userId, teamId)
|
||||
if len(v) > 0 {
|
||||
url += "?" + v.Encode()
|
||||
|
||||
@@ -67,6 +67,9 @@ type GetUserThreadsOpts struct {
|
||||
// TotalsOnly will not fetch any threads and just fetch the total counts
|
||||
TotalsOnly bool
|
||||
|
||||
// ThreadsOnly will fetch threads but not calculate totals and will return 0
|
||||
ThreadsOnly bool
|
||||
|
||||
// TeamOnly will only fetch threads and unreads for the specified team and excludes DMs/GMs
|
||||
TeamOnly bool
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user