MM-43956: Adds post counts by duration. (#20131)
* MM-43956: Adds post counts by day. * MM-43956: Test data cleanup. Switch from Unix to UnixMilli. * MM-43956: Changes from selecting date data types to strings in SQL. * MM-43956: Fixes date format key. * MM-43956: Adds missing user id scope for 'my' top channels graph. * MM-43956: Adds the ability to group post counts by hour. * MM-43956: Require enterprise or professional license. Reject guests. * MM-43956: Adds license for tests. * MM-43956: Renames function. * MM-43956: Omits future hours from post counts by hour. * MM-43956: Adjust API response grouping to users timezone. * MM-43956: Adds translation. * MM-43956: Adds user's timezone to the data tier for the grouping by day and hour. * MM-43956: Fixes layers. * MM-43956: Lint fix. * MM-43956: Fix store layers. * MM-43956: Switches to default name for time package; changes parameter names to avoid naming conflict. * MM-43956: Updates mocks. Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
c03eb778c8
Коммит
182ae1234a
@@ -262,6 +262,12 @@ type AppIface interface {
|
||||
// PopulateWebConnConfig checks if the connection id already exists in the hub,
|
||||
// and if so, accordingly populates the other fields of the webconn.
|
||||
PopulateWebConnConfig(s *model.Session, cfg *WebConnConfig, seqVal string) (*WebConnConfig, error)
|
||||
// PostCountsByDuration returns the post counts for the given channels, grouped by day, starting at the given time.
|
||||
// Unless one is specifically itending to omit results from part of the calendar day, it will typically makes the most sense to
|
||||
// use a sinceUnixMillis parameter value as returned by model.GetStartOfDayMillis.
|
||||
//
|
||||
// WARNING: PostCountsByDuration PERFORMS NO AUTHORIZATION CHECKS ON THE GIVEN CHANNELS.
|
||||
PostCountsByDuration(channelIDs []string, sinceUnixMillis int64, userID *string, grouping model.PostCountGrouping, groupingLocation *time.Location) ([]*model.DurationPostCount, *model.AppError)
|
||||
// PromoteGuestToUser Convert user's roles and all his membership's roles from
|
||||
// guest roles to regular user roles.
|
||||
PromoteGuestToUser(c *request.Context, user *model.User, requestorId string) *model.AppError
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/app/request"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
@@ -3417,3 +3418,19 @@ func (a *App) GetTopChannelsForUserSince(userID, teamID string, opts *model.Insi
|
||||
}
|
||||
return topChannels, nil
|
||||
}
|
||||
|
||||
// PostCountsByDuration returns the post counts for the given channels, grouped by day, starting at the given time.
|
||||
// Unless one is specifically itending to omit results from part of the calendar day, it will typically makes the most sense to
|
||||
// use a sinceUnixMillis parameter value as returned by model.GetStartOfDayMillis.
|
||||
//
|
||||
// WARNING: PostCountsByDuration PERFORMS NO AUTHORIZATION CHECKS ON THE GIVEN CHANNELS.
|
||||
func (a *App) PostCountsByDuration(channelIDs []string, sinceUnixMillis int64, userID *string, grouping model.PostCountGrouping, groupingLocation *time.Location) ([]*model.DurationPostCount, *model.AppError) {
|
||||
if !a.Config().FeatureFlags.InsightsEnabled {
|
||||
return nil, model.NewAppError("PostCountsByDuration", "api.insights.feature_disabled", nil, "", http.StatusNotImplemented)
|
||||
}
|
||||
postCountByDay, err := a.Srv().Store.Channel().PostCountsByDuration(channelIDs, sinceUnixMillis, userID, grouping, groupingLocation)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("PostCountsByDuration", "app.channel.get_post_count_by_day.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
return postCountByDay, nil
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
@@ -2418,10 +2419,10 @@ func TestGetTopChannelsForTeamSince(t *testing.T) {
|
||||
{ID: channel5.Id, MessageCount: 2},
|
||||
}
|
||||
|
||||
timeRange, _ := model.GetStartUnixMilliForTimeRange(model.TimeRangeToday)
|
||||
timeRange := model.StartOfDayForTimeRange(model.TimeRangeToday, time.Now().Location())
|
||||
|
||||
t.Run("get-top-channels-for-team-since", func(t *testing.T) {
|
||||
topChannels, err := th.App.GetTopChannelsForTeamSince(th.BasicChannel.TeamId, th.BasicUser.Id, &model.InsightsOpts{StartUnixMilli: timeRange, Page: 0, PerPage: 5})
|
||||
topChannels, err := th.App.GetTopChannelsForTeamSince(th.BasicChannel.TeamId, th.BasicUser.Id, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 0, PerPage: 5})
|
||||
require.Nil(t, err)
|
||||
|
||||
for i, channel := range topChannels.Items {
|
||||
@@ -2429,7 +2430,7 @@ func TestGetTopChannelsForTeamSince(t *testing.T) {
|
||||
assert.Equal(t, expectedTopChannels[i].MessageCount, channel.MessageCount)
|
||||
}
|
||||
|
||||
topChannels, err = th.App.GetTopChannelsForTeamSince(th.BasicChannel.TeamId, th.BasicUser.Id, &model.InsightsOpts{StartUnixMilli: timeRange, Page: 1, PerPage: 5})
|
||||
topChannels, err = th.App.GetTopChannelsForTeamSince(th.BasicChannel.TeamId, th.BasicUser.Id, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 1, PerPage: 5})
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, channel6.Id, topChannels.Items[0].ID)
|
||||
assert.Equal(t, int64(1), topChannels.Items[0].MessageCount)
|
||||
@@ -2476,10 +2477,10 @@ func TestGetTopChannelsForUserSince(t *testing.T) {
|
||||
{ID: channel5.Id, MessageCount: 2},
|
||||
}
|
||||
|
||||
timeRange, _ := model.GetStartUnixMilliForTimeRange(model.TimeRangeToday)
|
||||
timeRange := model.StartOfDayForTimeRange(model.TimeRangeToday, time.Now().Location())
|
||||
|
||||
t.Run("get-top-channels-for-user-since", func(t *testing.T) {
|
||||
topChannels, err := th.App.GetTopChannelsForUserSince(th.BasicUser.Id, "", &model.InsightsOpts{StartUnixMilli: timeRange, Page: 0, PerPage: 5})
|
||||
topChannels, err := th.App.GetTopChannelsForUserSince(th.BasicUser.Id, "", &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 0, PerPage: 5})
|
||||
require.Nil(t, err)
|
||||
|
||||
for i, channel := range topChannels.Items {
|
||||
@@ -2487,9 +2488,132 @@ func TestGetTopChannelsForUserSince(t *testing.T) {
|
||||
assert.Equal(t, expectedTopChannels[i].MessageCount, channel.MessageCount)
|
||||
}
|
||||
|
||||
topChannels, err = th.App.GetTopChannelsForUserSince(th.BasicUser.Id, th.BasicChannel.TeamId, &model.InsightsOpts{StartUnixMilli: timeRange, Page: 1, PerPage: 5})
|
||||
topChannels, err = th.App.GetTopChannelsForUserSince(th.BasicUser.Id, th.BasicChannel.TeamId, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 1, PerPage: 5})
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, channel6.Id, topChannels.Items[0].ID)
|
||||
assert.Equal(t, int64(1), topChannels.Items[0].MessageCount)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPostCountsByDuration(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.Server.configStore.SetReadOnlyFF(false)
|
||||
defer th.Server.configStore.SetReadOnlyFF(true)
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true })
|
||||
|
||||
channel2 := th.CreateChannel(th.BasicTeam)
|
||||
channel3 := th.CreatePrivateChannel(th.BasicTeam)
|
||||
channel4 := th.CreatePrivateChannel(th.BasicTeam)
|
||||
channel5 := th.CreateChannel(th.BasicTeam)
|
||||
channel6 := th.CreatePrivateChannel(th.BasicTeam)
|
||||
defer func() {
|
||||
th.App.PermanentDeleteChannel(channel2)
|
||||
th.App.PermanentDeleteChannel(channel3)
|
||||
th.App.PermanentDeleteChannel(channel4)
|
||||
th.App.PermanentDeleteChannel(channel5)
|
||||
th.App.PermanentDeleteChannel(channel6)
|
||||
}()
|
||||
th.AddUserToChannel(th.BasicUser, channel2)
|
||||
th.AddUserToChannel(th.BasicUser, channel3)
|
||||
th.AddUserToChannel(th.BasicUser, channel4)
|
||||
th.AddUserToChannel(th.BasicUser, channel5)
|
||||
th.AddUserToChannel(th.BasicUser, channel6)
|
||||
|
||||
channels := [6]*model.Channel{th.BasicChannel, channel2, channel3, channel4, channel5, channel6}
|
||||
channelIDs := []string{th.BasicChannel.Id, channel2.Id, channel3.Id, channel4.Id, channel5.Id, channel6.Id}
|
||||
|
||||
i := len(channels)
|
||||
for ci, channel := range channels {
|
||||
for j := i; j > 0; j-- {
|
||||
d1 := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).AddDate(0, 0, ci)
|
||||
d2 := time.Date(2009, time.November, 10, 9, 0, 0, 0, time.UTC).AddDate(0, 0, ci)
|
||||
_, err := th.App.CreatePost(th.Context, &model.Post{
|
||||
UserId: th.BasicUser.Id,
|
||||
ChannelId: channel.Id,
|
||||
CreateAt: d1.Unix() * 1000,
|
||||
}, channel, false, false)
|
||||
require.Nil(t, err)
|
||||
_, err = th.App.CreatePost(th.Context, &model.Post{
|
||||
UserId: th.BasicUser2.Id,
|
||||
ChannelId: channel.Id,
|
||||
CreateAt: d2.Unix() * 1000,
|
||||
}, channel, false, false)
|
||||
require.Nil(t, err)
|
||||
}
|
||||
i--
|
||||
}
|
||||
|
||||
expectedDayGrouping := map[string]map[string]int{
|
||||
"2009-11-10": {
|
||||
th.BasicChannel.Id: 6,
|
||||
},
|
||||
"2009-11-11": {
|
||||
channel2.Id: 5,
|
||||
},
|
||||
"2009-11-12": {
|
||||
channel3.Id: 4,
|
||||
},
|
||||
"2009-11-13": {
|
||||
channel4.Id: 3,
|
||||
},
|
||||
"2009-11-14": {
|
||||
channel5.Id: 2,
|
||||
},
|
||||
"2009-11-15": {
|
||||
channel6.Id: 1,
|
||||
},
|
||||
}
|
||||
|
||||
expectedHourGrouping := map[string]map[string]int{
|
||||
"2009-11-15T09": {
|
||||
channel6.Id: 1,
|
||||
},
|
||||
"2009-11-15T23": {
|
||||
channel6.Id: 1,
|
||||
},
|
||||
}
|
||||
|
||||
sinceUnixMillis := time.Date(2009, time.November, 9, 23, 0, 0, 0, time.UTC).UnixMilli()
|
||||
|
||||
t.Run("get-post-counts-by-day scoped by user, grouped by day", func(t *testing.T) {
|
||||
dailyPostCount, err := th.App.PostCountsByDuration(channelIDs, sinceUnixMillis, &th.BasicUser.Id, model.PostsByDay, time.Now().UTC().Location())
|
||||
require.Nil(t, err)
|
||||
require.GreaterOrEqual(t, len(dailyPostCount), 6)
|
||||
|
||||
for _, item := range dailyPostCount {
|
||||
if strings.HasPrefix(item.Duration, "2009") {
|
||||
expectedCount := expectedDayGrouping[item.Duration][item.ChannelID]
|
||||
assert.Equal(t, expectedCount, item.PostCount)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("get-post-counts-by-day all users, grouped by day", func(t *testing.T) {
|
||||
dailyPostCount, err := th.App.PostCountsByDuration(channelIDs, sinceUnixMillis, nil, model.PostsByDay, time.Now().UTC().Location())
|
||||
require.Nil(t, err)
|
||||
require.GreaterOrEqual(t, len(dailyPostCount), 6)
|
||||
|
||||
for _, item := range dailyPostCount {
|
||||
if strings.HasPrefix(item.Duration, "2009") {
|
||||
expectedCount := expectedDayGrouping[item.Duration][item.ChannelID]
|
||||
assert.Equal(t, expectedCount*2, item.PostCount)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("get-post-counts-by-day all users, grouped by hour", func(t *testing.T) {
|
||||
oneDaySince := time.Date(2009, time.November, 14, 23, 0, 0, 0, time.UTC).UnixMilli()
|
||||
dailyPostCount, err := th.App.PostCountsByDuration(channelIDs, oneDaySince, nil, model.PostsByHour, time.Now().UTC().Location())
|
||||
require.Nil(t, err)
|
||||
require.GreaterOrEqual(t, len(dailyPostCount), 1)
|
||||
|
||||
for _, item := range dailyPostCount {
|
||||
if strings.HasPrefix(item.Duration, "2009") {
|
||||
expectedCount := expectedHourGrouping[item.Duration][item.ChannelID]
|
||||
assert.Equal(t, expectedCount, item.PostCount)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -12654,6 +12654,28 @@ func (a *OpenTracingAppLayer) PostAddToChannelMessage(c *request.Context, user *
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) PostCountsByDuration(channelIDs []string, sinceUnixMillis int64, userID *string, grouping model.PostCountGrouping, groupingLocation *time.Location) ([]*model.DurationPostCount, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.PostCountsByDuration")
|
||||
|
||||
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.PostCountsByDuration(channelIDs, sinceUnixMillis, userID, grouping, groupingLocation)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) PostPatchWithProxyRemovedFromImageURLs(patch *model.PostPatch) *model.PostPatch {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.PostPatchWithProxyRemovedFromImageURLs")
|
||||
|
||||
@@ -231,10 +231,10 @@ func TestGetTopReactionsForTeamSince(t *testing.T) {
|
||||
expectedTopReactions[3] = &model.TopReaction{EmojiName: "sad", Count: int64(3)}
|
||||
expectedTopReactions[4] = &model.TopReaction{EmojiName: "happy", Count: int64(2)}
|
||||
|
||||
timeRange, _ := model.GetStartUnixMilliForTimeRange(model.TimeRangeToday)
|
||||
timeRange := model.StartOfDayForTimeRange(model.TimeRangeToday, time.Now().Location())
|
||||
|
||||
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})
|
||||
topReactions, err := th.App.GetTopReactionsForTeamSince(teamId, userId, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 0, PerPage: 5})
|
||||
require.Nil(t, err)
|
||||
reactions := topReactions.Items
|
||||
|
||||
@@ -242,7 +242,7 @@ func TestGetTopReactionsForTeamSince(t *testing.T) {
|
||||
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})
|
||||
topReactions, err = th.App.GetTopReactionsForTeamSince(teamId, userId, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 1, PerPage: 5})
|
||||
require.Nil(t, err)
|
||||
reactions = topReactions.Items
|
||||
|
||||
@@ -252,7 +252,7 @@ func TestGetTopReactionsForTeamSince(t *testing.T) {
|
||||
|
||||
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})
|
||||
_, err := th.App.GetTopReactionsForTeamSince(userId, teamId, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 0, PerPage: 5})
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
}
|
||||
@@ -402,10 +402,10 @@ func TestGetTopReactionsForUserSince(t *testing.T) {
|
||||
expectedTopReactions[3] = &model.TopReaction{EmojiName: "heart", Count: int64(3)}
|
||||
expectedTopReactions[4] = &model.TopReaction{EmojiName: "blush", Count: int64(2)}
|
||||
|
||||
timeRange, _ := model.GetStartUnixMilliForTimeRange(model.TimeRangeToday)
|
||||
timeRange := model.StartOfDayForTimeRange(model.TimeRangeToday, time.Now().Location())
|
||||
|
||||
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})
|
||||
topReactions, err := th.App.GetTopReactionsForUserSince(userId, teamId, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 0, PerPage: 5})
|
||||
require.Nil(t, err)
|
||||
reactions := topReactions.Items
|
||||
|
||||
@@ -414,7 +414,7 @@ func TestGetTopReactionsForUserSince(t *testing.T) {
|
||||
assert.Equal(t, expectedTopReactions[i].Count, reaction.Count)
|
||||
}
|
||||
|
||||
topReactions, err = th.App.GetTopReactionsForUserSince(userId, teamId, &model.InsightsOpts{StartUnixMilli: timeRange, Page: 1, PerPage: 5})
|
||||
topReactions, err = th.App.GetTopReactionsForUserSince(userId, teamId, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 1, PerPage: 5})
|
||||
require.Nil(t, err)
|
||||
reactions = topReactions.Items
|
||||
assert.Equal(t, "100", reactions[0].EmojiName)
|
||||
@@ -423,7 +423,7 @@ func TestGetTopReactionsForUserSince(t *testing.T) {
|
||||
|
||||
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})
|
||||
_, err := th.App.GetTopReactionsForUserSince(userId, teamId, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 0, PerPage: 5})
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user