Commit 8339099b authored by Alan (Maciej) Paruszewski's avatar Alan (Maciej) Paruszewski Committed by Alex Kalderimis

Add approvalRules to MergeRequest GraphQL API

This change adds new field to MergeRequest GraphQL API that allow users
to fetch information about MR approvals.

Changelog: added
EE: true
parent 84c13147
......@@ -2113,6 +2113,7 @@ Gitlab/NamespacedClass:
- 'ee/app/models/weight_note.rb'
- 'ee/app/policies/approval_merge_request_rule_policy.rb'
- 'ee/app/policies/approval_project_rule_policy.rb'
- 'ee/app/policies/approval_state_policy.rb'
- 'ee/app/policies/dast_scanner_profile_policy.rb'
- 'ee/app/policies/dast_site_profile_policy.rb'
- 'ee/app/policies/dast_site_validation_policy.rb'
......
......@@ -7568,9 +7568,19 @@ Describes a rule for who can approve merge requests.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="approvalruleapprovalsrequired"></a>`approvalsRequired` | [`Int`](#int) | Number of required approvals. |
| <a id="approvalruleapproved"></a>`approved` | [`Boolean`](#boolean) | Indicates if the rule is satisfied. |
| <a id="approvalruleapprovedby"></a>`approvedBy` | [`UserCoreConnection`](#usercoreconnection) | List of users defined in the rule that approved the merge request. (see [Connections](#connections)) |
| <a id="approvalrulecontainshiddengroups"></a>`containsHiddenGroups` | [`Boolean`](#boolean) | Indicates if the rule contains approvers from a hidden group. |
| <a id="approvalruleeligibleapprovers"></a>`eligibleApprovers` | [`UserCoreConnection`](#usercoreconnection) | List of all users eligible to approve the merge request (defined explicitly and from associated groups). (see [Connections](#connections)) |
| <a id="approvalrulegroups"></a>`groups` | [`GroupConnection`](#groupconnection) | List of groups added as approvers for the rule. (see [Connections](#connections)) |
| <a id="approvalruleid"></a>`id` | [`GlobalID!`](#globalid) | ID of the rule. |
| <a id="approvalrulename"></a>`name` | [`String`](#string) | Name of the rule. |
| <a id="approvalruleoverridden"></a>`overridden` | [`Boolean`](#boolean) | Indicates if the rule was overridden for the merge request. |
| <a id="approvalrulesection"></a>`section` | [`String`](#string) | Named section of the Code Owners file that the rule applies to. |
| <a id="approvalrulesourcerule"></a>`sourceRule` | [`ApprovalRule`](#approvalrule) | Source rule used to create the rule. |
| <a id="approvalruletype"></a>`type` | [`ApprovalRuleType`](#approvalruletype) | Type of the rule. |
| <a id="approvalruleusers"></a>`users` | [`UserCoreConnection`](#usercoreconnection) | List of users added as approvers for the rule. (see [Connections](#connections)) |
### `AwardEmoji`
......@@ -10600,6 +10610,7 @@ Maven metadata.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mergerequestallowcollaboration"></a>`allowCollaboration` | [`Boolean`](#boolean) | Indicates if members of the target project can push to the fork. |
| <a id="mergerequestapprovalstate"></a>`approvalState` | [`MergeRequestApprovalState!`](#mergerequestapprovalstate) | Information relating to rules that must be satisfied to merge this merge request. |
| <a id="mergerequestapprovalsleft"></a>`approvalsLeft` | [`Int`](#int) | Number of approvals left. |
| <a id="mergerequestapprovalsrequired"></a>`approvalsRequired` | [`Int`](#int) | Number of approvals required. |
| <a id="mergerequestapproved"></a>`approved` | [`Boolean!`](#boolean) | Indicates if the merge request has all the required approvals. Returns true if no required approvals are configured. |
......@@ -10746,6 +10757,17 @@ Returns [`String!`](#string).
| ---- | ---- | ----------- |
| <a id="mergerequestreferencefull"></a>`full` | [`Boolean`](#boolean) | Boolean option specifying whether the reference should be returned in full. |
### `MergeRequestApprovalState`
Information relating to rules that must be satisfied to merge this merge request.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mergerequestapprovalstateapprovalrulesoverwritten"></a>`approvalRulesOverwritten` | [`Boolean`](#boolean) | Indicates if the merge request approval rules are overwritten for the merge request. |
| <a id="mergerequestapprovalstaterules"></a>`rules` | [`[ApprovalRule!]`](#approvalrule) | List of approval rules associated with the merge request. |
### `MergeRequestAssignee`
A user assigned to a merge request.
......
......@@ -23,6 +23,8 @@ module EE
null: false, calls_gitaly: true,
method: :security_reports_up_to_date?,
description: 'Indicates if the target branch security reports are out of date.'
field :approval_state, ::Types::MergeRequests::ApprovalStateType, null: false,
description: 'Information relating to rules that must be satisfied to merge this merge request.'
end
def merge_trains_count
......
......@@ -6,6 +6,8 @@ module Types
description 'Describes a rule for who can approve merge requests.'
authorize :read_approval_rule
present_using ::ApprovalRulePresenter
field :id,
type: ::Types::GlobalIDType,
null: false,
......@@ -21,5 +23,60 @@ module Types
null: true,
method: :rule_type,
description: 'Type of the rule.'
field :approvals_required,
type: GraphQL::Types::Int,
null: true,
description: 'Number of required approvals.'
field :approved,
type: GraphQL::Types::Boolean,
method: :approved?,
null: true,
description: 'Indicates if the rule is satisfied.'
field :overridden,
type: GraphQL::Types::Boolean,
method: :overridden?,
null: true,
description: 'Indicates if the rule was overridden for the merge request.'
field :section,
type: GraphQL::Types::String,
null: true,
description: 'Named section of the Code Owners file that the rule applies to.'
field :contains_hidden_groups,
type: GraphQL::Types::Boolean,
method: :contains_hidden_groups?,
null: true,
description: 'Indicates if the rule contains approvers from a hidden group.'
field :source_rule,
type: self,
null: true,
description: 'Source rule used to create the rule.'
field :eligible_approvers,
type: ::Types::UserType.connection_type,
method: :approvers,
null: true,
description: 'List of all users eligible to approve the merge request (defined explicitly and from associated groups).'
field :users,
type: ::Types::UserType.connection_type,
null: true,
description: 'List of users added as approvers for the rule.'
field :approved_by,
type: ::Types::UserType.connection_type,
method: :approved_approvers,
null: true,
description: 'List of users defined in the rule that approved the merge request.'
field :groups,
type: ::Types::GroupType.connection_type,
null: true,
description: 'List of groups added as approvers for the rule.'
end
end
# frozen_string_literal: true
module Types
module MergeRequests
class ApprovalStateType < BaseObject
graphql_name 'MergeRequestApprovalState'
description 'Information relating to rules that must be satisfied to merge this merge request.'
authorize :read_merge_request
field :approval_rules_overwritten, GraphQL::Types::Boolean, method: :approval_rules_overwritten?,
description: 'Indicates if the merge request approval rules are overwritten for the merge request.', null: true
field :rules, [::Types::ApprovalRuleType], method: :wrapped_approval_rules,
description: 'List of approval rules associated with the merge request.', null: true, complexity: 5
end
end
end
# frozen_string_literal: true
class ApprovalStatePolicy < BasePolicy
delegate :project
end
......@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe GitlabSchema.types['MergeRequest'] do
it { expect(described_class).to have_graphql_fields(:approvals_required, :merge_trains_count).at_least }
it { expect(described_class).to have_graphql_fields(:approvals_required, :merge_trains_count, :approval_state).at_least }
it { expect(described_class).to have_graphql_field(:approved, complexity: 2, calls_gitaly?: true) }
it { expect(described_class).to have_graphql_field(:approvals_left, complexity: 2, calls_gitaly?: true) }
it { expect(described_class).to have_graphql_field(:has_security_reports, calls_gitaly?: true) }
......
......@@ -3,7 +3,12 @@
require 'spec_helper'
RSpec.describe GitlabSchema.types['ApprovalRule'] do
let(:fields) { %i[id name type] }
let(:fields) do
%i[
id name type approvals_required approved overridden section contains_hidden_groups source_rule
eligible_approvers users approved_by groups section
]
end
it { expect(described_class).to have_graphql_fields(fields) }
it { expect(described_class).to require_graphql_authorizations(:read_approval_rule) }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['MergeRequestApprovalState'] do
let(:fields) { %i[approval_rules_overwritten rules] }
it { expect(described_class).to have_graphql_fields(fields) }
it { expect(described_class).to require_graphql_authorizations(:read_merge_request) }
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ApprovalStatePolicy do
let!(:project) { create(:project) }
let!(:user) { create(:user) }
let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
let!(:approval_state) { ApprovalState.new(merge_request) }
subject(:policy) { described_class.new(user, approval_state) }
context 'when user does not have access to project' do
it { is_expected.to be_disallowed(:read_merge_request) }
end
context 'when user does have access to project' do
before do
project.add_developer(user)
end
it { is_expected.to be_allowed(:read_merge_request) }
end
end
......@@ -61,7 +61,13 @@ RSpec.describe 'MergeRequestReviewer' do
the_rule = eq(
'id' => global_id_of(rule),
'name' => rule.name,
'type' => 'CODE_OWNER'
'type' => 'CODE_OWNER',
'approvalsRequired' => 0,
'approved' => true,
'containsHiddenGroups' => false,
'overridden' => false,
'section' => 'codeowners',
'sourceRule' => nil
)
post_graphql(query)
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Query.project.mergeRequest.approvalState' do
include GraphqlHelpers
let_it_be_with_refind(:current_user) { create(:user) }
context 'when requesting information about approval state' do
let_it_be_with_refind(:user) { create(:user) }
let_it_be_with_refind(:group) { create(:group) }
let_it_be_with_refind(:project) { create(:project, :public, :repository, group: group) }
let_it_be_with_refind(:merge_request) { create(:merge_request, source_project: project) }
let_it_be_with_refind(:fields) do
<<~QUERY
approvalState {
approvalRulesOverwritten
rules {
id
name
type
approvalsRequired
approved
containsHiddenGroups
overridden
section
}
}
QUERY
end
let(:query) do
graphql_query_for(
:project,
{ full_path: project.full_path },
query_graphql_field(
:merge_request,
{ iid: merge_request.iid.to_s },
fields
)
)
end
let(:approval_state) do
graphql_data_at(:project,
:merge_request,
:approval_state)
end
before do
merge_request.reviewers << user
end
context 'when no approval rule is set to the MR' do
it 'returns null data' do
post_graphql(query)
expect(approval_state).to eq('approvalRulesOverwritten' => false, 'rules' => [])
end
end
context 'when the MR has approval rules configured' do
let(:code_owner_rule) { create(:code_owner_rule, merge_request: merge_request) }
before do
stub_licensed_features(merge_request_approvers: true)
code_owner_rule.users << user
end
it 'returns appropriate data' do
post_graphql(query)
expect(approval_state).to eq({
'approvalRulesOverwritten' => false,
'rules' => [{
'approvalsRequired' => 0,
'approved' => true,
'containsHiddenGroups' => false,
'id' => global_id_of(code_owner_rule),
'name' => code_owner_rule.name,
'overridden' => false,
'section' => 'codeowners',
'type' => 'CODE_OWNER'
}]
})
end
end
end
end
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