Commit 2e94399f authored by Fabio Pitino's avatar Fabio Pitino Committed by Marius Bobin

Separate CI minutes notification levels between new and legacy

Track notification levels separately for new and legacy tracking.
Use the feature flag to decide which one betwee new or legacy
tracking to send out the email. Each notification level is set
idependently from the other tracking.

Changelog: changed
EE: true
parent b5848df6
......@@ -8,10 +8,11 @@ module Ci
attr_reader :level
def initialize(project, namespace)
def initialize(project, namespace, tracking_strategy: nil)
@project = project
@namespace = project&.shared_runners_limit_namespace || namespace
@level = project || namespace
@tracking_strategy = tracking_strategy
end
def percent_total_minutes_remaining
......@@ -23,7 +24,7 @@ module Ci
attr_reader :project, :namespace
def quota
@quota ||= ::Ci::Minutes::Quota.new(namespace)
@quota ||= ::Ci::Minutes::Quota.new(namespace, tracking_strategy: @tracking_strategy)
end
end
end
......
......@@ -10,8 +10,8 @@ module Ci
exceeded: 0
}.freeze
def initialize(project, namespace)
@context = Ci::Minutes::Context.new(project, namespace)
def initialize(project, namespace, tracking_strategy: nil)
@context = Ci::Minutes::Context.new(project, namespace, tracking_strategy: tracking_strategy)
@stage = calculate_notification_stage if eligible_for_notifications?
end
......
......@@ -11,9 +11,12 @@ module Ci
attr_reader :namespace, :limit
def initialize(namespace)
def initialize(namespace, tracking_strategy: nil)
@namespace = namespace
@limit = ::Ci::Minutes::Limit.new(namespace)
# TODO: remove `tracking_strategy` after `ci_use_new_monthly_minutes` feature flag
# https://gitlab.com/gitlab-org/gitlab/-/issues/341730
@tracking_strategy = tracking_strategy
end
def enabled?
......@@ -36,21 +39,19 @@ module Ci
def total_minutes_used
strong_memoize(:total_minutes_used) do
if namespace.new_monthly_ci_minutes_enabled?
current_usage.amount_used.to_i
else
namespace.namespace_statistics&.shared_runners_seconds.to_i / 60
end
conditional_value(
when_new_strategy: -> { current_usage.amount_used.to_i },
when_legacy_strategy: -> { namespace.namespace_statistics&.shared_runners_seconds.to_i / 60 }
)
end
end
def reset_date
strong_memoize(:reset_date) do
if namespace.new_monthly_ci_minutes_enabled?
current_usage.date
else
namespace.namespace_statistics&.shared_runners_seconds_last_reset
end
conditional_value(
when_new_strategy: -> { current_usage.date },
when_legacy_strategy: -> { namespace.namespace_statistics&.shared_runners_seconds_last_reset }
)
end
end
......@@ -90,6 +91,18 @@ module Ci
def total_minutes_remaining
[current_balance, 0].max
end
def conditional_value(when_new_strategy:, when_legacy_strategy:)
if @tracking_strategy == :new
when_new_strategy.call
elsif @tracking_strategy == :legacy
when_legacy_strategy.call
elsif namespace.new_monthly_ci_minutes_enabled?
when_new_strategy.call
else
when_legacy_strategy.call
end
end
end
end
end
......@@ -3,58 +3,69 @@
module Ci
module Minutes
class EmailNotificationService < ::BaseService
include Gitlab::Utils::StrongMemoize
def execute
return unless notification.eligible_for_notifications?
legacy_notify
notify
end
private
# We use 2 notification objects for new and legacy tracking side-by-side.
# We read and write data to each tracking using the respective data but we alert
# only based on the currently active tracking.
def notification
@notification ||= ::Ci::Minutes::Notification.new(project, nil)
@notification ||= ::Ci::Minutes::Notification.new(project, nil, tracking_strategy: :new)
end
def legacy_notification
@legacy_notification ||= ::Ci::Minutes::Notification.new(project, nil, tracking_strategy: :legacy)
end
def notify
if notification.no_remaining_minutes?
notify_total_usage
return if namespace_usage.total_usage_notified?
namespace_usage.update!(notification_level: current_alert_percentage)
if ci_minutes_use_notification_level?
CiMinutesUsageMailer.notify(namespace, recipients).deliver_later
end
elsif notification.running_out?
notify_partial_usage
return if namespace_usage.usage_notified?(current_alert_percentage)
namespace_usage.update!(notification_level: current_alert_percentage)
if ci_minutes_use_notification_level?
CiMinutesUsageMailer.notify_limit(namespace, recipients, current_alert_percentage).deliver_later
end
end
end
def notify_total_usage
# TODO: Enable the FF on the month after this is released.
# https://gitlab.com/gitlab-org/gitlab/-/issues/339324
if Feature.enabled?(:ci_minutes_use_notification_level, namespace, default_enabled: :yaml)
return if namespace_usage.total_usage_notified?
else
def legacy_notify
if legacy_notification.no_remaining_minutes?
return if namespace.last_ci_minutes_notification_at
end
legacy_track_total_usage
namespace_usage.update!(notification_level: current_alert_percentage)
CiMinutesUsageMailer.notify(namespace, recipients).deliver_later
end
namespace.update_columns(last_ci_minutes_notification_at: Time.current)
def notify_partial_usage
# TODO: Enable the FF on the month after this is released.
# https://gitlab.com/gitlab-org/gitlab/-/issues/339324
if Feature.enabled?(:ci_minutes_use_notification_level, namespace, default_enabled: :yaml)
return if namespace_usage.usage_notified?(current_alert_percentage)
else
return if already_notified_running_out
end
unless ci_minutes_use_notification_level?
CiMinutesUsageMailer.notify(namespace, recipients).deliver_later
end
elsif legacy_notification.running_out?
current_alert_percentage = legacy_notification.stage_percentage
legacy_track_partial_usage
namespace_usage.update!(notification_level: current_alert_percentage)
# exit if we have already sent a notification for the same level
return if namespace.last_ci_minutes_usage_notification_level == current_alert_percentage
CiMinutesUsageMailer.notify_limit(namespace, recipients, current_alert_percentage).deliver_later
end
namespace.update_columns(last_ci_minutes_usage_notification_level: current_alert_percentage)
def already_notified_running_out
namespace.last_ci_minutes_usage_notification_level == current_alert_percentage
unless ci_minutes_use_notification_level?
CiMinutesUsageMailer.notify_limit(namespace, recipients, current_alert_percentage).deliver_later
end
end
end
def recipients
......@@ -74,14 +85,10 @@ module Ci
notification.stage_percentage
end
# TODO: delete this method after full rollout of ci_minutes_use_notification_level Feature Flag
def legacy_track_total_usage
namespace.update_columns(last_ci_minutes_notification_at: Time.current)
end
# TODO: delete this method after full rollout of ci_minutes_use_notification_level Feature Flag
def legacy_track_partial_usage
namespace.update_columns(last_ci_minutes_usage_notification_level: current_alert_percentage)
def ci_minutes_use_notification_level?
strong_memoize(:ci_minutes_use_notification_level) do
Feature.enabled?(:ci_minutes_use_notification_level, namespace, default_enabled: :yaml)
end
end
end
end
......
......@@ -92,6 +92,8 @@ RSpec.describe Ci::Minutes::Quota do
end
describe '#total_minutes_used' do
let(:namespace) { create(:namespace, :with_ci_minutes, ci_minutes_used: minutes_used) }
subject { quota.total_minutes_used }
where(:minutes_used, :expected_minutes) do
......@@ -103,10 +105,30 @@ RSpec.describe Ci::Minutes::Quota do
end
with_them do
let(:namespace) { create(:namespace, :with_ci_minutes, ci_minutes_used: minutes_used) }
it { is_expected.to eq(expected_minutes) }
end
context 'with tracking_strategy' do
where(:minutes_used, :legacy_minutes_used, :tracking_strategy, :ff_enabled, :expected_minutes) do
0 | 100 | nil | true | 0
0 | 100 | nil | false | 100
0 | 100 | :new | true | 0
0 | 100 | :new | false | 0
0 | 100 | :legacy | true | 100
0 | 100 | :legacy | false | 100
end
with_them do
let(:quota) { described_class.new(namespace, tracking_strategy: tracking_strategy) }
before do
stub_feature_flags(ci_use_new_monthly_minutes: ff_enabled)
namespace.namespace_statistics.update!(shared_runners_seconds: legacy_minutes_used.minutes)
end
it { is_expected.to eq(expected_minutes) }
end
end
end
describe '#percent_total_minutes_remaining' do
......@@ -214,6 +236,9 @@ RSpec.describe Ci::Minutes::Quota do
end
describe '#reset_date' do
let(:quota) { described_class.new(namespace, tracking_strategy: tracking_strategy) }
let(:tracking_strategy) { nil }
subject(:reset_date) { quota.reset_date }
around do |example|
......@@ -228,6 +253,22 @@ RSpec.describe Ci::Minutes::Quota do
expect(reset_date).to eq(Date.new(2021, 07, 1))
end
context 'when tracking_strategy: :new' do
let(:tracking_strategy) { :new }
it 'corresponds to the beginning of the current month' do
expect(reset_date).to eq(Date.new(2021, 07, 1))
end
end
context 'when tracking_strategy: :legacy' do
let(:tracking_strategy) { :legacy }
it 'corresponds to the current time' do
expect(reset_date).to eq(Date.new(2021, 07, 14))
end
end
context 'when feature flag ci_use_new_monthly_minutes is disabled' do
before do
stub_feature_flags(ci_use_new_monthly_minutes: false)
......
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