Commit 9cfa5259 authored by Paul Slaughter's avatar Paul Slaughter

Merge branch '214997-improve-the-you-re-running-out-of-ci-minutes-email-notification' into 'master'

Improve the running out of CI minutes email notification

Closes #214997

See merge request gitlab-org/gitlab!30188
parents 388b143b e5ee46da
# frozen_string_literal: true # frozen_string_literal: true
class CiMinutesUsageMailer < ApplicationMailer class CiMinutesUsageMailer < ApplicationMailer
helper EmailsHelper
layout 'mailer'
def notify(namespace_name, recipients) def notify(namespace_name, recipients)
@namespace_name = namespace_name @namespace_name = namespace_name
mail( mail(
bcc: recipients, 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 end
...@@ -16,8 +20,8 @@ class CiMinutesUsageMailer < ApplicationMailer ...@@ -16,8 +20,8 @@ class CiMinutesUsageMailer < ApplicationMailer
mail( mail(
bcc: recipients, bcc: recipients,
subject: "GitLab CI Runner Minutes quota for #{namespace_name} has \ subject: "Action required: Less than #{percentage_of_available_mins}% " \
less than #{percentage_of_available_mins}% available" "of Pipeline minutes remain for #{namespace_name}"
) )
end end
end end
# 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
# 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
# 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
%p %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 %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 %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)}
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 %>
%p %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 %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 %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)}
This is an automated notification to let you know that your CI Runner Minutes <%= _('%{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}%" } %>
quota for "<%= @namespace_name %>" is below <%= @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 %>
...@@ -6,7 +6,7 @@ module EE ...@@ -6,7 +6,7 @@ module EE
UpdateBuildMinutesService.new(build.project, nil).execute(build) UpdateBuildMinutesService.new(build.project, nil).execute(build)
# We need to use `reset` on `project` because their AR associations have been cached # We need to use `reset` on `project` because their AR associations have been cached
# and `Namespace#namespace_statistics` will return stale data. # 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) StoreSecurityScansWorker.perform_async(build.id)
......
---
title: Improve the running out of CI minutes email notification
merge_request: 30188
author:
type: changed
# 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
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
describe CiMinutesUsageNotifyService do describe Ci::Minutes::EmailNotificationService do
shared_examples 'namespace with available CI minutes' do shared_examples 'namespace with available CI minutes' do
context 'when usage is below the quote' do context 'when usage is below the quote' do
it 'does not send the email' do it 'does not send the email' do
......
...@@ -393,6 +393,12 @@ msgstr "" ...@@ -393,6 +393,12 @@ msgstr ""
msgid "%{name} found %{resultsString}" msgid "%{name} found %{resultsString}"
msgstr "" 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}" msgid "%{name} is scheduled for %{action}"
msgstr "" msgstr ""
...@@ -24007,6 +24013,12 @@ msgstr "" ...@@ -24007,6 +24013,12 @@ msgstr ""
msgid "We heard back from your U2F device. You have been authenticated." msgid "We heard back from your U2F device. You have been authenticated."
msgstr "" 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" msgid "We sent you an email with reset password instructions"
msgstr "" 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