[MM-41998] Channel Info RHS: display number of files for a channel (#19822)

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
Julien Tant
2022-03-25 13:28:14 -07:00
коммит произвёл GitHub
родитель 1c49366f00
Коммит 0babc01749
15 изменённых файлов: 181 добавлений и 0 удалений

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

@@ -709,6 +709,33 @@ func (th *TestHelper) CreateMessagePost(message string) *model.Post {
return th.CreateMessagePostWithClient(th.Client, th.BasicChannel, message) return th.CreateMessagePostWithClient(th.Client, th.BasicChannel, message)
} }
func (th *TestHelper) CreatePostWithFiles(files ...*model.FileInfo) *model.Post {
return th.CreatePostWithFilesWithClient(th.Client, th.BasicChannel, files...)
}
func (th *TestHelper) CreatePostInChannelWithFiles(channel *model.Channel, files ...*model.FileInfo) *model.Post {
return th.CreatePostWithFilesWithClient(th.Client, channel, files...)
}
func (th *TestHelper) CreatePostWithFilesWithClient(client *model.Client4, channel *model.Channel, files ...*model.FileInfo) *model.Post {
var fileIds model.StringArray
for i := range files {
fileIds = append(fileIds, files[i].Id)
}
post := &model.Post{
ChannelId: channel.Id,
Message: "message_" + model.NewId(),
FileIds: fileIds,
}
rpost, _, err := client.CreatePost(post)
if err != nil {
panic(err)
}
return rpost
}
func (th *TestHelper) CreatePostWithClient(client *model.Client4, channel *model.Channel) *model.Post { func (th *TestHelper) CreatePostWithClient(client *model.Client4, channel *model.Channel) *model.Post {
id := model.NewId() id := model.NewId()

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

@@ -642,11 +642,18 @@ func getChannelStats(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
filesCount, err := c.App.GetChannelFileCount(c.Params.ChannelId)
if err != nil {
c.Err = err
return
}
stats := model.ChannelStats{ stats := model.ChannelStats{
ChannelId: c.Params.ChannelId, ChannelId: c.Params.ChannelId,
MemberCount: memberCount, MemberCount: memberCount,
GuestCount: guestCount, GuestCount: guestCount,
PinnedPostCount: pinnedPostCount, PinnedPostCount: pinnedPostCount,
FilesCount: filesCount,
} }
if err := json.NewEncoder(w).Encode(stats); err != nil { if err := json.NewEncoder(w).Encode(stats); err != nil {
mlog.Warn("Error while writing response", mlog.Err(err)) mlog.Warn("Error while writing response", mlog.Err(err))

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

@@ -22,6 +22,7 @@ import (
"github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/plugin/plugintest/mock" "github.com/mattermost/mattermost-server/v6/plugin/plugintest/mock"
"github.com/mattermost/mattermost-server/v6/store/storetest/mocks" "github.com/mattermost/mattermost-server/v6/store/storetest/mocks"
"github.com/mattermost/mattermost-server/v6/utils/testutils"
) )
func TestCreateChannel(t *testing.T) { func TestCreateChannel(t *testing.T) {
@@ -2507,12 +2508,24 @@ func TestGetChannelStats(t *testing.T) {
require.Equal(t, channel.Id, stats.ChannelId, "couldn't get extra info") require.Equal(t, channel.Id, stats.ChannelId, "couldn't get extra info")
require.Equal(t, int64(1), stats.MemberCount, "got incorrect member count") require.Equal(t, int64(1), stats.MemberCount, "got incorrect member count")
require.Equal(t, int64(0), stats.PinnedPostCount, "got incorrect pinned post count") require.Equal(t, int64(0), stats.PinnedPostCount, "got incorrect pinned post count")
require.Equal(t, int64(0), stats.FilesCount, "got incorrect file count")
th.CreatePinnedPostWithClient(th.Client, channel) th.CreatePinnedPostWithClient(th.Client, channel)
stats, _, err = client.GetChannelStats(channel.Id, "") stats, _, err = client.GetChannelStats(channel.Id, "")
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, int64(1), stats.PinnedPostCount, "should have returned 1 pinned post count") require.Equal(t, int64(1), stats.PinnedPostCount, "should have returned 1 pinned post count")
// create a post with a file
sent, err := testutils.ReadTestFile("test.png")
require.NoError(t, err)
fileResp, _, err := client.UploadFile(sent, channel.Id, "test.png")
require.NoError(t, err)
th.CreatePostInChannelWithFiles(channel, fileResp.FileInfos...)
// make sure the file count channel stats is updated
stats, _, err = client.GetChannelStats(channel.Id, "")
require.NoError(t, err)
require.Equal(t, int64(1), stats.FilesCount, "should have returned 1 file count")
_, resp, err := client.GetChannelStats("junk", "") _, resp, err := client.GetChannelStats("junk", "")
require.Error(t, err) require.Error(t, err)
CheckBadRequestStatus(t, resp) CheckBadRequestStatus(t, resp)

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

@@ -561,6 +561,7 @@ type AppIface interface {
GetChannelByName(channelName, teamID string, includeDeleted bool) (*model.Channel, *model.AppError) GetChannelByName(channelName, teamID string, includeDeleted bool) (*model.Channel, *model.AppError)
GetChannelByNameForTeamName(channelName, teamName string, includeDeleted bool) (*model.Channel, *model.AppError) GetChannelByNameForTeamName(channelName, teamName string, includeDeleted bool) (*model.Channel, *model.AppError)
GetChannelCounts(teamID string, userID string) (*model.ChannelCounts, *model.AppError) GetChannelCounts(teamID string, userID string) (*model.ChannelCounts, *model.AppError)
GetChannelFileCount(channelID string) (int64, *model.AppError)
GetChannelGuestCount(channelID string) (int64, *model.AppError) GetChannelGuestCount(channelID string) (int64, *model.AppError)
GetChannelMember(ctx context.Context, channelID string, userID string) (*model.ChannelMember, *model.AppError) GetChannelMember(ctx context.Context, channelID string, userID string) (*model.ChannelMember, *model.AppError)
GetChannelMemberCount(channelID string) (int64, *model.AppError) GetChannelMemberCount(channelID string) (int64, *model.AppError)

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

@@ -2009,6 +2009,15 @@ func (a *App) GetChannelMemberCount(channelID string) (int64, *model.AppError) {
return count, nil return count, nil
} }
func (a *App) GetChannelFileCount(channelID string) (int64, *model.AppError) {
count, err := a.Srv().Store.Channel().GetFileCount(channelID)
if err != nil {
return 0, model.NewAppError("SqlChannelStore.GetFileCount", "app.channel.get_file_count.app_error", nil, err.Error(), http.StatusInternalServerError)
}
return count, nil
}
func (a *App) GetChannelGuestCount(channelID string) (int64, *model.AppError) { func (a *App) GetChannelGuestCount(channelID string) (int64, *model.AppError) {
count, err := a.Srv().Store.Channel().GetGuestCount(channelID, true) count, err := a.Srv().Store.Channel().GetGuestCount(channelID, true)
if err != nil { if err != nil {

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

@@ -4890,6 +4890,28 @@ func (a *OpenTracingAppLayer) GetChannelCounts(teamID string, userID string) (*m
return resultVar0, resultVar1 return resultVar0, resultVar1
} }
func (a *OpenTracingAppLayer) GetChannelFileCount(channelID string) (int64, *model.AppError) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetChannelFileCount")
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.GetChannelFileCount(channelID)
if resultVar1 != nil {
span.LogFields(spanlog.Error(resultVar1))
ext.Error.Set(span, true)
}
return resultVar0, resultVar1
}
func (a *OpenTracingAppLayer) GetChannelGroupUsers(channelID string) ([]*model.User, *model.AppError) { func (a *OpenTracingAppLayer) GetChannelGroupUsers(channelID string) ([]*model.User, *model.AppError) {
origCtx := a.ctx origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetChannelGroupUsers") span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetChannelGroupUsers")

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

@@ -4591,6 +4591,10 @@
"id": "app.channel.get_deleted.missing.app_error", "id": "app.channel.get_deleted.missing.app_error",
"translation": "No deleted channels exist." "translation": "No deleted channels exist."
}, },
{
"id": "app.channel.get_file_count.app_error",
"translation": "Unable to get the file count for the channel"
},
{ {
"id": "app.channel.get_for_post.app_error", "id": "app.channel.get_for_post.app_error",
"translation": "Unable to get the channel for the given post." "translation": "Unable to get the channel for the given post."

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

@@ -8,6 +8,7 @@ type ChannelStats struct {
MemberCount int64 `json:"member_count"` MemberCount int64 `json:"member_count"`
GuestCount int64 `json:"guest_count"` GuestCount int64 `json:"guest_count"`
PinnedPostCount int64 `json:"pinnedpost_count"` PinnedPostCount int64 `json:"pinnedpost_count"`
FilesCount int64 `json:"files_count"`
} }
func (o *ChannelStats) MemberCount_() float64 { func (o *ChannelStats) MemberCount_() float64 {

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

@@ -26,6 +26,7 @@ const (
ClusterEventInvalidateCacheForWebhooks ClusterEvent = "inv_webhooks" ClusterEventInvalidateCacheForWebhooks ClusterEvent = "inv_webhooks"
ClusterEventInvalidateCacheForEmojisById ClusterEvent = "inv_emojis_by_id" ClusterEventInvalidateCacheForEmojisById ClusterEvent = "inv_emojis_by_id"
ClusterEventInvalidateCacheForEmojisIdByName ClusterEvent = "inv_emojis_id_by_name" ClusterEventInvalidateCacheForEmojisIdByName ClusterEvent = "inv_emojis_id_by_name"
ClusterEventInvalidateCacheForChannelFileCount ClusterEvent = "inv_channel_file_count"
ClusterEventInvalidateCacheForChannelPinnedpostsCounts ClusterEvent = "inv_channel_pinnedposts_counts" ClusterEventInvalidateCacheForChannelPinnedpostsCounts ClusterEvent = "inv_channel_pinnedposts_counts"
ClusterEventInvalidateCacheForChannelMemberCounts ClusterEvent = "inv_channel_member_counts" ClusterEventInvalidateCacheForChannelMemberCounts ClusterEvent = "inv_channel_member_counts"
ClusterEventInvalidateCacheForLastPosts ClusterEvent = "inv_last_posts" ClusterEventInvalidateCacheForLastPosts ClusterEvent = "inv_last_posts"

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

@@ -1249,6 +1249,24 @@ func (s *OpenTracingLayerChannelStore) GetDeletedByName(team_id string, name str
return result, err return result, err
} }
func (s *OpenTracingLayerChannelStore) GetFileCount(channelID string) (int64, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.GetFileCount")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
result, err := s.ChannelStore.GetFileCount(channelID)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return result, err
}
func (s *OpenTracingLayerChannelStore) GetForPost(postID string) (*model.Channel, error) { func (s *OpenTracingLayerChannelStore) GetForPost(postID string) (*model.Channel, error) {
origCtx := s.Root.Store.Context() origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.GetForPost") span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.GetForPost")

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

@@ -1402,6 +1402,27 @@ func (s *RetryLayerChannelStore) GetDeletedByName(team_id string, name string) (
} }
func (s *RetryLayerChannelStore) GetFileCount(channelID string) (int64, error) {
tries := 0
for {
result, err := s.ChannelStore.GetFileCount(channelID)
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) GetForPost(postID string) (*model.Channel, error) { func (s *RetryLayerChannelStore) GetForPost(postID string) (*model.Channel, error) {
tries := 0 tries := 0

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

@@ -2188,6 +2188,25 @@ func (s SqlChannelStore) GetMemberCountFromCache(channelId string) int64 {
return count return count
} }
func (s SqlChannelStore) GetFileCount(channelId string) (int64, error) {
var count int64
err := s.GetReplicaX().Get(&count, `
SELECT
COUNT(*)
FROM
FileInfo
LEFT JOIN Posts as P ON FileInfo.PostId=P.Id
LEFT JOIN Channels as C on C.Id=P.ChannelId
WHERE
FileInfo.DeleteAt = 0
AND C.Id = ?`, channelId)
if err != nil {
return 0, errors.Wrapf(err, "failed to count files with channelId=%s", channelId)
}
return count, nil
}
//nolint:unparam //nolint:unparam
func (s SqlChannelStore) GetMemberCount(channelId string, allowFromCache bool) (int64, error) { func (s SqlChannelStore) GetMemberCount(channelId string, allowFromCache bool) (int64, error) {
var count int64 var count int64

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

@@ -213,6 +213,7 @@ type ChannelStore interface {
GetMemberForPost(postID string, userID string) (*model.ChannelMember, error) GetMemberForPost(postID string, userID string) (*model.ChannelMember, error)
InvalidateMemberCount(channelID string) InvalidateMemberCount(channelID string)
GetMemberCountFromCache(channelID string) int64 GetMemberCountFromCache(channelID string) int64
GetFileCount(channelID string) (int64, error)
GetMemberCount(channelID string, allowFromCache bool) (int64, error) GetMemberCount(channelID string, allowFromCache bool) (int64, error)
GetMemberCountsByGroup(ctx context.Context, channelID string, includeTimezones bool) ([]*model.ChannelMemberCountByGroup, error) GetMemberCountsByGroup(ctx context.Context, channelID string, includeTimezones bool) ([]*model.ChannelMemberCountByGroup, error)
InvalidatePinnedPostCount(channelID string) InvalidatePinnedPostCount(channelID string)

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

@@ -881,6 +881,27 @@ func (_m *ChannelStore) GetDeletedByName(team_id string, name string) (*model.Ch
return r0, r1 return r0, r1
} }
// GetFileCount provides a mock function with given fields: channelID
func (_m *ChannelStore) GetFileCount(channelID string) (int64, error) {
ret := _m.Called(channelID)
var r0 int64
if rf, ok := ret.Get(0).(func(string) int64); ok {
r0 = rf(channelID)
} else {
r0 = ret.Get(0).(int64)
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(channelID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetForPost provides a mock function with given fields: postID // GetForPost provides a mock function with given fields: postID
func (_m *ChannelStore) GetForPost(postID string) (*model.Channel, error) { func (_m *ChannelStore) GetForPost(postID string) (*model.Channel, error) {
ret := _m.Called(postID) ret := _m.Called(postID)

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

@@ -1157,6 +1157,22 @@ func (s *TimerLayerChannelStore) GetDeletedByName(team_id string, name string) (
return result, err return result, err
} }
func (s *TimerLayerChannelStore) GetFileCount(channelID string) (int64, error) {
start := timemodule.Now()
result, err := s.ChannelStore.GetFileCount(channelID)
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("ChannelStore.GetFileCount", success, elapsed)
}
return result, err
}
func (s *TimerLayerChannelStore) GetForPost(postID string) (*model.Channel, error) { func (s *TimerLayerChannelStore) GetForPost(postID string) (*model.Channel, error) {
start := timemodule.Now() start := timemodule.Now()