Merge pull request #20690 from plant99/MM-45118_my_top_dms
[MM-45118] Add insights endpoint for top dms
Этот коммит содержится в:
@@ -21,8 +21,11 @@ func (api *API) InitInsights() {
|
||||
api.BaseRoutes.InsightsForUser.Handle("/channels", api.APISessionRequired(minimumProfessionalLicense(rejectGuests(getTopChannelsForUserSince)))).Methods("GET")
|
||||
|
||||
// Threads
|
||||
api.BaseRoutes.InsightsForTeam.Handle("/threads", api.APISessionRequired(requireLicense(getTopThreadsForTeamSince))).Methods("GET")
|
||||
api.BaseRoutes.InsightsForUser.Handle("/threads", api.APISessionRequired(requireLicense(getTopThreadsForUserSince))).Methods("GET")
|
||||
api.BaseRoutes.InsightsForTeam.Handle("/threads", api.APISessionRequired(minimumProfessionalLicense(rejectGuests(getTopThreadsForTeamSince)))).Methods("GET")
|
||||
api.BaseRoutes.InsightsForUser.Handle("/threads", api.APISessionRequired(minimumProfessionalLicense(rejectGuests(getTopThreadsForUserSince)))).Methods("GET")
|
||||
|
||||
// user DMs
|
||||
api.BaseRoutes.InsightsForUser.Handle("/dms", api.APISessionRequired(minimumProfessionalLicense(rejectGuests(getTopDMsForUserSince)))).Methods("GET")
|
||||
|
||||
// New teammembers
|
||||
api.BaseRoutes.InsightsForTeam.Handle("/team_members", api.APISessionRequired(minimumProfessionalLicense(rejectGuests(getNewTeamMembersSince)))).Methods("GET")
|
||||
@@ -223,9 +226,9 @@ func getTopChannelsForUserSince(c *Context, w http.ResponseWriter, r *http.Reque
|
||||
return
|
||||
}
|
||||
|
||||
js, err := json.Marshal(topChannels)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("getTopChannelsForUserSince", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
js, jsonErr := json.Marshal(topChannels)
|
||||
if jsonErr != nil {
|
||||
c.Err = model.NewAppError("getTopChannelsForUserSince", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -245,21 +248,14 @@ func getTopThreadsForTeamSince(c *Context, w http.ResponseWriter, r *http.Reques
|
||||
return
|
||||
}
|
||||
|
||||
// license check
|
||||
lic := c.App.Srv().License()
|
||||
if lic.SkuShortName != model.LicenseShortSkuProfessional && lic.SkuShortName != model.LicenseShortSkuEnterprise {
|
||||
c.Err = model.NewAppError("", "api.insights.license_error", nil, "", http.StatusNotImplemented)
|
||||
// restrict users with no access to team
|
||||
user, err := c.App.GetUser(c.AppContext.Session().UserId)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
// restrict guests and users with no access to team
|
||||
user, appErr := c.App.GetUser(c.AppContext.Session().UserId)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), team.Id, model.PermissionViewTeam) || user.IsGuest() {
|
||||
if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), team.Id, model.PermissionViewTeam) {
|
||||
c.SetPermissionError(model.PermissionViewTeam)
|
||||
return
|
||||
}
|
||||
@@ -276,8 +272,8 @@ func getTopThreadsForTeamSince(c *Context, w http.ResponseWriter, r *http.Reques
|
||||
return
|
||||
}
|
||||
|
||||
js, err := json.Marshal(topThreads)
|
||||
if err != nil {
|
||||
js, jsonError := json.Marshal(topThreads)
|
||||
if jsonError != nil {
|
||||
c.Err = model.NewAppError("getTopThreadsForTeamSince", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
@@ -288,10 +284,10 @@ func getTopThreadsForTeamSince(c *Context, w http.ResponseWriter, r *http.Reques
|
||||
func getTopThreadsForUserSince(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.Params.TeamId = r.URL.Query().Get("team_id")
|
||||
|
||||
// restrict guests and users with no access to team
|
||||
user, appErr := c.App.GetUser(c.AppContext.Session().UserId)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
// restrict users with no access to team
|
||||
user, err := c.App.GetUser(c.AppContext.Session().UserId)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
// TeamId is an optional parameter
|
||||
@@ -307,14 +303,7 @@ func getTopThreadsForUserSince(c *Context, w http.ResponseWriter, r *http.Reques
|
||||
return
|
||||
}
|
||||
|
||||
// license check
|
||||
lic := c.App.Srv().License()
|
||||
if lic.SkuShortName != model.LicenseShortSkuProfessional && lic.SkuShortName != model.LicenseShortSkuEnterprise {
|
||||
c.Err = model.NewAppError("", "api.insights.license_error", nil, "", http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), team.Id, model.PermissionViewTeam) || user.IsGuest() {
|
||||
if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), team.Id, model.PermissionViewTeam) {
|
||||
c.SetPermissionError(model.PermissionViewTeam)
|
||||
return
|
||||
}
|
||||
@@ -332,9 +321,39 @@ func getTopThreadsForUserSince(c *Context, w http.ResponseWriter, r *http.Reques
|
||||
return
|
||||
}
|
||||
|
||||
js, err := json.Marshal(topThreads)
|
||||
js, jsonErr := json.Marshal(topThreads)
|
||||
if jsonErr != nil {
|
||||
c.Err = model.NewAppError("getTopThreadsForUserSince", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(js)
|
||||
}
|
||||
|
||||
// Top DMs
|
||||
func getTopDMsForUserSince(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
user, err := c.App.GetUser(c.AppContext.Session().UserId)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("getTopThreadsForUserSince", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
startTime := model.StartOfDayForTimeRange(c.Params.TimeRange, user.GetTimezoneLocation())
|
||||
|
||||
topDMs, err := c.App.GetTopDMsForUserSince(user.Id, &model.InsightsOpts{
|
||||
StartUnixMilli: startTime.UnixMilli(),
|
||||
Page: c.Params.Page,
|
||||
PerPage: c.Params.PerPage,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
js, jsonErr := json.Marshal(topDMs)
|
||||
if jsonErr != nil {
|
||||
c.Err = model.NewAppError("getTopDMsForUserSince", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -817,6 +817,126 @@ func TestGetTopThreadsForUserSince(t *testing.T) {
|
||||
require.Len(t, topUser2ThreadsAfterPrivateReplyDelete.Items, 0)
|
||||
}
|
||||
|
||||
func TestGetTopDMsForUserSince(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.ConfigStore.SetReadOnlyFF(false)
|
||||
defer th.ConfigStore.SetReadOnlyFF(true)
|
||||
th.App.UpdateConfig(func(c *model.Config) {
|
||||
*c.TeamSettings.EnableUserDeactivation = true
|
||||
})
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableBotAccountCreation = true })
|
||||
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional))
|
||||
|
||||
// basicuser1 - bu1, basicuser - bu
|
||||
// create dm channels for bu-bu, bu1-bu1, bu-bu1, bot-bu
|
||||
basicUser := th.BasicUser
|
||||
basicUser1 := th.BasicUser2
|
||||
|
||||
th.LoginBasic2()
|
||||
client := th.Client
|
||||
channelBu1Bu1, _, err := client.CreateDirectChannel(basicUser1.Id, basicUser1.Id)
|
||||
require.NoError(t, err)
|
||||
|
||||
th.LoginBasic()
|
||||
client = th.Client
|
||||
channelBuBu, _, err := client.CreateDirectChannel(basicUser.Id, basicUser.Id)
|
||||
require.NoError(t, err)
|
||||
channelBuBu1, _, err := client.CreateDirectChannel(basicUser.Id, basicUser1.Id)
|
||||
require.NoError(t, err)
|
||||
|
||||
// bot creation with permission
|
||||
th.AddPermissionToRole(model.PermissionCreateBot.Id, model.TeamUserRoleId)
|
||||
th.App.UpdateUserRoles(th.Context, th.BasicUser.Id, model.TeamUserRoleId+" "+model.SystemUserRoleId, false)
|
||||
bot := &model.Bot{
|
||||
Username: GenerateTestUsername(),
|
||||
DisplayName: "a bot",
|
||||
Description: "bot",
|
||||
}
|
||||
|
||||
createdBot, resp, err := th.Client.CreateBot(bot)
|
||||
require.NoError(t, err)
|
||||
CheckCreatedStatus(t, resp)
|
||||
defer th.App.PermanentDeleteBot(createdBot.UserId)
|
||||
channelBuBot, _, err := client.CreateDirectChannel(basicUser.Id, createdBot.UserId)
|
||||
require.NoError(t, err)
|
||||
|
||||
// create 2 posts in channelBu, 1 in channelBu1, 3 in channelBu12
|
||||
postsGenConfig := []map[string]interface{}{
|
||||
{
|
||||
"chId": channelBuBu.Id,
|
||||
"postCount": 2,
|
||||
},
|
||||
{
|
||||
"chId": channelBu1Bu1.Id,
|
||||
"postCount": 1,
|
||||
},
|
||||
{
|
||||
"chId": channelBuBu1.Id,
|
||||
"postCount": 3,
|
||||
},
|
||||
{
|
||||
"chId": channelBuBot.Id,
|
||||
"postCount": 3,
|
||||
},
|
||||
}
|
||||
|
||||
for _, postGen := range postsGenConfig {
|
||||
postCount := postGen["postCount"].(int)
|
||||
for i := 0; i < postCount; i++ {
|
||||
if postGen["chId"] == channelBu1Bu1.Id {
|
||||
th.LoginBasic2()
|
||||
client = th.Client
|
||||
userId := basicUser1.Id
|
||||
post := &model.Post{UserId: userId, ChannelId: postGen["chId"].(string), Message: "zz" + model.NewId() + "a"}
|
||||
_, _, err = client.CreatePost(post)
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
th.LoginBasic()
|
||||
client = th.Client
|
||||
userId := basicUser.Id
|
||||
post := &model.Post{UserId: userId, ChannelId: postGen["chId"].(string), Message: "zz" + model.NewId() + "a"}
|
||||
_, _, err = client.CreatePost(post)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get top dms for bu
|
||||
t.Run("get top dms for basic user 1", func(t *testing.T) {
|
||||
th.LoginBasic()
|
||||
client = th.Client
|
||||
topDMs, _, topDmsErr := client.GetTopDMsForUserSince("today", 0, 100)
|
||||
require.NoError(t, topDmsErr)
|
||||
require.Len(t, topDMs.Items, 1)
|
||||
require.Equal(t, topDMs.Items[0].MessageCount, int64(3))
|
||||
require.Equal(t, topDMs.Items[0].SecondParticipant.Id, basicUser1.Id)
|
||||
})
|
||||
|
||||
// get top dms for bu1
|
||||
t.Run("get top dms for basic user 2", func(t *testing.T) {
|
||||
th.LoginBasic2()
|
||||
client = th.Client
|
||||
topDMs, _, topDmsErr := client.GetTopDMsForUserSince("today", 0, 100)
|
||||
require.NoError(t, topDmsErr)
|
||||
require.Len(t, topDMs.Items, 1)
|
||||
require.Equal(t, topDMs.Items[0].MessageCount, int64(3))
|
||||
})
|
||||
// deactivate basicuser1
|
||||
_, err = th.Client.DeleteUser(basicUser1.Id)
|
||||
require.NoError(t, err)
|
||||
// deactivated users DMs should show in topDMs
|
||||
t.Run("get top dms for basic user 1", func(t *testing.T) {
|
||||
th.LoginBasic()
|
||||
client = th.Client
|
||||
topDMs, _, topDmsErr := client.GetTopDMsForUserSince("today", 0, 100)
|
||||
require.NoError(t, topDmsErr)
|
||||
require.Len(t, topDMs.Items, 1)
|
||||
require.Equal(t, topDMs.Items[0].MessageCount, int64(3))
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewTeamMembersSince(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
@@ -793,6 +793,7 @@ type AppIface interface {
|
||||
GetTokenById(token string) (*model.Token, *model.AppError)
|
||||
GetTopChannelsForTeamSince(c request.CTX, teamID, userID string, opts *model.InsightsOpts) (*model.TopChannelList, *model.AppError)
|
||||
GetTopChannelsForUserSince(c request.CTX, userID, teamID string, opts *model.InsightsOpts) (*model.TopChannelList, *model.AppError)
|
||||
GetTopDMsForUserSince(userID string, opts *model.InsightsOpts) (*model.TopDMList, *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)
|
||||
GetTopThreadsForTeamSince(c request.CTX, teamID, userID string, opts *model.InsightsOpts) (*model.TopThreadList, *model.AppError)
|
||||
|
||||
@@ -9955,6 +9955,28 @@ func (a *OpenTracingAppLayer) GetTopChannelsForUserSince(c request.CTX, userID s
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetTopDMsForUserSince(userID string, opts *model.InsightsOpts) (*model.TopDMList, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetTopDMsForUserSince")
|
||||
|
||||
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.GetTopDMsForUserSince(userID, 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")
|
||||
|
||||
11
app/post.go
11
app/post.go
@@ -1939,6 +1939,17 @@ func (a *App) GetTopThreadsForUserSince(c request.CTX, teamID, userID string, op
|
||||
return topThreadsWithEmbedAndImage, nil
|
||||
}
|
||||
|
||||
func (a *App) GetTopDMsForUserSince(userID string, opts *model.InsightsOpts) (*model.TopDMList, *model.AppError) {
|
||||
if !a.Config().FeatureFlags.InsightsEnabled {
|
||||
return nil, model.NewAppError("GetTopDMsForUserSince", "app.insights.feature_disabled", nil, "", http.StatusNotImplemented)
|
||||
}
|
||||
topDMs, err := a.Srv().Store.Post().GetTopDMsForUserSince(userID, opts.StartUnixMilli, opts.Page*opts.PerPage, opts.PerPage)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("GetTopDMsForUserSince", "app.post.get_top_dms_for_user_since.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
return topDMs, nil
|
||||
}
|
||||
|
||||
func (a *App) SetPostReminder(postID, userID string, targetTime int64) *model.AppError {
|
||||
// Store the reminder in the DB
|
||||
reminder := &model.PostReminder{
|
||||
|
||||
100
app/post_test.go
100
app/post_test.go
@@ -3062,3 +3062,103 @@ func TestGetTopThreadsForUserSince(t *testing.T) {
|
||||
require.Nil(t, appErr)
|
||||
require.Len(t, topUser2ThreadsAfterPrivateReplyDelete.Items, 0)
|
||||
}
|
||||
|
||||
func TestGetTopDMsForUserSince(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true })
|
||||
|
||||
// users
|
||||
user := th.CreateUser()
|
||||
u1 := th.CreateUser()
|
||||
u2 := th.CreateUser()
|
||||
u3 := th.CreateUser()
|
||||
u4 := th.CreateUser()
|
||||
// user direct messages
|
||||
chUser1, nErr := th.App.createDirectChannel(th.Context, u1.Id, user.Id)
|
||||
fmt.Println(chUser1, nErr)
|
||||
require.Nil(t, nErr)
|
||||
chUser2, nErr := th.App.createDirectChannel(th.Context, u2.Id, user.Id)
|
||||
require.Nil(t, nErr)
|
||||
chUser3, nErr := th.App.createDirectChannel(th.Context, u3.Id, user.Id)
|
||||
require.Nil(t, nErr)
|
||||
// other user direct message
|
||||
chUser3User4, nErr := th.App.createDirectChannel(th.Context, u3.Id, u4.Id)
|
||||
require.Nil(t, nErr)
|
||||
|
||||
// sample post data
|
||||
// for u1
|
||||
_, err := th.App.CreatePostAsUser(th.Context, &model.Post{
|
||||
ChannelId: chUser1.Id,
|
||||
UserId: u1.Id,
|
||||
}, "", false)
|
||||
require.Nil(t, err)
|
||||
_, err = th.App.CreatePostAsUser(th.Context, &model.Post{
|
||||
ChannelId: chUser1.Id,
|
||||
UserId: user.Id,
|
||||
}, "", false)
|
||||
require.Nil(t, err)
|
||||
// for u2: 1 post
|
||||
_, err = th.App.CreatePostAsUser(th.Context, &model.Post{
|
||||
ChannelId: chUser2.Id,
|
||||
UserId: u2.Id,
|
||||
}, "", false)
|
||||
require.Nil(t, err)
|
||||
// for user-u3: 3 posts
|
||||
for i := 0; i < 3; i++ {
|
||||
_, err = th.App.CreatePostAsUser(th.Context, &model.Post{
|
||||
ChannelId: chUser3.Id,
|
||||
UserId: user.Id,
|
||||
}, "", false)
|
||||
require.Nil(t, err)
|
||||
}
|
||||
// for u4-u3: 4 posts
|
||||
_, err = th.App.CreatePostAsUser(th.Context, &model.Post{
|
||||
ChannelId: chUser3User4.Id,
|
||||
UserId: u3.Id,
|
||||
}, "", false)
|
||||
require.Nil(t, err)
|
||||
_, err = th.App.CreatePostAsUser(th.Context, &model.Post{
|
||||
ChannelId: chUser3User4.Id,
|
||||
UserId: u4.Id,
|
||||
}, "", false)
|
||||
require.Nil(t, err)
|
||||
_, err = th.App.CreatePostAsUser(th.Context, &model.Post{
|
||||
ChannelId: chUser3User4.Id,
|
||||
UserId: u3.Id,
|
||||
}, "", false)
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err = th.App.CreatePostAsUser(th.Context, &model.Post{
|
||||
ChannelId: chUser3User4.Id,
|
||||
UserId: u4.Id,
|
||||
}, "", false)
|
||||
require.Nil(t, err)
|
||||
|
||||
t.Run("should return topDMs when userid is specified ", func(t *testing.T) {
|
||||
topDMs, err := th.App.GetTopDMsForUserSince(user.Id, &model.InsightsOpts{StartUnixMilli: 100, Page: 0, PerPage: 100})
|
||||
require.Nil(t, err)
|
||||
// len of topDMs.Items should be 3
|
||||
require.Len(t, topDMs.Items, 3)
|
||||
// check order, magnitude of items
|
||||
// fmt.Println(topDMs.Items[0].MessageCount, topDMs.Items[1].MessageCount, topDMs.Items[2].MessageCount)
|
||||
require.Equal(t, topDMs.Items[0].SecondParticipant.Id, u3.Id)
|
||||
require.Equal(t, topDMs.Items[0].MessageCount, int64(3))
|
||||
require.Equal(t, topDMs.Items[1].SecondParticipant.Id, u1.Id)
|
||||
require.Equal(t, topDMs.Items[1].MessageCount, int64(2))
|
||||
require.Equal(t, topDMs.Items[2].SecondParticipant.Id, u2.Id)
|
||||
require.Equal(t, topDMs.Items[2].MessageCount, int64(1))
|
||||
// this also ensures that u3-u4 conversation doesn't show up in others' top DMs.
|
||||
})
|
||||
t.Run("topDMs should only consider user's DM channels ", func(t *testing.T) {
|
||||
// u4 only takes part in one conversation
|
||||
topDMs, err := th.App.GetTopDMsForUserSince(u4.Id, &model.InsightsOpts{StartUnixMilli: 100, Page: 0, PerPage: 100})
|
||||
require.Nil(t, err)
|
||||
// len of topDMs.Items should be 3
|
||||
require.Len(t, topDMs.Items, 1)
|
||||
// check order, magnitude of items
|
||||
require.Equal(t, topDMs.Items[0].SecondParticipant.Id, u3.Id)
|
||||
require.Equal(t, topDMs.Items[0].MessageCount, int64(4))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1913,10 +1913,6 @@
|
||||
"id": "api.insights.feature_disabled",
|
||||
"translation": "Insights is behind a feature flag which is not enabled."
|
||||
},
|
||||
{
|
||||
"id": "api.insights.license_error",
|
||||
"translation": "Your license doesn't support Insights feature."
|
||||
},
|
||||
{
|
||||
"id": "api.invalid_channel",
|
||||
"translation": "Channel listed in the request doesn't belong to the user"
|
||||
@@ -5859,6 +5855,10 @@
|
||||
"id": "app.post.get_root_posts.app_error",
|
||||
"translation": "Unable to get the posts for the channel."
|
||||
},
|
||||
{
|
||||
"id": "app.post.get_top_dms_for_user_since.app_error",
|
||||
"translation": "Unable to get top DMs for user."
|
||||
},
|
||||
{
|
||||
"id": "app.post.get_top_threads_for_team_since.app_error",
|
||||
"translation": "Unable to get top threads for team."
|
||||
|
||||
@@ -6698,6 +6698,21 @@ func (c *Client4) GetTopReactionsForUserSince(teamId string, timeRange string, p
|
||||
return topReactions, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) GetTopDMsForUserSince(timeRange string, page int, perPage int) (*TopDMList, *Response, error) {
|
||||
query := fmt.Sprintf("?time_range=%v&page=%v&per_page=%v", timeRange, page, perPage)
|
||||
|
||||
r, err := c.DoAPIGet(c.usersRoute()+"/me/top/dms"+query, "")
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
var topDMs *TopDMList
|
||||
if jsonErr := json.NewDecoder(r.Body).Decode(&topDMs); jsonErr != nil {
|
||||
return nil, nil, NewAppError("GetTopReactionsForUserSince", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
return topDMs, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
// Timezone Section
|
||||
|
||||
// GetSupportedTimezone returns a page of supported timezones on the system.
|
||||
|
||||
@@ -90,6 +90,10 @@ type InsightUserInformation struct {
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
type TopDMInsightUserInformation struct {
|
||||
InsightUserInformation
|
||||
Position string `json:"position"`
|
||||
}
|
||||
type NewTeamMembersList struct {
|
||||
InsightsListData
|
||||
Items []*NewTeamMember `json:"items"`
|
||||
@@ -113,6 +117,25 @@ type DurationPostCount struct {
|
||||
PostCount int `db:"postcount"`
|
||||
}
|
||||
|
||||
// Top DMs
|
||||
type TopDM struct {
|
||||
MessageCount int64 `json:"post_count"`
|
||||
OutgoingMessageCount int64 `json:"outgoing_message_count"`
|
||||
Participants string `json:"-"`
|
||||
ChannelId string `json:"-"`
|
||||
SecondParticipant *TopDMInsightUserInformation `json:"second_participant"`
|
||||
}
|
||||
|
||||
type OutgoingMessageQueryResult struct {
|
||||
ChannelId string
|
||||
MessageCount int
|
||||
}
|
||||
|
||||
type TopDMList struct {
|
||||
InsightsListData
|
||||
Items []*TopDM `json:"items"`
|
||||
}
|
||||
|
||||
func TimeRangeToNumberDays(timeRange string) int {
|
||||
var n int
|
||||
switch timeRange {
|
||||
@@ -263,6 +286,20 @@ func GetTopThreadListWithPagination(threads []*TopThread, limit int) *TopThreadL
|
||||
return &TopThreadList{InsightsListData: InsightsListData{HasNext: hasNext}, Items: threads}
|
||||
}
|
||||
|
||||
// GetTopDMListWithPagination adds a rank to each item in the given list of TopDM and checks if there is
|
||||
// another page that can be fetched based on the given limit and offset. The given list of TopDM is assumed to be
|
||||
// sorted by MessageCount(score). Returns a TopDMList.
|
||||
func GetTopDMListWithPagination(dms []*TopDM, limit int) *TopDMList {
|
||||
// Add pagination support
|
||||
var hasNext bool
|
||||
if (limit != 0) && (len(dms) == limit+1) {
|
||||
hasNext = true
|
||||
dms = dms[:len(dms)-1]
|
||||
}
|
||||
|
||||
return &TopDMList{InsightsListData: InsightsListData{HasNext: hasNext}, Items: dms}
|
||||
}
|
||||
|
||||
func GetNewTeamMembersListWithPagination(teamMembers []*NewTeamMember, limit int) *NewTeamMembersList {
|
||||
var hasNext bool
|
||||
if (limit != 0) && (len(teamMembers) == limit+1) {
|
||||
|
||||
@@ -120,3 +120,41 @@ func TestGetTopThreadListWithPagination(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTopDMsListWithPagination(t *testing.T) {
|
||||
dms := []*TopDM{
|
||||
{SecondParticipant: &TopDMInsightUserInformation{InsightUserInformation: InsightUserInformation{Id: NewId()}}, MessageCount: 100},
|
||||
{SecondParticipant: &TopDMInsightUserInformation{InsightUserInformation: InsightUserInformation{Id: NewId()}}, MessageCount: 80},
|
||||
{SecondParticipant: &TopDMInsightUserInformation{InsightUserInformation: InsightUserInformation{Id: NewId()}}, MessageCount: 90},
|
||||
{SecondParticipant: &TopDMInsightUserInformation{InsightUserInformation: InsightUserInformation{Id: NewId()}}, MessageCount: 76},
|
||||
{SecondParticipant: &TopDMInsightUserInformation{InsightUserInformation: InsightUserInformation{Id: NewId()}}, MessageCount: 43},
|
||||
{SecondParticipant: &TopDMInsightUserInformation{InsightUserInformation: InsightUserInformation{Id: NewId()}}, MessageCount: 2},
|
||||
{SecondParticipant: &TopDMInsightUserInformation{InsightUserInformation: InsightUserInformation{Id: NewId()}}, MessageCount: 1},
|
||||
}
|
||||
hasNextTT := []struct {
|
||||
Description string
|
||||
Limit int
|
||||
Offset int
|
||||
Expected *TopDMList
|
||||
}{
|
||||
{
|
||||
Description: "has one page",
|
||||
Limit: len(dms),
|
||||
Offset: 0,
|
||||
Expected: &TopDMList{InsightsListData: InsightsListData{HasNext: false}, Items: dms},
|
||||
},
|
||||
{
|
||||
Description: "has more than one page",
|
||||
Limit: len(dms) - 1,
|
||||
Offset: 0,
|
||||
Expected: &TopDMList{InsightsListData: InsightsListData{HasNext: true}, Items: dms},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range hasNextTT {
|
||||
t.Run(test.Description, func(t *testing.T) {
|
||||
actual := GetTopDMListWithPagination(dms, test.Limit)
|
||||
assert.Equal(t, test.Expected.HasNext, actual.HasNext)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6043,6 +6043,24 @@ func (s *OpenTracingLayerPostStore) GetSingle(id string, inclDeleted bool) (*mod
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerPostStore) GetTopDMsForUserSince(userID string, since int64, offset int, limit int) (*model.TopDMList, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.GetTopDMsForUserSince")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.PostStore.GetTopDMsForUserSince(userID, since, offset, limit)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerPostStore) HasAutoResponsePostByUserSince(options model.GetPostsSinceOptions, userId string) (bool, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.HasAutoResponsePostByUserSince")
|
||||
|
||||
@@ -6848,6 +6848,27 @@ func (s *RetryLayerPostStore) GetSingle(id string, inclDeleted bool) (*model.Pos
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerPostStore) GetTopDMsForUserSince(userID string, since int64, offset int, limit int) (*model.TopDMList, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.PostStore.GetTopDMsForUserSince(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 *RetryLayerPostStore) HasAutoResponsePostByUserSince(options model.GetPostsSinceOptions, userId string) (bool, error) {
|
||||
|
||||
tries := 0
|
||||
|
||||
@@ -2986,6 +2986,148 @@ func (s *SqlPostStore) updateThreadsFromPosts(transaction *sqlxTxWrapper, posts
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SqlPostStore) GetTopDMsForUserSince(userID string, since int64, offset int, limit int) (*model.TopDMList, error) {
|
||||
channelSelector := s.getQueryBuilder().Select("Id", "TotalMsgCount").From("Channels").Join("ChannelMembers as cm on cm.ChannelId = Channels.Id").
|
||||
Where(sq.And{
|
||||
sq.Expr("Channels.Type = 'D'"),
|
||||
sq.Eq{"cm.UserId": userID},
|
||||
})
|
||||
var aggregator string
|
||||
|
||||
if s.DriverName() == model.DatabaseDriverMysql {
|
||||
aggregator = "group_concat(distinct cm.UserId) as Participants"
|
||||
} else {
|
||||
aggregator = "string_agg(distinct cm.UserId, ',') as Participants"
|
||||
}
|
||||
|
||||
topDMsBuilder := s.getQueryBuilder().Select("vch.TotalMsgCount as MessageCount", aggregator, "vch.Id as ChannelId").FromSelect(channelSelector, "vch").
|
||||
Join("ChannelMembers as cm on cm.ChannelId = vch.Id").
|
||||
Join("Posts as p on p.ChannelId = vch.Id").
|
||||
Where(sq.And{
|
||||
sq.Gt{
|
||||
"p.UpdateAt": since,
|
||||
},
|
||||
sq.Eq{
|
||||
"p.DeleteAt": 0,
|
||||
},
|
||||
}).GroupBy("vch.id", "vch.TotalMsgCount")
|
||||
|
||||
topDMsBuilder = topDMsBuilder.OrderBy("MessageCount DESC").Limit(uint64(limit + 1)).Offset(uint64(offset))
|
||||
|
||||
topDMs := make([]*model.TopDM, 0)
|
||||
sql, args, err := topDMsBuilder.ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GetTopDMsForUserSince_ToSql")
|
||||
}
|
||||
err = s.GetReplicaX().Select(&topDMs, sql, args...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to find top DMs for user-id: %s", userID)
|
||||
}
|
||||
|
||||
// fill SecondParticipant column
|
||||
topDMs, err = postProcessTopDMs(s, userID, topDMs, since)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return model.GetTopDMListWithPagination(topDMs, limit), nil
|
||||
}
|
||||
|
||||
func postProcessTopDMs(s *SqlPostStore, userID string, topDMs []*model.TopDM, since int64) ([]*model.TopDM, error) {
|
||||
var topDMsFiltered = []*model.TopDM{}
|
||||
var secondParticipantIds []string
|
||||
var channelIds []string
|
||||
|
||||
// identify second participant in a list of participants
|
||||
for _, topDM := range topDMs {
|
||||
participants := strings.Split(topDM.Participants, ",")
|
||||
var secondParticipantId string
|
||||
if len(participants) == 1 {
|
||||
// channel with self
|
||||
secondParticipantId = "-1"
|
||||
} else {
|
||||
if participants[0] == userID {
|
||||
secondParticipantId = participants[1]
|
||||
} else {
|
||||
secondParticipantId = participants[0]
|
||||
}
|
||||
}
|
||||
secondParticipantIds = append(secondParticipantIds, secondParticipantId)
|
||||
channelIds = append(channelIds, topDM.ChannelId)
|
||||
}
|
||||
|
||||
// get user profiles
|
||||
users, err := s.User().GetProfileByIds(context.Background(), secondParticipantIds, &store.UserGetByIdsOpts{}, true)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get second participants' information")
|
||||
}
|
||||
|
||||
// get outgoing message count for userId
|
||||
outgoingMessagesQuery := s.getQueryBuilder().Select("ch.Id as ChannelId, count(p.Id) as MessageCount").From("Channels as ch").
|
||||
Join("Posts as p on p.ChannelId=ch.Id").Where(
|
||||
sq.And{
|
||||
sq.Gt{
|
||||
"p.UpdateAt": since,
|
||||
},
|
||||
sq.Eq{
|
||||
"p.DeleteAt": 0,
|
||||
},
|
||||
sq.Eq{
|
||||
"ch.Id": channelIds,
|
||||
},
|
||||
sq.Eq{
|
||||
"p.UserId": userID,
|
||||
},
|
||||
}).GroupBy("ch.Id")
|
||||
|
||||
outgoingMessages := make([]*model.OutgoingMessageQueryResult, 0)
|
||||
sql, args, err := outgoingMessagesQuery.ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GetTopDMsForUserSince_outgoingMessagesQuery_ToSql")
|
||||
}
|
||||
err = s.GetReplicaX().Select(&outgoingMessages, sql, args...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to find top DMs for user-id: %s", userID)
|
||||
}
|
||||
|
||||
// create map of channelId -> MessageCount
|
||||
outgoingMessagesMap := make(map[string]int)
|
||||
for _, outgoingMessage := range outgoingMessages {
|
||||
outgoingMessagesMap[outgoingMessage.ChannelId] = outgoingMessage.MessageCount
|
||||
}
|
||||
|
||||
// create map of userId -> User
|
||||
usersMap := make(map[string]*model.User)
|
||||
for _, user := range users {
|
||||
usersMap[user.Id] = user
|
||||
}
|
||||
|
||||
for index, topDM := range topDMs {
|
||||
if secondParticipantIds[index] == "-1" {
|
||||
continue
|
||||
}
|
||||
user := usersMap[secondParticipantIds[index]]
|
||||
if user.IsBot {
|
||||
continue
|
||||
}
|
||||
topDM.SecondParticipant = &model.TopDMInsightUserInformation{
|
||||
InsightUserInformation: model.InsightUserInformation{
|
||||
Id: user.Id,
|
||||
LastPictureUpdate: user.LastPictureUpdate,
|
||||
FirstName: user.FirstName,
|
||||
LastName: user.LastName,
|
||||
Username: user.Username,
|
||||
NickName: user.Nickname,
|
||||
},
|
||||
Position: user.Position,
|
||||
}
|
||||
|
||||
topDM.OutgoingMessageCount = int64(outgoingMessagesMap[topDM.ChannelId])
|
||||
topDMsFiltered = append(topDMsFiltered, topDM)
|
||||
}
|
||||
|
||||
return topDMsFiltered, nil
|
||||
}
|
||||
|
||||
func (s *SqlPostStore) SetPostReminder(reminder *model.PostReminder) error {
|
||||
transaction, err := s.GetMasterX().Beginx()
|
||||
if err != nil {
|
||||
|
||||
@@ -393,6 +393,9 @@ type PostStore interface {
|
||||
GetPostReminderMetadata(postID string) (*PostReminderMetadata, error)
|
||||
// GetNthRecentPostTime returns the CreateAt time of the nth most recent post.
|
||||
GetNthRecentPostTime(n int64) (int64, error)
|
||||
|
||||
// Insights - top DMs
|
||||
GetTopDMsForUserSince(userID string, since int64, offset int, limit int) (*model.TopDMList, error)
|
||||
}
|
||||
|
||||
type UserStore interface {
|
||||
|
||||
@@ -748,6 +748,29 @@ func (_m *PostStore) GetSingle(id string, inclDeleted bool) (*model.Post, error)
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetTopDMsForUserSince provides a mock function with given fields: userID, since, offset, limit
|
||||
func (_m *PostStore) GetTopDMsForUserSince(userID string, since int64, offset int, limit int) (*model.TopDMList, error) {
|
||||
ret := _m.Called(userID, since, offset, limit)
|
||||
|
||||
var r0 *model.TopDMList
|
||||
if rf, ok := ret.Get(0).(func(string, int64, int, int) *model.TopDMList); ok {
|
||||
r0 = rf(userID, since, offset, limit)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.TopDMList)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string, int64, int, int) error); ok {
|
||||
r1 = rf(userID, since, offset, limit)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// HasAutoResponsePostByUserSince provides a mock function with given fields: options, userId
|
||||
func (_m *PostStore) HasAutoResponsePostByUserSince(options model.GetPostsSinceOptions, userId string) (bool, error) {
|
||||
ret := _m.Called(options, userId)
|
||||
|
||||
@@ -62,6 +62,7 @@ func TestPostStore(t *testing.T, ss store.Store, s SqlStore) {
|
||||
t.Run("GetPostReminders", func(t *testing.T) { testGetPostReminders(t, ss, s) })
|
||||
t.Run("GetPostReminderMetadata", func(t *testing.T) { testGetPostReminderMetadata(t, ss, s) })
|
||||
t.Run("GetNthRecentPostTime", func(t *testing.T) { testGetNthRecentPostTime(t, ss) })
|
||||
t.Run("GetTopDMsForUserSince", func(t *testing.T) { testGetTopDMsForUserSince(t, ss, s) })
|
||||
}
|
||||
|
||||
func testPostStoreSave(t *testing.T, ss store.Store) {
|
||||
@@ -4058,3 +4059,124 @@ func testGetNthRecentPostTime(t *testing.T, ss store.Store) {
|
||||
assert.Error(t, err)
|
||||
assert.IsType(t, &store.ErrNotFound{}, err)
|
||||
}
|
||||
|
||||
func testGetTopDMsForUserSince(t *testing.T, ss store.Store, s SqlStore) {
|
||||
// users
|
||||
user := model.User{Email: MakeEmail(), Username: model.NewId()}
|
||||
u1 := model.User{Email: MakeEmail(), Username: model.NewId()}
|
||||
u2 := model.User{Email: MakeEmail(), Username: model.NewId()}
|
||||
u3 := model.User{Email: MakeEmail(), Username: model.NewId()}
|
||||
u4 := model.User{Email: MakeEmail(), Username: model.NewId()}
|
||||
_, err := ss.User().Save(&user)
|
||||
require.NoError(t, err)
|
||||
_, err = ss.User().Save(&u1)
|
||||
require.NoError(t, err)
|
||||
_, err = ss.User().Save(&u2)
|
||||
require.NoError(t, err)
|
||||
_, err = ss.User().Save(&u3)
|
||||
require.NoError(t, err)
|
||||
_, err = ss.User().Save(&u4)
|
||||
require.NoError(t, err)
|
||||
// user direct messages
|
||||
chUser1, nErr := ss.Channel().CreateDirectChannel(&u1, &user)
|
||||
require.NoError(t, nErr)
|
||||
chUser2, nErr := ss.Channel().CreateDirectChannel(&u2, &user)
|
||||
require.NoError(t, nErr)
|
||||
chUser3, nErr := ss.Channel().CreateDirectChannel(&u3, &user)
|
||||
require.NoError(t, nErr)
|
||||
// other user direct message
|
||||
chUser3User4, nErr := ss.Channel().CreateDirectChannel(&u3, &u4)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
// sample post data
|
||||
// for u1
|
||||
_, err = ss.Post().Save(&model.Post{
|
||||
ChannelId: chUser1.Id,
|
||||
UserId: u1.Id,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = ss.Post().Save(&model.Post{
|
||||
ChannelId: chUser1.Id,
|
||||
UserId: user.Id,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// for u2: 1 post
|
||||
postToDelete, err := ss.Post().Save(&model.Post{
|
||||
ChannelId: chUser2.Id,
|
||||
UserId: u2.Id,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// for user-u3: 3 posts
|
||||
for i := 0; i < 3; i++ {
|
||||
_, err = ss.Post().Save(&model.Post{
|
||||
ChannelId: chUser3.Id,
|
||||
UserId: user.Id,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
// for u4-u3: 4 posts
|
||||
_, err = ss.Post().Save(&model.Post{
|
||||
ChannelId: chUser3User4.Id,
|
||||
UserId: u3.Id,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = ss.Post().Save(&model.Post{
|
||||
ChannelId: chUser3User4.Id,
|
||||
UserId: u4.Id,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = ss.Post().Save(&model.Post{
|
||||
ChannelId: chUser3User4.Id,
|
||||
UserId: u3.Id,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = ss.Post().Save(&model.Post{
|
||||
ChannelId: chUser3User4.Id,
|
||||
UserId: u4.Id,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
t.Run("should return topDMs when userid is specified ", func(t *testing.T) {
|
||||
topDMs, storeErr := ss.Post().GetTopDMsForUserSince(user.Id, 100, 0, 100)
|
||||
require.NoError(t, storeErr)
|
||||
// len of topDMs.Items should be 3
|
||||
require.Len(t, topDMs.Items, 3)
|
||||
// check order, magnitude of items
|
||||
require.Equal(t, topDMs.Items[0].SecondParticipant.Id, u3.Id)
|
||||
require.Equal(t, topDMs.Items[0].MessageCount, int64(3))
|
||||
require.Equal(t, topDMs.Items[0].OutgoingMessageCount, int64(3))
|
||||
require.Equal(t, topDMs.Items[1].SecondParticipant.Id, u1.Id)
|
||||
require.Equal(t, topDMs.Items[1].MessageCount, int64(2))
|
||||
require.Equal(t, topDMs.Items[1].OutgoingMessageCount, int64(1))
|
||||
require.Equal(t, topDMs.Items[2].SecondParticipant.Id, u2.Id)
|
||||
require.Equal(t, topDMs.Items[2].MessageCount, int64(1))
|
||||
require.Equal(t, topDMs.Items[2].OutgoingMessageCount, int64(0))
|
||||
// this also ensures that u3-u4 conversation doesn't show up in others' top DMs.
|
||||
})
|
||||
t.Run("topDMs should only consider user's DM channels ", func(t *testing.T) {
|
||||
// u4 only takes part in one conversation
|
||||
topDMs, storeErr := ss.Post().GetTopDMsForUserSince(u4.Id, 100, 0, 100)
|
||||
require.NoError(t, storeErr)
|
||||
// len of topDMs.Items should be 3
|
||||
require.Len(t, topDMs.Items, 1)
|
||||
// check order, magnitude of items
|
||||
require.Equal(t, topDMs.Items[0].SecondParticipant.Id, u3.Id)
|
||||
require.Equal(t, topDMs.Items[0].MessageCount, int64(4))
|
||||
})
|
||||
t.Run("topDMs will not consider self dms", func(t *testing.T) {
|
||||
chUser, nErr := ss.Channel().CreateDirectChannel(&user, &user)
|
||||
require.NoError(t, nErr)
|
||||
_, err = ss.Post().Save(&model.Post{
|
||||
ChannelId: chUser.Id,
|
||||
UserId: user.Id,
|
||||
})
|
||||
// delete u2 post
|
||||
err := ss.Post().Delete(postToDelete.Id, 200, user.Id)
|
||||
require.NoError(t, err)
|
||||
// u4 only takes part in one conversation
|
||||
topDMs, err := ss.Post().GetTopDMsForUserSince(user.Id, 100, 0, 100)
|
||||
require.NoError(t, err)
|
||||
// len of topDMs.Items should be 3
|
||||
require.Len(t, topDMs.Items, 2)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5466,6 +5466,22 @@ func (s *TimerLayerPostStore) GetSingle(id string, inclDeleted bool) (*model.Pos
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerPostStore) GetTopDMsForUserSince(userID string, since int64, offset int, limit int) (*model.TopDMList, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.PostStore.GetTopDMsForUserSince(userID, since, offset, limit)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("PostStore.GetTopDMsForUserSince", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerPostStore) HasAutoResponsePostByUserSince(options model.GetPostsSinceOptions, userId string) (bool, error) {
|
||||
start := time.Now()
|
||||
|
||||
|
||||
Ссылка в новой задаче
Block a user