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>
Этот коммит содержится в:
Martin Kraft
2022-06-13 16:22:34 -04:00
коммит произвёл GitHub
родитель c03eb778c8
Коммит 182ae1234a
29 изменённых файлов: 2055 добавлений и 1533 удалений

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

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