MM-39918: mmctl: list archived private channels in channel list command with system admin user (#28676)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
7846ac86b4
Коммит
5ebdea506f
@@ -897,7 +897,8 @@ func getDeletedChannelsForTeam(c *Context, w http.ResponseWriter, r *http.Reques
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
channels, err := c.App.GetDeletedChannels(c.AppContext, c.Params.TeamId, c.Params.Page*c.Params.PerPage, c.Params.PerPage, c.AppContext.Session().UserId)
|
skipTeamMembershipCheck := c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem)
|
||||||
|
channels, err := c.App.GetDeletedChannels(c.AppContext, c.Params.TeamId, c.Params.Page*c.Params.PerPage, c.Params.PerPage, c.AppContext.Session().UserId, skipTeamMembershipCheck)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Err = err
|
c.Err = err
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -959,7 +959,8 @@ func TestGetDeletedChannelsForTeam(t *testing.T) {
|
|||||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||||
channels, _, err = client.GetDeletedChannelsForTeam(context.Background(), team.Id, 0, 100, "")
|
channels, _, err = client.GetDeletedChannelsForTeam(context.Background(), team.Id, 0, 100, "")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, channels, numInitialChannelsForTeam+2)
|
// Local admin should see private archived channels
|
||||||
|
require.Len(t, channels, numInitialChannelsForTeam+4)
|
||||||
})
|
})
|
||||||
|
|
||||||
channels, _, err = client.GetDeletedChannelsForTeam(context.Background(), team.Id, 0, 1, "")
|
channels, _, err = client.GetDeletedChannelsForTeam(context.Background(), team.Id, 0, 1, "")
|
||||||
|
|||||||
@@ -685,7 +685,7 @@ type AppIface interface {
|
|||||||
GetCookieDomain() string
|
GetCookieDomain() string
|
||||||
GetCustomStatus(userID string) (*model.CustomStatus, *model.AppError)
|
GetCustomStatus(userID string) (*model.CustomStatus, *model.AppError)
|
||||||
GetDefaultProfileImage(user *model.User) ([]byte, *model.AppError)
|
GetDefaultProfileImage(user *model.User) ([]byte, *model.AppError)
|
||||||
GetDeletedChannels(c request.CTX, teamID string, offset int, limit int, userID string) (model.ChannelList, *model.AppError)
|
GetDeletedChannels(c request.CTX, teamID string, offset int, limit int, userID string, skipTeamMembershipCheck bool) (model.ChannelList, *model.AppError)
|
||||||
GetDraft(userID, channelID, rootID string) (*model.Draft, *model.AppError)
|
GetDraft(userID, channelID, rootID string) (*model.Draft, *model.AppError)
|
||||||
GetDraftsForUser(rctx request.CTX, userID, teamID string) ([]*model.Draft, *model.AppError)
|
GetDraftsForUser(rctx request.CTX, userID, teamID string) ([]*model.Draft, *model.AppError)
|
||||||
GetEditHistoryForPost(postID string) ([]*model.Post, *model.AppError)
|
GetEditHistoryForPost(postID string) ([]*model.Post, *model.AppError)
|
||||||
|
|||||||
@@ -2019,8 +2019,8 @@ func (a *App) GetAllChannelsCount(c request.CTX, opts model.ChannelSearchOpts) (
|
|||||||
return count, nil
|
return count, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) GetDeletedChannels(c request.CTX, teamID string, offset int, limit int, userID string) (model.ChannelList, *model.AppError) {
|
func (a *App) GetDeletedChannels(c request.CTX, teamID string, offset int, limit int, userID string, skipTeamMembershipCheck bool) (model.ChannelList, *model.AppError) {
|
||||||
list, err := a.Srv().Store().Channel().GetDeleted(teamID, offset, limit, userID)
|
list, err := a.Srv().Store().Channel().GetDeleted(teamID, offset, limit, userID, skipTeamMembershipCheck)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var nfErr *store.ErrNotFound
|
var nfErr *store.ErrNotFound
|
||||||
switch {
|
switch {
|
||||||
|
|||||||
@@ -6379,7 +6379,7 @@ func (a *OpenTracingAppLayer) GetDefaultProfileImage(user *model.User) ([]byte,
|
|||||||
return resultVar0, resultVar1
|
return resultVar0, resultVar1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *OpenTracingAppLayer) GetDeletedChannels(c request.CTX, teamID string, offset int, limit int, userID string) (model.ChannelList, *model.AppError) {
|
func (a *OpenTracingAppLayer) GetDeletedChannels(c request.CTX, teamID string, offset int, limit int, userID string, skipTeamMembershipCheck bool) (model.ChannelList, *model.AppError) {
|
||||||
origCtx := a.ctx
|
origCtx := a.ctx
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetDeletedChannels")
|
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetDeletedChannels")
|
||||||
|
|
||||||
@@ -6391,7 +6391,7 @@ func (a *OpenTracingAppLayer) GetDeletedChannels(c request.CTX, teamID string, o
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
resultVar0, resultVar1 := a.app.GetDeletedChannels(c, teamID, offset, limit, userID)
|
resultVar0, resultVar1 := a.app.GetDeletedChannels(c, teamID, offset, limit, userID, skipTeamMembershipCheck)
|
||||||
|
|
||||||
if resultVar1 != nil {
|
if resultVar1 != nil {
|
||||||
span.LogFields(spanlog.Error(resultVar1))
|
span.LogFields(spanlog.Error(resultVar1))
|
||||||
|
|||||||
@@ -1425,7 +1425,7 @@ func (s *OpenTracingLayerChannelStore) GetChannelsWithUnreadsAndWithMentions(ctx
|
|||||||
return result, resultVar1, resultVar2, err
|
return result, resultVar1, resultVar2, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *OpenTracingLayerChannelStore) GetDeleted(teamID string, offset int, limit int, userID string) (model.ChannelList, error) {
|
func (s *OpenTracingLayerChannelStore) GetDeleted(teamID string, offset int, limit int, userID string, skipTeamMembershipCheck bool) (model.ChannelList, error) {
|
||||||
origCtx := s.Root.Store.Context()
|
origCtx := s.Root.Store.Context()
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.GetDeleted")
|
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.GetDeleted")
|
||||||
s.Root.Store.SetContext(newCtx)
|
s.Root.Store.SetContext(newCtx)
|
||||||
@@ -1434,7 +1434,7 @@ func (s *OpenTracingLayerChannelStore) GetDeleted(teamID string, offset int, lim
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
result, err := s.ChannelStore.GetDeleted(teamID, offset, limit, userID)
|
result, err := s.ChannelStore.GetDeleted(teamID, offset, limit, userID, skipTeamMembershipCheck)
|
||||||
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)
|
||||||
|
|||||||
@@ -1584,11 +1584,11 @@ func (s *RetryLayerChannelStore) GetChannelsWithUnreadsAndWithMentions(ctx conte
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *RetryLayerChannelStore) GetDeleted(teamID string, offset int, limit int, userID string) (model.ChannelList, error) {
|
func (s *RetryLayerChannelStore) GetDeleted(teamID string, offset int, limit int, userID string, skipTeamMembershipCheck bool) (model.ChannelList, error) {
|
||||||
|
|
||||||
tries := 0
|
tries := 0
|
||||||
for {
|
for {
|
||||||
result, err := s.ChannelStore.GetDeleted(teamID, offset, limit, userID)
|
result, err := s.ChannelStore.GetDeleted(teamID, offset, limit, userID, skipTeamMembershipCheck)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1490,24 +1490,37 @@ func (s SqlChannelStore) GetDeletedByName(teamId string, name string) (*model.Ch
|
|||||||
return &channel, nil
|
return &channel, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s SqlChannelStore) GetDeleted(teamId string, offset int, limit int, userId string) (model.ChannelList, error) {
|
func (s SqlChannelStore) GetDeleted(teamId string, offset int, limit int, userId string, skipTeamMembershipCheck bool) (model.ChannelList, error) {
|
||||||
channels := model.ChannelList{}
|
channels := model.ChannelList{}
|
||||||
|
|
||||||
query := `
|
builder := s.getQueryBuilder().
|
||||||
SELECT * FROM Channels
|
Select("*").
|
||||||
WHERE (TeamId = ? OR TeamId = '')
|
From("Channels").
|
||||||
AND DeleteAt != 0
|
Where(sq.Or{
|
||||||
AND Type != ?
|
sq.Eq{"TeamId": teamId},
|
||||||
UNION
|
sq.Eq{"TeamId": ""},
|
||||||
SELECT * FROM Channels
|
}).
|
||||||
WHERE (TeamId = ? OR TeamId = '')
|
Where(sq.NotEq{"DeleteAt": 0}).
|
||||||
AND DeleteAt != 0
|
OrderBy("DisplayName").
|
||||||
AND Type = ?
|
Limit(uint64(limit)).
|
||||||
AND Id IN (SELECT ChannelId FROM ChannelMembers WHERE UserId = ?)
|
Offset(uint64(offset))
|
||||||
ORDER BY DisplayName LIMIT ? OFFSET ?
|
|
||||||
`
|
|
||||||
|
|
||||||
if err := s.GetReplicaX().Select(&channels, query, teamId, model.ChannelTypePrivate, teamId, model.ChannelTypePrivate, userId, limit, offset); err != nil {
|
if !skipTeamMembershipCheck {
|
||||||
|
builder = builder.Where(sq.Or{
|
||||||
|
sq.NotEq{"Type": model.ChannelTypePrivate},
|
||||||
|
sq.And{
|
||||||
|
sq.Eq{"Type": model.ChannelTypePrivate},
|
||||||
|
sq.Expr("Id IN (?)", sq.Select("ChannelId").From("ChannelMembers").Where(sq.Eq{"UserId": userId})),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
query, args, err := builder.ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "GetDeleted_ToSql")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.GetReplicaX().Select(&channels, query, args...); err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return nil, store.NewErrNotFound("Channel", fmt.Sprintf("TeamId=%s,UserId=%s", teamId, userId))
|
return nil, store.NewErrNotFound("Channel", fmt.Sprintf("TeamId=%s,UserId=%s", teamId, userId))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ type ChannelStore interface {
|
|||||||
GetByNameIncludeDeleted(teamID string, name string, allowFromCache bool) (*model.Channel, error)
|
GetByNameIncludeDeleted(teamID string, name string, allowFromCache bool) (*model.Channel, error)
|
||||||
GetByNamesIncludeDeleted(teamID string, names []string, allowFromCache bool) ([]*model.Channel, error)
|
GetByNamesIncludeDeleted(teamID string, names []string, allowFromCache bool) ([]*model.Channel, error)
|
||||||
GetDeletedByName(teamID string, name string) (*model.Channel, error)
|
GetDeletedByName(teamID string, name string) (*model.Channel, error)
|
||||||
GetDeleted(teamID string, offset int, limit int, userID string) (model.ChannelList, error)
|
GetDeleted(teamID string, offset int, limit int, userID string, skipTeamMembershipCheck bool) (model.ChannelList, error)
|
||||||
GetChannels(teamID, userID string, opts *model.ChannelSearchOpts) (model.ChannelList, error)
|
GetChannels(teamID, userID string, opts *model.ChannelSearchOpts) (model.ChannelList, error)
|
||||||
GetChannelsByUser(userID string, includeDeleted bool, lastDeleteAt, pageSize int, fromChannelID string) (model.ChannelList, error)
|
GetChannelsByUser(userID string, includeDeleted bool, lastDeleteAt, pageSize int, fromChannelID string) (model.ChannelList, error)
|
||||||
GetAllChannelMemberIdsByChannelId(id string) ([]string, error)
|
GetAllChannelMemberIdsByChannelId(id string) ([]string, error)
|
||||||
|
|||||||
@@ -992,60 +992,79 @@ func testChannelStoreGetDeletedByName(t *testing.T, rctx request.CTX, ss store.S
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testChannelStoreGetDeleted(t *testing.T, rctx request.CTX, ss store.Store) {
|
func testChannelStoreGetDeleted(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||||
o1 := model.Channel{}
|
teamID := model.NewId()
|
||||||
o1.TeamId = model.NewId()
|
|
||||||
o1.DisplayName = "Channel1"
|
|
||||||
o1.Name = NewTestID()
|
|
||||||
o1.Type = model.ChannelTypeOpen
|
|
||||||
|
|
||||||
userID := model.NewId()
|
|
||||||
|
|
||||||
|
o1 := model.Channel{
|
||||||
|
TeamId: teamID,
|
||||||
|
DisplayName: "Channel1",
|
||||||
|
Name: NewTestID(),
|
||||||
|
Type: model.ChannelTypeOpen,
|
||||||
|
}
|
||||||
_, nErr := ss.Channel().Save(rctx, &o1, -1)
|
_, nErr := ss.Channel().Save(rctx, &o1, -1)
|
||||||
require.NoError(t, nErr)
|
require.NoError(t, nErr)
|
||||||
|
|
||||||
err := ss.Channel().Delete(o1.Id, model.GetMillis())
|
err := ss.Channel().Delete(o1.Id, model.GetMillis())
|
||||||
require.NoError(t, err, "channel should have been deleted")
|
require.NoError(t, err, "channel should have been deleted")
|
||||||
|
|
||||||
list, nErr := ss.Channel().GetDeleted(o1.TeamId, 0, 100, userID)
|
userID := model.NewId()
|
||||||
|
|
||||||
|
list, nErr := ss.Channel().GetDeleted(teamID, 0, 100, userID, false)
|
||||||
require.NoError(t, nErr, nErr)
|
require.NoError(t, nErr, nErr)
|
||||||
require.Len(t, list, 1, "wrong list")
|
require.Len(t, list, 1, "wrong list")
|
||||||
require.Equal(t, o1.Name, list[0].Name, "missing channel")
|
require.Equal(t, o1.Name, list[0].Name, "missing channel")
|
||||||
|
|
||||||
o2 := model.Channel{}
|
o2 := model.Channel{
|
||||||
o2.TeamId = o1.TeamId
|
TeamId: teamID,
|
||||||
o2.DisplayName = "Channel2"
|
DisplayName: "Channel2",
|
||||||
o2.Name = NewTestID()
|
Name: NewTestID(),
|
||||||
o2.Type = model.ChannelTypeOpen
|
Type: model.ChannelTypeOpen,
|
||||||
|
}
|
||||||
_, nErr = ss.Channel().Save(rctx, &o2, -1)
|
_, nErr = ss.Channel().Save(rctx, &o2, -1)
|
||||||
require.NoError(t, nErr)
|
require.NoError(t, nErr)
|
||||||
|
|
||||||
list, nErr = ss.Channel().GetDeleted(o1.TeamId, 0, 100, userID)
|
list, nErr = ss.Channel().GetDeleted(teamID, 0, 100, userID, false)
|
||||||
require.NoError(t, nErr, nErr)
|
require.NoError(t, nErr, nErr)
|
||||||
require.Len(t, list, 1, "wrong list")
|
require.Len(t, list, 1, "wrong list")
|
||||||
|
|
||||||
o3 := model.Channel{}
|
o3 := model.Channel{
|
||||||
o3.TeamId = o1.TeamId
|
TeamId: teamID,
|
||||||
o3.DisplayName = "Channel3"
|
DisplayName: "Channel3",
|
||||||
o3.Name = NewTestID()
|
Name: NewTestID(),
|
||||||
o3.Type = model.ChannelTypeOpen
|
Type: model.ChannelTypeOpen,
|
||||||
|
}
|
||||||
_, nErr = ss.Channel().Save(rctx, &o3, -1)
|
_, nErr = ss.Channel().Save(rctx, &o3, -1)
|
||||||
require.NoError(t, nErr)
|
require.NoError(t, nErr)
|
||||||
|
|
||||||
|
o4 := model.Channel{
|
||||||
|
TeamId: teamID,
|
||||||
|
DisplayName: "Channel4",
|
||||||
|
Name: NewTestID(),
|
||||||
|
Type: model.ChannelTypePrivate,
|
||||||
|
}
|
||||||
|
_, nErr = ss.Channel().Save(rctx, &o4, -1)
|
||||||
|
require.NoError(t, nErr)
|
||||||
|
|
||||||
|
err = ss.Channel().Delete(o4.Id, model.GetMillis())
|
||||||
|
require.NoError(t, err, "channel should have been deleted")
|
||||||
|
|
||||||
err = ss.Channel().Delete(o3.Id, model.GetMillis())
|
err = ss.Channel().Delete(o3.Id, model.GetMillis())
|
||||||
require.NoError(t, err, "channel should have been deleted")
|
require.NoError(t, err, "channel should have been deleted")
|
||||||
|
|
||||||
list, nErr = ss.Channel().GetDeleted(o1.TeamId, 0, 100, userID)
|
list, nErr = ss.Channel().GetDeleted(teamID, 0, 100, userID, false)
|
||||||
require.NoError(t, nErr, nErr)
|
require.NoError(t, nErr, nErr)
|
||||||
require.Len(t, list, 2, "wrong list length")
|
require.Len(t, list, 2, "wrong list length")
|
||||||
|
|
||||||
list, nErr = ss.Channel().GetDeleted(o1.TeamId, 0, 1, userID)
|
list, nErr = ss.Channel().GetDeleted(teamID, 0, 1, userID, false)
|
||||||
require.NoError(t, nErr, nErr)
|
require.NoError(t, nErr, nErr)
|
||||||
require.Len(t, list, 1, "wrong list length")
|
require.Len(t, list, 1, "wrong list length")
|
||||||
|
|
||||||
list, nErr = ss.Channel().GetDeleted(o1.TeamId, 1, 1, userID)
|
list, nErr = ss.Channel().GetDeleted(teamID, 1, 1, userID, false)
|
||||||
require.NoError(t, nErr, nErr)
|
require.NoError(t, nErr, nErr)
|
||||||
require.Len(t, list, 1, "wrong list length")
|
require.Len(t, list, 1, "wrong list length")
|
||||||
|
|
||||||
|
list, nErr = ss.Channel().GetDeleted(teamID, 0, 100, userID, true)
|
||||||
|
require.NoError(t, nErr, nErr)
|
||||||
|
require.Len(t, list, 3, "wrong list length")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testChannelMemberStore(t *testing.T, rctx request.CTX, ss store.Store) {
|
func testChannelMemberStore(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||||
|
|||||||
@@ -1210,9 +1210,9 @@ func (_m *ChannelStore) GetChannelsWithUnreadsAndWithMentions(ctx context.Contex
|
|||||||
return r0, r1, r2, r3
|
return r0, r1, r2, r3
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDeleted provides a mock function with given fields: teamID, offset, limit, userID
|
// GetDeleted provides a mock function with given fields: teamID, offset, limit, userID, skipTeamMembershipCheck
|
||||||
func (_m *ChannelStore) GetDeleted(teamID string, offset int, limit int, userID string) (model.ChannelList, error) {
|
func (_m *ChannelStore) GetDeleted(teamID string, offset int, limit int, userID string, skipTeamMembershipCheck bool) (model.ChannelList, error) {
|
||||||
ret := _m.Called(teamID, offset, limit, userID)
|
ret := _m.Called(teamID, offset, limit, userID, skipTeamMembershipCheck)
|
||||||
|
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
panic("no return value specified for GetDeleted")
|
panic("no return value specified for GetDeleted")
|
||||||
@@ -1220,19 +1220,19 @@ func (_m *ChannelStore) GetDeleted(teamID string, offset int, limit int, userID
|
|||||||
|
|
||||||
var r0 model.ChannelList
|
var r0 model.ChannelList
|
||||||
var r1 error
|
var r1 error
|
||||||
if rf, ok := ret.Get(0).(func(string, int, int, string) (model.ChannelList, error)); ok {
|
if rf, ok := ret.Get(0).(func(string, int, int, string, bool) (model.ChannelList, error)); ok {
|
||||||
return rf(teamID, offset, limit, userID)
|
return rf(teamID, offset, limit, userID, skipTeamMembershipCheck)
|
||||||
}
|
}
|
||||||
if rf, ok := ret.Get(0).(func(string, int, int, string) model.ChannelList); ok {
|
if rf, ok := ret.Get(0).(func(string, int, int, string, bool) model.ChannelList); ok {
|
||||||
r0 = rf(teamID, offset, limit, userID)
|
r0 = rf(teamID, offset, limit, userID, skipTeamMembershipCheck)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
if ret.Get(0) != nil {
|
||||||
r0 = ret.Get(0).(model.ChannelList)
|
r0 = ret.Get(0).(model.ChannelList)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if rf, ok := ret.Get(1).(func(string, int, int, string) error); ok {
|
if rf, ok := ret.Get(1).(func(string, int, int, string, bool) error); ok {
|
||||||
r1 = rf(teamID, offset, limit, userID)
|
r1 = rf(teamID, offset, limit, userID, skipTeamMembershipCheck)
|
||||||
} else {
|
} else {
|
||||||
r1 = ret.Error(1)
|
r1 = ret.Error(1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1327,10 +1327,10 @@ func (s *TimerLayerChannelStore) GetChannelsWithUnreadsAndWithMentions(ctx conte
|
|||||||
return result, resultVar1, resultVar2, err
|
return result, resultVar1, resultVar2, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TimerLayerChannelStore) GetDeleted(teamID string, offset int, limit int, userID string) (model.ChannelList, error) {
|
func (s *TimerLayerChannelStore) GetDeleted(teamID string, offset int, limit int, userID string, skipTeamMembershipCheck bool) (model.ChannelList, error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
result, err := s.ChannelStore.GetDeleted(teamID, offset, limit, userID)
|
result, err := s.ChannelStore.GetDeleted(teamID, offset, limit, userID, skipTeamMembershipCheck)
|
||||||
|
|
||||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||||
if s.Root.Metrics != nil {
|
if s.Root.Metrics != nil {
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user