Commit e5cbeb75 authored by Jarka Košanová's avatar Jarka Košanová

Add members to project graphQL endpoint

- add ProjectMembersResolver calling the respective finder
- add members to project type
parent 9daed804
# frozen_string_literal: true
module Resolvers
class ProjectMembersResolver < BaseResolver
argument :search, GraphQL::STRING_TYPE,
required: false,
description: 'Search query'
type Types::ProjectMemberType, null: true
alias_method :project, :object
def resolve(**args)
return Member.none unless project.present?
MembersFinder
.new(project, context[:current_user], params: args)
.execute
end
end
end
# frozen_string_literal: true
module Types
class ProjectMemberType < BaseObject
graphql_name 'ProjectMember'
description 'Member of a project'
authorize :read_project
field :id, GraphQL::ID_TYPE, null: false,
description: 'ID of the member'
field :access_level, GraphQL::INT_TYPE, null: false,
description: 'Access level of the member'
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 }
end
end
...@@ -139,6 +139,11 @@ module Types ...@@ -139,6 +139,11 @@ module Types
description: 'Issues of the project', description: 'Issues of the project',
resolver: Resolvers::IssuesResolver resolver: Resolvers::IssuesResolver
field :project_members,
Types::ProjectMemberType.connection_type,
description: 'Members of the project',
resolver: Resolvers::ProjectMembersResolver
field :environments, field :environments,
Types::EnvironmentType.connection_type, Types::EnvironmentType.connection_type,
null: true, null: true,
......
---
title: Add members to project graphQL endpoint
merge_request: 33418
author:
type: added
...@@ -8632,6 +8632,36 @@ type Project { ...@@ -8632,6 +8632,36 @@ type Project {
""" """
printingMergeRequestLinkEnabled: Boolean printingMergeRequestLinkEnabled: Boolean
"""
Members of the project
"""
projectMembers(
"""
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
): ProjectMemberConnection
""" """
Indicates if there is public access to pipelines and job details of the project, including output logs and artifacts Indicates if there is public access to pipelines and job details of the project, including output logs and artifacts
""" """
...@@ -9018,6 +9048,61 @@ type ProjectEdge { ...@@ -9018,6 +9048,61 @@ type ProjectEdge {
node: Project node: Project
} }
"""
Member of a project
"""
type ProjectMember {
"""
Access level of the member
"""
accessLevel: Int!
"""
ID of the member
"""
id: ID!
"""
User that is associated with the member object
"""
user: User!
}
"""
The connection type for ProjectMember.
"""
type ProjectMemberConnection {
"""
A list of edges.
"""
edges: [ProjectMemberEdge]
"""
A list of nodes.
"""
nodes: [ProjectMember]
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
}
"""
An edge in a connection.
"""
type ProjectMemberEdge {
"""
A cursor for use in pagination.
"""
cursor: String!
"""
The item at the end of the edge.
"""
node: ProjectMember
}
type ProjectPermissions { type ProjectPermissions {
""" """
Indicates the user can perform `admin_operations` on this resource Indicates the user can perform `admin_operations` on this resource
......
...@@ -25317,6 +25317,69 @@ ...@@ -25317,6 +25317,69 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "projectMembers",
"description": "Members of the project",
"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": "ProjectMemberConnection",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "publicJobs", "name": "publicJobs",
"description": "Indicates if there is public access to pipelines and job details of the project, including output logs and artifacts", "description": "Indicates if there is public access to pipelines and job details of the project, including output logs and artifacts",
...@@ -26329,6 +26392,185 @@ ...@@ -26329,6 +26392,185 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "OBJECT",
"name": "ProjectMember",
"description": "Member of a project",
"fields": [
{
"name": "accessLevel",
"description": "Access level of the member",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "id",
"description": "ID of the member",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "user",
"description": "User that is associated with the member object",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "User",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "ProjectMemberConnection",
"description": "The connection type for ProjectMember.",
"fields": [
{
"name": "edges",
"description": "A list of edges.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "ProjectMemberEdge",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "ProjectMember",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "pageInfo",
"description": "Information to aid in pagination.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "PageInfo",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "ProjectMemberEdge",
"description": "An edge in a connection.",
"fields": [
{
"name": "cursor",
"description": "A cursor for use in pagination.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "node",
"description": "The item at the end of the edge.",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "ProjectMember",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{ {
"kind": "OBJECT", "kind": "OBJECT",
"name": "ProjectPermissions", "name": "ProjectPermissions",
...@@ -1258,6 +1258,16 @@ Information about pagination in a connection. ...@@ -1258,6 +1258,16 @@ Information about pagination in a connection.
| `webUrl` | String | Web URL of the project | | `webUrl` | String | Web URL of the project |
| `wikiEnabled` | Boolean | Indicates if Wikis are enabled for the current user | | `wikiEnabled` | Boolean | Indicates if Wikis are enabled for the current user |
## ProjectMember
Member of a project
| Name | Type | Description |
| --- | ---- | ---------- |
| `accessLevel` | Int! | Access level of the member |
| `id` | ID! | ID of the member |
| `user` | User! | User that is associated with the member object |
## ProjectPermissions ## ProjectPermissions
| Name | Type | Description | | Name | Type | Description |
......
# frozen_string_literal: true
require 'spec_helper'
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, :public, 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: { context: 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 project is nil' do
let(:project) { nil }
it 'returns nil' do
expect(subject).to be_empty
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe GitlabSchema.types['ProjectMember'] do
specify { expect(described_class.graphql_name).to eq('ProjectMember') }
it 'has the expected fields' do
expected_fields = %w[id accessLevel user]
expect(described_class).to have_graphql_fields(*expected_fields)
end
specify { expect(described_class).to require_graphql_authorizations(:read_project) }
end
...@@ -95,6 +95,13 @@ describe GitlabSchema.types['Project'] do ...@@ -95,6 +95,13 @@ describe GitlabSchema.types['Project'] do
it { is_expected.to have_graphql_resolver(Resolvers::EnvironmentsResolver) } it { is_expected.to have_graphql_resolver(Resolvers::EnvironmentsResolver) }
end end
describe 'members field' do
subject { described_class.fields['projectMembers'] }
it { is_expected.to have_graphql_type(Types::ProjectMemberType.connection_type) }
it { is_expected.to have_graphql_resolver(Resolvers::ProjectMembersResolver) }
end
describe 'boards field' do describe 'boards field' do
subject { described_class.fields['boards'] } subject { described_class.fields['boards'] }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment