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
a5ffcc26
Commit
a5ffcc26
authored
Sep 14, 2020
by
charlie ablett
Committed by
Bob Van Landuyt
Sep 14, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Expose group memberships under group
- Use GroupMembersFinder
parent
cb3da621
Changes
15
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
307 additions
and
81 deletions
+307
-81
app/graphql/resolvers/group_members_resolver.rb
app/graphql/resolvers/group_members_resolver.rb
+19
-0
app/graphql/resolvers/members_resolver.rb
app/graphql/resolvers/members_resolver.rb
+24
-0
app/graphql/resolvers/project_members_resolver.rb
app/graphql/resolvers/project_members_resolver.rb
+3
-13
app/graphql/types/group_type.rb
app/graphql/types/group_type.rb
+6
-0
app/graphql/types/member_interface.rb
app/graphql/types/member_interface.rb
+1
-2
app/policies/group_policy.rb
app/policies/group_policy.rb
+1
-0
changelogs/unreleased/233856-cablett-group-users.yml
changelogs/unreleased/233856-cablett-group-users.yml
+5
-0
doc/api/graphql/reference/gitlab_schema.graphql
doc/api/graphql/reference/gitlab_schema.graphql
+30
-0
doc/api/graphql/reference/gitlab_schema.json
doc/api/graphql/reference/gitlab_schema.json
+63
-0
spec/graphql/resolvers/group_members_resolver_spec.rb
spec/graphql/resolvers/group_members_resolver_spec.rb
+12
-0
spec/graphql/resolvers/project_members_resolver_spec.rb
spec/graphql/resolvers/project_members_resolver_spec.rb
+4
-57
spec/graphql/types/group_type_spec.rb
spec/graphql/types/group_type_spec.rb
+8
-1
spec/requests/api/graphql/group/group_members_spec.rb
spec/requests/api/graphql/group/group_members_spec.rb
+67
-0
spec/requests/api/graphql/group_query_spec.rb
spec/requests/api/graphql/group_query_spec.rb
+3
-8
spec/support/shared_examples/graphql/members_shared_examples.rb
...upport/shared_examples/graphql/members_shared_examples.rb
+61
-0
No files found.
app/graphql/resolvers/group_members_resolver.rb
0 → 100644
View file @
a5ffcc26
# frozen_string_literal: true
module
Resolvers
class
GroupMembersResolver
<
MembersResolver
authorize
:read_group_member
private
def
preloads
{
user:
[
:user
,
:source
]
}
end
def
finder_class
GroupMembersFinder
end
end
end
app/graphql/resolvers/members_resolver.rb
0 → 100644
View file @
a5ffcc26
# frozen_string_literal: true
module
Resolvers
class
MembersResolver
<
BaseResolver
include
Gitlab
::
Graphql
::
Authorize
::
AuthorizeResource
include
LooksAhead
argument
:search
,
GraphQL
::
STRING_TYPE
,
required:
false
,
description:
'Search query'
def
resolve_with_lookahead
(
**
args
)
authorize!
(
object
)
apply_lookahead
(
finder_class
.
new
(
object
,
current_user
,
params:
args
).
execute
)
end
private
def
finder_class
# override in subclass
end
end
end
app/graphql/resolvers/project_members_resolver.rb
View file @
a5ffcc26
# frozen_string_literal: true
module
Resolvers
class
ProjectMembersResolver
<
BaseResolver
include
Gitlab
::
Graphql
::
Authorize
::
AuthorizeResource
argument
:search
,
GraphQL
::
STRING_TYPE
,
required:
false
,
description:
'Search query'
class
ProjectMembersResolver
<
MembersResolver
type
Types
::
MemberInterface
,
null:
true
authorize
:read_project_member
alias_method
:project
,
:object
def
resolve
(
**
args
)
authorize!
(
project
)
private
def
finder_class
MembersFinder
.
new
(
project
,
current_user
,
params:
args
)
.
execute
end
end
end
app/graphql/types/group_type.rb
View file @
a5ffcc26
...
...
@@ -75,6 +75,12 @@ module Types
description:
'Title of the label'
end
field
:group_members
,
Types
::
GroupMemberType
.
connection_type
,
description:
'A membership of a user within this group'
,
extras:
[
:lookahead
],
resolver:
Resolvers
::
GroupMembersResolver
def
label
(
title
:)
BatchLoader
::
GraphQL
.
for
(
title
).
batch
(
key:
group
)
do
|
titles
,
loader
,
args
|
LabelsFinder
...
...
app/graphql/types/member_interface.rb
View file @
a5ffcc26
...
...
@@ -23,8 +23,7 @@ module Types
description:
'Date and time the membership expires'
field
:user
,
Types
::
UserType
,
null:
false
,
description:
'User that is associated with the member object'
,
resolve:
->
(
obj
,
_args
,
_ctx
)
{
Gitlab
::
Graphql
::
Loaders
::
BatchModelLoader
.
new
(
User
,
obj
.
user_id
).
find
}
description:
'User that is associated with the member object'
definition_methods
do
def
resolve_type
(
object
,
context
)
...
...
app/policies/group_policy.rb
View file @
a5ffcc26
...
...
@@ -80,6 +80,7 @@ class GroupPolicy < BasePolicy
enable
:read_list
enable
:read_label
enable
:read_board
enable
:read_group_member
end
rule
{
~
can?
(
:read_group
)
}.
policy
do
...
...
changelogs/unreleased/233856-cablett-group-users.yml
0 → 100644
View file @
a5ffcc26
---
title
:
Expose group memberships under group via GraphQL
merge_request
:
39331
author
:
type
:
added
doc/api/graphql/reference/gitlab_schema.graphql
View file @
a5ffcc26
...
...
@@ -6711,6 +6711,36 @@ type Group {
"""
fullPath
:
ID
!
"""
A
membership
of
a
user
within
this
group
"""
groupMembers
(
"""
Returns
the
elements
in
the
list
that
come
after
the
specified
cursor
.
"""
after
:
String
"""
Returns
the
elements
in
the
list
that
come
before
the
specified
cursor
.
"""
before
:
String
"""
Returns
the
first
_n_
elements
from
the
list
.
"""
first
:
Int
"""
Returns
the
last
_n_
elements
from
the
list
.
"""
last
:
Int
"""
Search
query
"""
search
:
String
):
GroupMemberConnection
"""
Indicates
if
Group
timelogs
are
enabled
for
namespace
"""
...
...
doc/api/graphql/reference/gitlab_schema.json
View file @
a5ffcc26
...
...
@@ -18665,6 +18665,69 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "groupMembers",
"description": "A membership of a user within this group",
"args": [
{
"name": "search",
"description": "Search query",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "before",
"description": "Returns the elements in the list that come before the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "first",
"description": "Returns the first _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
},
{
"name": "last",
"description": "Returns the last _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "GroupMemberConnection",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "groupTimelogsEnabled",
"description": "Indicates if Group timelogs are enabled for namespace",
spec/graphql/resolvers/group_members_resolver_spec.rb
0 → 100644
View file @
a5ffcc26
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
Resolvers
::
GroupMembersResolver
do
include
GraphqlHelpers
it_behaves_like
'querying members with a group'
do
let_it_be
(
:resource_member
)
{
create
(
:group_member
,
user:
user_1
,
group:
group_1
)
}
let_it_be
(
:resource
)
{
group_1
}
end
end
spec/graphql/resolvers/project_members_resolver_spec.rb
View file @
a5ffcc26
...
...
@@ -5,62 +5,9 @@ require 'spec_helper'
RSpec
.
describe
Resolvers
::
ProjectMembersResolver
do
include
GraphqlHelpers
context
"with a group"
do
let_it_be
(
:root_group
)
{
create
(
:group
)
}
let_it_be
(
:group_1
)
{
create
(
:group
,
parent:
root_group
)
}
let_it_be
(
:group_2
)
{
create
(
:group
,
parent:
root_group
)
}
let_it_be
(
:project
)
{
create
(
:project
,
group:
group_1
)
}
let_it_be
(
:user_1
)
{
create
(
:user
,
name:
'test user'
)
}
let_it_be
(
:user_2
)
{
create
(
:user
,
name:
'test user 2'
)
}
let_it_be
(
:user_3
)
{
create
(
:user
,
name:
'another user 1'
)
}
let_it_be
(
:user_4
)
{
create
(
:user
,
name:
'another user 2'
)
}
let_it_be
(
:project_member
)
{
create
(
:project_member
,
user:
user_1
,
project:
project
)
}
let_it_be
(
:group_1_member
)
{
create
(
:group_member
,
user:
user_2
,
group:
group_1
)
}
let_it_be
(
:group_2_member
)
{
create
(
:group_member
,
user:
user_3
,
group:
group_2
)
}
let_it_be
(
:root_group_member
)
{
create
(
:group_member
,
user:
user_4
,
group:
root_group
)
}
let
(
:args
)
{
{}
}
subject
do
resolve
(
described_class
,
obj:
project
,
args:
args
,
ctx:
{
current_user:
user_4
})
end
describe
'#resolve'
do
it
'finds all project members'
do
expect
(
subject
).
to
contain_exactly
(
project_member
,
group_1_member
,
root_group_member
)
end
context
'with search'
do
context
'when the search term matches a user'
do
let
(
:args
)
{
{
search:
'test'
}
}
it
'searches users by user name'
do
expect
(
subject
).
to
contain_exactly
(
project_member
,
group_1_member
)
end
end
context
'when the search term does not match any user'
do
let
(
:args
)
{
{
search:
'nothing'
}
}
it
'is empty'
do
expect
(
subject
).
to
be_empty
end
end
end
context
'when user can not see project members'
do
let_it_be
(
:other_user
)
{
create
(
:user
)
}
subject
do
resolve
(
described_class
,
obj:
project
,
args:
args
,
ctx:
{
current_user:
other_user
})
end
it
'raises an error'
do
expect
{
subject
}.
to
raise_error
(
Gitlab
::
Graphql
::
Errors
::
ResourceNotAvailable
)
end
end
end
it_behaves_like
'querying members with a group'
do
let_it_be
(
:project
)
{
create
(
:project
,
group:
group_1
)
}
let_it_be
(
:resource_member
)
{
create
(
:project_member
,
user:
user_1
,
project:
project
)
}
let_it_be
(
:resource
)
{
project
}
end
end
spec/graphql/types/group_type_spec.rb
View file @
a5ffcc26
...
...
@@ -16,7 +16,7 @@ RSpec.describe GitlabSchema.types['Group'] do
web_url avatar_url share_with_group_lock project_creation_level
subgroup_creation_level require_two_factor_authentication
two_factor_grace_period auto_devops_enabled emails_disabled
mentions_disabled parent boards milestones
mentions_disabled parent boards milestones
group_members
]
expect
(
described_class
).
to
include_graphql_fields
(
*
expected_fields
)
...
...
@@ -30,5 +30,12 @@ RSpec.describe GitlabSchema.types['Group'] do
end
end
describe
'members field'
do
subject
{
described_class
.
fields
[
'groupMembers'
]
}
it
{
is_expected
.
to
have_graphql_type
(
Types
::
GroupMemberType
.
connection_type
)
}
it
{
is_expected
.
to
have_graphql_resolver
(
Resolvers
::
GroupMembersResolver
)
}
end
it_behaves_like
'a GraphQL type with labels'
end
spec/requests/api/graphql/group/group_members_spec.rb
0 → 100644
View file @
a5ffcc26
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
'getting group members information'
do
include
GraphqlHelpers
let_it_be
(
:group
)
{
create
(
:group
,
:public
)
}
let_it_be
(
:user
)
{
create
(
:user
)
}
let_it_be
(
:user_1
)
{
create
(
:user
,
username:
'user'
)
}
let_it_be
(
:user_2
)
{
create
(
:user
,
username:
'test'
)
}
let
(
:member_data
)
{
graphql_data
[
'group'
][
'groupMembers'
][
'edges'
]
}
before
do
[
user_1
,
user_2
].
each
{
|
user
|
group
.
add_guest
(
user
)
}
end
context
'when the request is correct'
do
it_behaves_like
'a working graphql query'
do
before
do
fetch_members
(
user
)
end
end
it
'returns group members successfully'
do
fetch_members
(
user
)
expect
(
graphql_errors
).
to
be_nil
expect_array_response
(
user_1
.
to_global_id
.
to_s
,
user_2
.
to_global_id
.
to_s
)
end
it
'returns members that match the search query'
do
fetch_members
(
user
,
{
search:
'test'
})
expect
(
graphql_errors
).
to
be_nil
expect_array_response
(
user_2
.
to_global_id
.
to_s
)
end
end
def
fetch_members
(
user
=
nil
,
args
=
{})
post_graphql
(
members_query
(
args
),
current_user:
user
)
end
def
members_query
(
args
=
{})
members_node
=
<<~
NODE
edges {
node {
user {
id
}
}
}
NODE
graphql_query_for
(
"group"
,
{
full_path:
group
.
full_path
},
[
query_graphql_field
(
"groupMembers"
,
args
,
members_node
)]
)
end
def
expect_array_response
(
*
items
)
expect
(
response
).
to
have_gitlab_http_status
(
:success
)
expect
(
member_data
).
to
be_an
Array
expect
(
member_data
.
map
{
|
node
|
node
[
"node"
][
"user"
][
"id"
]
}).
to
match_array
(
items
)
end
end
spec/requests/api/graphql/group_query_spec.rb
View file @
a5ffcc26
...
...
@@ -89,18 +89,13 @@ RSpec.describe 'getting group information', :do_not_mock_admin_mode do
end
it
'avoids N+1 queries'
do
control_count
=
ActiveRecord
::
QueryRecorder
.
new
do
post_graphql
(
group_query
(
group1
),
current_user:
admin
)
end
.
count
pending
(
'See: https://gitlab.com/gitlab-org/gitlab/-/issues/245272'
)
queries
=
[{
query:
group_query
(
group1
)
},
{
query:
group_query
(
group2
)
}]
expect
do
post_multiplex
(
queries
,
current_user:
admin
)
end
.
not_to
exceed_query_limit
(
control_count
)
expect
(
graphql_errors
).
to
contain_exactly
(
nil
,
nil
)
expect
{
post_multiplex
(
queries
,
current_user:
admin
)
}
.
to
issue_same_number_of_queries_as
{
post_graphql
(
group_query
(
group1
),
current_user:
admin
)
}
end
end
...
...
spec/support/shared_examples/graphql/members_shared_examples.rb
View file @
a5ffcc26
...
...
@@ -20,3 +20,64 @@ RSpec.shared_examples 'a working membership object query' do |model_option|
).
to
eq
(
'DEVELOPER'
)
end
end
RSpec
.
shared_examples
'querying members with a group'
do
let_it_be
(
:root_group
)
{
create
(
:group
,
:private
)
}
let_it_be
(
:group_1
)
{
create
(
:group
,
:private
,
parent:
root_group
,
name:
'Main Group'
)
}
let_it_be
(
:group_2
)
{
create
(
:group
,
:private
,
parent:
root_group
)
}
let_it_be
(
:user_1
)
{
create
(
:user
,
name:
'test user'
)
}
let_it_be
(
:user_2
)
{
create
(
:user
,
name:
'test user 2'
)
}
let_it_be
(
:user_3
)
{
create
(
:user
,
name:
'another user 1'
)
}
let_it_be
(
:user_4
)
{
create
(
:user
,
name:
'another user 2'
)
}
let_it_be
(
:root_group_member
)
{
create
(
:group_member
,
user:
user_4
,
group:
root_group
)
}
let_it_be
(
:group_1_member
)
{
create
(
:group_member
,
user:
user_2
,
group:
group_1
)
}
let_it_be
(
:group_2_member
)
{
create
(
:group_member
,
user:
user_3
,
group:
group_2
)
}
let
(
:args
)
{
{}
}
subject
do
resolve
(
described_class
,
obj:
resource
,
args:
args
,
ctx:
{
current_user:
user_4
})
end
describe
'#resolve'
do
before
do
group_1
.
add_maintainer
(
user_4
)
end
it
'finds all resource members'
do
expect
(
subject
).
to
contain_exactly
(
resource_member
,
group_1_member
,
root_group_member
)
end
context
'with search'
do
context
'when the search term matches a user'
do
let
(
:args
)
{
{
search:
'test'
}
}
it
'searches users by user name'
do
expect
(
subject
).
to
contain_exactly
(
resource_member
,
group_1_member
)
end
end
context
'when the search term does not match any user'
do
let
(
:args
)
{
{
search:
'nothing'
}
}
it
'is empty'
do
expect
(
subject
).
to
be_empty
end
end
end
context
'when user can not see resource members'
do
let_it_be
(
:other_user
)
{
create
(
:user
)
}
subject
do
resolve
(
described_class
,
obj:
resource
,
args:
args
,
ctx:
{
current_user:
other_user
})
end
it
'raises an error'
do
expect
{
subject
}.
to
raise_error
(
Gitlab
::
Graphql
::
Errors
::
ResourceNotAvailable
)
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