Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
e1ef201b
Commit
e1ef201b
authored
Oct 30, 2020
by
Bob Van Landuyt
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch '249589-group-count' into 'master'
API for user groups count See merge request gitlab-org/gitlab!44069
parents
cb9a7736
26cf7513
Changes
14
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
249 additions
and
2 deletions
+249
-2
app/finders/user_groups_counter.rb
app/finders/user_groups_counter.rb
+30
-0
app/graphql/resolvers/users/group_count_resolver.rb
app/graphql/resolvers/users/group_count_resolver.rb
+25
-0
app/graphql/types/user_type.rb
app/graphql/types/user_type.rb
+4
-0
app/models/group.rb
app/models/group.rb
+11
-0
app/policies/user_policy.rb
app/policies/user_policy.rb
+1
-0
config/feature_flags/development/user_group_counts.yml
config/feature_flags/development/user_group_counts.yml
+7
-0
doc/api/graphql/reference/gitlab_schema.graphql
doc/api/graphql/reference/gitlab_schema.graphql
+5
-0
doc/api/graphql/reference/gitlab_schema.json
doc/api/graphql/reference/gitlab_schema.json
+14
-0
doc/api/graphql/reference/index.md
doc/api/graphql/reference/index.md
+1
-0
spec/finders/user_groups_counter_spec.rb
spec/finders/user_groups_counter_spec.rb
+45
-0
spec/graphql/resolvers/users/group_count_resolver_spec.rb
spec/graphql/resolvers/users/group_count_resolver_spec.rb
+62
-0
spec/graphql/types/user_type_spec.rb
spec/graphql/types/user_type_spec.rb
+1
-0
spec/models/group_spec.rb
spec/models/group_spec.rb
+25
-2
spec/policies/user_policy_spec.rb
spec/policies/user_policy_spec.rb
+18
-0
No files found.
app/finders/user_groups_counter.rb
0 → 100644
View file @
e1ef201b
# frozen_string_literal: true
class
UserGroupsCounter
def
initialize
(
user_ids
)
@user_ids
=
user_ids
end
def
execute
Namespace
.
unscoped
do
Namespace
.
from_union
([
groups
,
project_groups
]).
group
(
:user_id
).
count
# rubocop: disable CodeReuse/ActiveRecord
end
end
private
attr_reader
:user_ids
def
groups
Group
.
for_authorized_group_members
(
user_ids
)
.
select
(
'namespaces.*, members.user_id as user_id'
)
end
def
project_groups
Group
.
for_authorized_project_members
(
user_ids
)
.
select
(
'namespaces.*, project_authorizations.user_id as user_id'
)
end
end
app/graphql/resolvers/users/group_count_resolver.rb
0 → 100644
View file @
e1ef201b
# frozen_string_literal: true
module
Resolvers
module
Users
class
GroupCountResolver
<
BaseResolver
alias_method
:user
,
:object
def
resolve
(
**
args
)
return
unless
can_read_group_count?
BatchLoader
::
GraphQL
.
for
(
user
.
id
).
batch
do
|
user_ids
,
loader
|
results
=
UserGroupsCounter
.
new
(
user_ids
).
execute
results
.
each
do
|
user_id
,
count
|
loader
.
call
(
user_id
,
count
)
end
end
end
def
can_read_group_count?
current_user
&
.
can?
(
:read_group_count
,
user
)
end
end
end
end
app/graphql/types/user_type.rb
View file @
e1ef201b
...
@@ -32,6 +32,10 @@ module Types
...
@@ -32,6 +32,10 @@ module Types
field
:group_memberships
,
Types
::
GroupMemberType
.
connection_type
,
null:
true
,
field
:group_memberships
,
Types
::
GroupMemberType
.
connection_type
,
null:
true
,
description:
'Group memberships of the user'
,
description:
'Group memberships of the user'
,
method: :group_members
method: :group_members
field
:group_count
,
GraphQL
::
INT_TYPE
,
null:
true
,
resolver:
Resolvers
::
Users
::
GroupCountResolver
,
description:
'Group count for the user'
,
feature_flag: :user_group_counts
field
:status
,
Types
::
UserStatusType
,
null:
true
,
field
:status
,
Types
::
UserStatusType
,
null:
true
,
description:
'User status'
description:
'User status'
field
:project_memberships
,
Types
::
ProjectMemberType
.
connection_type
,
null:
true
,
field
:project_memberships
,
Types
::
ProjectMemberType
.
connection_type
,
null:
true
,
...
...
app/models/group.rb
View file @
e1ef201b
...
@@ -98,6 +98,17 @@ class Group < Namespace
...
@@ -98,6 +98,17 @@ class Group < Namespace
scope
:by_id
,
->
(
groups
)
{
where
(
id:
groups
)
}
scope
:by_id
,
->
(
groups
)
{
where
(
id:
groups
)
}
scope
:for_authorized_group_members
,
->
(
user_ids
)
do
joins
(
:group_members
)
.
where
(
"members.user_id IN (?)"
,
user_ids
)
.
where
(
"access_level >= ?"
,
Gitlab
::
Access
::
GUEST
)
end
scope
:for_authorized_project_members
,
->
(
user_ids
)
do
joins
(
projects: :project_authorizations
)
.
where
(
"project_authorizations.user_id IN (?)"
,
user_ids
)
end
class
<<
self
class
<<
self
def
sort_by_attribute
(
method
)
def
sort_by_attribute
(
method
)
if
method
==
'storage_size_desc'
if
method
==
'storage_size_desc'
...
...
app/policies/user_policy.rb
View file @
e1ef201b
...
@@ -21,6 +21,7 @@ class UserPolicy < BasePolicy
...
@@ -21,6 +21,7 @@ class UserPolicy < BasePolicy
enable
:update_user
enable
:update_user
enable
:update_user_status
enable
:update_user_status
enable
:read_user_personal_access_tokens
enable
:read_user_personal_access_tokens
enable
:read_group_count
end
end
rule
{
default
}.
enable
:read_user_profile
rule
{
default
}.
enable
:read_user_profile
...
...
config/feature_flags/development/user_group_counts.yml
0 → 100644
View file @
e1ef201b
---
name
:
user_group_counts
introduced_by_url
:
https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44069/
rollout_issue_url
:
type
:
development
group
:
group::compliance
default_enabled
:
false
doc/api/graphql/reference/gitlab_schema.graphql
View file @
e1ef201b
...
@@ -21541,6 +21541,11 @@ type User {
...
@@ -21541,6 +21541,11 @@ type User {
"""
"""
email
:
String
email
:
String
"""
Group
count
for
the
user
.
Available
only
when
feature
flag
`
user_group_counts
`
is
enabled
"""
groupCount
:
Int
"""
"""
Group
memberships
of
the
user
Group
memberships
of
the
user
"""
"""
...
...
doc/api/graphql/reference/gitlab_schema.json
View file @
e1ef201b
...
@@ -62400,6 +62400,20 @@
...
@@ -62400,6 +62400,20 @@
"isDeprecated": false,
"isDeprecated": false,
"deprecationReason": null
"deprecationReason": null
},
},
{
"name": "groupCount",
"description": "Group count for the user. Available only when feature flag `user_group_counts` is enabled",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
{
"name": "groupMemberships",
"name": "groupMemberships",
"description": "Group memberships of the user",
"description": "Group memberships of the user",
doc/api/graphql/reference/index.md
View file @
e1ef201b
...
@@ -3041,6 +3041,7 @@ Autogenerated return type of UpdateSnippet.
...
@@ -3041,6 +3041,7 @@ Autogenerated return type of UpdateSnippet.
| ----- | ---- | ----------- |
| ----- | ---- | ----------- |
|
`avatarUrl`
| String | URL of the user's avatar |
|
`avatarUrl`
| String | URL of the user's avatar |
|
`email`
| String | User email |
|
`email`
| String | User email |
|
`groupCount`
| Int | Group count for the user. Available only when feature flag
`user_group_counts`
is enabled |
|
`id`
| ID! | ID of the user |
|
`id`
| ID! | ID of the user |
|
`name`
| String! | Human-readable name of the user |
|
`name`
| String! | Human-readable name of the user |
|
`state`
| UserState! | State of the user |
|
`state`
| UserState! | State of the user |
...
...
spec/finders/user_groups_counter_spec.rb
0 → 100644
View file @
e1ef201b
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
UserGroupsCounter
do
subject
{
described_class
.
new
(
user_ids
).
execute
}
describe
'#execute'
do
let_it_be
(
:user
)
{
create
(
:user
)
}
let_it_be
(
:group1
)
{
create
(
:group
)
}
let_it_be
(
:group_member1
)
{
create
(
:group_member
,
source:
group1
,
user_id:
user
.
id
,
access_level:
Gitlab
::
Access
::
OWNER
)
}
let_it_be
(
:user_ids
)
{
[
user
.
id
]
}
it
'returns authorized group count for the user'
do
expect
(
subject
[
user
.
id
]).
to
eq
(
1
)
end
context
'when request to join group is pending'
do
let_it_be
(
:pending_group
)
{
create
(
:group
)
}
let_it_be
(
:pending_group_member
)
{
create
(
:group_member
,
requested_at:
Time
.
current
.
utc
,
source:
pending_group
,
user_id:
user
.
id
)
}
it
'does not include pending group in the count'
do
expect
(
subject
[
user
.
id
]).
to
eq
(
1
)
end
end
context
'when user is part of sub group'
do
let_it_be
(
:sub_group
)
{
create
(
:group
,
parent:
create
(
:group
))
}
let_it_be
(
:sub_group_member1
)
{
create
(
:group_member
,
source:
sub_group
,
user_id:
user
.
id
,
access_level:
Gitlab
::
Access
::
DEVELOPER
)
}
it
'includes sub group in the count'
do
expect
(
subject
[
user
.
id
]).
to
eq
(
2
)
end
end
context
'when user is part of namespaced project'
do
let_it_be
(
:project
)
{
create
(
:project
,
group:
create
(
:group
))
}
let_it_be
(
:project_member
)
{
create
(
:project_member
,
source:
project
,
user_id:
user
.
id
,
access_level:
Gitlab
::
Access
::
REPORTER
)
}
it
'includes the project group'
do
expect
(
subject
[
user
.
id
]).
to
eq
(
2
)
end
end
end
end
spec/graphql/resolvers/users/group_count_resolver_spec.rb
0 → 100644
View file @
e1ef201b
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
Resolvers
::
Users
::
GroupCountResolver
do
include
GraphqlHelpers
describe
'#resolve'
do
let_it_be
(
:user1
)
{
create
(
:user
)
}
let_it_be
(
:user2
)
{
create
(
:user
)
}
let_it_be
(
:group1
)
{
create
(
:group
)
}
let_it_be
(
:group2
)
{
create
(
:group
)
}
let_it_be
(
:project
)
{
create
(
:project
,
group:
create
(
:group
))
}
let_it_be
(
:group_member1
)
{
create
(
:group_member
,
source:
group1
,
user_id:
user1
.
id
,
access_level:
Gitlab
::
Access
::
OWNER
)
}
let_it_be
(
:project_member1
)
{
create
(
:project_member
,
source:
project
,
user_id:
user1
.
id
,
access_level:
Gitlab
::
Access
::
DEVELOPER
)
}
let_it_be
(
:group_member2
)
{
create
(
:group_member
,
source:
group2
,
user_id:
user2
.
id
,
access_level:
Gitlab
::
Access
::
DEVELOPER
)
}
it
'resolves group count for users'
do
current_user
=
user1
result
=
batch_sync
do
[
user1
,
user2
].
map
{
|
user
|
resolve_group_count
(
user
,
current_user
)
}
end
expect
(
result
).
to
eq
([
2
,
nil
])
end
context
'permissions'
do
context
'when current_user is an admin'
,
:enable_admin_mode
do
let_it_be
(
:admin
)
{
create
(
:admin
)
}
it
do
result
=
batch_sync
do
[
user1
,
user2
].
map
{
|
user
|
resolve_group_count
(
user
,
admin
)
}
end
expect
(
result
).
to
eq
([
2
,
1
])
end
end
context
'when current_user does not have access to the requested resource'
do
it
do
result
=
batch_sync
{
resolve_group_count
(
user1
,
user2
)
}
expect
(
result
).
to
be
nil
end
end
context
'when current_user does not exist'
do
it
do
result
=
batch_sync
{
resolve_group_count
(
user1
,
nil
)
}
expect
(
result
).
to
be
nil
end
end
end
end
def
resolve_group_count
(
user
,
current_user
)
resolve
(
described_class
,
obj:
user
,
ctx:
{
current_user:
current_user
})
end
end
spec/graphql/types/user_type_spec.rb
View file @
e1ef201b
...
@@ -24,6 +24,7 @@ RSpec.describe GitlabSchema.types['User'] do
...
@@ -24,6 +24,7 @@ RSpec.describe GitlabSchema.types['User'] do
authoredMergeRequests
authoredMergeRequests
assignedMergeRequests
assignedMergeRequests
groupMemberships
groupMemberships
groupCount
projectMemberships
projectMemberships
starredProjects
starredProjects
]
]
...
...
spec/models/group_spec.rb
View file @
e1ef201b
...
@@ -308,8 +308,10 @@ RSpec.describe Group do
...
@@ -308,8 +308,10 @@ RSpec.describe Group do
end
end
describe
'scopes'
do
describe
'scopes'
do
let!
(
:private_group
)
{
create
(
:group
,
:private
)
}
let_it_be
(
:private_group
)
{
create
(
:group
,
:private
)
}
let!
(
:internal_group
)
{
create
(
:group
,
:internal
)
}
let_it_be
(
:internal_group
)
{
create
(
:group
,
:internal
)
}
let_it_be
(
:user1
)
{
create
(
:user
)
}
let_it_be
(
:user2
)
{
create
(
:user
)
}
describe
'public_only'
do
describe
'public_only'
do
subject
{
described_class
.
public_only
.
to_a
}
subject
{
described_class
.
public_only
.
to_a
}
...
@@ -328,6 +330,27 @@ RSpec.describe Group do
...
@@ -328,6 +330,27 @@ RSpec.describe Group do
it
{
is_expected
.
to
match_array
([
private_group
,
internal_group
])
}
it
{
is_expected
.
to
match_array
([
private_group
,
internal_group
])
}
end
end
describe
'for_authorized_group_members'
do
let_it_be
(
:group_member1
)
{
create
(
:group_member
,
source:
private_group
,
user_id:
user1
.
id
,
access_level:
Gitlab
::
Access
::
OWNER
)
}
it
do
result
=
described_class
.
for_authorized_group_members
([
user1
.
id
,
user2
.
id
])
expect
(
result
).
to
match_array
([
private_group
])
end
end
describe
'for_authorized_project_members'
do
let_it_be
(
:project
)
{
create
(
:project
,
group:
internal_group
)
}
let_it_be
(
:project_member1
)
{
create
(
:project_member
,
source:
project
,
user_id:
user1
.
id
,
access_level:
Gitlab
::
Access
::
DEVELOPER
)
}
it
do
result
=
described_class
.
for_authorized_project_members
([
user1
.
id
,
user2
.
id
])
expect
(
result
).
to
match_array
([
internal_group
])
end
end
end
end
describe
'#to_reference'
do
describe
'#to_reference'
do
...
...
spec/policies/user_policy_spec.rb
View file @
e1ef201b
...
@@ -102,4 +102,22 @@ RSpec.describe UserPolicy do
...
@@ -102,4 +102,22 @@ RSpec.describe UserPolicy do
end
end
end
end
end
end
describe
"reading a user's group count"
do
context
"when current_user is an admin"
,
:enable_admin_mode
do
let
(
:current_user
)
{
create
(
:user
,
:admin
)
}
it
{
is_expected
.
to
be_allowed
(
:read_group_count
)
}
end
context
"for self users"
do
let
(
:user
)
{
current_user
}
it
{
is_expected
.
to
be_allowed
(
:read_group_count
)
}
end
context
"when accessing a different user's group count"
do
it
{
is_expected
.
not_to
be_allowed
(
:read_group_count
)
}
end
end
end
end
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment