Commit aafd5672 authored by Doug Stull's avatar Doug Stull

Merge branch 'reset-ci-minutes-monthly-notifications' into 'master'

Reset CI minutes notifications for new monthly tracking

See merge request gitlab-org/gitlab!69063
parents 7002b287 771646f4
...@@ -9,7 +9,7 @@ module EE ...@@ -9,7 +9,7 @@ module EE
def reset_runners_minutes def reset_runners_minutes
group group
if ClearNamespaceSharedRunnersMinutesService.new(@group).execute if ::Ci::Minutes::ResetUsageService.new(@group).execute
redirect_to [:admin, @group], notice: _('Group pipeline minutes were successfully reset.') redirect_to [:admin, @group], notice: _('Group pipeline minutes were successfully reset.')
else else
flash.now[:error] = _('There was an error resetting group pipeline minutes.') flash.now[:error] = _('There was an error resetting group pipeline minutes.')
......
...@@ -9,7 +9,7 @@ module EE ...@@ -9,7 +9,7 @@ module EE
def reset_runners_minutes def reset_runners_minutes
user user
if ClearNamespaceSharedRunnersMinutesService.new(@user.namespace).execute if ::Ci::Minutes::ResetUsageService.new(@user.namespace).execute
redirect_to [:admin, @user], notice: _('User pipeline minutes were successfully reset.') redirect_to [:admin, @user], notice: _('User pipeline minutes were successfully reset.')
else else
flash.now[:error] = _('There was an error resetting user pipeline minutes.') flash.now[:error] = _('There was an error resetting user pipeline minutes.')
......
...@@ -36,8 +36,21 @@ module Ci ...@@ -36,8 +36,21 @@ module Ci
update_counters(usage, amount_used: amount) update_counters(usage, amount_used: amount)
end end
def self.reset_current_usage(namespace)
update_current(namespace, amount_used: 0, notification_level: Notification::PERCENTAGES.fetch(:not_set))
end
def self.reset_current_notification_level(namespace)
update_current(namespace, notification_level: Notification::PERCENTAGES.fetch(:not_set))
end
def self.update_current(namespace, attributes)
current_month.for_namespace(namespace).update_all(attributes)
end
private_class_method :update_current
def total_usage_notified? def total_usage_notified?
usage_notified?(0) usage_notified?(Notification::PERCENTAGES.fetch(:exceeded))
end end
# Notification_level is set to 100 (meaning 100% remaining minutes) by default. # Notification_level is set to 100 (meaning 100% remaining minutes) by default.
......
...@@ -4,6 +4,7 @@ module Ci ...@@ -4,6 +4,7 @@ module Ci
module Minutes module Minutes
class Notification class Notification
PERCENTAGES = { PERCENTAGES = {
not_set: 100,
warning: 30, warning: 30,
danger: 5, danger: 5,
exceeded: 0 exceeded: 0
......
# frozen_string_literal: true
module Ci
module Minutes
class ResetUsageService < BaseService
def initialize(namespace)
@namespace = namespace
end
def execute
Ci::Minutes::NamespaceMonthlyUsage.reset_current_usage(@namespace)
reset_legacy_usage
::Ci::Minutes::RefreshCachedDataService.new(@namespace).execute
true
end
private
# rubocop: disable CodeReuse/ActiveRecord
def reset_legacy_usage
NamespaceStatistics.where(namespace: @namespace).update_all(
shared_runners_seconds: 0,
shared_runners_seconds_last_reset: Time.current)
end
# rubocop: enable CodeReuse/ActiveRecord
end
end
end
# frozen_string_literal: true
class ClearNamespaceSharedRunnersMinutesService < BaseService
def initialize(namespace)
@namespace = namespace
end
# rubocop: disable CodeReuse/ActiveRecord
def execute
NamespaceStatistics.where(namespace: @namespace).update_all(
shared_runners_seconds: 0,
shared_runners_seconds_last_reset: Time.current
).tap do
::Ci::Minutes::RefreshCachedDataService.new(@namespace).execute
end
end
# rubocop: enable CodeReuse/ActiveRecord
end
...@@ -26,12 +26,14 @@ module EE ...@@ -26,12 +26,14 @@ module EE
if params[:extra_shared_runners_minutes_limit].present? if params[:extra_shared_runners_minutes_limit].present?
update_attrs[:last_ci_minutes_notification_at] = nil update_attrs[:last_ci_minutes_notification_at] = nil
update_attrs[:last_ci_minutes_usage_notification_level] = nil update_attrs[:last_ci_minutes_usage_notification_level] = nil
::Ci::Runner.instance_type.each(&:tick_runner_queue) ::Ci::Runner.instance_type.each(&:tick_runner_queue)
end end
namespace.update(update_attrs).tap do namespace.update(update_attrs).tap do
if update_attrs[:extra_shared_runners_minutes_limit].present? || update_attrs[:shared_runners_minutes_limit].present? if update_attrs[:extra_shared_runners_minutes_limit].present? || update_attrs[:shared_runners_minutes_limit].present?
::Ci::Minutes::RefreshCachedDataService.new(namespace).execute ::Ci::Minutes::RefreshCachedDataService.new(namespace).execute
::Ci::Minutes::NamespaceMonthlyUsage.reset_current_notification_level(namespace)
end end
end end
end end
......
...@@ -14,7 +14,7 @@ RSpec.describe Admin::GroupsController do ...@@ -14,7 +14,7 @@ RSpec.describe Admin::GroupsController do
subject { post :reset_runners_minutes, params: { id: group } } subject { post :reset_runners_minutes, params: { id: group } }
before do before do
allow_next_instance_of(ClearNamespaceSharedRunnersMinutesService) do |instance| allow_next_instance_of(Ci::Minutes::ResetUsageService) do |instance|
allow(instance).to receive(:execute).and_return(clear_runners_minutes_service_result) allow(instance).to receive(:execute).and_return(clear_runners_minutes_service_result)
end end
end end
......
...@@ -64,7 +64,7 @@ RSpec.describe Admin::UsersController do ...@@ -64,7 +64,7 @@ RSpec.describe Admin::UsersController do
subject { post :reset_runners_minutes, params: { id: user } } subject { post :reset_runners_minutes, params: { id: user } }
before do before do
allow_next_instance_of(ClearNamespaceSharedRunnersMinutesService) do |instance| allow_next_instance_of(Ci::Minutes::ResetUsageService) do |instance|
allow(instance).to receive(:execute).and_return(clear_runners_minutes_service_result) allow(instance).to receive(:execute).and_return(clear_runners_minutes_service_result)
end end
end end
......
...@@ -6,4 +6,8 @@ FactoryBot.define do ...@@ -6,4 +6,8 @@ FactoryBot.define do
namespace factory: :namespace namespace factory: :namespace
date { Time.current.utc.beginning_of_month } date { Time.current.utc.beginning_of_month }
end end
trait :with_warning_notification_level do
notification_level { ::Ci::Minutes::Notification::PERCENTAGES.fetch(:warning) }
end
end end
...@@ -35,7 +35,7 @@ RSpec.describe 'Reset namespace pipeline minutes', :js do ...@@ -35,7 +35,7 @@ RSpec.describe 'Reset namespace pipeline minutes', :js do
shared_examples 'rendering error' do shared_examples 'rendering error' do
context 'when resetting pipeline minutes fails' do context 'when resetting pipeline minutes fails' do
before do before do
allow_any_instance_of(ClearNamespaceSharedRunnersMinutesService).to receive(:execute).and_return(false) allow_any_instance_of(Ci::Minutes::ResetUsageService).to receive(:execute).and_return(false)
end end
it 'renders edit page with an error' do it 'renders edit page with an error' do
......
...@@ -5,11 +5,14 @@ require 'spec_helper' ...@@ -5,11 +5,14 @@ require 'spec_helper'
RSpec.describe Ci::Minutes::NamespaceMonthlyUsage do RSpec.describe Ci::Minutes::NamespaceMonthlyUsage do
let_it_be(:namespace) { create(:namespace) } let_it_be(:namespace) { create(:namespace) }
describe 'unique index' do let_it_be_with_refind(:current_usage) do
before_all do create(:ci_namespace_monthly_usage,
create(:ci_namespace_monthly_usage, namespace: namespace) :with_warning_notification_level,
namespace: namespace,
amount_used: 100)
end end
describe 'unique index' do
it 'raises unique index violation' do it 'raises unique index violation' do
expect { create(:ci_namespace_monthly_usage, namespace: namespace) } expect { create(:ci_namespace_monthly_usage, namespace: namespace) }
.to raise_error { ActiveRecord::RecordNotUnique } .to raise_error { ActiveRecord::RecordNotUnique }
...@@ -36,10 +39,13 @@ RSpec.describe Ci::Minutes::NamespaceMonthlyUsage do ...@@ -36,10 +39,13 @@ RSpec.describe Ci::Minutes::NamespaceMonthlyUsage do
end end
end end
context 'when namespace usage does not exist' do context 'when namespace usage does not exist for current month' do
it_behaves_like 'creates usage record' before do
current_usage.destroy!
end end
it_behaves_like 'creates usage record'
context 'when namespace usage exists for previous months' do context 'when namespace usage exists for previous months' do
before do before do
create(:ci_namespace_monthly_usage, namespace: namespace, date: described_class.beginning_of_month(2.months.ago)) create(:ci_namespace_monthly_usage, namespace: namespace, date: described_class.beginning_of_month(2.months.ago))
...@@ -48,27 +54,24 @@ RSpec.describe Ci::Minutes::NamespaceMonthlyUsage do ...@@ -48,27 +54,24 @@ RSpec.describe Ci::Minutes::NamespaceMonthlyUsage do
it_behaves_like 'creates usage record' it_behaves_like 'creates usage record'
end end
context 'when a usage for another namespace exists for the current month' do
let!(:usage) { create(:ci_namespace_monthly_usage) }
it_behaves_like 'creates usage record'
end
end
context 'when namespace usage exists for the current month' do context 'when namespace usage exists for the current month' do
it 'returns the existing usage' do it 'returns the existing usage' do
freeze_time do freeze_time do
usage = create(:ci_namespace_monthly_usage, namespace: namespace) expect(subject).to eq(current_usage)
expect(subject).to eq(usage)
end end
end end
end end
context 'when a usage for another namespace exists for the current month' do
let!(:usage) { create(:ci_namespace_monthly_usage) }
it_behaves_like 'creates usage record'
end
end end
describe '.increase_usage' do describe '.increase_usage' do
subject { described_class.increase_usage(usage, amount) } subject { described_class.increase_usage(current_usage, amount) }
let(:usage) { create(:ci_namespace_monthly_usage, namespace: namespace, amount_used: 100.0) }
context 'when amount is greater than 0' do context 'when amount is greater than 0' do
let(:amount) { 10.5 } let(:amount) { 10.5 }
...@@ -76,7 +79,7 @@ RSpec.describe Ci::Minutes::NamespaceMonthlyUsage do ...@@ -76,7 +79,7 @@ RSpec.describe Ci::Minutes::NamespaceMonthlyUsage do
it 'updates the current month usage' do it 'updates the current month usage' do
subject subject
expect(usage.reload.amount_used).to eq(110.5) expect(current_usage.reload.amount_used).to eq(110.5)
end end
end end
...@@ -86,45 +89,109 @@ RSpec.describe Ci::Minutes::NamespaceMonthlyUsage do ...@@ -86,45 +89,109 @@ RSpec.describe Ci::Minutes::NamespaceMonthlyUsage do
it 'does not update the current month usage' do it 'does not update the current month usage' do
subject subject
expect(usage.reload.amount_used).to eq(100.0) expect(current_usage.reload.amount_used).to eq(100.0)
end end
end end
end end
describe '.for_namespace' do describe '.for_namespace' do
it 'returns usages for the namespace' do it 'returns usages for the namespace' do
matching_usage = create(:ci_namespace_monthly_usage, namespace: namespace)
create(:ci_namespace_monthly_usage, namespace: create(:namespace)) create(:ci_namespace_monthly_usage, namespace: create(:namespace))
usages = described_class.for_namespace(namespace) usages = described_class.for_namespace(namespace)
expect(usages).to contain_exactly(matching_usage) expect(usages).to contain_exactly(current_usage)
end
end
describe '.reset_current_usage', :aggregate_failures do
subject { described_class.reset_current_usage(namespace) }
it 'resets current usage and notification level' do
subject
current_usage.reload
expect(current_usage.amount_used).to eq(0)
expect(current_usage.notification_level).to eq(Ci::Minutes::Notification::PERCENTAGES.fetch(:not_set))
end
it 'does not reset data from previous months' do
previous_usage = create(:ci_namespace_monthly_usage,
:with_warning_notification_level,
namespace: namespace,
date: 1.month.ago.beginning_of_month.to_date)
subject
previous_usage.reload
expect(previous_usage.amount_used).to eq(100)
expect(previous_usage.notification_level).to eq(Ci::Minutes::Notification::PERCENTAGES.fetch(:warning))
end
it 'does not reset data from other namespaces' do
another_usage = create(:ci_namespace_monthly_usage, :with_warning_notification_level)
subject
another_usage.reload
expect(another_usage.amount_used).to eq(100)
expect(another_usage.notification_level).to eq(Ci::Minutes::Notification::PERCENTAGES.fetch(:warning))
end
end
describe '.reset_current_notification_level' do
subject { described_class.reset_current_notification_level(namespace) }
it 'resets current notification level' do
expect { subject }
.to change { current_usage.reload.notification_level }
.to(Ci::Minutes::Notification::PERCENTAGES.fetch(:not_set))
end
it 'does not reset notification level from previous months' do
previous_usage = create(:ci_namespace_monthly_usage,
:with_warning_notification_level,
namespace: namespace,
date: 1.month.ago.beginning_of_month.to_date)
expect { subject }
.not_to change { previous_usage.reload.notification_level }
end
it 'does not reset notification level from other namespaces' do
another_usage = create(:ci_namespace_monthly_usage, :with_warning_notification_level)
expect { subject }
.not_to change { another_usage.reload.notification_level }
end end
end end
describe '#usage_notified?' do describe '#usage_notified?' do
let(:usage) { build(:ci_namespace_monthly_usage, notification_level: notification_level) } subject { current_usage.usage_notified?(remaining_percentage) }
let(:notification_level) { 100 }
subject { usage.usage_notified?(remaining_percentage) } before do
current_usage.update!(notification_level: 30)
end
context 'when parameter is different than notification level' do context 'when parameter is different than notification level' do
let(:remaining_percentage) { 30 } let(:remaining_percentage) { 5 }
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
end end
context 'when parameter is same as the notification level' do context 'when parameter is same as the notification level' do
let(:remaining_percentage) { notification_level } let(:remaining_percentage) { 30 }
it { is_expected.to be_truthy } it { is_expected.to be_truthy }
end end
end end
describe '#total_usage_notified?' do describe '#total_usage_notified?' do
let(:usage) { build(:ci_namespace_monthly_usage, notification_level: notification_level) } before do
current_usage.update!(notification_level: notification_level)
end
subject { usage.total_usage_notified? } subject { current_usage.total_usage_notified? }
context 'notification level is higher than zero' do context 'notification level is higher than zero' do
let(:notification_level) { 30 } let(:notification_level) { 30 }
......
...@@ -234,6 +234,17 @@ RSpec.describe API::Namespaces do ...@@ -234,6 +234,17 @@ RSpec.describe API::Namespaces do
subject subject
end end
context 'when current CI minutes notification level is set' do
let!(:usage) { create(:ci_namespace_monthly_usage, :with_warning_notification_level, namespace: group1) }
it 'resets the current CI minutes notification level' do
expect do
put api("/namespaces/#{group1.id}", admin), params: params
end.to change { usage.reload.notification_level }
.to(Ci::Minutes::Notification::PERCENTAGES.fetch(:not_set))
end
end
context 'when request has extra_shared_runners_minutes_limit param' do context 'when request has extra_shared_runners_minutes_limit param' do
before do before do
params[:extra_shared_runners_minutes_limit] = 1000 params[:extra_shared_runners_minutes_limit] = 1000
...@@ -264,6 +275,17 @@ RSpec.describe API::Namespaces do ...@@ -264,6 +275,17 @@ RSpec.describe API::Namespaces do
expect(pending_build.reload.minutes_exceeded).to eq(false) expect(pending_build.reload.minutes_exceeded).to eq(false)
end end
context 'when current CI minutes notification level is set' do
let!(:usage) { create(:ci_namespace_monthly_usage, :with_warning_notification_level, namespace: group1) }
it 'resets the current CI minutes notification level' do
expect do
put api("/namespaces/#{group1.id}", admin), params: params
end.to change { usage.reload.notification_level }
.to(Ci::Minutes::Notification::PERCENTAGES.fetch(:not_set))
end
end
end end
context 'when neither minutes limit params is provided' do context 'when neither minutes limit params is provided' do
...@@ -273,6 +295,18 @@ RSpec.describe API::Namespaces do ...@@ -273,6 +295,18 @@ RSpec.describe API::Namespaces do
subject subject
end end
context 'when current CI minutes notification level is set' do
let!(:usage) { create(:ci_namespace_monthly_usage, :with_warning_notification_level, namespace: group1) }
it 'does not reset the current CI minutes notification level' do
params.delete(:shared_runners_minutes_limit)
expect do
put api("/namespaces/#{group1.id}", admin), params: params
end.not_to change { usage.reload.notification_level }
end
end
end end
end end
......
...@@ -2,22 +2,37 @@ ...@@ -2,22 +2,37 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe ClearNamespaceSharedRunnersMinutesService do RSpec.describe Ci::Minutes::ResetUsageService do
include AfterNextHelpers include AfterNextHelpers
describe '#execute' do describe '#execute' do
subject { described_class.new(namespace).execute } subject { described_class.new(namespace).execute }
context 'when project has namespace_statistics' do context 'when project has namespace_statistics' do
let(:namespace) { create(:namespace, :with_used_build_minutes_limit) } let_it_be(:namespace) { create(:namespace, :with_used_build_minutes_limit) }
it 'clears counters' do let_it_be(:namespace_usage) do
create(:ci_namespace_monthly_usage, :with_warning_notification_level,
namespace: namespace,
amount_used: 100)
end
it 'clears the amount used and notification levels', :aggregate_failures do
subject
namespace_usage.reload
expect(namespace_usage.amount_used).to eq(0)
expect(namespace_usage.notification_level)
.to eq(Ci::Minutes::Notification::PERCENTAGES.fetch(:not_set))
end
it 'clears legacy counters' do
subject subject
expect(namespace.namespace_statistics.reload.shared_runners_seconds).to eq(0) expect(namespace.namespace_statistics.reload.shared_runners_seconds).to eq(0)
end end
it 'resets timer' do it 'resets legacy timer' do
subject subject
expect(namespace.namespace_statistics.reload.shared_runners_seconds_last_reset).to be_like_time(Time.current) expect(namespace.namespace_statistics.reload.shared_runners_seconds_last_reset).to be_like_time(Time.current)
......
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