Commit 868cb7a4 authored by Vitaly Slobodin's avatar Vitaly Slobodin Committed by James Lopez

Implement License mailer

LicenseMailer is a mailer for sending
emails related to licenses, for example
sending an email when EE instance
is approaching the active user count threshold.
parent 8a5d5b40
......@@ -526,6 +526,9 @@ Settings.cron_jobs['member_invitation_reminder_emails_worker']['cron'] ||= '0 0
Settings.cron_jobs['member_invitation_reminder_emails_worker']['job_class'] = 'MemberInvitationReminderEmailsWorker'
Gitlab.ee do
Settings.cron_jobs['active_user_count_threshold_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['active_user_count_threshold_worker']['cron'] ||= '0 12 * * *'
Settings.cron_jobs['active_user_count_threshold_worker']['job_class'] = 'ActiveUserCountThresholdWorker'
Settings.cron_jobs['adjourned_group_deletion_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['adjourned_group_deletion_worker']['cron'] ||= '0 3 * * *'
Settings.cron_jobs['adjourned_group_deletion_worker']['job_class'] = 'AdjournedGroupDeletionWorker'
......
......@@ -42,6 +42,6 @@ module LicenseMonitoringHelper
end
def remaining_user_count
strong_memoize(:remaining_user_count) { current_license.restricted_user_count }
strong_memoize(:remaining_user_count) { current_license.remaining_user_count }
end
end
# frozen_string_literal: true
class LicenseMailer < ApplicationMailer
helper EmailsHelper
layout 'mailer'
def approaching_active_user_count_limit(recipients)
@license = License.current
return unless @license
mail(
bcc: recipients,
subject: "Your subscription is nearing its user limit"
)
end
end
# frozen_string_literal: true
class LicenseMailerPreview < ActionMailer::Preview
def approaching_active_user_count_limit
::LicenseMailer.approaching_active_user_count_limit(%w(admin@example.com))
end
end
......@@ -291,6 +291,15 @@ class License < ApplicationRecord
decryptable_licenses.sort_by { |license| [license.starts_at, license.created_at, license.expires_at] }.reverse
end
def with_valid_license
current_license = License.current
return unless current_license
return if current_license.trial?
yield(current_license) if block_given?
end
private
def load_future_dated
......
- users_over_license_link = link_to("users over license", "https://docs.gitlab.com/ee/subscriptions/#users-over-license")
- self_managed_subscriptions_doc_link = link_to("self-managed subscriptions", "https://docs.gitlab.com/ee/subscriptions/self_managed/index.html")
- subscriptions_doc_link = link_to("our documentation", "https://docs.gitlab.com/ee/subscriptions")
%p
= _("Dear Administrator,")
%p
= html_escape(_("We would like to inform you that your subscription GitLab Enterprise Edition %{plan_name} is nearing its user limit. You have %{active_user_count} active users, which is almost at the user limit of %{maximum_user_count}.")) % { plan_name: @license.plan.titleize, active_user_count: @license.current_active_users_count, maximum_user_count: @license.restricted_user_count }
%p
= html_escape(_("If the number of active users exceeds the user limit, you will be charged for the number of %{users_over_license_link} at your next license reconciliation.")) % { users_over_license_link: users_over_license_link }
%p
= html_escape(_("For more information on how the number of active users is calculated, see the %{self_managed_subscriptions_doc_link} documentation.")) % { self_managed_subscriptions_doc_link: self_managed_subscriptions_doc_link }
%p
= html_escape(_("You can find more information about GitLab subscriptions in %{subscriptions_doc_link}.")) % { subscriptions_doc_link: subscriptions_doc_link }
%p
= _("Please reach out if you have any questions and we'll be happy to assist.")
%p
= _("Thank you for your business.")
%p
= _("GitLab Billing Team.")
# frozen_string_literal: true
class ActiveUserCountThresholdWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
# rubocop:disable Scalability/CronWorkerContext
# This worker does not perform work scoped to a context
include CronjobQueue
# rubocop:enable Scalability/CronWorkerContext
feature_category :provision
def perform
License.with_valid_license do |license|
break unless license.active_user_count_threshold_reached?
# rubocop:disable CodeReuse/ActiveRecord
recipients = User
.active
.admins
.pluck(:email)
.to_set
# rubocop:enable CodeReuse/ActiveRecord
recipients << license.licensee["Email"] if license.licensee["Email"]
LicenseMailer.approaching_active_user_count_limit(recipients.to_a)
end
end
end
......@@ -3,6 +3,14 @@
#
# Do not edit it manually!
---
- :name: cronjob:active_user_count_threshold
:feature_category: :provision
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:tags: []
- :name: cronjob:adjourned_group_deletion
:feature_category: :authentication_and_authorization
:has_external_dependencies:
......
......@@ -10,7 +10,7 @@ class HistoricalDataWorker # rubocop:disable Scalability/IdempotentWorker
feature_category :provision
def perform
return if License.current.nil? || License.current&.trial?
return if License.current.nil? || License.current.trial?
HistoricalData.track!
end
......
---
title: Send email reminder when approaching active user limit
merge_request: 42453
author:
type: added
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe LicenseMailer do
include EmailSpec::Matchers
let(:recipients) { %w(admin@example.com another_admin@example.com) }
let_it_be(:license) { create_current_license({ plan: License::STARTER_PLAN, restrictions: { active_user_count: 21 } }) }
describe '#approaching_active_user_count_limit' do
let(:subject_text) { 'Your subscription is nearing its user limit' }
let(:subscription_name) { 'GitLab Enterprise Edition Starter' }
let(:active_user_count) { 20 }
subject { described_class.approaching_active_user_count_limit(recipients) }
before do
allow(license).to receive(:current_active_users_count).and_return(active_user_count)
allow(License).to receive(:current).and_return(license)
end
context 'when license is present' do
it { is_expected.to have_subject subject_text }
it { is_expected.to bcc_to recipients }
it { is_expected.to have_body_text "your subscription #{subscription_name}" }
it { is_expected.to have_body_text "You have #{active_user_count} active users" }
it { is_expected.to have_body_text "the user limit of #{license.restricted_user_count}" }
end
context 'when license is not present' do
it 'does not send email' do
expect { subject }.not_to change(ActionMailer::Base.deliveries, :count)
end
end
end
end
......@@ -3,6 +3,8 @@
require "spec_helper"
RSpec.describe License do
using RSpec::Parameterized::TableSyntax
let(:gl_license) { build(:gitlab_license) }
let(:license) { build(:license, data: gl_license.export) }
......@@ -26,8 +28,6 @@ RSpec.describe License do
end
describe '#check_users_limit' do
using RSpec::Parameterized::TableSyntax
before do
create(:group_member, :guest)
create(:group_member, :reporter)
......@@ -550,6 +550,39 @@ RSpec.describe License do
it { is_expected.to be(false) }
end
end
describe '.with_valid_license' do
context 'when license trial' do
before do
allow(license).to receive(:trial?).and_return(true)
allow(License).to receive(:current).and_return(license)
end
it 'does not yield block' do
expect { |b| License.with_valid_license(&b) }.not_to yield_control
end
end
context 'when license nil' do
before do
allow(License).to receive(:current).and_return(nil)
end
it 'does not yield block' do
expect { |b| License.with_valid_license(&b) }.not_to yield_control
end
end
context 'when license is valid' do
before do
allow(License).to receive(:current).and_return(license)
end
it 'yields block' do
expect { |b| License.with_valid_license(&b) }.to yield_with_args(license)
end
end
end
end
describe "#md5" do
......@@ -742,8 +775,6 @@ RSpec.describe License do
end
describe '#maximum_user_count' do
using RSpec::Parameterized::TableSyntax
subject { license.maximum_user_count }
where(:current_active_users_count, :historical_max, :expected) do
......@@ -907,8 +938,6 @@ RSpec.describe License do
end
describe '#paid?' do
using RSpec::Parameterized::TableSyntax
where(:plan, :paid_result) do
License::STARTER_PLAN | true
License::PREMIUM_PLAN | true
......@@ -928,8 +957,6 @@ RSpec.describe License do
end
describe '#started?' do
using RSpec::Parameterized::TableSyntax
where(:starts_at, :result) do
Date.current - 1.month | true
Date.current | true
......@@ -948,8 +975,6 @@ RSpec.describe License do
end
describe '#future_dated?' do
using RSpec::Parameterized::TableSyntax
where(:starts_at, :result) do
Date.current - 1.month | false
Date.current | false
......@@ -983,8 +1008,6 @@ RSpec.describe License do
end
context 'for license with users' do
using RSpec::Parameterized::TableSyntax
where(:restricted_user_count, :active_user_count, :percentage, :threshold_value) do
3 | 2 | false | 1
20 | 18 | false | 2
......@@ -1006,8 +1029,6 @@ RSpec.describe License do
end
describe '#active_user_count_threshold_reached?' do
using RSpec::Parameterized::TableSyntax
subject { license.active_user_count_threshold_reached? }
where(:restricted_user_count, :current_active_users_count, :result) do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ActiveUserCountThresholdWorker do
using RSpec::Parameterized::TableSyntax
subject { described_class.new }
let(:license) { build(:license) }
describe '#perform' do
where(:trial?, :threshold_reached?, :should_send_reminder?) do
false | false | false
false | true | true
true | false | false
true | true | false
end
with_them do
before do
allow(license).to receive(:trial?).and_return(trial?)
allow(license).to receive(:active_user_count_threshold_reached?).and_return(threshold_reached?)
allow(License).to receive(:current).and_return(license)
end
it do
if should_send_reminder?
expect(LicenseMailer).to receive(:approaching_active_user_count_limit)
else
expect(LicenseMailer).not_to receive(:approaching_active_user_count_limit)
end
subject.perform
end
end
context 'recipients' do
let_it_be(:admins) { create_list(:admin, 3) }
before do
allow(license).to receive(:trial?).and_return(false)
allow(license).to receive(:active_user_count_threshold_reached?).and_return(true)
allow(License).to receive(:current).and_return(license)
end
it 'sends reminder to admins only' do
admins_emails = admins.pluck(:email)
expect(LicenseMailer).to receive(:approaching_active_user_count_limit).with(array_including(*admins_emails))
subject.perform
end
it 'adds a licensee email to the recipients list' do
allow(license).to receive(:licensee).and_return({ 'Email' => admins.first.email })
licensee_email = license.licensee['Email']
expect(LicenseMailer).to receive(:approaching_active_user_count_limit).with(array_including([licensee_email]))
subject.perform
end
it 'sends reminder to unique emails' do
admins_emails = admins.pluck(:email)
allow(license.licensee).to receive('Email').and_return(admins.first.email)
expect(LicenseMailer).to receive(:approaching_active_user_count_limit).with(array_including(*admins_emails))
subject.perform
end
it 'sends reminder to active admins only' do
admins.first.deactivate!
active_admins_emails = admins.drop(1).pluck(:email)
expect(LicenseMailer).to receive(:approaching_active_user_count_limit).with(array_including(*active_admins_emails))
subject.perform
end
end
context 'when there is no license' do
it 'does not send a reminder' do
expect(LicenseMailer).not_to receive(:approaching_active_user_count_limit)
subject.perform
end
end
end
end
......@@ -8187,6 +8187,9 @@ msgstr ""
msgid "Days to merge"
msgstr ""
msgid "Dear Administrator,"
msgstr ""
msgid "Debug"
msgstr ""
......@@ -11362,6 +11365,9 @@ msgstr ""
msgid "For more info, read the documentation."
msgstr ""
msgid "For more information on how the number of active users is calculated, see the %{self_managed_subscriptions_doc_link} documentation."
msgstr ""
msgid "For more information, go to the "
msgstr ""
......@@ -11938,6 +11944,9 @@ msgstr ""
msgid "GitLab API"
msgstr ""
msgid "GitLab Billing Team."
msgstr ""
msgid "GitLab Group Runners can execute code for all the projects in this group."
msgstr ""
......@@ -13250,6 +13259,9 @@ msgstr ""
msgid "If enabled, access to projects will be validated on an external service using their classification label."
msgstr ""
msgid "If the number of active users exceeds the user limit, you will be charged for the number of %{users_over_license_link} at your next license reconciliation."
msgstr ""
msgid "If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded."
msgstr ""
......@@ -19064,6 +19076,9 @@ msgstr ""
msgid "Please provide attributes to update"
msgstr ""
msgid "Please reach out if you have any questions and we'll be happy to assist."
msgstr ""
msgid "Please refer to %{docs_url}"
msgstr ""
......@@ -25305,6 +25320,9 @@ msgstr ""
msgid "Thank you for signing up for your free trial! You will get additional instructions in your inbox shortly."
msgstr ""
msgid "Thank you for your business."
msgstr ""
msgid "Thank you for your feedback!"
msgstr ""
......@@ -28689,6 +28707,9 @@ msgstr ""
msgid "We will notify %{inviter} that you declined their invitation to join GitLab. You will stop receiving reminders."
msgstr ""
msgid "We would like to inform you that your subscription GitLab Enterprise Edition %{plan_name} is nearing its user limit. You have %{active_user_count} active users, which is almost at the user limit of %{maximum_user_count}."
msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
......@@ -29276,6 +29297,9 @@ msgstr ""
msgid "You can filter by 'days to merge' by clicking on the columns in the chart."
msgstr ""
msgid "You can find more information about GitLab subscriptions in %{subscriptions_doc_link}."
msgstr ""
msgid "You can generate an access token scoped to this project for each application to use the GitLab API."
msgstr ""
......
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