Commit f1bc8d2d authored by Alex Buijs's avatar Alex Buijs

Add invitation reminder email service

This service is to be called from the
MemberInvitationReminderEmailsWorker
cron job
parent 0ced66ba
# frozen_string_literal: true
module Members
class InvitationReminderEmailService
include Gitlab::Utils::StrongMemoize
attr_reader :invitation
MAX_INVITATION_LIFESPAN = 14.0
REMINDER_RATIO = [2, 5, 10].freeze
def initialize(invitation)
@invitation = invitation
end
def execute
return unless experiment_enabled?
reminder_index = days_on_which_to_send_reminders.index(days_after_invitation_sent)
return unless reminder_index
invitation.send_invitation_reminder(reminder_index)
end
private
def experiment_enabled?
Gitlab::Experimentation.enabled_for_attribute?(:invitation_reminders, invitation.invite_email)
end
def days_after_invitation_sent
(Date.today - invitation.created_at.to_date).to_i
end
def days_on_which_to_send_reminders
# Don't send any reminders if the invitation has expired or expires today
return [] if invitation.expires_at && invitation.expires_at <= Date.today
# Calculate the number of days on which to send reminders based on the MAX_INVITATION_LIFESPAN and the REMINDER_RATIO
REMINDER_RATIO.map { |number_of_days| ((number_of_days * invitation_lifespan_in_days) / MAX_INVITATION_LIFESPAN).ceil }.uniq
end
def invitation_lifespan_in_days
# When the invitation lifespan is more than 14 days or does not expire, send the reminders within 14 days
strong_memoize(:invitation_lifespan_in_days) do
if invitation.expires_at
[(invitation.expires_at - invitation.created_at.to_date).to_i, MAX_INVITATION_LIFESPAN].min
else
MAX_INVITATION_LIFESPAN
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Members::InvitationReminderEmailService do
describe 'sending invitation reminders' do
subject { described_class.new(invitation).execute }
let_it_be(:frozen_time) { Date.today.beginning_of_day }
let_it_be(:invitation) { build(:group_member, :invited, created_at: frozen_time) }
context 'when the experiment is disabled' do
before do
allow(Gitlab::Experimentation).to receive(:enabled_for_attribute?).and_return(false)
invitation.expires_at = frozen_time + 2.days
end
it 'does not send an invitation' do
travel_to(frozen_time + 1.day) do
expect(invitation).not_to receive(:send_invitation_reminder)
subject
end
end
end
context 'when the experiment is enabled' do
before do
allow(Gitlab::Experimentation).to receive(:enabled_for_attribute?).and_return(true)
invitation.expires_at = frozen_time + expires_at_days.days if expires_at_days
end
using RSpec::Parameterized::TableSyntax
where(:expires_at_days, :send_reminder_at_days) do
0 | []
1 | []
2 | [1]
3 | [1, 2]
4 | [1, 2, 3]
5 | [1, 2, 4]
6 | [1, 3, 5]
7 | [1, 3, 5]
8 | [2, 3, 6]
9 | [2, 4, 7]
10 | [2, 4, 8]
11 | [2, 4, 8]
12 | [2, 5, 9]
13 | [2, 5, 10]
14 | [2, 5, 10]
15 | [2, 5, 10]
nil | [2, 5, 10]
end
with_them do
# Create an invitation today with an expiration date from 0 to 10 days in the future or without an expiration date
# We chose 10 days here, because we fetch invitations that were created at most 10 days ago.
(0..10).each do |day|
it 'sends an invitation reminder only on the expected days' do
next if day > (expires_at_days || 10) # We don't need to test after the invitation has already expired
# We are traveling in a loop from today to 10 days from now
travel_to(frozen_time + day.days) do
# Given an expiration date and the number of days after the creation of the invitation based on the current day in the loop, a reminder may be sent
if (reminder_index = send_reminder_at_days.index(day))
expect(invitation).to receive(:send_invitation_reminder).with(reminder_index)
else
expect(invitation).not_to receive(:send_invitation_reminder)
end
subject
end
end
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