diff --git a/ee/app/mailers/ci_minutes_usage_mailer.rb b/ee/app/mailers/ci_minutes_usage_mailer.rb index 4069ada35a36911c77dba9f3522aa292a8e8b310..51a28989968bd740988b50117e1504c25105b779 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 0000000000000000000000000000000000000000..3d4b5537902868d1ac088da23e7cc7927fdb12f1 --- /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 0000000000000000000000000000000000000000..ded134368d0b7935d5605580184a1050cd51c453 --- /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 2f7c24d1cc537023d854fb8c5782e490d5e8189e..0000000000000000000000000000000000000000 --- 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 d5fd78c904ab682e6f66f7b91087bf148ba1d403..e65c5c323e6143abca2006d0a822ebb07aaf7bf5 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 9cd3ccabee62fa8b01e759a29116ea63382aabab..a64cdce76449152c073f77f96f843c9c67204832 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 96628d838dc6e8612b0ed43cda2ac7c27c1079b6..201762bb821262774fc6b4811c3f13e84a37c306 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 eaf96243a74167a73fedfd29c9b66b2c68a1dccf..0223c26d285308042370b1bba03519b8294abc53 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 ff75f4735fe1227891177aa8b6da22990b0b1f26..546a3a3596cd767d41d9700fbeb37b8646fe067b 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 0000000000000000000000000000000000000000..6c646c6c666245c23b676eb0d3a4dec1c7cbf872 --- /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 0000000000000000000000000000000000000000..80b6327f538542cb2f86570141091afc3c24a2c6 --- /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 f2254ce6177c7d76784ecc52e3e1f472e586966c..b80ed815186552dff6d3bec8a84448e69f15d43e 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 0e227370741d100de7139e3525984c729e1d3843..270a8866e5fca025fc6c9e9a099435455d94accc 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 ""