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