[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
Этот коммит содержится в:
Mylon Suren
2022-04-26 14:42:24 -04:00
коммит произвёл GitHub
родитель 9d37a6cc3d
Коммит 52ac449012
22 изменённых файлов: 1453 добавлений и 510 удалений

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

@@ -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
api4/insights.go Обычный файл
Просмотреть файл

@@ -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
api4/insights_test.go Обычный файл
Просмотреть файл

@@ -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)
})
}

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

@@ -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)
}

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

@@ -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)
})
}

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

@@ -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)

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

@@ -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
}

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

@@ -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)
})
}

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

@@ -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")

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

@@ -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."

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

@@ -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.

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

@@ -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}
}

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

@@ -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")

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

@@ -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

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

@@ -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
}

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

@@ -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 {

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

@@ -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 {

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

@@ -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()

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

@@ -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()

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

@@ -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

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

@@ -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