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
description: 'Issues of the project',
resolver: Resolvers::IssuesResolver
field :project_members,
Types::ProjectMemberType.connection_type,
description: 'Members of the project',
resolver: Resolvers::ProjectMembersResolver
field :environments,
Types::EnvironmentType.connection_type,
null: true,
......
---
title: Add members to project graphQL endpoint
merge_request: 33418
author:
type: added
......@@ -8632,6 +8632,36 @@ type Project {
"""
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
"""
......@@ -9018,6 +9048,61 @@ type ProjectEdge {
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 {
"""
Indicates the user can perform `admin_operations` on this resource
......
......@@ -25317,6 +25317,69 @@
"isDeprecated": false,
"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",
"description": "Indicates if there is public access to pipelines and job details of the project, including output logs and artifacts",
......@@ -26329,6 +26392,185 @@
"enumValues": 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",
"name": "ProjectPermissions",
......@@ -1258,6 +1258,16 @@ Information about pagination in a connection.
| `webUrl` | String | Web URL of the project |
| `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
| 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
it { is_expected.to have_graphql_resolver(Resolvers::EnvironmentsResolver) }
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
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