From e5ee46da246fd43d05000fcd336329fea0cb97c0 Mon Sep 17 00:00:00 2001
From: Doug Stull <dstull@gitlab.com>
Date: Thu, 14 May 2020 22:16:12 +0000
Subject: [PATCH] Improve ci minutes notification emails

- as per spec
- reorganize code a bit

https://gitlab.com/gitlab-org/gitlab/-/merge_requests/30188
---
 ee/app/mailers/ci_minutes_usage_mailer.rb     | 10 ++--
 .../ci_minutes_usage_mailer_preview.rb        | 11 ++++
 .../ci/minutes/email_notification_service.rb  | 53 +++++++++++++++++++
 .../ci_minutes_usage_notify_service.rb        | 49 -----------------
 .../ci_minutes_usage_mailer/notify.html.haml  |  6 +--
 .../ci_minutes_usage_mailer/notify.text.erb   |  7 +--
 .../notify_limit.html.haml                    |  6 +--
 .../notify_limit.text.erb                     |  7 ++-
 ee/app/workers/ee/build_finished_worker.rb    |  2 +-
 ...g-out-of-ci-minutes-email-notification.yml |  5 ++
 .../mailers/ci_minutes_usage_mailer_spec.rb   | 41 ++++++++++++++
 .../email_notification_service_spec.rb}       |  2 +-
 locale/gitlab.pot                             | 12 +++++
 13 files changed, 144 insertions(+), 67 deletions(-)
 create mode 100644 ee/app/mailers/previews/ci_minutes_usage_mailer_preview.rb
 create mode 100644 ee/app/services/ci/minutes/email_notification_service.rb
 delete mode 100644 ee/app/services/ci_minutes_usage_notify_service.rb
 create mode 100644 ee/changelogs/unreleased/214997-improve-the-you-re-running-out-of-ci-minutes-email-notification.yml
 create mode 100644 ee/spec/mailers/ci_minutes_usage_mailer_spec.rb
 rename ee/spec/services/{ci_minutes_usage_notify_service_spec.rb => ci/minutes/email_notification_service_spec.rb} (99%)

diff --git a/ee/app/mailers/ci_minutes_usage_mailer.rb b/ee/app/mailers/ci_minutes_usage_mailer.rb
index 4069ada35a36..51a28989968b 100644
--- a/ee/app/mailers/ci_minutes_usage_mailer.rb
+++ b/ee/app/mailers/ci_minutes_usage_mailer.rb
@@ -1,12 +1,16 @@
 # frozen_string_literal: true
 
 class CiMinutesUsageMailer < ApplicationMailer
+  helper EmailsHelper
+
+  layout 'mailer'
+
   def notify(namespace_name, recipients)
     @namespace_name = namespace_name
 
     mail(
       bcc: recipients,
-      subject: "GitLab CI Runner Minutes quota for #{namespace_name} has run out"
+      subject: "Action required: There are no remaining Pipeline minutes for #{namespace_name}"
     )
   end
 
@@ -16,8 +20,8 @@ def notify_limit(namespace_name, recipients, percentage_of_available_mins)
 
     mail(
       bcc: recipients,
-      subject: "GitLab CI Runner Minutes quota for #{namespace_name} has \
-                less than #{percentage_of_available_mins}% available"
+      subject: "Action required: Less than #{percentage_of_available_mins}% " \
+               "of Pipeline minutes remain for #{namespace_name}"
     )
   end
 end
diff --git a/ee/app/mailers/previews/ci_minutes_usage_mailer_preview.rb b/ee/app/mailers/previews/ci_minutes_usage_mailer_preview.rb
new file mode 100644
index 000000000000..3d4b55379028
--- /dev/null
+++ b/ee/app/mailers/previews/ci_minutes_usage_mailer_preview.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class CiMinutesUsageMailerPreview < ActionMailer::Preview
+  def out_of_minutes
+    ::CiMinutesUsageMailer.notify('GROUP_NAME', %w(bob@example.com))
+  end
+
+  def limit_warning
+    ::CiMinutesUsageMailer.notify_limit('GROUP_NAME', %w(bob@example.com), 30)
+  end
+end
diff --git a/ee/app/services/ci/minutes/email_notification_service.rb b/ee/app/services/ci/minutes/email_notification_service.rb
new file mode 100644
index 000000000000..ded134368d0b
--- /dev/null
+++ b/ee/app/services/ci/minutes/email_notification_service.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+module Ci
+  module Minutes
+    class EmailNotificationService < ::BaseService
+      def execute
+        return unless ::Gitlab.com?
+        return unless namespace.shared_runners_minutes_limit_enabled?
+
+        notify_on_total_usage
+        notify_on_partial_usage
+      end
+
+      private
+
+      def recipients
+        namespace.user? ? [namespace.owner.email] : namespace.owners.pluck(:email) # rubocop:disable CodeReuse/ActiveRecord
+      end
+
+      def notify_on_total_usage
+        return unless namespace.shared_runners_minutes_used? && namespace.last_ci_minutes_notification_at.nil?
+
+        namespace.update_columns(last_ci_minutes_notification_at: Time.now)
+
+        CiMinutesUsageMailer.notify(namespace.name, recipients).deliver_later
+      end
+
+      def notify_on_partial_usage
+        return if namespace.shared_runners_minutes_used?
+        return if namespace.last_ci_minutes_usage_notification_level == current_alert_level
+        return if alert_levels.max < namespace.shared_runners_remaining_minutes_percent
+
+        namespace.update_columns(last_ci_minutes_usage_notification_level: current_alert_level)
+
+        CiMinutesUsageMailer.notify_limit(namespace.name, recipients, current_alert_level).deliver_later
+      end
+
+      def namespace
+        @namespace ||= project.shared_runners_limit_namespace
+      end
+
+      def alert_levels
+        @alert_levels ||= EE::Namespace::CI_USAGE_ALERT_LEVELS.sort
+      end
+
+      def current_alert_level
+        remaining_percent = namespace.shared_runners_remaining_minutes_percent
+
+        @current_alert_level ||= alert_levels.find { |level| level >= remaining_percent }
+      end
+    end
+  end
+end
diff --git a/ee/app/services/ci_minutes_usage_notify_service.rb b/ee/app/services/ci_minutes_usage_notify_service.rb
deleted file mode 100644
index 2f7c24d1cc53..000000000000
--- a/ee/app/services/ci_minutes_usage_notify_service.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-# frozen_string_literal: true
-
-class CiMinutesUsageNotifyService < BaseService
-  def execute
-    return unless ::Gitlab.com?
-    return unless namespace.shared_runners_minutes_limit_enabled?
-
-    notify_on_total_usage
-    notify_on_partial_usage
-  end
-
-  private
-
-  def recipients
-    namespace.user? ? [namespace.owner.email] : namespace.owners.pluck(:email) # rubocop:disable CodeReuse/ActiveRecord
-  end
-
-  def notify_on_total_usage
-    return unless namespace.shared_runners_minutes_used? && namespace.last_ci_minutes_notification_at.nil?
-
-    namespace.update_columns(last_ci_minutes_notification_at: Time.now)
-
-    CiMinutesUsageMailer.notify(namespace.name, recipients).deliver_later
-  end
-
-  def notify_on_partial_usage
-    return if namespace.shared_runners_minutes_used?
-    return if namespace.last_ci_minutes_usage_notification_level == current_alert_level
-    return if alert_levels.max < namespace.shared_runners_remaining_minutes_percent
-
-    namespace.update_columns(last_ci_minutes_usage_notification_level: current_alert_level)
-
-    CiMinutesUsageMailer.notify_limit(namespace.name, recipients, current_alert_level).deliver_later
-  end
-
-  def namespace
-    @namespace ||= project.shared_runners_limit_namespace
-  end
-
-  def alert_levels
-    @alert_levels ||= EE::Namespace::CI_USAGE_ALERT_LEVELS.sort
-  end
-
-  def current_alert_level
-    remaining_percent = namespace.shared_runners_remaining_minutes_percent
-
-    @current_alert_level ||= alert_levels.find { |level| level >= remaining_percent }
-  end
-end
diff --git a/ee/app/views/ci_minutes_usage_mailer/notify.html.haml b/ee/app/views/ci_minutes_usage_mailer/notify.html.haml
index d5fd78c904ab..e65c5c323e61 100644
--- a/ee/app/views/ci_minutes_usage_mailer/notify.html.haml
+++ b/ee/app/views/ci_minutes_usage_mailer/notify.html.haml
@@ -1,6 +1,6 @@
 %p
-  This is an automated notification to let you know that your CI Runner Minutes quota for "#{@namespace_name}" has run out.
+  = _('%{name} has run out of Shared Runner Pipeline minutes so no new jobs or pipelines in its projects will run.') % { name: @namespace_name }
 %p
-  Click #{link_to('here', EE::SUBSCRIPTIONS_PLANS_URL)} to purchase more minutes.
+  = _('We recommend that you buy more Pipeline minutes to resume normal service.')
 %p
-  If you need assistance, please contact #{link_to('GitLab support', EE::CUSTOMER_SUPPORT_URL)}.
+  #{link_to('Buy more Pipeline minutes →', EE::SUBSCRIPTIONS_MORE_MINUTES_URL)}
diff --git a/ee/app/views/ci_minutes_usage_mailer/notify.text.erb b/ee/app/views/ci_minutes_usage_mailer/notify.text.erb
index 9cd3ccabee62..a64cdce76449 100644
--- a/ee/app/views/ci_minutes_usage_mailer/notify.text.erb
+++ b/ee/app/views/ci_minutes_usage_mailer/notify.text.erb
@@ -1,5 +1,6 @@
-This is an automated notification to let you know that your CI Runner Minutes quota for "<%= @namespace_name %>" has run out.
+<%= _('%{name} has run out of Shared Runner Pipeline minutes so no new jobs or pipelines in its projects will run.') % { name: @namespace_name } %>
 
-Please visit <%= EE::SUBSCRIPTIONS_PLANS_URL %> to purchase more minutes.
+<%= _('We recommend that you buy more Pipeline minutes to resume normal service.') %>
 
-If you need assistance, please contact GitLab support (<%= EE::CUSTOMER_SUPPORT_URL %>).
+
+Buy more Pipeline minutes → <%= EE::SUBSCRIPTIONS_MORE_MINUTES_URL %>
diff --git a/ee/app/views/ci_minutes_usage_mailer/notify_limit.html.haml b/ee/app/views/ci_minutes_usage_mailer/notify_limit.html.haml
index 96628d838dc6..201762bb8212 100644
--- a/ee/app/views/ci_minutes_usage_mailer/notify_limit.html.haml
+++ b/ee/app/views/ci_minutes_usage_mailer/notify_limit.html.haml
@@ -1,6 +1,6 @@
 %p
-  This is an automated notification to let you know that your CI Runner Minutes quota for "#{@namespace_name}" is below #{@percentage_of_available_mins}%.
+  = _('%{name} has %{percent} or less Shared Runner Pipeline minutes remaining. Once it runs out, no new jobs or pipelines in its projects will run.') % { name: @namespace_name, percent: "#{@percentage_of_available_mins}%" }
 %p
-  Click #{link_to('here', EE::SUBSCRIPTIONS_PLANS_URL)} to purchase more minutes.
+  = _('We recommend that you buy more Pipeline minutes to avoid any interruption of service.')
 %p
-  If you need assistance, please contact #{link_to('GitLab support', EE::CUSTOMER_SUPPORT_URL)}.
+  #{link_to('Buy more Pipeline minutes →', EE::SUBSCRIPTIONS_MORE_MINUTES_URL)}
diff --git a/ee/app/views/ci_minutes_usage_mailer/notify_limit.text.erb b/ee/app/views/ci_minutes_usage_mailer/notify_limit.text.erb
index eaf96243a741..0223c26d2853 100644
--- a/ee/app/views/ci_minutes_usage_mailer/notify_limit.text.erb
+++ b/ee/app/views/ci_minutes_usage_mailer/notify_limit.text.erb
@@ -1,6 +1,5 @@
-This is an automated notification to let you know that your CI Runner Minutes
-quota for "<%= @namespace_name %>" is below <%= @percentage_of_available_mins %>%.
+<%= _('%{name} has %{percent} or less Shared Runner Pipeline minutes remaining. Once it runs out, no new jobs or pipelines in its projects will run.') % { name: @namespace_name, percent: "#{@percentage_of_available_mins}%" } %>
 
-Please visit <%= EE::SUBSCRIPTIONS_PLANS_URL %> to purchase more minutes.
+<%= _('We recommend that you buy more Pipeline minutes to avoid any interruption of service.') %>
 
-If you need assistance, please contact GitLab support (<%= EE::CUSTOMER_SUPPORT_URL %>).
+Buy more Pipeline minutes → <%= EE::SUBSCRIPTIONS_MORE_MINUTES_URL %>
diff --git a/ee/app/workers/ee/build_finished_worker.rb b/ee/app/workers/ee/build_finished_worker.rb
index ff75f4735fe1..546a3a3596cd 100644
--- a/ee/app/workers/ee/build_finished_worker.rb
+++ b/ee/app/workers/ee/build_finished_worker.rb
@@ -6,7 +6,7 @@ def process_build(build)
       UpdateBuildMinutesService.new(build.project, nil).execute(build)
       # We need to use `reset` on `project` because their AR associations have been cached
       # and `Namespace#namespace_statistics` will return stale data.
-      CiMinutesUsageNotifyService.new(build.project.reset).execute
+      ::Ci::Minutes::EmailNotificationService.new(build.project.reset).execute
 
       StoreSecurityScansWorker.perform_async(build.id)
 
diff --git a/ee/changelogs/unreleased/214997-improve-the-you-re-running-out-of-ci-minutes-email-notification.yml b/ee/changelogs/unreleased/214997-improve-the-you-re-running-out-of-ci-minutes-email-notification.yml
new file mode 100644
index 000000000000..6c646c6c6662
--- /dev/null
+++ b/ee/changelogs/unreleased/214997-improve-the-you-re-running-out-of-ci-minutes-email-notification.yml
@@ -0,0 +1,5 @@
+---
+title: Improve the running out of CI minutes email notification
+merge_request: 30188
+author:
+type: changed
diff --git a/ee/spec/mailers/ci_minutes_usage_mailer_spec.rb b/ee/spec/mailers/ci_minutes_usage_mailer_spec.rb
new file mode 100644
index 000000000000..80b6327f5385
--- /dev/null
+++ b/ee/spec/mailers/ci_minutes_usage_mailer_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe CiMinutesUsageMailer do
+  include EmailSpec::Matchers
+
+  let(:namespace_name) { 'GROUP_NAME' }
+  let(:recipients) { %w(bob@example.com john@example.com) }
+
+  shared_examples 'mail format' do
+    it { is_expected.to have_subject subject_text }
+    it { is_expected.to bcc_to recipients }
+    it { is_expected.to have_body_text body_text }
+  end
+
+  describe '#notify' do
+    it_behaves_like 'mail format' do
+      let(:subject_text) do
+        "Action required: There are no remaining Pipeline minutes for #{namespace_name}"
+      end
+
+      let(:body_text) { "#{namespace_name} has run out of Shared Runner Pipeline minutes" }
+
+      subject { described_class.notify(namespace_name, recipients) }
+    end
+  end
+
+  describe '#notify_limit' do
+    it_behaves_like 'mail format' do
+      let(:percent) { 30 }
+      let(:subject_text) do
+        "Action required: Less than #{percent}% of Pipeline minutes remain for #{namespace_name}"
+      end
+
+      let(:body_text) { "#{namespace_name} has #{percent}% or less Shared Runner Pipeline minutes" }
+
+      subject { described_class.notify_limit(namespace_name, recipients, percent) }
+    end
+  end
+end
diff --git a/ee/spec/services/ci_minutes_usage_notify_service_spec.rb b/ee/spec/services/ci/minutes/email_notification_service_spec.rb
similarity index 99%
rename from ee/spec/services/ci_minutes_usage_notify_service_spec.rb
rename to ee/spec/services/ci/minutes/email_notification_service_spec.rb
index f2254ce6177c..b80ed8151865 100644
--- a/ee/spec/services/ci_minutes_usage_notify_service_spec.rb
+++ b/ee/spec/services/ci/minutes/email_notification_service_spec.rb
@@ -2,7 +2,7 @@
 
 require 'spec_helper'
 
-describe CiMinutesUsageNotifyService do
+describe Ci::Minutes::EmailNotificationService do
   shared_examples 'namespace with available CI minutes' do
     context 'when usage is below the quote' do
       it 'does not send the email' do
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 0e227370741d..270a8866e5fc 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -393,6 +393,12 @@ msgstr ""
 msgid "%{name} found %{resultsString}"
 msgstr ""
 
+msgid "%{name} has %{percent} or less Shared Runner Pipeline minutes remaining. Once it runs out, no new jobs or pipelines in its projects will run."
+msgstr ""
+
+msgid "%{name} has run out of Shared Runner Pipeline minutes so no new jobs or pipelines in its projects will run."
+msgstr ""
+
 msgid "%{name} is scheduled for %{action}"
 msgstr ""
 
@@ -23887,6 +23893,12 @@ msgstr ""
 msgid "We heard back from your U2F device. You have been authenticated."
 msgstr ""
 
+msgid "We recommend that you buy more Pipeline minutes to avoid any interruption of service."
+msgstr ""
+
+msgid "We recommend that you buy more Pipeline minutes to resume normal service."
+msgstr ""
+
 msgid "We sent you an email with reset password instructions"
 msgstr ""
 
-- 
GitLab