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