MM-14897: Changes to be able to add and remove groups from channels. (#10794)

* MM-15162: Changes for LDAP groups removals phase.

* MM-14897: Changes to be able to add and remove groups from channels.

* Update model/client4.go

* MM-14897: PR-requested change to string interpolation.
Этот коммит содержится в:
Martin Kraft
2019-05-15 12:03:47 -04:00
коммит произвёл GitHub
родитель dd33bc13a7
Коммит 1b78f9debc
18 изменённых файлов: 475 добавлений и 143 удалений

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

@@ -483,18 +483,44 @@ func getGroupsByChannel(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) { var permission *model.Permission
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) channel, err := c.App.GetChannel(c.Params.ChannelId)
if err != nil {
c.Err = err
return
}
if channel.Type == model.CHANNEL_PRIVATE {
permission = model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS
} else {
permission = model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS
}
if !c.App.SessionHasPermissionToChannel(c.App.Session, c.Params.ChannelId, permission) {
c.SetPermissionError(permission)
return return
} }
groups, err := c.App.GetGroupsByChannel(c.Params.ChannelId, c.Params.Page, c.Params.PerPage) opts := model.GroupSearchOpts{
Q: c.Params.Q,
IncludeMemberCount: c.Params.IncludeMemberCount,
}
if c.Params.Paginate == nil || *c.Params.Paginate {
opts.PageOpts = &model.PageOpts{Page: c.Params.Page, PerPage: c.Params.PerPage}
}
groups, totalCount, err := c.App.GetGroupsByChannel(c.Params.ChannelId, opts)
if err != nil { if err != nil {
c.Err = err c.Err = err
return return
} }
b, marshalErr := json.Marshal(groups) b, marshalErr := json.Marshal(struct {
Groups []*model.Group `json:"groups"`
Count int `json:"total_group_count"`
}{
Groups: groups,
Count: totalCount,
})
if marshalErr != nil { if marshalErr != nil {
c.Err = model.NewAppError("Api4.getGroupsByChannel", "api.marshal_error", nil, marshalErr.Error(), http.StatusInternalServerError) c.Err = model.NewAppError("Api4.getGroupsByChannel", "api.marshal_error", nil, marshalErr.Error(), http.StatusInternalServerError)
return return
@@ -569,6 +595,26 @@ func getGroups(c *Context, w http.ResponseWriter, r *http.Request) {
opts.NotAssociatedToTeam = teamID opts.NotAssociatedToTeam = teamID
} }
channelID := c.Params.NotAssociatedToChannel
if len(channelID) == 26 {
channel, err := c.App.GetChannel(channelID)
if err != nil {
c.Err = err
return
}
var permission *model.Permission
if channel.Type == model.CHANNEL_PRIVATE {
permission = model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS
} else {
permission = model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS
}
if !c.App.SessionHasPermissionToChannel(c.App.Session, channelID, permission) {
c.SetPermissionError(permission)
return
}
opts.NotAssociatedToChannel = channelID
}
groups, err := c.App.GetGroups(c.Params.Page, c.Params.PerPage, opts) groups, err := c.App.GetGroups(c.Params.Page, c.Params.PerPage, opts)
if err != nil { if err != nil {
c.Err = err c.Err = err

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

@@ -8,9 +8,10 @@ import (
"net/http" "net/http"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/stretchr/testify/assert"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
) )
@@ -654,25 +655,34 @@ func TestGetGroupsByChannel(t *testing.T) {
}) })
assert.Nil(t, err) assert.Nil(t, err)
_, response := th.SystemAdminClient.GetGroupsByChannel("asdfasdf", 0, 60) opts := model.GroupSearchOpts{
PageOpts: &model.PageOpts{
Page: 0,
PerPage: 60,
},
}
_, _, response := th.SystemAdminClient.GetGroupsByChannel("asdfasdf", opts)
CheckBadRequestStatus(t, response) CheckBadRequestStatus(t, response)
th.App.SetLicense(nil) th.App.SetLicense(nil)
_, response = th.SystemAdminClient.GetGroupsByChannel(th.BasicChannel.Id, 0, 60) _, _, response = th.SystemAdminClient.GetGroupsByChannel(th.BasicChannel.Id, opts)
CheckNotImplementedStatus(t, response) CheckNotImplementedStatus(t, response)
th.App.SetLicense(model.NewTestLicense("ldap")) th.App.SetLicense(model.NewTestLicense("ldap"))
_, response = th.Client.GetGroupsByChannel(th.BasicChannel.Id, 0, 60) privateChannel := th.CreateChannelWithClient(th.SystemAdminClient, model.CHANNEL_PRIVATE)
_, _, response = th.Client.GetGroupsByChannel(privateChannel.Id, opts)
CheckForbiddenStatus(t, response) CheckForbiddenStatus(t, response)
groups, response := th.SystemAdminClient.GetGroupsByChannel(th.BasicChannel.Id, 0, 60) groups, _, response := th.SystemAdminClient.GetGroupsByChannel(th.BasicChannel.Id, opts)
assert.Nil(t, response.Error) assert.Nil(t, response.Error)
assert.ElementsMatch(t, []*model.Group{group}, groups) assert.ElementsMatch(t, []*model.Group{group}, groups)
groups, response = th.SystemAdminClient.GetGroupsByChannel(model.NewId(), 0, 60) groups, _, response = th.SystemAdminClient.GetGroupsByChannel(model.NewId(), opts)
assert.Nil(t, response.Error) assert.Equal(t, "store.sql_channel.get.existing.app_error", response.Error.Id)
assert.Empty(t, groups) assert.Empty(t, groups)
} }

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

@@ -165,12 +165,20 @@ func (a *App) ChannelMembersToRemove() ([]*model.ChannelMember, *model.AppError)
return result.Data.([]*model.ChannelMember), nil return result.Data.([]*model.ChannelMember), nil
} }
func (a *App) GetGroupsByChannel(channelId string, page, perPage int) ([]*model.Group, *model.AppError) { func (a *App) GetGroupsByChannel(channelId string, opts model.GroupSearchOpts) ([]*model.Group, int, *model.AppError) {
result := <-a.Srv.Store.Group().GetGroupsByChannel(channelId, page, perPage) result := <-a.Srv.Store.Group().GetGroupsByChannel(channelId, opts)
if result.Err != nil { if result.Err != nil {
return nil, result.Err return nil, 0, result.Err
} }
return result.Data.([]*model.Group), nil groups := result.Data.([]*model.Group)
result = <-a.Srv.Store.Group().CountGroupsByChannel(channelId, opts)
if result.Err != nil {
return nil, 0, result.Err
}
count := result.Data.(int64)
return groups, int(count), nil
} }
func (a *App) GetGroupsByTeam(teamId string, opts model.GroupSearchOpts) ([]*model.Group, int, *model.AppError) { func (a *App) GetGroupsByTeam(teamId string, opts model.GroupSearchOpts) ([]*model.Group, int, *model.AppError) {

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

@@ -215,11 +215,18 @@ func TestGetGroupsByChannel(t *testing.T) {
require.Nil(t, err) require.Nil(t, err)
require.NotNil(t, gs) require.NotNil(t, gs)
groups, err := th.App.GetGroupsByChannel(th.BasicChannel.Id, 0, 60) opts := model.GroupSearchOpts{
PageOpts: &model.PageOpts{
Page: 0,
PerPage: 60,
},
}
groups, _, err := th.App.GetGroupsByChannel(th.BasicChannel.Id, opts)
require.Nil(t, err) require.Nil(t, err)
require.ElementsMatch(t, []*model.Group{group}, groups) require.ElementsMatch(t, []*model.Group{group}, groups)
groups, err = th.App.GetGroupsByChannel(model.NewId(), 0, 60) groups, _, err = th.App.GetGroupsByChannel(model.NewId(), opts)
require.Nil(t, err) require.Nil(t, err)
require.Empty(t, groups) require.Empty(t, groups)
} }

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

@@ -128,7 +128,7 @@ func channelGroupEnableCmdF(command *cobra.Command, args []string) error {
return errors.New("Unable to find channel '" + args[0] + "'") return errors.New("Unable to find channel '" + args[0] + "'")
} }
groups, appErr := a.GetGroupsByChannel(channel.Id, 0, 9999) groups, _, appErr := a.GetGroupsByChannel(channel.Id, model.GroupSearchOpts{})
if appErr != nil { if appErr != nil {
return appErr return appErr
} }
@@ -198,7 +198,7 @@ func channelGroupListCmdF(command *cobra.Command, args []string) error {
return errors.New("Unable to find channel '" + args[0] + "'") return errors.New("Unable to find channel '" + args[0] + "'")
} }
groups, appErr := a.GetGroupsByChannel(channel.Id, 0, 9999) groups, _, appErr := a.GetGroupsByChannel(channel.Id, model.GroupSearchOpts{})
if appErr != nil { if appErr != nil {
return appErr return appErr
} }

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

@@ -3311,15 +3311,27 @@ func (c *Client4) UnlinkLdapGroup(dn string) (*Group, *Response) {
} }
// GetGroupsByChannel retrieves the Mattermost Groups associated with a given channel // GetGroupsByChannel retrieves the Mattermost Groups associated with a given channel
func (c *Client4) GetGroupsByChannel(channelId string, page, perPage int) ([]*Group, *Response) { func (c *Client4) GetGroupsByChannel(channelId string, opts GroupSearchOpts) ([]*Group, int, *Response) {
path := fmt.Sprintf("%s/groups?page=%v&per_page=%v", c.GetChannelRoute(channelId), page, perPage) path := fmt.Sprintf("%s/groups?q=%v&include_member_count=%v", c.GetChannelRoute(channelId), opts.Q, opts.IncludeMemberCount)
if opts.PageOpts != nil {
path = fmt.Sprintf("%s&page=%v&per_page=%v", path, opts.PageOpts.Page, opts.PageOpts.PerPage)
}
r, appErr := c.DoApiGet(path, "") r, appErr := c.DoApiGet(path, "")
if appErr != nil { if appErr != nil {
return nil, BuildErrorResponse(r, appErr) return nil, 0, BuildErrorResponse(r, appErr)
} }
defer closeBody(r) defer closeBody(r)
return GroupsFromJson(r.Body), BuildResponse(r) responseData := struct {
Groups []*Group `json:"groups"`
Count int `json:"total_group_count"`
}{}
if err := json.NewDecoder(r.Body).Decode(&responseData); err != nil {
appErr := NewAppError("Api4.GetGroupsByChannel", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
return nil, 0, BuildErrorResponse(r, appErr)
}
return responseData.Groups, responseData.Count, BuildResponse(r)
} }
// GetGroupsByTeam retrieves the Mattermost Groups associated with a given team // GetGroupsByTeam retrieves the Mattermost Groups associated with a given team
@@ -3349,8 +3361,12 @@ func (c *Client4) GetGroupsByTeam(teamId string, opts GroupSearchOpts) ([]*Group
// GetGroups retrieves Mattermost Groups // GetGroups retrieves Mattermost Groups
func (c *Client4) GetGroups(opts GroupSearchOpts) ([]*Group, *Response) { func (c *Client4) GetGroups(opts GroupSearchOpts) ([]*Group, *Response) {
path := fmt.Sprintf( path := fmt.Sprintf(
"%s?include_member_count=%v&not_associated_to_team=%v&q=%v", "%s?include_member_count=%v&not_associated_to_team=%v&not_associated_to_channel=%v&q=%v",
c.GetGroupsRoute(), opts.IncludeMemberCount, opts.NotAssociatedToTeam, opts.Q, c.GetGroupsRoute(),
opts.IncludeMemberCount,
opts.NotAssociatedToTeam,
opts.NotAssociatedToChannel,
opts.Q,
) )
if opts.PageOpts != nil { if opts.PageOpts != nil {
path = fmt.Sprintf("%s&page=%v&per_page=%v", path, opts.PageOpts.Page, opts.PageOpts.PerPage) path = fmt.Sprintf("%s&page=%v&per_page=%v", path, opts.PageOpts.Page, opts.PageOpts.PerPage)

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

@@ -58,6 +58,7 @@ type LdapGroupSearchOpts struct {
type GroupSearchOpts struct { type GroupSearchOpts struct {
Q string Q string
NotAssociatedToTeam string NotAssociatedToTeam string
NotAssociatedToChannel string
IncludeMemberCount bool IncludeMemberCount bool
PageOpts *PageOpts PageOpts *PageOpts
} }

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

@@ -466,9 +466,15 @@ func (s *LayeredGroupStore) ChannelMembersToRemove() StoreChannel {
}) })
} }
func (s *LayeredGroupStore) GetGroupsByChannel(channelId string, page, perPage int) StoreChannel { func (s *LayeredGroupStore) GetGroupsByChannel(channelId string, opts model.GroupSearchOpts) StoreChannel {
return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult { return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult {
return supplier.GetGroupsByChannel(s.TmpContext, channelId, page, perPage) return supplier.GetGroupsByChannel(s.TmpContext, channelId, opts)
})
}
func (s *LayeredGroupStore) CountGroupsByChannel(channelId string, opts model.GroupSearchOpts) StoreChannel {
return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult {
return supplier.CountGroupsByChannel(s.TmpContext, channelId, opts)
}) })
} }

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

@@ -74,8 +74,11 @@ type LayeredStoreSupplier interface {
TeamMembersToRemove(ctx context.Context, hints ...LayeredStoreHint) *LayeredStoreSupplierResult TeamMembersToRemove(ctx context.Context, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
ChannelMembersToRemove(ctx context.Context, hints ...LayeredStoreHint) *LayeredStoreSupplierResult ChannelMembersToRemove(ctx context.Context, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
GetGroupsByChannel(ctx context.Context, channelId string, page, perPage int, hints ...LayeredStoreHint) *LayeredStoreSupplierResult GetGroupsByChannel(ctx context.Context, channelId string, opts model.GroupSearchOpts, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
CountGroupsByChannel(ctx context.Context, channelId string, opts model.GroupSearchOpts, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
GetGroupsByTeam(ctx context.Context, teamId string, opts model.GroupSearchOpts, hints ...LayeredStoreHint) *LayeredStoreSupplierResult GetGroupsByTeam(ctx context.Context, teamId string, opts model.GroupSearchOpts, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
CountGroupsByTeam(ctx context.Context, teamId string, opts model.GroupSearchOpts, hints ...LayeredStoreHint) *LayeredStoreSupplierResult CountGroupsByTeam(ctx context.Context, teamId string, opts model.GroupSearchOpts, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
GetGroups(ctx context.Context, page, perPage int, opts model.GroupSearchOpts, hints ...LayeredStoreHint) *LayeredStoreSupplierResult GetGroups(ctx context.Context, page, perPage int, opts model.GroupSearchOpts, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
} }

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

@@ -109,8 +109,12 @@ func (s *LocalCacheSupplier) ChannelMembersToRemove(ctx context.Context, hints .
return s.Next().ChannelMembersToRemove(ctx, hints...) return s.Next().ChannelMembersToRemove(ctx, hints...)
} }
func (s *LocalCacheSupplier) GetGroupsByChannel(ctx context.Context, channelId string, page, perPage int, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { func (s *LocalCacheSupplier) GetGroupsByChannel(ctx context.Context, channelId string, opts model.GroupSearchOpts, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
return s.Next().GetGroupsByChannel(ctx, channelId, page, perPage, hints...) return s.Next().GetGroupsByChannel(ctx, channelId, opts, hints...)
}
func (s *LocalCacheSupplier) CountGroupsByChannel(ctx context.Context, channelId string, opts model.GroupSearchOpts, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
return s.Next().CountGroupsByChannel(ctx, channelId, opts, hints...)
} }
func (s *LocalCacheSupplier) GetGroupsByTeam(ctx context.Context, teamId string, opts model.GroupSearchOpts, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { func (s *LocalCacheSupplier) GetGroupsByTeam(ctx context.Context, teamId string, opts model.GroupSearchOpts, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {

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

@@ -109,9 +109,14 @@ func (s *RedisSupplier) ChannelMembersToRemove(ctx context.Context, hints ...Lay
return s.Next().ChannelMembersToRemove(ctx, hints...) return s.Next().ChannelMembersToRemove(ctx, hints...)
} }
func (s *RedisSupplier) GetGroupsByChannel(ctx context.Context, channelId string, page, perPage int, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { func (s *RedisSupplier) GetGroupsByChannel(ctx context.Context, channelId string, opts model.GroupSearchOpts, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
// TODO: Redis caching. // TODO: Redis caching.
return s.Next().GetGroupsByChannel(ctx, channelId, page, perPage, hints...) return s.Next().GetGroupsByChannel(ctx, channelId, opts, hints...)
}
func (s *RedisSupplier) CountGroupsByChannel(ctx context.Context, channelId string, opts model.GroupSearchOpts, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
// TODO: Redis caching.
return s.Next().CountGroupsByChannel(ctx, channelId, opts, hints...)
} }
func (s *RedisSupplier) GetGroupsByTeam(ctx context.Context, teamId string, opts model.GroupSearchOpts, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { func (s *RedisSupplier) GetGroupsByTeam(ctx context.Context, teamId string, opts model.GroupSearchOpts, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {

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

@@ -827,32 +827,47 @@ func (s *SqlSupplier) TeamMembersToRemove(ctx context.Context, hints ...store.La
return result return result
} }
func (s *SqlSupplier) GetGroupsByChannel(ctx context.Context, channelId string, page, perPage int, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { func (s *SqlSupplier) CountGroupsByChannel(ctx context.Context, channelId string, opts model.GroupSearchOpts, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
result := store.NewSupplierResult() result := store.NewSupplierResult()
var groups []*model.Group countQuery := s.groupsBySyncableBaseQuery(model.GroupSyncableTypeChannel, selectCountGroups, channelId, opts)
offset := page * perPage
_, err := s.GetReplica().Select(&groups, `
SELECT
ug.*
FROM
GroupChannels gc
LEFT JOIN
UserGroups ug
ON
gc.GroupId = ug.Id
WHERE
gc.DeleteAt = 0
AND
ug.DeleteAt = 0
AND
gc.ChannelId = :ChannelId
ORDER BY
ug.DisplayName
LIMIT :Limit
OFFSET :Offset`,
map[string]interface{}{"ChannelId": channelId, "Limit": perPage, "Offset": offset})
countQueryString, args, err := countQuery.ToSql()
if err != nil {
result.Err = model.NewAppError("SqlGroupStore.CountGroupsByChannel", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError)
return result
}
count, err := s.GetReplica().SelectInt(countQueryString, args...)
if err != nil {
result.Err = model.NewAppError("SqlGroupStore.CountGroupsByChannel", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
return result
}
result.Data = count
return result
}
func (s *SqlSupplier) GetGroupsByChannel(ctx context.Context, channelId string, opts model.GroupSearchOpts, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
result := store.NewSupplierResult()
query := s.groupsBySyncableBaseQuery(model.GroupSyncableTypeChannel, selectGroups, channelId, opts)
if opts.PageOpts != nil {
offset := uint64(opts.PageOpts.Page * opts.PageOpts.PerPage)
query = query.OrderBy("ug.DisplayName").Limit(uint64(opts.PageOpts.PerPage)).Offset(offset)
}
queryString, args, err := query.ToSql()
if err != nil {
result.Err = model.NewAppError("SqlGroupStore.GetGroupsByChannel", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError)
return result
}
var groups []*model.Group
_, err = s.GetReplica().Select(&groups, queryString, args...)
if err != nil { if err != nil {
result.Err = model.NewAppError("SqlGroupStore.GetGroupsByChannel", "store.select_error", nil, err.Error(), http.StatusInternalServerError) result.Err = model.NewAppError("SqlGroupStore.GetGroupsByChannel", "store.select_error", nil, err.Error(), http.StatusInternalServerError)
return result return result
@@ -919,25 +934,35 @@ func (s *SqlSupplier) ChannelMembersToRemove(ctx context.Context, hints ...store
return result return result
} }
func (s *SqlSupplier) groupsByTeamBaseQuery(t selectType, teamID string, opts model.GroupSearchOpts) squirrel.SelectBuilder { func (s *SqlSupplier) groupsBySyncableBaseQuery(st model.GroupSyncableType, t selectType, syncableID string, opts model.GroupSearchOpts) squirrel.SelectBuilder {
selectStrs := map[selectType]string{ selectStrs := map[selectType]string{
selectGroups: "ug.*", selectGroups: "ug.*",
selectCountGroups: "COUNT(*)", selectCountGroups: "COUNT(*)",
} }
var table string
var idCol string
if st == model.GroupSyncableTypeTeam {
table = "GroupTeams"
idCol = "TeamId"
} else {
table = "GroupChannels"
idCol = "ChannelId"
}
query := s.getQueryBuilder(). query := s.getQueryBuilder().
Select(selectStrs[t]). Select(selectStrs[t]).
From("GroupTeams gt"). From(fmt.Sprintf("%s gs", table)).
LeftJoin("UserGroups ug ON gt.GroupId = ug.Id"). LeftJoin("UserGroups ug ON gs.GroupId = ug.Id").
Where("ug.DeleteAt = 0 AND gt.TeamId = ? AND gt.DeleteAt = 0", teamID) Where(fmt.Sprintf("ug.DeleteAt = 0 AND gs.%s = ? AND gs.DeleteAt = 0", idCol), syncableID)
if opts.IncludeMemberCount && t == selectGroups { if opts.IncludeMemberCount && t == selectGroups {
query = s.getQueryBuilder(). query = s.getQueryBuilder().
Select("ug.*, coalesce(Members.MemberCount, 0) AS MemberCount"). Select("ug.*, coalesce(Members.MemberCount, 0) AS MemberCount").
From("UserGroups ug"). From("UserGroups ug").
LeftJoin("(SELECT GroupMembers.GroupId, COUNT(*) AS MemberCount FROM GroupMembers WHERE GroupMembers.DeleteAt = 0 GROUP BY GroupId) AS Members ON Members.GroupId = ug.Id"). LeftJoin("(SELECT GroupMembers.GroupId, COUNT(*) AS MemberCount FROM GroupMembers WHERE GroupMembers.DeleteAt = 0 GROUP BY GroupId) AS Members ON Members.GroupId = ug.Id").
LeftJoin("GroupTeams ON GroupTeams.GroupId = ug.Id"). LeftJoin(fmt.Sprintf("%[1]s ON %[1]s.GroupId = ug.Id", table)).
Where("GroupTeams.DeleteAt = 0 AND GroupTeams.TeamId = ?", teamID). Where(fmt.Sprintf("%[1]s.DeleteAt = 0 AND %[1]s.%[2]s = ?", table, idCol), syncableID).
OrderBy("ug.DisplayName") OrderBy("ug.DisplayName")
} }
@@ -956,7 +981,7 @@ func (s *SqlSupplier) groupsByTeamBaseQuery(t selectType, teamID string, opts mo
func (s *SqlSupplier) CountGroupsByTeam(ctx context.Context, teamId string, opts model.GroupSearchOpts, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { func (s *SqlSupplier) CountGroupsByTeam(ctx context.Context, teamId string, opts model.GroupSearchOpts, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
result := store.NewSupplierResult() result := store.NewSupplierResult()
countQuery := s.groupsByTeamBaseQuery(selectCountGroups, teamId, opts) countQuery := s.groupsBySyncableBaseQuery(model.GroupSyncableTypeTeam, selectCountGroups, teamId, opts)
countQueryString, args, err := countQuery.ToSql() countQueryString, args, err := countQuery.ToSql()
if err != nil { if err != nil {
@@ -978,7 +1003,7 @@ func (s *SqlSupplier) CountGroupsByTeam(ctx context.Context, teamId string, opts
func (s *SqlSupplier) GetGroupsByTeam(ctx context.Context, teamId string, opts model.GroupSearchOpts, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { func (s *SqlSupplier) GetGroupsByTeam(ctx context.Context, teamId string, opts model.GroupSearchOpts, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
result := store.NewSupplierResult() result := store.NewSupplierResult()
query := s.groupsByTeamBaseQuery(selectGroups, teamId, opts) query := s.groupsBySyncableBaseQuery(model.GroupSyncableTypeTeam, selectGroups, teamId, opts)
if opts.PageOpts != nil { if opts.PageOpts != nil {
offset := uint64(opts.PageOpts.Page * opts.PageOpts.PerPage) offset := uint64(opts.PageOpts.Page * opts.PageOpts.PerPage)
@@ -1045,6 +1070,22 @@ func (s *SqlSupplier) GetGroups(ctx context.Context, page, perPage int, opts mod
`, opts.NotAssociatedToTeam) `, opts.NotAssociatedToTeam)
} }
if len(opts.NotAssociatedToChannel) == 26 {
groupsQuery = groupsQuery.Where(`
g.Id NOT IN (
SELECT
Id
FROM
UserGroups
JOIN GroupChannels ON GroupChannels.GroupId = UserGroups.Id
WHERE
GroupChannels.DeleteAt = 0
AND UserGroups.DeleteAt = 0
AND GroupChannels.ChannelId = ?
)
`, opts.NotAssociatedToChannel)
}
queryString, args, err := groupsQuery.ToSql() queryString, args, err := groupsQuery.ToSql()
if err != nil { if err != nil {
result.Err = model.NewAppError("SqlGroupStore.GetGroups", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError) result.Err = model.NewAppError("SqlGroupStore.GetGroups", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError)

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

@@ -594,9 +594,12 @@ type GroupStore interface {
TeamMembersToRemove() StoreChannel TeamMembersToRemove() StoreChannel
ChannelMembersToRemove() StoreChannel ChannelMembersToRemove() StoreChannel
GetGroupsByChannel(channelId string, page, perPage int) StoreChannel GetGroupsByChannel(channelId string, opts model.GroupSearchOpts) StoreChannel
CountGroupsByChannel(channelId string, opts model.GroupSearchOpts) StoreChannel
GetGroupsByTeam(teamId string, opts model.GroupSearchOpts) StoreChannel GetGroupsByTeam(teamId string, opts model.GroupSearchOpts) StoreChannel
CountGroupsByTeam(teamId string, opts model.GroupSearchOpts) StoreChannel CountGroupsByTeam(teamId string, opts model.GroupSearchOpts) StoreChannel
GetGroups(page, perPage int, opts model.GroupSearchOpts) StoreChannel GetGroups(page, perPage int, opts model.GroupSearchOpts) StoreChannel
} }

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

@@ -1633,23 +1633,44 @@ func testGetGroupsByChannel(t *testing.T, ss store.Store) {
}) })
require.Nil(t, res.Err) require.Nil(t, res.Err)
// add members
u1 := &model.User{
Email: MakeEmail(),
Username: model.NewId(),
}
res = <-ss.User().Save(u1)
require.Nil(t, res.Err)
user1 := res.Data.(*model.User)
<-ss.Group().CreateOrRestoreMember(group1.Id, user1.Id)
group1WithMemberCount := model.Group(*group1)
group1WithMemberCount.MemberCount = model.NewInt(1)
group2WithMemberCount := model.Group(*group2)
group2WithMemberCount.MemberCount = model.NewInt(0)
testCases := []struct { testCases := []struct {
Name string Name string
ChannelId string ChannelId string
Page int Page int
PerPage int PerPage int
Result []*model.Group Result []*model.Group
Opts model.GroupSearchOpts
TotalCount *int64
}{ }{
{ {
Name: "Get the two Groups for Channel1", Name: "Get the two Groups for Channel1",
ChannelId: channel1.Id, ChannelId: channel1.Id,
Opts: model.GroupSearchOpts{},
Page: 0, Page: 0,
PerPage: 60, PerPage: 60,
Result: []*model.Group{group1, group2}, Result: []*model.Group{group1, group2},
TotalCount: model.NewInt64(2),
}, },
{ {
Name: "Get first Group for Channel1 with page 0 with 1 element", Name: "Get first Group for Channel1 with page 0 with 1 element",
ChannelId: channel1.Id, ChannelId: channel1.Id,
Opts: model.GroupSearchOpts{},
Page: 0, Page: 0,
PerPage: 1, PerPage: 1,
Result: []*model.Group{group1}, Result: []*model.Group{group1},
@@ -1657,6 +1678,7 @@ func testGetGroupsByChannel(t *testing.T, ss store.Store) {
{ {
Name: "Get second Group for Channel1 with page 1 with 1 element", Name: "Get second Group for Channel1 with page 1 with 1 element",
ChannelId: channel1.Id, ChannelId: channel1.Id,
Opts: model.GroupSearchOpts{},
Page: 1, Page: 1,
PerPage: 1, PerPage: 1,
Result: []*model.Group{group2}, Result: []*model.Group{group2},
@@ -1664,6 +1686,7 @@ func testGetGroupsByChannel(t *testing.T, ss store.Store) {
{ {
Name: "Get third Group for Channel2", Name: "Get third Group for Channel2",
ChannelId: channel2.Id, ChannelId: channel2.Id,
Opts: model.GroupSearchOpts{},
Page: 0, Page: 0,
PerPage: 60, PerPage: 60,
Result: []*model.Group{group3}, Result: []*model.Group{group3},
@@ -1671,17 +1694,64 @@ func testGetGroupsByChannel(t *testing.T, ss store.Store) {
{ {
Name: "Get empty Groups for a fake id", Name: "Get empty Groups for a fake id",
ChannelId: model.NewId(), ChannelId: model.NewId(),
Opts: model.GroupSearchOpts{},
Page: 0, Page: 0,
PerPage: 60, PerPage: 60,
Result: []*model.Group{}, Result: []*model.Group{},
TotalCount: model.NewInt64(0),
},
{
Name: "Get group matching name",
ChannelId: channel1.Id,
Opts: model.GroupSearchOpts{Q: string([]rune(group1.Name)[2:10])}, // very low change of a name collision
Page: 0,
PerPage: 100,
Result: []*model.Group{group1},
TotalCount: model.NewInt64(1),
},
{
Name: "Get group matching display name",
ChannelId: channel1.Id,
Opts: model.GroupSearchOpts{Q: "rouP-1"},
Page: 0,
PerPage: 100,
Result: []*model.Group{group1},
TotalCount: model.NewInt64(1),
},
{
Name: "Get group matching multiple display names",
ChannelId: channel1.Id,
Opts: model.GroupSearchOpts{Q: "roUp-"},
Page: 0,
PerPage: 100,
Result: []*model.Group{group1, group2},
TotalCount: model.NewInt64(2),
},
{
Name: "Include member counts",
ChannelId: channel1.Id,
Opts: model.GroupSearchOpts{IncludeMemberCount: true},
Page: 0,
PerPage: 2,
Result: []*model.Group{&group1WithMemberCount, &group2WithMemberCount},
}, },
} }
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) { t.Run(tc.Name, func(t *testing.T) {
res := <-ss.Group().GetGroupsByChannel(tc.ChannelId, tc.Page, tc.PerPage) if tc.Opts.PageOpts == nil {
tc.Opts.PageOpts = &model.PageOpts{}
}
tc.Opts.PageOpts.Page = tc.Page
tc.Opts.PageOpts.PerPage = tc.PerPage
res := <-ss.Group().GetGroupsByChannel(tc.ChannelId, tc.Opts)
require.Nil(t, res.Err) require.Nil(t, res.Err)
require.ElementsMatch(t, tc.Result, res.Data.([]*model.Group)) require.ElementsMatch(t, tc.Result, res.Data.([]*model.Group))
if tc.TotalCount != nil {
res = <-ss.Group().CountGroupsByChannel(tc.ChannelId, tc.Opts)
count := res.Data.(int64)
require.Equal(t, *tc.TotalCount, count)
}
}) })
} }
} }
@@ -1904,8 +1974,19 @@ func testGetGroups(t *testing.T, ss store.Store) {
team1, err := ss.Team().Save(team1) team1, err := ss.Team().Save(team1)
require.Nil(t, err) require.Nil(t, err)
// Create Channel1
channel1 := &model.Channel{
TeamId: model.NewId(),
DisplayName: "Channel1",
Name: model.NewId(),
Type: model.CHANNEL_PRIVATE,
}
res := <-ss.Channel().Save(channel1, 9999)
require.Nil(t, res.Err)
channel1 = res.Data.(*model.Channel)
// Create Groups 1 and 2 // Create Groups 1 and 2
res := <-ss.Group().Create(&model.Group{ res = <-ss.Group().Create(&model.Group{
Name: model.NewId(), Name: model.NewId(),
DisplayName: "group-1", DisplayName: "group-1",
RemoteId: model.NewId(), RemoteId: model.NewId(),
@@ -1948,6 +2029,17 @@ func testGetGroups(t *testing.T, ss store.Store) {
team2, err = ss.Team().Save(team2) team2, err = ss.Team().Save(team2)
require.Nil(t, err) require.Nil(t, err)
// Create Channel2
channel2 := &model.Channel{
TeamId: model.NewId(),
DisplayName: "Channel2",
Name: model.NewId(),
Type: model.CHANNEL_PRIVATE,
}
res = <-ss.Channel().Save(channel2, 9999)
require.Nil(t, res.Err)
channel2 = res.Data.(*model.Channel)
// Create Group3 // Create Group3
res = <-ss.Group().Create(&model.Group{ res = <-ss.Group().Create(&model.Group{
Name: model.NewId(), Name: model.NewId(),
@@ -1967,6 +2059,26 @@ func testGetGroups(t *testing.T, ss store.Store) {
}) })
require.Nil(t, res.Err) require.Nil(t, res.Err)
// And associate Group1 to Channel2
res = <-ss.Group().CreateGroupSyncable(&model.GroupSyncable{
AutoAdd: true,
SyncableId: channel2.Id,
Type: model.GroupSyncableTypeChannel,
GroupId: group1.Id,
})
require.Nil(t, res.Err)
// And associate Group2 and Group3 to Channel1
for _, g := range []*model.Group{group2, group3} {
res = <-ss.Group().CreateGroupSyncable(&model.GroupSyncable{
AutoAdd: true,
SyncableId: channel1.Id,
Type: model.GroupSyncableTypeChannel,
GroupId: g.Id,
})
require.Nil(t, res.Err)
}
// add members // add members
u1 := &model.User{ u1 := &model.User{
Email: MakeEmail(), Email: MakeEmail(),
@@ -2082,6 +2194,9 @@ func testGetGroups(t *testing.T, ss store.Store) {
Page: 0, Page: 0,
PerPage: 100, PerPage: 100,
Resultf: func(groups []*model.Group) bool { Resultf: func(groups []*model.Group) bool {
if len(groups) == 0 {
return false
}
for _, g := range groups { for _, g := range groups {
if g.Id == group3.Id { if g.Id == group3.Id {
return false return false
@@ -2096,6 +2211,9 @@ func testGetGroups(t *testing.T, ss store.Store) {
Page: 0, Page: 0,
PerPage: 100, PerPage: 100,
Resultf: func(groups []*model.Group) bool { Resultf: func(groups []*model.Group) bool {
if len(groups) == 0 {
return false
}
for _, g := range groups { for _, g := range groups {
if g.Id == group1.Id || g.Id == group2.Id { if g.Id == group1.Id || g.Id == group2.Id {
return false return false

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

@@ -45,6 +45,22 @@ func (_m *GroupStore) ChannelMembersToRemove() store.StoreChannel {
return r0 return r0
} }
// CountGroupsByChannel provides a mock function with given fields: channelId, opts
func (_m *GroupStore) CountGroupsByChannel(channelId string, opts model.GroupSearchOpts) store.StoreChannel {
ret := _m.Called(channelId, opts)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(string, model.GroupSearchOpts) store.StoreChannel); ok {
r0 = rf(channelId, opts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// CountGroupsByTeam provides a mock function with given fields: teamId, opts // CountGroupsByTeam provides a mock function with given fields: teamId, opts
func (_m *GroupStore) CountGroupsByTeam(teamId string, opts model.GroupSearchOpts) store.StoreChannel { func (_m *GroupStore) CountGroupsByTeam(teamId string, opts model.GroupSearchOpts) store.StoreChannel {
ret := _m.Called(teamId, opts) ret := _m.Called(teamId, opts)
@@ -253,13 +269,13 @@ func (_m *GroupStore) GetGroups(page int, perPage int, opts model.GroupSearchOpt
return r0 return r0
} }
// GetGroupsByChannel provides a mock function with given fields: channelId, page, perPage // GetGroupsByChannel provides a mock function with given fields: channelId, opts
func (_m *GroupStore) GetGroupsByChannel(channelId string, page int, perPage int) store.StoreChannel { func (_m *GroupStore) GetGroupsByChannel(channelId string, opts model.GroupSearchOpts) store.StoreChannel {
ret := _m.Called(channelId, page, perPage) ret := _m.Called(channelId, opts)
var r0 store.StoreChannel var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(string, int, int) store.StoreChannel); ok { if rf, ok := ret.Get(0).(func(string, model.GroupSearchOpts) store.StoreChannel); ok {
r0 = rf(channelId, page, perPage) r0 = rf(channelId, opts)
} else { } else {
if ret.Get(0) != nil { if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel) r0 = ret.Get(0).(store.StoreChannel)

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

@@ -193,6 +193,29 @@ func (_m *LayeredStoreDatabaseLayer) Compliance() store.ComplianceStore {
return r0 return r0
} }
// CountGroupsByChannel provides a mock function with given fields: ctx, channelId, opts, hints
func (_m *LayeredStoreDatabaseLayer) CountGroupsByChannel(ctx context.Context, channelId string, opts model.GroupSearchOpts, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, channelId, opts)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, string, model.GroupSearchOpts, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, channelId, opts, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// CountGroupsByTeam provides a mock function with given fields: ctx, teamId, opts, hints // CountGroupsByTeam provides a mock function with given fields: ctx, teamId, opts, hints
func (_m *LayeredStoreDatabaseLayer) CountGroupsByTeam(ctx context.Context, teamId string, opts model.GroupSearchOpts, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { func (_m *LayeredStoreDatabaseLayer) CountGroupsByTeam(ctx context.Context, teamId string, opts model.GroupSearchOpts, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints)) _va := make([]interface{}, len(hints))
@@ -276,20 +299,20 @@ func (_m *LayeredStoreDatabaseLayer) GetGroups(ctx context.Context, page int, pe
return r0 return r0
} }
// GetGroupsByChannel provides a mock function with given fields: ctx, channelId, page, perPage, hints // GetGroupsByChannel provides a mock function with given fields: ctx, channelId, opts, hints
func (_m *LayeredStoreDatabaseLayer) GetGroupsByChannel(ctx context.Context, channelId string, page int, perPage int, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { func (_m *LayeredStoreDatabaseLayer) GetGroupsByChannel(ctx context.Context, channelId string, opts model.GroupSearchOpts, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints)) _va := make([]interface{}, len(hints))
for _i := range hints { for _i := range hints {
_va[_i] = hints[_i] _va[_i] = hints[_i]
} }
var _ca []interface{} var _ca []interface{}
_ca = append(_ca, ctx, channelId, page, perPage) _ca = append(_ca, ctx, channelId, opts)
_ca = append(_ca, _va...) _ca = append(_ca, _va...)
ret := _m.Called(_ca...) ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, string, int, int, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok { if rf, ok := ret.Get(0).(func(context.Context, string, model.GroupSearchOpts, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, channelId, page, perPage, hints...) r0 = rf(ctx, channelId, opts, hints...)
} else { } else {
if ret.Get(0) != nil { if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult) r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)

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

@@ -60,6 +60,29 @@ func (_m *LayeredStoreSupplier) ChannelMembersToRemove(ctx context.Context, hint
return r0 return r0
} }
// CountGroupsByChannel provides a mock function with given fields: ctx, channelId, opts, hints
func (_m *LayeredStoreSupplier) CountGroupsByChannel(ctx context.Context, channelId string, opts model.GroupSearchOpts, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints))
for _i := range hints {
_va[_i] = hints[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, channelId, opts)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, string, model.GroupSearchOpts, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, channelId, opts, hints...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)
}
}
return r0
}
// CountGroupsByTeam provides a mock function with given fields: ctx, teamId, opts, hints // CountGroupsByTeam provides a mock function with given fields: ctx, teamId, opts, hints
func (_m *LayeredStoreSupplier) CountGroupsByTeam(ctx context.Context, teamId string, opts model.GroupSearchOpts, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { func (_m *LayeredStoreSupplier) CountGroupsByTeam(ctx context.Context, teamId string, opts model.GroupSearchOpts, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints)) _va := make([]interface{}, len(hints))
@@ -106,20 +129,20 @@ func (_m *LayeredStoreSupplier) GetGroups(ctx context.Context, page int, perPage
return r0 return r0
} }
// GetGroupsByChannel provides a mock function with given fields: ctx, channelId, page, perPage, hints // GetGroupsByChannel provides a mock function with given fields: ctx, channelId, opts, hints
func (_m *LayeredStoreSupplier) GetGroupsByChannel(ctx context.Context, channelId string, page int, perPage int, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { func (_m *LayeredStoreSupplier) GetGroupsByChannel(ctx context.Context, channelId string, opts model.GroupSearchOpts, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
_va := make([]interface{}, len(hints)) _va := make([]interface{}, len(hints))
for _i := range hints { for _i := range hints {
_va[_i] = hints[_i] _va[_i] = hints[_i]
} }
var _ca []interface{} var _ca []interface{}
_ca = append(_ca, ctx, channelId, page, perPage) _ca = append(_ca, ctx, channelId, opts)
_ca = append(_ca, _va...) _ca = append(_ca, _va...)
ret := _m.Called(_ca...) ret := _m.Called(_ca...)
var r0 *store.LayeredStoreSupplierResult var r0 *store.LayeredStoreSupplierResult
if rf, ok := ret.Get(0).(func(context.Context, string, int, int, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok { if rf, ok := ret.Get(0).(func(context.Context, string, model.GroupSearchOpts, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
r0 = rf(ctx, channelId, page, perPage, hints...) r0 = rf(ctx, channelId, opts, hints...)
} else { } else {
if ret.Get(0) != nil { if ret.Get(0) != nil {
r0 = ret.Get(0).(*store.LayeredStoreSupplierResult) r0 = ret.Get(0).(*store.LayeredStoreSupplierResult)

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

@@ -63,6 +63,7 @@ type Params struct {
IsLinked *bool IsLinked *bool
IsConfigured *bool IsConfigured *bool
NotAssociatedToTeam string NotAssociatedToTeam string
NotAssociatedToChannel string
Paginate *bool Paginate *bool
IncludeMemberCount bool IncludeMemberCount bool
} }
@@ -249,6 +250,7 @@ func ParamsFromRequest(r *http.Request) *Params {
} }
params.NotAssociatedToTeam = query.Get("not_associated_to_team") params.NotAssociatedToTeam = query.Get("not_associated_to_team")
params.NotAssociatedToChannel = query.Get("not_associated_to_channel")
if val, err := strconv.ParseBool(query.Get("paginate")); err == nil { if val, err := strconv.ParseBool(query.Get("paginate")); err == nil {
params.Paginate = &val params.Paginate = &val