MM-16224: Adds new API endpoint + (App & Client & Store) to retrieve the difference between the set of channel members and given group members. (#11186)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
4d223ba3a2
Коммит
e15a75a2ec
@@ -1128,3 +1128,80 @@ func (s *SqlGroupStore) CountTeamMembersMinusGroupMembers(teamID string, groupID
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (s *SqlGroupStore) channelMembersMinusGroupMembersQuery(channelID string, groupIDs []string, isCount bool) squirrel.SelectBuilder {
|
||||
var selectStr string
|
||||
|
||||
if isCount {
|
||||
selectStr = "count(DISTINCT Users.Id)"
|
||||
} else {
|
||||
tmpl := "Users.*, ChannelMembers.SchemeGuest, ChannelMembers.SchemeAdmin, ChannelMembers.SchemeUser, %s AS GroupIDs"
|
||||
if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
|
||||
selectStr = fmt.Sprintf(tmpl, "group_concat(UserGroups.Id)")
|
||||
} else {
|
||||
selectStr = fmt.Sprintf(tmpl, "string_agg(UserGroups.Id, ',')")
|
||||
}
|
||||
}
|
||||
|
||||
subQuery := s.getQueryBuilder().Select("GroupMembers.UserId").
|
||||
From("GroupMembers").
|
||||
Join("UserGroups ON UserGroups.Id = GroupMembers.GroupId").
|
||||
Where("GroupMembers.DeleteAt = 0").
|
||||
Where(fmt.Sprintf("GroupMembers.GroupId IN ('%s')", strings.Join(groupIDs, "', '")))
|
||||
|
||||
sql, _ := subQuery.MustSql()
|
||||
|
||||
query := s.getQueryBuilder().Select(selectStr).
|
||||
From("ChannelMembers").
|
||||
Join("Channels ON Channels.Id = ChannelMembers.ChannelId").
|
||||
Join("Users ON Users.Id = ChannelMembers.UserId").
|
||||
LeftJoin("Bots ON Bots.UserId = ChannelMembers.UserId").
|
||||
Join("GroupMembers ON GroupMembers.UserId = Users.Id").
|
||||
Join("UserGroups ON UserGroups.Id = GroupMembers.GroupId").
|
||||
Where("Channels.DeleteAt = 0").
|
||||
Where("Users.DeleteAt = 0").
|
||||
Where("Bots.UserId IS NULL").
|
||||
Where("Channels.Id = ?", channelID).
|
||||
Where(fmt.Sprintf("Users.Id NOT IN (%s)", sql))
|
||||
|
||||
if !isCount {
|
||||
query = query.GroupBy("Users.Id, ChannelMembers.SchemeGuest, ChannelMembers.SchemeAdmin, ChannelMembers.SchemeUser")
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
// ChannelMembersMinusGroupMembers returns the set of users in the given channel minus the set of users in the given
|
||||
// groups.
|
||||
func (s *SqlGroupStore) ChannelMembersMinusGroupMembers(channelID string, groupIDs []string, page, perPage int) ([]*model.UserWithGroups, *model.AppError) {
|
||||
query := s.channelMembersMinusGroupMembersQuery(channelID, groupIDs, false)
|
||||
query = query.OrderBy("Users.Id").Limit(uint64(perPage)).Offset(uint64(page * perPage))
|
||||
|
||||
queryString, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("SqlGroupStore.ChannelMembersMinusGroupMembers", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
var users []*model.UserWithGroups
|
||||
if _, err = s.GetReplica().Select(&users, queryString, args...); err != nil {
|
||||
return nil, model.NewAppError("SqlGroupStore.ChannelMembersMinusGroupMembers", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// CountChannelMembersMinusGroupMembers returns the count of the set of users in the given channel minus the set of users
|
||||
// in the given groups.
|
||||
func (s *SqlGroupStore) CountChannelMembersMinusGroupMembers(channelID string, groupIDs []string) (int64, *model.AppError) {
|
||||
queryString, args, err := s.channelMembersMinusGroupMembersQuery(channelID, groupIDs, true).ToSql()
|
||||
if err != nil {
|
||||
return 0, model.NewAppError("SqlGroupStore.CountChannelMembersMinusGroupMembers", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
var count int64
|
||||
if count, err = s.GetReplica().SelectInt(queryString, args...); err != nil {
|
||||
return 0, model.NewAppError("SqlGroupStore.CountChannelMembersMinusGroupMembers", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
@@ -605,6 +605,8 @@ type GroupStore interface {
|
||||
|
||||
TeamMembersMinusGroupMembers(teamID string, groupIDs []string, page, perPage int) ([]*model.UserWithGroups, *model.AppError)
|
||||
CountTeamMembersMinusGroupMembers(teamID string, groupIDs []string) (int64, *model.AppError)
|
||||
ChannelMembersMinusGroupMembers(channelID string, groupIDs []string, page, perPage int) ([]*model.UserWithGroups, *model.AppError)
|
||||
CountChannelMembersMinusGroupMembers(channelID string, groupIDs []string) (int64, *model.AppError)
|
||||
}
|
||||
|
||||
type LinkMetadataStore interface {
|
||||
|
||||
@@ -47,6 +47,7 @@ func TestGroupStore(t *testing.T, ss store.Store) {
|
||||
t.Run("GetGroups", func(t *testing.T) { testGetGroups(t, ss) })
|
||||
|
||||
t.Run("TeamMembersMinusGroupMembers", func(t *testing.T) { testTeamMembersMinusGroupMembers(t, ss) })
|
||||
t.Run("ChannelMembersMinusGroupMembers", func(t *testing.T) { testChannelMembersMinusGroupMembers(t, ss) })
|
||||
}
|
||||
|
||||
func testGroupStoreCreate(t *testing.T, ss store.Store) {
|
||||
@@ -2392,3 +2393,157 @@ func testTeamMembersMinusGroupMembers(t *testing.T, ss store.Store) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testChannelMembersMinusGroupMembers(t *testing.T, ss store.Store) {
|
||||
const numberOfGroups = 3
|
||||
const numberOfUsers = 4
|
||||
|
||||
groups := []*model.Group{}
|
||||
users := []*model.User{}
|
||||
|
||||
channel := &model.Channel{
|
||||
TeamId: model.NewId(),
|
||||
DisplayName: "A Name",
|
||||
Name: model.NewId(),
|
||||
Type: model.CHANNEL_PRIVATE,
|
||||
GroupConstrained: model.NewBool(true),
|
||||
}
|
||||
channel, err := ss.Channel().Save(channel, 9999)
|
||||
require.Nil(t, err)
|
||||
|
||||
for i := 0; i < numberOfUsers; i++ {
|
||||
user := &model.User{
|
||||
Email: MakeEmail(),
|
||||
Username: model.NewId(),
|
||||
}
|
||||
res := <-ss.User().Save(user)
|
||||
require.Nil(t, res.Err)
|
||||
user = res.Data.(*model.User)
|
||||
users = append(users, user)
|
||||
|
||||
trueOrFalse := int(math.Mod(float64(i), 2)) == 0
|
||||
res = <-ss.Channel().SaveMember(&model.ChannelMember{
|
||||
ChannelId: channel.Id,
|
||||
UserId: user.Id,
|
||||
SchemeUser: trueOrFalse,
|
||||
SchemeAdmin: !trueOrFalse,
|
||||
NotifyProps: model.GetDefaultChannelNotifyProps(),
|
||||
})
|
||||
require.Nil(t, res.Err)
|
||||
}
|
||||
|
||||
for i := 0; i < numberOfGroups; i++ {
|
||||
group := &model.Group{
|
||||
Name: fmt.Sprintf("n_%d_%s", i, model.NewId()),
|
||||
DisplayName: model.NewId(),
|
||||
Source: model.GroupSourceLdap,
|
||||
Description: model.NewId(),
|
||||
RemoteId: model.NewId(),
|
||||
}
|
||||
res := <-ss.Group().Create(group)
|
||||
require.Nil(t, res.Err)
|
||||
group = res.Data.(*model.Group)
|
||||
groups = append(groups, group)
|
||||
}
|
||||
|
||||
sort.Slice(users, func(i, j int) bool {
|
||||
return users[i].Id < users[j].Id
|
||||
})
|
||||
|
||||
// Add even users to even group, and the inverse
|
||||
for i := 0; i < numberOfUsers; i++ {
|
||||
groupIndex := int(math.Mod(float64(i), 2))
|
||||
res := <-ss.Group().CreateOrRestoreMember(groups[groupIndex].Id, users[i].Id)
|
||||
require.Nil(t, res.Err)
|
||||
|
||||
// Add everyone to group 2
|
||||
res = <-ss.Group().CreateOrRestoreMember(groups[numberOfGroups-1].Id, users[i].Id)
|
||||
require.Nil(t, res.Err)
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
expectedUserIDs []string
|
||||
expectedTotalCount int64
|
||||
groupIDs []string
|
||||
page int
|
||||
perPage int
|
||||
setup func()
|
||||
teardown func()
|
||||
}{
|
||||
"No group IDs, all members": {
|
||||
expectedUserIDs: []string{users[0].Id, users[1].Id, users[2].Id, users[3].Id},
|
||||
expectedTotalCount: numberOfUsers,
|
||||
groupIDs: []string{},
|
||||
page: 0,
|
||||
perPage: 100,
|
||||
},
|
||||
"All members, page 1": {
|
||||
expectedUserIDs: []string{users[0].Id, users[1].Id},
|
||||
expectedTotalCount: numberOfUsers,
|
||||
groupIDs: []string{},
|
||||
page: 0,
|
||||
perPage: 2,
|
||||
},
|
||||
"All members, page 2": {
|
||||
expectedUserIDs: []string{users[2].Id, users[3].Id},
|
||||
expectedTotalCount: numberOfUsers,
|
||||
groupIDs: []string{},
|
||||
page: 1,
|
||||
perPage: 2,
|
||||
},
|
||||
"Group 1, even users would be removed": {
|
||||
expectedUserIDs: []string{users[0].Id, users[2].Id},
|
||||
expectedTotalCount: 2,
|
||||
groupIDs: []string{groups[1].Id},
|
||||
page: 0,
|
||||
perPage: 100,
|
||||
},
|
||||
"Group 0, odd users would be removed": {
|
||||
expectedUserIDs: []string{users[1].Id, users[3].Id},
|
||||
expectedTotalCount: 2,
|
||||
groupIDs: []string{groups[0].Id},
|
||||
page: 0,
|
||||
perPage: 100,
|
||||
},
|
||||
"All groups, no users would be removed": {
|
||||
expectedUserIDs: []string{},
|
||||
expectedTotalCount: 0,
|
||||
groupIDs: []string{groups[0].Id, groups[1].Id},
|
||||
page: 0,
|
||||
perPage: 100,
|
||||
},
|
||||
}
|
||||
|
||||
mapUserIDs := func(users []*model.UserWithGroups) []string {
|
||||
ids := []string{}
|
||||
for _, user := range users {
|
||||
ids = append(ids, user.Id)
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
for tcName, tc := range testCases {
|
||||
t.Run(tcName, func(t *testing.T) {
|
||||
if tc.setup != nil {
|
||||
tc.setup()
|
||||
}
|
||||
|
||||
if tc.teardown != nil {
|
||||
defer tc.teardown()
|
||||
}
|
||||
|
||||
actual, err := ss.Group().ChannelMembersMinusGroupMembers(channel.Id, tc.groupIDs, tc.page, tc.perPage)
|
||||
require.Nil(t, err)
|
||||
require.ElementsMatch(t, tc.expectedUserIDs, mapUserIDs(actual))
|
||||
|
||||
for _, user := range actual {
|
||||
require.NotNil(t, user.GroupIDs)
|
||||
require.True(t, (user.SchemeAdmin || user.SchemeUser))
|
||||
}
|
||||
|
||||
actualCount, err := ss.Group().CountChannelMembersMinusGroupMembers(channel.Id, tc.groupIDs)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, tc.expectedTotalCount, actualCount)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,31 @@ type GroupStore struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// ChannelMembersMinusGroupMembers provides a mock function with given fields: channelID, groupIDs, page, perPage
|
||||
func (_m *GroupStore) ChannelMembersMinusGroupMembers(channelID string, groupIDs []string, page int, perPage int) ([]*model.UserWithGroups, *model.AppError) {
|
||||
ret := _m.Called(channelID, groupIDs, page, perPage)
|
||||
|
||||
var r0 []*model.UserWithGroups
|
||||
if rf, ok := ret.Get(0).(func(string, []string, int, int) []*model.UserWithGroups); ok {
|
||||
r0 = rf(channelID, groupIDs, page, perPage)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.UserWithGroups)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(1).(func(string, []string, int, int) *model.AppError); ok {
|
||||
r1 = rf(channelID, groupIDs, page, perPage)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// ChannelMembersToAdd provides a mock function with given fields: since
|
||||
func (_m *GroupStore) ChannelMembersToAdd(since int64) ([]*model.UserChannelIDPair, *model.AppError) {
|
||||
ret := _m.Called(since)
|
||||
@@ -63,6 +88,29 @@ func (_m *GroupStore) ChannelMembersToRemove() ([]*model.ChannelMember, *model.A
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// CountChannelMembersMinusGroupMembers provides a mock function with given fields: channelID, groupIDs
|
||||
func (_m *GroupStore) CountChannelMembersMinusGroupMembers(channelID string, groupIDs []string) (int64, *model.AppError) {
|
||||
ret := _m.Called(channelID, groupIDs)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(string, []string) int64); ok {
|
||||
r0 = rf(channelID, groupIDs)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(1).(func(string, []string) *model.AppError); ok {
|
||||
r1 = rf(channelID, groupIDs)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// CountGroupsByChannel provides a mock function with given fields: channelId, opts
|
||||
func (_m *GroupStore) CountGroupsByChannel(channelId string, opts model.GroupSearchOpts) (int64, *model.AppError) {
|
||||
ret := _m.Called(channelId, opts)
|
||||
|
||||
Ссылка в новой задаче
Block a user