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
def reset_runners_minutes
group
if ClearNamespaceSharedRunnersMinutesService.new(@group).execute
if ::Ci::Minutes::ResetUsageService.new(@group).execute
redirect_to [:admin, @group], notice: _('Group pipeline minutes were successfully reset.')
else
flash.now[:error] = _('There was an error resetting group pipeline minutes.')
......
......@@ -9,7 +9,7 @@ module EE
def reset_runners_minutes
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.')
else
flash.now[:error] = _('There was an error resetting user pipeline minutes.')
......
......@@ -36,8 +36,21 @@ module Ci
update_counters(usage, amount_used: amount)
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?
usage_notified?(0)
usage_notified?(Notification::PERCENTAGES.fetch(:exceeded))
end
# Notification_level is set to 100 (meaning 100% remaining minutes) by default.
......
......@@ -4,6 +4,7 @@ module Ci
module Minutes
class Notification
PERCENTAGES = {
not_set: 100,
warning: 30,
danger: 5,
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
if params[:extra_shared_runners_minutes_limit].present?
update_attrs[:last_ci_minutes_notification_at] = nil
update_attrs[:last_ci_minutes_usage_notification_level] = nil
::Ci::Runner.instance_type.each(&:tick_runner_queue)
end
namespace.update(update_attrs).tap do
if update_attrs[:extra_shared_runners_minutes_limit].present? || update_attrs[:shared_runners_minutes_limit].present?
::Ci::Minutes::RefreshCachedDataService.new(namespace).execute
::Ci::Minutes::NamespaceMonthlyUsage.reset_current_notification_level(namespace)
end
end
end
......
......@@ -14,7 +14,7 @@ RSpec.describe Admin::GroupsController do
subject { post :reset_runners_minutes, params: { id: group } }
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)
end
end
......
......@@ -64,7 +64,7 @@ RSpec.describe Admin::UsersController do
subject { post :reset_runners_minutes, params: { id: user } }
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)
end
end
......
......@@ -6,4 +6,8 @@ FactoryBot.define do
namespace factory: :namespace
date { Time.current.utc.beginning_of_month }
end
trait :with_warning_notification_level do
notification_level { ::Ci::Minutes::Notification::PERCENTAGES.fetch(:warning) }
end
end
......@@ -35,7 +35,7 @@ RSpec.describe 'Reset namespace pipeline minutes', :js do
shared_examples 'rendering error' do
context 'when resetting pipeline minutes fails' 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
it 'renders edit page with an error' do
......
......@@ -5,11 +5,14 @@ require 'spec_helper'
RSpec.describe Ci::Minutes::NamespaceMonthlyUsage do
let_it_be(:namespace) { create(:namespace) }
describe 'unique index' do
before_all do
create(:ci_namespace_monthly_usage, namespace: namespace)
end
let_it_be_with_refind(:current_usage) do
create(:ci_namespace_monthly_usage,
:with_warning_notification_level,
namespace: namespace,
amount_used: 100)
end
describe 'unique index' do
it 'raises unique index violation' do
expect { create(:ci_namespace_monthly_usage, namespace: namespace) }
.to raise_error { ActiveRecord::RecordNotUnique }
......@@ -36,39 +39,39 @@ RSpec.describe Ci::Minutes::NamespaceMonthlyUsage do
end
end
context 'when namespace usage does not exist' do
it_behaves_like 'creates usage record'
end
context 'when namespace usage exists for previous months' do
context 'when namespace usage does not exist for current month' do
before do
create(:ci_namespace_monthly_usage, namespace: namespace, date: described_class.beginning_of_month(2.months.ago))
current_usage.destroy!
end
it_behaves_like 'creates usage record'
context 'when namespace usage exists for previous months' do
before do
create(:ci_namespace_monthly_usage, namespace: namespace, date: described_class.beginning_of_month(2.months.ago))
end
it_behaves_like 'creates usage record'
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
it 'returns the existing usage' do
freeze_time do
usage = create(:ci_namespace_monthly_usage, namespace: namespace)
expect(subject).to eq(usage)
expect(subject).to eq(current_usage)
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
describe '.increase_usage' do
subject { described_class.increase_usage(usage, amount) }
let(:usage) { create(:ci_namespace_monthly_usage, namespace: namespace, amount_used: 100.0) }
subject { described_class.increase_usage(current_usage, amount) }
context 'when amount is greater than 0' do
let(:amount) { 10.5 }
......@@ -76,7 +79,7 @@ RSpec.describe Ci::Minutes::NamespaceMonthlyUsage do
it 'updates the current month usage' do
subject
expect(usage.reload.amount_used).to eq(110.5)
expect(current_usage.reload.amount_used).to eq(110.5)
end
end
......@@ -86,45 +89,109 @@ RSpec.describe Ci::Minutes::NamespaceMonthlyUsage do
it 'does not update the current month usage' do
subject
expect(usage.reload.amount_used).to eq(100.0)
expect(current_usage.reload.amount_used).to eq(100.0)
end
end
end
describe '.for_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))
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
describe '#usage_notified?' do
let(:usage) { build(:ci_namespace_monthly_usage, notification_level: notification_level) }
let(:notification_level) { 100 }
subject { current_usage.usage_notified?(remaining_percentage) }
subject { usage.usage_notified?(remaining_percentage) }
before do
current_usage.update!(notification_level: 30)
end
context 'when parameter is different than notification level' do
let(:remaining_percentage) { 30 }
let(:remaining_percentage) { 5 }
it { is_expected.to be_falsey }
end
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 }
end
end
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
let(:notification_level) { 30 }
......
......@@ -234,6 +234,17 @@ RSpec.describe API::Namespaces do
subject
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
before do
params[:extra_shared_runners_minutes_limit] = 1000
......@@ -264,6 +275,17 @@ RSpec.describe API::Namespaces do
expect(pending_build.reload.minutes_exceeded).to eq(false)
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
context 'when neither minutes limit params is provided' do
......@@ -273,6 +295,18 @@ RSpec.describe API::Namespaces do
subject
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
......
......@@ -2,22 +2,37 @@
require 'spec_helper'
RSpec.describe ClearNamespaceSharedRunnersMinutesService do
RSpec.describe Ci::Minutes::ResetUsageService do
include AfterNextHelpers
describe '#execute' do
subject { described_class.new(namespace).execute }
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
expect(namespace.namespace_statistics.reload.shared_runners_seconds).to eq(0)
end
it 'resets timer' do
it 'resets legacy timer' do
subject
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