[MM-42739] Insights - Top Channels API Endpoint (#19953)

* [MM-42739] Initial setup for top channels for team

* [MM-42739] Add initial tests

* [MM-42739] Update tests

* [MM-42739] Add top channels for user

* [MM-42739] Fix query

* [MM-42739] Update query

* [MM-42739] Improve query performance

* [MM-42739] Remove rank

* [MM-42739] Fix tests to use new time range today

* [MM-42739] Add tests for top channels for user

* [MM-42739] Add test for pagination

* Remove top channels by time struct

* [MM-42739] Update test names

* [MM-42739] Remove rank from top reactions

* [MM-42739] Return empty array instead of nil when result is empty

* [MM-42739] Add additional tests and update permissions check for teams

* [MM-42739] Add excluded channel tests for top reactions

* [MM-42739] Move insights to api4/insights and keep time range as string until required

* [MM-42739] Update queries only check DeleteAt after union

* [MM-42739] Improve query performance by using publicchannels table

* [MM-42739] Fix broken query after merge
This commit is contained in:
Mylon Suren
2022-04-26 14:42:24 -04:00
committed by GitHub
parent 9d37a6cc3d
commit 52ac449012
22 changed files with 1453 additions and 510 deletions
+7
View File
@@ -134,6 +134,9 @@ type Routes struct {
SharedChannels *mux.Router // 'api/v4/sharedchannels'
Permissions *mux.Router // 'api/v4/permissions'
InsightsForTeam *mux.Router // 'api/v4/teams/{team_id:[A-Za-z0-9]+}/top'
InsightsForUser *mux.Router // 'api/v4/users/me/top'
}
type API struct {
@@ -255,6 +258,9 @@ func Init(srv *app.Server) (*API, error) {
api.BaseRoutes.Permissions = api.BaseRoutes.APIRoot.PathPrefix("/permissions").Subrouter()
api.BaseRoutes.InsightsForTeam = api.BaseRoutes.Team.PathPrefix("/top").Subrouter()
api.BaseRoutes.InsightsForUser = api.BaseRoutes.Users.PathPrefix("/me/top").Subrouter()
api.InitUser()
api.InitBot()
api.InitTeam()
@@ -296,6 +302,7 @@ func Init(srv *app.Server) (*API, error) {
api.InitSharedChannels()
api.InitPermissions()
api.InitExport()
api.InitInsights()
if err := api.InitGraphQL(); err != nil {
return nil, err
}
+204
View File
@@ -0,0 +1,204 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package api4
import (
"encoding/json"
"net/http"
"github.com/mattermost/mattermost-server/v6/model"
)
func (api *API) InitInsights() {
// Reactions
api.BaseRoutes.InsightsForTeam.Handle("/reactions", api.APISessionRequired(getTopReactionsForTeamSince)).Methods("GET")
api.BaseRoutes.InsightsForUser.Handle("/reactions", api.APISessionRequired(getTopReactionsForUserSince)).Methods("GET")
// Channels
api.BaseRoutes.InsightsForTeam.Handle("/channels", api.APISessionRequired(getTopChannelsForTeamSince)).Methods("GET")
api.BaseRoutes.InsightsForUser.Handle("/channels", api.APISessionRequired(getTopChannelsForUserSince)).Methods("GET")
}
// Top Reactions
func getTopReactionsForTeamSince(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireTeamId()
if c.Err != nil {
return
}
team, err := c.App.GetTeam(c.Params.TeamId)
if err != nil {
c.Err = err
return
}
if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), team.Id, model.PermissionViewTeam) {
c.SetPermissionError(model.PermissionViewTeam)
return
}
startTime, err := model.GetStartUnixMilliForTimeRange(c.Params.TimeRange)
if err != nil {
c.Err = err
return
}
topReactionList, err := c.App.GetTopReactionsForTeamSince(c.Params.TeamId, c.AppContext.Session().UserId, &model.InsightsOpts{
StartUnixMilli: startTime,
Page: c.Params.Page,
PerPage: c.Params.PerPage,
})
if err != nil {
c.Err = err
return
}
js, jsonErr := json.Marshal(topReactionList)
if jsonErr != nil {
c.Err = model.NewAppError("getTopReactionsForTeamSince", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
return
}
w.Write(js)
}
func getTopReactionsForUserSince(c *Context, w http.ResponseWriter, r *http.Request) {
c.Params.TeamId = r.URL.Query().Get("team_id")
// TeamId is an optional parameter
if c.Params.TeamId != "" {
if !model.IsValidId(c.Params.TeamId) {
c.SetInvalidURLParam("team_id")
return
}
team, teamErr := c.App.GetTeam(c.Params.TeamId)
if teamErr != nil {
c.Err = teamErr
return
}
if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), team.Id, model.PermissionViewTeam) {
c.SetPermissionError(model.PermissionViewTeam)
return
}
}
startTime, err := model.GetStartUnixMilliForTimeRange(c.Params.TimeRange)
if err != nil {
c.Err = err
return
}
topReactionList, err := c.App.GetTopReactionsForUserSince(c.AppContext.Session().UserId, c.Params.TeamId, &model.InsightsOpts{
StartUnixMilli: startTime,
Page: c.Params.Page,
PerPage: c.Params.PerPage,
})
if err != nil {
c.Err = err
return
}
js, jsonErr := json.Marshal(topReactionList)
if jsonErr != nil {
c.Err = model.NewAppError("getTopReactionsForUserSince", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
return
}
w.Write(js)
}
// Top Channels
func getTopChannelsForTeamSince(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireTeamId()
if c.Err != nil {
return
}
team, err := c.App.GetTeam(c.Params.TeamId)
if err != nil {
c.Err = err
return
}
if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), team.Id, model.PermissionViewTeam) {
c.SetPermissionError(model.PermissionViewTeam)
return
}
startTime, err := model.GetStartUnixMilliForTimeRange(c.Params.TimeRange)
if err != nil {
c.Err = err
return
}
topChannels, err := c.App.GetTopChannelsForTeamSince(c.Params.TeamId, c.AppContext.Session().UserId, &model.InsightsOpts{
StartUnixMilli: startTime,
Page: c.Params.Page,
PerPage: c.Params.PerPage,
})
if err != nil {
c.Err = err
return
}
js, jsonErr := json.Marshal(topChannels)
if jsonErr != nil {
c.Err = model.NewAppError("getTopChannelsForTeamSince", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
return
}
w.Write(js)
}
func getTopChannelsForUserSince(c *Context, w http.ResponseWriter, r *http.Request) {
c.Params.TeamId = r.URL.Query().Get("team_id")
// TeamId is an optional parameter
if c.Params.TeamId != "" {
if !model.IsValidId(c.Params.TeamId) {
c.SetInvalidURLParam("team_id")
return
}
team, teamErr := c.App.GetTeam(c.Params.TeamId)
if teamErr != nil {
c.Err = teamErr
return
}
if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), team.Id, model.PermissionViewTeam) {
c.SetPermissionError(model.PermissionViewTeam)
return
}
}
startTime, err := model.GetStartUnixMilliForTimeRange(c.Params.TimeRange)
if err != nil {
c.Err = err
return
}
topChannels, err := c.App.GetTopChannelsForUserSince(c.AppContext.Session().UserId, c.Params.TeamId, &model.InsightsOpts{
StartUnixMilli: startTime,
Page: c.Params.Page,
PerPage: c.Params.PerPage,
})
if err != nil {
c.Err = err
return
}
js, jsonErr := json.Marshal(topChannels)
if jsonErr != nil {
c.Err = model.NewAppError("getTopChannelsForUserSince", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
return
}
w.Write(js)
}
+600
View File
@@ -0,0 +1,600 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package api4
import (
"testing"
"time"
"github.com/mattermost/mattermost-server/v6/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// Top Reactions
func TestGetTopReactionsForTeamSince(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
th.ConfigStore.SetReadOnlyFF(false)
defer th.ConfigStore.SetReadOnlyFF(true)
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true })
client := th.Client
userId := th.BasicUser.Id
user2Id := th.BasicUser2.Id
post1 := &model.Post{UserId: userId, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"}
post2 := &model.Post{UserId: userId, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"}
post3 := &model.Post{UserId: userId, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"}
post4 := &model.Post{UserId: user2Id, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"}
post5 := &model.Post{UserId: user2Id, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"}
post1, _, _ = client.CreatePost(post1)
post2, _, _ = client.CreatePost(post2)
post3, _, _ = client.CreatePost(post3)
post4, _, _ = client.CreatePost(post4)
post5, _, _ = client.CreatePost(post5)
userReactions := []*model.Reaction{
{
UserId: userId,
PostId: post1.Id,
EmojiName: "happy",
},
{
UserId: user2Id,
PostId: post1.Id,
EmojiName: "happy",
},
{
UserId: userId,
PostId: post1.Id,
EmojiName: "sad",
},
{
UserId: user2Id,
PostId: post1.Id,
EmojiName: "sad",
},
{
UserId: userId,
PostId: post1.Id,
EmojiName: "smile",
},
{
UserId: userId,
PostId: post1.Id,
EmojiName: "joy",
},
{
UserId: userId,
PostId: post1.Id,
EmojiName: "100",
},
{
UserId: userId,
PostId: post2.Id,
EmojiName: "sad",
},
{
UserId: userId,
PostId: post2.Id,
EmojiName: "smile",
},
{
UserId: userId,
PostId: post2.Id,
EmojiName: "joy",
},
{
UserId: userId,
PostId: post2.Id,
EmojiName: "100",
},
{
UserId: userId,
PostId: post3.Id,
EmojiName: "smile",
},
{
UserId: user2Id,
PostId: post3.Id,
EmojiName: "smile",
},
{
UserId: userId,
PostId: post3.Id,
EmojiName: "joy",
},
{
UserId: userId,
PostId: post3.Id,
EmojiName: "100",
},
{
UserId: userId,
PostId: post4.Id,
EmojiName: "joy",
},
{
UserId: user2Id,
PostId: post4.Id,
EmojiName: "joy",
},
{
UserId: userId,
PostId: post4.Id,
EmojiName: "100",
},
{
UserId: userId,
PostId: post5.Id,
EmojiName: "100",
},
{
UserId: user2Id,
PostId: post5.Id,
EmojiName: "100",
},
{
UserId: user2Id,
PostId: post5.Id,
EmojiName: "+1",
},
{
UserId: userId,
PostId: post1.Id,
EmojiName: "100",
CreateAt: model.GetMillisForTime(time.Now().Add(time.Hour * time.Duration(-25))),
},
}
for _, userReaction := range userReactions {
_, err := th.App.Srv().Store.Reaction().Save(userReaction)
require.NoError(t, err)
}
teamId := th.BasicChannel.TeamId
var expectedTopReactions [5]*model.TopReaction
expectedTopReactions[0] = &model.TopReaction{EmojiName: "100", Count: int64(6)}
expectedTopReactions[1] = &model.TopReaction{EmojiName: "joy", Count: int64(5)}
expectedTopReactions[2] = &model.TopReaction{EmojiName: "smile", Count: int64(4)}
expectedTopReactions[3] = &model.TopReaction{EmojiName: "sad", Count: int64(3)}
expectedTopReactions[4] = &model.TopReaction{EmojiName: "happy", Count: int64(2)}
t.Run("get-top-reactions-for-team-since", func(t *testing.T) {
topReactions, _, err := client.GetTopReactionsForTeamSince(teamId, model.TimeRangeToday, 0, 5)
require.NoError(t, err)
reactions := topReactions.Items
for i, reaction := range reactions {
assert.Equal(t, expectedTopReactions[i].EmojiName, reaction.EmojiName)
assert.Equal(t, expectedTopReactions[i].Count, reaction.Count)
}
topReactions, _, err = client.GetTopReactionsForTeamSince(teamId, model.TimeRangeToday, 1, 5)
require.NoError(t, err)
reactions = topReactions.Items
assert.Equal(t, "+1", reactions[0].EmojiName)
assert.Equal(t, int64(1), reactions[0].Count)
})
t.Run("get-top-reactions-for-team-since exclude channels user is not member of", func(t *testing.T) {
excludedChannel := th.CreatePrivateChannel()
for i := 0; i < 10; i++ {
post, _, err := client.CreatePost(&model.Post{UserId: userId, ChannelId: excludedChannel.Id, Message: "zz" + model.NewId() + "a"})
require.NoError(t, err)
reaction := &model.Reaction{
UserId: userId,
PostId: post.Id,
EmojiName: "confused",
}
_, err = th.App.Srv().Store.Reaction().Save(reaction)
require.NoError(t, err)
}
th.RemoveUserFromChannel(th.BasicUser, excludedChannel)
topReactions, _, err := client.GetTopReactionsForTeamSince(teamId, model.TimeRangeToday, 0, 5)
require.NoError(t, err)
reactions := topReactions.Items
for i, reaction := range reactions {
assert.Equal(t, expectedTopReactions[i].EmojiName, reaction.EmojiName)
assert.Equal(t, expectedTopReactions[i].Count, reaction.Count)
}
topReactions, _, err = client.GetTopReactionsForTeamSince(teamId, model.TimeRangeToday, 1, 5)
require.NoError(t, err)
reactions = topReactions.Items
assert.Equal(t, "+1", reactions[0].EmojiName)
assert.Equal(t, int64(1), reactions[0].Count)
})
t.Run("get-top-reactions-for-team-since invalid team id", func(t *testing.T) {
_, resp, err := client.GetTopReactionsForTeamSince("12345", model.TimeRangeToday, 0, 5)
assert.Error(t, err)
CheckBadRequestStatus(t, resp)
_, resp, err = client.GetTopReactionsForTeamSince(model.NewId(), model.TimeRangeToday, 0, 5)
assert.Error(t, err)
CheckNotFoundStatus(t, resp)
})
t.Run("get-top-reactions-for-team-since not a member of team", func(t *testing.T) {
th.UnlinkUserFromTeam(th.BasicUser, th.BasicTeam)
_, resp, err := client.GetTopReactionsForTeamSince(teamId, model.TimeRangeToday, 0, 5)
assert.Error(t, err)
CheckForbiddenStatus(t, resp)
})
}
func TestGetTopReactionsForUserSince(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
th.ConfigStore.SetReadOnlyFF(false)
defer th.ConfigStore.SetReadOnlyFF(true)
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true })
client := th.Client
userId := th.BasicUser.Id
post1 := &model.Post{UserId: userId, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"}
post2 := &model.Post{UserId: userId, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"}
post3 := &model.Post{UserId: userId, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"}
post4 := &model.Post{UserId: userId, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"}
post5 := &model.Post{UserId: userId, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"}
post6 := &model.Post{UserId: userId, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"}
post1, _, _ = client.CreatePost(post1)
post2, _, _ = client.CreatePost(post2)
post3, _, _ = client.CreatePost(post3)
post4, _, _ = client.CreatePost(post4)
post5, _, _ = client.CreatePost(post5)
post6, _, _ = client.CreatePost(post6)
userReactions := []*model.Reaction{
{
UserId: userId,
PostId: post1.Id,
EmojiName: "happy",
},
{
UserId: userId,
PostId: post2.Id,
EmojiName: "happy",
},
{
UserId: userId,
PostId: post3.Id,
EmojiName: "happy",
},
{
UserId: userId,
PostId: post4.Id,
EmojiName: "happy",
},
{
UserId: userId,
PostId: post5.Id,
EmojiName: "happy",
},
{
UserId: userId,
PostId: post6.Id,
EmojiName: "happy",
},
{
UserId: userId,
PostId: post1.Id,
EmojiName: "smile",
},
{
UserId: userId,
PostId: post2.Id,
EmojiName: "smile",
},
{
UserId: userId,
PostId: post3.Id,
EmojiName: "smile",
},
{
UserId: userId,
PostId: post4.Id,
EmojiName: "smile",
},
{
UserId: userId,
PostId: post5.Id,
EmojiName: "smile",
},
{
UserId: userId,
PostId: post1.Id,
EmojiName: "+1",
},
{
UserId: userId,
PostId: post2.Id,
EmojiName: "+1",
},
{
UserId: userId,
PostId: post3.Id,
EmojiName: "+1",
},
{
UserId: userId,
PostId: post4.Id,
EmojiName: "+1",
},
{
UserId: userId,
PostId: post1.Id,
EmojiName: "heart",
},
{
UserId: userId,
PostId: post2.Id,
EmojiName: "heart",
},
{
UserId: userId,
PostId: post3.Id,
EmojiName: "heart",
},
{
UserId: userId,
PostId: post1.Id,
EmojiName: "blush",
},
{
UserId: userId,
PostId: post2.Id,
EmojiName: "blush",
},
{
UserId: userId,
PostId: post1.Id,
EmojiName: "100",
},
{
UserId: userId,
PostId: post1.Id,
EmojiName: "100",
CreateAt: model.GetMillisForTime(time.Now().Add(time.Hour * time.Duration(-25))),
},
}
for _, userReaction := range userReactions {
_, err := th.App.Srv().Store.Reaction().Save(userReaction)
require.NoError(t, err)
}
teamId := th.BasicChannel.TeamId
var expectedTopReactions [5]*model.TopReaction
expectedTopReactions[0] = &model.TopReaction{EmojiName: "happy", Count: int64(6)}
expectedTopReactions[1] = &model.TopReaction{EmojiName: "smile", Count: int64(5)}
expectedTopReactions[2] = &model.TopReaction{EmojiName: "+1", Count: int64(4)}
expectedTopReactions[3] = &model.TopReaction{EmojiName: "heart", Count: int64(3)}
expectedTopReactions[4] = &model.TopReaction{EmojiName: "blush", Count: int64(2)}
t.Run("get-top-reactions-for-user-since", func(t *testing.T) {
topReactions, _, err := client.GetTopReactionsForUserSince(teamId, model.TimeRangeToday, 0, 5)
require.NoError(t, err)
reactions := topReactions.Items
for i, reaction := range reactions {
assert.Equal(t, expectedTopReactions[i].EmojiName, reaction.EmojiName)
assert.Equal(t, expectedTopReactions[i].Count, reaction.Count)
}
topReactions, _, err = client.GetTopReactionsForUserSince(teamId, model.TimeRangeToday, 1, 5)
require.NoError(t, err)
reactions = topReactions.Items
assert.Equal(t, "100", reactions[0].EmojiName)
assert.Equal(t, int64(1), reactions[0].Count)
})
t.Run("get-top-reactions-for-user-since invalid team id", func(t *testing.T) {
_, resp, err := client.GetTopReactionsForUserSince("invalid_team_id", model.TimeRangeToday, 0, 5)
assert.Error(t, err)
CheckBadRequestStatus(t, resp)
_, resp, err = client.GetTopReactionsForUserSince(model.NewId(), model.TimeRangeToday, 0, 5)
assert.Error(t, err)
CheckNotFoundStatus(t, resp)
})
t.Run("get-top-reactions-for-user-since not a member of team", func(t *testing.T) {
th.UnlinkUserFromTeam(th.BasicUser, th.BasicTeam)
_, resp, err := client.GetTopReactionsForUserSince(teamId, model.TimeRangeToday, 0, 5)
assert.Error(t, err)
CheckForbiddenStatus(t, resp)
})
}
// Top Channels
func TestGetTopChannelsForTeamSince(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
th.ConfigStore.SetReadOnlyFF(false)
defer th.ConfigStore.SetReadOnlyFF(true)
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true })
client := th.Client
userId := th.BasicUser.Id
channel4 := th.CreatePublicChannel()
channel5 := th.CreatePrivateChannel()
channel6 := th.CreatePrivateChannel()
th.App.AddUserToChannel(th.BasicUser, channel4, false)
th.App.AddUserToChannel(th.BasicUser, channel5, false)
th.App.AddUserToChannel(th.BasicUser, channel6, false)
channelIDs := [6]string{th.BasicChannel.Id, th.BasicChannel2.Id, th.BasicPrivateChannel.Id, channel4.Id, channel5.Id, channel6.Id}
i := len(channelIDs)
for _, channelID := range channelIDs {
for j := i; j > 0; j-- {
_, _, err := client.CreatePost(&model.Post{UserId: userId, ChannelId: channelID, Message: "zz" + model.NewId() + "a"})
require.NoError(t, err)
}
i--
}
teamId := th.BasicChannel.TeamId
expectedTopChannels := []struct {
ID string
MessageCount int64
}{
{ID: th.BasicChannel.Id, MessageCount: 7},
{ID: th.BasicChannel2.Id, MessageCount: 5},
{ID: th.BasicPrivateChannel.Id, MessageCount: 4},
{ID: channel4.Id, MessageCount: 3},
{ID: channel5.Id, MessageCount: 2},
}
t.Run("get-top-channels-for-team-since", func(t *testing.T) {
topChannels, _, err := client.GetTopChannelsForTeamSince(teamId, model.TimeRangeToday, 0, 5)
require.NoError(t, err)
for i, channel := range topChannels.Items {
assert.Equal(t, expectedTopChannels[i].ID, channel.ID)
assert.Equal(t, expectedTopChannels[i].MessageCount, channel.MessageCount)
}
topChannels, _, err = client.GetTopChannelsForTeamSince(teamId, model.TimeRangeToday, 1, 5)
require.NoError(t, err)
assert.Equal(t, channel6.Id, topChannels.Items[0].ID)
assert.Equal(t, int64(1), topChannels.Items[0].MessageCount)
})
t.Run("get-top-channels-for-user-since exclude channels user is not member of", func(t *testing.T) {
excludedChannel := th.CreatePrivateChannel()
for i := 0; i < 10; i++ {
_, _, err := client.CreatePost(&model.Post{UserId: userId, ChannelId: excludedChannel.Id, Message: "zz" + model.NewId() + "a"})
require.NoError(t, err)
}
th.RemoveUserFromChannel(th.BasicUser, excludedChannel)
topChannels, _, err := client.GetTopChannelsForTeamSince(teamId, model.TimeRangeToday, 0, 5)
require.NoError(t, err)
for i, channel := range topChannels.Items {
assert.Equal(t, expectedTopChannels[i].ID, channel.ID)
assert.Equal(t, expectedTopChannels[i].MessageCount, channel.MessageCount)
}
})
t.Run("get-top-channels-for-team-since invalid team id", func(t *testing.T) {
_, resp, err := client.GetTopChannelsForTeamSince("12345", model.TimeRangeToday, 0, 5)
assert.Error(t, err)
CheckBadRequestStatus(t, resp)
_, resp, err = client.GetTopChannelsForTeamSince(model.NewId(), model.TimeRangeToday, 0, 5)
assert.Error(t, err)
CheckNotFoundStatus(t, resp)
})
t.Run("get-top-channels-for-team-since not a member of team", func(t *testing.T) {
th.UnlinkUserFromTeam(th.BasicUser, th.BasicTeam)
_, resp, err := client.GetTopChannelsForTeamSince(teamId, model.TimeRangeToday, 0, 5)
assert.Error(t, err)
CheckForbiddenStatus(t, resp)
})
}
func TestGetTopChannelsForUserSince(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
th.ConfigStore.SetReadOnlyFF(false)
defer th.ConfigStore.SetReadOnlyFF(true)
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true })
client := th.Client
userId := th.BasicUser.Id
channel4 := th.CreatePublicChannel()
channel5 := th.CreatePrivateChannel()
channel6 := th.CreatePrivateChannel()
th.App.AddUserToChannel(th.BasicUser, channel4, false)
th.App.AddUserToChannel(th.BasicUser, channel5, false)
th.App.AddUserToChannel(th.BasicUser, channel6, false)
channelIDs := [6]string{th.BasicChannel.Id, th.BasicChannel2.Id, th.BasicPrivateChannel.Id, channel4.Id, channel5.Id, channel6.Id}
i := len(channelIDs)
for _, channelID := range channelIDs {
for j := i; j > 0; j-- {
_, _, err := client.CreatePost(&model.Post{UserId: userId, ChannelId: channelID, Message: "zz" + model.NewId() + "a"})
require.NoError(t, err)
}
i--
}
teamId := th.BasicChannel.TeamId
expectedTopChannels := []struct {
ID string
MessageCount int64
}{
{ID: th.BasicChannel.Id, MessageCount: 6},
{ID: th.BasicChannel2.Id, MessageCount: 5},
{ID: th.BasicPrivateChannel.Id, MessageCount: 4},
{ID: channel4.Id, MessageCount: 3},
{ID: channel5.Id, MessageCount: 2},
}
t.Run("get-top-channels-for-user-since", func(t *testing.T) {
topChannels, _, err := client.GetTopChannelsForUserSince(teamId, model.TimeRangeToday, 0, 5)
require.NoError(t, err)
for i, channel := range topChannels.Items {
assert.Equal(t, expectedTopChannels[i].ID, channel.ID)
assert.Equal(t, expectedTopChannels[i].MessageCount, channel.MessageCount)
}
topChannels, _, err = client.GetTopChannelsForUserSince("", model.TimeRangeToday, 1, 5)
require.NoError(t, err)
assert.Equal(t, channel6.Id, topChannels.Items[0].ID)
assert.Equal(t, int64(1), topChannels.Items[0].MessageCount)
})
t.Run("get-top-channels-for-user-since invalid team id", func(t *testing.T) {
_, resp, err := client.GetTopChannelsForUserSince("12345", model.TimeRangeToday, 0, 5)
assert.Error(t, err)
CheckBadRequestStatus(t, resp)
_, resp, err = client.GetTopChannelsForUserSince(model.NewId(), model.TimeRangeToday, 0, 5)
assert.Error(t, err)
CheckNotFoundStatus(t, resp)
})
t.Run("get-top-channels-for-user-since not a member of team", func(t *testing.T) {
th.UnlinkUserFromTeam(th.BasicUser, th.BasicTeam)
_, resp, err := client.GetTopChannelsForUserSince(teamId, model.TimeRangeToday, 0, 5)
assert.Error(t, err)
CheckForbiddenStatus(t, resp)
})
}
-85
View File
@@ -16,9 +16,6 @@ func (api *API) InitReaction() {
api.BaseRoutes.Post.Handle("/reactions", api.APISessionRequired(getReactions)).Methods("GET")
api.BaseRoutes.ReactionByNameForPostForUser.Handle("", api.APISessionRequired(deleteReaction)).Methods("DELETE")
api.BaseRoutes.Posts.Handle("/ids/reactions", api.APISessionRequired(getBulkReactions)).Methods("POST")
api.BaseRoutes.Team.Handle("/top/reactions", api.APISessionRequired(getTopReactionsForTeamSince)).Methods("GET")
api.BaseRoutes.Users.Handle("/me/top/reactions", api.APISessionRequired(getTopReactionsForUserSince)).Methods("GET")
}
func saveReaction(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -141,85 +138,3 @@ func getBulkReactions(c *Context, w http.ResponseWriter, r *http.Request) {
}
w.Write(js)
}
func getTopReactionsForTeamSince(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireTeamId().RequireTimeRange()
if c.Err != nil {
return
}
team, err := c.App.GetTeam(c.Params.TeamId)
if err != nil {
c.Err = err
return
}
if (!team.AllowOpenInvite || team.Type != model.TeamOpen) && !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), team.Id, model.PermissionViewTeam) {
c.SetPermissionError(model.PermissionViewTeam)
return
}
topReactionList, err := c.App.GetTopReactionsForTeamSince(c.Params.TeamId, c.AppContext.Session().UserId, &model.InsightsOpts{
StartUnixMilli: c.Params.TimeRange,
Page: c.Params.Page,
PerPage: c.Params.PerPage,
})
if err != nil {
c.Err = err
return
}
js, jsonErr := json.Marshal(topReactionList)
if jsonErr != nil {
c.Err = model.NewAppError("getTopReactionsForTeamSince", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
return
}
w.Write(js)
}
func getTopReactionsForUserSince(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireTimeRange()
if c.Err != nil {
return
}
c.Params.TeamId = r.URL.Query().Get("team_id")
// TeamId is an optional parameter
if c.Params.TeamId != "" {
if !model.IsValidId(c.Params.TeamId) {
c.SetInvalidURLParam("team_id")
return
}
team, teamErr := c.App.GetTeam(c.Params.TeamId)
if teamErr != nil {
c.Err = teamErr
return
}
if (!team.AllowOpenInvite || team.Type != model.TeamOpen) && !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), team.Id, model.PermissionViewTeam) {
c.SetPermissionError(model.PermissionViewTeam)
return
}
}
topReactionList, err := c.App.GetTopReactionsForUserSince(c.AppContext.Session().UserId, c.Params.TeamId, &model.InsightsOpts{
StartUnixMilli: c.Params.TimeRange,
Page: c.Params.Page,
PerPage: c.Params.PerPage,
})
if err != nil {
c.Err = err
return
}
js, jsonErr := json.Marshal(topReactionList)
if jsonErr != nil {
c.Err = model.NewAppError("getTopReactionsForUserSince", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
return
}
w.Write(js)
}
-365
View File
@@ -6,7 +6,6 @@ package api4
import (
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -585,367 +584,3 @@ func TestGetBulkReactions(t *testing.T) {
CheckUnauthorizedStatus(t, resp)
})
}
func TestGetTopReactionsForTeamSince(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
th.ConfigStore.SetReadOnlyFF(false)
defer th.ConfigStore.SetReadOnlyFF(true)
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true })
client := th.Client
userId := th.BasicUser.Id
user2Id := th.BasicUser2.Id
post1 := &model.Post{UserId: userId, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"}
post2 := &model.Post{UserId: userId, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"}
post3 := &model.Post{UserId: userId, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"}
post4 := &model.Post{UserId: user2Id, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"}
post5 := &model.Post{UserId: user2Id, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"}
post1, _, _ = client.CreatePost(post1)
post2, _, _ = client.CreatePost(post2)
post3, _, _ = client.CreatePost(post3)
post4, _, _ = client.CreatePost(post4)
post5, _, _ = client.CreatePost(post5)
userReactions := []*model.Reaction{
{
UserId: userId,
PostId: post1.Id,
EmojiName: "happy",
},
{
UserId: user2Id,
PostId: post1.Id,
EmojiName: "happy",
},
{
UserId: userId,
PostId: post1.Id,
EmojiName: "sad",
},
{
UserId: user2Id,
PostId: post1.Id,
EmojiName: "sad",
},
{
UserId: userId,
PostId: post1.Id,
EmojiName: "smile",
},
{
UserId: userId,
PostId: post1.Id,
EmojiName: "joy",
},
{
UserId: userId,
PostId: post1.Id,
EmojiName: "100",
},
{
UserId: userId,
PostId: post2.Id,
EmojiName: "sad",
},
{
UserId: userId,
PostId: post2.Id,
EmojiName: "smile",
},
{
UserId: userId,
PostId: post2.Id,
EmojiName: "joy",
},
{
UserId: userId,
PostId: post2.Id,
EmojiName: "100",
},
{
UserId: userId,
PostId: post3.Id,
EmojiName: "smile",
},
{
UserId: user2Id,
PostId: post3.Id,
EmojiName: "smile",
},
{
UserId: userId,
PostId: post3.Id,
EmojiName: "joy",
},
{
UserId: userId,
PostId: post3.Id,
EmojiName: "100",
},
{
UserId: userId,
PostId: post4.Id,
EmojiName: "joy",
},
{
UserId: user2Id,
PostId: post4.Id,
EmojiName: "joy",
},
{
UserId: userId,
PostId: post4.Id,
EmojiName: "100",
},
{
UserId: userId,
PostId: post5.Id,
EmojiName: "100",
},
{
UserId: user2Id,
PostId: post5.Id,
EmojiName: "100",
},
{
UserId: user2Id,
PostId: post5.Id,
EmojiName: "+1",
},
{
UserId: userId,
PostId: post1.Id,
EmojiName: "100",
CreateAt: model.GetMillisForTime(time.Now().Add(time.Hour * time.Duration(-25))),
},
}
for _, userReaction := range userReactions {
_, err := th.App.Srv().Store.Reaction().Save(userReaction)
require.NoError(t, err)
}
teamId := th.BasicChannel.TeamId
var expectedTopReactions [5]*model.TopReaction
expectedTopReactions[0] = &model.TopReaction{EmojiName: "100", Count: int64(6)}
expectedTopReactions[1] = &model.TopReaction{EmojiName: "joy", Count: int64(5)}
expectedTopReactions[2] = &model.TopReaction{EmojiName: "smile", Count: int64(4)}
expectedTopReactions[3] = &model.TopReaction{EmojiName: "sad", Count: int64(3)}
expectedTopReactions[4] = &model.TopReaction{EmojiName: "happy", Count: int64(2)}
t.Run("get-top-reactions-for-team-since", func(t *testing.T) {
topReactions, _, err := client.GetTopReactionsForTeamSince(teamId, model.TimeRangeToday, 0, 5)
require.NoError(t, err)
reactions := topReactions.Items
for i, reaction := range reactions {
assert.Equal(t, expectedTopReactions[i].EmojiName, reaction.EmojiName)
assert.Equal(t, expectedTopReactions[i].Count, reaction.Count)
}
topReactions, _, err = client.GetTopReactionsForTeamSince(teamId, model.TimeRangeToday, 1, 5)
require.NoError(t, err)
reactions = topReactions.Items
assert.Equal(t, "+1", reactions[0].EmojiName)
assert.Equal(t, int64(1), reactions[0].Count)
})
t.Run("get-top-reactions-for-team-since invalid team id", func(t *testing.T) {
_, resp, err := client.GetTopReactionsForTeamSince("12345", model.TimeRangeToday, 0, 5)
assert.Error(t, err)
CheckBadRequestStatus(t, resp)
_, resp, err = client.GetTopReactionsForTeamSince(model.NewId(), model.TimeRangeToday, 0, 5)
assert.Error(t, err)
CheckNotFoundStatus(t, resp)
})
}
func TestGetTopReactionsForUserSince(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
th.ConfigStore.SetReadOnlyFF(false)
defer th.ConfigStore.SetReadOnlyFF(true)
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true })
client := th.Client
userId := th.BasicUser.Id
post1 := &model.Post{UserId: userId, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"}
post2 := &model.Post{UserId: userId, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"}
post3 := &model.Post{UserId: userId, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"}
post4 := &model.Post{UserId: userId, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"}
post5 := &model.Post{UserId: userId, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"}
post6 := &model.Post{UserId: userId, ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a"}
post1, _, _ = client.CreatePost(post1)
post2, _, _ = client.CreatePost(post2)
post3, _, _ = client.CreatePost(post3)
post4, _, _ = client.CreatePost(post4)
post5, _, _ = client.CreatePost(post5)
post6, _, _ = client.CreatePost(post6)
userReactions := []*model.Reaction{
{
UserId: userId,
PostId: post1.Id,
EmojiName: "happy",
},
{
UserId: userId,
PostId: post2.Id,
EmojiName: "happy",
},
{
UserId: userId,
PostId: post3.Id,
EmojiName: "happy",
},
{
UserId: userId,
PostId: post4.Id,
EmojiName: "happy",
},
{
UserId: userId,
PostId: post5.Id,
EmojiName: "happy",
},
{
UserId: userId,
PostId: post6.Id,
EmojiName: "happy",
},
{
UserId: userId,
PostId: post1.Id,
EmojiName: "smile",
},
{
UserId: userId,
PostId: post2.Id,
EmojiName: "smile",
},
{
UserId: userId,
PostId: post3.Id,
EmojiName: "smile",
},
{
UserId: userId,
PostId: post4.Id,
EmojiName: "smile",
},
{
UserId: userId,
PostId: post5.Id,
EmojiName: "smile",
},
{
UserId: userId,
PostId: post1.Id,
EmojiName: "+1",
},
{
UserId: userId,
PostId: post2.Id,
EmojiName: "+1",
},
{
UserId: userId,
PostId: post3.Id,
EmojiName: "+1",
},
{
UserId: userId,
PostId: post4.Id,
EmojiName: "+1",
},
{
UserId: userId,
PostId: post1.Id,
EmojiName: "heart",
},
{
UserId: userId,
PostId: post2.Id,
EmojiName: "heart",
},
{
UserId: userId,
PostId: post3.Id,
EmojiName: "heart",
},
{
UserId: userId,
PostId: post1.Id,
EmojiName: "blush",
},
{
UserId: userId,
PostId: post2.Id,
EmojiName: "blush",
},
{
UserId: userId,
PostId: post1.Id,
EmojiName: "100",
},
{
UserId: userId,
PostId: post1.Id,
EmojiName: "100",
CreateAt: model.GetMillisForTime(time.Now().Add(time.Hour * time.Duration(-25))),
},
}
for _, userReaction := range userReactions {
_, err := th.App.Srv().Store.Reaction().Save(userReaction)
require.NoError(t, err)
}
teamId := th.BasicChannel.TeamId
var expectedTopReactions [5]*model.TopReaction
expectedTopReactions[0] = &model.TopReaction{EmojiName: "happy", Count: int64(6)}
expectedTopReactions[1] = &model.TopReaction{EmojiName: "smile", Count: int64(5)}
expectedTopReactions[2] = &model.TopReaction{EmojiName: "+1", Count: int64(4)}
expectedTopReactions[3] = &model.TopReaction{EmojiName: "heart", Count: int64(3)}
expectedTopReactions[4] = &model.TopReaction{EmojiName: "blush", Count: int64(2)}
t.Run("get-top-reactions-for-user-since", func(t *testing.T) {
topReactions, _, err := client.GetTopReactionsForUserSince(teamId, model.TimeRangeToday, 0, 5)
require.NoError(t, err)
reactions := topReactions.Items
for i, reaction := range reactions {
assert.Equal(t, expectedTopReactions[i].EmojiName, reaction.EmojiName)
assert.Equal(t, expectedTopReactions[i].Count, reaction.Count)
}
topReactions, _, err = client.GetTopReactionsForUserSince(teamId, model.TimeRangeToday, 1, 5)
require.NoError(t, err)
reactions = topReactions.Items
assert.Equal(t, "100", reactions[0].EmojiName)
assert.Equal(t, int64(1), reactions[0].Count)
})
t.Run("get-top-reactions-for-user-since invalid team id", func(t *testing.T) {
_, resp, err := client.GetTopReactionsForUserSince("invalid_team_id", model.TimeRangeToday, 0, 5)
assert.Error(t, err)
CheckBadRequestStatus(t, resp)
_, resp, err = client.GetTopReactionsForUserSince(model.NewId(), model.TimeRangeToday, 0, 5)
assert.Error(t, err)
CheckNotFoundStatus(t, resp)
})
}
+2
View File
@@ -754,6 +754,8 @@ type AppIface interface {
GetThreadMembershipsForUser(userID, teamID string) ([]*model.ThreadMembership, error)
GetThreadsForUser(userID, teamID string, options model.GetUserThreadsOpts) (*model.Threads, *model.AppError)
GetTokenById(token string) (*model.Token, *model.AppError)
GetTopChannelsForTeamSince(teamID, userID string, opts *model.InsightsOpts) (*model.TopChannelList, *model.AppError)
GetTopChannelsForUserSince(userID, teamID string, opts *model.InsightsOpts) (*model.TopChannelList, *model.AppError)
GetTopReactionsForTeamSince(teamID string, userID string, opts *model.InsightsOpts) (*model.TopReactionList, *model.AppError)
GetTopReactionsForUserSince(userID string, teamID string, opts *model.InsightsOpts) (*model.TopReactionList, *model.AppError)
GetUploadSession(uploadId string) (*model.UploadSession, *model.AppError)
+24
View File
@@ -3439,3 +3439,27 @@ func (s *Server) getDirectChannel(userID, otherUserID string) (*model.Channel, *
return channel, nil
}
func (a *App) GetTopChannelsForTeamSince(teamID, userID string, opts *model.InsightsOpts) (*model.TopChannelList, *model.AppError) {
if !a.Config().FeatureFlags.InsightsEnabled {
return nil, model.NewAppError("GetTopChannelsForTeamSince", "api.insights.feature_disabled", nil, "", http.StatusNotImplemented)
}
topChannels, err := a.Srv().Store.Channel().GetTopChannelsForTeamSince(teamID, userID, opts.StartUnixMilli, opts.Page*opts.PerPage, opts.PerPage)
if err != nil {
return nil, model.NewAppError("GetTopChannelsForTeamSince", "app.channel.get_top_for_team_since.app_error", nil, err.Error(), http.StatusInternalServerError)
}
return topChannels, nil
}
func (a *App) GetTopChannelsForUserSince(userID, teamID string, opts *model.InsightsOpts) (*model.TopChannelList, *model.AppError) {
if !a.Config().FeatureFlags.InsightsEnabled {
return nil, model.NewAppError("GetTopChannelsForUserSince", "api.insights.feature_disabled", nil, "", http.StatusNotImplemented)
}
topChannels, err := a.Srv().Store.Channel().GetTopChannelsForUserSince(userID, teamID, opts.StartUnixMilli, opts.Page*opts.PerPage, opts.PerPage)
if err != nil {
return nil, model.NewAppError("GetTopChannelsForUserSince", "app.channel.get_top_for_user_since.app_error", nil, err.Error(), http.StatusInternalServerError)
}
return topChannels, nil
}
+116
View File
@@ -2446,3 +2446,119 @@ func TestMarkUnreadWithThreads(t *testing.T) {
})
})
}
func TestGetTopChannelsForTeamSince(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
th.Server.configStore.SetReadOnlyFF(false)
defer th.Server.configStore.SetReadOnlyFF(true)
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true })
channel2 := th.CreateChannel(th.BasicTeam)
channel3 := th.CreatePrivateChannel(th.BasicTeam)
channel4 := th.CreatePrivateChannel(th.BasicTeam)
channel5 := th.CreateChannel(th.BasicTeam)
channel6 := th.CreatePrivateChannel(th.BasicTeam)
th.AddUserToChannel(th.BasicUser, channel2)
th.AddUserToChannel(th.BasicUser, channel3)
th.AddUserToChannel(th.BasicUser, channel4)
th.AddUserToChannel(th.BasicUser, channel5)
th.AddUserToChannel(th.BasicUser, channel6)
channels := [6]*model.Channel{th.BasicChannel, channel2, channel3, channel4, channel5, channel6}
i := len(channels)
for _, channel := range channels {
for j := i; j > 0; j-- {
th.CreatePost(channel)
}
i--
}
expectedTopChannels := []struct {
ID string
MessageCount int64
}{
{ID: th.BasicChannel.Id, MessageCount: 7},
{ID: channel2.Id, MessageCount: 5},
{ID: channel3.Id, MessageCount: 4},
{ID: channel4.Id, MessageCount: 3},
{ID: channel5.Id, MessageCount: 2},
}
timeRange, _ := model.GetStartUnixMilliForTimeRange(model.TimeRangeToday)
t.Run("get-top-channels-for-team-since", func(t *testing.T) {
topChannels, err := th.App.GetTopChannelsForTeamSince(th.BasicChannel.TeamId, th.BasicUser.Id, &model.InsightsOpts{StartUnixMilli: timeRange, Page: 0, PerPage: 5})
require.Nil(t, err)
for i, channel := range topChannels.Items {
assert.Equal(t, expectedTopChannels[i].ID, channel.ID)
assert.Equal(t, expectedTopChannels[i].MessageCount, channel.MessageCount)
}
topChannels, err = th.App.GetTopChannelsForTeamSince(th.BasicChannel.TeamId, th.BasicUser.Id, &model.InsightsOpts{StartUnixMilli: timeRange, Page: 1, PerPage: 5})
require.Nil(t, err)
assert.Equal(t, channel6.Id, topChannels.Items[0].ID)
assert.Equal(t, int64(1), topChannels.Items[0].MessageCount)
})
}
func TestGetTopChannelsForUserSince(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
th.Server.configStore.SetReadOnlyFF(false)
defer th.Server.configStore.SetReadOnlyFF(true)
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true })
channel2 := th.CreateChannel(th.BasicTeam)
channel3 := th.CreatePrivateChannel(th.BasicTeam)
channel4 := th.CreatePrivateChannel(th.BasicTeam)
channel5 := th.CreateChannel(th.BasicTeam)
channel6 := th.CreatePrivateChannel(th.BasicTeam)
th.AddUserToChannel(th.BasicUser, channel2)
th.AddUserToChannel(th.BasicUser, channel3)
th.AddUserToChannel(th.BasicUser, channel4)
th.AddUserToChannel(th.BasicUser, channel5)
th.AddUserToChannel(th.BasicUser, channel6)
channels := [6]*model.Channel{th.BasicChannel, channel2, channel3, channel4, channel5, channel6}
i := len(channels)
for _, channel := range channels {
for j := i; j > 0; j-- {
th.CreatePost(channel)
}
i--
}
expectedTopChannels := []struct {
ID string
MessageCount int64
}{
{ID: th.BasicChannel.Id, MessageCount: 7},
{ID: channel2.Id, MessageCount: 5},
{ID: channel3.Id, MessageCount: 4},
{ID: channel4.Id, MessageCount: 3},
{ID: channel5.Id, MessageCount: 2},
}
timeRange, _ := model.GetStartUnixMilliForTimeRange(model.TimeRangeToday)
t.Run("get-top-channels-for-user-since", func(t *testing.T) {
topChannels, err := th.App.GetTopChannelsForUserSince(th.BasicUser.Id, "", &model.InsightsOpts{StartUnixMilli: timeRange, Page: 0, PerPage: 5})
require.Nil(t, err)
for i, channel := range topChannels.Items {
assert.Equal(t, expectedTopChannels[i].ID, channel.ID)
assert.Equal(t, expectedTopChannels[i].MessageCount, channel.MessageCount)
}
topChannels, err = th.App.GetTopChannelsForUserSince(th.BasicUser.Id, th.BasicChannel.TeamId, &model.InsightsOpts{StartUnixMilli: timeRange, Page: 1, PerPage: 5})
require.Nil(t, err)
assert.Equal(t, channel6.Id, topChannels.Items[0].ID)
assert.Equal(t, int64(1), topChannels.Items[0].MessageCount)
})
}
+44
View File
@@ -9596,6 +9596,50 @@ func (a *OpenTracingAppLayer) GetTokenById(token string) (*model.Token, *model.A
return resultVar0, resultVar1
}
func (a *OpenTracingAppLayer) GetTopChannelsForTeamSince(teamID string, userID string, opts *model.InsightsOpts) (*model.TopChannelList, *model.AppError) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetTopChannelsForTeamSince")
a.ctx = newCtx
a.app.Srv().Store.SetContext(newCtx)
defer func() {
a.app.Srv().Store.SetContext(origCtx)
a.ctx = origCtx
}()
defer span.Finish()
resultVar0, resultVar1 := a.app.GetTopChannelsForTeamSince(teamID, userID, opts)
if resultVar1 != nil {
span.LogFields(spanlog.Error(resultVar1))
ext.Error.Set(span, true)
}
return resultVar0, resultVar1
}
func (a *OpenTracingAppLayer) GetTopChannelsForUserSince(userID string, teamID string, opts *model.InsightsOpts) (*model.TopChannelList, *model.AppError) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetTopChannelsForUserSince")
a.ctx = newCtx
a.app.Srv().Store.SetContext(newCtx)
defer func() {
a.app.Srv().Store.SetContext(origCtx)
a.ctx = origCtx
}()
defer span.Finish()
resultVar0, resultVar1 := a.app.GetTopChannelsForUserSince(userID, teamID, opts)
if resultVar1 != nil {
span.LogFields(spanlog.Error(resultVar1))
ext.Error.Set(span, true)
}
return resultVar0, resultVar1
}
func (a *OpenTracingAppLayer) GetTopReactionsForTeamSince(teamID string, userID string, opts *model.InsightsOpts) (*model.TopReactionList, *model.AppError) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetTopReactionsForTeamSince")
+8
View File
@@ -4499,6 +4499,14 @@
"id": "app.channel.get_public_channels.get.app_error",
"translation": "Unable to get public channels."
},
{
"id": "app.channel.get_top_for_team_since.app_error",
"translation": " "
},
{
"id": "app.channel.get_top_for_user_since.app_error",
"translation": " "
},
{
"id": "app.channel.get_unread.app_error",
"translation": "Unable to get the channel unread messages."
+35
View File
@@ -3603,6 +3603,41 @@ func (c *Client4) AutocompleteChannelsForTeamForSearch(teamId, name string) (Cha
return ch, BuildResponse(r), nil
}
// GetTopChannelsForTeamSince will return an ordered list of the top channels in a given team.
func (c *Client4) GetTopChannelsForTeamSince(teamId string, timeRange string, page int, perPage int) (*TopChannelList, *Response, error) {
query := fmt.Sprintf("?time_range=%v&page=%v&per_page=%v", timeRange, page, perPage)
r, err := c.DoAPIGet(c.teamRoute(teamId)+"/top/channels"+query, "")
if err != nil {
return nil, BuildResponse(r), err
}
defer closeBody(r)
var topChannels *TopChannelList
if jsonErr := json.NewDecoder(r.Body).Decode(&topChannels); jsonErr != nil {
return nil, nil, NewAppError("GetTopChannelsForTeamSince", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
}
return topChannels, BuildResponse(r), nil
}
// GetTopChannelsForUserSince will return an ordered list of your top channels in a given team.
func (c *Client4) GetTopChannelsForUserSince(teamId string, timeRange string, page int, perPage int) (*TopChannelList, *Response, error) {
query := fmt.Sprintf("?time_range=%v&page=%v&per_page=%v", timeRange, page, perPage)
if teamId != "" {
query += fmt.Sprintf("&team_id=%v", teamId)
}
r, err := c.DoAPIGet(c.usersRoute()+"/me/top/channels"+query, "")
if err != nil {
return nil, BuildResponse(r), err
}
defer closeBody(r)
var topChannels *TopChannelList
if jsonErr := json.NewDecoder(r.Body).Decode(&topChannels); jsonErr != nil {
return nil, nil, NewAppError("GetTopChannelsForUserSince", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
}
return topChannels, BuildResponse(r), nil
}
// Post Section
// CreatePost creates a post based on the provided post struct.
+32 -12
View File
@@ -24,21 +24,32 @@ type InsightsListData struct {
HasNext bool `json:"has_next"`
}
type InsightsData struct {
Rank int `json:"rank"`
}
// Top Reactions
type TopReactionList struct {
InsightsListData
Items []*TopReaction `json:"items"`
}
type TopReaction struct {
InsightsData
EmojiName string `json:"emoji_name"`
Count int64 `json:"count"`
}
// Top Channels
type TopChannelList struct {
InsightsListData
Items []*TopChannel `json:"items"`
}
type TopChannel struct {
ID string `json:"id"`
Type ChannelType `json:"type"`
DisplayName string `json:"display_name"`
Name string `json:"name"`
TeamID string `json:"team_id"`
MessageCount int64 `json:"message_count"`
}
// GetStartUnixMilliForTimeRange gets the unix start time in milliseconds from the given time range.
// Time range can be one of: "1_day", "7_day", or "28_day".
func GetStartUnixMilliForTimeRange(timeRange string) (int64, *AppError) {
@@ -56,10 +67,10 @@ func GetStartUnixMilliForTimeRange(timeRange string) (int64, *AppError) {
return GetStartOfDayMillis(now, offset), NewAppError("Insights.IsValidRequest", "model.insights.time_range.app_error", nil, "", http.StatusBadRequest)
}
// GetTopReactionListWithRankAndPagination adds a rank to each item in the given list of TopReaction and checks if there is
// GetTopReactionListWithPagination adds a rank to each item in the given list of TopReaction and checks if there is
// another page that can be fetched based on the given limit and offset. The given list of TopReaction is assumed to be
// sorted by Count. Returns a TopReactionList.
func GetTopReactionListWithRankAndPagination(reactions []*TopReaction, limit int, offset int) *TopReactionList {
func GetTopReactionListWithPagination(reactions []*TopReaction, limit int) *TopReactionList {
// Add pagination support
var hasNext bool
if (limit != 0) && (len(reactions) == limit+1) {
@@ -67,10 +78,19 @@ func GetTopReactionListWithRankAndPagination(reactions []*TopReaction, limit int
reactions = reactions[:len(reactions)-1]
}
// Assign rank to each reaction
for i, reaction := range reactions {
reaction.Rank = offset + i + 1
}
return &TopReactionList{InsightsListData: InsightsListData{HasNext: hasNext}, Items: reactions}
}
// GetTopChannelListWithPagination adds a rank to each item in the given list of TopChannel and checks if there is
// another page that can be fetched based on the given limit and offset. The given list of TopChannel is assumed to be
// sorted by Score. Returns a TopChannelList.
func GetTopChannelListWithPagination(channels []*TopChannel, limit int) *TopChannelList {
// Add pagination support
var hasNext bool
if (limit != 0) && (len(channels) == limit+1) {
hasNext = true
channels = channels[:len(channels)-1]
}
return &TopChannelList{InsightsListData: InsightsListData{HasNext: hasNext}, Items: channels}
}
+39 -16
View File
@@ -29,8 +29,7 @@ func TestGetStartUnixMilliForTimeRang(t *testing.T) {
}
}
func TestGetTopReactionListWithRankAndPagination(t *testing.T) {
func TestGetTopReactionListWithPagination(t *testing.T) {
reactions := []*TopReaction{
{EmojiName: "smile", Count: 200},
{EmojiName: "+1", Count: 190},
@@ -61,21 +60,45 @@ func TestGetTopReactionListWithRankAndPagination(t *testing.T) {
for _, test := range hasNextTC {
t.Run(test.Description, func(t *testing.T) {
actual := GetTopReactionListWithRankAndPagination(reactions, test.Limit, test.Offset)
actual := GetTopReactionListWithPagination(reactions, test.Limit)
assert.Equal(t, test.Expected.HasNext, actual.HasNext)
})
}
}
func TestGetTopChannelListWithPagination(t *testing.T) {
channels := []*TopChannel{
{ID: NewId(), MessageCount: 200},
{ID: NewId(), MessageCount: 150},
{ID: NewId(), MessageCount: 120},
{ID: NewId(), MessageCount: 105},
{ID: NewId(), MessageCount: 5},
{ID: NewId(), MessageCount: 2}}
hasNextTC := []struct {
Description string
Limit int
Offset int
Expected *TopChannelList
}{
{
Description: "has one page",
Limit: len(channels),
Offset: 0,
Expected: &TopChannelList{InsightsListData: InsightsListData{HasNext: false}, Items: channels},
},
{
Description: "has more than one page",
Limit: len(channels) - 1,
Offset: 0,
Expected: &TopChannelList{InsightsListData: InsightsListData{HasNext: true}, Items: channels},
},
}
for _, test := range hasNextTC {
t.Run(test.Description, func(t *testing.T) {
actual := GetTopChannelListWithPagination(channels, test.Limit)
assert.Equal(t, test.Expected.HasNext, actual.HasNext)
})
}
t.Run("ranks for first and second page", func(t *testing.T) {
firstPage := GetTopReactionListWithRankAndPagination(reactions, 5, 0)
for i, r := range firstPage.Items {
assert.Equal(t, i+1, r.Rank)
}
secondPage := GetTopReactionListWithRankAndPagination(reactions, 5, 5)
for i, r := range secondPage.Items {
assert.Equal(t, i+1+5, r.Rank)
}
})
}
@@ -1748,6 +1748,42 @@ func (s *OpenTracingLayerChannelStore) GetTeamMembersForChannel(channelID string
return result, err
}
func (s *OpenTracingLayerChannelStore) GetTopChannelsForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopChannelList, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.GetTopChannelsForTeamSince")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
result, err := s.ChannelStore.GetTopChannelsForTeamSince(teamID, userID, since, offset, limit)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return result, err
}
func (s *OpenTracingLayerChannelStore) GetTopChannelsForUserSince(userID string, teamID string, since int64, offset int, limit int) (*model.TopChannelList, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.GetTopChannelsForUserSince")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
result, err := s.ChannelStore.GetTopChannelsForUserSince(userID, teamID, since, offset, limit)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return result, err
}
func (s *OpenTracingLayerChannelStore) GroupSyncedChannelCount() (int64, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.GroupSyncedChannelCount")
+42
View File
@@ -1975,6 +1975,48 @@ func (s *RetryLayerChannelStore) GetTeamMembersForChannel(channelID string) ([]s
}
func (s *RetryLayerChannelStore) GetTopChannelsForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopChannelList, error) {
tries := 0
for {
result, err := s.ChannelStore.GetTopChannelsForTeamSince(teamID, userID, since, offset, limit)
if err == nil {
return result, nil
}
if !isRepeatableError(err) {
return result, err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return result, err
}
timepkg.Sleep(100 * timepkg.Millisecond)
}
}
func (s *RetryLayerChannelStore) GetTopChannelsForUserSince(userID string, teamID string, since int64, offset int, limit int) (*model.TopChannelList, error) {
tries := 0
for {
result, err := s.ChannelStore.GetTopChannelsForUserSince(userID, teamID, since, offset, limit)
if err == nil {
return result, nil
}
if !isRepeatableError(err) {
return result, err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return result, err
}
timepkg.Sleep(100 * timepkg.Millisecond)
}
}
func (s *RetryLayerChannelStore) GroupSyncedChannelCount() (int64, error) {
tries := 0
+158
View File
@@ -4097,3 +4097,161 @@ func (s SqlChannelStore) GetTeamForChannel(channelID string) (*model.Team, error
}
return &team, nil
}
// GetTopChannelsForTeamSince returns the filtered post counts of the following Channels sets:
// a) those that are private channels in the given user's membership graph on the given team, and
// b) those that are public channels in the given team.
func (s SqlChannelStore) GetTopChannelsForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopChannelList, error) {
channels := make([]*model.TopChannel, 0)
var args []interface{}
postgresPropQuery := `AND (Posts.Props ->> 'from_bot' IS NULL OR Posts.Props ->> 'from_bot' = 'false')`
mySqlPropsQuery := `AND (JSON_EXTRACT(Posts.Props, '$.from_bot') IS NULL OR JSON_EXTRACT(Posts.Props, '$.from_bot') = 'false')`
query := `
SELECT
ID,
Type,
DisplayName,
Name,
TeamID,
MessageCount
FROM
((SELECT
Posts.ChannelId AS ID,
'O' AS Type,
PublicChannels.DisplayName AS DisplayName,
PublicChannels.Name AS Name,
PublicChannels.TeamId AS TeamID,
count(Posts.Id) AS MessageCount,
PublicChannels.DeleteAt AS DeleteAt
FROM
Posts
LEFT JOIN PublicChannels on Posts.ChannelId = PublicChannels.Id
WHERE
Posts.DeleteAt = 0
AND Posts.CreateAt > ?
AND Posts.Type = ''`
args = []interface{}{since}
if s.DriverName() == model.DatabaseDriverMysql {
query += mySqlPropsQuery
} else if s.DriverName() == model.DatabaseDriverPostgres {
query += postgresPropQuery
}
query += `
AND PublicChannels.TeamId = ?
GROUP BY
Posts.ChannelId,
PublicChannels.DisplayName,
PublicChannels.Name,
PublicChannels.TeamId,
PublicChannels.DeleteAt)
UNION ALL
(SELECT
Posts.ChannelId AS ID,
Channels.Type AS Type,
Channels.DisplayName AS DisplayName,
Channels.Name AS Name,
Channels.TeamId AS TeamID,
count(Posts.Id) AS MessageCount,
Channels.DeleteAt AS DeleteAt
FROM
Posts
LEFT JOIN Channels on Posts.ChannelId = Channels.Id
LEFT JOIN ChannelMembers on Posts.ChannelId = ChannelMembers.ChannelId
WHERE
Posts.DeleteAt = 0
AND Posts.CreateAt > ?
AND Posts.Type = ''`
args = append(args, teamID, since)
if s.DriverName() == model.DatabaseDriverMysql {
query += mySqlPropsQuery
} else if s.DriverName() == model.DatabaseDriverPostgres {
query += postgresPropQuery
}
query += `
AND Channels.TeamId = ?
AND Channels.Type = 'P'
AND ChannelMembers.UserId = ?
GROUP BY
Posts.ChannelId,
Channels.Type,
Channels.DisplayName,
Channels.Name,
Channels.TeamId,
Channels.DeleteAt)) AS A
WHERE
DeleteAt = 0
ORDER BY
MessageCount DESC,
Name ASC
LIMIT ?
OFFSET ?`
args = append(args, teamID, userID, limit+1, offset)
if err := s.GetReplicaX().Select(&channels, query, args...); err != nil {
return nil, errors.Wrap(err, "failed to get top Channels")
}
return model.GetTopChannelListWithPagination(channels, limit), nil
}
// GetTopChannelsForUserSince returns the filtered post counts of channels with with posts created by the user
// after the given timestamp within the given team (or across the workspace if no team is given). Excludes DM and GM channels.
func (s SqlChannelStore) GetTopChannelsForUserSince(userID string, teamID string, since int64, offset int, limit int) (*model.TopChannelList, error) {
channels := make([]*model.TopChannel, 0)
var args []interface{}
var query string
query = `
SELECT
Posts.ChannelId AS ID,
Channels.Type AS Type,
Channels.DisplayName AS DisplayName,
Channels.Name AS Name,
Channels.TeamId AS TeamID,
count(Posts.Id) AS MessageCount
FROM
Posts
LEFT JOIN Channels on Posts.ChannelId = Channels.Id
LEFT JOIN ChannelMembers on Posts.ChannelId = ChannelMembers.ChannelId
WHERE
Posts.DeleteAt = 0
AND Posts.CreateAt > ?
AND Posts.Type = ''
AND Posts.UserID = ?
AND Channels.DeleteAt = 0
AND (Channels.Type = 'O' OR Channels.Type = 'P')
AND ChannelMembers.UserId = ?`
args = []interface{}{since, userID, userID}
if teamID != "" {
query += `
AND Channels.TeamID = ?`
args = append(args, teamID)
}
query += `
Group By
Posts.ChannelId,
Channels.Type,
Channels.DisplayName,
Channels.Name,
Channels.TeamId
ORDER BY
MessageCount DESC,
Name ASC
LIMIT ?
OFFSET ?`
args = append(args, limit+1, offset)
if err := s.GetReplicaX().Select(&channels, query, args...); err != nil {
return nil, errors.Wrap(err, "failed to get top Channels")
}
return model.GetTopChannelListWithPagination(channels, limit), nil
}
+22 -15
View File
@@ -232,7 +232,7 @@ func (s *SqlReactionStore) PermanentDeleteBatch(endTime int64, limit int64) (int
// a) those created by anyone in private channels in the given user's membership graph on the given team, and
// b) those created by anyone in public channels on the given team.
func (s *SqlReactionStore) GetTopForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopReactionList, error) {
var reactions []*model.TopReaction
reactions := make([]*model.TopReaction, 0)
query := `
SELECT
@@ -241,34 +241,41 @@ func (s *SqlReactionStore) GetTopForTeamSince(teamID string, userID string, sinc
FROM ((
SELECT
EmojiName,
count(EmojiName) AS EmojiCount
count(EmojiName) AS EmojiCount,
Reactions.DeleteAt AS DeleteAt,
Reactions.CreateAt AS CreateAt
FROM
ChannelMembers
INNER JOIN Channels ON ChannelMembers.ChannelId = Channels.Id
INNER JOIN Posts ON Channels.Id = Posts.ChannelId
INNER JOIN Reactions ON Posts.Id = Reactions.PostId
WHERE
Reactions.DeleteAt = 0
AND ChannelMembers.UserId = ?
ChannelMembers.UserId = ?
AND Channels.Type = 'P'
AND Channels.TeamId = ?
AND Reactions.CreateAt > ?
GROUP BY
Reactions.EmojiName)
Reactions.EmojiName,
Reactions.DeleteAt,
Reactions.CreateAt)
UNION ALL (
SELECT
EmojiName,
count(EmojiName) AS EmojiCount
count(EmojiName) AS EmojiCount,
Reactions.DeleteAt AS DeleteAt,
Reactions.CreateAt AS CreateAt
FROM
Reactions
INNER JOIN Posts ON Reactions.PostId = Posts.Id
INNER JOIN PublicChannels ON Posts.ChannelId = PublicChannels.Id
WHERE
Reactions.DeleteAt = 0
AND PublicChannels.TeamId = ?
AND Reactions.CreateAt > ?
PublicChannels.TeamId = ?
GROUP BY
Reactions.EmojiName)) AS A
Reactions.EmojiName,
Reactions.DeleteAt,
Reactions.CreateAt)) AS A
WHERE
DeleteAt = 0
AND CreateAt > ?
GROUP BY
EmojiName
ORDER BY
@@ -277,18 +284,18 @@ func (s *SqlReactionStore) GetTopForTeamSince(teamID string, userID string, sinc
LIMIT ?
OFFSET ?`
if err := s.GetReplicaX().Select(&reactions, query, userID, teamID, since, teamID, since, limit+1, offset); err != nil {
if err := s.GetReplicaX().Select(&reactions, query, userID, teamID, teamID, since, limit+1, offset); err != nil {
return nil, errors.Wrap(err, "failed to get top Reactions")
}
return model.GetTopReactionListWithRankAndPagination(reactions, limit, offset), nil
return model.GetTopReactionListWithPagination(reactions, limit), nil
}
// GetTopForUserSince returns the instance counts of the following Reactions sets:
// a) those created by the given user in any channel type on the given team (across the workspace if no team is given), and
// b) those created by the given user in DM or group channels.
func (s *SqlReactionStore) GetTopForUserSince(userID string, teamID string, since int64, offset int, limit int) (*model.TopReactionList, error) {
var reactions []*model.TopReaction
reactions := make([]*model.TopReaction, 0)
var args []interface{}
var query string
@@ -339,7 +346,7 @@ func (s *SqlReactionStore) GetTopForUserSince(userID string, teamID string, sinc
return nil, errors.Wrap(err, "failed to get top Reactions")
}
return model.GetTopReactionListWithRankAndPagination(reactions, limit, offset), nil
return model.GetTopReactionListWithPagination(reactions, limit), nil
}
func (s *SqlReactionStore) saveReactionAndUpdatePost(transaction *sqlxTxWrapper, reaction *model.Reaction) error {
+4
View File
@@ -281,6 +281,10 @@ type ChannelStore interface {
SetShared(channelId string, shared bool) error
// GetTeamForChannel returns the team for a given channelID.
GetTeamForChannel(channelID string) (*model.Team, error)
// Insights
GetTopChannelsForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopChannelList, error)
GetTopChannelsForUserSince(userID string, teamID string, since int64, offset int, limit int) (*model.TopChannelList, error)
}
type ChannelMemberHistoryStore interface {
+46
View File
@@ -1508,6 +1508,52 @@ func (_m *ChannelStore) GetTeamMembersForChannel(channelID string) ([]string, er
return r0, r1
}
// GetTopChannelsForTeamSince provides a mock function with given fields: teamID, userID, since, offset, limit
func (_m *ChannelStore) GetTopChannelsForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopChannelList, error) {
ret := _m.Called(teamID, userID, since, offset, limit)
var r0 *model.TopChannelList
if rf, ok := ret.Get(0).(func(string, string, int64, int, int) *model.TopChannelList); ok {
r0 = rf(teamID, userID, since, offset, limit)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.TopChannelList)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, string, int64, int, int) error); ok {
r1 = rf(teamID, userID, since, offset, limit)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetTopChannelsForUserSince provides a mock function with given fields: userID, teamID, since, offset, limit
func (_m *ChannelStore) GetTopChannelsForUserSince(userID string, teamID string, since int64, offset int, limit int) (*model.TopChannelList, error) {
ret := _m.Called(userID, teamID, since, offset, limit)
var r0 *model.TopChannelList
if rf, ok := ret.Get(0).(func(string, string, int64, int, int) *model.TopChannelList); ok {
r0 = rf(userID, teamID, since, offset, limit)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.TopChannelList)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, string, int64, int, int) error); ok {
r1 = rf(userID, teamID, since, offset, limit)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GroupSyncedChannelCount provides a mock function with given fields:
func (_m *ChannelStore) GroupSyncedChannelCount() (int64, error) {
ret := _m.Called()
+32
View File
@@ -1605,6 +1605,38 @@ func (s *TimerLayerChannelStore) GetTeamMembersForChannel(channelID string) ([]s
return result, err
}
func (s *TimerLayerChannelStore) GetTopChannelsForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopChannelList, error) {
start := timemodule.Now()
result, err := s.ChannelStore.GetTopChannelsForTeamSince(teamID, userID, since, offset, limit)
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("ChannelStore.GetTopChannelsForTeamSince", success, elapsed)
}
return result, err
}
func (s *TimerLayerChannelStore) GetTopChannelsForUserSince(userID string, teamID string, since int64, offset int, limit int) (*model.TopChannelList, error) {
start := timemodule.Now()
result, err := s.ChannelStore.GetTopChannelsForUserSince(userID, teamID, since, offset, limit)
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("ChannelStore.GetTopChannelsForUserSince", success, elapsed)
}
return result, err
}
func (s *TimerLayerChannelStore) GroupSyncedChannelCount() (int64, error) {
start := timemodule.Now()
-11
View File
@@ -374,17 +374,6 @@ func (c *Context) RequireTimestamp() *Context {
return c
}
func (c *Context) RequireTimeRange() *Context {
if c.Err != nil {
return c
}
if c.Params.TimeRange == 0 {
c.SetInvalidURLParam("time_range")
}
return c
}
func (c *Context) RequireChannelId() *Context {
if c.Err != nil {
return c
+2 -6
View File
@@ -31,7 +31,7 @@ type Params struct {
TokenId string
ThreadId string
Timestamp int64
TimeRange int64
TimeRange string
ChannelId string
PostId string
PolicyId string
@@ -255,11 +255,7 @@ func ParamsFromRequest(r *http.Request) *Params {
params.Timestamp = val
}
if val, err := model.GetStartUnixMilliForTimeRange(query.Get("time_range")); err != nil {
params.TimeRange = 0
} else {
params.TimeRange = val
}
params.TimeRange = query.Get("time_range")
if val, err := strconv.ParseBool(query.Get("permanent")); err == nil {
params.Permanent = val