[MM-42742] Add top reactions endpoint (#19850)
Этот коммит содержится в:
@@ -16,6 +16,9 @@ func (api *API) InitReaction() {
|
|||||||
api.BaseRoutes.Post.Handle("/reactions", api.APISessionRequired(getReactions)).Methods("GET")
|
api.BaseRoutes.Post.Handle("/reactions", api.APISessionRequired(getReactions)).Methods("GET")
|
||||||
api.BaseRoutes.ReactionByNameForPostForUser.Handle("", api.APISessionRequired(deleteReaction)).Methods("DELETE")
|
api.BaseRoutes.ReactionByNameForPostForUser.Handle("", api.APISessionRequired(deleteReaction)).Methods("DELETE")
|
||||||
api.BaseRoutes.Posts.Handle("/ids/reactions", api.APISessionRequired(getBulkReactions)).Methods("POST")
|
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) {
|
func saveReaction(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -138,3 +141,85 @@ func getBulkReactions(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
w.Write(js)
|
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,6 +6,7 @@ package api4
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -584,3 +585,367 @@ func TestGetBulkReactions(t *testing.T) {
|
|||||||
CheckUnauthorizedStatus(t, resp)
|
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)
|
GetThreadMembershipsForUser(userID, teamID string) ([]*model.ThreadMembership, error)
|
||||||
GetThreadsForUser(userID, teamID string, options model.GetUserThreadsOpts) (*model.Threads, *model.AppError)
|
GetThreadsForUser(userID, teamID string, options model.GetUserThreadsOpts) (*model.Threads, *model.AppError)
|
||||||
GetTokenById(token string) (*model.Token, *model.AppError)
|
GetTokenById(token string) (*model.Token, *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)
|
GetUploadSession(uploadId string) (*model.UploadSession, *model.AppError)
|
||||||
GetUploadSessionsForUser(userID string) ([]*model.UploadSession, *model.AppError)
|
GetUploadSessionsForUser(userID string) ([]*model.UploadSession, *model.AppError)
|
||||||
GetUser(userID string) (*model.User, *model.AppError)
|
GetUser(userID string) (*model.User, *model.AppError)
|
||||||
|
|||||||
@@ -9596,6 +9596,50 @@ func (a *OpenTracingAppLayer) GetTokenById(token string) (*model.Token, *model.A
|
|||||||
return resultVar0, resultVar1
|
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")
|
||||||
|
|
||||||
|
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.GetTopReactionsForTeamSince(teamID, userID, opts)
|
||||||
|
|
||||||
|
if resultVar1 != nil {
|
||||||
|
span.LogFields(spanlog.Error(resultVar1))
|
||||||
|
ext.Error.Set(span, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultVar0, resultVar1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *OpenTracingAppLayer) GetTopReactionsForUserSince(userID string, teamID string, opts *model.InsightsOpts) (*model.TopReactionList, *model.AppError) {
|
||||||
|
origCtx := a.ctx
|
||||||
|
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetTopReactionsForUserSince")
|
||||||
|
|
||||||
|
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.GetTopReactionsForUserSince(userID, teamID, opts)
|
||||||
|
|
||||||
|
if resultVar1 != nil {
|
||||||
|
span.LogFields(spanlog.Error(resultVar1))
|
||||||
|
ext.Error.Set(span, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultVar0, resultVar1
|
||||||
|
}
|
||||||
|
|
||||||
func (a *OpenTracingAppLayer) GetTotalUsersStats(viewRestrictions *model.ViewUsersRestrictions) (*model.UsersStats, *model.AppError) {
|
func (a *OpenTracingAppLayer) GetTotalUsersStats(viewRestrictions *model.ViewUsersRestrictions) (*model.UsersStats, *model.AppError) {
|
||||||
origCtx := a.ctx
|
origCtx := a.ctx
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetTotalUsersStats")
|
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetTotalUsersStats")
|
||||||
|
|||||||
@@ -96,6 +96,30 @@ func populateEmptyReactions(postIDs []string, reactions map[string][]*model.Reac
|
|||||||
return reactions
|
return reactions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) GetTopReactionsForTeamSince(teamID string, userID string, opts *model.InsightsOpts) (*model.TopReactionList, *model.AppError) {
|
||||||
|
if !a.Config().FeatureFlags.InsightsEnabled {
|
||||||
|
return nil, model.NewAppError("GetTopReactionsForTeamSince", "api.insights.feature_disabled", nil, "", http.StatusNotImplemented)
|
||||||
|
}
|
||||||
|
|
||||||
|
topReactionList, err := a.Srv().Store.Reaction().GetTopForTeamSince(teamID, userID, opts.StartUnixMilli, opts.Page*opts.PerPage, opts.PerPage)
|
||||||
|
if err != nil {
|
||||||
|
return nil, model.NewAppError("GetTopReactionsForTeamSince", "app.reaction.get_top_for_team_since.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
return topReactionList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) GetTopReactionsForUserSince(userID string, teamID string, opts *model.InsightsOpts) (*model.TopReactionList, *model.AppError) {
|
||||||
|
if !a.Config().FeatureFlags.InsightsEnabled {
|
||||||
|
return nil, model.NewAppError("GetTopReactionsForUserSince", "api.insights.feature_disabled", nil, "", http.StatusNotImplemented)
|
||||||
|
}
|
||||||
|
|
||||||
|
topReactionList, err := a.Srv().Store.Reaction().GetTopForUserSince(userID, teamID, opts.StartUnixMilli, opts.Page*opts.PerPage, opts.PerPage)
|
||||||
|
if err != nil {
|
||||||
|
return nil, model.NewAppError("GetTopReactionsForUserSince", "app.reaction.get_top_for_user_since.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
return topReactionList, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) DeleteReactionForPost(c *request.Context, reaction *model.Reaction) *model.AppError {
|
func (a *App) DeleteReactionForPost(c *request.Context, reaction *model.Reaction) *model.AppError {
|
||||||
post, err := a.GetSinglePost(reaction.PostId)
|
post, err := a.GetSinglePost(reaction.PostId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -84,3 +85,345 @@ func TestSharedChannelSyncForReactionActions(t *testing.T) {
|
|||||||
assert.Equal(t, channel.Id, sharedChannelService.channelNotifications[1])
|
assert.Equal(t, channel.Id, sharedChannelService.channelNotifications[1])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetTopReactionsForTeamSince(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 })
|
||||||
|
|
||||||
|
userId := th.BasicUser.Id
|
||||||
|
user2Id := th.BasicUser2.Id
|
||||||
|
|
||||||
|
post1 := th.CreatePost(th.BasicChannel)
|
||||||
|
post2 := th.CreatePost(th.BasicChannel)
|
||||||
|
post3 := th.CreatePost(th.BasicChannel)
|
||||||
|
post4 := th.CreatePost(th.BasicChannel)
|
||||||
|
post5 := th.CreatePost(th.BasicChannel)
|
||||||
|
|
||||||
|
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)}
|
||||||
|
|
||||||
|
timeRange, _ := model.GetStartUnixMilliForTimeRange(model.TimeRangeToday)
|
||||||
|
|
||||||
|
t.Run("get-top-reactions-for-team-since", func(t *testing.T) {
|
||||||
|
topReactions, err := th.App.GetTopReactionsForTeamSince(teamId, userId, &model.InsightsOpts{StartUnixMilli: timeRange, Page: 0, PerPage: 5})
|
||||||
|
require.Nil(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 = th.App.GetTopReactionsForTeamSince(teamId, userId, &model.InsightsOpts{StartUnixMilli: timeRange, Page: 1, PerPage: 5})
|
||||||
|
require.Nil(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 feature flag", func(t *testing.T) {
|
||||||
|
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = false })
|
||||||
|
_, err := th.App.GetTopReactionsForTeamSince(userId, teamId, &model.InsightsOpts{StartUnixMilli: timeRange, Page: 0, PerPage: 5})
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTopReactionsForUserSince(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 })
|
||||||
|
|
||||||
|
userId := th.BasicUser.Id
|
||||||
|
|
||||||
|
post1 := th.CreatePost(th.BasicChannel)
|
||||||
|
post2 := th.CreatePost(th.BasicChannel)
|
||||||
|
post3 := th.CreatePost(th.BasicChannel)
|
||||||
|
post4 := th.CreatePost(th.BasicChannel)
|
||||||
|
post5 := th.CreatePost(th.BasicChannel)
|
||||||
|
post6 := th.CreatePost(th.BasicChannel)
|
||||||
|
|
||||||
|
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)}
|
||||||
|
|
||||||
|
timeRange, _ := model.GetStartUnixMilliForTimeRange(model.TimeRangeToday)
|
||||||
|
|
||||||
|
t.Run("get-top-reactions-for-user-since", func(t *testing.T) {
|
||||||
|
topReactions, err := th.App.GetTopReactionsForUserSince(userId, teamId, &model.InsightsOpts{StartUnixMilli: timeRange, Page: 0, PerPage: 5})
|
||||||
|
require.Nil(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 = th.App.GetTopReactionsForUserSince(userId, teamId, &model.InsightsOpts{StartUnixMilli: timeRange, Page: 1, PerPage: 5})
|
||||||
|
require.Nil(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 feature flag", func(t *testing.T) {
|
||||||
|
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = false })
|
||||||
|
_, err := th.App.GetTopReactionsForUserSince(userId, teamId, &model.InsightsOpts{StartUnixMilli: timeRange, Page: 0, PerPage: 5})
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
16
i18n/en.json
16
i18n/en.json
@@ -1881,6 +1881,10 @@
|
|||||||
"id": "api.incoming_webhook.invalid_username.app_error",
|
"id": "api.incoming_webhook.invalid_username.app_error",
|
||||||
"translation": "Invalid username."
|
"translation": "Invalid username."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "api.insights.feature_disabled",
|
||||||
|
"translation": " "
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "api.invalid_channel",
|
"id": "api.invalid_channel",
|
||||||
"translation": "Channel listed in the request doesn't belong to the user"
|
"translation": "Channel listed in the request doesn't belong to the user"
|
||||||
@@ -5887,6 +5891,14 @@
|
|||||||
"id": "app.reaction.get_for_post.app_error",
|
"id": "app.reaction.get_for_post.app_error",
|
||||||
"translation": "Unable to get reactions for post."
|
"translation": "Unable to get reactions for post."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "app.reaction.get_top_for_team_since.app_error",
|
||||||
|
"translation": " "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.reaction.get_top_for_user_since.app_error",
|
||||||
|
"translation": " "
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "app.reaction.save.save.app_error",
|
"id": "app.reaction.save.save.app_error",
|
||||||
"translation": "Unable to save reaction."
|
"translation": "Unable to save reaction."
|
||||||
@@ -8451,6 +8463,10 @@
|
|||||||
"id": "model.incoming_hook.username.app_error",
|
"id": "model.incoming_hook.username.app_error",
|
||||||
"translation": "Invalid username."
|
"translation": "Invalid username."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "model.insights.time_range.app_error",
|
||||||
|
"translation": " "
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "model.job.is_valid.create_at.app_error",
|
"id": "model.job.is_valid.create_at.app_error",
|
||||||
"translation": "Create at must be a valid time."
|
"translation": "Create at must be a valid time."
|
||||||
|
|||||||
@@ -6496,6 +6496,39 @@ func (c *Client4) GetBulkReactions(postIds []string) (map[string][]*Reaction, *R
|
|||||||
return reactions, BuildResponse(r), nil
|
return reactions, BuildResponse(r), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client4) GetTopReactionsForTeamSince(teamId string, timeRange string, page int, perPage int) (*TopReactionList, *Response, error) {
|
||||||
|
query := fmt.Sprintf("?time_range=%v&page=%v&per_page=%v", timeRange, page, perPage)
|
||||||
|
r, err := c.DoAPIGet(c.teamRoute(teamId)+"/top/reactions"+query, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, BuildResponse(r), err
|
||||||
|
}
|
||||||
|
defer closeBody(r)
|
||||||
|
var topReactions *TopReactionList
|
||||||
|
if jsonErr := json.NewDecoder(r.Body).Decode(&topReactions); jsonErr != nil {
|
||||||
|
return nil, nil, NewAppError("GetTopReactionsForTeamSince", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
return topReactions, BuildResponse(r), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client4) GetTopReactionsForUserSince(teamId string, timeRange string, page int, perPage int) (*TopReactionList, *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/reactions"+query, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, BuildResponse(r), err
|
||||||
|
}
|
||||||
|
defer closeBody(r)
|
||||||
|
var topReactions *TopReactionList
|
||||||
|
if jsonErr := json.NewDecoder(r.Body).Decode(&topReactions); jsonErr != nil {
|
||||||
|
return nil, nil, NewAppError("GetTopReactionsForUserSince", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
return topReactions, BuildResponse(r), nil
|
||||||
|
}
|
||||||
|
|
||||||
// Timezone Section
|
// Timezone Section
|
||||||
|
|
||||||
// GetSupportedTimezone returns a page of supported timezones on the system.
|
// GetSupportedTimezone returns a page of supported timezones on the system.
|
||||||
|
|||||||
76
model/insights.go
Обычный файл
76
model/insights.go
Обычный файл
@@ -0,0 +1,76 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TimeRangeToday string = "today"
|
||||||
|
TimeRange7Day string = "7_day"
|
||||||
|
TimeRange28Day string = "28_day"
|
||||||
|
)
|
||||||
|
|
||||||
|
type InsightsOpts struct {
|
||||||
|
StartUnixMilli int64
|
||||||
|
Page int
|
||||||
|
PerPage int
|
||||||
|
}
|
||||||
|
|
||||||
|
type InsightsListData struct {
|
||||||
|
HasNext bool `json:"has_next"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InsightsData struct {
|
||||||
|
Rank int `json:"rank"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TopReactionList struct {
|
||||||
|
InsightsListData
|
||||||
|
Items []*TopReaction `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TopReaction struct {
|
||||||
|
InsightsData
|
||||||
|
EmojiName string `json:"emoji_name"`
|
||||||
|
Count int64 `json:"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) {
|
||||||
|
now := time.Now()
|
||||||
|
_, offset := now.Zone()
|
||||||
|
switch timeRange {
|
||||||
|
case TimeRangeToday:
|
||||||
|
return GetStartOfDayMillis(now, offset), nil
|
||||||
|
case TimeRange7Day:
|
||||||
|
return GetStartOfDayMillis(now.Add(time.Hour*time.Duration(-168)), offset), nil
|
||||||
|
case TimeRange28Day:
|
||||||
|
return GetStartOfDayMillis(now.Add(time.Hour*time.Duration(-672)), offset), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
// 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 {
|
||||||
|
// Add pagination support
|
||||||
|
var hasNext bool
|
||||||
|
if (limit != 0) && (len(reactions) == limit+1) {
|
||||||
|
hasNext = true
|
||||||
|
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}
|
||||||
|
}
|
||||||
81
model/insights_test.go
Обычный файл
81
model/insights_test.go
Обычный файл
@@ -0,0 +1,81 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetStartUnixMilliForTimeRang(t *testing.T) {
|
||||||
|
tc := [3]string{"today", "7_day", "28_day"}
|
||||||
|
|
||||||
|
for _, timeRange := range tc {
|
||||||
|
t.Run(timeRange, func(t *testing.T) {
|
||||||
|
_, err := GetStartUnixMilliForTimeRange(timeRange)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidTimeRanges := [3]string{"", "1_day", "10_day"}
|
||||||
|
|
||||||
|
for _, timeRange := range invalidTimeRanges {
|
||||||
|
t.Run(timeRange, func(t *testing.T) {
|
||||||
|
_, err := GetStartUnixMilliForTimeRange(timeRange)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTopReactionListWithRankAndPagination(t *testing.T) {
|
||||||
|
|
||||||
|
reactions := []*TopReaction{
|
||||||
|
{EmojiName: "smile", Count: 200},
|
||||||
|
{EmojiName: "+1", Count: 190},
|
||||||
|
{EmojiName: "100", Count: 100},
|
||||||
|
{EmojiName: "-1", Count: 75},
|
||||||
|
{EmojiName: "checkmark", Count: 50},
|
||||||
|
{EmojiName: "mattermost", Count: 49}}
|
||||||
|
|
||||||
|
hasNextTC := []struct {
|
||||||
|
Description string
|
||||||
|
Limit int
|
||||||
|
Offset int
|
||||||
|
Expected *TopReactionList
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Description: "has one page",
|
||||||
|
Limit: len(reactions),
|
||||||
|
Offset: 0,
|
||||||
|
Expected: &TopReactionList{InsightsListData: InsightsListData{HasNext: false}, Items: reactions},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Description: "has more than one page",
|
||||||
|
Limit: len(reactions) - 1,
|
||||||
|
Offset: 0,
|
||||||
|
Expected: &TopReactionList{InsightsListData: InsightsListData{HasNext: true}, Items: reactions},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range hasNextTC {
|
||||||
|
t.Run(test.Description, func(t *testing.T) {
|
||||||
|
actual := GetTopReactionListWithRankAndPagination(reactions, test.Limit, test.Offset)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -6451,6 +6451,42 @@ func (s *OpenTracingLayerReactionStore) GetForPostSince(postId string, since int
|
|||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *OpenTracingLayerReactionStore) GetTopForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopReactionList, error) {
|
||||||
|
origCtx := s.Root.Store.Context()
|
||||||
|
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ReactionStore.GetTopForTeamSince")
|
||||||
|
s.Root.Store.SetContext(newCtx)
|
||||||
|
defer func() {
|
||||||
|
s.Root.Store.SetContext(origCtx)
|
||||||
|
}()
|
||||||
|
|
||||||
|
defer span.Finish()
|
||||||
|
result, err := s.ReactionStore.GetTopForTeamSince(teamID, userID, since, offset, limit)
|
||||||
|
if err != nil {
|
||||||
|
span.LogFields(spanlog.Error(err))
|
||||||
|
ext.Error.Set(span, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OpenTracingLayerReactionStore) GetTopForUserSince(userID string, teamID string, since int64, offset int, limit int) (*model.TopReactionList, error) {
|
||||||
|
origCtx := s.Root.Store.Context()
|
||||||
|
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ReactionStore.GetTopForUserSince")
|
||||||
|
s.Root.Store.SetContext(newCtx)
|
||||||
|
defer func() {
|
||||||
|
s.Root.Store.SetContext(origCtx)
|
||||||
|
}()
|
||||||
|
|
||||||
|
defer span.Finish()
|
||||||
|
result, err := s.ReactionStore.GetTopForUserSince(userID, teamID, since, offset, limit)
|
||||||
|
if err != nil {
|
||||||
|
span.LogFields(spanlog.Error(err))
|
||||||
|
ext.Error.Set(span, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
func (s *OpenTracingLayerReactionStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, error) {
|
func (s *OpenTracingLayerReactionStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, error) {
|
||||||
origCtx := s.Root.Store.Context()
|
origCtx := s.Root.Store.Context()
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ReactionStore.PermanentDeleteBatch")
|
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ReactionStore.PermanentDeleteBatch")
|
||||||
|
|||||||
@@ -7315,6 +7315,48 @@ func (s *RetryLayerReactionStore) GetForPostSince(postId string, since int64, ex
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *RetryLayerReactionStore) GetTopForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopReactionList, error) {
|
||||||
|
|
||||||
|
tries := 0
|
||||||
|
for {
|
||||||
|
result, err := s.ReactionStore.GetTopForTeamSince(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 *RetryLayerReactionStore) GetTopForUserSince(userID string, teamID string, since int64, offset int, limit int) (*model.TopReactionList, error) {
|
||||||
|
|
||||||
|
tries := 0
|
||||||
|
for {
|
||||||
|
result, err := s.ReactionStore.GetTopForUserSince(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 *RetryLayerReactionStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, error) {
|
func (s *RetryLayerReactionStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, error) {
|
||||||
|
|
||||||
tries := 0
|
tries := 0
|
||||||
|
|||||||
@@ -228,6 +228,121 @@ func (s *SqlReactionStore) PermanentDeleteBatch(endTime int64, limit int64) (int
|
|||||||
return rowsAffected, nil
|
return rowsAffected, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTopForTeamSince returns the instance counts of the following Reactions sets:
|
||||||
|
// 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
|
||||||
|
|
||||||
|
query := `
|
||||||
|
SELECT
|
||||||
|
EmojiName,
|
||||||
|
sum(EmojiCount) AS Count
|
||||||
|
FROM ((
|
||||||
|
SELECT
|
||||||
|
EmojiName,
|
||||||
|
count(EmojiName) AS EmojiCount
|
||||||
|
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 = ?
|
||||||
|
AND Channels.Type = 'P'
|
||||||
|
AND Channels.TeamId = ?
|
||||||
|
AND Reactions.CreateAt > ?
|
||||||
|
GROUP BY
|
||||||
|
Reactions.EmojiName)
|
||||||
|
UNION ALL (
|
||||||
|
SELECT
|
||||||
|
EmojiName,
|
||||||
|
count(EmojiName) AS EmojiCount
|
||||||
|
FROM
|
||||||
|
Reactions
|
||||||
|
INNER JOIN Posts ON Reactions.PostId = Posts.Id
|
||||||
|
INNER JOIN Channels ON Posts.ChannelId = Channels.Id
|
||||||
|
WHERE
|
||||||
|
Reactions.DeleteAt = 0
|
||||||
|
AND Channels.Type = 'O'
|
||||||
|
AND Channels.TeamId = ?
|
||||||
|
AND Reactions.CreateAt > ?
|
||||||
|
GROUP BY
|
||||||
|
Reactions.EmojiName)) AS A
|
||||||
|
GROUP BY
|
||||||
|
EmojiName
|
||||||
|
ORDER BY
|
||||||
|
Count DESC,
|
||||||
|
EmojiName ASC
|
||||||
|
LIMIT ?
|
||||||
|
OFFSET ?`
|
||||||
|
|
||||||
|
if err := s.GetReplicaX().Select(&reactions, query, userID, teamID, since, teamID, since, limit+1, offset); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to get top Reactions")
|
||||||
|
}
|
||||||
|
|
||||||
|
return model.GetTopReactionListWithRankAndPagination(reactions, limit, offset), 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
|
||||||
|
var args []interface{}
|
||||||
|
var query string
|
||||||
|
|
||||||
|
if teamID != "" {
|
||||||
|
query = `
|
||||||
|
SELECT
|
||||||
|
EmojiName,
|
||||||
|
count(EmojiName) AS Count
|
||||||
|
FROM
|
||||||
|
Reactions
|
||||||
|
INNER JOIN Posts ON Reactions.PostId = Posts.Id
|
||||||
|
INNER JOIN Channels ON Posts.ChannelId = Channels.Id
|
||||||
|
WHERE
|
||||||
|
Reactions.DeleteAt = 0
|
||||||
|
AND Reactions.UserId = ?
|
||||||
|
AND (Channels.TeamId = ? OR Channels.Type = 'D' OR Channels.Type = 'G')
|
||||||
|
AND Reactions.CreateAt > ?
|
||||||
|
GROUP BY
|
||||||
|
EmojiName
|
||||||
|
ORDER BY
|
||||||
|
Count DESC,
|
||||||
|
EmojiName ASC
|
||||||
|
LIMIT ?
|
||||||
|
OFFSET ?`
|
||||||
|
args = []interface{}{userID, teamID, since, limit + 1, offset}
|
||||||
|
} else {
|
||||||
|
query = `
|
||||||
|
SELECT
|
||||||
|
EmojiName,
|
||||||
|
count(EmojiName) AS Count
|
||||||
|
FROM
|
||||||
|
Reactions
|
||||||
|
WHERE
|
||||||
|
Reactions.DeleteAt = 0
|
||||||
|
AND Reactions.UserId = ?
|
||||||
|
AND Reactions.CreateAt > ?
|
||||||
|
GROUP BY
|
||||||
|
Reactions.EmojiName
|
||||||
|
ORDER BY
|
||||||
|
Count DESC,
|
||||||
|
EmojiName ASC
|
||||||
|
LIMIT ?
|
||||||
|
OFFSET ?`
|
||||||
|
args = []interface{}{userID, since, limit + 1, offset}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.GetReplicaX().Select(&reactions, query, args...); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to get top Reactions")
|
||||||
|
}
|
||||||
|
|
||||||
|
return model.GetTopReactionListWithRankAndPagination(reactions, limit, offset), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SqlReactionStore) saveReactionAndUpdatePost(transaction *sqlxTxWrapper, reaction *model.Reaction) error {
|
func (s *SqlReactionStore) saveReactionAndUpdatePost(transaction *sqlxTxWrapper, reaction *model.Reaction) error {
|
||||||
reaction.DeleteAt = 0
|
reaction.DeleteAt = 0
|
||||||
|
|
||||||
|
|||||||
@@ -679,6 +679,8 @@ type ReactionStore interface {
|
|||||||
BulkGetForPosts(postIds []string) ([]*model.Reaction, error)
|
BulkGetForPosts(postIds []string) ([]*model.Reaction, error)
|
||||||
DeleteOrphanedRows(limit int) (int64, error)
|
DeleteOrphanedRows(limit int) (int64, error)
|
||||||
PermanentDeleteBatch(endTime int64, limit int64) (int64, error)
|
PermanentDeleteBatch(endTime int64, limit int64) (int64, error)
|
||||||
|
GetTopForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopReactionList, error)
|
||||||
|
GetTopForUserSince(userID string, teamID string, since int64, offset int, limit int) (*model.TopReactionList, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type JobStore interface {
|
type JobStore interface {
|
||||||
|
|||||||
@@ -141,6 +141,52 @@ func (_m *ReactionStore) GetForPostSince(postId string, since int64, excludeRemo
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTopForTeamSince provides a mock function with given fields: teamID, userID, since, offset, limit
|
||||||
|
func (_m *ReactionStore) GetTopForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopReactionList, error) {
|
||||||
|
ret := _m.Called(teamID, userID, since, offset, limit)
|
||||||
|
|
||||||
|
var r0 *model.TopReactionList
|
||||||
|
if rf, ok := ret.Get(0).(func(string, string, int64, int, int) *model.TopReactionList); ok {
|
||||||
|
r0 = rf(teamID, userID, since, offset, limit)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(*model.TopReactionList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTopForUserSince provides a mock function with given fields: userID, teamID, since, offset, limit
|
||||||
|
func (_m *ReactionStore) GetTopForUserSince(userID string, teamID string, since int64, offset int, limit int) (*model.TopReactionList, error) {
|
||||||
|
ret := _m.Called(userID, teamID, since, offset, limit)
|
||||||
|
|
||||||
|
var r0 *model.TopReactionList
|
||||||
|
if rf, ok := ret.Get(0).(func(string, string, int64, int, int) *model.TopReactionList); ok {
|
||||||
|
r0 = rf(userID, teamID, since, offset, limit)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(*model.TopReactionList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
// PermanentDeleteBatch provides a mock function with given fields: endTime, limit
|
// PermanentDeleteBatch provides a mock function with given fields: endTime, limit
|
||||||
func (_m *ReactionStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, error) {
|
func (_m *ReactionStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, error) {
|
||||||
ret := _m.Called(endTime, limit)
|
ret := _m.Called(endTime, limit)
|
||||||
|
|||||||
@@ -5833,6 +5833,38 @@ func (s *TimerLayerReactionStore) GetForPostSince(postId string, since int64, ex
|
|||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *TimerLayerReactionStore) GetTopForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopReactionList, error) {
|
||||||
|
start := timemodule.Now()
|
||||||
|
|
||||||
|
result, err := s.ReactionStore.GetTopForTeamSince(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("ReactionStore.GetTopForTeamSince", success, elapsed)
|
||||||
|
}
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TimerLayerReactionStore) GetTopForUserSince(userID string, teamID string, since int64, offset int, limit int) (*model.TopReactionList, error) {
|
||||||
|
start := timemodule.Now()
|
||||||
|
|
||||||
|
result, err := s.ReactionStore.GetTopForUserSince(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("ReactionStore.GetTopForUserSince", success, elapsed)
|
||||||
|
}
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
func (s *TimerLayerReactionStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, error) {
|
func (s *TimerLayerReactionStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, error) {
|
||||||
start := timemodule.Now()
|
start := timemodule.Now()
|
||||||
|
|
||||||
|
|||||||
@@ -374,6 +374,17 @@ func (c *Context) RequireTimestamp() *Context {
|
|||||||
return c
|
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 {
|
func (c *Context) RequireChannelId() *Context {
|
||||||
if c.Err != nil {
|
if c.Err != nil {
|
||||||
return c
|
return c
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ type Params struct {
|
|||||||
TokenId string
|
TokenId string
|
||||||
ThreadId string
|
ThreadId string
|
||||||
Timestamp int64
|
Timestamp int64
|
||||||
|
TimeRange int64
|
||||||
ChannelId string
|
ChannelId string
|
||||||
PostId string
|
PostId string
|
||||||
PolicyId string
|
PolicyId string
|
||||||
@@ -254,6 +255,12 @@ func ParamsFromRequest(r *http.Request) *Params {
|
|||||||
params.Timestamp = val
|
params.Timestamp = val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if val, err := model.GetStartUnixMilliForTimeRange(query.Get("time_range")); err != nil {
|
||||||
|
params.TimeRange = 0
|
||||||
|
} else {
|
||||||
|
params.TimeRange = val
|
||||||
|
}
|
||||||
|
|
||||||
if val, err := strconv.ParseBool(query.Get("permanent")); err == nil {
|
if val, err := strconv.ParseBool(query.Get("permanent")); err == nil {
|
||||||
params.Permanent = val
|
params.Permanent = val
|
||||||
}
|
}
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user