MM-44088: Add teamID filter to channelMembers (#20176)

We add 2 new params to channel members query.
1. Filter by teamId.
2. Negate that filter.

We include some more optimizations like:
- Moved the team role checks inside the dataloader.
- Moved the channel pretty name computation inside the loader.

Now that we load less data on initial load, we can reduce
the concurrency requirement to be a bit on the safer side.

```release-note
NONE
```
Этот коммит содержится в:
Agniva De Sarker
2022-05-11 13:54:12 +05:30
коммит произвёл GitHub
родитель 5ac3dbf058
Коммит a6d8e45297
14 изменённых файлов: 225 добавлений и 75 удалений

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

@@ -34,7 +34,7 @@ const (
usersLoaderCtx ctxKey = 4 usersLoaderCtx ctxKey = 4
) )
const loaderBatchCapacity = web.PerPageMaximum + 100 const loaderBatchCapacity = web.PerPageMaximum
//go:embed schema.graphqls //go:embed schema.graphqls
var schemaRaw string var schemaRaw string

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

@@ -11,6 +11,7 @@ import (
"github.com/graph-gophers/dataloader/v6" "github.com/graph-gophers/dataloader/v6"
"github.com/mattermost/mattermost-server/v6/app" "github.com/mattermost/mattermost-server/v6/app"
"github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/store"
"github.com/mattermost/mattermost-server/v6/web" "github.com/mattermost/mattermost-server/v6/web"
) )
@@ -211,7 +212,9 @@ func (*resolver) ChannelsLeft(ctx context.Context, args struct {
// match with api4.getChannelMember // match with api4.getChannelMember
func (*resolver) ChannelMembers(ctx context.Context, args struct { func (*resolver) ChannelMembers(ctx context.Context, args struct {
UserID string UserID string
TeamID string
ChannelID string ChannelID string
ExcludeTeam bool
First int32 First int32
After string After string
LastUpdateAt float64 LastUpdateAt float64
@@ -263,7 +266,29 @@ func (*resolver) ChannelMembers(ctx context.Context, args struct {
} }
} }
members, err := c.App.Srv().Store.Channel().GetMembersForUserWithCursor(args.UserID, afterChannel, afterUser, limit, int(args.LastUpdateAt)) if args.TeamID != "" {
if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), args.TeamID, model.PermissionViewTeam) {
primaryTeam := *c.App.Config().TeamSettings.ExperimentalPrimaryTeam
if primaryTeam != "" {
team, appErr := c.App.GetTeamByName(primaryTeam)
if appErr != nil {
return []*channelMember{}, appErr
}
args.TeamID = team.Id
} else {
return []*channelMember{}, nil
}
}
}
opts := &store.ChannelMemberGraphQLSearchOpts{
AfterChannel: afterChannel,
AfterUser: afterUser,
Limit: limit,
LastUpdateAt: int(args.LastUpdateAt),
ExcludeTeam: args.ExcludeTeam,
}
members, err := c.App.Srv().Store.Channel().GetMembersForUserWithCursor(args.UserID, args.TeamID, opts)
if err != nil { if err != nil {
return nil, err return nil, err
} }

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

@@ -41,7 +41,7 @@ func (cm *channelMember) Channel(ctx context.Context) (*channel, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
channel := result.(*model.Channel) channel := result.(*channel)
if channel.Type == model.ChannelTypeOpen { if channel.Type == model.ChannelTypeOpen {
if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), channel.TeamId, model.PermissionReadPublicChannel) && if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), channel.TeamId, model.PermissionReadPublicChannel) &&
@@ -56,20 +56,7 @@ func (cm *channelMember) Channel(ctx context.Context) (*channel, error) {
} }
} }
appErr := c.App.FillInChannelProps(channel) return channel, nil
if appErr != nil {
return nil, appErr
}
res, err := postProcessChannels(c, []*model.Channel{channel})
if err != nil {
return nil, err
}
// A bit of defence-in-depth; can probably be removed after a deeper look.
if len(res) != 1 {
return nil, fmt.Errorf("postProcessChannels: incorrect number of channels returned %d", len(res))
}
return res[0], nil
} }
func graphQLChannelsLoader(ctx context.Context, keys dataloader.Keys) []*dataloader.Result { func graphQLChannelsLoader(ctx context.Context, keys dataloader.Keys) []*dataloader.Result {
@@ -98,7 +85,7 @@ func graphQLChannelsLoader(ctx context.Context, keys dataloader.Keys) []*dataloa
return result return result
} }
func getGraphQLChannels(c *web.Context, channelIDs []string) ([]*model.Channel, error) { func getGraphQLChannels(c *web.Context, channelIDs []string) ([]*channel, error) {
channels, appErr := c.App.GetChannels(channelIDs) channels, appErr := c.App.GetChannels(channelIDs)
if appErr != nil { if appErr != nil {
return nil, appErr return nil, appErr
@@ -108,18 +95,28 @@ func getGraphQLChannels(c *web.Context, channelIDs []string) ([]*model.Channel,
return nil, fmt.Errorf("all channels were not found. Requested %d; Found %d", len(channelIDs), len(channels)) return nil, fmt.Errorf("all channels were not found. Requested %d; Found %d", len(channelIDs), len(channels))
} }
appErr = c.App.FillInChannelsProps(model.ChannelList(channels))
if appErr != nil {
return nil, appErr
}
res, err := postProcessChannels(c, channels)
if err != nil {
return nil, err
}
// The channels need to be in the exact same order as the input slice. // The channels need to be in the exact same order as the input slice.
tmp := make(map[string]*model.Channel) tmp := make(map[string]*channel)
for _, ch := range channels { for _, ch := range res {
tmp[ch.Id] = ch tmp[ch.Id] = ch
} }
// We reuse the same slice and just rewrite the channels. // We reuse the same slice and just rewrite the channels.
for i, id := range channelIDs { for i, id := range channelIDs {
channels[i] = tmp[id] res[i] = tmp[id]
} }
return channels, nil return res, nil
} }
func (cm *channelMember) Roles_(ctx context.Context) ([]*model.Role, error) { func (cm *channelMember) Roles_(ctx context.Context) ([]*model.Role, error) {

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

@@ -292,6 +292,45 @@ func TestGraphQLChannelMembers(t *testing.T) {
require.Len(t, resp.Errors, 1) require.Len(t, resp.Errors, 1)
}) })
t.Run("team_filter", func(t *testing.T) {
query := `query channelMembers($teamId: String, $excludeTeam: Boolean = false) {
channelMembers(userId: "me", teamId: $teamId, excludeTeam: $excludeTeam) {
channel {
id
}
}
}
`
input := graphQLInput{
OperationName: "channelMembers",
Query: query,
Variables: map[string]interface{}{
"teamId": th.BasicTeam.Id,
},
}
resp, err := th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.ChannelMembers, 5)
input = graphQLInput{
OperationName: "channelMembers",
Query: query,
Variables: map[string]interface{}{
"teamId": th.BasicTeam.Id,
"excludeTeam": true,
},
}
resp, err = th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
assert.Len(t, q.ChannelMembers, 4)
})
t.Run("UpdateAt", func(t *testing.T) { t.Run("UpdateAt", func(t *testing.T) {
query := `query channelMembers($first: Int, $after: String = "", $lastUpdateAt: Float) { query := `query channelMembers($first: Int, $after: String = "", $lastUpdateAt: Float) {
channelMembers(userId: "me", first: $first, after: $after, lastUpdateAt: $lastUpdateAt) { channelMembers(userId: "me", first: $first, after: $after, lastUpdateAt: $lastUpdateAt) {

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

@@ -14,11 +14,6 @@ import (
) )
func getGraphQLTeam(ctx context.Context, id string) (*model.Team, error) { func getGraphQLTeam(ctx context.Context, id string) (*model.Team, error) {
c, err := getCtx(ctx)
if err != nil {
return nil, err
}
loader, err := getTeamsLoader(ctx) loader, err := getTeamsLoader(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -30,15 +25,6 @@ func getGraphQLTeam(ctx context.Context, id string) (*model.Team, error) {
return nil, err return nil, err
} }
team := result.(*model.Team) team := result.(*model.Team)
team = team.ShallowCopy()
if (!team.AllowOpenInvite || team.Type != model.TeamOpen) &&
!c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), team.Id, model.PermissionViewTeam) {
c.SetPermissionError(model.PermissionViewTeam)
return nil, c.Err
}
team = c.App.SanitizeTeam(*c.AppContext.Session(), team)
return team, nil return team, nil
} }
@@ -78,6 +64,18 @@ func getGraphQLTeams(c *web.Context, teamIDs []string) ([]*model.Team, error) {
return nil, fmt.Errorf("All teams were not found. Requested %d; Found %d", len(teamIDs), len(teams)) return nil, fmt.Errorf("All teams were not found. Requested %d; Found %d", len(teamIDs), len(teams))
} }
// We pre-calculate this so that it's not computed in separate goroutines outside
// the dataloader.
for i := range teams {
if (!teams[i].AllowOpenInvite || teams[i].Type != model.TeamOpen) &&
!c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), teams[i].Id, model.PermissionViewTeam) {
c.SetPermissionError(model.PermissionViewTeam)
return nil, c.Err
}
teams[i] = c.App.SanitizeTeam(*c.AppContext.Session(), teams[i])
}
// The teams need to be in the exact same order as the input slice. // The teams need to be in the exact same order as the input slice.
tmp := make(map[string]*model.Team) tmp := make(map[string]*model.Team)
for _, ch := range teams { for _, ch := range teams {

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

@@ -19,6 +19,8 @@ type Query {
since: Float!): [String!]! since: Float!): [String!]!
channelMembers(userId: String!, channelMembers(userId: String!,
channelId: String = "", channelId: String = "",
teamId: String = "",
excludeTeam: Boolean = false,
first: Int = 60, first: Int = 60,
after: String = "", after: String = "",
lastUpdateAt: Float = 0): [ChannelMember]! lastUpdateAt: Float = 0): [ChannelMember]!

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

@@ -284,8 +284,8 @@ func generateLayer(name, templateFile string) ([]byte, error) {
switch param.Type { switch param.Type {
case "ChannelSearchOpts", "UserGetByIdsOpts", "ThreadMembershipOpts": case "ChannelSearchOpts", "UserGetByIdsOpts", "ThreadMembershipOpts":
paramsWithType = append(paramsWithType, fmt.Sprintf("%s store.%s", param.Name, param.Type)) paramsWithType = append(paramsWithType, fmt.Sprintf("%s store.%s", param.Name, param.Type))
case "*UserGetByIdsOpts": case "*UserGetByIdsOpts", "*ChannelMemberGraphQLSearchOpts":
paramsWithType = append(paramsWithType, fmt.Sprintf("%s *store.UserGetByIdsOpts", param.Name)) paramsWithType = append(paramsWithType, fmt.Sprintf("%s *store.%s", param.Name, strings.TrimPrefix(param.Type, "*")))
default: default:
paramsWithType = append(paramsWithType, fmt.Sprintf("%s %s", param.Name, param.Type)) paramsWithType = append(paramsWithType, fmt.Sprintf("%s %s", param.Name, param.Type))
} }
@@ -298,8 +298,8 @@ func generateLayer(name, templateFile string) ([]byte, error) {
switch param.Type { switch param.Type {
case "ChannelSearchOpts", "UserGetByIdsOpts", "ThreadMembershipOpts": case "ChannelSearchOpts", "UserGetByIdsOpts", "ThreadMembershipOpts":
paramsWithType = append(paramsWithType, fmt.Sprintf("%s store.%s", param.Name, param.Type)) paramsWithType = append(paramsWithType, fmt.Sprintf("%s store.%s", param.Name, param.Type))
case "*UserGetByIdsOpts": case "*UserGetByIdsOpts", "*ChannelMemberGraphQLSearchOpts":
paramsWithType = append(paramsWithType, fmt.Sprintf("%s *store.UserGetByIdsOpts", param.Name)) paramsWithType = append(paramsWithType, fmt.Sprintf("%s *store.%s", param.Name, strings.TrimPrefix(param.Type, "*")))
default: default:
paramsWithType = append(paramsWithType, fmt.Sprintf("%s %s", param.Name, param.Type)) paramsWithType = append(paramsWithType, fmt.Sprintf("%s %s", param.Name, param.Type))
} }

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

@@ -1478,7 +1478,7 @@ func (s *OpenTracingLayerChannelStore) GetMembersForUser(teamID string, userID s
return result, err return result, err
} }
func (s *OpenTracingLayerChannelStore) GetMembersForUserWithCursor(userID string, afterChannel string, afterUser string, limit int, lastUpdateAt int) (model.ChannelMembers, error) { func (s *OpenTracingLayerChannelStore) GetMembersForUserWithCursor(userID string, teamID string, opts *store.ChannelMemberGraphQLSearchOpts) (model.ChannelMembers, error) {
origCtx := s.Root.Store.Context() origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.GetMembersForUserWithCursor") span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.GetMembersForUserWithCursor")
s.Root.Store.SetContext(newCtx) s.Root.Store.SetContext(newCtx)
@@ -1487,7 +1487,7 @@ func (s *OpenTracingLayerChannelStore) GetMembersForUserWithCursor(userID string
}() }()
defer span.Finish() defer span.Finish()
result, err := s.ChannelStore.GetMembersForUserWithCursor(userID, afterChannel, afterUser, limit, lastUpdateAt) result, err := s.ChannelStore.GetMembersForUserWithCursor(userID, teamID, opts)
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)

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

@@ -1660,11 +1660,11 @@ func (s *RetryLayerChannelStore) GetMembersForUser(teamID string, userID string)
} }
func (s *RetryLayerChannelStore) GetMembersForUserWithCursor(userID string, afterChannel string, afterUser string, limit int, lastUpdateAt int) (model.ChannelMembers, error) { func (s *RetryLayerChannelStore) GetMembersForUserWithCursor(userID string, teamID string, opts *store.ChannelMemberGraphQLSearchOpts) (model.ChannelMembers, error) {
tries := 0 tries := 0
for { for {
result, err := s.ChannelStore.GetMembersForUserWithCursor(userID, afterChannel, afterUser, limit, lastUpdateAt) result, err := s.ChannelStore.GetMembersForUserWithCursor(userID, teamID, opts)
if err == nil { if err == nil {
return result, nil return result, nil
} }

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

@@ -2814,7 +2814,7 @@ func (s SqlChannelStore) GetMembersForUser(teamID string, userID string) (model.
return dbMembers.ToModel(), nil return dbMembers.ToModel(), nil
} }
func (s SqlChannelStore) GetMembersForUserWithCursor(userID, afterChannel, afterUser string, limit, lastUpdateAt int) (model.ChannelMembers, error) { func (s SqlChannelStore) GetMembersForUserWithCursor(userID, teamID string, opts *store.ChannelMemberGraphQLSearchOpts) (model.ChannelMembers, error) {
query := s.getQueryBuilder(). query := s.getQueryBuilder().
Select("ChannelMembers.*", Select("ChannelMembers.*",
"TeamScheme.DefaultChannelGuestRole TeamSchemeDefaultGuestRole", "TeamScheme.DefaultChannelGuestRole TeamSchemeDefaultGuestRole",
@@ -2834,20 +2834,36 @@ func (s SqlChannelStore) GetMembersForUserWithCursor(userID, afterChannel, after
}). }).
OrderBy("ChannelId, UserId ASC"). OrderBy("ChannelId, UserId ASC").
// The limit is verified at the GraphQL layer. // The limit is verified at the GraphQL layer.
Limit(uint64(limit)) Limit(uint64(opts.Limit))
if afterChannel != "" && afterUser != "" { if teamID != "" {
if opts.ExcludeTeam {
// Exclude this team and DM/GMs
query = query.Where(sq.And{
sq.NotEq{"Channels.TeamId": teamID},
sq.NotEq{"Channels.TeamId": ""},
})
} else {
// Include this team and DM/GMs
query = query.Where(sq.Or{ query = query.Where(sq.Or{
sq.Gt{"ChannelMembers.ChannelId": afterChannel}, sq.Eq{"Channels.TeamId": teamID},
sq.Eq{"Channels.TeamId": ""},
})
}
}
if opts.AfterChannel != "" && opts.AfterUser != "" {
query = query.Where(sq.Or{
sq.Gt{"ChannelMembers.ChannelId": opts.AfterChannel},
sq.And{ sq.And{
sq.Eq{"ChannelMembers.ChannelId": afterChannel}, sq.Eq{"ChannelMembers.ChannelId": opts.AfterChannel},
sq.Gt{"ChannelMembers.UserId": afterUser}, sq.Gt{"ChannelMembers.UserId": opts.AfterUser},
}, },
}) })
} }
if lastUpdateAt != 0 { if opts.LastUpdateAt != 0 {
query = query.Where(sq.GtOrEq{"ChannelMembers.LastUpdateAt": lastUpdateAt}) query = query.Where(sq.GtOrEq{"ChannelMembers.LastUpdateAt": opts.LastUpdateAt})
} }
queryString, args, err := query.ToSql() queryString, args, err := query.ToSql()

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

@@ -234,7 +234,7 @@ type ChannelStore interface {
GetMembersForUser(teamID string, userID string) (model.ChannelMembers, error) GetMembersForUser(teamID string, userID string) (model.ChannelMembers, error)
GetTeamMembersForChannel(channelID string) ([]string, error) GetTeamMembersForChannel(channelID string) ([]string, error)
GetMembersForUserWithPagination(userID string, page, perPage int) (model.ChannelMembersWithTeamData, error) GetMembersForUserWithPagination(userID string, page, perPage int) (model.ChannelMembersWithTeamData, error)
GetMembersForUserWithCursor(userID, afterChannel, afterUser string, limit, lastUpdateAt int) (model.ChannelMembers, error) GetMembersForUserWithCursor(userID, teamID string, opts *ChannelMemberGraphQLSearchOpts) (model.ChannelMembers, error)
Autocomplete(userID, term string, includeDeleted bool) (model.ChannelListWithTeamData, error) Autocomplete(userID, term string, includeDeleted bool) (model.ChannelListWithTeamData, error)
AutocompleteInTeam(teamID, userID, term string, includeDeleted bool) (model.ChannelList, error) AutocompleteInTeam(teamID, userID, term string, includeDeleted bool) (model.ChannelList, error)
AutocompleteInTeamForSearch(teamID string, userID string, term string, includeDeleted bool) (model.ChannelList, error) AutocompleteInTeamForSearch(teamID string, userID string, term string, includeDeleted bool) (model.ChannelList, error)
@@ -983,3 +983,13 @@ type ThreadMembershipOpts struct {
// should be updated. // should be updated.
UpdateParticipants bool UpdateParticipants bool
} }
// ChannelMemberGraphQLSearchOpts contains the options for a graphQL query
// to get the channel members.
type ChannelMemberGraphQLSearchOpts struct {
AfterChannel string
AfterUser string
Limit int
LastUpdateAt int
ExcludeTeam bool
}

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

@@ -4438,6 +4438,14 @@ func testChannelStoreGetMembersForUserWithCursor(t *testing.T, ss store.Store) {
_, err := ss.Team().Save(&t1) _, err := ss.Team().Save(&t1)
require.NoError(t, err) require.NoError(t, err)
t2 := model.Team{}
t2.DisplayName = "Team2"
t2.Name = NewTestId()
t2.Email = MakeEmail()
t2.Type = model.TeamOpen
_, err = ss.Team().Save(&t2)
require.NoError(t, err)
o1 := model.Channel{} o1 := model.Channel{}
o1.TeamId = t1.Id o1.TeamId = t1.Id
o1.DisplayName = "Channel1" o1.DisplayName = "Channel1"
@@ -4454,6 +4462,14 @@ func testChannelStoreGetMembersForUserWithCursor(t *testing.T, ss store.Store) {
_, nErr = ss.Channel().Save(&o2, -1) _, nErr = ss.Channel().Save(&o2, -1)
require.NoError(t, nErr) require.NoError(t, nErr)
o3 := model.Channel{}
o3.TeamId = t2.Id
o3.DisplayName = "Channel3"
o3.Name = NewTestId()
o3.Type = model.ChannelTypeOpen
_, nErr = ss.Channel().Save(&o3, -1)
require.NoError(t, nErr)
m1 := model.ChannelMember{} m1 := model.ChannelMember{}
m1.ChannelId = o1.Id m1.ChannelId = o1.Id
m1.UserId = model.NewId() m1.UserId = model.NewId()
@@ -4468,15 +4484,29 @@ func testChannelStoreGetMembersForUserWithCursor(t *testing.T, ss store.Store) {
_, err = ss.Channel().SaveMember(&m2) _, err = ss.Channel().SaveMember(&m2)
require.NoError(t, err) require.NoError(t, err)
m3 := model.ChannelMember{}
m3.ChannelId = o3.Id
m3.UserId = m1.UserId
m3.NotifyProps = model.GetDefaultChannelNotifyProps()
_, err = ss.Channel().SaveMember(&m3)
require.NoError(t, err)
t.Run("with channels", func(t *testing.T) { t.Run("with channels", func(t *testing.T) {
var members model.ChannelMembers var members model.ChannelMembers
members, err = ss.Channel().GetMembersForUserWithCursor(m1.UserId, "", "", 1, 0) opts := &store.ChannelMemberGraphQLSearchOpts{
Limit: 1,
}
members, err = ss.Channel().GetMembersForUserWithCursor(m1.UserId, "", opts)
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, members, 1) assert.Len(t, members, 1)
members, err = ss.Channel().GetMembersForUserWithCursor(m1.UserId, "", "", 3, 0) opts.Limit = 3
members, err = ss.Channel().GetMembersForUserWithCursor(m1.UserId, "", opts)
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, members, 2) assert.Len(t, members, 3)
members, err = ss.Channel().GetMembersForUserWithCursor(m1.UserId, members[0].ChannelId, m1.UserId, 1, 0) opts.AfterChannel = members[0].ChannelId
opts.AfterUser = m1.UserId
opts.Limit = 1
members, err = ss.Channel().GetMembersForUserWithCursor(m1.UserId, "", opts)
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, members, 1) assert.Len(t, members, 1)
}) })
@@ -4495,15 +4525,41 @@ func testChannelStoreGetMembersForUserWithCursor(t *testing.T, ss store.Store) {
_, nErr = ss.Channel().CreateDirectChannel(&u3, &u4) _, nErr = ss.Channel().CreateDirectChannel(&u3, &u4)
require.NoError(t, nErr) require.NoError(t, nErr)
members, err2 := ss.Channel().GetMembersForUserWithCursor(m1.UserId, "", "", 10, 0) opts := &store.ChannelMemberGraphQLSearchOpts{
Limit: 10,
}
members, err2 := ss.Channel().GetMembersForUserWithCursor(m1.UserId, "", opts)
require.NoError(t, err2) require.NoError(t, err2)
assert.Len(t, members, 4) assert.Len(t, members, 5)
members, err2 = ss.Channel().GetMembersForUserWithCursor(m1.UserId, "", "", 2, 0) opts.Limit = 2
members, err2 = ss.Channel().GetMembersForUserWithCursor(m1.UserId, "", opts)
require.NoError(t, err2) require.NoError(t, err2)
assert.Len(t, members, 2) assert.Len(t, members, 2)
members, err2 = ss.Channel().GetMembersForUserWithCursor(m1.UserId, members[1].ChannelId, m1.UserId, 2, 0) opts.AfterChannel = members[1].ChannelId
opts.AfterUser = m1.UserId
opts.Limit = 2
members, err2 = ss.Channel().GetMembersForUserWithCursor(m1.UserId, "", opts)
require.NoError(t, err2)
assert.Len(t, members, 2)
})
t.Run("for a specific team", func(t *testing.T) {
opts := &store.ChannelMemberGraphQLSearchOpts{
Limit: 10,
}
members, err2 := ss.Channel().GetMembersForUserWithCursor(m1.UserId, t2.Id, opts)
require.NoError(t, err2)
assert.Len(t, members, 3)
})
t.Run("excluding a team", func(t *testing.T) {
opts := &store.ChannelMemberGraphQLSearchOpts{
Limit: 10,
ExcludeTeam: true,
}
members, err2 := ss.Channel().GetMembersForUserWithCursor(m1.UserId, t2.Id, opts)
require.NoError(t, err2) require.NoError(t, err2)
assert.Len(t, members, 2) assert.Len(t, members, 2)
}) })
@@ -4529,17 +4585,24 @@ func testChannelStoreGetMembersForUserWithCursor(t *testing.T, ss store.Store) {
_, err = ss.Channel().SaveMember(cm) _, err = ss.Channel().SaveMember(cm)
require.NoError(t, err) require.NoError(t, err)
} }
members, err := ss.Channel().GetMembersForUserWithCursor(m1.UserId, "", "", 10, 0) opts := &store.ChannelMemberGraphQLSearchOpts{
Limit: 10,
}
members, err := ss.Channel().GetMembersForUserWithCursor(m1.UserId, "", opts)
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, members, 5) assert.Len(t, members, 6)
members, err = ss.Channel().GetMembersForUserWithCursor(m1.UserId, "", "", 2, 0) opts.Limit = 2
members, err = ss.Channel().GetMembersForUserWithCursor(m1.UserId, "", opts)
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, members, 2) assert.Len(t, members, 2)
members, err = ss.Channel().GetMembersForUserWithCursor(m1.UserId, members[1].ChannelId, m1.UserId, 10, 0) opts.AfterChannel = members[1].ChannelId
opts.AfterUser = m1.UserId
opts.Limit = 10
members, err = ss.Channel().GetMembersForUserWithCursor(m1.UserId, "", opts)
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, members, 3) assert.Len(t, members, 4)
}) })
} }

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

@@ -1165,13 +1165,13 @@ func (_m *ChannelStore) GetMembersForUser(teamID string, userID string) (model.C
return r0, r1 return r0, r1
} }
// GetMembersForUserWithCursor provides a mock function with given fields: userID, afterChannel, afterUser, limit, lastUpdateAt // GetMembersForUserWithCursor provides a mock function with given fields: userID, teamID, opts
func (_m *ChannelStore) GetMembersForUserWithCursor(userID string, afterChannel string, afterUser string, limit int, lastUpdateAt int) (model.ChannelMembers, error) { func (_m *ChannelStore) GetMembersForUserWithCursor(userID string, teamID string, opts *store.ChannelMemberGraphQLSearchOpts) (model.ChannelMembers, error) {
ret := _m.Called(userID, afterChannel, afterUser, limit, lastUpdateAt) ret := _m.Called(userID, teamID, opts)
var r0 model.ChannelMembers var r0 model.ChannelMembers
if rf, ok := ret.Get(0).(func(string, string, string, int, int) model.ChannelMembers); ok { if rf, ok := ret.Get(0).(func(string, string, *store.ChannelMemberGraphQLSearchOpts) model.ChannelMembers); ok {
r0 = rf(userID, afterChannel, afterUser, limit, lastUpdateAt) r0 = rf(userID, teamID, opts)
} else { } else {
if ret.Get(0) != nil { if ret.Get(0) != nil {
r0 = ret.Get(0).(model.ChannelMembers) r0 = ret.Get(0).(model.ChannelMembers)
@@ -1179,8 +1179,8 @@ func (_m *ChannelStore) GetMembersForUserWithCursor(userID string, afterChannel
} }
var r1 error var r1 error
if rf, ok := ret.Get(1).(func(string, string, string, int, int) error); ok { if rf, ok := ret.Get(1).(func(string, string, *store.ChannelMemberGraphQLSearchOpts) error); ok {
r1 = rf(userID, afterChannel, afterUser, limit, lastUpdateAt) r1 = rf(userID, teamID, opts)
} else { } else {
r1 = ret.Error(1) r1 = ret.Error(1)
} }

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

@@ -1365,10 +1365,10 @@ func (s *TimerLayerChannelStore) GetMembersForUser(teamID string, userID string)
return result, err return result, err
} }
func (s *TimerLayerChannelStore) GetMembersForUserWithCursor(userID string, afterChannel string, afterUser string, limit int, lastUpdateAt int) (model.ChannelMembers, error) { func (s *TimerLayerChannelStore) GetMembersForUserWithCursor(userID string, teamID string, opts *store.ChannelMemberGraphQLSearchOpts) (model.ChannelMembers, error) {
start := timemodule.Now() start := timemodule.Now()
result, err := s.ChannelStore.GetMembersForUserWithCursor(userID, afterChannel, afterUser, limit, lastUpdateAt) result, err := s.ChannelStore.GetMembersForUserWithCursor(userID, teamID, opts)
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
if s.Root.Metrics != nil { if s.Root.Metrics != nil {