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 удалений

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

@@ -209,3 +209,24 @@ func requireLicense(f handlerFunc) handlerFunc {
f(c, w, r)
}
}
func minimumProfessionalLicense(f handlerFunc) handlerFunc {
return func(c *Context, w http.ResponseWriter, r *http.Request) {
lic := c.App.Srv().License()
if lic == nil || (lic.SkuShortName != model.LicenseShortSkuProfessional && lic.SkuShortName != model.LicenseShortSkuEnterprise) {
c.Err = model.NewAppError("", "api.license_error.professional_or_enterprise", nil, "", http.StatusNotImplemented)
return
}
f(c, w, r)
}
}
func rejectGuests(f handlerFunc) handlerFunc {
return func(c *Context, w http.ResponseWriter, r *http.Request) {
if c.AppContext.Session().Props[model.SessionPropIsGuest] == "true" {
c.Err = model.NewAppError("", "api.authorization_error.guest", nil, "", http.StatusNotImplemented)
return
}
f(c, w, r)
}
}

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

@@ -6,18 +6,20 @@ package api4
import (
"encoding/json"
"net/http"
"time"
"github.com/mattermost/mattermost-server/v6/app"
"github.com/mattermost/mattermost-server/v6/model"
)
func (api *API) InitInsights() {
// Reactions
api.BaseRoutes.InsightsForTeam.Handle("/reactions", api.APISessionRequired(getTopReactionsForTeamSince)).Methods("GET")
api.BaseRoutes.InsightsForUser.Handle("/reactions", api.APISessionRequired(getTopReactionsForUserSince)).Methods("GET")
api.BaseRoutes.InsightsForTeam.Handle("/reactions", api.APISessionRequired(minimumProfessionalLicense(rejectGuests(getTopReactionsForTeamSince)))).Methods("GET")
api.BaseRoutes.InsightsForUser.Handle("/reactions", api.APISessionRequired(minimumProfessionalLicense(rejectGuests(getTopReactionsForUserSince)))).Methods("GET")
// Channels
api.BaseRoutes.InsightsForTeam.Handle("/channels", api.APISessionRequired(getTopChannelsForTeamSince)).Methods("GET")
api.BaseRoutes.InsightsForUser.Handle("/channels", api.APISessionRequired(getTopChannelsForUserSince)).Methods("GET")
api.BaseRoutes.InsightsForTeam.Handle("/channels", api.APISessionRequired(minimumProfessionalLicense(rejectGuests(getTopChannelsForTeamSince)))).Methods("GET")
api.BaseRoutes.InsightsForUser.Handle("/channels", api.APISessionRequired(minimumProfessionalLicense(rejectGuests(getTopChannelsForUserSince)))).Methods("GET")
}
// Top Reactions
@@ -39,14 +41,16 @@ func getTopReactionsForTeamSince(c *Context, w http.ResponseWriter, r *http.Requ
return
}
startTime, err := model.GetStartUnixMilliForTimeRange(c.Params.TimeRange)
user, err := c.App.GetUser(c.AppContext.Session().UserId)
if err != nil {
c.Err = err
return
}
startTime := model.StartOfDayForTimeRange(c.Params.TimeRange, user.GetTimezoneLocation())
topReactionList, err := c.App.GetTopReactionsForTeamSince(c.Params.TeamId, c.AppContext.Session().UserId, &model.InsightsOpts{
StartUnixMilli: startTime,
StartUnixMilli: startTime.UnixMilli(),
Page: c.Params.Page,
PerPage: c.Params.PerPage,
})
@@ -86,14 +90,16 @@ func getTopReactionsForUserSince(c *Context, w http.ResponseWriter, r *http.Requ
}
}
startTime, err := model.GetStartUnixMilliForTimeRange(c.Params.TimeRange)
user, err := c.App.GetUser(c.AppContext.Session().UserId)
if err != nil {
c.Err = err
return
}
startTime := model.StartOfDayForTimeRange(c.Params.TimeRange, user.GetTimezoneLocation())
topReactionList, err := c.App.GetTopReactionsForUserSince(c.AppContext.Session().UserId, c.Params.TeamId, &model.InsightsOpts{
StartUnixMilli: startTime,
StartUnixMilli: startTime.UnixMilli(),
Page: c.Params.Page,
PerPage: c.Params.PerPage,
})
@@ -130,14 +136,17 @@ func getTopChannelsForTeamSince(c *Context, w http.ResponseWriter, r *http.Reque
return
}
startTime, err := model.GetStartUnixMilliForTimeRange(c.Params.TimeRange)
user, err := c.App.GetUser(c.AppContext.Session().UserId)
if err != nil {
c.Err = err
return
}
loc := user.GetTimezoneLocation()
startTime := model.StartOfDayForTimeRange(c.Params.TimeRange, loc)
topChannels, err := c.App.GetTopChannelsForTeamSince(c.Params.TeamId, c.AppContext.Session().UserId, &model.InsightsOpts{
StartUnixMilli: startTime,
StartUnixMilli: startTime.UnixMilli(),
Page: c.Params.Page,
PerPage: c.Params.PerPage,
})
@@ -146,6 +155,12 @@ func getTopChannelsForTeamSince(c *Context, w http.ResponseWriter, r *http.Reque
return
}
topChannels.PostCountByDuration, err = postCountByDurationViewModel(c.App, topChannels, startTime, c.Params.TimeRange, nil, loc)
if err != nil {
c.Err = err
return
}
js, jsonErr := json.Marshal(topChannels)
if jsonErr != nil {
c.Err = model.NewAppError("getTopChannelsForTeamSince", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
@@ -177,14 +192,17 @@ func getTopChannelsForUserSince(c *Context, w http.ResponseWriter, r *http.Reque
}
}
startTime, err := model.GetStartUnixMilliForTimeRange(c.Params.TimeRange)
user, err := c.App.GetUser(c.AppContext.Session().UserId)
if err != nil {
c.Err = err
return
}
loc := user.GetTimezoneLocation()
startTime := model.StartOfDayForTimeRange(c.Params.TimeRange, loc)
topChannels, err := c.App.GetTopChannelsForUserSince(c.AppContext.Session().UserId, c.Params.TeamId, &model.InsightsOpts{
StartUnixMilli: startTime,
StartUnixMilli: startTime.UnixMilli(),
Page: c.Params.Page,
PerPage: c.Params.PerPage,
})
@@ -194,6 +212,12 @@ func getTopChannelsForUserSince(c *Context, w http.ResponseWriter, r *http.Reque
return
}
topChannels.PostCountByDuration, err = postCountByDurationViewModel(c.App, topChannels, startTime, c.Params.TimeRange, &c.AppContext.Session().UserId, loc)
if err != nil {
c.Err = err
return
}
js, jsonErr := json.Marshal(topChannels)
if jsonErr != nil {
c.Err = model.NewAppError("getTopChannelsForUserSince", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
@@ -202,3 +226,23 @@ func getTopChannelsForUserSince(c *Context, w http.ResponseWriter, r *http.Reque
w.Write(js)
}
// postCountByDurationViewModel expects a list of channels that are pre-authorized for the given user to view.
func postCountByDurationViewModel(app app.AppIface, topChannelList *model.TopChannelList, startTime *time.Time, timeRange string, userID *string, location *time.Location) (model.ChannelPostCountByDuration, *model.AppError) {
if len(topChannelList.Items) == 0 {
return nil, nil
}
var postCountsByDay []*model.DurationPostCount
channelIDs := topChannelList.ChannelIDs()
var grouping model.PostCountGrouping
if timeRange == model.TimeRangeToday {
grouping = model.PostsByHour
} else {
grouping = model.PostsByDay
}
postCountsByDay, err := app.PostCountsByDuration(channelIDs, startTime.UnixMilli(), userID, grouping, location)
if err != nil {
return nil, err
}
return model.ToDailyPostCountViewModel(postCountsByDay, startTime, model.TimeRangeToNumberDays(timeRange), channelIDs), nil
}

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

@@ -21,6 +21,7 @@ func TestGetTopReactionsForTeamSince(t *testing.T) {
th.ConfigStore.SetReadOnlyFF(false)
defer th.ConfigStore.SetReadOnlyFF(true)
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true })
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional))
client := th.Client
@@ -246,6 +247,7 @@ func TestGetTopReactionsForUserSince(t *testing.T) {
th.ConfigStore.SetReadOnlyFF(false)
defer th.ConfigStore.SetReadOnlyFF(true)
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true })
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional))
client := th.Client
@@ -437,6 +439,7 @@ func TestGetTopChannelsForTeamSince(t *testing.T) {
th.ConfigStore.SetReadOnlyFF(false)
defer th.ConfigStore.SetReadOnlyFF(true)
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true })
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional))
client := th.Client
userId := th.BasicUser.Id
@@ -485,6 +488,10 @@ func TestGetTopChannelsForTeamSince(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, channel6.Id, topChannels.Items[0].ID)
assert.Equal(t, int64(1), topChannels.Items[0].MessageCount)
t.Run("has post count by day", func(t *testing.T) {
require.NotNil(t, topChannels.PostCountByDuration)
})
})
t.Run("get-top-channels-for-user-since exclude channels user is not member of", func(t *testing.T) {
@@ -531,6 +538,7 @@ func TestGetTopChannelsForUserSince(t *testing.T) {
th.ConfigStore.SetReadOnlyFF(false)
defer th.ConfigStore.SetReadOnlyFF(true)
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true })
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional))
client := th.Client
userId := th.BasicUser.Id
@@ -579,6 +587,10 @@ func TestGetTopChannelsForUserSince(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, channel6.Id, topChannels.Items[0].ID)
assert.Equal(t, int64(1), topChannels.Items[0].MessageCount)
t.Run("has post count by day", func(t *testing.T) {
require.NotNil(t, topChannels.PostCountByDuration)
})
})
t.Run("get-top-channels-for-user-since invalid team id", func(t *testing.T) {

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

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

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

@@ -187,6 +187,10 @@
"id": "api.admin.upload_brand_image.too_large.app_error",
"translation": "Unable to upload file. File is too large."
},
{
"id": "api.authorization_error.guest",
"translation": " "
},
{
"id": "api.back_to_app",
"translation": "Back to {{.SiteName}}"
@@ -2053,6 +2057,10 @@
"id": "api.license_error",
"translation": "api endpoint requires a license"
},
{
"id": "api.license_error.professional_or_enterprise",
"translation": " "
},
{
"id": "api.marshal_error",
"translation": "Failed to marshal."
@@ -4531,6 +4539,10 @@
"id": "app.channel.get_pinnedpost_count.app_error",
"translation": "Unable to get the channel pinned post count."
},
{
"id": "app.channel.get_post_count_by_day.app_error",
"translation": " "
},
{
"id": "app.channel.get_private_channels.get.app_error",
"translation": "Unable to get private channels."
@@ -8531,10 +8543,6 @@
"id": "model.incoming_hook.username.app_error",
"translation": "Invalid username."
},
{
"id": "model.insights.time_range.app_error",
"translation": " "
},
{
"id": "model.job.is_valid.create_at.app_error",
"translation": "Create at must be a valid time."

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

@@ -4,14 +4,18 @@
package model
import (
"net/http"
"time"
)
type PostCountGrouping string
const (
TimeRangeToday string = "today"
TimeRange7Day string = "7_day"
TimeRange28Day string = "28_day"
PostsByHour PostCountGrouping = "hour"
PostsByDay PostCountGrouping = "day"
)
type InsightsOpts struct {
@@ -38,7 +42,16 @@ type TopReaction struct {
// Top Channels
type TopChannelList struct {
InsightsListData
Items []*TopChannel `json:"items"`
Items []*TopChannel `json:"items"`
PostCountByDuration ChannelPostCountByDuration `json:"channel_post_counts_by_duration"`
}
func (t *TopChannelList) ChannelIDs() []string {
var ids []string
for _, item := range t.Items {
ids = append(ids, item.ID)
}
return ids
}
type TopChannel struct {
@@ -50,21 +63,116 @@ type TopChannel struct {
MessageCount int64 `json:"message_count"`
}
// GetStartUnixMilliForTimeRange gets the unix start time in milliseconds from the given time range.
// Time range can be one of: "1_day", "7_day", or "28_day".
func GetStartUnixMilliForTimeRange(timeRange string) (int64, *AppError) {
now := time.Now()
_, offset := now.Zone()
type DurationPostCount struct {
ChannelID string `db:"channelid"`
// Duration is an ISO8601 date string representing either a day or a day and hour (ex. "2022-05-26" or "2022-05-26T14").
Duration string `db:"duration"`
PostCount int `db:"postcount"`
}
func TimeRangeToNumberDays(timeRange string) int {
var n int
switch timeRange {
case TimeRangeToday:
return GetStartOfDayMillis(now, offset), nil
n = 1
case TimeRange7Day:
return GetStartOfDayMillis(now.Add(time.Hour*time.Duration(-168)), offset), nil
n = 7
case TimeRange28Day:
return GetStartOfDayMillis(now.Add(time.Hour*time.Duration(-672)), offset), nil
n = 28
}
return n
}
// ChannelPostCountByDuration contains a count of posts by channel id, grouped by ISO8601 date string.
// Example 1 (grouped by day):
// cpc := model.ChannelPostCountByDuration{
// "2009-11-11": {
// "ezbp7nqxzjgdir8riodyafr9ww": 90,
// "p949c1xdojfgzffxma3p3s3ikr": 201,
// },
// "2009-11-12": {
// "ezbp7nqxzjgdir8riodyafr9ww": 45,
// "p949c1xdojfgzffxma3p3s3ikr": 68,
// },
// }
// Example 2 (grouped by hour):
// cpc := model.ChannelPostCountByDuration{
// "2009-11-11T01": {
// "ezbp7nqxzjgdir8riodyafr9ww": 90,
// "p949c1xdojfgzffxma3p3s3ikr": 201,
// },
// "2009-11-11T02": {
// "ezbp7nqxzjgdir8riodyafr9ww": 45,
// "p949c1xdojfgzffxma3p3s3ikr": 68,
// },
// }
type ChannelPostCountByDuration map[string]map[string]int
func blankChannelCountsMap(channelIDs []string) map[string]int {
blankChannelCounts := map[string]int{}
for _, id := range channelIDs {
blankChannelCounts[id] = 0
}
return blankChannelCounts
}
func ToDailyPostCountViewModel(dpc []*DurationPostCount, startTime *time.Time, numDays int, channelIDs []string) ChannelPostCountByDuration {
viewModel := ChannelPostCountByDuration{}
keyTime := *startTime
nowAtLocation := time.Now().In(startTime.Location())
if numDays == 1 {
for keyTime.Before(nowAtLocation) {
dateTimeKey := keyTime.Format(time.RFC3339)
viewModel[dateTimeKey] = blankChannelCountsMap(channelIDs)
keyTime = keyTime.Add(time.Hour)
}
} else {
for keyTime.Before(nowAtLocation) {
dateTimeKey := keyTime.Format("2006-01-02")
viewModel[dateTimeKey] = blankChannelCountsMap(channelIDs)
keyTime = keyTime.Add(24 * time.Hour)
}
}
return GetStartOfDayMillis(now, offset), NewAppError("Insights.IsValidRequest", "model.insights.time_range.app_error", nil, "", http.StatusBadRequest)
for _, item := range dpc {
var parseFormat string
var keyFormat string
if numDays == 1 {
parseFormat = "2006-01-02T15 "
keyFormat = time.RFC3339
} else {
parseFormat = "2006-01-02"
keyFormat = parseFormat
}
durTime, err := time.ParseInLocation(parseFormat, item.Duration, startTime.Location())
if err != nil {
continue
}
localizedKey := durTime.Format(keyFormat)
_, hasKey := viewModel[localizedKey]
if !hasKey {
viewModel[localizedKey] = map[string]int{}
}
viewModel[localizedKey][item.ChannelID] = item.PostCount
}
return viewModel
}
// StartOfDayForTimeRange gets the unix start time in milliseconds from the given time range.
// Time range can be one of: "today", "7_day", or "28_day".
func StartOfDayForTimeRange(timeRange string, location *time.Location) *time.Time {
now := time.Now().In(location)
resultTime := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, location)
switch timeRange {
case TimeRange7Day:
resultTime = resultTime.Add(time.Hour * time.Duration(-144))
case TimeRange28Day:
resultTime = resultTime.Add(time.Hour * time.Duration(-648))
}
return &resultTime
}
// GetTopReactionListWithPagination adds a rank to each item in the given list of TopReaction and checks if there is

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

@@ -9,26 +9,6 @@ import (
"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 TestGetTopReactionListWithPagination(t *testing.T) {
reactions := []*TopReaction{
{EmojiName: "smile", Count: 200},

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

@@ -11,6 +11,7 @@ import (
"regexp"
"sort"
"strings"
"time"
"unicode/utf8"
"golang.org/x/crypto/bcrypt"
@@ -782,6 +783,14 @@ func (u *User) GetPreferredTimezone() string {
return GetPreferredTimezone(u.Timezone)
}
func (u *User) GetTimezoneLocation() *time.Location {
loc, _ := time.LoadLocation(u.GetPreferredTimezone())
if loc == nil {
loc = time.Now().UTC().Location()
}
return loc
}
// IsRemote returns true if the user belongs to a remote cluster (has RemoteId).
func (u *User) IsRemote() bool {
return u.RemoteId != nil && *u.RemoteId != ""

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

@@ -8,6 +8,7 @@ package opentracinglayer
import (
"context"
"time"
"github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/services/tracing"

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

@@ -9,6 +9,7 @@ package retrylayer
import (
"context"
timepkg "time"
"time"
"github.com/lib/pq"
"github.com/mattermost/mattermost-server/v6/model"

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

@@ -8,7 +8,7 @@ package timerlayer
import (
"context"
timemodule "time"
"time"
"github.com/mattermost/mattermost-server/v6/einterfaces"
"github.com/mattermost/mattermost-server/v6/model"
@@ -38,13 +38,13 @@ type {{.Name}} struct {
{{range $substoreName, $substore := .SubStores}}
{{range $index, $element := $substore.Methods}}
func (s *{{$.Name}}{{$substoreName}}Store) {{$index}}({{$element.Params | joinParamsWithType}}) {{$element.Results | joinResultsForSignature}} {
start := timemodule.Now()
start := time.Now()
{{if $element.Results | len | eq 0}}
s.{{$substoreName}}Store.{{$index}}({{$element.Params | joinParams}})
{{else}}
{{genResultsVars $element.Results false }} := s.{{$substoreName}}Store.{{$index}}({{$element.Params | joinParams}})
{{end}}
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {
success := "false"
if {{$element.Results | errorToBoolean}} {

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

@@ -8,6 +8,7 @@ package opentracinglayer
import (
"context"
"time"
"github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/services/tracing"
@@ -745,7 +746,7 @@ func (s *OpenTracingLayerChannelStore) CreateSidebarCategory(userID string, team
return result, err
}
func (s *OpenTracingLayerChannelStore) Delete(channelID string, time int64) error {
func (s *OpenTracingLayerChannelStore) Delete(channelID string, timestamp int64) error {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.Delete")
s.Root.Store.SetContext(newCtx)
@@ -754,7 +755,7 @@ func (s *OpenTracingLayerChannelStore) Delete(channelID string, time int64) erro
}()
defer span.Finish()
err := s.ChannelStore.Delete(channelID, time)
err := s.ChannelStore.Delete(channelID, timestamp)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
@@ -2014,6 +2015,24 @@ func (s *OpenTracingLayerChannelStore) PermanentDeleteMembersByUser(userID strin
return err
}
func (s *OpenTracingLayerChannelStore) PostCountsByDuration(channelIDs []string, sinceUnixMillis int64, userID *string, duration model.PostCountGrouping, groupingLocation *time.Location) ([]*model.DurationPostCount, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.PostCountsByDuration")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
result, err := s.ChannelStore.PostCountsByDuration(channelIDs, sinceUnixMillis, userID, duration, groupingLocation)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return result, err
}
func (s *OpenTracingLayerChannelStore) RemoveAllDeactivatedMembers(channelID string) error {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.RemoveAllDeactivatedMembers")
@@ -2086,7 +2105,7 @@ func (s *OpenTracingLayerChannelStore) ResetAllChannelSchemes() error {
return err
}
func (s *OpenTracingLayerChannelStore) Restore(channelID string, time int64) error {
func (s *OpenTracingLayerChannelStore) Restore(channelID string, timestamp int64) error {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.Restore")
s.Root.Store.SetContext(newCtx)
@@ -2095,7 +2114,7 @@ func (s *OpenTracingLayerChannelStore) Restore(channelID string, time int64) err
}()
defer span.Finish()
err := s.ChannelStore.Restore(channelID, time)
err := s.ChannelStore.Restore(channelID, timestamp)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
@@ -2788,7 +2807,7 @@ func (s *OpenTracingLayerCommandStore) AnalyticsCommandCount(teamID string) (int
return result, err
}
func (s *OpenTracingLayerCommandStore) Delete(commandID string, time int64) error {
func (s *OpenTracingLayerCommandStore) Delete(commandID string, timestamp int64) error {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "CommandStore.Delete")
s.Root.Store.SetContext(newCtx)
@@ -2797,7 +2816,7 @@ func (s *OpenTracingLayerCommandStore) Delete(commandID string, time int64) erro
}()
defer span.Finish()
err := s.CommandStore.Delete(commandID, time)
err := s.CommandStore.Delete(commandID, timestamp)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
@@ -3107,7 +3126,7 @@ func (s *OpenTracingLayerComplianceStore) Update(compliance *model.Compliance) (
return result, err
}
func (s *OpenTracingLayerEmojiStore) Delete(emoji *model.Emoji, time int64) error {
func (s *OpenTracingLayerEmojiStore) Delete(emoji *model.Emoji, timestamp int64) error {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "EmojiStore.Delete")
s.Root.Store.SetContext(newCtx)
@@ -3116,7 +3135,7 @@ func (s *OpenTracingLayerEmojiStore) Delete(emoji *model.Emoji, time int64) erro
}()
defer span.Finish()
err := s.EmojiStore.Delete(emoji, time)
err := s.EmojiStore.Delete(emoji, timestamp)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
@@ -5450,7 +5469,7 @@ func (s *OpenTracingLayerPostStore) ClearCaches() {
}
func (s *OpenTracingLayerPostStore) Delete(postID string, time int64, deleteByID string) error {
func (s *OpenTracingLayerPostStore) Delete(postID string, timestamp int64, deleteByID string) error {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.Delete")
s.Root.Store.SetContext(newCtx)
@@ -5459,7 +5478,7 @@ func (s *OpenTracingLayerPostStore) Delete(postID string, time int64, deleteByID
}()
defer span.Finish()
err := s.PostStore.Delete(postID, time, deleteByID)
err := s.PostStore.Delete(postID, timestamp, deleteByID)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
@@ -5682,7 +5701,7 @@ func (s *OpenTracingLayerPostStore) GetParentsForExportAfter(limit int, afterID
return result, err
}
func (s *OpenTracingLayerPostStore) GetPostAfterTime(channelID string, time int64, collapsedThreads bool) (*model.Post, error) {
func (s *OpenTracingLayerPostStore) GetPostAfterTime(channelID string, timestamp int64, collapsedThreads bool) (*model.Post, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.GetPostAfterTime")
s.Root.Store.SetContext(newCtx)
@@ -5691,7 +5710,7 @@ func (s *OpenTracingLayerPostStore) GetPostAfterTime(channelID string, time int6
}()
defer span.Finish()
result, err := s.PostStore.GetPostAfterTime(channelID, time, collapsedThreads)
result, err := s.PostStore.GetPostAfterTime(channelID, timestamp, collapsedThreads)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
@@ -5700,7 +5719,7 @@ func (s *OpenTracingLayerPostStore) GetPostAfterTime(channelID string, time int6
return result, err
}
func (s *OpenTracingLayerPostStore) GetPostIdAfterTime(channelID string, time int64, collapsedThreads bool) (string, error) {
func (s *OpenTracingLayerPostStore) GetPostIdAfterTime(channelID string, timestamp int64, collapsedThreads bool) (string, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.GetPostIdAfterTime")
s.Root.Store.SetContext(newCtx)
@@ -5709,7 +5728,7 @@ func (s *OpenTracingLayerPostStore) GetPostIdAfterTime(channelID string, time in
}()
defer span.Finish()
result, err := s.PostStore.GetPostIdAfterTime(channelID, time, collapsedThreads)
result, err := s.PostStore.GetPostIdAfterTime(channelID, timestamp, collapsedThreads)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
@@ -5718,7 +5737,7 @@ func (s *OpenTracingLayerPostStore) GetPostIdAfterTime(channelID string, time in
return result, err
}
func (s *OpenTracingLayerPostStore) GetPostIdBeforeTime(channelID string, time int64, collapsedThreads bool) (string, error) {
func (s *OpenTracingLayerPostStore) GetPostIdBeforeTime(channelID string, timestamp int64, collapsedThreads bool) (string, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.GetPostIdBeforeTime")
s.Root.Store.SetContext(newCtx)
@@ -5727,7 +5746,7 @@ func (s *OpenTracingLayerPostStore) GetPostIdBeforeTime(channelID string, time i
}()
defer span.Finish()
result, err := s.PostStore.GetPostIdBeforeTime(channelID, time, collapsedThreads)
result, err := s.PostStore.GetPostIdBeforeTime(channelID, timestamp, collapsedThreads)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
@@ -5826,7 +5845,7 @@ func (s *OpenTracingLayerPostStore) GetPostsByIds(postIds []string) ([]*model.Po
return result, err
}
func (s *OpenTracingLayerPostStore) GetPostsCreatedAt(channelID string, time int64) ([]*model.Post, error) {
func (s *OpenTracingLayerPostStore) GetPostsCreatedAt(channelID string, timestamp int64) ([]*model.Post, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.GetPostsCreatedAt")
s.Root.Store.SetContext(newCtx)
@@ -5835,7 +5854,7 @@ func (s *OpenTracingLayerPostStore) GetPostsCreatedAt(channelID string, time int
}()
defer span.Finish()
result, err := s.PostStore.GetPostsCreatedAt(channelID, time)
result, err := s.PostStore.GetPostsCreatedAt(channelID, timestamp)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
@@ -7639,7 +7658,7 @@ func (s *OpenTracingLayerSessionStore) UpdateExpiredNotify(sessionid string, not
return err
}
func (s *OpenTracingLayerSessionStore) UpdateExpiresAt(sessionID string, time int64) error {
func (s *OpenTracingLayerSessionStore) UpdateExpiresAt(sessionID string, timestamp int64) error {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "SessionStore.UpdateExpiresAt")
s.Root.Store.SetContext(newCtx)
@@ -7648,7 +7667,7 @@ func (s *OpenTracingLayerSessionStore) UpdateExpiresAt(sessionID string, time in
}()
defer span.Finish()
err := s.SessionStore.UpdateExpiresAt(sessionID, time)
err := s.SessionStore.UpdateExpiresAt(sessionID, timestamp)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
@@ -7657,7 +7676,7 @@ func (s *OpenTracingLayerSessionStore) UpdateExpiresAt(sessionID string, time in
return err
}
func (s *OpenTracingLayerSessionStore) UpdateLastActivityAt(sessionID string, time int64) error {
func (s *OpenTracingLayerSessionStore) UpdateLastActivityAt(sessionID string, timestamp int64) error {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "SessionStore.UpdateLastActivityAt")
s.Root.Store.SetContext(newCtx)
@@ -7666,7 +7685,7 @@ func (s *OpenTracingLayerSessionStore) UpdateLastActivityAt(sessionID string, ti
}()
defer span.Finish()
err := s.SessionStore.UpdateLastActivityAt(sessionID, time)
err := s.SessionStore.UpdateLastActivityAt(sessionID, timestamp)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
@@ -10000,7 +10019,7 @@ func (s *OpenTracingLayerUploadSessionStore) Update(session *model.UploadSession
return err
}
func (s *OpenTracingLayerUserStore) AnalyticsActiveCount(time int64, options model.UserCountOptions) (int64, error) {
func (s *OpenTracingLayerUserStore) AnalyticsActiveCount(timestamp int64, options model.UserCountOptions) (int64, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "UserStore.AnalyticsActiveCount")
s.Root.Store.SetContext(newCtx)
@@ -10009,7 +10028,7 @@ func (s *OpenTracingLayerUserStore) AnalyticsActiveCount(time int64, options mod
}()
defer span.Finish()
result, err := s.UserStore.AnalyticsActiveCount(time, options)
result, err := s.UserStore.AnalyticsActiveCount(timestamp, options)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
@@ -11598,7 +11617,7 @@ func (s *OpenTracingLayerWebhookStore) ClearCaches() {
}
func (s *OpenTracingLayerWebhookStore) DeleteIncoming(webhookID string, time int64) error {
func (s *OpenTracingLayerWebhookStore) DeleteIncoming(webhookID string, timestamp int64) error {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "WebhookStore.DeleteIncoming")
s.Root.Store.SetContext(newCtx)
@@ -11607,7 +11626,7 @@ func (s *OpenTracingLayerWebhookStore) DeleteIncoming(webhookID string, time int
}()
defer span.Finish()
err := s.WebhookStore.DeleteIncoming(webhookID, time)
err := s.WebhookStore.DeleteIncoming(webhookID, timestamp)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
@@ -11616,7 +11635,7 @@ func (s *OpenTracingLayerWebhookStore) DeleteIncoming(webhookID string, time int
return err
}
func (s *OpenTracingLayerWebhookStore) DeleteOutgoing(webhookID string, time int64) error {
func (s *OpenTracingLayerWebhookStore) DeleteOutgoing(webhookID string, timestamp int64) error {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "WebhookStore.DeleteOutgoing")
s.Root.Store.SetContext(newCtx)
@@ -11625,7 +11644,7 @@ func (s *OpenTracingLayerWebhookStore) DeleteOutgoing(webhookID string, time int
}()
defer span.Finish()
err := s.WebhookStore.DeleteOutgoing(webhookID, time)
err := s.WebhookStore.DeleteOutgoing(webhookID, timestamp)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)

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

@@ -8,6 +8,7 @@ package retrylayer
import (
"context"
"time"
timepkg "time"
"github.com/go-sql-driver/mysql"
@@ -814,11 +815,11 @@ func (s *RetryLayerChannelStore) CreateSidebarCategory(userID string, teamID str
}
func (s *RetryLayerChannelStore) Delete(channelID string, time int64) error {
func (s *RetryLayerChannelStore) Delete(channelID string, timestamp int64) error {
tries := 0
for {
err := s.ChannelStore.Delete(channelID, time)
err := s.ChannelStore.Delete(channelID, timestamp)
if err == nil {
return nil
}
@@ -2212,6 +2213,27 @@ func (s *RetryLayerChannelStore) PermanentDeleteMembersByUser(userID string) err
}
func (s *RetryLayerChannelStore) PostCountsByDuration(channelIDs []string, sinceUnixMillis int64, userID *string, duration model.PostCountGrouping, groupingLocation *time.Location) ([]*model.DurationPostCount, error) {
tries := 0
for {
result, err := s.ChannelStore.PostCountsByDuration(channelIDs, sinceUnixMillis, userID, duration, groupingLocation)
if err == nil {
return result, nil
}
if !isRepeatableError(err) {
return result, err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return result, err
}
timepkg.Sleep(100 * timepkg.Millisecond)
}
}
func (s *RetryLayerChannelStore) RemoveAllDeactivatedMembers(channelID string) error {
tries := 0
@@ -2296,11 +2318,11 @@ func (s *RetryLayerChannelStore) ResetAllChannelSchemes() error {
}
func (s *RetryLayerChannelStore) Restore(channelID string, time int64) error {
func (s *RetryLayerChannelStore) Restore(channelID string, timestamp int64) error {
tries := 0
for {
err := s.ChannelStore.Restore(channelID, time)
err := s.ChannelStore.Restore(channelID, timestamp)
if err == nil {
return nil
}
@@ -3115,11 +3137,11 @@ func (s *RetryLayerCommandStore) AnalyticsCommandCount(teamID string) (int64, er
}
func (s *RetryLayerCommandStore) Delete(commandID string, time int64) error {
func (s *RetryLayerCommandStore) Delete(commandID string, timestamp int64) error {
tries := 0
for {
err := s.CommandStore.Delete(commandID, time)
err := s.CommandStore.Delete(commandID, timestamp)
if err == nil {
return nil
}
@@ -3478,11 +3500,11 @@ func (s *RetryLayerComplianceStore) Update(compliance *model.Compliance) (*model
}
func (s *RetryLayerEmojiStore) Delete(emoji *model.Emoji, time int64) error {
func (s *RetryLayerEmojiStore) Delete(emoji *model.Emoji, timestamp int64) error {
tries := 0
for {
err := s.EmojiStore.Delete(emoji, time)
err := s.EmojiStore.Delete(emoji, timestamp)
if err == nil {
return nil
}
@@ -6184,11 +6206,11 @@ func (s *RetryLayerPostStore) ClearCaches() {
}
func (s *RetryLayerPostStore) Delete(postID string, time int64, deleteByID string) error {
func (s *RetryLayerPostStore) Delete(postID string, timestamp int64, deleteByID string) error {
tries := 0
for {
err := s.PostStore.Delete(postID, time, deleteByID)
err := s.PostStore.Delete(postID, timestamp, deleteByID)
if err == nil {
return nil
}
@@ -6427,11 +6449,11 @@ func (s *RetryLayerPostStore) GetParentsForExportAfter(limit int, afterID string
}
func (s *RetryLayerPostStore) GetPostAfterTime(channelID string, time int64, collapsedThreads bool) (*model.Post, error) {
func (s *RetryLayerPostStore) GetPostAfterTime(channelID string, timestamp int64, collapsedThreads bool) (*model.Post, error) {
tries := 0
for {
result, err := s.PostStore.GetPostAfterTime(channelID, time, collapsedThreads)
result, err := s.PostStore.GetPostAfterTime(channelID, timestamp, collapsedThreads)
if err == nil {
return result, nil
}
@@ -6448,11 +6470,11 @@ func (s *RetryLayerPostStore) GetPostAfterTime(channelID string, time int64, col
}
func (s *RetryLayerPostStore) GetPostIdAfterTime(channelID string, time int64, collapsedThreads bool) (string, error) {
func (s *RetryLayerPostStore) GetPostIdAfterTime(channelID string, timestamp int64, collapsedThreads bool) (string, error) {
tries := 0
for {
result, err := s.PostStore.GetPostIdAfterTime(channelID, time, collapsedThreads)
result, err := s.PostStore.GetPostIdAfterTime(channelID, timestamp, collapsedThreads)
if err == nil {
return result, nil
}
@@ -6469,11 +6491,11 @@ func (s *RetryLayerPostStore) GetPostIdAfterTime(channelID string, time int64, c
}
func (s *RetryLayerPostStore) GetPostIdBeforeTime(channelID string, time int64, collapsedThreads bool) (string, error) {
func (s *RetryLayerPostStore) GetPostIdBeforeTime(channelID string, timestamp int64, collapsedThreads bool) (string, error) {
tries := 0
for {
result, err := s.PostStore.GetPostIdBeforeTime(channelID, time, collapsedThreads)
result, err := s.PostStore.GetPostIdBeforeTime(channelID, timestamp, collapsedThreads)
if err == nil {
return result, nil
}
@@ -6595,11 +6617,11 @@ func (s *RetryLayerPostStore) GetPostsByIds(postIds []string) ([]*model.Post, er
}
func (s *RetryLayerPostStore) GetPostsCreatedAt(channelID string, time int64) ([]*model.Post, error) {
func (s *RetryLayerPostStore) GetPostsCreatedAt(channelID string, timestamp int64) ([]*model.Post, error) {
tries := 0
for {
result, err := s.PostStore.GetPostsCreatedAt(channelID, time)
result, err := s.PostStore.GetPostsCreatedAt(channelID, timestamp)
if err == nil {
return result, nil
}
@@ -8701,11 +8723,11 @@ func (s *RetryLayerSessionStore) UpdateExpiredNotify(sessionid string, notified
}
func (s *RetryLayerSessionStore) UpdateExpiresAt(sessionID string, time int64) error {
func (s *RetryLayerSessionStore) UpdateExpiresAt(sessionID string, timestamp int64) error {
tries := 0
for {
err := s.SessionStore.UpdateExpiresAt(sessionID, time)
err := s.SessionStore.UpdateExpiresAt(sessionID, timestamp)
if err == nil {
return nil
}
@@ -8722,11 +8744,11 @@ func (s *RetryLayerSessionStore) UpdateExpiresAt(sessionID string, time int64) e
}
func (s *RetryLayerSessionStore) UpdateLastActivityAt(sessionID string, time int64) error {
func (s *RetryLayerSessionStore) UpdateLastActivityAt(sessionID string, timestamp int64) error {
tries := 0
for {
err := s.SessionStore.UpdateLastActivityAt(sessionID, time)
err := s.SessionStore.UpdateLastActivityAt(sessionID, timestamp)
if err == nil {
return nil
}
@@ -11428,11 +11450,11 @@ func (s *RetryLayerUploadSessionStore) Update(session *model.UploadSession) erro
}
func (s *RetryLayerUserStore) AnalyticsActiveCount(time int64, options model.UserCountOptions) (int64, error) {
func (s *RetryLayerUserStore) AnalyticsActiveCount(timestamp int64, options model.UserCountOptions) (int64, error) {
tries := 0
for {
result, err := s.UserStore.AnalyticsActiveCount(time, options)
result, err := s.UserStore.AnalyticsActiveCount(timestamp, options)
if err == nil {
return result, nil
}
@@ -13219,11 +13241,11 @@ func (s *RetryLayerWebhookStore) ClearCaches() {
}
func (s *RetryLayerWebhookStore) DeleteIncoming(webhookID string, time int64) error {
func (s *RetryLayerWebhookStore) DeleteIncoming(webhookID string, timestamp int64) error {
tries := 0
for {
err := s.WebhookStore.DeleteIncoming(webhookID, time)
err := s.WebhookStore.DeleteIncoming(webhookID, timestamp)
if err == nil {
return nil
}
@@ -13240,11 +13262,11 @@ func (s *RetryLayerWebhookStore) DeleteIncoming(webhookID string, time int64) er
}
func (s *RetryLayerWebhookStore) DeleteOutgoing(webhookID string, time int64) error {
func (s *RetryLayerWebhookStore) DeleteOutgoing(webhookID string, timestamp int64) error {
tries := 0
for {
err := s.WebhookStore.DeleteOutgoing(webhookID, time)
err := s.WebhookStore.DeleteOutgoing(webhookID, timestamp)
if err == nil {
return nil
}

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

@@ -4297,3 +4297,53 @@ func (s SqlChannelStore) GetTopChannelsForUserSince(userID string, teamID string
return model.GetTopChannelListWithPagination(channels, limit), nil
}
func (s SqlChannelStore) PostCountsByDuration(channelIDs []string, sinceUnixMillis int64, userID *string, duration model.PostCountGrouping, atLocation *time.Location) ([]*model.DurationPostCount, error) {
var unixSelect string
var propsQuery string
loc := atLocation.String()
if loc == "Local" {
loc = "UTC"
}
if s.DriverName() == model.DatabaseDriverMysql {
if duration == model.PostsByDay {
unixSelect = `DATE_FORMAT(CONVERT_TZ(FROM_UNIXTIME(Posts.CreateAt / 1000), 'GMT', '` + loc + `'),'%Y-%m-%d') AS duration`
} else {
unixSelect = `DATE_FORMAT(CONVERT_TZ(FROM_UNIXTIME(Posts.CreateAt / 1000), 'GMT', '` + loc + `'),'%Y-%m-%dT%H') AS duration`
}
propsQuery = `(JSON_EXTRACT(Posts.Props, '$.from_bot') IS NULL OR JSON_EXTRACT(Posts.Props, '$.from_bot') = 'false')`
} else if s.DriverName() == model.DatabaseDriverPostgres {
if duration == model.PostsByDay {
unixSelect = fmt.Sprintf(`TO_CHAR(TO_TIMESTAMP(Posts.CreateAt / 1000) AT TIME ZONE '%s', 'YYYY-MM-DD') AS duration`, loc)
} else {
unixSelect = fmt.Sprintf(`TO_CHAR(TO_TIMESTAMP(Posts.CreateAt / 1000) AT TIME ZONE '%s', 'YYYY-MM-DD"T"HH24') AS duration`, loc)
}
propsQuery = `(Posts.Props ->> 'from_bot' IS NULL OR Posts.Props ->> 'from_bot' = 'false')`
}
query := sq.
Select("Posts.ChannelId AS channelid", unixSelect, "count(Posts.Id) AS postcount").
From("Posts").
LeftJoin("Channels ON Posts.ChannelId = Channels.Id").
Where(sq.And{
sq.Eq{"Posts.DeleteAt": 0},
sq.Gt{"Posts.CreateAt": sinceUnixMillis},
sq.Eq{"Posts.Type": ""},
sq.Eq{"Channels.Id": channelIDs},
}).
Where(propsQuery).
GroupBy("channelid", "duration").
OrderBy("channelid", "duration")
if userID != nil && model.IsValidId(*userID) {
query = query.Where(sq.And{sq.Eq{"Posts.UserId": *userID}})
}
queryString, args, err := query.ToSql()
if err != nil {
return nil, errors.Wrap(err, "failed to parse query")
}
dailyPostCounts := make([]*model.DurationPostCount, 0)
if err := s.GetReplicaX().Select(&dailyPostCounts, queryString, args...); err != nil {
return nil, errors.Wrap(err, "failed to get post counts by duration")
}
return dailyPostCounts, nil
}

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

@@ -178,8 +178,8 @@ type ChannelStore interface {
GetMany(ids []string, allowFromCache bool) (model.ChannelList, error)
InvalidateChannel(id string)
InvalidateChannelByName(teamID, name string)
Delete(channelID string, time int64) error
Restore(channelID string, time int64) error
Delete(channelID string, timestamp int64) error
Restore(channelID string, timestamp int64) error
SetDeleteAt(channelID string, deleteAt int64, updateAt int64) error
PermanentDelete(channelID string) error
PermanentDeleteByTeam(teamID string) error
@@ -293,6 +293,7 @@ type ChannelStore interface {
// Insights
GetTopChannelsForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopChannelList, error)
GetTopChannelsForUserSince(userID string, teamID string, since int64, offset int, limit int) (*model.TopChannelList, error)
PostCountsByDuration(channelIDs []string, sinceUnixMillis int64, userID *string, duration model.PostCountGrouping, groupingLocation *time.Location) ([]*model.DurationPostCount, error)
}
type ChannelMemberHistoryStore interface {
@@ -338,7 +339,7 @@ type PostStore interface {
Update(newPost *model.Post, oldPost *model.Post) (*model.Post, error)
Get(ctx context.Context, id string, opts model.GetPostsOptions, userID string, sanitizeOptions map[string]bool) (*model.PostList, error)
GetSingle(id string, inclDeleted bool) (*model.Post, error)
Delete(postID string, time int64, deleteByID string) error
Delete(postID string, timestamp int64, deleteByID string) error
PermanentDeleteByUser(userID string) error
PermanentDeleteByChannel(channelID string) error
GetPosts(options model.GetPostsOptions, allowFromCache bool, sanitizeOptions map[string]bool) (*model.PostList, error)
@@ -349,9 +350,9 @@ type PostStore interface {
GetPostsBefore(options model.GetPostsOptions, sanitizeOptions map[string]bool) (*model.PostList, error)
GetPostsAfter(options model.GetPostsOptions, sanitizeOptions map[string]bool) (*model.PostList, error)
GetPostsSince(options model.GetPostsSinceOptions, allowFromCache bool, sanitizeOptions map[string]bool) (*model.PostList, error)
GetPostAfterTime(channelID string, time int64, collapsedThreads bool) (*model.Post, error)
GetPostIdAfterTime(channelID string, time int64, collapsedThreads bool) (string, error)
GetPostIdBeforeTime(channelID string, time int64, collapsedThreads bool) (string, error)
GetPostAfterTime(channelID string, timestamp int64, collapsedThreads bool) (*model.Post, error)
GetPostIdAfterTime(channelID string, timestamp int64, collapsedThreads bool) (string, error)
GetPostIdBeforeTime(channelID string, timestamp int64, collapsedThreads bool) (string, error)
GetEtag(channelID string, allowFromCache bool, collapsedThreads bool) string
Search(teamID string, userID string, params *model.SearchParams) (*model.PostList, error)
AnalyticsUserCountsWithPostsByDay(teamID string) (model.AnalyticsRows, error)
@@ -360,7 +361,7 @@ type PostStore interface {
ClearCaches()
InvalidateLastPostTimeCache(channelID string)
GetLastPostRowCreateAt() (int64, error)
GetPostsCreatedAt(channelID string, time int64) ([]*model.Post, error)
GetPostsCreatedAt(channelID string, timestamp int64) ([]*model.Post, error)
Overwrite(post *model.Post) (*model.Post, error)
OverwriteMultiple(posts []*model.Post) ([]*model.Post, int, error)
GetPostsByIds(postIds []string) ([]*model.Post, error)
@@ -422,7 +423,7 @@ type UserStore interface {
UpdateFailedPasswordAttempts(userID string, attempts int) error
GetSystemAdminProfiles() (map[string]*model.User, error)
PermanentDelete(userID string) error
AnalyticsActiveCount(time int64, options model.UserCountOptions) (int64, error)
AnalyticsActiveCount(timestamp int64, options model.UserCountOptions) (int64, error)
AnalyticsActiveCountForPeriod(startTime int64, endTime int64, options model.UserCountOptions) (int64, error)
GetUnreadCount(userID string) (int64, error)
GetUnreadCountForChannel(userID string, channelID string) (int64, error)
@@ -478,8 +479,8 @@ type SessionStore interface {
RemoveAllSessions() error
PermanentDeleteSessionsByUser(teamID string) error
GetLastSessionRowCreateAt() (int64, error)
UpdateExpiresAt(sessionID string, time int64) error
UpdateLastActivityAt(sessionID string, time int64) error
UpdateExpiresAt(sessionID string, timestamp int64) error
UpdateLastActivityAt(sessionID string, timestamp int64) error
UpdateRoles(userID string, roles string) (string, error)
UpdateDeviceId(id string, deviceID string, expiresAt int64) (string, error)
UpdateProps(session *model.Session) error
@@ -563,7 +564,7 @@ type WebhookStore interface {
GetIncomingByTeamByUser(teamID string, userID string, offset, limit int) ([]*model.IncomingWebhook, error)
UpdateIncoming(webhook *model.IncomingWebhook) (*model.IncomingWebhook, error)
GetIncomingByChannel(channelID string) ([]*model.IncomingWebhook, error)
DeleteIncoming(webhookID string, time int64) error
DeleteIncoming(webhookID string, timestamp int64) error
PermanentDeleteIncomingByChannel(channelID string) error
PermanentDeleteIncomingByUser(userID string) error
@@ -575,7 +576,7 @@ type WebhookStore interface {
GetOutgoingListByUser(userID string, offset, limit int) ([]*model.OutgoingWebhook, error)
GetOutgoingByTeam(teamID string, offset, limit int) ([]*model.OutgoingWebhook, error)
GetOutgoingByTeamByUser(teamID string, userID string, offset, limit int) ([]*model.OutgoingWebhook, error)
DeleteOutgoing(webhookID string, time int64) error
DeleteOutgoing(webhookID string, timestamp int64) error
PermanentDeleteOutgoingByChannel(channelID string) error
PermanentDeleteOutgoingByUser(userID string) error
UpdateOutgoing(hook *model.OutgoingWebhook) (*model.OutgoingWebhook, error)
@@ -591,7 +592,7 @@ type CommandStore interface {
GetByTrigger(teamID string, trigger string) (*model.Command, error)
Get(id string) (*model.Command, error)
GetByTeam(teamID string) ([]*model.Command, error)
Delete(commandID string, time int64) error
Delete(commandID string, timestamp int64) error
PermanentDeleteByTeam(teamID string) error
PermanentDeleteByUser(userID string) error
Update(hook *model.Command) (*model.Command, error)
@@ -639,7 +640,7 @@ type EmojiStore interface {
GetByName(ctx context.Context, name string, allowFromCache bool) (*model.Emoji, error)
GetMultipleByName(names []string) ([]*model.Emoji, error)
GetList(offset, limit int, sort string) ([]*model.Emoji, error)
Delete(emoji *model.Emoji, time int64) error
Delete(emoji *model.Emoji, timestamp int64) error
Search(name string, prefixOnly bool, limit int) ([]*model.Emoji, error)
}

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

@@ -148,6 +148,7 @@ func TestChannelStore(t *testing.T, ss store.Store, s SqlStore) {
t.Run("UpdateSidebarChannelsByPreferences", func(t *testing.T) { testUpdateSidebarChannelsByPreferences(t, ss) })
t.Run("SetShared", func(t *testing.T) { testSetShared(t, ss) })
t.Run("GetTeamForChannel", func(t *testing.T) { testGetTeamForChannel(t, ss) })
t.Run("PostCountsByDuration", func(t *testing.T) { testChannelPostCountsByDuration(t, ss) })
}
func testChannelStoreSave(t *testing.T, ss store.Store) {
@@ -7904,3 +7905,38 @@ func testGetTeamForChannel(t *testing.T, ss store.Store) {
var nfErr *store.ErrNotFound
require.True(t, errors.As(err, &nfErr))
}
func testChannelPostCountsByDuration(t *testing.T, ss store.Store) {
team, err := ss.Team().Save(&model.Team{
Name: model.NewId(),
DisplayName: "DisplayName",
Email: MakeEmail(),
Type: model.TeamOpen,
})
require.NoError(t, err)
defer func() { ss.Team().PermanentDelete(team.Id) }()
channel := &model.Channel{
TeamId: team.Id,
DisplayName: "test_share_flag",
Name: "test_share_flag",
Type: model.ChannelTypeOpen,
}
channelSaved, err := ss.Channel().Save(channel, 999)
require.NoError(t, err)
defer func() { ss.Channel().PermanentDelete(channelSaved.Id) }()
userID := model.NewId()
_, err = ss.Post().Save(&model.Post{
UserId: userID,
ChannelId: channel.Id,
Message: "test",
})
require.NoError(t, err)
dpc, err := ss.Channel().PostCountsByDuration([]string{channelSaved.Id}, 0, &userID, model.PostsByDay, time.Now().Location())
require.NoError(t, err)
require.Len(t, dpc, 1)
require.Equal(t, channel.Id, dpc[0].ChannelID)
require.Equal(t, 1, dpc[0].PostCount)
}

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

@@ -11,6 +11,8 @@ import (
mock "github.com/stretchr/testify/mock"
store "github.com/mattermost/mattermost-server/v6/store"
time "time"
)
// ChannelStore is an autogenerated mock type for the ChannelStore type
@@ -266,13 +268,13 @@ func (_m *ChannelStore) CreateSidebarCategory(userID string, teamID string, newC
return r0, r1
}
// Delete provides a mock function with given fields: channelID, time
func (_m *ChannelStore) Delete(channelID string, time int64) error {
ret := _m.Called(channelID, time)
// Delete provides a mock function with given fields: channelID, timestamp
func (_m *ChannelStore) Delete(channelID string, timestamp int64) error {
ret := _m.Called(channelID, timestamp)
var r0 error
if rf, ok := ret.Get(0).(func(string, int64) error); ok {
r0 = rf(channelID, time)
r0 = rf(channelID, timestamp)
} else {
r0 = ret.Error(0)
}
@@ -1717,6 +1719,29 @@ func (_m *ChannelStore) PermanentDeleteMembersByUser(userID string) error {
return r0
}
// PostCountsByDuration provides a mock function with given fields: channelIDs, sinceUnixMillis, userID, duration, groupingLocation
func (_m *ChannelStore) PostCountsByDuration(channelIDs []string, sinceUnixMillis int64, userID *string, duration model.PostCountGrouping, groupingLocation *time.Location) ([]*model.DurationPostCount, error) {
ret := _m.Called(channelIDs, sinceUnixMillis, userID, duration, groupingLocation)
var r0 []*model.DurationPostCount
if rf, ok := ret.Get(0).(func([]string, int64, *string, model.PostCountGrouping, *time.Location) []*model.DurationPostCount); ok {
r0 = rf(channelIDs, sinceUnixMillis, userID, duration, groupingLocation)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.DurationPostCount)
}
}
var r1 error
if rf, ok := ret.Get(1).(func([]string, int64, *string, model.PostCountGrouping, *time.Location) error); ok {
r1 = rf(channelIDs, sinceUnixMillis, userID, duration, groupingLocation)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// RemoveAllDeactivatedMembers provides a mock function with given fields: channelID
func (_m *ChannelStore) RemoveAllDeactivatedMembers(channelID string) error {
ret := _m.Called(channelID)
@@ -1773,13 +1798,13 @@ func (_m *ChannelStore) ResetAllChannelSchemes() error {
return r0
}
// Restore provides a mock function with given fields: channelID, time
func (_m *ChannelStore) Restore(channelID string, time int64) error {
ret := _m.Called(channelID, time)
// Restore provides a mock function with given fields: channelID, timestamp
func (_m *ChannelStore) Restore(channelID string, timestamp int64) error {
ret := _m.Called(channelID, timestamp)
var r0 error
if rf, ok := ret.Get(0).(func(string, int64) error); ok {
r0 = rf(channelID, time)
r0 = rf(channelID, timestamp)
} else {
r0 = ret.Error(0)
}

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

@@ -35,13 +35,13 @@ func (_m *CommandStore) AnalyticsCommandCount(teamID string) (int64, error) {
return r0, r1
}
// Delete provides a mock function with given fields: commandID, time
func (_m *CommandStore) Delete(commandID string, time int64) error {
ret := _m.Called(commandID, time)
// Delete provides a mock function with given fields: commandID, timestamp
func (_m *CommandStore) Delete(commandID string, timestamp int64) error {
ret := _m.Called(commandID, timestamp)
var r0 error
if rf, ok := ret.Get(0).(func(string, int64) error); ok {
r0 = rf(commandID, time)
r0 = rf(commandID, timestamp)
} else {
r0 = ret.Error(0)
}

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

@@ -16,13 +16,13 @@ type EmojiStore struct {
mock.Mock
}
// Delete provides a mock function with given fields: emoji, time
func (_m *EmojiStore) Delete(emoji *model.Emoji, time int64) error {
ret := _m.Called(emoji, time)
// Delete provides a mock function with given fields: emoji, timestamp
func (_m *EmojiStore) Delete(emoji *model.Emoji, timestamp int64) error {
ret := _m.Called(emoji, timestamp)
var r0 error
if rf, ok := ret.Get(0).(func(*model.Emoji, int64) error); ok {
r0 = rf(emoji, time)
r0 = rf(emoji, timestamp)
} else {
r0 = ret.Error(0)
}

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

@@ -88,13 +88,13 @@ func (_m *PostStore) ClearCaches() {
_m.Called()
}
// Delete provides a mock function with given fields: postID, time, deleteByID
func (_m *PostStore) Delete(postID string, time int64, deleteByID string) error {
ret := _m.Called(postID, time, deleteByID)
// Delete provides a mock function with given fields: postID, timestamp, deleteByID
func (_m *PostStore) Delete(postID string, timestamp int64, deleteByID string) error {
ret := _m.Called(postID, timestamp, deleteByID)
var r0 error
if rf, ok := ret.Get(0).(func(string, int64, string) error); ok {
r0 = rf(postID, time, deleteByID)
r0 = rf(postID, timestamp, deleteByID)
} else {
r0 = ret.Error(0)
}
@@ -354,13 +354,13 @@ func (_m *PostStore) GetParentsForExportAfter(limit int, afterID string) ([]*mod
return r0, r1
}
// GetPostAfterTime provides a mock function with given fields: channelID, time, collapsedThreads
func (_m *PostStore) GetPostAfterTime(channelID string, time int64, collapsedThreads bool) (*model.Post, error) {
ret := _m.Called(channelID, time, collapsedThreads)
// GetPostAfterTime provides a mock function with given fields: channelID, timestamp, collapsedThreads
func (_m *PostStore) GetPostAfterTime(channelID string, timestamp int64, collapsedThreads bool) (*model.Post, error) {
ret := _m.Called(channelID, timestamp, collapsedThreads)
var r0 *model.Post
if rf, ok := ret.Get(0).(func(string, int64, bool) *model.Post); ok {
r0 = rf(channelID, time, collapsedThreads)
r0 = rf(channelID, timestamp, collapsedThreads)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Post)
@@ -369,7 +369,7 @@ func (_m *PostStore) GetPostAfterTime(channelID string, time int64, collapsedThr
var r1 error
if rf, ok := ret.Get(1).(func(string, int64, bool) error); ok {
r1 = rf(channelID, time, collapsedThreads)
r1 = rf(channelID, timestamp, collapsedThreads)
} else {
r1 = ret.Error(1)
}
@@ -377,20 +377,20 @@ func (_m *PostStore) GetPostAfterTime(channelID string, time int64, collapsedThr
return r0, r1
}
// GetPostIdAfterTime provides a mock function with given fields: channelID, time, collapsedThreads
func (_m *PostStore) GetPostIdAfterTime(channelID string, time int64, collapsedThreads bool) (string, error) {
ret := _m.Called(channelID, time, collapsedThreads)
// GetPostIdAfterTime provides a mock function with given fields: channelID, timestamp, collapsedThreads
func (_m *PostStore) GetPostIdAfterTime(channelID string, timestamp int64, collapsedThreads bool) (string, error) {
ret := _m.Called(channelID, timestamp, collapsedThreads)
var r0 string
if rf, ok := ret.Get(0).(func(string, int64, bool) string); ok {
r0 = rf(channelID, time, collapsedThreads)
r0 = rf(channelID, timestamp, collapsedThreads)
} else {
r0 = ret.Get(0).(string)
}
var r1 error
if rf, ok := ret.Get(1).(func(string, int64, bool) error); ok {
r1 = rf(channelID, time, collapsedThreads)
r1 = rf(channelID, timestamp, collapsedThreads)
} else {
r1 = ret.Error(1)
}
@@ -398,20 +398,20 @@ func (_m *PostStore) GetPostIdAfterTime(channelID string, time int64, collapsedT
return r0, r1
}
// GetPostIdBeforeTime provides a mock function with given fields: channelID, time, collapsedThreads
func (_m *PostStore) GetPostIdBeforeTime(channelID string, time int64, collapsedThreads bool) (string, error) {
ret := _m.Called(channelID, time, collapsedThreads)
// GetPostIdBeforeTime provides a mock function with given fields: channelID, timestamp, collapsedThreads
func (_m *PostStore) GetPostIdBeforeTime(channelID string, timestamp int64, collapsedThreads bool) (string, error) {
ret := _m.Called(channelID, timestamp, collapsedThreads)
var r0 string
if rf, ok := ret.Get(0).(func(string, int64, bool) string); ok {
r0 = rf(channelID, time, collapsedThreads)
r0 = rf(channelID, timestamp, collapsedThreads)
} else {
r0 = ret.Get(0).(string)
}
var r1 error
if rf, ok := ret.Get(1).(func(string, int64, bool) error); ok {
r1 = rf(channelID, time, collapsedThreads)
r1 = rf(channelID, timestamp, collapsedThreads)
} else {
r1 = ret.Error(1)
}
@@ -534,13 +534,13 @@ func (_m *PostStore) GetPostsByIds(postIds []string) ([]*model.Post, error) {
return r0, r1
}
// GetPostsCreatedAt provides a mock function with given fields: channelID, time
func (_m *PostStore) GetPostsCreatedAt(channelID string, time int64) ([]*model.Post, error) {
ret := _m.Called(channelID, time)
// GetPostsCreatedAt provides a mock function with given fields: channelID, timestamp
func (_m *PostStore) GetPostsCreatedAt(channelID string, timestamp int64) ([]*model.Post, error) {
ret := _m.Called(channelID, timestamp)
var r0 []*model.Post
if rf, ok := ret.Get(0).(func(string, int64) []*model.Post); ok {
r0 = rf(channelID, time)
r0 = rf(channelID, timestamp)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.Post)
@@ -549,7 +549,7 @@ func (_m *PostStore) GetPostsCreatedAt(channelID string, time int64) ([]*model.P
var r1 error
if rf, ok := ret.Get(1).(func(string, int64) error); ok {
r1 = rf(channelID, time)
r1 = rf(channelID, timestamp)
} else {
r1 = ret.Error(1)
}

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

@@ -264,13 +264,13 @@ func (_m *SessionStore) UpdateExpiredNotify(sessionid string, notified bool) err
return r0
}
// UpdateExpiresAt provides a mock function with given fields: sessionID, time
func (_m *SessionStore) UpdateExpiresAt(sessionID string, time int64) error {
ret := _m.Called(sessionID, time)
// UpdateExpiresAt provides a mock function with given fields: sessionID, timestamp
func (_m *SessionStore) UpdateExpiresAt(sessionID string, timestamp int64) error {
ret := _m.Called(sessionID, timestamp)
var r0 error
if rf, ok := ret.Get(0).(func(string, int64) error); ok {
r0 = rf(sessionID, time)
r0 = rf(sessionID, timestamp)
} else {
r0 = ret.Error(0)
}
@@ -278,13 +278,13 @@ func (_m *SessionStore) UpdateExpiresAt(sessionID string, time int64) error {
return r0
}
// UpdateLastActivityAt provides a mock function with given fields: sessionID, time
func (_m *SessionStore) UpdateLastActivityAt(sessionID string, time int64) error {
ret := _m.Called(sessionID, time)
// UpdateLastActivityAt provides a mock function with given fields: sessionID, timestamp
func (_m *SessionStore) UpdateLastActivityAt(sessionID string, timestamp int64) error {
ret := _m.Called(sessionID, timestamp)
var r0 error
if rf, ok := ret.Get(0).(func(string, int64) error); ok {
r0 = rf(sessionID, time)
r0 = rf(sessionID, timestamp)
} else {
r0 = ret.Error(0)
}

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

@@ -18,20 +18,20 @@ type UserStore struct {
mock.Mock
}
// AnalyticsActiveCount provides a mock function with given fields: time, options
func (_m *UserStore) AnalyticsActiveCount(time int64, options model.UserCountOptions) (int64, error) {
ret := _m.Called(time, options)
// AnalyticsActiveCount provides a mock function with given fields: timestamp, options
func (_m *UserStore) AnalyticsActiveCount(timestamp int64, options model.UserCountOptions) (int64, error) {
ret := _m.Called(timestamp, options)
var r0 int64
if rf, ok := ret.Get(0).(func(int64, model.UserCountOptions) int64); ok {
r0 = rf(time, options)
r0 = rf(timestamp, options)
} else {
r0 = ret.Get(0).(int64)
}
var r1 error
if rf, ok := ret.Get(1).(func(int64, model.UserCountOptions) error); ok {
r1 = rf(time, options)
r1 = rf(timestamp, options)
} else {
r1 = ret.Error(1)
}

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

@@ -61,13 +61,13 @@ func (_m *WebhookStore) ClearCaches() {
_m.Called()
}
// DeleteIncoming provides a mock function with given fields: webhookID, time
func (_m *WebhookStore) DeleteIncoming(webhookID string, time int64) error {
ret := _m.Called(webhookID, time)
// DeleteIncoming provides a mock function with given fields: webhookID, timestamp
func (_m *WebhookStore) DeleteIncoming(webhookID string, timestamp int64) error {
ret := _m.Called(webhookID, timestamp)
var r0 error
if rf, ok := ret.Get(0).(func(string, int64) error); ok {
r0 = rf(webhookID, time)
r0 = rf(webhookID, timestamp)
} else {
r0 = ret.Error(0)
}
@@ -75,13 +75,13 @@ func (_m *WebhookStore) DeleteIncoming(webhookID string, time int64) error {
return r0
}
// DeleteOutgoing provides a mock function with given fields: webhookID, time
func (_m *WebhookStore) DeleteOutgoing(webhookID string, time int64) error {
ret := _m.Called(webhookID, time)
// DeleteOutgoing provides a mock function with given fields: webhookID, timestamp
func (_m *WebhookStore) DeleteOutgoing(webhookID string, timestamp int64) error {
ret := _m.Called(webhookID, timestamp)
var r0 error
if rf, ok := ret.Get(0).(func(string, int64) error); ok {
r0 = rf(webhookID, time)
r0 = rf(webhookID, timestamp)
} else {
r0 = ret.Error(0)
}

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

@@ -36,7 +36,7 @@ func TestPostStore(t *testing.T, ss store.Store, s SqlStore) {
t.Run("GetPosts", func(t *testing.T) { testPostStoreGetPosts(t, ss) })
t.Run("GetPostBeforeAfter", func(t *testing.T) { testPostStoreGetPostBeforeAfter(t, ss) })
t.Run("UserCountsWithPostsByDay", func(t *testing.T) { testUserCountsWithPostsByDay(t, ss) })
t.Run("PostCountsByDay", func(t *testing.T) { testPostCountsByDay(t, ss) })
t.Run("PostCountsByDuration", func(t *testing.T) { testPostCountsByDay(t, ss) })
t.Run("GetFlaggedPostsForTeam", func(t *testing.T) { testPostStoreGetFlaggedPostsForTeam(t, ss, s) })
t.Run("GetFlaggedPosts", func(t *testing.T) { testPostStoreGetFlaggedPosts(t, ss) })
t.Run("GetFlaggedPostsForChannel", func(t *testing.T) { testPostStoreGetFlaggedPostsForChannel(t, ss) })

Разница между файлами не показана из-за своего большого размера Загрузить разницу