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
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