MM-25263 Add group members to search and get users and create getGroupStats endpoint (#14733)
Add tests for SearchInGroup
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
f6c934d7e0
Коммит
77bee1d4f1
@@ -52,6 +52,10 @@ func (api *API) InitGroup() {
|
||||
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/{syncable_type:teams|channels}/{syncable_id:[A-Za-z0-9]+}/patch",
|
||||
api.ApiSessionRequired(patchGroupSyncable)).Methods("PUT")
|
||||
|
||||
// GET /api/v4/groups/:group_id/stats
|
||||
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/stats",
|
||||
api.ApiSessionRequired(getGroupStats)).Methods("GET")
|
||||
|
||||
// GET /api/v4/groups/:group_id/members?page=0&per_page=100
|
||||
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/members",
|
||||
api.ApiSessionRequired(getGroupMembers)).Methods("GET")
|
||||
@@ -530,6 +534,41 @@ func getGroupMembers(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(b)
|
||||
}
|
||||
|
||||
func getGroupStats(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequireGroupId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if c.App.Srv().License() == nil || !*c.App.Srv().License().Features.LDAPGroups {
|
||||
c.Err = model.NewAppError("Api4.getGroupStats", "api.ldap_groups.license_error", nil, "", http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_MANAGE_SYSTEM) {
|
||||
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
|
||||
return
|
||||
}
|
||||
|
||||
groupID := c.Params.GroupId
|
||||
count, err := c.App.GetGroupMemberCount(groupID)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
b, marshalErr := json.Marshal(model.GroupStats{
|
||||
GroupID: groupID,
|
||||
TotalMemberCount: count,
|
||||
})
|
||||
if marshalErr != nil {
|
||||
c.Err = model.NewAppError("Api4.getGroupStats", "api.marshal_error", nil, marshalErr.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(b)
|
||||
}
|
||||
|
||||
func getGroupsByUserId(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequireUserId()
|
||||
if c.Err != nil {
|
||||
|
||||
@@ -1024,6 +1024,54 @@ func TestGetGroupsByUserId(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestGetGroupStats(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
id := model.NewId()
|
||||
group, err := th.App.CreateGroup(&model.Group{
|
||||
DisplayName: "dn-foo_" + id,
|
||||
Name: model.NewString("name" + id),
|
||||
Source: model.GroupSourceLdap,
|
||||
Description: "description_" + id,
|
||||
RemoteId: model.NewId(),
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
|
||||
var response *model.Response
|
||||
var stats *model.GroupStats
|
||||
|
||||
t.Run("Requires ldap license", func(t *testing.T) {
|
||||
_, response = th.SystemAdminClient.GetGroupStats(group.Id)
|
||||
CheckNotImplementedStatus(t, response)
|
||||
})
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicense("ldap"))
|
||||
|
||||
t.Run("Requires manage system permission to access group stats", func(t *testing.T) {
|
||||
th.Client.Login(th.BasicUser.Email, th.BasicUser.Password)
|
||||
_, response = th.Client.GetGroupStats(group.Id)
|
||||
CheckForbiddenStatus(t, response)
|
||||
})
|
||||
|
||||
t.Run("Returns stats for a group with no members", func(t *testing.T) {
|
||||
stats, _ = th.SystemAdminClient.GetGroupStats(group.Id)
|
||||
assert.Equal(t, stats.GroupID, group.Id)
|
||||
assert.Equal(t, stats.TotalMemberCount, int64(0))
|
||||
})
|
||||
|
||||
user1, err := th.App.CreateUser(&model.User{Email: th.GenerateTestEmail(), Nickname: "test user1", Password: "test-password-1", Username: "test-user-1", Roles: model.SYSTEM_USER_ROLE_ID})
|
||||
assert.Nil(t, err)
|
||||
_, err = th.App.UpsertGroupMember(group.Id, user1.Id)
|
||||
assert.Nil(t, err)
|
||||
|
||||
t.Run("Returns stats for a group with members", func(t *testing.T) {
|
||||
stats, _ = th.SystemAdminClient.GetGroupStats(group.Id)
|
||||
assert.Equal(t, stats.GroupID, group.Id)
|
||||
assert.Equal(t, stats.TotalMemberCount, int64(1))
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetGroupsGroupConstrainedParentTeam(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
32
api4/user.go
32
api4/user.go
@@ -527,6 +527,7 @@ func getUsers(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
inTeamId := r.URL.Query().Get("in_team")
|
||||
notInTeamId := r.URL.Query().Get("not_in_team")
|
||||
inChannelId := r.URL.Query().Get("in_channel")
|
||||
inGroupId := r.URL.Query().Get("in_group")
|
||||
notInChannelId := r.URL.Query().Get("not_in_channel")
|
||||
groupConstrained := r.URL.Query().Get("group_constrained")
|
||||
withoutTeam := r.URL.Query().Get("without_team")
|
||||
@@ -546,7 +547,7 @@ func getUsers(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Currently only supports sorting on a team
|
||||
// or sort="status" on inChannelId
|
||||
if (sort == "last_activity_at" || sort == "create_at") && (inTeamId == "" || notInTeamId != "" || inChannelId != "" || notInChannelId != "" || withoutTeam != "") {
|
||||
if (sort == "last_activity_at" || sort == "create_at") && (inTeamId == "" || notInTeamId != "" || inChannelId != "" || notInChannelId != "" || withoutTeam != "" || inGroupId != "") {
|
||||
c.SetInvalidUrlParam("sort")
|
||||
return
|
||||
}
|
||||
@@ -570,6 +571,7 @@ func getUsers(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
InChannelId: inChannelId,
|
||||
NotInTeamId: notInTeamId,
|
||||
NotInChannelId: notInChannelId,
|
||||
InGroupId: inGroupId,
|
||||
GroupConstrained: groupConstrainedBool,
|
||||
WithoutTeam: withoutTeamBool,
|
||||
Inactive: inactiveBool,
|
||||
@@ -637,6 +639,22 @@ func getUsers(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
} else {
|
||||
profiles, err = c.App.GetUsersInChannelPage(inChannelId, c.Params.Page, c.Params.PerPage, c.IsSystemAdmin())
|
||||
}
|
||||
} else if len(inGroupId) > 0 {
|
||||
if c.App.Srv().License() == nil || !*c.App.Srv().License().Features.LDAPGroups {
|
||||
c.Err = model.NewAppError("Api4.getUsersInGroup", "api.ldap_groups.license_error", nil, "", http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_MANAGE_SYSTEM) {
|
||||
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
|
||||
return
|
||||
}
|
||||
|
||||
profiles, _, err = c.App.GetGroupMemberUsersPage(inGroupId, c.Params.Page, c.Params.PerPage)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
} else {
|
||||
userGetOptions, err = c.App.RestrictUsersGetByPermissions(c.App.Session().UserId, userGetOptions)
|
||||
if err != nil {
|
||||
@@ -749,6 +767,18 @@ func searchUsers(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if props.InGroupId != "" {
|
||||
if c.App.Srv().License() == nil || !*c.App.Srv().License().Features.LDAPGroups {
|
||||
c.Err = model.NewAppError("Api4.searchUsers", "api.ldap_groups.license_error", nil, "", http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_MANAGE_SYSTEM) {
|
||||
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if props.InChannelId != "" && !c.App.SessionHasPermissionToChannel(*c.App.Session(), props.InChannelId, model.PERMISSION_READ_CHANNEL) {
|
||||
c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
|
||||
return
|
||||
|
||||
@@ -1084,6 +1084,44 @@ func TestSearchUsers(t *testing.T) {
|
||||
CheckNoError(t, resp)
|
||||
|
||||
require.True(t, findUserInList(th.BasicUser.Id, users), "should have found user")
|
||||
|
||||
id := model.NewId()
|
||||
group, err := th.App.CreateGroup(&model.Group{
|
||||
DisplayName: "dn-foo_" + id,
|
||||
Name: model.NewString("name" + id),
|
||||
Source: model.GroupSourceLdap,
|
||||
Description: "description_" + id,
|
||||
RemoteId: model.NewId(),
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
|
||||
search = &model.UserSearch{Term: th.BasicUser.Username, InGroupId: group.Id}
|
||||
t.Run("Requires ldap license when searching in group", func(t *testing.T) {
|
||||
_, resp = th.SystemAdminClient.SearchUsers(search)
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
})
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicense("ldap"))
|
||||
|
||||
t.Run("Requires manage system permission when searching for users in a group", func(t *testing.T) {
|
||||
_, resp = th.Client.SearchUsers(search)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
})
|
||||
|
||||
t.Run("Returns empty list when no users found searching for users in a group", func(t *testing.T) {
|
||||
users, resp = th.SystemAdminClient.SearchUsers(search)
|
||||
CheckNoError(t, resp)
|
||||
require.Empty(t, users)
|
||||
})
|
||||
|
||||
_, err = th.App.UpsertGroupMember(group.Id, th.BasicUser.Id)
|
||||
assert.Nil(t, err)
|
||||
|
||||
t.Run("Returns user in group user found in group", func(t *testing.T) {
|
||||
users, resp = th.SystemAdminClient.SearchUsers(search)
|
||||
CheckNoError(t, resp)
|
||||
require.Equal(t, users[0].Id, th.BasicUser.Id)
|
||||
})
|
||||
}
|
||||
|
||||
func findUserInList(id string, users []*model.User) bool {
|
||||
@@ -2328,6 +2366,54 @@ func TestGetUsersNotInChannel(t *testing.T) {
|
||||
CheckNoError(t, resp)
|
||||
}
|
||||
|
||||
func TestGetUsersInGroup(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
id := model.NewId()
|
||||
group, err := th.App.CreateGroup(&model.Group{
|
||||
DisplayName: "dn-foo_" + id,
|
||||
Name: model.NewString("name" + id),
|
||||
Source: model.GroupSourceLdap,
|
||||
Description: "description_" + id,
|
||||
RemoteId: model.NewId(),
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
|
||||
var response *model.Response
|
||||
var users []*model.User
|
||||
|
||||
t.Run("Requires ldap license", func(t *testing.T) {
|
||||
_, response = th.SystemAdminClient.GetUsersInGroup(group.Id, 0, 60, "")
|
||||
CheckNotImplementedStatus(t, response)
|
||||
})
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicense("ldap"))
|
||||
|
||||
t.Run("Requires manage system permission to access users in group", func(t *testing.T) {
|
||||
th.Client.Login(th.BasicUser.Email, th.BasicUser.Password)
|
||||
_, response = th.Client.GetUsersInGroup(group.Id, 0, 60, "")
|
||||
CheckForbiddenStatus(t, response)
|
||||
})
|
||||
|
||||
user1, err := th.App.CreateUser(&model.User{Email: th.GenerateTestEmail(), Nickname: "test user1", Password: "test-password-1", Username: "test-user-1", Roles: model.SYSTEM_USER_ROLE_ID})
|
||||
assert.Nil(t, err)
|
||||
_, err = th.App.UpsertGroupMember(group.Id, user1.Id)
|
||||
assert.Nil(t, err)
|
||||
|
||||
t.Run("Returns users in group when called by system admin", func(t *testing.T) {
|
||||
users, response = th.SystemAdminClient.GetUsersInGroup(group.Id, 0, 60, "")
|
||||
CheckNoError(t, response)
|
||||
assert.Equal(t, users[0].Id, user1.Id)
|
||||
})
|
||||
|
||||
t.Run("Returns no users when pagination out of range", func(t *testing.T) {
|
||||
users, response = th.SystemAdminClient.GetUsersInGroup(group.Id, 5, 60, "")
|
||||
CheckNoError(t, response)
|
||||
assert.Empty(t, users)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdateUserMfa(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
@@ -541,6 +541,7 @@ type AppIface interface {
|
||||
GetGroupByName(name string, opts model.GroupSearchOpts) (*model.Group, *model.AppError)
|
||||
GetGroupByRemoteID(remoteID string, groupSource model.GroupSource) (*model.Group, *model.AppError)
|
||||
GetGroupChannel(userIds []string) (*model.Channel, *model.AppError)
|
||||
GetGroupMemberCount(groupID string) (int64, *model.AppError)
|
||||
GetGroupMemberUsers(groupID string) ([]*model.User, *model.AppError)
|
||||
GetGroupMemberUsersPage(groupID string, page int, perPage int) ([]*model.User, int, *model.AppError)
|
||||
GetGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) (*model.GroupSyncable, *model.AppError)
|
||||
@@ -836,6 +837,7 @@ type AppIface interface {
|
||||
SearchUserAccessTokens(term string) ([]*model.UserAccessToken, *model.AppError)
|
||||
SearchUsers(props *model.UserSearch, options *model.UserSearchOptions) ([]*model.User, *model.AppError)
|
||||
SearchUsersInChannel(channelId string, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError)
|
||||
SearchUsersInGroup(groupID string, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError)
|
||||
SearchUsersInTeam(teamId, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError)
|
||||
SearchUsersNotInChannel(teamId string, channelId string, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError)
|
||||
SearchUsersNotInTeam(notInTeamId string, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError)
|
||||
|
||||
@@ -59,6 +59,10 @@ func (a *App) DeleteGroup(groupID string) (*model.Group, *model.AppError) {
|
||||
return deletedGroup, err
|
||||
}
|
||||
|
||||
func (a *App) GetGroupMemberCount(groupID string) (int64, *model.AppError) {
|
||||
return a.Srv().Store.Group().GetMemberCount(groupID)
|
||||
}
|
||||
|
||||
func (a *App) GetGroupMemberUsers(groupID string) ([]*model.User, *model.AppError) {
|
||||
return a.Srv().Store.Group().GetMemberUsers(groupID)
|
||||
}
|
||||
@@ -69,7 +73,7 @@ func (a *App) GetGroupMemberUsersPage(groupID string, page int, perPage int) ([]
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
count, err := a.Srv().Store.Group().GetMemberCount(groupID)
|
||||
count, err := a.GetGroupMemberCount(groupID)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
@@ -5311,6 +5311,28 @@ func (a *OpenTracingAppLayer) GetGroupChannel(userIds []string) (*model.Channel,
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetGroupMemberCount(groupID string) (int64, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetGroupMemberCount")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store.SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.GetGroupMemberCount(groupID)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetGroupMemberUsers(groupID string) ([]*model.User, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetGroupMemberUsers")
|
||||
@@ -12120,6 +12142,28 @@ func (a *OpenTracingAppLayer) SearchUsersInChannel(channelId string, term string
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) SearchUsersInGroup(groupID string, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SearchUsersInGroup")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store.SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.SearchUsersInGroup(groupID, term, options)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) SearchUsersInTeam(teamId string, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SearchUsersInTeam")
|
||||
|
||||
17
app/user.go
17
app/user.go
@@ -1693,6 +1693,9 @@ func (a *App) SearchUsers(props *model.UserSearch, options *model.UserSearchOpti
|
||||
if props.NotInTeamId != "" {
|
||||
return a.SearchUsersNotInTeam(props.NotInTeamId, props.Term, options)
|
||||
}
|
||||
if props.InGroupId != "" {
|
||||
return a.SearchUsersInGroup(props.InGroupId, props.Term, options)
|
||||
}
|
||||
return a.SearchUsersInTeam(props.TeamId, props.Term, options)
|
||||
}
|
||||
|
||||
@@ -1768,6 +1771,20 @@ func (a *App) SearchUsersWithoutTeam(term string, options *model.UserSearchOptio
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (a *App) SearchUsersInGroup(groupID string, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError) {
|
||||
term = strings.TrimSpace(term)
|
||||
users, err := a.Srv().Store.User().SearchInGroup(groupID, term, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
a.SanitizeProfile(user, options.IsAdmin)
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (a *App) AutocompleteUsersInChannel(teamId string, channelId string, term string, options *model.UserSearchOptions) (*model.UserAutocompleteInChannel, *model.AppError) {
|
||||
term = strings.TrimSpace(term)
|
||||
|
||||
|
||||
@@ -994,6 +994,17 @@ func (c *Client4) GetUsersWithoutTeam(page int, perPage int, etag string) ([]*Us
|
||||
return UserListFromJson(r.Body), BuildResponse(r)
|
||||
}
|
||||
|
||||
// GetUsersInGroup returns a page of users in a group. Page counting starts at 0.
|
||||
func (c *Client4) GetUsersInGroup(groupID string, page int, perPage int, etag string) ([]*User, *Response) {
|
||||
query := fmt.Sprintf("?in_group=%v&page=%v&per_page=%v", groupID, page, perPage)
|
||||
r, err := c.DoApiGet(c.GetUsersRoute()+query, etag)
|
||||
if err != nil {
|
||||
return nil, BuildErrorResponse(r, err)
|
||||
}
|
||||
defer closeBody(r)
|
||||
return UserListFromJson(r.Body), BuildResponse(r)
|
||||
}
|
||||
|
||||
// GetUsersByIds returns a list of users based on the provided user ids.
|
||||
func (c *Client4) GetUsersByIds(userIds []string) ([]*User, *Response) {
|
||||
r, err := c.DoApiPost(c.GetUsersRoute()+"/ids", ArrayToJson(userIds))
|
||||
@@ -5127,3 +5138,13 @@ func (c *Client4) RequestTrialLicense(users int) (bool, *Response) {
|
||||
defer closeBody(r)
|
||||
return CheckStatusOK(r), BuildResponse(r)
|
||||
}
|
||||
|
||||
// GetGroupStats retrieves stats for a Mattermost Group
|
||||
func (c *Client4) GetGroupStats(groupID string) (*GroupStats, *Response) {
|
||||
r, appErr := c.DoApiGet(c.GetGroupRoute(groupID)+"/stats", "")
|
||||
if appErr != nil {
|
||||
return nil, BuildErrorResponse(r, appErr)
|
||||
}
|
||||
defer closeBody(r)
|
||||
return GroupStatsFromJson(r.Body), BuildResponse(r)
|
||||
}
|
||||
|
||||
@@ -94,6 +94,11 @@ type PageOpts struct {
|
||||
PerPage int
|
||||
}
|
||||
|
||||
type GroupStats struct {
|
||||
GroupID string `json:"group_id"`
|
||||
TotalMemberCount int64 `json:"total_member_count"`
|
||||
}
|
||||
|
||||
func (group *Group) Patch(patch *GroupPatch) {
|
||||
if patch.Name != nil {
|
||||
group.Name = patch.Name
|
||||
@@ -208,3 +213,9 @@ func GroupPatchFromJson(data io.Reader) *GroupPatch {
|
||||
json.NewDecoder(data).Decode(&groupPatch)
|
||||
return groupPatch
|
||||
}
|
||||
|
||||
func GroupStatsFromJson(data io.Reader) *GroupStats {
|
||||
var groupStats *GroupStats
|
||||
json.NewDecoder(data).Decode(&groupStats)
|
||||
return groupStats
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ type UserGetOptions struct {
|
||||
InChannelId string
|
||||
// Filters the users not in the channel
|
||||
NotInChannelId string
|
||||
// Filters the users in the group
|
||||
InGroupId string
|
||||
// Filters the users group constrained
|
||||
GroupConstrained bool
|
||||
// Filters the users without a team
|
||||
|
||||
@@ -18,6 +18,7 @@ type UserSearch struct {
|
||||
NotInTeamId string `json:"not_in_team_id"`
|
||||
InChannelId string `json:"in_channel_id"`
|
||||
NotInChannelId string `json:"not_in_channel_id"`
|
||||
InGroupId string `json:"in_group_id"`
|
||||
GroupConstrained bool `json:"group_constrained"`
|
||||
AllowInactive bool `json:"allow_inactive"`
|
||||
WithoutTeam bool `json:"without_team"`
|
||||
|
||||
@@ -8225,6 +8225,24 @@ func (s *OpenTracingLayerUserStore) SearchInChannel(channelId string, term strin
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerUserStore) SearchInGroup(groupID string, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "UserStore.SearchInGroup")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := s.UserStore.SearchInGroup(groupID, term, options)
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerUserStore) SearchNotInChannel(teamId string, channelId string, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "UserStore.SearchNotInChannel")
|
||||
|
||||
@@ -1240,6 +1240,15 @@ func (us SqlUserStore) SearchInChannel(channelId string, term string, options *m
|
||||
return us.performSearch(query, term, options)
|
||||
}
|
||||
|
||||
func (us SqlUserStore) SearchInGroup(groupID string, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError) {
|
||||
query := us.usersQuery.
|
||||
Join("GroupMembers gm ON ( gm.UserId = u.Id AND gm.GroupId = ? )", groupID).
|
||||
OrderBy("Username ASC").
|
||||
Limit(uint64(options.Limit))
|
||||
|
||||
return us.performSearch(query, term, options)
|
||||
}
|
||||
|
||||
var spaceFulltextSearchChar = []string{
|
||||
"<",
|
||||
">",
|
||||
|
||||
@@ -322,6 +322,7 @@ type UserStore interface {
|
||||
SearchInChannel(channelId string, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError)
|
||||
SearchNotInChannel(teamId string, channelId string, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError)
|
||||
SearchWithoutTeam(term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError)
|
||||
SearchInGroup(groupID string, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError)
|
||||
AnalyticsGetInactiveUsersCount() (int64, *model.AppError)
|
||||
AnalyticsGetSystemAdminCount() (int64, *model.AppError)
|
||||
AnalyticsGetGuestCount() (int64, *model.AppError)
|
||||
|
||||
@@ -1164,6 +1164,31 @@ func (_m *UserStore) SearchInChannel(channelId string, term string, options *mod
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SearchInGroup provides a mock function with given fields: groupID, term, options
|
||||
func (_m *UserStore) SearchInGroup(groupID string, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError) {
|
||||
ret := _m.Called(groupID, term, options)
|
||||
|
||||
var r0 []*model.User
|
||||
if rf, ok := ret.Get(0).(func(string, string, *model.UserSearchOptions) []*model.User); ok {
|
||||
r0 = rf(groupID, term, options)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.User)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(1).(func(string, string, *model.UserSearchOptions) *model.AppError); ok {
|
||||
r1 = rf(groupID, term, options)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SearchNotInChannel provides a mock function with given fields: teamId, channelId, term, options
|
||||
func (_m *UserStore) SearchNotInChannel(teamId string, channelId string, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError) {
|
||||
ret := _m.Called(teamId, channelId, term, options)
|
||||
|
||||
@@ -72,6 +72,7 @@ func TestUserStore(t *testing.T, ss store.Store, s SqlSupplier) {
|
||||
t.Run("SearchInChannel", func(t *testing.T) { testUserStoreSearchInChannel(t, ss) })
|
||||
t.Run("SearchNotInTeam", func(t *testing.T) { testUserStoreSearchNotInTeam(t, ss) })
|
||||
t.Run("SearchWithoutTeam", func(t *testing.T) { testUserStoreSearchWithoutTeam(t, ss) })
|
||||
t.Run("SearchInGroup", func(t *testing.T) { testUserStoreSearchInGroup(t, ss) })
|
||||
t.Run("GetProfilesNotInTeam", func(t *testing.T) { testUserStoreGetProfilesNotInTeam(t, ss) })
|
||||
t.Run("ClearAllCustomRoleAssignments", func(t *testing.T) { testUserStoreClearAllCustomRoleAssignments(t, ss) })
|
||||
t.Run("GetAllAfter", func(t *testing.T) { testUserStoreGetAllAfter(t, ss) })
|
||||
@@ -2918,6 +2919,146 @@ func testUserStoreSearchWithoutTeam(t *testing.T, ss store.Store) {
|
||||
}
|
||||
}
|
||||
|
||||
func testUserStoreSearchInGroup(t *testing.T, ss store.Store) {
|
||||
u1 := &model.User{
|
||||
Username: "jimbo1" + model.NewId(),
|
||||
FirstName: "Tim",
|
||||
LastName: "Bill",
|
||||
Nickname: "Rob",
|
||||
Email: "harold" + model.NewId() + "@simulator.amazonses.com",
|
||||
}
|
||||
_, err := ss.User().Save(u1)
|
||||
require.Nil(t, err)
|
||||
defer func() { require.Nil(t, ss.User().PermanentDelete(u1.Id)) }()
|
||||
|
||||
u2 := &model.User{
|
||||
Username: "jim-bobby" + model.NewId(),
|
||||
Email: MakeEmail(),
|
||||
}
|
||||
_, err = ss.User().Save(u2)
|
||||
require.Nil(t, err)
|
||||
defer func() { require.Nil(t, ss.User().PermanentDelete(u2.Id)) }()
|
||||
|
||||
u3 := &model.User{
|
||||
Username: "jimbo3" + model.NewId(),
|
||||
Email: MakeEmail(),
|
||||
DeleteAt: 1,
|
||||
}
|
||||
_, err = ss.User().Save(u3)
|
||||
require.Nil(t, err)
|
||||
defer func() { require.Nil(t, ss.User().PermanentDelete(u3.Id)) }()
|
||||
|
||||
// The users returned from the database will have AuthData as an empty string.
|
||||
nilAuthData := model.NewString("")
|
||||
|
||||
u1.AuthData = nilAuthData
|
||||
u2.AuthData = nilAuthData
|
||||
u3.AuthData = nilAuthData
|
||||
|
||||
g1 := &model.Group{
|
||||
Name: model.NewString(model.NewId()),
|
||||
DisplayName: model.NewId(),
|
||||
Description: model.NewId(),
|
||||
Source: model.GroupSourceLdap,
|
||||
RemoteId: model.NewId(),
|
||||
}
|
||||
_, err = ss.Group().Create(g1)
|
||||
require.Nil(t, err)
|
||||
|
||||
g2 := &model.Group{
|
||||
Name: model.NewString(model.NewId()),
|
||||
DisplayName: model.NewId(),
|
||||
Description: model.NewId(),
|
||||
Source: model.GroupSourceLdap,
|
||||
RemoteId: model.NewId(),
|
||||
}
|
||||
_, err = ss.Group().Create(g2)
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err = ss.Group().UpsertMember(g1.Id, u1.Id)
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err = ss.Group().UpsertMember(g2.Id, u2.Id)
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err = ss.Group().UpsertMember(g1.Id, u3.Id)
|
||||
require.Nil(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
Description string
|
||||
GroupId string
|
||||
Term string
|
||||
Options *model.UserSearchOptions
|
||||
Expected []*model.User
|
||||
}{
|
||||
{
|
||||
"search jimb, group 1",
|
||||
g1.Id,
|
||||
"jimb",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
|
||||
},
|
||||
[]*model.User{u1},
|
||||
},
|
||||
{
|
||||
"search jimb, group 1, allow inactive",
|
||||
g1.Id,
|
||||
"jimb",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
AllowInactive: true,
|
||||
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
|
||||
},
|
||||
[]*model.User{u1, u3},
|
||||
},
|
||||
{
|
||||
"search jimb, group 1, limit 1",
|
||||
g1.Id,
|
||||
"jimb",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
AllowInactive: true,
|
||||
Limit: 1,
|
||||
},
|
||||
[]*model.User{u1},
|
||||
},
|
||||
{
|
||||
"search jimb, group 2",
|
||||
g2.Id,
|
||||
"jimb",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
|
||||
},
|
||||
[]*model.User{},
|
||||
},
|
||||
{
|
||||
"search jimb, allow inactive, group 2",
|
||||
g2.Id,
|
||||
"jimb",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
AllowInactive: true,
|
||||
Limit: model.USER_SEARCH_DEFAULT_LIMIT,
|
||||
},
|
||||
[]*model.User{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.Description, func(t *testing.T) {
|
||||
users, err := ss.User().SearchInGroup(
|
||||
testCase.GroupId,
|
||||
testCase.Term,
|
||||
testCase.Options,
|
||||
)
|
||||
require.Nil(t, err)
|
||||
assertUsers(t, testCase.Expected, users)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testCount(t *testing.T, ss store.Store) {
|
||||
// Regular
|
||||
teamId := model.NewId()
|
||||
|
||||
@@ -7444,6 +7444,22 @@ func (s *TimerLayerUserStore) SearchInChannel(channelId string, term string, opt
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (s *TimerLayerUserStore) SearchInGroup(groupID string, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError) {
|
||||
start := timemodule.Now()
|
||||
|
||||
resultVar0, resultVar1 := s.UserStore.SearchInGroup(groupID, term, options)
|
||||
|
||||
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if resultVar1 == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("UserStore.SearchInGroup", success, elapsed)
|
||||
}
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (s *TimerLayerUserStore) SearchNotInChannel(teamId string, channelId string, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError) {
|
||||
start := timemodule.Now()
|
||||
|
||||
|
||||
Ссылка в новой задаче
Block a user