MM-43956: Adds post counts by duration. (#20131)
* MM-43956: Adds post counts by day. * MM-43956: Test data cleanup. Switch from Unix to UnixMilli. * MM-43956: Changes from selecting date data types to strings in SQL. * MM-43956: Fixes date format key. * MM-43956: Adds missing user id scope for 'my' top channels graph. * MM-43956: Adds the ability to group post counts by hour. * MM-43956: Require enterprise or professional license. Reject guests. * MM-43956: Adds license for tests. * MM-43956: Renames function. * MM-43956: Omits future hours from post counts by hour. * MM-43956: Adjust API response grouping to users timezone. * MM-43956: Adds translation. * MM-43956: Adds user's timezone to the data tier for the grouping by day and hour. * MM-43956: Fixes layers. * MM-43956: Lint fix. * MM-43956: Fix store layers. * MM-43956: Switches to default name for time package; changes parameter names to avoid naming conflict. * MM-43956: Updates mocks. Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
c03eb778c8
Коммит
182ae1234a
@@ -209,3 +209,24 @@ func requireLicense(f handlerFunc) handlerFunc {
|
|||||||
f(c, w, r)
|
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 (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v6/app"
|
||||||
"github.com/mattermost/mattermost-server/v6/model"
|
"github.com/mattermost/mattermost-server/v6/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (api *API) InitInsights() {
|
func (api *API) InitInsights() {
|
||||||
// Reactions
|
// Reactions
|
||||||
api.BaseRoutes.InsightsForTeam.Handle("/reactions", api.APISessionRequired(getTopReactionsForTeamSince)).Methods("GET")
|
api.BaseRoutes.InsightsForTeam.Handle("/reactions", api.APISessionRequired(minimumProfessionalLicense(rejectGuests(getTopReactionsForTeamSince)))).Methods("GET")
|
||||||
api.BaseRoutes.InsightsForUser.Handle("/reactions", api.APISessionRequired(getTopReactionsForUserSince)).Methods("GET")
|
api.BaseRoutes.InsightsForUser.Handle("/reactions", api.APISessionRequired(minimumProfessionalLicense(rejectGuests(getTopReactionsForUserSince)))).Methods("GET")
|
||||||
|
|
||||||
// Channels
|
// Channels
|
||||||
api.BaseRoutes.InsightsForTeam.Handle("/channels", api.APISessionRequired(getTopChannelsForTeamSince)).Methods("GET")
|
api.BaseRoutes.InsightsForTeam.Handle("/channels", api.APISessionRequired(minimumProfessionalLicense(rejectGuests(getTopChannelsForTeamSince)))).Methods("GET")
|
||||||
api.BaseRoutes.InsightsForUser.Handle("/channels", api.APISessionRequired(getTopChannelsForUserSince)).Methods("GET")
|
api.BaseRoutes.InsightsForUser.Handle("/channels", api.APISessionRequired(minimumProfessionalLicense(rejectGuests(getTopChannelsForUserSince)))).Methods("GET")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Top Reactions
|
// Top Reactions
|
||||||
@@ -39,14 +41,16 @@ func getTopReactionsForTeamSince(c *Context, w http.ResponseWriter, r *http.Requ
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
startTime, err := model.GetStartUnixMilliForTimeRange(c.Params.TimeRange)
|
user, err := c.App.GetUser(c.AppContext.Session().UserId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Err = err
|
c.Err = err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startTime := model.StartOfDayForTimeRange(c.Params.TimeRange, user.GetTimezoneLocation())
|
||||||
|
|
||||||
topReactionList, err := c.App.GetTopReactionsForTeamSince(c.Params.TeamId, c.AppContext.Session().UserId, &model.InsightsOpts{
|
topReactionList, err := c.App.GetTopReactionsForTeamSince(c.Params.TeamId, c.AppContext.Session().UserId, &model.InsightsOpts{
|
||||||
StartUnixMilli: startTime,
|
StartUnixMilli: startTime.UnixMilli(),
|
||||||
Page: c.Params.Page,
|
Page: c.Params.Page,
|
||||||
PerPage: c.Params.PerPage,
|
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 {
|
if err != nil {
|
||||||
c.Err = err
|
c.Err = err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startTime := model.StartOfDayForTimeRange(c.Params.TimeRange, user.GetTimezoneLocation())
|
||||||
|
|
||||||
topReactionList, err := c.App.GetTopReactionsForUserSince(c.AppContext.Session().UserId, c.Params.TeamId, &model.InsightsOpts{
|
topReactionList, err := c.App.GetTopReactionsForUserSince(c.AppContext.Session().UserId, c.Params.TeamId, &model.InsightsOpts{
|
||||||
StartUnixMilli: startTime,
|
StartUnixMilli: startTime.UnixMilli(),
|
||||||
Page: c.Params.Page,
|
Page: c.Params.Page,
|
||||||
PerPage: c.Params.PerPage,
|
PerPage: c.Params.PerPage,
|
||||||
})
|
})
|
||||||
@@ -130,14 +136,17 @@ func getTopChannelsForTeamSince(c *Context, w http.ResponseWriter, r *http.Reque
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
startTime, err := model.GetStartUnixMilliForTimeRange(c.Params.TimeRange)
|
user, err := c.App.GetUser(c.AppContext.Session().UserId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Err = err
|
c.Err = err
|
||||||
return
|
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{
|
topChannels, err := c.App.GetTopChannelsForTeamSince(c.Params.TeamId, c.AppContext.Session().UserId, &model.InsightsOpts{
|
||||||
StartUnixMilli: startTime,
|
StartUnixMilli: startTime.UnixMilli(),
|
||||||
Page: c.Params.Page,
|
Page: c.Params.Page,
|
||||||
PerPage: c.Params.PerPage,
|
PerPage: c.Params.PerPage,
|
||||||
})
|
})
|
||||||
@@ -146,6 +155,12 @@ func getTopChannelsForTeamSince(c *Context, w http.ResponseWriter, r *http.Reque
|
|||||||
return
|
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)
|
js, jsonErr := json.Marshal(topChannels)
|
||||||
if jsonErr != nil {
|
if jsonErr != nil {
|
||||||
c.Err = model.NewAppError("getTopChannelsForTeamSince", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
|
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 {
|
if err != nil {
|
||||||
c.Err = err
|
c.Err = err
|
||||||
return
|
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{
|
topChannels, err := c.App.GetTopChannelsForUserSince(c.AppContext.Session().UserId, c.Params.TeamId, &model.InsightsOpts{
|
||||||
StartUnixMilli: startTime,
|
StartUnixMilli: startTime.UnixMilli(),
|
||||||
Page: c.Params.Page,
|
Page: c.Params.Page,
|
||||||
PerPage: c.Params.PerPage,
|
PerPage: c.Params.PerPage,
|
||||||
})
|
})
|
||||||
@@ -194,6 +212,12 @@ func getTopChannelsForUserSince(c *Context, w http.ResponseWriter, r *http.Reque
|
|||||||
return
|
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)
|
js, jsonErr := json.Marshal(topChannels)
|
||||||
if jsonErr != nil {
|
if jsonErr != nil {
|
||||||
c.Err = model.NewAppError("getTopChannelsForUserSince", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
|
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)
|
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)
|
th.ConfigStore.SetReadOnlyFF(false)
|
||||||
defer th.ConfigStore.SetReadOnlyFF(true)
|
defer th.ConfigStore.SetReadOnlyFF(true)
|
||||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true })
|
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true })
|
||||||
|
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional))
|
||||||
|
|
||||||
client := th.Client
|
client := th.Client
|
||||||
|
|
||||||
@@ -246,6 +247,7 @@ func TestGetTopReactionsForUserSince(t *testing.T) {
|
|||||||
th.ConfigStore.SetReadOnlyFF(false)
|
th.ConfigStore.SetReadOnlyFF(false)
|
||||||
defer th.ConfigStore.SetReadOnlyFF(true)
|
defer th.ConfigStore.SetReadOnlyFF(true)
|
||||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true })
|
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true })
|
||||||
|
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional))
|
||||||
|
|
||||||
client := th.Client
|
client := th.Client
|
||||||
|
|
||||||
@@ -437,6 +439,7 @@ func TestGetTopChannelsForTeamSince(t *testing.T) {
|
|||||||
th.ConfigStore.SetReadOnlyFF(false)
|
th.ConfigStore.SetReadOnlyFF(false)
|
||||||
defer th.ConfigStore.SetReadOnlyFF(true)
|
defer th.ConfigStore.SetReadOnlyFF(true)
|
||||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true })
|
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true })
|
||||||
|
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional))
|
||||||
|
|
||||||
client := th.Client
|
client := th.Client
|
||||||
userId := th.BasicUser.Id
|
userId := th.BasicUser.Id
|
||||||
@@ -485,6 +488,10 @@ func TestGetTopChannelsForTeamSince(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, channel6.Id, topChannels.Items[0].ID)
|
assert.Equal(t, channel6.Id, topChannels.Items[0].ID)
|
||||||
assert.Equal(t, int64(1), topChannels.Items[0].MessageCount)
|
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) {
|
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)
|
th.ConfigStore.SetReadOnlyFF(false)
|
||||||
defer th.ConfigStore.SetReadOnlyFF(true)
|
defer th.ConfigStore.SetReadOnlyFF(true)
|
||||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true })
|
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true })
|
||||||
|
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional))
|
||||||
|
|
||||||
client := th.Client
|
client := th.Client
|
||||||
userId := th.BasicUser.Id
|
userId := th.BasicUser.Id
|
||||||
@@ -579,6 +587,10 @@ func TestGetTopChannelsForUserSince(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, channel6.Id, topChannels.Items[0].ID)
|
assert.Equal(t, channel6.Id, topChannels.Items[0].ID)
|
||||||
assert.Equal(t, int64(1), topChannels.Items[0].MessageCount)
|
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) {
|
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,
|
// PopulateWebConnConfig checks if the connection id already exists in the hub,
|
||||||
// and if so, accordingly populates the other fields of the webconn.
|
// and if so, accordingly populates the other fields of the webconn.
|
||||||
PopulateWebConnConfig(s *model.Session, cfg *WebConnConfig, seqVal string) (*WebConnConfig, error)
|
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
|
// PromoteGuestToUser Convert user's roles and all his membership's roles from
|
||||||
// guest roles to regular user roles.
|
// guest roles to regular user roles.
|
||||||
PromoteGuestToUser(c *request.Context, user *model.User, requestorId string) *model.AppError
|
PromoteGuestToUser(c *request.Context, user *model.User, requestorId string) *model.AppError
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v6/app/request"
|
"github.com/mattermost/mattermost-server/v6/app/request"
|
||||||
"github.com/mattermost/mattermost-server/v6/model"
|
"github.com/mattermost/mattermost-server/v6/model"
|
||||||
@@ -3417,3 +3418,19 @@ func (a *App) GetTopChannelsForUserSince(userID, teamID string, opts *model.Insi
|
|||||||
}
|
}
|
||||||
return topChannels, nil
|
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"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
@@ -2418,10 +2419,10 @@ func TestGetTopChannelsForTeamSince(t *testing.T) {
|
|||||||
{ID: channel5.Id, MessageCount: 2},
|
{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) {
|
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)
|
require.Nil(t, err)
|
||||||
|
|
||||||
for i, channel := range topChannels.Items {
|
for i, channel := range topChannels.Items {
|
||||||
@@ -2429,7 +2430,7 @@ func TestGetTopChannelsForTeamSince(t *testing.T) {
|
|||||||
assert.Equal(t, expectedTopChannels[i].MessageCount, channel.MessageCount)
|
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)
|
require.Nil(t, err)
|
||||||
assert.Equal(t, channel6.Id, topChannels.Items[0].ID)
|
assert.Equal(t, channel6.Id, topChannels.Items[0].ID)
|
||||||
assert.Equal(t, int64(1), topChannels.Items[0].MessageCount)
|
assert.Equal(t, int64(1), topChannels.Items[0].MessageCount)
|
||||||
@@ -2476,10 +2477,10 @@ func TestGetTopChannelsForUserSince(t *testing.T) {
|
|||||||
{ID: channel5.Id, MessageCount: 2},
|
{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) {
|
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)
|
require.Nil(t, err)
|
||||||
|
|
||||||
for i, channel := range topChannels.Items {
|
for i, channel := range topChannels.Items {
|
||||||
@@ -2487,9 +2488,132 @@ func TestGetTopChannelsForUserSince(t *testing.T) {
|
|||||||
assert.Equal(t, expectedTopChannels[i].MessageCount, channel.MessageCount)
|
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)
|
require.Nil(t, err)
|
||||||
assert.Equal(t, channel6.Id, topChannels.Items[0].ID)
|
assert.Equal(t, channel6.Id, topChannels.Items[0].ID)
|
||||||
assert.Equal(t, int64(1), topChannels.Items[0].MessageCount)
|
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
|
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 {
|
func (a *OpenTracingAppLayer) PostPatchWithProxyRemovedFromImageURLs(patch *model.PostPatch) *model.PostPatch {
|
||||||
origCtx := a.ctx
|
origCtx := a.ctx
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.PostPatchWithProxyRemovedFromImageURLs")
|
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[3] = &model.TopReaction{EmojiName: "sad", Count: int64(3)}
|
||||||
expectedTopReactions[4] = &model.TopReaction{EmojiName: "happy", Count: int64(2)}
|
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) {
|
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)
|
require.Nil(t, err)
|
||||||
reactions := topReactions.Items
|
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].EmojiName, reaction.EmojiName)
|
||||||
assert.Equal(t, expectedTopReactions[i].Count, reaction.Count)
|
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)
|
require.Nil(t, err)
|
||||||
reactions = topReactions.Items
|
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) {
|
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 })
|
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)
|
assert.NotNil(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -402,10 +402,10 @@ func TestGetTopReactionsForUserSince(t *testing.T) {
|
|||||||
expectedTopReactions[3] = &model.TopReaction{EmojiName: "heart", Count: int64(3)}
|
expectedTopReactions[3] = &model.TopReaction{EmojiName: "heart", Count: int64(3)}
|
||||||
expectedTopReactions[4] = &model.TopReaction{EmojiName: "blush", Count: int64(2)}
|
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) {
|
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)
|
require.Nil(t, err)
|
||||||
reactions := topReactions.Items
|
reactions := topReactions.Items
|
||||||
|
|
||||||
@@ -414,7 +414,7 @@ func TestGetTopReactionsForUserSince(t *testing.T) {
|
|||||||
assert.Equal(t, expectedTopReactions[i].Count, reaction.Count)
|
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)
|
require.Nil(t, err)
|
||||||
reactions = topReactions.Items
|
reactions = topReactions.Items
|
||||||
assert.Equal(t, "100", reactions[0].EmojiName)
|
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) {
|
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 })
|
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)
|
assert.NotNil(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
16
i18n/en.json
16
i18n/en.json
@@ -187,6 +187,10 @@
|
|||||||
"id": "api.admin.upload_brand_image.too_large.app_error",
|
"id": "api.admin.upload_brand_image.too_large.app_error",
|
||||||
"translation": "Unable to upload file. File is too large."
|
"translation": "Unable to upload file. File is too large."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "api.authorization_error.guest",
|
||||||
|
"translation": " "
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "api.back_to_app",
|
"id": "api.back_to_app",
|
||||||
"translation": "Back to {{.SiteName}}"
|
"translation": "Back to {{.SiteName}}"
|
||||||
@@ -2053,6 +2057,10 @@
|
|||||||
"id": "api.license_error",
|
"id": "api.license_error",
|
||||||
"translation": "api endpoint requires a license"
|
"translation": "api endpoint requires a license"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "api.license_error.professional_or_enterprise",
|
||||||
|
"translation": " "
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "api.marshal_error",
|
"id": "api.marshal_error",
|
||||||
"translation": "Failed to marshal."
|
"translation": "Failed to marshal."
|
||||||
@@ -4531,6 +4539,10 @@
|
|||||||
"id": "app.channel.get_pinnedpost_count.app_error",
|
"id": "app.channel.get_pinnedpost_count.app_error",
|
||||||
"translation": "Unable to get the channel pinned post count."
|
"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",
|
"id": "app.channel.get_private_channels.get.app_error",
|
||||||
"translation": "Unable to get private channels."
|
"translation": "Unable to get private channels."
|
||||||
@@ -8531,10 +8543,6 @@
|
|||||||
"id": "model.incoming_hook.username.app_error",
|
"id": "model.incoming_hook.username.app_error",
|
||||||
"translation": "Invalid username."
|
"translation": "Invalid username."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "model.insights.time_range.app_error",
|
|
||||||
"translation": " "
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "model.job.is_valid.create_at.app_error",
|
"id": "model.job.is_valid.create_at.app_error",
|
||||||
"translation": "Create at must be a valid time."
|
"translation": "Create at must be a valid time."
|
||||||
|
|||||||
@@ -4,14 +4,18 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type PostCountGrouping string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TimeRangeToday string = "today"
|
TimeRangeToday string = "today"
|
||||||
TimeRange7Day string = "7_day"
|
TimeRange7Day string = "7_day"
|
||||||
TimeRange28Day string = "28_day"
|
TimeRange28Day string = "28_day"
|
||||||
|
|
||||||
|
PostsByHour PostCountGrouping = "hour"
|
||||||
|
PostsByDay PostCountGrouping = "day"
|
||||||
)
|
)
|
||||||
|
|
||||||
type InsightsOpts struct {
|
type InsightsOpts struct {
|
||||||
@@ -38,7 +42,16 @@ type TopReaction struct {
|
|||||||
// Top Channels
|
// Top Channels
|
||||||
type TopChannelList struct {
|
type TopChannelList struct {
|
||||||
InsightsListData
|
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 {
|
type TopChannel struct {
|
||||||
@@ -50,21 +63,116 @@ type TopChannel struct {
|
|||||||
MessageCount int64 `json:"message_count"`
|
MessageCount int64 `json:"message_count"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStartUnixMilliForTimeRange gets the unix start time in milliseconds from the given time range.
|
type DurationPostCount struct {
|
||||||
// Time range can be one of: "1_day", "7_day", or "28_day".
|
ChannelID string `db:"channelid"`
|
||||||
func GetStartUnixMilliForTimeRange(timeRange string) (int64, *AppError) {
|
// Duration is an ISO8601 date string representing either a day or a day and hour (ex. "2022-05-26" or "2022-05-26T14").
|
||||||
now := time.Now()
|
Duration string `db:"duration"`
|
||||||
_, offset := now.Zone()
|
PostCount int `db:"postcount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TimeRangeToNumberDays(timeRange string) int {
|
||||||
|
var n int
|
||||||
switch timeRange {
|
switch timeRange {
|
||||||
case TimeRangeToday:
|
case TimeRangeToday:
|
||||||
return GetStartOfDayMillis(now, offset), nil
|
n = 1
|
||||||
case TimeRange7Day:
|
case TimeRange7Day:
|
||||||
return GetStartOfDayMillis(now.Add(time.Hour*time.Duration(-168)), offset), nil
|
n = 7
|
||||||
case TimeRange28Day:
|
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
|
// 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"
|
"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) {
|
func TestGetTopReactionListWithPagination(t *testing.T) {
|
||||||
reactions := []*TopReaction{
|
reactions := []*TopReaction{
|
||||||
{EmojiName: "smile", Count: 200},
|
{EmojiName: "smile", Count: 200},
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
@@ -782,6 +783,14 @@ func (u *User) GetPreferredTimezone() string {
|
|||||||
return GetPreferredTimezone(u.Timezone)
|
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).
|
// IsRemote returns true if the user belongs to a remote cluster (has RemoteId).
|
||||||
func (u *User) IsRemote() bool {
|
func (u *User) IsRemote() bool {
|
||||||
return u.RemoteId != nil && *u.RemoteId != ""
|
return u.RemoteId != nil && *u.RemoteId != ""
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ package opentracinglayer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v6/model"
|
"github.com/mattermost/mattermost-server/v6/model"
|
||||||
"github.com/mattermost/mattermost-server/v6/services/tracing"
|
"github.com/mattermost/mattermost-server/v6/services/tracing"
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ package retrylayer
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
timepkg "time"
|
timepkg "time"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
"github.com/mattermost/mattermost-server/v6/model"
|
"github.com/mattermost/mattermost-server/v6/model"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ package timerlayer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
timemodule "time"
|
"time"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v6/einterfaces"
|
"github.com/mattermost/mattermost-server/v6/einterfaces"
|
||||||
"github.com/mattermost/mattermost-server/v6/model"
|
"github.com/mattermost/mattermost-server/v6/model"
|
||||||
@@ -38,13 +38,13 @@ type {{.Name}} struct {
|
|||||||
{{range $substoreName, $substore := .SubStores}}
|
{{range $substoreName, $substore := .SubStores}}
|
||||||
{{range $index, $element := $substore.Methods}}
|
{{range $index, $element := $substore.Methods}}
|
||||||
func (s *{{$.Name}}{{$substoreName}}Store) {{$index}}({{$element.Params | joinParamsWithType}}) {{$element.Results | joinResultsForSignature}} {
|
func (s *{{$.Name}}{{$substoreName}}Store) {{$index}}({{$element.Params | joinParamsWithType}}) {{$element.Results | joinResultsForSignature}} {
|
||||||
start := timemodule.Now()
|
start := time.Now()
|
||||||
{{if $element.Results | len | eq 0}}
|
{{if $element.Results | len | eq 0}}
|
||||||
s.{{$substoreName}}Store.{{$index}}({{$element.Params | joinParams}})
|
s.{{$substoreName}}Store.{{$index}}({{$element.Params | joinParams}})
|
||||||
{{else}}
|
{{else}}
|
||||||
{{genResultsVars $element.Results false }} := s.{{$substoreName}}Store.{{$index}}({{$element.Params | joinParams}})
|
{{genResultsVars $element.Results false }} := s.{{$substoreName}}Store.{{$index}}({{$element.Params | joinParams}})
|
||||||
{{end}}
|
{{end}}
|
||||||
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
|
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||||
if s.Root.Metrics != nil {
|
if s.Root.Metrics != nil {
|
||||||
success := "false"
|
success := "false"
|
||||||
if {{$element.Results | errorToBoolean}} {
|
if {{$element.Results | errorToBoolean}} {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ package opentracinglayer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v6/model"
|
"github.com/mattermost/mattermost-server/v6/model"
|
||||||
"github.com/mattermost/mattermost-server/v6/services/tracing"
|
"github.com/mattermost/mattermost-server/v6/services/tracing"
|
||||||
@@ -745,7 +746,7 @@ func (s *OpenTracingLayerChannelStore) CreateSidebarCategory(userID string, team
|
|||||||
return result, err
|
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()
|
origCtx := s.Root.Store.Context()
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.Delete")
|
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.Delete")
|
||||||
s.Root.Store.SetContext(newCtx)
|
s.Root.Store.SetContext(newCtx)
|
||||||
@@ -754,7 +755,7 @@ func (s *OpenTracingLayerChannelStore) Delete(channelID string, time int64) erro
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
err := s.ChannelStore.Delete(channelID, time)
|
err := s.ChannelStore.Delete(channelID, timestamp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
span.LogFields(spanlog.Error(err))
|
span.LogFields(spanlog.Error(err))
|
||||||
ext.Error.Set(span, true)
|
ext.Error.Set(span, true)
|
||||||
@@ -2014,6 +2015,24 @@ func (s *OpenTracingLayerChannelStore) PermanentDeleteMembersByUser(userID strin
|
|||||||
return err
|
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 {
|
func (s *OpenTracingLayerChannelStore) RemoveAllDeactivatedMembers(channelID string) error {
|
||||||
origCtx := s.Root.Store.Context()
|
origCtx := s.Root.Store.Context()
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.RemoveAllDeactivatedMembers")
|
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.RemoveAllDeactivatedMembers")
|
||||||
@@ -2086,7 +2105,7 @@ func (s *OpenTracingLayerChannelStore) ResetAllChannelSchemes() error {
|
|||||||
return err
|
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()
|
origCtx := s.Root.Store.Context()
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.Restore")
|
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.Restore")
|
||||||
s.Root.Store.SetContext(newCtx)
|
s.Root.Store.SetContext(newCtx)
|
||||||
@@ -2095,7 +2114,7 @@ func (s *OpenTracingLayerChannelStore) Restore(channelID string, time int64) err
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
err := s.ChannelStore.Restore(channelID, time)
|
err := s.ChannelStore.Restore(channelID, timestamp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
span.LogFields(spanlog.Error(err))
|
span.LogFields(spanlog.Error(err))
|
||||||
ext.Error.Set(span, true)
|
ext.Error.Set(span, true)
|
||||||
@@ -2788,7 +2807,7 @@ func (s *OpenTracingLayerCommandStore) AnalyticsCommandCount(teamID string) (int
|
|||||||
return result, err
|
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()
|
origCtx := s.Root.Store.Context()
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "CommandStore.Delete")
|
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "CommandStore.Delete")
|
||||||
s.Root.Store.SetContext(newCtx)
|
s.Root.Store.SetContext(newCtx)
|
||||||
@@ -2797,7 +2816,7 @@ func (s *OpenTracingLayerCommandStore) Delete(commandID string, time int64) erro
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
err := s.CommandStore.Delete(commandID, time)
|
err := s.CommandStore.Delete(commandID, timestamp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
span.LogFields(spanlog.Error(err))
|
span.LogFields(spanlog.Error(err))
|
||||||
ext.Error.Set(span, true)
|
ext.Error.Set(span, true)
|
||||||
@@ -3107,7 +3126,7 @@ func (s *OpenTracingLayerComplianceStore) Update(compliance *model.Compliance) (
|
|||||||
return result, err
|
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()
|
origCtx := s.Root.Store.Context()
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "EmojiStore.Delete")
|
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "EmojiStore.Delete")
|
||||||
s.Root.Store.SetContext(newCtx)
|
s.Root.Store.SetContext(newCtx)
|
||||||
@@ -3116,7 +3135,7 @@ func (s *OpenTracingLayerEmojiStore) Delete(emoji *model.Emoji, time int64) erro
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
err := s.EmojiStore.Delete(emoji, time)
|
err := s.EmojiStore.Delete(emoji, timestamp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
span.LogFields(spanlog.Error(err))
|
span.LogFields(spanlog.Error(err))
|
||||||
ext.Error.Set(span, true)
|
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()
|
origCtx := s.Root.Store.Context()
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.Delete")
|
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.Delete")
|
||||||
s.Root.Store.SetContext(newCtx)
|
s.Root.Store.SetContext(newCtx)
|
||||||
@@ -5459,7 +5478,7 @@ func (s *OpenTracingLayerPostStore) Delete(postID string, time int64, deleteByID
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
err := s.PostStore.Delete(postID, time, deleteByID)
|
err := s.PostStore.Delete(postID, timestamp, deleteByID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
span.LogFields(spanlog.Error(err))
|
span.LogFields(spanlog.Error(err))
|
||||||
ext.Error.Set(span, true)
|
ext.Error.Set(span, true)
|
||||||
@@ -5682,7 +5701,7 @@ func (s *OpenTracingLayerPostStore) GetParentsForExportAfter(limit int, afterID
|
|||||||
return result, err
|
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()
|
origCtx := s.Root.Store.Context()
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.GetPostAfterTime")
|
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.GetPostAfterTime")
|
||||||
s.Root.Store.SetContext(newCtx)
|
s.Root.Store.SetContext(newCtx)
|
||||||
@@ -5691,7 +5710,7 @@ func (s *OpenTracingLayerPostStore) GetPostAfterTime(channelID string, time int6
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
result, err := s.PostStore.GetPostAfterTime(channelID, time, collapsedThreads)
|
result, err := s.PostStore.GetPostAfterTime(channelID, timestamp, collapsedThreads)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
span.LogFields(spanlog.Error(err))
|
span.LogFields(spanlog.Error(err))
|
||||||
ext.Error.Set(span, true)
|
ext.Error.Set(span, true)
|
||||||
@@ -5700,7 +5719,7 @@ func (s *OpenTracingLayerPostStore) GetPostAfterTime(channelID string, time int6
|
|||||||
return result, err
|
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()
|
origCtx := s.Root.Store.Context()
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.GetPostIdAfterTime")
|
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.GetPostIdAfterTime")
|
||||||
s.Root.Store.SetContext(newCtx)
|
s.Root.Store.SetContext(newCtx)
|
||||||
@@ -5709,7 +5728,7 @@ func (s *OpenTracingLayerPostStore) GetPostIdAfterTime(channelID string, time in
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
result, err := s.PostStore.GetPostIdAfterTime(channelID, time, collapsedThreads)
|
result, err := s.PostStore.GetPostIdAfterTime(channelID, timestamp, collapsedThreads)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
span.LogFields(spanlog.Error(err))
|
span.LogFields(spanlog.Error(err))
|
||||||
ext.Error.Set(span, true)
|
ext.Error.Set(span, true)
|
||||||
@@ -5718,7 +5737,7 @@ func (s *OpenTracingLayerPostStore) GetPostIdAfterTime(channelID string, time in
|
|||||||
return result, err
|
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()
|
origCtx := s.Root.Store.Context()
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.GetPostIdBeforeTime")
|
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.GetPostIdBeforeTime")
|
||||||
s.Root.Store.SetContext(newCtx)
|
s.Root.Store.SetContext(newCtx)
|
||||||
@@ -5727,7 +5746,7 @@ func (s *OpenTracingLayerPostStore) GetPostIdBeforeTime(channelID string, time i
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
result, err := s.PostStore.GetPostIdBeforeTime(channelID, time, collapsedThreads)
|
result, err := s.PostStore.GetPostIdBeforeTime(channelID, timestamp, collapsedThreads)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
span.LogFields(spanlog.Error(err))
|
span.LogFields(spanlog.Error(err))
|
||||||
ext.Error.Set(span, true)
|
ext.Error.Set(span, true)
|
||||||
@@ -5826,7 +5845,7 @@ func (s *OpenTracingLayerPostStore) GetPostsByIds(postIds []string) ([]*model.Po
|
|||||||
return result, err
|
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()
|
origCtx := s.Root.Store.Context()
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.GetPostsCreatedAt")
|
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.GetPostsCreatedAt")
|
||||||
s.Root.Store.SetContext(newCtx)
|
s.Root.Store.SetContext(newCtx)
|
||||||
@@ -5835,7 +5854,7 @@ func (s *OpenTracingLayerPostStore) GetPostsCreatedAt(channelID string, time int
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
result, err := s.PostStore.GetPostsCreatedAt(channelID, time)
|
result, err := s.PostStore.GetPostsCreatedAt(channelID, timestamp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
span.LogFields(spanlog.Error(err))
|
span.LogFields(spanlog.Error(err))
|
||||||
ext.Error.Set(span, true)
|
ext.Error.Set(span, true)
|
||||||
@@ -7639,7 +7658,7 @@ func (s *OpenTracingLayerSessionStore) UpdateExpiredNotify(sessionid string, not
|
|||||||
return err
|
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()
|
origCtx := s.Root.Store.Context()
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "SessionStore.UpdateExpiresAt")
|
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "SessionStore.UpdateExpiresAt")
|
||||||
s.Root.Store.SetContext(newCtx)
|
s.Root.Store.SetContext(newCtx)
|
||||||
@@ -7648,7 +7667,7 @@ func (s *OpenTracingLayerSessionStore) UpdateExpiresAt(sessionID string, time in
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
err := s.SessionStore.UpdateExpiresAt(sessionID, time)
|
err := s.SessionStore.UpdateExpiresAt(sessionID, timestamp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
span.LogFields(spanlog.Error(err))
|
span.LogFields(spanlog.Error(err))
|
||||||
ext.Error.Set(span, true)
|
ext.Error.Set(span, true)
|
||||||
@@ -7657,7 +7676,7 @@ func (s *OpenTracingLayerSessionStore) UpdateExpiresAt(sessionID string, time in
|
|||||||
return err
|
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()
|
origCtx := s.Root.Store.Context()
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "SessionStore.UpdateLastActivityAt")
|
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "SessionStore.UpdateLastActivityAt")
|
||||||
s.Root.Store.SetContext(newCtx)
|
s.Root.Store.SetContext(newCtx)
|
||||||
@@ -7666,7 +7685,7 @@ func (s *OpenTracingLayerSessionStore) UpdateLastActivityAt(sessionID string, ti
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
err := s.SessionStore.UpdateLastActivityAt(sessionID, time)
|
err := s.SessionStore.UpdateLastActivityAt(sessionID, timestamp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
span.LogFields(spanlog.Error(err))
|
span.LogFields(spanlog.Error(err))
|
||||||
ext.Error.Set(span, true)
|
ext.Error.Set(span, true)
|
||||||
@@ -10000,7 +10019,7 @@ func (s *OpenTracingLayerUploadSessionStore) Update(session *model.UploadSession
|
|||||||
return err
|
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()
|
origCtx := s.Root.Store.Context()
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "UserStore.AnalyticsActiveCount")
|
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "UserStore.AnalyticsActiveCount")
|
||||||
s.Root.Store.SetContext(newCtx)
|
s.Root.Store.SetContext(newCtx)
|
||||||
@@ -10009,7 +10028,7 @@ func (s *OpenTracingLayerUserStore) AnalyticsActiveCount(time int64, options mod
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
result, err := s.UserStore.AnalyticsActiveCount(time, options)
|
result, err := s.UserStore.AnalyticsActiveCount(timestamp, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
span.LogFields(spanlog.Error(err))
|
span.LogFields(spanlog.Error(err))
|
||||||
ext.Error.Set(span, true)
|
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()
|
origCtx := s.Root.Store.Context()
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "WebhookStore.DeleteIncoming")
|
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "WebhookStore.DeleteIncoming")
|
||||||
s.Root.Store.SetContext(newCtx)
|
s.Root.Store.SetContext(newCtx)
|
||||||
@@ -11607,7 +11626,7 @@ func (s *OpenTracingLayerWebhookStore) DeleteIncoming(webhookID string, time int
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
err := s.WebhookStore.DeleteIncoming(webhookID, time)
|
err := s.WebhookStore.DeleteIncoming(webhookID, timestamp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
span.LogFields(spanlog.Error(err))
|
span.LogFields(spanlog.Error(err))
|
||||||
ext.Error.Set(span, true)
|
ext.Error.Set(span, true)
|
||||||
@@ -11616,7 +11635,7 @@ func (s *OpenTracingLayerWebhookStore) DeleteIncoming(webhookID string, time int
|
|||||||
return err
|
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()
|
origCtx := s.Root.Store.Context()
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "WebhookStore.DeleteOutgoing")
|
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "WebhookStore.DeleteOutgoing")
|
||||||
s.Root.Store.SetContext(newCtx)
|
s.Root.Store.SetContext(newCtx)
|
||||||
@@ -11625,7 +11644,7 @@ func (s *OpenTracingLayerWebhookStore) DeleteOutgoing(webhookID string, time int
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
err := s.WebhookStore.DeleteOutgoing(webhookID, time)
|
err := s.WebhookStore.DeleteOutgoing(webhookID, timestamp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
span.LogFields(spanlog.Error(err))
|
span.LogFields(spanlog.Error(err))
|
||||||
ext.Error.Set(span, true)
|
ext.Error.Set(span, true)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ package retrylayer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
timepkg "time"
|
timepkg "time"
|
||||||
|
|
||||||
"github.com/go-sql-driver/mysql"
|
"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
|
tries := 0
|
||||||
for {
|
for {
|
||||||
err := s.ChannelStore.Delete(channelID, time)
|
err := s.ChannelStore.Delete(channelID, timestamp)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return 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 {
|
func (s *RetryLayerChannelStore) RemoveAllDeactivatedMembers(channelID string) error {
|
||||||
|
|
||||||
tries := 0
|
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
|
tries := 0
|
||||||
for {
|
for {
|
||||||
err := s.ChannelStore.Restore(channelID, time)
|
err := s.ChannelStore.Restore(channelID, timestamp)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return 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
|
tries := 0
|
||||||
for {
|
for {
|
||||||
err := s.CommandStore.Delete(commandID, time)
|
err := s.CommandStore.Delete(commandID, timestamp)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return 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
|
tries := 0
|
||||||
for {
|
for {
|
||||||
err := s.EmojiStore.Delete(emoji, time)
|
err := s.EmojiStore.Delete(emoji, timestamp)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return 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
|
tries := 0
|
||||||
for {
|
for {
|
||||||
err := s.PostStore.Delete(postID, time, deleteByID)
|
err := s.PostStore.Delete(postID, timestamp, deleteByID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return 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
|
tries := 0
|
||||||
for {
|
for {
|
||||||
result, err := s.PostStore.GetPostAfterTime(channelID, time, collapsedThreads)
|
result, err := s.PostStore.GetPostAfterTime(channelID, timestamp, collapsedThreads)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return result, 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
|
tries := 0
|
||||||
for {
|
for {
|
||||||
result, err := s.PostStore.GetPostIdAfterTime(channelID, time, collapsedThreads)
|
result, err := s.PostStore.GetPostIdAfterTime(channelID, timestamp, collapsedThreads)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return result, 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
|
tries := 0
|
||||||
for {
|
for {
|
||||||
result, err := s.PostStore.GetPostIdBeforeTime(channelID, time, collapsedThreads)
|
result, err := s.PostStore.GetPostIdBeforeTime(channelID, timestamp, collapsedThreads)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return result, 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
|
tries := 0
|
||||||
for {
|
for {
|
||||||
result, err := s.PostStore.GetPostsCreatedAt(channelID, time)
|
result, err := s.PostStore.GetPostsCreatedAt(channelID, timestamp)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return result, 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
|
tries := 0
|
||||||
for {
|
for {
|
||||||
err := s.SessionStore.UpdateExpiresAt(sessionID, time)
|
err := s.SessionStore.UpdateExpiresAt(sessionID, timestamp)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return 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
|
tries := 0
|
||||||
for {
|
for {
|
||||||
err := s.SessionStore.UpdateLastActivityAt(sessionID, time)
|
err := s.SessionStore.UpdateLastActivityAt(sessionID, timestamp)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return 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
|
tries := 0
|
||||||
for {
|
for {
|
||||||
result, err := s.UserStore.AnalyticsActiveCount(time, options)
|
result, err := s.UserStore.AnalyticsActiveCount(timestamp, options)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return result, 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
|
tries := 0
|
||||||
for {
|
for {
|
||||||
err := s.WebhookStore.DeleteIncoming(webhookID, time)
|
err := s.WebhookStore.DeleteIncoming(webhookID, timestamp)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return 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
|
tries := 0
|
||||||
for {
|
for {
|
||||||
err := s.WebhookStore.DeleteOutgoing(webhookID, time)
|
err := s.WebhookStore.DeleteOutgoing(webhookID, timestamp)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4297,3 +4297,53 @@ func (s SqlChannelStore) GetTopChannelsForUserSince(userID string, teamID string
|
|||||||
|
|
||||||
return model.GetTopChannelListWithPagination(channels, limit), nil
|
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)
|
GetMany(ids []string, allowFromCache bool) (model.ChannelList, error)
|
||||||
InvalidateChannel(id string)
|
InvalidateChannel(id string)
|
||||||
InvalidateChannelByName(teamID, name string)
|
InvalidateChannelByName(teamID, name string)
|
||||||
Delete(channelID string, time int64) error
|
Delete(channelID string, timestamp int64) error
|
||||||
Restore(channelID string, time int64) error
|
Restore(channelID string, timestamp int64) error
|
||||||
SetDeleteAt(channelID string, deleteAt int64, updateAt int64) error
|
SetDeleteAt(channelID string, deleteAt int64, updateAt int64) error
|
||||||
PermanentDelete(channelID string) error
|
PermanentDelete(channelID string) error
|
||||||
PermanentDeleteByTeam(teamID string) error
|
PermanentDeleteByTeam(teamID string) error
|
||||||
@@ -293,6 +293,7 @@ type ChannelStore interface {
|
|||||||
// Insights
|
// Insights
|
||||||
GetTopChannelsForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopChannelList, error)
|
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)
|
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 {
|
type ChannelMemberHistoryStore interface {
|
||||||
@@ -338,7 +339,7 @@ type PostStore interface {
|
|||||||
Update(newPost *model.Post, oldPost *model.Post) (*model.Post, error)
|
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)
|
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)
|
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
|
PermanentDeleteByUser(userID string) error
|
||||||
PermanentDeleteByChannel(channelID string) error
|
PermanentDeleteByChannel(channelID string) error
|
||||||
GetPosts(options model.GetPostsOptions, allowFromCache bool, sanitizeOptions map[string]bool) (*model.PostList, 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)
|
GetPostsBefore(options model.GetPostsOptions, sanitizeOptions map[string]bool) (*model.PostList, error)
|
||||||
GetPostsAfter(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)
|
GetPostsSince(options model.GetPostsSinceOptions, allowFromCache bool, sanitizeOptions map[string]bool) (*model.PostList, error)
|
||||||
GetPostAfterTime(channelID string, time int64, collapsedThreads bool) (*model.Post, error)
|
GetPostAfterTime(channelID string, timestamp int64, collapsedThreads bool) (*model.Post, error)
|
||||||
GetPostIdAfterTime(channelID string, time int64, collapsedThreads bool) (string, error)
|
GetPostIdAfterTime(channelID string, timestamp int64, collapsedThreads bool) (string, error)
|
||||||
GetPostIdBeforeTime(channelID string, time int64, collapsedThreads bool) (string, error)
|
GetPostIdBeforeTime(channelID string, timestamp int64, collapsedThreads bool) (string, error)
|
||||||
GetEtag(channelID string, allowFromCache bool, collapsedThreads bool) string
|
GetEtag(channelID string, allowFromCache bool, collapsedThreads bool) string
|
||||||
Search(teamID string, userID string, params *model.SearchParams) (*model.PostList, error)
|
Search(teamID string, userID string, params *model.SearchParams) (*model.PostList, error)
|
||||||
AnalyticsUserCountsWithPostsByDay(teamID string) (model.AnalyticsRows, error)
|
AnalyticsUserCountsWithPostsByDay(teamID string) (model.AnalyticsRows, error)
|
||||||
@@ -360,7 +361,7 @@ type PostStore interface {
|
|||||||
ClearCaches()
|
ClearCaches()
|
||||||
InvalidateLastPostTimeCache(channelID string)
|
InvalidateLastPostTimeCache(channelID string)
|
||||||
GetLastPostRowCreateAt() (int64, error)
|
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)
|
Overwrite(post *model.Post) (*model.Post, error)
|
||||||
OverwriteMultiple(posts []*model.Post) ([]*model.Post, int, error)
|
OverwriteMultiple(posts []*model.Post) ([]*model.Post, int, error)
|
||||||
GetPostsByIds(postIds []string) ([]*model.Post, error)
|
GetPostsByIds(postIds []string) ([]*model.Post, error)
|
||||||
@@ -422,7 +423,7 @@ type UserStore interface {
|
|||||||
UpdateFailedPasswordAttempts(userID string, attempts int) error
|
UpdateFailedPasswordAttempts(userID string, attempts int) error
|
||||||
GetSystemAdminProfiles() (map[string]*model.User, error)
|
GetSystemAdminProfiles() (map[string]*model.User, error)
|
||||||
PermanentDelete(userID string) 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)
|
AnalyticsActiveCountForPeriod(startTime int64, endTime int64, options model.UserCountOptions) (int64, error)
|
||||||
GetUnreadCount(userID string) (int64, error)
|
GetUnreadCount(userID string) (int64, error)
|
||||||
GetUnreadCountForChannel(userID string, channelID string) (int64, error)
|
GetUnreadCountForChannel(userID string, channelID string) (int64, error)
|
||||||
@@ -478,8 +479,8 @@ type SessionStore interface {
|
|||||||
RemoveAllSessions() error
|
RemoveAllSessions() error
|
||||||
PermanentDeleteSessionsByUser(teamID string) error
|
PermanentDeleteSessionsByUser(teamID string) error
|
||||||
GetLastSessionRowCreateAt() (int64, error)
|
GetLastSessionRowCreateAt() (int64, error)
|
||||||
UpdateExpiresAt(sessionID string, time int64) error
|
UpdateExpiresAt(sessionID string, timestamp int64) error
|
||||||
UpdateLastActivityAt(sessionID string, time int64) error
|
UpdateLastActivityAt(sessionID string, timestamp int64) error
|
||||||
UpdateRoles(userID string, roles string) (string, error)
|
UpdateRoles(userID string, roles string) (string, error)
|
||||||
UpdateDeviceId(id string, deviceID string, expiresAt int64) (string, error)
|
UpdateDeviceId(id string, deviceID string, expiresAt int64) (string, error)
|
||||||
UpdateProps(session *model.Session) error
|
UpdateProps(session *model.Session) error
|
||||||
@@ -563,7 +564,7 @@ type WebhookStore interface {
|
|||||||
GetIncomingByTeamByUser(teamID string, userID string, offset, limit int) ([]*model.IncomingWebhook, error)
|
GetIncomingByTeamByUser(teamID string, userID string, offset, limit int) ([]*model.IncomingWebhook, error)
|
||||||
UpdateIncoming(webhook *model.IncomingWebhook) (*model.IncomingWebhook, error)
|
UpdateIncoming(webhook *model.IncomingWebhook) (*model.IncomingWebhook, error)
|
||||||
GetIncomingByChannel(channelID string) ([]*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
|
PermanentDeleteIncomingByChannel(channelID string) error
|
||||||
PermanentDeleteIncomingByUser(userID string) error
|
PermanentDeleteIncomingByUser(userID string) error
|
||||||
|
|
||||||
@@ -575,7 +576,7 @@ type WebhookStore interface {
|
|||||||
GetOutgoingListByUser(userID string, offset, limit int) ([]*model.OutgoingWebhook, error)
|
GetOutgoingListByUser(userID string, offset, limit int) ([]*model.OutgoingWebhook, error)
|
||||||
GetOutgoingByTeam(teamID 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)
|
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
|
PermanentDeleteOutgoingByChannel(channelID string) error
|
||||||
PermanentDeleteOutgoingByUser(userID string) error
|
PermanentDeleteOutgoingByUser(userID string) error
|
||||||
UpdateOutgoing(hook *model.OutgoingWebhook) (*model.OutgoingWebhook, error)
|
UpdateOutgoing(hook *model.OutgoingWebhook) (*model.OutgoingWebhook, error)
|
||||||
@@ -591,7 +592,7 @@ type CommandStore interface {
|
|||||||
GetByTrigger(teamID string, trigger string) (*model.Command, error)
|
GetByTrigger(teamID string, trigger string) (*model.Command, error)
|
||||||
Get(id string) (*model.Command, error)
|
Get(id string) (*model.Command, error)
|
||||||
GetByTeam(teamID 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
|
PermanentDeleteByTeam(teamID string) error
|
||||||
PermanentDeleteByUser(userID string) error
|
PermanentDeleteByUser(userID string) error
|
||||||
Update(hook *model.Command) (*model.Command, 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)
|
GetByName(ctx context.Context, name string, allowFromCache bool) (*model.Emoji, error)
|
||||||
GetMultipleByName(names []string) ([]*model.Emoji, error)
|
GetMultipleByName(names []string) ([]*model.Emoji, error)
|
||||||
GetList(offset, limit int, sort 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)
|
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("UpdateSidebarChannelsByPreferences", func(t *testing.T) { testUpdateSidebarChannelsByPreferences(t, ss) })
|
||||||
t.Run("SetShared", func(t *testing.T) { testSetShared(t, ss) })
|
t.Run("SetShared", func(t *testing.T) { testSetShared(t, ss) })
|
||||||
t.Run("GetTeamForChannel", func(t *testing.T) { testGetTeamForChannel(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) {
|
func testChannelStoreSave(t *testing.T, ss store.Store) {
|
||||||
@@ -7904,3 +7905,38 @@ func testGetTeamForChannel(t *testing.T, ss store.Store) {
|
|||||||
var nfErr *store.ErrNotFound
|
var nfErr *store.ErrNotFound
|
||||||
require.True(t, errors.As(err, &nfErr))
|
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"
|
mock "github.com/stretchr/testify/mock"
|
||||||
|
|
||||||
store "github.com/mattermost/mattermost-server/v6/store"
|
store "github.com/mattermost/mattermost-server/v6/store"
|
||||||
|
|
||||||
|
time "time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ChannelStore is an autogenerated mock type for the ChannelStore type
|
// 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
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete provides a mock function with given fields: channelID, time
|
// Delete provides a mock function with given fields: channelID, timestamp
|
||||||
func (_m *ChannelStore) Delete(channelID string, time int64) error {
|
func (_m *ChannelStore) Delete(channelID string, timestamp int64) error {
|
||||||
ret := _m.Called(channelID, time)
|
ret := _m.Called(channelID, timestamp)
|
||||||
|
|
||||||
var r0 error
|
var r0 error
|
||||||
if rf, ok := ret.Get(0).(func(string, int64) error); ok {
|
if rf, ok := ret.Get(0).(func(string, int64) error); ok {
|
||||||
r0 = rf(channelID, time)
|
r0 = rf(channelID, timestamp)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Error(0)
|
r0 = ret.Error(0)
|
||||||
}
|
}
|
||||||
@@ -1717,6 +1719,29 @@ func (_m *ChannelStore) PermanentDeleteMembersByUser(userID string) error {
|
|||||||
return r0
|
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
|
// RemoveAllDeactivatedMembers provides a mock function with given fields: channelID
|
||||||
func (_m *ChannelStore) RemoveAllDeactivatedMembers(channelID string) error {
|
func (_m *ChannelStore) RemoveAllDeactivatedMembers(channelID string) error {
|
||||||
ret := _m.Called(channelID)
|
ret := _m.Called(channelID)
|
||||||
@@ -1773,13 +1798,13 @@ func (_m *ChannelStore) ResetAllChannelSchemes() error {
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore provides a mock function with given fields: channelID, time
|
// Restore provides a mock function with given fields: channelID, timestamp
|
||||||
func (_m *ChannelStore) Restore(channelID string, time int64) error {
|
func (_m *ChannelStore) Restore(channelID string, timestamp int64) error {
|
||||||
ret := _m.Called(channelID, time)
|
ret := _m.Called(channelID, timestamp)
|
||||||
|
|
||||||
var r0 error
|
var r0 error
|
||||||
if rf, ok := ret.Get(0).(func(string, int64) error); ok {
|
if rf, ok := ret.Get(0).(func(string, int64) error); ok {
|
||||||
r0 = rf(channelID, time)
|
r0 = rf(channelID, timestamp)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Error(0)
|
r0 = ret.Error(0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,13 +35,13 @@ func (_m *CommandStore) AnalyticsCommandCount(teamID string) (int64, error) {
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete provides a mock function with given fields: commandID, time
|
// Delete provides a mock function with given fields: commandID, timestamp
|
||||||
func (_m *CommandStore) Delete(commandID string, time int64) error {
|
func (_m *CommandStore) Delete(commandID string, timestamp int64) error {
|
||||||
ret := _m.Called(commandID, time)
|
ret := _m.Called(commandID, timestamp)
|
||||||
|
|
||||||
var r0 error
|
var r0 error
|
||||||
if rf, ok := ret.Get(0).(func(string, int64) error); ok {
|
if rf, ok := ret.Get(0).(func(string, int64) error); ok {
|
||||||
r0 = rf(commandID, time)
|
r0 = rf(commandID, timestamp)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Error(0)
|
r0 = ret.Error(0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,13 +16,13 @@ type EmojiStore struct {
|
|||||||
mock.Mock
|
mock.Mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete provides a mock function with given fields: emoji, time
|
// Delete provides a mock function with given fields: emoji, timestamp
|
||||||
func (_m *EmojiStore) Delete(emoji *model.Emoji, time int64) error {
|
func (_m *EmojiStore) Delete(emoji *model.Emoji, timestamp int64) error {
|
||||||
ret := _m.Called(emoji, time)
|
ret := _m.Called(emoji, timestamp)
|
||||||
|
|
||||||
var r0 error
|
var r0 error
|
||||||
if rf, ok := ret.Get(0).(func(*model.Emoji, int64) error); ok {
|
if rf, ok := ret.Get(0).(func(*model.Emoji, int64) error); ok {
|
||||||
r0 = rf(emoji, time)
|
r0 = rf(emoji, timestamp)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Error(0)
|
r0 = ret.Error(0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,13 +88,13 @@ func (_m *PostStore) ClearCaches() {
|
|||||||
_m.Called()
|
_m.Called()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete provides a mock function with given fields: postID, time, deleteByID
|
// Delete provides a mock function with given fields: postID, timestamp, deleteByID
|
||||||
func (_m *PostStore) Delete(postID string, time int64, deleteByID string) error {
|
func (_m *PostStore) Delete(postID string, timestamp int64, deleteByID string) error {
|
||||||
ret := _m.Called(postID, time, deleteByID)
|
ret := _m.Called(postID, timestamp, deleteByID)
|
||||||
|
|
||||||
var r0 error
|
var r0 error
|
||||||
if rf, ok := ret.Get(0).(func(string, int64, string) error); ok {
|
if rf, ok := ret.Get(0).(func(string, int64, string) error); ok {
|
||||||
r0 = rf(postID, time, deleteByID)
|
r0 = rf(postID, timestamp, deleteByID)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Error(0)
|
r0 = ret.Error(0)
|
||||||
}
|
}
|
||||||
@@ -354,13 +354,13 @@ func (_m *PostStore) GetParentsForExportAfter(limit int, afterID string) ([]*mod
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPostAfterTime provides a mock function with given fields: channelID, time, collapsedThreads
|
// GetPostAfterTime provides a mock function with given fields: channelID, timestamp, collapsedThreads
|
||||||
func (_m *PostStore) GetPostAfterTime(channelID string, time int64, collapsedThreads bool) (*model.Post, error) {
|
func (_m *PostStore) GetPostAfterTime(channelID string, timestamp int64, collapsedThreads bool) (*model.Post, error) {
|
||||||
ret := _m.Called(channelID, time, collapsedThreads)
|
ret := _m.Called(channelID, timestamp, collapsedThreads)
|
||||||
|
|
||||||
var r0 *model.Post
|
var r0 *model.Post
|
||||||
if rf, ok := ret.Get(0).(func(string, int64, bool) *model.Post); ok {
|
if rf, ok := ret.Get(0).(func(string, int64, bool) *model.Post); ok {
|
||||||
r0 = rf(channelID, time, collapsedThreads)
|
r0 = rf(channelID, timestamp, collapsedThreads)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
if ret.Get(0) != nil {
|
||||||
r0 = ret.Get(0).(*model.Post)
|
r0 = ret.Get(0).(*model.Post)
|
||||||
@@ -369,7 +369,7 @@ func (_m *PostStore) GetPostAfterTime(channelID string, time int64, collapsedThr
|
|||||||
|
|
||||||
var r1 error
|
var r1 error
|
||||||
if rf, ok := ret.Get(1).(func(string, int64, bool) error); ok {
|
if rf, ok := ret.Get(1).(func(string, int64, bool) error); ok {
|
||||||
r1 = rf(channelID, time, collapsedThreads)
|
r1 = rf(channelID, timestamp, collapsedThreads)
|
||||||
} else {
|
} else {
|
||||||
r1 = ret.Error(1)
|
r1 = ret.Error(1)
|
||||||
}
|
}
|
||||||
@@ -377,20 +377,20 @@ func (_m *PostStore) GetPostAfterTime(channelID string, time int64, collapsedThr
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPostIdAfterTime provides a mock function with given fields: channelID, time, collapsedThreads
|
// GetPostIdAfterTime provides a mock function with given fields: channelID, timestamp, collapsedThreads
|
||||||
func (_m *PostStore) GetPostIdAfterTime(channelID string, time int64, collapsedThreads bool) (string, error) {
|
func (_m *PostStore) GetPostIdAfterTime(channelID string, timestamp int64, collapsedThreads bool) (string, error) {
|
||||||
ret := _m.Called(channelID, time, collapsedThreads)
|
ret := _m.Called(channelID, timestamp, collapsedThreads)
|
||||||
|
|
||||||
var r0 string
|
var r0 string
|
||||||
if rf, ok := ret.Get(0).(func(string, int64, bool) string); ok {
|
if rf, ok := ret.Get(0).(func(string, int64, bool) string); ok {
|
||||||
r0 = rf(channelID, time, collapsedThreads)
|
r0 = rf(channelID, timestamp, collapsedThreads)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Get(0).(string)
|
r0 = ret.Get(0).(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
var r1 error
|
var r1 error
|
||||||
if rf, ok := ret.Get(1).(func(string, int64, bool) error); ok {
|
if rf, ok := ret.Get(1).(func(string, int64, bool) error); ok {
|
||||||
r1 = rf(channelID, time, collapsedThreads)
|
r1 = rf(channelID, timestamp, collapsedThreads)
|
||||||
} else {
|
} else {
|
||||||
r1 = ret.Error(1)
|
r1 = ret.Error(1)
|
||||||
}
|
}
|
||||||
@@ -398,20 +398,20 @@ func (_m *PostStore) GetPostIdAfterTime(channelID string, time int64, collapsedT
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPostIdBeforeTime provides a mock function with given fields: channelID, time, collapsedThreads
|
// GetPostIdBeforeTime provides a mock function with given fields: channelID, timestamp, collapsedThreads
|
||||||
func (_m *PostStore) GetPostIdBeforeTime(channelID string, time int64, collapsedThreads bool) (string, error) {
|
func (_m *PostStore) GetPostIdBeforeTime(channelID string, timestamp int64, collapsedThreads bool) (string, error) {
|
||||||
ret := _m.Called(channelID, time, collapsedThreads)
|
ret := _m.Called(channelID, timestamp, collapsedThreads)
|
||||||
|
|
||||||
var r0 string
|
var r0 string
|
||||||
if rf, ok := ret.Get(0).(func(string, int64, bool) string); ok {
|
if rf, ok := ret.Get(0).(func(string, int64, bool) string); ok {
|
||||||
r0 = rf(channelID, time, collapsedThreads)
|
r0 = rf(channelID, timestamp, collapsedThreads)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Get(0).(string)
|
r0 = ret.Get(0).(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
var r1 error
|
var r1 error
|
||||||
if rf, ok := ret.Get(1).(func(string, int64, bool) error); ok {
|
if rf, ok := ret.Get(1).(func(string, int64, bool) error); ok {
|
||||||
r1 = rf(channelID, time, collapsedThreads)
|
r1 = rf(channelID, timestamp, collapsedThreads)
|
||||||
} else {
|
} else {
|
||||||
r1 = ret.Error(1)
|
r1 = ret.Error(1)
|
||||||
}
|
}
|
||||||
@@ -534,13 +534,13 @@ func (_m *PostStore) GetPostsByIds(postIds []string) ([]*model.Post, error) {
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPostsCreatedAt provides a mock function with given fields: channelID, time
|
// GetPostsCreatedAt provides a mock function with given fields: channelID, timestamp
|
||||||
func (_m *PostStore) GetPostsCreatedAt(channelID string, time int64) ([]*model.Post, error) {
|
func (_m *PostStore) GetPostsCreatedAt(channelID string, timestamp int64) ([]*model.Post, error) {
|
||||||
ret := _m.Called(channelID, time)
|
ret := _m.Called(channelID, timestamp)
|
||||||
|
|
||||||
var r0 []*model.Post
|
var r0 []*model.Post
|
||||||
if rf, ok := ret.Get(0).(func(string, int64) []*model.Post); ok {
|
if rf, ok := ret.Get(0).(func(string, int64) []*model.Post); ok {
|
||||||
r0 = rf(channelID, time)
|
r0 = rf(channelID, timestamp)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
if ret.Get(0) != nil {
|
||||||
r0 = ret.Get(0).([]*model.Post)
|
r0 = ret.Get(0).([]*model.Post)
|
||||||
@@ -549,7 +549,7 @@ func (_m *PostStore) GetPostsCreatedAt(channelID string, time int64) ([]*model.P
|
|||||||
|
|
||||||
var r1 error
|
var r1 error
|
||||||
if rf, ok := ret.Get(1).(func(string, int64) error); ok {
|
if rf, ok := ret.Get(1).(func(string, int64) error); ok {
|
||||||
r1 = rf(channelID, time)
|
r1 = rf(channelID, timestamp)
|
||||||
} else {
|
} else {
|
||||||
r1 = ret.Error(1)
|
r1 = ret.Error(1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -264,13 +264,13 @@ func (_m *SessionStore) UpdateExpiredNotify(sessionid string, notified bool) err
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateExpiresAt provides a mock function with given fields: sessionID, time
|
// UpdateExpiresAt provides a mock function with given fields: sessionID, timestamp
|
||||||
func (_m *SessionStore) UpdateExpiresAt(sessionID string, time int64) error {
|
func (_m *SessionStore) UpdateExpiresAt(sessionID string, timestamp int64) error {
|
||||||
ret := _m.Called(sessionID, time)
|
ret := _m.Called(sessionID, timestamp)
|
||||||
|
|
||||||
var r0 error
|
var r0 error
|
||||||
if rf, ok := ret.Get(0).(func(string, int64) error); ok {
|
if rf, ok := ret.Get(0).(func(string, int64) error); ok {
|
||||||
r0 = rf(sessionID, time)
|
r0 = rf(sessionID, timestamp)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Error(0)
|
r0 = ret.Error(0)
|
||||||
}
|
}
|
||||||
@@ -278,13 +278,13 @@ func (_m *SessionStore) UpdateExpiresAt(sessionID string, time int64) error {
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateLastActivityAt provides a mock function with given fields: sessionID, time
|
// UpdateLastActivityAt provides a mock function with given fields: sessionID, timestamp
|
||||||
func (_m *SessionStore) UpdateLastActivityAt(sessionID string, time int64) error {
|
func (_m *SessionStore) UpdateLastActivityAt(sessionID string, timestamp int64) error {
|
||||||
ret := _m.Called(sessionID, time)
|
ret := _m.Called(sessionID, timestamp)
|
||||||
|
|
||||||
var r0 error
|
var r0 error
|
||||||
if rf, ok := ret.Get(0).(func(string, int64) error); ok {
|
if rf, ok := ret.Get(0).(func(string, int64) error); ok {
|
||||||
r0 = rf(sessionID, time)
|
r0 = rf(sessionID, timestamp)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Error(0)
|
r0 = ret.Error(0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,20 +18,20 @@ type UserStore struct {
|
|||||||
mock.Mock
|
mock.Mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// AnalyticsActiveCount provides a mock function with given fields: time, options
|
// AnalyticsActiveCount provides a mock function with given fields: timestamp, options
|
||||||
func (_m *UserStore) AnalyticsActiveCount(time int64, options model.UserCountOptions) (int64, error) {
|
func (_m *UserStore) AnalyticsActiveCount(timestamp int64, options model.UserCountOptions) (int64, error) {
|
||||||
ret := _m.Called(time, options)
|
ret := _m.Called(timestamp, options)
|
||||||
|
|
||||||
var r0 int64
|
var r0 int64
|
||||||
if rf, ok := ret.Get(0).(func(int64, model.UserCountOptions) int64); ok {
|
if rf, ok := ret.Get(0).(func(int64, model.UserCountOptions) int64); ok {
|
||||||
r0 = rf(time, options)
|
r0 = rf(timestamp, options)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Get(0).(int64)
|
r0 = ret.Get(0).(int64)
|
||||||
}
|
}
|
||||||
|
|
||||||
var r1 error
|
var r1 error
|
||||||
if rf, ok := ret.Get(1).(func(int64, model.UserCountOptions) error); ok {
|
if rf, ok := ret.Get(1).(func(int64, model.UserCountOptions) error); ok {
|
||||||
r1 = rf(time, options)
|
r1 = rf(timestamp, options)
|
||||||
} else {
|
} else {
|
||||||
r1 = ret.Error(1)
|
r1 = ret.Error(1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,13 +61,13 @@ func (_m *WebhookStore) ClearCaches() {
|
|||||||
_m.Called()
|
_m.Called()
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteIncoming provides a mock function with given fields: webhookID, time
|
// DeleteIncoming provides a mock function with given fields: webhookID, timestamp
|
||||||
func (_m *WebhookStore) DeleteIncoming(webhookID string, time int64) error {
|
func (_m *WebhookStore) DeleteIncoming(webhookID string, timestamp int64) error {
|
||||||
ret := _m.Called(webhookID, time)
|
ret := _m.Called(webhookID, timestamp)
|
||||||
|
|
||||||
var r0 error
|
var r0 error
|
||||||
if rf, ok := ret.Get(0).(func(string, int64) error); ok {
|
if rf, ok := ret.Get(0).(func(string, int64) error); ok {
|
||||||
r0 = rf(webhookID, time)
|
r0 = rf(webhookID, timestamp)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Error(0)
|
r0 = ret.Error(0)
|
||||||
}
|
}
|
||||||
@@ -75,13 +75,13 @@ func (_m *WebhookStore) DeleteIncoming(webhookID string, time int64) error {
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteOutgoing provides a mock function with given fields: webhookID, time
|
// DeleteOutgoing provides a mock function with given fields: webhookID, timestamp
|
||||||
func (_m *WebhookStore) DeleteOutgoing(webhookID string, time int64) error {
|
func (_m *WebhookStore) DeleteOutgoing(webhookID string, timestamp int64) error {
|
||||||
ret := _m.Called(webhookID, time)
|
ret := _m.Called(webhookID, timestamp)
|
||||||
|
|
||||||
var r0 error
|
var r0 error
|
||||||
if rf, ok := ret.Get(0).(func(string, int64) error); ok {
|
if rf, ok := ret.Get(0).(func(string, int64) error); ok {
|
||||||
r0 = rf(webhookID, time)
|
r0 = rf(webhookID, timestamp)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Error(0)
|
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("GetPosts", func(t *testing.T) { testPostStoreGetPosts(t, ss) })
|
||||||
t.Run("GetPostBeforeAfter", func(t *testing.T) { testPostStoreGetPostBeforeAfter(t, ss) })
|
t.Run("GetPostBeforeAfter", func(t *testing.T) { testPostStoreGetPostBeforeAfter(t, ss) })
|
||||||
t.Run("UserCountsWithPostsByDay", func(t *testing.T) { testUserCountsWithPostsByDay(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("GetFlaggedPostsForTeam", func(t *testing.T) { testPostStoreGetFlaggedPostsForTeam(t, ss, s) })
|
||||||
t.Run("GetFlaggedPosts", func(t *testing.T) { testPostStoreGetFlaggedPosts(t, ss) })
|
t.Run("GetFlaggedPosts", func(t *testing.T) { testPostStoreGetFlaggedPosts(t, ss) })
|
||||||
t.Run("GetFlaggedPostsForChannel", func(t *testing.T) { testPostStoreGetFlaggedPostsForChannel(t, ss) })
|
t.Run("GetFlaggedPostsForChannel", func(t *testing.T) { testPostStoreGetFlaggedPostsForChannel(t, ss) })
|
||||||
|
|||||||
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Ссылка в новой задаче
Block a user