Commit 7c704815 authored by Marius Bobin's avatar Marius Bobin

Merge branch 'track-ci-minutes-notification-level-independently' into 'master'

Separate CI minutes notification levels between new and legacy tracking

See merge request gitlab-org/gitlab!75512
parents f06bfa21 2e94399f
...@@ -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