Commit aadc1706 authored by Thong Kuah's avatar Thong Kuah

Merge branch 'mwps-notify' into 'master'

Send an email when MR is set to MWPS

See merge request gitlab-org/gitlab!33460
parents e014ebc5 a59d719a
...@@ -92,6 +92,13 @@ module Emails ...@@ -92,6 +92,13 @@ module Emails
mail_answer_thread(@merge_request, merge_request_thread_options(resolved_by_user_id, recipient_id, reason)) mail_answer_thread(@merge_request, merge_request_thread_options(resolved_by_user_id, recipient_id, reason))
end end
def merge_when_pipeline_succeeds_email(recipient_id, merge_request_id, mwps_set_by_user_id, reason = nil)
setup_merge_request_mail(merge_request_id, recipient_id)
@mwps_set_by = ::User.find(mwps_set_by_user_id)
mail_answer_thread(@merge_request, merge_request_thread_options(mwps_set_by_user_id, recipient_id, reason))
end
private private
def setup_merge_request_mail(merge_request_id, recipient_id, present: false) def setup_merge_request_mail(merge_request_id, recipient_id, present: false)
......
...@@ -177,6 +177,10 @@ class NotifyPreview < ActionMailer::Preview ...@@ -177,6 +177,10 @@ class NotifyPreview < ActionMailer::Preview
Notify.service_desk_thank_you_email(issue.id).message Notify.service_desk_thank_you_email(issue.id).message
end end
def merge_when_pipeline_succeeds_email
Notify.merge_when_pipeline_succeeds_email(user.id, merge_request.id, user.id).message
end
private private
def project def project
......
...@@ -11,7 +11,7 @@ module AutoMerge ...@@ -11,7 +11,7 @@ module AutoMerge
yield if block_given? yield if block_given?
end end
# Notify the event that auto merge is enabled or merge param is updated notify(merge_request)
AutoMergeProcessWorker.perform_async(merge_request.id) AutoMergeProcessWorker.perform_async(merge_request.id)
strategy.to_sym strategy.to_sym
...@@ -62,6 +62,10 @@ module AutoMerge ...@@ -62,6 +62,10 @@ module AutoMerge
private private
# Overridden in child classes
def notify(merge_request)
end
def strategy def strategy
strong_memoize(:strategy) do strong_memoize(:strategy) do
self.class.name.demodulize.remove('Service').underscore self.class.name.demodulize.remove('Service').underscore
......
...@@ -34,5 +34,13 @@ module AutoMerge ...@@ -34,5 +34,13 @@ module AutoMerge
merge_request.actual_head_pipeline&.active? merge_request.actual_head_pipeline&.active?
end end
end end
private
def notify(merge_request)
return unless Feature.enabled?(:mwps_notification, project)
notification_service.async.merge_when_pipeline_succeeds(merge_request, current_user) if merge_request.saved_change_to_auto_merge_enabled?
end
end end
end end
...@@ -582,6 +582,14 @@ class NotificationService ...@@ -582,6 +582,14 @@ class NotificationService
end end
end end
def merge_when_pipeline_succeeds(merge_request, current_user)
recipients = ::NotificationRecipients::BuildService.build_recipients(merge_request, current_user, action: 'merge_when_pipeline_succeeds')
recipients.each do |recipient|
mailer.merge_when_pipeline_succeeds_email(recipient.user.id, merge_request.id, current_user.id).deliver_later
end
end
protected protected
def new_resource_email(target, method) def new_resource_email(target, method)
......
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
%html{ lang: "en" }
%head
%meta{ content: "text/html; charset=UTF-8", "http-equiv" => "Content-Type" }
%meta{ content: "width=device-width, initial-scale=1", name: "viewport" }
%meta{ content: "IE=edge", "http-equiv" => "X-UA-Compatible" }
%title= message.subject
:css
/* CLIENT-SPECIFIC STYLES */
body, table, td, a { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; }
img { -ms-interpolation-mode: bicubic; }
/* iOS BLUE LINKS */
a[x-apple-data-detectors] {
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
/* ANDROID MARGIN HACK */
body { margin:0 !important; }
div[style*="margin: 16px 0"] { margin:0 !important; }
@media only screen and (max-width: 639px) {
body, #body {
min-width: 320px !important;
}
table.wrapper {
width: 100% !important;
min-width: 320px !important;
}
table.wrapper > tbody > tr > td {
border-left: 0 !important;
border-right: 0 !important;
border-radius: 0 !important;
padding-left: 10px !important;
padding-right: 10px !important;
}
}
ul.assignees-list {
list-style: none;
padding: 0px;
display: block;
margin-top: 0px;
}
ul.assignees-list li {
display: inline-block;
padding-right: 12px;
padding-top: 8px;
}
%body{ style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;height:100%;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" }
%table#body{ border: "0", cellpadding: "0", cellspacing: "0", style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;" }
%tbody
%tr.line
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#6b4fbb;height:4px;font-size:4px;line-height:4px;" }
%tr.header
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
%img{ alt: "GitLab", height: "50", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo.gif'), width: "55" }
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" }
%table.wrapper{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:640px;margin:0 auto;border-collapse:separate;border-spacing:0;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#ffffff;text-align:left;padding:18px 25px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" }
%table.content{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;border-collapse:separate;border-spacing:0;" }
%tbody
%tr.success
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;color:#ffffff;background-color:#31af64;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;margin:0 auto;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;" }
%img{ alt: "✓", height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-check-green-inverted.gif'), style: "display:block;", width: "13" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;" }
%span= _('Merge request was scheduled to merge after pipeline succeeds')
%tr.spacer
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" }
&nbsp;
%tr.section
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;line-height:1.4;text-align:center;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;width:100%;" }
%tbody
%tr{ style: 'width:100%;' }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;text-align:center;" }
%img{ src: image_url('mailers/approval/icon-merge-request-gray.gif'), style: "height:18px;width:18px;margin-bottom:-4px;", alt: "Merge request icon" }
%span{ style: "font-weight: 600;color:#333333;" }= _('Merge request')
%a{ href: merge_request_url(@merge_request), style: "font-weight: 600;color:#3777b0;text-decoration:none" }= @merge_request.to_reference
%span= _('was scheduled to merge after pipeline succeeds by')
%img.avatar{ height: "24", src: avatar_icon_for_user(@mwps_set_by, 24, only_path: false), style: "border-radius:12px;margin:-7px 0 -7px 3px;", width: "24", alt: "Avatar" }
%a.muted{ href: user_url(@mwps_set_by), style: "color:#333333;text-decoration:none;" }
= @mwps_set_by.name
%tr.spacer
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" }
&nbsp;
%tr.section
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" }
%table.info{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;" }= _('Project')
-# haml-lint:disable NoPlainNodes
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;" }
- namespace_name = @project.group ? @project.group.name : @project.namespace.owner.name
- namespace_url = @project.group ? group_url(@project.group) : user_url(@project.namespace.owner)
%a.muted{ href: namespace_url, style: "color:#333333;text-decoration:none;" }
= namespace_name
\/
%a.muted{ href: project_url(@project), style: "color:#333333;text-decoration:none;" }
= @project.name
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" }= _('Branch')
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
%img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13", alt: "Branch icon" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
%span.muted{ style: "color:#333333;text-decoration:none;" }
= @merge_request.source_branch
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" }= _('Author')
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
%img.avatar{ height: "24", src: avatar_icon_for_user(@merge_request.author, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
%a.muted{ href: user_url(@merge_request.author), style: "color:#333333;text-decoration:none;" }
= @merge_request.author.name
- if @merge_request.assignees.any?
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" }
= assignees_label(@merge_request, include_value: false)
%td{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; margin: 0; padding: 14px 0 0px 5px; font-size: 15px; line-height: 1.4; color: #333333; font-weight: 400; width: 75%; border-top-style: solid; border-top-color: #ededed; border-top-width: 1px; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt;" }
%ul.assignees-list{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; font-size: 15px; line-height: 1.4; padding-right: 5px; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt;" }
- @merge_request.assignees.each do |assignee|
%li
%img.avatar{ alt: "Avatar", height: "24", src: avatar_icon_for_user(assignee, 24, only_path: false), style: "border-radius: 12px; max-width: 100%; height: auto; -ms-interpolation-mode: bicubic; margin: -2px 0;", width: "24" }
%a.muted{ href: user_url(assignee), style: "color: #333333; text-decoration: none; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; vertical-align: top;" }
= assignee.name
-# EE-specific start
= render 'layouts/mailer/additional_text'
-# EE-specific end
%tr.footer
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
%img{ alt: "GitLab", height: "33", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif'), style: "display:block;margin:0 auto 1em;", width: "90" }
%div
- manage_notifications_link = link_to(_("Manage all notifications"), profile_notifications_url, style: "color:#3777b0;text-decoration:none;")
- help_link = link_to(_("Help"), help_url, style: "color:#3777b0;text-decoration:none;")
= _("You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}").html_safe % { host: Gitlab.config.gitlab.host, manage_notifications_link: manage_notifications_link, help_link: help_link }
Merge Request #{@merge_request.to_reference} was scheduled to merge after pipeline succeeds by #{sanitize_name(@mwps_set_by.name)}
Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
= merge_path_description(@merge_request, 'to')
Author: #{sanitize_name(@merge_request.author_name)}
= assignees_label(@merge_request)
...@@ -14528,6 +14528,9 @@ msgstr "" ...@@ -14528,6 +14528,9 @@ msgstr ""
msgid "Merge request dependencies" msgid "Merge request dependencies"
msgstr "" msgstr ""
msgid "Merge request was scheduled to merge after pipeline succeeds"
msgstr ""
msgid "Merge requests" msgid "Merge requests"
msgstr "" msgstr ""
...@@ -28884,6 +28887,9 @@ msgstr "" ...@@ -28884,6 +28887,9 @@ msgstr ""
msgid "vulnerability|dismissed" msgid "vulnerability|dismissed"
msgstr "" msgstr ""
msgid "was scheduled to merge after pipeline succeeds by"
msgstr ""
msgid "wiki page" msgid "wiki page"
msgstr "" msgstr ""
......
...@@ -17,4 +17,20 @@ RSpec.describe Emails::MergeRequests do ...@@ -17,4 +17,20 @@ RSpec.describe Emails::MergeRequests do
expect(subject).to have_body_text current_user.name expect(subject).to have_body_text current_user.name
end end
end end
describe "#merge_when_pipeline_succeeds_email" do
let(:user) { create(:user) }
let(:merge_request) { create(:merge_request) }
let(:current_user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:title) { "Merge request #{merge_request.to_reference} was scheduled to merge after pipeline succeeds by #{current_user.name}" }
subject { Notify.merge_when_pipeline_succeeds_email(user.id, merge_request.id, current_user.id) }
it "has required details" do
expect(subject).to have_content title
expect(subject).to have_content merge_request.to_reference
expect(subject).to have_content current_user.name
end
end
end end
...@@ -69,6 +69,7 @@ RSpec.describe AutoMerge::MergeWhenPipelineSucceedsService do ...@@ -69,6 +69,7 @@ RSpec.describe AutoMerge::MergeWhenPipelineSucceedsService do
before do before do
allow(merge_request) allow(merge_request)
.to receive_messages(head_pipeline: pipeline, actual_head_pipeline: pipeline) .to receive_messages(head_pipeline: pipeline, actual_head_pipeline: pipeline)
expect(MailScheduler::NotificationServiceWorker).to receive(:perform_async).with('merge_when_pipeline_succeeds', merge_request, user).once
service.execute(merge_request) service.execute(merge_request)
end end
...@@ -90,6 +91,18 @@ RSpec.describe AutoMerge::MergeWhenPipelineSucceedsService do ...@@ -90,6 +91,18 @@ RSpec.describe AutoMerge::MergeWhenPipelineSucceedsService do
end end
end end
context 'without feature enabled' do
it 'does not send notification' do
stub_feature_flags(mwps_notification: false)
allow(merge_request)
.to receive_messages(head_pipeline: pipeline, actual_head_pipeline: pipeline)
expect(MailScheduler::NotificationServiceWorker).not_to receive(:perform_async)
service.execute(merge_request)
end
end
context 'already approved' do context 'already approved' do
let(:service) { described_class.new(project, user, should_remove_source_branch: true) } let(:service) { described_class.new(project, user, should_remove_source_branch: true) }
let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch) } let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch) }
...@@ -106,6 +119,7 @@ RSpec.describe AutoMerge::MergeWhenPipelineSucceedsService do ...@@ -106,6 +119,7 @@ RSpec.describe AutoMerge::MergeWhenPipelineSucceedsService do
it 'updates the merge params' do it 'updates the merge params' do
expect(SystemNoteService).not_to receive(:merge_when_pipeline_succeeds) expect(SystemNoteService).not_to receive(:merge_when_pipeline_succeeds)
expect(MailScheduler::NotificationServiceWorker).not_to receive(:perform_async).with('merge_when_pipeline_succeeds', any_args)
service.execute(mr_merge_if_green_enabled) service.execute(mr_merge_if_green_enabled)
expect(mr_merge_if_green_enabled.merge_params).to have_key('should_remove_source_branch') expect(mr_merge_if_green_enabled.merge_params).to have_key('should_remove_source_branch')
......
...@@ -2023,6 +2023,26 @@ RSpec.describe NotificationService, :mailer do ...@@ -2023,6 +2023,26 @@ RSpec.describe NotificationService, :mailer do
let(:notification_trigger) { notification.resolve_all_discussions(merge_request, @u_disabled) } let(:notification_trigger) { notification.resolve_all_discussions(merge_request, @u_disabled) }
end end
end end
describe '#merge_when_pipeline_succeeds' do
it 'send notification that merge will happen when pipeline succeeds' do
notification.merge_when_pipeline_succeeds(merge_request, assignee)
should_email(merge_request.author)
should_email(@u_watcher)
should_email(@subscriber)
end
it_behaves_like 'participating notifications' do
let(:participant) { create(:user, username: 'user-participant') }
let(:issuable) { merge_request }
let(:notification_trigger) { notification.merge_when_pipeline_succeeds(merge_request, @u_disabled) }
end
it_behaves_like 'project emails are disabled' do
let(:notification_target) { merge_request }
let(:notification_trigger) { notification.merge_when_pipeline_succeeds(merge_request, @u_disabled) }
end
end
end end
describe 'Projects', :deliver_mails_inline do describe 'Projects', :deliver_mails_inline do
......
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