Commit bfa50243 authored by Heinrich Lee Yu's avatar Heinrich Lee Yu

Merge branch 'reminder-emails-notifications' into 'master'

Add reminder emails

See merge request gitlab-org/gitlab!42988
parents 7629fadf 98d0868d
...@@ -52,6 +52,10 @@ a { ...@@ -52,6 +52,10 @@ a {
margin-top: 0; margin-top: 0;
} }
.invite-body {
width: 360px;
}
.invite-actions { .invite-actions {
margin-top: 24px; margin-top: 24px;
} }
...@@ -64,6 +68,15 @@ a { ...@@ -64,6 +68,15 @@ a {
color: $white; color: $white;
} }
.invite-btn-decline {
border-radius: $border-radius-default;
border: 1px solid $purple;
padding: $gl-btn-vert-padding $gl-btn-horz-padding;
cursor: pointer;
color: $purple;
margin-left: 4px;
}
tr td { tr td {
font-family: $mailer-font; font-family: $mailer-font;
} }
......
# frozen_string_literal: true
module ReminderEmailsHelper
def invitation_reminder_salutation(reminder_index, format: nil)
case reminder_index
when 0
s_('InviteReminderEmail|Invitation pending')
when 1
if format == :html
s_('InviteReminderEmail|Hey there %{wave_emoji}').html_safe % { wave_emoji: Gitlab::Emoji.gl_emoji_tag('wave') }
else
s_('InviteReminderEmail|Hey there!')
end
when 2
s_('InviteReminderEmail|In case you missed it...')
end
end
def invitation_reminder_body(member, reminder_index, format: nil)
options = {
inviter: sanitize_name(member.created_by.name),
strong_start: '',
strong_end: '',
project_or_group_name: member_source.human_name,
project_or_group: member_source.model_name.singular,
role: member.human_access.downcase
}
if format == :html
options.merge!(
inviter: (link_to member.created_by.name, user_url(member.created_by)).html_safe,
strong_start: '<strong>'.html_safe,
strong_end: '</strong>'.html_safe
)
end
if reminder_index == 2
options[:invitation_age] = (Date.current - member.created_at.to_date).to_i
end
body = invitation_reminder_body_text(reminder_index)
(format == :html ? html_escape(body) : body ) % options
end
def invitation_reminder_accept_link(token, format: nil)
case format
when :html
link_to s_('InviteReminderEmail|Accept invitation'), invite_url(token), class: 'invite-btn-join'
else
s_('InviteReminderEmail|Accept invitation: %{invite_url}') % { invite_url: invite_url(token) }
end
end
def invitation_reminder_decline_link(token, format: nil)
case format
when :html
link_to s_('InviteReminderEmail|Decline invitation'), decline_invite_url(token), class: 'invite-btn-decline'
else
s_('InviteReminderEmail|Decline invitation: %{decline_url}') % { decline_url: decline_invite_url(token) }
end
end
private
def invitation_reminder_body_text(reminder_index)
case reminder_index
when 0
s_('InviteReminderEmail|%{inviter} is waiting for you to join the %{strong_start}%{project_or_group_name}%{strong_end} %{project_or_group} as a %{role}.')
when 1
s_('InviteReminderEmail|This is a friendly reminder that %{inviter} invited you to join the %{strong_start}%{project_or_group_name}%{strong_end} %{project_or_group} as a %{role}.')
when 2
s_("InviteReminderEmail|It's been %{invitation_age} days since %{inviter} invited you to join the %{strong_start}%{project_or_group_name}%{strong_end} %{project_or_group} as a %{role}. What would you like to do?")
end
end
end
...@@ -88,6 +88,29 @@ module Emails ...@@ -88,6 +88,29 @@ module Emails
end end
end end
def member_invited_reminder_email(member_source_type, member_id, token, reminder_index)
@member_source_type = member_source_type
@member_id = member_id
@token = token
@reminder_index = reminder_index
return unless member_exists? && member.created_by && member.invite_to_unknown_user?
subjects = {
0 => s_("InviteReminderEmail|%{inviter}'s invitation to GitLab is pending"),
1 => s_('InviteReminderEmail|%{inviter} is waiting for you to join GitLab'),
2 => s_('InviteReminderEmail|%{inviter} is still waiting for you to join GitLab')
}
subject_line = subjects[reminder_index] % { inviter: member.created_by.name }
member_email_with_layout(
layout: 'experiment_mailer',
to: member.invite_email,
subject: subject(subject_line)
)
end
def member_invite_accepted_email(member_source_type, member_id) def member_invite_accepted_email(member_source_type, member_id)
@member_source_type = member_source_type @member_source_type = member_source_type
@member_id = member_id @member_id = member_id
......
...@@ -4,6 +4,7 @@ class Notify < ApplicationMailer ...@@ -4,6 +4,7 @@ class Notify < ApplicationMailer
include ActionDispatch::Routing::PolymorphicRoutes include ActionDispatch::Routing::PolymorphicRoutes
include GitlabRoutingHelper include GitlabRoutingHelper
include EmailsHelper include EmailsHelper
include ReminderEmailsHelper
include IssuablesHelper include IssuablesHelper
include Emails::Issues include Emails::Issues
...@@ -26,6 +27,7 @@ class Notify < ApplicationMailer ...@@ -26,6 +27,7 @@ class Notify < ApplicationMailer
helper DiffHelper helper DiffHelper
helper BlobHelper helper BlobHelper
helper EmailsHelper helper EmailsHelper
helper ReminderEmailsHelper
helper MembersHelper helper MembersHelper
helper AvatarsHelper helper AvatarsHelper
helper GitlabRoutingHelper helper GitlabRoutingHelper
......
...@@ -435,6 +435,10 @@ class NotificationService ...@@ -435,6 +435,10 @@ class NotificationService
mailer.member_invited_email(group_member.real_source_type, group_member.id, token).deliver_later mailer.member_invited_email(group_member.real_source_type, group_member.id, token).deliver_later
end end
def invite_member_reminder(group_member, token, reminder_index)
mailer.member_invited_reminder_email(group_member.real_source_type, group_member.id, token, reminder_index).deliver_later
end
def accept_group_invite(group_member) def accept_group_invite(group_member)
mailer.member_invite_accepted_email(group_member.real_source_type, group_member.id).deliver_later mailer.member_invite_accepted_email(group_member.real_source_type, group_member.id).deliver_later
end end
......
%tr
%td.text-content
%h2.invite-header
= invitation_reminder_salutation(@reminder_index, format: :html)
%p.invite-body
= invitation_reminder_body(member, @reminder_index, format: :html)
%p.invite-actions
= invitation_reminder_accept_link(@token, format: :html)
= invitation_reminder_decline_link(@token, format: :html)
<%= invitation_reminder_salutation(@reminder_index) %>
<%= invitation_reminder_body(member, @reminder_index) %>
<%= invitation_reminder_accept_link(@token) %>
<%= invitation_reminder_decline_link(@token) %>
...@@ -14084,6 +14084,48 @@ msgstr "" ...@@ -14084,6 +14084,48 @@ msgstr ""
msgid "InviteMembers|Invite team members" msgid "InviteMembers|Invite team members"
msgstr "" msgstr ""
msgid "InviteReminderEmail|%{inviter} is still waiting for you to join GitLab"
msgstr ""
msgid "InviteReminderEmail|%{inviter} is waiting for you to join GitLab"
msgstr ""
msgid "InviteReminderEmail|%{inviter} is waiting for you to join the %{strong_start}%{project_or_group_name}%{strong_end} %{project_or_group} as a %{role}."
msgstr ""
msgid "InviteReminderEmail|%{inviter}'s invitation to GitLab is pending"
msgstr ""
msgid "InviteReminderEmail|Accept invitation"
msgstr ""
msgid "InviteReminderEmail|Accept invitation: %{invite_url}"
msgstr ""
msgid "InviteReminderEmail|Decline invitation"
msgstr ""
msgid "InviteReminderEmail|Decline invitation: %{decline_url}"
msgstr ""
msgid "InviteReminderEmail|Hey there %{wave_emoji}"
msgstr ""
msgid "InviteReminderEmail|Hey there!"
msgstr ""
msgid "InviteReminderEmail|In case you missed it..."
msgstr ""
msgid "InviteReminderEmail|Invitation pending"
msgstr ""
msgid "InviteReminderEmail|It's been %{invitation_age} days since %{inviter} invited you to join the %{strong_start}%{project_or_group_name}%{strong_end} %{project_or_group} as a %{role}. What would you like to do?"
msgstr ""
msgid "InviteReminderEmail|This is a friendly reminder that %{inviter} invited you to join the %{strong_start}%{project_or_group_name}%{strong_end} %{project_or_group} as a %{role}."
msgstr ""
msgid "Invited" msgid "Invited"
msgstr "" msgstr ""
......
...@@ -1640,6 +1640,88 @@ RSpec.describe Notify do ...@@ -1640,6 +1640,88 @@ RSpec.describe Notify do
end end
end end
describe 'group invitation reminders' do
let_it_be(:inviter) { create(:user).tap { |u| group.add_user(u, Gitlab::Access::OWNER) } }
let(:group_member) { invite_to_group(group, inviter: inviter) }
subject { described_class.member_invited_reminder_email('Group', group_member.id, group_member.invite_token, reminder_index) }
describe 'not sending a reminder' do
let(:reminder_index) { 0 }
context 'member does not exist' do
let(:group_member) { double(id: nil, invite_token: nil) }
it_behaves_like 'no email is sent'
end
context 'member is not created by a user' do
before do
group_member.update(created_by: nil)
end
it_behaves_like 'no email is sent'
end
context 'member is a known user' do
before do
group_member.update(user: create(:user))
end
it_behaves_like 'no email is sent'
end
end
describe 'the first reminder' do
let(:reminder_index) { 0 }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'contains all the useful information' do
is_expected.to have_subject "#{inviter.name}'s invitation to GitLab is pending"
is_expected.to have_body_text group.human_name
is_expected.to have_body_text group_member.human_access.downcase
is_expected.to have_body_text invite_url(group_member.invite_token)
is_expected.to have_body_text decline_invite_url(group_member.invite_token)
end
end
describe 'the second reminder' do
let(:reminder_index) { 1 }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'contains all the useful information' do
is_expected.to have_subject "#{inviter.name} is waiting for you to join GitLab"
is_expected.to have_body_text group.human_name
is_expected.to have_body_text group_member.human_access.downcase
is_expected.to have_body_text invite_url(group_member.invite_token)
is_expected.to have_body_text decline_invite_url(group_member.invite_token)
end
end
describe 'the third reminder' do
let(:reminder_index) { 2 }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'contains all the useful information' do
is_expected.to have_subject "#{inviter.name} is still waiting for you to join GitLab"
is_expected.to have_body_text group.human_name
is_expected.to have_body_text group_member.human_access.downcase
is_expected.to have_body_text invite_url(group_member.invite_token)
is_expected.to have_body_text decline_invite_url(group_member.invite_token)
end
end
end
describe 'group invitation accepted' do describe 'group invitation accepted' do
let(:invited_user) { create(:user, name: 'invited user') } let(:invited_user) { create(:user, name: 'invited user') }
let(:owner) { create(:user).tap { |u| group.add_user(u, Gitlab::Access::OWNER) } } let(:owner) { create(:user).tap { |u| group.add_user(u, Gitlab::Access::OWNER) } }
......
...@@ -2293,6 +2293,25 @@ RSpec.describe NotificationService, :mailer do ...@@ -2293,6 +2293,25 @@ RSpec.describe NotificationService, :mailer do
end end
end end
describe '#invite_member_reminder' do
let_it_be(:group_member) { create(:group_member) }
subject { notification.invite_member_reminder(group_member, 'token', 0) }
it 'calls the Notify.invite_member_reminder method with the right params' do
expect(Notify).to receive(:member_invited_reminder_email).with('Group', group_member.id, 'token', 0).at_least(:once).and_call_original
subject
end
it 'sends exactly one email' do
subject
expect_delivery_jobs_count(1)
expect_enqueud_email('Group', group_member.id, 'token', 0, mail: 'member_invited_reminder_email')
end
end
describe 'GroupMember', :deliver_mails_inline do describe 'GroupMember', :deliver_mails_inline do
let(:added_user) { create(:user) } let(:added_user) { create(:user) }
......
...@@ -267,3 +267,9 @@ RSpec.shared_examples 'appearance header and footer not enabled' do ...@@ -267,3 +267,9 @@ RSpec.shared_examples 'appearance header and footer not enabled' do
end end
end end
end end
RSpec.shared_examples 'no email is sent' do
it 'does not send an email' do
expect(subject.message).to be_a_kind_of(ActionMailer::Base::NullMail)
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