Commit a401d237 authored by Igor Drozdov's avatar Igor Drozdov

Add MergeRequests::ApprovalService to CE

This service is required for implementing Approvals for CE
parent 879d1679
...@@ -18,7 +18,7 @@ class SystemNoteMetadata < ApplicationRecord ...@@ -18,7 +18,7 @@ class SystemNoteMetadata < ApplicationRecord
designs_added designs_modified designs_removed designs_discussion_added designs_added designs_modified designs_removed designs_discussion_added
title time_tracking branch milestone discussion task moved title time_tracking branch milestone discussion task moved
opened closed merged duplicate locked unlocked outdated opened closed merged duplicate locked unlocked outdated
tag due_date pinned_embed cherry_pick health_status tag due_date pinned_embed cherry_pick health_status approved
].freeze ].freeze
validates :note, presence: true validates :note, presence: true
......
...@@ -138,6 +138,10 @@ class EventCreateService ...@@ -138,6 +138,10 @@ class EventCreateService
event event
end end
def approve_mr(merge_request, current_user)
create_record_event(merge_request, current_user, :approved)
end
private private
def existing_wiki_event(wiki_page_meta, action) def existing_wiki_event(wiki_page_meta, action)
......
...@@ -2,35 +2,27 @@ ...@@ -2,35 +2,27 @@
module MergeRequests module MergeRequests
class ApprovalService < MergeRequests::BaseService class ApprovalService < MergeRequests::BaseService
IncorrectApprovalPasswordError = Class.new(StandardError)
def execute(merge_request) def execute(merge_request)
if incorrect_approval_password?(merge_request)
raise IncorrectApprovalPasswordError
end
approval = merge_request.approvals.new(user: current_user) approval = merge_request.approvals.new(user: current_user)
if save_approval(approval) return unless save_approval(approval)
merge_request.reset_approval_cache!
create_event(merge_request)
create_approval_note(merge_request)
mark_pending_todos_as_done(merge_request)
if merge_request.approvals_left.zero? reset_approvals_cache(merge_request)
notification_service.async.approve_mr(merge_request, current_user) create_event(merge_request)
execute_hooks(merge_request, 'approved') create_approval_note(merge_request)
else mark_pending_todos_as_done(merge_request)
execute_hooks(merge_request, 'approval') execute_approval_hooks(merge_request, current_user)
end
end
end end
private private
def incorrect_approval_password?(merge_request) def reset_approvals_cache(merge_request)
merge_request.project.require_password_to_approve? && merge_request.approvals.reset
!Gitlab::Auth.find_with_user_password(current_user.username, params[:approval_password]) end
def execute_approval_hooks(merge_request, current_user)
# Only one approval is required for a merge request to be approved
execute_hooks(merge_request, 'approved')
end end
def save_approval(approval) def save_approval(approval)
...@@ -47,19 +39,10 @@ module MergeRequests ...@@ -47,19 +39,10 @@ module MergeRequests
todo_service.resolve_todos_for_target(merge_request, current_user) todo_service.resolve_todos_for_target(merge_request, current_user)
end end
def calculate_approvals_metrics(merge_request)
return unless merge_request.project.feature_available?(:code_review_analytics)
::Analytics::RefreshApprovalsData.new(merge_request).execute
end
def create_event(merge_request) def create_event(merge_request)
# Making sure MergeRequest::Metrics updates are in sync with event_service.approve_mr(merge_request, current_user)
# Event creation.
Event.transaction do
event_service.approve_mr(merge_request, current_user)
calculate_approvals_metrics(merge_request)
end
end end
end end
end end
MergeRequests::ApprovalService.prepend_if_ee('EE::MergeRequests::ApprovalService')
...@@ -273,6 +273,26 @@ module SystemNoteService ...@@ -273,6 +273,26 @@ module SystemNoteService
::SystemNotes::DesignManagementService.new(noteable: design.issue, project: design.project, author: discussion_note.author).design_discussion_added(discussion_note) ::SystemNotes::DesignManagementService.new(noteable: design.issue, project: design.project, author: discussion_note.author).design_discussion_added(discussion_note)
end end
# Called when the merge request is approved by user
#
# noteable - Noteable object
# user - User performing approve
#
# Example Note text:
#
# "approved this merge request"
#
# Returns the created Note object
def approve_mr(noteable, user)
merge_requests_service(noteable, noteable.project, user).approve_mr
end
private
def merge_requests_service(noteable, project, author)
::SystemNotes::MergeRequestsService.new(noteable: noteable, project: project, author: author)
end
end end
SystemNoteService.prepend_if_ee('EE::SystemNoteService') SystemNoteService.prepend_if_ee('EE::SystemNoteService')
...@@ -150,6 +150,19 @@ module SystemNotes ...@@ -150,6 +150,19 @@ module SystemNotes
create_note(summary) create_note(summary)
end end
# Called when the merge request is approved by user
#
# Example Note text:
#
# "approved this merge request"
#
# Returns the created Note object
def approve_mr
body = "approved this merge request"
create_note(NoteSummary.new(noteable, project, author, body, action: 'approved'))
end
end end
end end
......
...@@ -5,7 +5,7 @@ module EE ...@@ -5,7 +5,7 @@ module EE
extend ::Gitlab::Utils::Override extend ::Gitlab::Utils::Override
EE_ICON_TYPES = %w[ EE_ICON_TYPES = %w[
weight approved unapproved relate unrelate published weight unapproved relate unrelate published
epic_issue_added issue_added_to_epic epic_issue_removed issue_removed_from_epic epic_issue_added issue_added_to_epic epic_issue_removed issue_removed_from_epic
epic_issue_moved issue_changed_epic epic_date_changed relate_epic unrelate_epic epic_issue_moved issue_changed_epic epic_date_changed relate_epic unrelate_epic
vulnerability_confirmed vulnerability_dismissed vulnerability_resolved vulnerability_confirmed vulnerability_dismissed vulnerability_resolved
......
...@@ -13,9 +13,5 @@ module EE ...@@ -13,9 +13,5 @@ module EE
def reopen_epic(epic, current_user) def reopen_epic(epic, current_user)
create_record_event(epic, current_user, :reopened) create_record_event(epic, current_user, :reopened)
end end
def approve_mr(merge_request, current_user)
create_record_event(merge_request, current_user, :approved)
end
end end
end end
# frozen_string_literal: true
module EE
module MergeRequests
module ApprovalService
extend ::Gitlab::Utils::Override
IncorrectApprovalPasswordError = Class.new(StandardError)
override :execute
def execute(merge_request)
if incorrect_approval_password?(merge_request)
raise IncorrectApprovalPasswordError
end
super
end
private
override :reset_approvals_cache
def reset_approvals_cache(merge_request)
merge_request.reset_approval_cache!
end
override :execute_approval_hooks
def execute_approval_hooks(merge_request, current_user)
if merge_request.approvals_left.zero?
notification_service.async.approve_mr(merge_request, current_user)
execute_hooks(merge_request, 'approved')
else
execute_hooks(merge_request, 'approval')
end
end
override :create_event
def create_event(merge_request)
# Making sure MergeRequest::Metrics updates are in sync with
# Event creation.
::Event.transaction do
event_service.approve_mr(merge_request, current_user)
calculate_approvals_metrics(merge_request)
end
end
def incorrect_approval_password?(merge_request)
merge_request.project.require_password_to_approve? &&
!::Gitlab::Auth.find_with_user_password(current_user.username, params[:approval_password])
end
def calculate_approvals_metrics(merge_request)
return unless merge_request.project.feature_available?(:code_review_analytics)
::Analytics::RefreshApprovalsData.new(merge_request).execute
end
end
end
end
...@@ -48,20 +48,6 @@ module EE ...@@ -48,20 +48,6 @@ module EE
issuables_service(noteable, noteable.project, author).change_iteration(iteration) issuables_service(noteable, noteable.project, author).change_iteration(iteration)
end end
# Called when the merge request is approved by user
#
# noteable - Noteable object
# user - User performing approve
#
# Example Note text:
#
# "approved this merge request"
#
# Returns the created Note object
def approve_mr(noteable, user)
merge_requests_service(noteable, noteable.project, user).approve_mr
end
def unapprove_mr(noteable, user) def unapprove_mr(noteable, user)
merge_requests_service(noteable, noteable.project, user).unapprove_mr merge_requests_service(noteable, noteable.project, user).unapprove_mr
end end
......
...@@ -3,19 +3,6 @@ ...@@ -3,19 +3,6 @@
module EE module EE
module SystemNotes module SystemNotes
module MergeRequestsService module MergeRequestsService
# Called when the merge request is approved by user
#
# Example Note text:
#
# "approved this merge request"
#
# Returns the created Note object
def approve_mr
body = "approved this merge request"
create_note(NoteSummary.new(noteable, project, author, body, action: 'approved'))
end
def unapprove_mr def unapprove_mr
body = "unapproved this merge request" body = "unapproved this merge request"
......
...@@ -49,19 +49,4 @@ RSpec.describe EventCreateService do ...@@ -49,19 +49,4 @@ RSpec.describe EventCreateService do
expect(event.group_id).to eq epic.group_id expect(event.group_id).to eq epic.group_id
end end
end end
describe 'Merge Requests' do
let_it_be(:user) { create(:user) }
let(:merge_request) { create(:merge_request) }
describe '#approve_mr' do
it { expect(service.approve_mr(merge_request, user)).to be_truthy }
it 'creates new event' do
service.approve_mr(merge_request, user)
change { Event.approved_action.where(target: merge_request).count }.by(1)
end
end
end
end end
...@@ -7,20 +7,6 @@ RSpec.describe ::SystemNotes::MergeRequestsService do ...@@ -7,20 +7,6 @@ RSpec.describe ::SystemNotes::MergeRequestsService do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:noteable) { create(:merge_request, source_project: project) } let(:noteable) { create(:merge_request, source_project: project) }
describe '#approve_mr' do
subject { described_class.new(noteable: noteable, project: project, author: author).approve_mr }
it_behaves_like 'a system note' do
let(:action) { 'approved' }
end
context 'when merge request approved' do
it 'sets the note text' do
expect(subject.note).to eq "approved this merge request"
end
end
end
describe '#unapprove_mr' do describe '#unapprove_mr' do
subject { described_class.new(noteable: noteable, project: project, author: author).unapprove_mr } subject { described_class.new(noteable: noteable, project: project, author: author).unapprove_mr }
......
...@@ -48,16 +48,6 @@ RSpec.describe SystemNoteService do ...@@ -48,16 +48,6 @@ RSpec.describe SystemNoteService do
end end
end end
describe '.approve_mr' do
it 'calls MergeRequestsService' do
expect_next_instance_of(::SystemNotes::MergeRequestsService) do |service|
expect(service).to receive(:approve_mr)
end
described_class.approve_mr(noteable, author)
end
end
describe '.unapprove_mr' do describe '.unapprove_mr' do
it 'calls MergeRequestsService' do it 'calls MergeRequestsService' do
expect_next_instance_of(::SystemNotes::MergeRequestsService) do |service| expect_next_instance_of(::SystemNotes::MergeRequestsService) do |service|
......
...@@ -87,6 +87,18 @@ RSpec.describe EventCreateService do ...@@ -87,6 +87,18 @@ RSpec.describe EventCreateService do
expect { service.reopen_mr(merge_request, merge_request.author) }.to change { ResourceStateEvent.count } expect { service.reopen_mr(merge_request, merge_request.author) }.to change { ResourceStateEvent.count }
end end
end end
describe '#approve_mr' do
let(:merge_request) { create(:merge_request) }
it { expect(service.approve_mr(merge_request, user)).to be_truthy }
it 'creates new event' do
service.approve_mr(merge_request, user)
change { Event.approved_action.where(target: merge_request).count }.by(1)
end
end
end end
describe 'Milestone' do describe 'Milestone' do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe MergeRequests::ApprovalService do
describe '#execute' do
let(:user) { create(:user) }
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.project }
let!(:todo) { create(:todo, user: user, project: project, target: merge_request) }
subject(:service) { described_class.new(project, user) }
context 'with invalid approval' do
before do
allow(merge_request.approvals).to receive(:new).and_return(double(save: false))
end
it 'does not create an approval note' do
expect(SystemNoteService).not_to receive(:approve_mr)
service.execute(merge_request)
end
it 'does not mark pending todos as done' do
service.execute(merge_request)
expect(todo.reload).to be_pending
end
end
context 'with valid approval' do
it 'creates an approval note and marks pending todos as done' do
expect(SystemNoteService).to receive(:approve_mr).with(merge_request, user)
expect(merge_request.approvals).to receive(:reset)
service.execute(merge_request)
expect(todo.reload).to be_done
end
it 'creates approve MR event' do
expect_next_instance_of(EventCreateService) do |instance|
expect(instance).to receive(:approve_mr)
.with(merge_request, user)
end
service.execute(merge_request)
end
context 'with remaining approvals' do
it 'fires an approval webhook' do
expect(service).to receive(:execute_hooks).with(merge_request, 'approved')
service.execute(merge_request)
end
end
end
end
end
...@@ -661,4 +661,14 @@ RSpec.describe SystemNoteService do ...@@ -661,4 +661,14 @@ RSpec.describe SystemNoteService do
described_class.design_discussion_added(discussion_note) described_class.design_discussion_added(discussion_note)
end end
end end
describe '.approve_mr' do
it 'calls MergeRequestsService' do
expect_next_instance_of(::SystemNotes::MergeRequestsService) do |service|
expect(service).to receive(:approve_mr)
end
described_class.approve_mr(noteable, author)
end
end
end end
...@@ -261,4 +261,18 @@ RSpec.describe ::SystemNotes::MergeRequestsService do ...@@ -261,4 +261,18 @@ RSpec.describe ::SystemNotes::MergeRequestsService do
expect(subject.commit_id).to eq(commit_sha) expect(subject.commit_id).to eq(commit_sha)
end end
end end
describe '#approve_mr' do
subject { described_class.new(noteable: noteable, project: project, author: author).approve_mr }
it_behaves_like 'a system note' do
let(:action) { 'approved' }
end
context 'when merge request approved' do
it 'sets the note text' do
expect(subject.note).to eq "approved this merge request"
end
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