Commit 388bc64c authored by Michael Kozono's avatar Michael Kozono

Merge branch 'ph/329783/interactsWithMergeRequestProjectMembers' into 'master'

Adds mergeRequestInteraction to GraphQL user type

See merge request gitlab-org/gitlab!77048
parents cf9b4b60 09ee9d96
...@@ -25,6 +25,12 @@ module Types ...@@ -25,6 +25,12 @@ module Types
field :user, Types::UserType, null: true, field :user, Types::UserType, null: true,
description: 'User that is associated with the member object.' description: 'User that is associated with the member object.'
field :merge_request_interaction, Types::UserMergeRequestInteractionType,
null: true,
description: 'Find a merge request.' do
argument :id, ::Types::GlobalIDType[::MergeRequest], required: true, description: 'Global ID of the merge request.'
end
definition_methods do definition_methods do
def resolve_type(object, context) def resolve_type(object, context)
case object case object
...@@ -37,5 +43,11 @@ module Types ...@@ -37,5 +43,11 @@ module Types
end end
end end
end end
def merge_request_interaction(id: nil)
Gitlab::Graphql::Lazy.with_value(GitlabSchema.object_from_id(id, expected_class: ::MergeRequest)) do |merge_request|
Users::MergeRequestInteraction.new(user: object.user, merge_request: merge_request) if merge_request
end
end
end end
end end
...@@ -5,6 +5,8 @@ module Types ...@@ -5,6 +5,8 @@ module Types
module InteractsWithMergeRequest module InteractsWithMergeRequest
extend ActiveSupport::Concern extend ActiveSupport::Concern
include FindClosest
included do included do
field :merge_request_interaction, field :merge_request_interaction,
type: ::Types::UserMergeRequestInteractionType, type: ::Types::UserMergeRequestInteractionType,
...@@ -13,8 +15,9 @@ module Types ...@@ -13,8 +15,9 @@ module Types
description: "Details of this user's interactions with the merge request." description: "Details of this user's interactions with the merge request."
end end
def merge_request_interaction(parent:) def merge_request_interaction(parent:, id: nil)
merge_request = closest_parent([::Types::MergeRequestType], parent) merge_request = closest_parent([::Types::MergeRequestType], parent)
return unless merge_request return unless merge_request
Users::MergeRequestInteraction.new(user: object, merge_request: merge_request) Users::MergeRequestInteraction.new(user: object, merge_request: merge_request)
......
...@@ -11382,6 +11382,20 @@ Represents a Group Membership. ...@@ -11382,6 +11382,20 @@ Represents a Group Membership.
| <a id="groupmemberuser"></a>`user` | [`UserCore`](#usercore) | User that is associated with the member object. | | <a id="groupmemberuser"></a>`user` | [`UserCore`](#usercore) | User that is associated with the member object. |
| <a id="groupmemberuserpermissions"></a>`userPermissions` | [`GroupPermissions!`](#grouppermissions) | Permissions for the current user on the resource. | | <a id="groupmemberuserpermissions"></a>`userPermissions` | [`GroupPermissions!`](#grouppermissions) | Permissions for the current user on the resource. |
#### Fields with arguments
##### `GroupMember.mergeRequestInteraction`
Find a merge request.
Returns [`UserMergeRequestInteraction`](#usermergerequestinteraction).
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="groupmembermergerequestinteractionid"></a>`id` | [`MergeRequestID!`](#mergerequestid) | Global ID of the merge request. |
### `GroupPermissions` ### `GroupPermissions`
#### Fields #### Fields
...@@ -14311,6 +14325,20 @@ Represents a Project Membership. ...@@ -14311,6 +14325,20 @@ Represents a Project Membership.
| <a id="projectmemberuser"></a>`user` | [`UserCore`](#usercore) | User that is associated with the member object. | | <a id="projectmemberuser"></a>`user` | [`UserCore`](#usercore) | User that is associated with the member object. |
| <a id="projectmemberuserpermissions"></a>`userPermissions` | [`ProjectPermissions!`](#projectpermissions) | Permissions for the current user on the resource. | | <a id="projectmemberuserpermissions"></a>`userPermissions` | [`ProjectPermissions!`](#projectpermissions) | Permissions for the current user on the resource. |
#### Fields with arguments
##### `ProjectMember.mergeRequestInteraction`
Find a merge request.
Returns [`UserMergeRequestInteraction`](#usermergerequestinteraction).
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="projectmembermergerequestinteractionid"></a>`id` | [`MergeRequestID!`](#mergerequestid) | Global ID of the merge request. |
### `ProjectPermissions` ### `ProjectPermissions`
#### Fields #### Fields
...@@ -18725,6 +18753,20 @@ Implementations: ...@@ -18725,6 +18753,20 @@ Implementations:
| <a id="memberinterfaceupdatedat"></a>`updatedAt` | [`Time`](#time) | Date and time the membership was last updated. | | <a id="memberinterfaceupdatedat"></a>`updatedAt` | [`Time`](#time) | Date and time the membership was last updated. |
| <a id="memberinterfaceuser"></a>`user` | [`UserCore`](#usercore) | User that is associated with the member object. | | <a id="memberinterfaceuser"></a>`user` | [`UserCore`](#usercore) | User that is associated with the member object. |
##### Fields with arguments
###### `MemberInterface.mergeRequestInteraction`
Find a merge request.
Returns [`UserMergeRequestInteraction`](#usermergerequestinteraction).
####### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="memberinterfacemergerequestinteractionid"></a>`id` | [`MergeRequestID!`](#mergerequestid) | Global ID of the merge request. |
#### `NoteableInterface` #### `NoteableInterface`
Implementations: Implementations:
...@@ -12,6 +12,7 @@ RSpec.describe Types::MemberInterface do ...@@ -12,6 +12,7 @@ RSpec.describe Types::MemberInterface do
updated_at updated_at
expires_at expires_at
user user
merge_request_interaction
] ]
expect(described_class).to have_graphql_fields(*expected_fields) expect(described_class).to have_graphql_fields(*expected_fields)
...@@ -40,4 +41,16 @@ RSpec.describe Types::MemberInterface do ...@@ -40,4 +41,16 @@ RSpec.describe Types::MemberInterface do
end end
end end
end end
describe '#merge_request_interaction' do
subject { described_class.fields['mergeRequestInteraction'] }
it 'returns the correct type' do
is_expected.to have_graphql_type(Types::UserMergeRequestInteractionType)
end
it 'has the correct arguments' do
expect(subject.arguments).to have_key('id')
end
end
end end
...@@ -110,6 +110,102 @@ RSpec.describe 'getting project members information' do ...@@ -110,6 +110,102 @@ RSpec.describe 'getting project members information' do
end end
end end
context 'merge request interactions' do
let(:project_path) { var('ID!').with(parent_project.full_path) }
let(:mr_a) do
var('MergeRequestID!')
.with(global_id_of(create(:merge_request, source_project: parent_project, source_branch: 'branch-1')))
end
let(:mr_b) do
var('MergeRequestID!')
.with(global_id_of(create(:merge_request, source_project: parent_project, source_branch: 'branch-2')))
end
let(:interaction_query) do
<<~HEREDOC
edges {
node {
user {
id
}
mrA: #{query_graphql_field(:merge_request_interaction, { id: mr_a }, 'canMerge')}
}
}
HEREDOC
end
let(:interaction_b_query) do
<<~HEREDOC
edges {
node {
user {
id
}
mrA: #{query_graphql_field(:merge_request_interaction, { id: mr_a }, 'canMerge')}
mrB: #{query_graphql_field(:merge_request_interaction, { id: mr_b }, 'canMerge')}
}
}
HEREDOC
end
it 'avoids N+1 queries, when requesting multiple MRs' do
control_query = with_signature(
[project_path, mr_a],
graphql_query_for(:project, { full_path: project_path },
query_graphql_field(:project_members, nil, interaction_query))
)
query_two = with_signature(
[project_path, mr_a, mr_b],
graphql_query_for(:project, { full_path: project_path },
query_graphql_field(:project_members, nil, interaction_b_query))
)
control_count = ActiveRecord::QueryRecorder.new do
post_graphql(control_query, current_user: user, variables: [project_path, mr_a])
end
# two project members, neither of whom can merge
expect(can_merge(:mrA)).to eq [false, false]
expect do
post_graphql(query_two, current_user: user, variables: [project_path, mr_a, mr_b])
expect(can_merge(:mrA)).to eq [false, false]
expect(can_merge(:mrB)).to eq [false, false]
end.not_to exceed_query_limit(control_count)
end
it 'avoids N+1 queries, when more users are involved' do
new_user = create(:user)
query = with_signature(
[project_path, mr_a],
graphql_query_for(:project, { full_path: project_path },
query_graphql_field(:project_members, nil, interaction_query))
)
control_count = ActiveRecord::QueryRecorder.new do
post_graphql(query, current_user: user, variables: [project_path, mr_a])
end
# two project members, neither of whom can merge
expect(can_merge(:mrA)).to eq [false, false]
parent_project.add_guest(new_user)
expect do
post_graphql(query, current_user: user, variables: [project_path, mr_a])
expect(can_merge(:mrA)).to eq [false, false, false]
end.not_to exceed_query_limit(control_count)
end
def can_merge(name)
graphql_data_at(:project, :project_members, :edges, :node, name, :can_merge)
end
end
context 'when unauthenticated' do context 'when unauthenticated' do
it 'returns members' do it 'returns members' do
fetch_members(current_user: nil, project: parent_project) fetch_members(current_user: nil, project: parent_project)
......
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