From 1db9f826c16053e32a1d234bf40b2ca399779cdf Mon Sep 17 00:00:00 2001 From: Rodolfo Santos Date: Fri, 16 Sep 2016 08:02:42 -0300 Subject: [PATCH 1/3] Add setting to only allow merge requests to be merged when all discussions are resolved MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/controllers/projects_controller.rb | 5 +- app/models/merge_request.rb | 7 +++ .../_merge_request_settings.html.haml | 4 ++ .../merge_requests/widget/_open.html.haml | 4 +- .../open/_unresolved_discussions.html.haml | 6 ++ ...setting-to-check-unresolved-discussion.yml | 4 ++ ...w_merge_if_all_discussions_are_resolved.rb | 17 ++++++ db/schema.rb | 1 + doc/api/projects.md | 10 ++++ lib/api/entities.rb | 1 + lib/api/projects.rb | 9 ++- .../merge_requests_controller_spec.rb | 26 +++++++++ spec/factories/merge_requests.rb | 5 ++ ...f_mergeable_with_unresolved_discussions.rb | 53 +++++++++++++++++ spec/models/merge_request_spec.rb | 58 ++++++++++++++++++- spec/requests/api/projects_spec.rb | 36 +++++++++++- 16 files changed, 238 insertions(+), 8 deletions(-) create mode 100644 app/views/projects/merge_requests/widget/open/_unresolved_discussions.html.haml create mode 100644 changelogs/unreleased/20968-add-setting-to-check-unresolved-discussion.yml create mode 100644 db/migrate/20160914131004_only_allow_merge_if_all_discussions_are_resolved.rb create mode 100644 spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions.rb diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index bce5e29d8d81..ee72c8ba72f5 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -334,8 +334,9 @@ class ProjectsController < Projects::ApplicationController :issues_tracker_id, :default_branch, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex, - :public_builds, :only_allow_merge_if_build_succeeds, :request_access_enabled, - :lfs_enabled, project_feature_attributes + :public_builds, :only_allow_merge_if_build_succeeds, + :only_allow_merge_if_all_discussions_are_resolved, + :request_access_enabled, :lfs_enabled, project_feature_attributes ) end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 6b8ac3fb48b9..d76feb9680e0 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -425,6 +425,7 @@ class MergeRequest < ActiveRecord::Base return false if work_in_progress? return false if broken? return false unless skip_ci_check || mergeable_ci_state? + return false unless mergeable_discussions_state? true end @@ -493,6 +494,12 @@ class MergeRequest < ActiveRecord::Base discussions_resolvable? && diff_discussions.none?(&:to_be_resolved?) end + def mergeable_discussions_state? + return true unless project.only_allow_merge_if_all_discussions_are_resolved? + + discussions_resolved? + end + def hook_attrs attrs = { source: source_project.try(:hook_attrs), diff --git a/app/views/projects/_merge_request_settings.html.haml b/app/views/projects/_merge_request_settings.html.haml index 80053dd501bc..6e143c4b570e 100644 --- a/app/views/projects/_merge_request_settings.html.haml +++ b/app/views/projects/_merge_request_settings.html.haml @@ -12,3 +12,7 @@ %span.descr Builds need to be configured to enable this feature. = link_to icon('question-circle'), help_page_path('user/project/merge_requests/merge_when_build_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds') + .checkbox + = f.label :only_allow_merge_if_all_discussions_are_resolved do + = f.check_box :only_allow_merge_if_all_discussions_are_resolved + %strong Only allow merge requests to be merged if all discussions are resolved diff --git a/app/views/projects/merge_requests/widget/_open.html.haml b/app/views/projects/merge_requests/widget/_open.html.haml index 842b6df310df..01314eb37d0b 100644 --- a/app/views/projects/merge_requests/widget/_open.html.haml +++ b/app/views/projects/merge_requests/widget/_open.html.haml @@ -23,8 +23,10 @@ = render 'projects/merge_requests/widget/open/merge_when_build_succeeds' - elsif !@merge_request.can_be_merged_by?(current_user) = render 'projects/merge_requests/widget/open/not_allowed' - - elsif !@merge_request.mergeable_ci_state? && @pipeline && @pipeline.failed? + - elsif !@merge_request.mergeable_ci_state? = render 'projects/merge_requests/widget/open/build_failed' + - elsif !@merge_request.mergeable_discussions_state? + = render 'projects/merge_requests/widget/open/unresolved_discussions' - elsif @merge_request.can_be_merged? || resolved_conflicts = render 'projects/merge_requests/widget/open/accept' diff --git a/app/views/projects/merge_requests/widget/open/_unresolved_discussions.html.haml b/app/views/projects/merge_requests/widget/open/_unresolved_discussions.html.haml new file mode 100644 index 000000000000..35d5677ee377 --- /dev/null +++ b/app/views/projects/merge_requests/widget/open/_unresolved_discussions.html.haml @@ -0,0 +1,6 @@ +%h4 + = icon('exclamation-triangle') + This merge request has unresolved discussions + +%p + Please resolve these discussions to allow this merge request to be merged. \ No newline at end of file diff --git a/changelogs/unreleased/20968-add-setting-to-check-unresolved-discussion.yml b/changelogs/unreleased/20968-add-setting-to-check-unresolved-discussion.yml new file mode 100644 index 000000000000..8f03746ff802 --- /dev/null +++ b/changelogs/unreleased/20968-add-setting-to-check-unresolved-discussion.yml @@ -0,0 +1,4 @@ +--- +title: Add setting to only allow merge requests to be merged when all discussions are resolved +merge_request: 7125 +author: Rodolfo Arruda diff --git a/db/migrate/20160914131004_only_allow_merge_if_all_discussions_are_resolved.rb b/db/migrate/20160914131004_only_allow_merge_if_all_discussions_are_resolved.rb new file mode 100644 index 000000000000..fad62d716b30 --- /dev/null +++ b/db/migrate/20160914131004_only_allow_merge_if_all_discussions_are_resolved.rb @@ -0,0 +1,17 @@ +class OnlyAllowMergeIfAllDiscussionsAreResolved < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + disable_ddl_transaction! + + def up + add_column_with_default(:projects, + :only_allow_merge_if_all_discussions_are_resolved, + :boolean, + default: false) + end + + def down + remove_column(:projects, :only_allow_merge_if_all_discussions_are_resolved) + end +end diff --git a/db/schema.rb b/db/schema.rb index dc088925d978..5476b0c93e5c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -905,6 +905,7 @@ ActiveRecord::Schema.define(version: 20161103171205) do t.boolean "has_external_wiki" t.boolean "lfs_enabled" t.text "description_html" + t.boolean "only_allow_merge_if_all_discussions_are_resolved", default: false, null: false end add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree diff --git a/doc/api/projects.md b/doc/api/projects.md index 4f4b20a1874a..bbb3bfb49950 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -89,6 +89,7 @@ Parameters: "public_builds": true, "shared_with_groups": [], "only_allow_merge_if_build_succeeds": false, + "only_allow_merge_if_all_discussions_are_resolved": false, "request_access_enabled": false }, { @@ -151,6 +152,7 @@ Parameters: "public_builds": true, "shared_with_groups": [], "only_allow_merge_if_build_succeeds": false, + "only_allow_merge_if_all_discussions_are_resolved": false, "request_access_enabled": false } ] @@ -429,6 +431,7 @@ Parameters: } ], "only_allow_merge_if_build_succeeds": false, + "only_allow_merge_if_all_discussions_are_resolved": false, "request_access_enabled": false } ``` @@ -602,6 +605,7 @@ Parameters: | `import_url` | string | no | URL to import repository from | | `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members | | `only_allow_merge_if_build_succeeds` | boolean | no | Set whether merge requests can only be merged with successful builds | +| `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved | | `lfs_enabled` | boolean | no | Enable LFS | | `request_access_enabled` | boolean | no | Allow users to request member access | @@ -634,6 +638,7 @@ Parameters: | `import_url` | string | no | URL to import repository from | | `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members | | `only_allow_merge_if_build_succeeds` | boolean | no | Set whether merge requests can only be merged with successful builds | +| `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved | | `lfs_enabled` | boolean | no | Enable LFS | | `request_access_enabled` | boolean | no | Allow users to request member access | @@ -665,6 +670,7 @@ Parameters: | `import_url` | string | no | URL to import repository from | | `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members | | `only_allow_merge_if_build_succeeds` | boolean | no | Set whether merge requests can only be merged with successful builds | +| `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved | | `lfs_enabled` | boolean | no | Enable LFS | | `request_access_enabled` | boolean | no | Allow users to request member access | @@ -752,6 +758,7 @@ Example response: "public_builds": true, "shared_with_groups": [], "only_allow_merge_if_build_succeeds": false, + "only_allow_merge_if_all_discussions_are_resolved": false, "request_access_enabled": false } ``` @@ -820,6 +827,7 @@ Example response: "public_builds": true, "shared_with_groups": [], "only_allow_merge_if_build_succeeds": false, + "only_allow_merge_if_all_discussions_are_resolved": false, "request_access_enabled": false } ``` @@ -908,6 +916,7 @@ Example response: "public_builds": true, "shared_with_groups": [], "only_allow_merge_if_build_succeeds": false, + "only_allow_merge_if_all_discussions_are_resolved": false, "request_access_enabled": false } ``` @@ -996,6 +1005,7 @@ Example response: "public_builds": true, "shared_with_groups": [], "only_allow_merge_if_build_succeeds": false, + "only_allow_merge_if_all_discussions_are_resolved": false, "request_access_enabled": false } ``` diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 1f378ba16354..01e31f6f7d14 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -100,6 +100,7 @@ module API end expose :only_allow_merge_if_build_succeeds expose :request_access_enabled + expose :only_allow_merge_if_all_discussions_are_resolved end class Member < UserBasic diff --git a/lib/api/projects.rb b/lib/api/projects.rb index da16e24d7ea2..6b856128c2e8 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -139,7 +139,8 @@ module API :shared_runners_enabled, :snippets_enabled, :visibility_level, - :wiki_enabled] + :wiki_enabled, + :only_allow_merge_if_all_discussions_are_resolved] attrs = map_public_to_visibility_level(attrs) @project = ::Projects::CreateService.new(current_user, attrs).execute if @project.saved? @@ -193,7 +194,8 @@ module API :shared_runners_enabled, :snippets_enabled, :visibility_level, - :wiki_enabled] + :wiki_enabled, + :only_allow_merge_if_all_discussions_are_resolved] attrs = map_public_to_visibility_level(attrs) @project = ::Projects::CreateService.new(user, attrs).execute if @project.saved? @@ -275,7 +277,8 @@ module API :shared_runners_enabled, :snippets_enabled, :visibility_level, - :wiki_enabled] + :wiki_enabled, + :only_allow_merge_if_all_discussions_are_resolved] attrs = map_public_to_visibility_level(attrs) authorize_admin_project authorize! :rename_project, user_project if attrs[:name].present? diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 940d54f8686e..79820e9a4352 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -297,6 +297,32 @@ describe Projects::MergeRequestsController do end end end + + context 'when project project has unresolved discussion' do + before do + project.update_column(:only_allow_merge_if_all_discussions_are_resolved, allowed) + end + + context "when the only_allow_merge_if_all_discussions_are_resolved? is true" do + let(:allowed) { true } + + it 'returns :failed' do + merge_with_sha + + expect(assigns(:status)).to eq(:failed) + end + end + + context "when the only_allow_merge_if_all_discussions_are_resolved? is false" do + let(:allowed) { false } + + it 'returns :failed' do + merge_with_sha + + expect(assigns(:status)).to eq(:success) + end + end + end end end diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index f780e01253c1..37eb49c94df1 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -68,6 +68,11 @@ FactoryGirl.define do factory :closed_merge_request, traits: [:closed] factory :reopened_merge_request, traits: [:reopened] factory :merge_request_with_diffs, traits: [:with_diffs] + factory :merge_request_with_diff_notes do + after(:create) do |mr| + create(:diff_note_on_merge_request, noteable: mr, project: mr.source_project) + end + end factory :labeled_merge_request do transient do diff --git a/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions.rb b/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions.rb new file mode 100644 index 000000000000..100ddda01677 --- /dev/null +++ b/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions.rb @@ -0,0 +1,53 @@ +require 'spec_helper' + +feature 'Check if mergeable with unresolved discussions', js: true, feature: true do + let!(:user) { create(:user) } + let!(:project) { create(:project, :public, only_allow_merge_if_all_discussions_are_resolved: allowed) } + let!(:merge_request) { create(:merge_request_with_diff_notes, source_project: project, author: user, title: "Bug NS-04" ) } + + before do + login_as user + project.team << [user, :master] + end + + context 'when only_allow_merge_if_all_discussions_are_resolved is false' do + let(:allowed) { false } + + it 'allows MR to be merged' do + visit_merge_request(merge_request) + + expect(page).to have_button 'Accept Merge Request' + end + end + + context 'when only_allow_merge_if_all_discussions_are_resolved is true' do + let(:allowed) { true } + + context "when discussions are resolved" do + + before do + merge_request.discussions.each { |d| d.resolve!(user) } + end + + it 'allows MR to be merged' do + visit_merge_request(merge_request) + + expect(page).to have_button 'Accept Merge Request' + end + end + + context "when discussions are unresolved" do + + it 'does not allow to merge' do + visit_merge_request(merge_request) + + expect(page).not_to have_button 'Accept Merge Request' + expect(page).to have_content('This merge request has unresolved discussions') + end + end + end + + def visit_merge_request(merge_request) + visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request) + end +end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 1067ff7bb4d1..f3d0373e6d76 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -837,6 +837,17 @@ describe MergeRequest, models: true do expect(subject.mergeable_state?).to be_falsey end end + + context "when project settings restrict to merge only when all the discussions are resolved" do + before do + project.only_allow_merge_if_all_discussions_are_resolved = true + allow(subject).to receive(:mergeable_discussions_state?) { false } + end + + it 'returns false' do + expect(subject.mergeable_state?).to be_falsey + end + end end end @@ -887,7 +898,52 @@ describe MergeRequest, models: true do end end - describe '#environments' do + describe '#mergeable_discussions_state?' do + let!(:user) { create(:user) } + let!(:project) { create(:project, only_allow_merge_if_all_discussions_are_resolved: allowed) } + + subject { create(:merge_request_with_diff_notes, source_project: project) } + + context 'when is true' do + let(:allowed) { true } + + context 'when discussions are resolved' do + before do + subject.discussions.each { |d| d.resolve!(user) } + end + + it 'returns true' do + expect(subject.mergeable_discussions_state?).to be_truthy + end + end + + context 'when discussions are unresolved' do + before do + subject.discussions.map(&:unresolve!) + end + + it 'returns false' do + expect(subject.mergeable_discussions_state?).to be_falsey + end + end + end + + context 'when is false' do + let(:allowed) { false } + + context 'when discussions are unresolved' do + before do + subject.discussions.map(&:unresolve!) + end + + it 'returns true' do + expect(subject.mergeable_discussions_state?).to be_truthy + end + end + end + end + + describe "#environments" do let(:project) { create(:project) } let(:merge_request) { create(:merge_request, source_project: project) } diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 973928d007ab..3c8f0ac531a4 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -256,7 +256,8 @@ describe API::API, api: true do merge_requests_enabled: false, wiki_enabled: false, only_allow_merge_if_build_succeeds: false, - request_access_enabled: true + request_access_enabled: true, + only_allow_merge_if_all_discussions_are_resolved: false }) post api('/projects', user), project @@ -327,6 +328,22 @@ describe API::API, api: true do expect(json_response['only_allow_merge_if_build_succeeds']).to be_truthy end + it 'sets a project as allowing merge even if discussions are unresolved' do + project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: false }) + + post api('/projects', user), project + + expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_falsey + end + + it 'sets a project as allowing merge only if all discussions are resolved' do + project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: true }) + + post api('/projects', user), project + + expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_truthy + end + context 'when a visibility level is restricted' do before do @project = attributes_for(:project, { public: true }) @@ -448,6 +465,22 @@ describe API::API, api: true do post api("/projects/user/#{user.id}", admin), project expect(json_response['only_allow_merge_if_build_succeeds']).to be_truthy end + + it 'sets a project as allowing merge even if discussions are unresolved' do + project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: false }) + + post api("/projects/user/#{user.id}", admin), project + + expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_falsey + end + + it 'sets a project as allowing merge only if all discussions are resolved' do + project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: true }) + + post api("/projects/user/#{user.id}", admin), project + + expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_truthy + end end describe "POST /projects/:id/uploads" do @@ -509,6 +542,7 @@ describe API::API, api: true do expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name) expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access) expect(json_response['only_allow_merge_if_build_succeeds']).to eq(project.only_allow_merge_if_build_succeeds) + expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved) end it 'returns a project by path name' do -- 2.22.0 From 3f029144607428aa21e81d1d4b40544b835f3d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 26 Oct 2016 19:19:17 +0200 Subject: [PATCH 2/3] Complete and improve specs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/controllers/projects_controller.rb | 4 +- .../merge_requests_controller_spec.rb | 68 +++++++++++++++---- ...f_mergeable_with_unresolved_discussions.rb | 50 +++++++++----- spec/models/merge_request_spec.rb | 39 +++++------ 4 files changed, 105 insertions(+), 56 deletions(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index ee72c8ba72f5..6988527a3be4 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -334,9 +334,9 @@ class ProjectsController < Projects::ApplicationController :issues_tracker_id, :default_branch, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex, - :public_builds, :only_allow_merge_if_build_succeeds, + :public_builds, :only_allow_merge_if_build_succeeds, :request_access_enabled, :only_allow_merge_if_all_discussions_are_resolved, - :request_access_enabled, :lfs_enabled, project_feature_attributes + :lfs_enabled, project_feature_attributes ) end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 79820e9a4352..49127aecc63e 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -298,28 +298,68 @@ describe Projects::MergeRequestsController do end end - context 'when project project has unresolved discussion' do - before do - project.update_column(:only_allow_merge_if_all_discussions_are_resolved, allowed) - end + describe 'only_allow_merge_if_all_discussions_are_resolved? setting' do + let(:merge_request) { create(:merge_request_with_diff_notes, source_project: project, author: user) } + + context 'when enabled' do + before do + project.update_column(:only_allow_merge_if_all_discussions_are_resolved, true) + end + + context 'with unresolved discussion' do + before do + expect(merge_request).not_to be_discussions_resolved + end + + it 'returns :failed' do + merge_with_sha + + expect(assigns(:status)).to eq(:failed) + end + end - context "when the only_allow_merge_if_all_discussions_are_resolved? is true" do - let(:allowed) { true } + context 'with all discussions resolved' do + before do + merge_request.discussions.each { |d| d.resolve!(user) } + expect(merge_request).to be_discussions_resolved + end - it 'returns :failed' do - merge_with_sha + it 'returns :success' do + merge_with_sha - expect(assigns(:status)).to eq(:failed) + expect(assigns(:status)).to eq(:success) + end end end - context "when the only_allow_merge_if_all_discussions_are_resolved? is false" do - let(:allowed) { false } + context 'when disabled' do + before do + project.update_column(:only_allow_merge_if_all_discussions_are_resolved, false) + end + + context 'with unresolved discussion' do + before do + expect(merge_request).not_to be_discussions_resolved + end + + it 'returns :success' do + merge_with_sha - it 'returns :failed' do - merge_with_sha + expect(assigns(:status)).to eq(:success) + end + end + + context 'with all discussions resolved' do + before do + merge_request.discussions.each { |d| d.resolve!(user) } + expect(merge_request).to be_discussions_resolved + end + + it 'returns :success' do + merge_with_sha - expect(assigns(:status)).to eq(:success) + expect(assigns(:status)).to eq(:success) + end end end end diff --git a/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions.rb b/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions.rb index 100ddda01677..7f11db3c4170 100644 --- a/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions.rb +++ b/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions.rb @@ -1,30 +1,30 @@ require 'spec_helper' feature 'Check if mergeable with unresolved discussions', js: true, feature: true do - let!(:user) { create(:user) } - let!(:project) { create(:project, :public, only_allow_merge_if_all_discussions_are_resolved: allowed) } - let!(:merge_request) { create(:merge_request_with_diff_notes, source_project: project, author: user, title: "Bug NS-04" ) } + let(:user) { create(:user) } + let(:project) { create(:project) } + let!(:merge_request) { create(:merge_request_with_diff_notes, source_project: project, author: user) } before do login_as user project.team << [user, :master] end - context 'when only_allow_merge_if_all_discussions_are_resolved is false' do - let(:allowed) { false } - - it 'allows MR to be merged' do - visit_merge_request(merge_request) - - expect(page).to have_button 'Accept Merge Request' + context 'when project.only_allow_merge_if_all_discussions_are_resolved == true' do + before do + project.update_column(:only_allow_merge_if_all_discussions_are_resolved, true) end - end - context 'when only_allow_merge_if_all_discussions_are_resolved is true' do - let(:allowed) { true } + context 'with unresolved discussions' do + it 'does not allow to merge' do + visit_merge_request(merge_request) - context "when discussions are resolved" do + expect(page).not_to have_button 'Accept Merge Request' + expect(page).to have_content('This merge request has unresolved discussions') + end + end + context 'with all discussions resolved' do before do merge_request.discussions.each { |d| d.resolve!(user) } end @@ -35,14 +35,30 @@ feature 'Check if mergeable with unresolved discussions', js: true, feature: tru expect(page).to have_button 'Accept Merge Request' end end + end - context "when discussions are unresolved" do + context 'when project.only_allow_merge_if_all_discussions_are_resolved == false' do + before do + project.update_column(:only_allow_merge_if_all_discussions_are_resolved, false) + end + context 'with unresolved discussions' do it 'does not allow to merge' do visit_merge_request(merge_request) - expect(page).not_to have_button 'Accept Merge Request' - expect(page).to have_content('This merge request has unresolved discussions') + expect(page).to have_button 'Accept Merge Request' + end + end + + context 'with all discussions resolved' do + before do + merge_request.discussions.each { |d| d.resolve!(user) } + end + + it 'allows MR to be merged' do + visit_merge_request(merge_request) + + expect(page).to have_button 'Accept Merge Request' end end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index f3d0373e6d76..fb032a89d503 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -825,11 +825,8 @@ describe MergeRequest, models: true do end context 'when failed' do - before { allow(subject).to receive(:broken?) { false } } - - context 'when project settings restrict to merge only if build succeeds and build failed' do + context 'when #mergeable_ci_state? is false' do before do - project.only_allow_merge_if_build_succeeds = true allow(subject).to receive(:mergeable_ci_state?) { false } end @@ -838,9 +835,8 @@ describe MergeRequest, models: true do end end - context "when project settings restrict to merge only when all the discussions are resolved" do + context 'when #mergeable_discussions_state? is false' do before do - project.only_allow_merge_if_all_discussions_are_resolved = true allow(subject).to receive(:mergeable_discussions_state?) { false } end @@ -899,45 +895,42 @@ describe MergeRequest, models: true do end describe '#mergeable_discussions_state?' do - let!(:user) { create(:user) } - let!(:project) { create(:project, only_allow_merge_if_all_discussions_are_resolved: allowed) } - - subject { create(:merge_request_with_diff_notes, source_project: project) } + let(:merge_request) { create(:merge_request_with_diff_notes, source_project: project) } - context 'when is true' do - let(:allowed) { true } + context 'when project.only_allow_merge_if_all_discussions_are_resolved == true' do + let(:project) { create(:project, only_allow_merge_if_all_discussions_are_resolved: true) } - context 'when discussions are resolved' do + context 'with all discussions resolved' do before do - subject.discussions.each { |d| d.resolve!(user) } + merge_request.discussions.each { |d| d.resolve!(merge_request.author) } end it 'returns true' do - expect(subject.mergeable_discussions_state?).to be_truthy + expect(merge_request.mergeable_discussions_state?).to be_truthy end end - context 'when discussions are unresolved' do + context 'with unresolved discussions' do before do - subject.discussions.map(&:unresolve!) + merge_request.discussions.each(&:unresolve!) end it 'returns false' do - expect(subject.mergeable_discussions_state?).to be_falsey + expect(merge_request.mergeable_discussions_state?).to be_falsey end end end - context 'when is false' do - let(:allowed) { false } + context 'when project.only_allow_merge_if_all_discussions_are_resolved == false' do + let(:project) { create(:project, only_allow_merge_if_all_discussions_are_resolved: false) } - context 'when discussions are unresolved' do + context 'with unresolved discussions' do before do - subject.discussions.map(&:unresolve!) + merge_request.discussions.each(&:unresolve!) end it 'returns true' do - expect(subject.mergeable_discussions_state?).to be_truthy + expect(merge_request.mergeable_discussions_state?).to be_truthy end end end -- 2.22.0 From 065ba130585cbce5a2835def94a650d26493abb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 31 Oct 2016 16:00:42 +0100 Subject: [PATCH 3/3] Add documentation for the "Only allow merge requests to be merged if all discussions are resolved" feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- ..._merge_if_all_discussions_are_resolved.png | Bin 0 -> 24693 bytes ...ge_if_all_discussions_are_resolved_msg.png | Bin 0 -> 6940 bytes .../merge_request_discussion_resolution.md | 18 ++++++++++++++++++ .../merge_when_build_succeeds.md | 4 ++-- 4 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 doc/user/project/merge_requests/img/only_allow_merge_if_all_discussions_are_resolved.png create mode 100644 doc/user/project/merge_requests/img/only_allow_merge_if_all_discussions_are_resolved_msg.png diff --git a/doc/user/project/merge_requests/img/only_allow_merge_if_all_discussions_are_resolved.png b/doc/user/project/merge_requests/img/only_allow_merge_if_all_discussions_are_resolved.png new file mode 100644 index 0000000000000000000000000000000000000000..52c8acf15e02e51caf7df99fb5febaeb054162e2 GIT binary patch literal 24693 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�ZcJ8QhH;Wl-ZB7 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 MdsVq|8Pl)-10vh$`v3p{ literal 0 HcmV?d00001 diff --git a/doc/user/project/merge_requests/img/only_allow_merge_if_all_discussions_are_resolved_msg.png b/doc/user/project/merge_requests/img/only_allow_merge_if_all_discussions_are_resolved_msg.png new file mode 100644 index 0000000000000000000000000000000000000000..79ba5c362c7543a0bb83e99e753434333f29bc13 GIT binary patch literal 6940 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 [Introduced][ce-7125] in GitLab 8.14. + +You can prevent merge requests from being merged until all discussions are resolved. + +Navigate to your project's settings page, select the +**Only allow merge requests to be merged if all discussions are resolved** check +box and hit **Save** for the changes to take effect. + +![Only allow merge if all the discussions are resolved settings](img/only_allow_merge_if_all_discussions_are_resolved.png) + +From now on, you will not be able to merge from the UI until all discussions +are resolved. + +![Only allow merge if all the discussions are resolved message](img/only_allow_merge_if_all_discussions_are_resolved_msg.png) + [ce-5022]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5022 +[ce-7125]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7125 [resolve-discussion-button]: img/resolve_discussion_button.png [resolve-comment-button]: img/resolve_comment_button.png [discussion-view]: img/discussion_view.png diff --git a/doc/user/project/merge_requests/merge_when_build_succeeds.md b/doc/user/project/merge_requests/merge_when_build_succeeds.md index c138061fd402..d4e5b5de6857 100644 --- a/doc/user/project/merge_requests/merge_when_build_succeeds.md +++ b/doc/user/project/merge_requests/merge_when_build_succeeds.md @@ -40,7 +40,7 @@ hit **Save** for the changes to take effect. ![Only allow merge if build succeeds settings](img/merge_when_build_succeeds_only_if_succeeds_settings.png) -From now on, every time the pipelinefails you will not be able to merge the +From now on, every time the pipeline fails you will not be able to merge the merge request from the UI, until you make all relevant builds pass. -![Only allow merge if build succeeds msg](img/merge_when_build_succeeds_only_if_succeeds_msg.png) +![Only allow merge if build succeeds message](img/merge_when_build_succeeds_only_if_succeeds_msg.png) -- 2.22.0