Commit 1f3794ea authored by Allison Browne's avatar Allison Browne Committed by Fabio Pitino

Add Quota Presenter to separate Quota related display logic

parent 7c2fbd9e
...@@ -9,39 +9,14 @@ module Ci ...@@ -9,39 +9,14 @@ module Ci
class Quota class Quota
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
Report = Struct.new(:used, :limit, :status) attr_reader :namespace
def initialize(namespace) def initialize(namespace)
@namespace = namespace @namespace = namespace
end end
def enabled? def enabled?
namespace_root? && total_minutes.nonzero? namespace_root? && !namespace_unlimited_minutes?
end
# Status of the monthly allowance being used.
def monthly_minutes_report
Report.new(monthly_minutes_used, minutes_limit, report_status)
end
def monthly_percent_used
return 0 unless enabled?
return 0 if monthly_minutes == 0
100 * monthly_minutes_used.to_i / monthly_minutes
end
# Status of any purchased minutes used.
def purchased_minutes_report
status = purchased_minutes_used_up? ? :over_quota : :under_quota
Report.new(purchased_minutes_used, purchased_minutes, status)
end
def purchased_percent_used
return 0 unless enabled?
return 0 if purchased_minutes == 0
100 * purchased_minutes_used.to_i / purchased_minutes
end end
def minutes_used_up? def minutes_used_up?
...@@ -58,14 +33,6 @@ module Ci ...@@ -58,14 +33,6 @@ module Ci
total_minutes.to_i - total_minutes_used total_minutes.to_i - total_minutes_used
end end
def display_shared_runners_data?
namespace_root? && any_project_enabled?
end
def display_minutes_available_data?
display_shared_runners_data? && total_minutes.nonzero?
end
def total_minutes def total_minutes
strong_memoize(:total_minutes) do strong_memoize(:total_minutes) do
monthly_minutes + purchased_minutes monthly_minutes + purchased_minutes
...@@ -78,54 +45,43 @@ module Ci ...@@ -78,54 +45,43 @@ module Ci
end end
end end
def any_project_enabled? def purchased_minutes
strong_memoize(:any_project_enabled) do strong_memoize(:purchased_minutes) do
namespace.any_project_with_shared_runners_enabled? namespace.extra_shared_runners_minutes_limit.to_i
end end
end end
private def namespace_root?
strong_memoize(:namespace_root) do
attr_reader :namespace namespace.root?
def minutes_limit
return _('Not supported') unless display_shared_runners_data?
if display_minutes_available_data?
monthly_minutes
else
_('Unlimited')
end end
end end
def report_status def namespace_unlimited_minutes?
return :disabled unless enabled? total_minutes.to_i == 0
monthly_minutes_used_up? ? :over_quota : :under_quota
end end
def total_minutes_remaining def monthly_minutes
[current_balance, 0].max strong_memoize(:monthly_minutes) do
(namespace.shared_runners_minutes_limit || ::Gitlab::CurrentSettings.shared_runners_minutes).to_i
end
end end
# === private to view ===
def monthly_minutes_used_up? def monthly_minutes_used_up?
return false unless enabled? return false unless enabled?
monthly_minutes_used >= monthly_minutes monthly_minutes_used >= monthly_minutes
end end
def purchased_minutes_used_up?
return false unless enabled?
any_minutes_purchased? && purchased_minutes_used >= purchased_minutes
end
def monthly_minutes_used def monthly_minutes_used
total_minutes_used - purchased_minutes_used total_minutes_used - purchased_minutes_used
end end
def monthly_minutes_available? def purchased_minutes_used_up?
total_minutes_used <= monthly_minutes return false unless enabled?
any_minutes_purchased? && purchased_minutes_used >= purchased_minutes
end end
def purchased_minutes_used def purchased_minutes_used
...@@ -134,6 +90,12 @@ module Ci ...@@ -134,6 +90,12 @@ module Ci
total_minutes_used - monthly_minutes total_minutes_used - monthly_minutes
end end
private
def monthly_minutes_available?
total_minutes_used <= monthly_minutes
end
def no_minutes_purchased? def no_minutes_purchased?
purchased_minutes == 0 purchased_minutes == 0
end end
...@@ -142,22 +104,9 @@ module Ci ...@@ -142,22 +104,9 @@ module Ci
purchased_minutes > 0 purchased_minutes > 0
end end
def monthly_minutes # === private to model ===
strong_memoize(:monthly_minutes) do def total_minutes_remaining
(namespace.shared_runners_minutes_limit || ::Gitlab::CurrentSettings.shared_runners_minutes).to_i [current_balance, 0].max
end
end
def purchased_minutes
strong_memoize(:purchased_minutes) do
namespace.extra_shared_runners_minutes_limit.to_i
end
end
def namespace_root?
strong_memoize(:namespace_root) do
namespace.root?
end
end end
end end
end end
......
# frozen_string_literal: true
module Ci
module Minutes
class QuotaPresenter < Gitlab::View::Presenter::Simple
include Gitlab::Utils::StrongMemoize
presents Quota, as: :quota
Report = Struct.new(:used, :limit, :status)
# Status of the monthly allowance being used.
def monthly_minutes_report
Report.new(quota.monthly_minutes_used, minutes_limit, report_status)
end
def monthly_percent_used
return 0 unless quota.enabled?
return 0 if quota.monthly_minutes == 0
100 * quota.monthly_minutes_used.to_i / quota.monthly_minutes
end
# Status of any purchased minutes used.
def purchased_minutes_report
status = quota.purchased_minutes_used_up? ? :over_quota : :under_quota
Report.new(quota.purchased_minutes_used, quota.purchased_minutes, status)
end
def purchased_percent_used
return 0 unless quota.enabled?
return 0 if quota.purchased_minutes == 0
100 * quota.purchased_minutes_used.to_i / quota.purchased_minutes
end
def display_minutes_available_data?
display_shared_runners_data? && !quota.namespace_unlimited_minutes?
end
def display_shared_runners_data?
quota.namespace_root? && any_project_enabled?
end
def any_project_enabled?
strong_memoize(:any_project_enabled) do
quota.namespace.any_project_with_shared_runners_enabled?
end
end
private
def report_status
return :disabled unless quota.enabled?
quota.monthly_minutes_used_up? ? :over_quota : :under_quota
end
def minutes_limit
return _('Not supported') unless display_shared_runners_data?
if display_minutes_available_data?
quota.monthly_minutes
else
_('Unlimited')
end
end
end
end
end
...@@ -2,11 +2,12 @@ ...@@ -2,11 +2,12 @@
- namespace = local_assigns.fetch(:namespace) - namespace = local_assigns.fetch(:namespace)
- minutes_quota = namespace.ci_minutes_quota - minutes_quota = namespace.ci_minutes_quota
- minutes_quota_presenter = Ci::Minutes::QuotaPresenter.new(minutes_quota)
- return unless minutes_quota.enabled? - return unless minutes_quota.enabled?
- if minutes_quota.display_shared_runners_data? - if minutes_quota_presenter.display_shared_runners_data?
%li %li
%span.light= _('Additional minutes:') %span.light= _('Additional minutes:')
%strong %strong
= ci_minutes_report(minutes_quota.purchased_minutes_report) = ci_minutes_report(minutes_quota_presenter.purchased_minutes_report)
- namespace = local_assigns.fetch(:namespace) - namespace = local_assigns.fetch(:namespace)
- minutes_quota = namespace.ci_minutes_quota - minutes_quota = namespace.ci_minutes_quota
- minutes_quota_presenter = Ci::Minutes::QuotaPresenter.new(minutes_quota)
- if minutes_quota.display_shared_runners_data? - if minutes_quota_presenter.display_shared_runners_data?
%li %li
%span.light= _('Pipeline minutes quota:') %span.light= _('Pipeline minutes quota:')
%strong %strong
= ci_minutes_report(minutes_quota.monthly_minutes_report) = ci_minutes_report(minutes_quota_presenter.monthly_minutes_report)
= link_to sprite_icon('question-o'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'shared-runners-pipeline-minutes-quota'), target: '_blank' = link_to sprite_icon('question-o'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'shared-runners-pipeline-minutes-quota'), target: '_blank'
- return unless Gitlab.com? - return unless Gitlab.com?
- minutes_quota = namespace.ci_minutes_quota - minutes_quota = namespace.ci_minutes_quota
- return unless minutes_quota.display_minutes_available_data? && minutes_quota.purchased_minutes_report.limit > 0 - minutes_quota_presenter = Ci::Minutes::QuotaPresenter.new(minutes_quota)
- return unless minutes_quota_presenter.display_minutes_available_data? && minutes_quota_presenter.purchased_minutes_report.limit > 0
.row .row
.col-sm-6 .col-sm-6
%strong %strong
= _("Additional minutes") = _("Additional minutes")
%div %div
= ci_minutes_report(minutes_quota.purchased_minutes_report) = ci_minutes_report(minutes_quota_presenter.purchased_minutes_report)
minutes minutes
= link_to sprite_icon('question-o'), help_page_path('subscriptions/gitlab_com/index', anchor: 'purchase-additional-ci-minutes'), target: '_blank', rel: 'noopener noreferrer' = link_to sprite_icon('question-o'), help_page_path('subscriptions/gitlab_com/index', anchor: 'purchase-additional-ci-minutes'), target: '_blank', rel: 'noopener noreferrer'
.col-sm-6.right .col-sm-6.right
#{minutes_quota.purchased_percent_used}% used #{minutes_quota_presenter.purchased_percent_used}% used
= ci_minutes_progress_bar(minutes_quota.purchased_percent_used) = ci_minutes_progress_bar(minutes_quota_presenter.purchased_percent_used)
- namespace = locals.fetch(:namespace) - namespace = locals.fetch(:namespace)
- projects = locals.fetch(:projects) - projects = locals.fetch(:projects)
- minutes_quota = namespace.ci_minutes_quota - minutes_quota = namespace.ci_minutes_quota
- minutes_quota_presenter = Ci::Minutes::QuotaPresenter.new(minutes_quota)
.pipeline-quota.container-fluid .pipeline-quota.container-fluid
.row .row
...@@ -21,19 +23,19 @@ ...@@ -21,19 +23,19 @@
- else - else
= s_('UsageQuota|Current period usage') = s_('UsageQuota|Current period usage')
%div %div
= ci_minutes_report(minutes_quota.monthly_minutes_report) = ci_minutes_report(minutes_quota_presenter.monthly_minutes_report)
minutes minutes
= link_to sprite_icon('question-o'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'shared-runners-pipeline-minutes-quota'), target: '_blank', 'aria-label': _('Shared runners help link') = link_to sprite_icon('question-o'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'shared-runners-pipeline-minutes-quota'), target: '_blank', 'aria-label': _('Shared runners help link')
.col-sm-6.right .col-sm-6.right
- if minutes_quota.display_minutes_available_data? - if minutes_quota_presenter.display_minutes_available_data?
#{minutes_quota.monthly_percent_used}% used #{minutes_quota_presenter.monthly_percent_used}% used
- elsif !minutes_quota.any_project_enabled? - elsif !minutes_quota_presenter.any_project_enabled?
0% used 0% used
- else - else
= s_('UsageQuota|Unlimited') = s_('UsageQuota|Unlimited')
= ci_minutes_progress_bar(minutes_quota.monthly_percent_used) = ci_minutes_progress_bar(minutes_quota_presenter.monthly_percent_used)
= render 'namespaces/pipelines_quota/extra_shared_runners_minutes_quota', namespace: namespace = render 'namespaces/pipelines_quota/extra_shared_runners_minutes_quota', namespace: namespace
...@@ -48,7 +50,7 @@ ...@@ -48,7 +50,7 @@
= _('Minutes') = _('Minutes')
%tbody %tbody
- if !minutes_quota.any_project_enabled? - if !minutes_quota_presenter.any_project_enabled?
%tr %tr
%td{ colspan: 2 } %td{ colspan: 2 }
.nothing-here-block .nothing-here-block
......
...@@ -51,9 +51,10 @@ RSpec.describe EE::NamespacesHelper do ...@@ -51,9 +51,10 @@ RSpec.describe EE::NamespacesHelper do
describe '#ci_minutes_report' do describe '#ci_minutes_report' do
let(:quota) { Ci::Minutes::Quota.new(user_group) } let(:quota) { Ci::Minutes::Quota.new(user_group) }
let(:quota_presenter) { Ci::Minutes::QuotaPresenter.new(quota) }
describe 'rendering monthly minutes report' do describe 'rendering monthly minutes report' do
let(:report) { quota.monthly_minutes_report } let(:report) { quota_presenter.monthly_minutes_report }
context "when ci minutes quota is not enabled" do context "when ci minutes quota is not enabled" do
before do before do
...@@ -62,7 +63,7 @@ RSpec.describe EE::NamespacesHelper do ...@@ -62,7 +63,7 @@ RSpec.describe EE::NamespacesHelper do
context 'and the namespace is eligible for unlimited' do context 'and the namespace is eligible for unlimited' do
before do before do
allow(quota).to receive(:namespace_eligible?).and_return(true) allow(quota).to receive(:namespace_root?).and_return(true)
allow(user_group).to receive(:any_project_with_shared_runners_enabled?).and_return(true) allow(user_group).to receive(:any_project_with_shared_runners_enabled?).and_return(true)
end end
...@@ -79,7 +80,7 @@ RSpec.describe EE::NamespacesHelper do ...@@ -79,7 +80,7 @@ RSpec.describe EE::NamespacesHelper do
context 'and the namespace is not eligible for unlimited' do context 'and the namespace is not eligible for unlimited' do
before do before do
allow(quota).to receive(:namespace_eligible?).and_return(false) allow(quota).to receive(:namespace_root?).and_return(false)
end end
it 'returns Not supported for the limit section' do it 'returns Not supported for the limit section' do
...@@ -103,7 +104,7 @@ RSpec.describe EE::NamespacesHelper do ...@@ -103,7 +104,7 @@ RSpec.describe EE::NamespacesHelper do
end end
describe 'rendering purchased minutes report' do describe 'rendering purchased minutes report' do
let(:report) { quota.purchased_minutes_report } let(:report) { Ci::Minutes::QuotaPresenter.new(quota).purchased_minutes_report }
context 'when extra minutes are assigned' do context 'when extra minutes are assigned' do
it 'returns the proper values for used and limit sections' do it 'returns the proper values for used and limit sections' do
......
...@@ -63,244 +63,6 @@ RSpec.describe Ci::Minutes::Quota do ...@@ -63,244 +63,6 @@ RSpec.describe Ci::Minutes::Quota do
end end
end end
describe '#monthly_minutes_report' do
context 'when the quota is not enabled' do
before do
allow(quota).to receive(:enabled?).and_return(false)
allow(namespace).to receive(:root?).and_return(namespace_eligible)
allow(namespace).to receive(:any_project_with_shared_runners_enabled?).and_return(true)
end
context 'when the namespace is not eligible' do
let(:namespace_eligible) { false }
it 'returns not supported report with no usage' do
report = quota.monthly_minutes_report
expect(report.limit).to eq 'Not supported'
expect(report.used).to eq 0
expect(report.status).to eq :disabled
end
end
context 'when the namespace is eligible' do
let(:namespace_eligible) { true }
context 'when minutes are not used' do
it 'returns unlimited report with no usage' do
report = quota.monthly_minutes_report
expect(report.limit).to eq 'Unlimited'
expect(report.used).to eq 0
expect(report.status).to eq :disabled
end
end
context 'when minutes are used' do
before do
namespace.namespace_statistics.shared_runners_seconds = 20.minutes
end
it 'returns unlimited report with usage' do
report = quota.monthly_minutes_report
expect(report.limit).to eq 'Unlimited'
expect(report.used).to eq 20
expect(report.status).to eq :disabled
end
end
end
end
context 'when limited' do
before do
allow(quota).to receive(:enabled?).and_return(true)
allow(namespace).to receive(:any_project_with_shared_runners_enabled?).and_return(true)
namespace.shared_runners_minutes_limit = 100
end
context 'when minutes are not all used' do
before do
namespace.namespace_statistics.shared_runners_seconds = 30.minutes
end
it 'returns report with under quota' do
report = quota.monthly_minutes_report
expect(report.limit).to eq 100
expect(report.used).to eq 30
expect(report.status).to eq :under_quota
end
end
context 'when minutes are all used' do
before do
namespace.namespace_statistics.shared_runners_seconds = 101.minutes
end
it 'returns report with over quota' do
report = quota.monthly_minutes_report
expect(report.limit).to eq 100
expect(report.used).to eq 101
expect(report.status).to eq :over_quota
end
end
end
end
describe '#purchased_minutes_report' do
context 'when limit enabled' do
before do
allow(quota).to receive(:enabled?).and_return(true)
namespace.shared_runners_minutes_limit = 200
end
context 'when extra minutes have been purchased' do
before do
namespace.extra_shared_runners_minutes_limit = 100
end
context 'when all monthly minutes are used and some puarchased minutes are used' do
before do
namespace.namespace_statistics.shared_runners_seconds = 250.minutes
end
it 'returns report with under quota' do
report = quota.purchased_minutes_report
expect(report.limit).to eq 100
expect(report.used).to eq 50
expect(report.status).to eq :under_quota
end
end
context 'when all monthly and all puarchased minutes have been used' do
before do
namespace.namespace_statistics.shared_runners_seconds = 301.minutes
end
it 'returns report with over quota' do
report = quota.purchased_minutes_report
expect(report.limit).to eq 100
expect(report.used).to eq 101
expect(report.status).to eq :over_quota
end
end
context 'when not all monthly minutes have been used' do
before do
namespace.namespace_statistics.shared_runners_seconds = 190.minutes
end
it 'returns report with no usage' do
report = quota.purchased_minutes_report
expect(report.limit).to eq 100
expect(report.used).to eq 0
expect(report.status).to eq :under_quota
end
end
end
context 'when no extra minutes have been purchased' do
before do
namespace.extra_shared_runners_minutes_limit = nil
end
context 'when all monthly minutes have been used' do
before do
namespace.namespace_statistics.shared_runners_seconds = 201.minutes
end
it 'returns report without usage' do
report = quota.purchased_minutes_report
expect(report.limit).to eq 0
expect(report.used).to eq 0
expect(report.status).to eq :under_quota
end
end
context 'when not all monthly minutes have been used' do
before do
namespace.namespace_statistics.shared_runners_seconds = 190.minutes
end
it 'returns report with no usage' do
report = quota.purchased_minutes_report
expect(report.limit).to eq 0
expect(report.used).to eq 0
expect(report.status).to eq :under_quota
end
end
end
end
end
describe '#monthly_percent_used' do
subject { quota.monthly_percent_used }
where(:limit_enabled, :monthly_limit, :purchased_limit, :minutes_used, :result, :title) do
false | 200 | 0 | 40 | 0 | 'limit not enabled'
true | 200 | 0 | 0 | 0 | 'monthly limit set and no usage'
true | 200 | 0 | 40 | 20 | 'monthly limit set and usage lower than 100%'
true | 200 | 0 | 200 | 100 | 'monthly limit set and usage at 100%'
true | 200 | 0 | 210 | 105 | 'monthly limit set and usage above 100%'
true | 0 | 0 | 0 | 0 | 'monthly limit not set and no usage'
true | 0 | 0 | 40 | 0 | 'monthly limit not set and some usage'
true | 200 | 100 | 0 | 0 | 'monthly and purchased limits set and no usage'
true | 200 | 100 | 40 | 20 | 'monthly and purchased limits set and low usage'
true | 200 | 100 | 210 | 100 | 'usage capped to 100% and overflows into purchased minutes'
end
with_them do
before do
allow(quota).to receive(:enabled?).and_return(limit_enabled)
allow(namespace).to receive(:any_project_with_shared_runners_enabled?).and_return(true)
namespace.shared_runners_minutes_limit = monthly_limit
namespace.extra_shared_runners_minutes_limit = purchased_limit
namespace.namespace_statistics.shared_runners_seconds = minutes_used.minutes
end
it 'returns the percentage' do
is_expected.to eq result
end
end
end
describe '#purchased_percent_used' do
subject { quota.purchased_percent_used }
where(:limit_enabled, :monthly_limit, :purchased_limit, :minutes_used, :result, :title) do
false | 0 | 0 | 40 | 0 | 'limit not enabled'
true | 0 | 200 | 40 | 20 | 'monthly limit not set and purchased limit set and low usage'
true | 200 | 0 | 40 | 0 | 'monthly limit set and purchased limit not set and usage below monthly'
true | 200 | 0 | 240 | 0 | 'monthly limit set and purchased limit not set and usage above monthly'
true | 200 | 200 | 0 | 0 | 'monthly and purchased limits set and no usage'
true | 200 | 200 | 40 | 0 | 'monthly and purchased limits set and usage below monthly'
true | 200 | 200 | 200 | 0 | 'monthly and purchased limits set and monthly minutes maxed out'
true | 200 | 200 | 300 | 50 | 'monthly and purchased limits set and some purchased minutes used'
true | 200 | 200 | 400 | 100 | 'monthly and purchased limits set and all minutes used'
true | 200 | 200 | 430 | 115 | 'monthly and purchased limits set and usage beyond all limits'
end
with_them do
before do
allow(quota).to receive(:enabled?).and_return(limit_enabled)
namespace.shared_runners_minutes_limit = monthly_limit
namespace.extra_shared_runners_minutes_limit = purchased_limit
namespace.namespace_statistics.shared_runners_seconds = minutes_used.minutes
end
it 'returns the percentage' do
is_expected.to eq result
end
end
end
describe '#minutes_used_up?' do describe '#minutes_used_up?' do
subject { quota.minutes_used_up? } subject { quota.minutes_used_up? }
...@@ -394,98 +156,85 @@ RSpec.describe Ci::Minutes::Quota do ...@@ -394,98 +156,85 @@ RSpec.describe Ci::Minutes::Quota do
end end
end end
describe '#any_project_enabled?' do describe '#monthly_minutes_used_up?' do
let_it_be(:project) { create(:project, namespace: namespace) } subject { quota.monthly_minutes_used_up? }
context 'when namespace has any project with shared runners enabled' do context 'when quota is enabled' do
before do let(:total_minutes) { 1000 }
project.update!(shared_runners_enabled: true)
end
it 'returns true' do
expect(quota.any_project_enabled?).to be_truthy
end
end
context 'when namespace has no projects with shared runners enabled' do
before do before do
project.update!(shared_runners_enabled: false) allow(namespace).to receive(:shared_runners_minutes_limit).and_return(total_minutes)
end allow(namespace).to receive(:shared_runners_seconds).and_return(total_minutes_used * 60)
it 'returns false' do
expect(quota.any_project_enabled?).to be_falsey
end end
end
it 'does not trigger additional queries when called multiple times' do context 'when monthly minutes quota greater than monthly minutes used' do
# memoizes the result let(:total_minutes_used) { total_minutes - 1 }
quota.any_project_enabled?
# count it { is_expected.to be_falsey }
actual = ActiveRecord::QueryRecorder.new do
quota.any_project_enabled?
end end
expect(actual.count).to eq(0) context 'when monthly minutes quota less than monthly minutes used' do
end let(:total_minutes_used) { total_minutes + 1 }
end
describe '#display_shared_runners_data?' do
let_it_be(:project) { create(:project, namespace: namespace, shared_runners_enabled: true) }
subject { quota.display_shared_runners_data? } it { is_expected.to be_truthy }
end
context 'when the namespace is root and it has a project with shared runners enabled' do
it { is_expected.to be_truthy }
end
context 'when the namespace is not root' do context 'when monthly minutes quota equals monthly minutes used' do
let(:namespace) { create(:group, :nested) } let(:total_minutes_used) { total_minutes }
it { is_expected.to be_falsey } it { is_expected.to be_truthy }
end
end end
context 'when the namespaces has no project with shared runners enabled' do context 'when quota is disabled' do
before do before do
project.update!(shared_runners_enabled: false) allow(namespace).to receive(:shared_runners_minutes_limit).and_return(0)
end end
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
end end
end end
describe '#display_minutes_available_data?' do describe 'purchased_minutes_used_up?' do
let_it_be(:project) { create(:project, namespace: namespace, shared_runners_enabled: true) } subject { quota.purchased_minutes_used_up? }
subject { quota.display_minutes_available_data? }
context 'when the namespace is root and it has a project with shared runners enabled' do
context 'when there is a minutes limit' do
before do
namespace.update!(shared_runners_minutes_limit: 200)
end
it { is_expected.to be_truthy } context 'when quota is enabled' do
before do
allow(namespace).to receive(:shared_runners_minutes_limit).and_return(1000)
end end
context 'when there is no minutes limit' do context 'when no minutes are purchased' do
let(:purchased_minutes) { 0 }
before do before do
namespace.update!(shared_runners_minutes_limit: 0) allow(namespace).to receive(:extra_shared_runners_minutes_limit).and_return(purchased_minutes)
end end
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
end end
end
context 'when the namespace is not root' do context 'when minutes are purchased' do
let(:namespace) { create(:group, :nested) } where(:purchased_minutes, :monthly_minutes, :total_minutes_used, :result) do
1000 | 1000 | 2001 | true
1000 | 1000 | 2000 | true
1000 | 1000 | 1999 | false
end
it { is_expected.to be_falsey } with_them do
before do
allow(namespace).to receive(:shared_runners_seconds).and_return(total_minutes_used * 60)
allow(namespace).to receive(:extra_shared_runners_minutes_limit).and_return(purchased_minutes)
allow(namespace).to receive(:shared_runners_minutes_limit).and_return(monthly_minutes)
end
it { is_expected.to eq(result) }
end
end
end end
context 'when the namespaces has no project with shared runners enabled' do context 'when quota is disabled' do
before do before do
project.update!(shared_runners_enabled: false) allow(namespace).to receive(:shared_runners_minutes_limit).and_return(0)
end end
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::Minutes::QuotaPresenter do
using RSpec::Parameterized::TableSyntax
let_it_be_with_reload(:namespace) do
create(:group, namespace_statistics: create(:namespace_statistics))
end
let(:quota) { Ci::Minutes::Quota.new(namespace) }
subject(:presenter) { described_class.new(quota) }
describe '#monthly_minutes_report' do
context 'when the quota is not enabled' do
before do
allow(quota).to receive(:enabled?).and_return(false)
allow(namespace).to receive(:root?).and_return(namespace_eligible)
allow(namespace).to receive(:any_project_with_shared_runners_enabled?).and_return(true)
end
context 'when the namespace is not eligible' do
let(:namespace_eligible) { false }
it 'returns not supported report with no usage' do
report = presenter.monthly_minutes_report
expect(report.limit).to eq 'Not supported'
expect(report.used).to eq 0
expect(report.status).to eq :disabled
end
end
context 'when the namespace is eligible' do
let(:namespace_eligible) { true }
context 'when minutes are not used' do
it 'returns unlimited report with no usage' do
report = presenter.monthly_minutes_report
expect(report.limit).to eq 'Unlimited'
expect(report.used).to eq 0
expect(report.status).to eq :disabled
end
end
context 'when minutes are used' do
before do
namespace.namespace_statistics.shared_runners_seconds = 20.minutes
end
it 'returns unlimited report with usage' do
report = presenter.monthly_minutes_report
expect(report.limit).to eq 'Unlimited'
expect(report.used).to eq 20
expect(report.status).to eq :disabled
end
end
end
end
context 'when limited' do
before do
allow(presenter).to receive(:enabled?).and_return(true)
allow(namespace).to receive(:any_project_with_shared_runners_enabled?).and_return(true)
namespace.shared_runners_minutes_limit = 100
end
context 'when minutes are not all used' do
before do
namespace.namespace_statistics.shared_runners_seconds = 30.minutes
end
it 'returns report with under quota' do
report = presenter.monthly_minutes_report
expect(report.limit).to eq 100
expect(report.used).to eq 30
expect(report.status).to eq :under_quota
end
end
context 'when minutes are all used' do
before do
namespace.namespace_statistics.shared_runners_seconds = 101.minutes
end
it 'returns report with over quota' do
report = presenter.monthly_minutes_report
expect(report.limit).to eq 100
expect(report.used).to eq 101
expect(report.status).to eq :over_quota
end
end
end
end
describe '#purchased_minutes_report' do
context 'when limit enabled' do
before do
allow(quota).to receive(:enabled?).and_return(true)
namespace.shared_runners_minutes_limit = 200
end
context 'when extra minutes have been purchased' do
before do
namespace.extra_shared_runners_minutes_limit = 100
end
context 'when all monthly minutes are used and some puarchased minutes are used' do
before do
namespace.namespace_statistics.shared_runners_seconds = 250.minutes
end
it 'returns report with under quota' do
report = presenter.purchased_minutes_report
expect(report.limit).to eq 100
expect(report.used).to eq 50
expect(report.status).to eq :under_quota
end
end
context 'when all monthly and all puarchased minutes have been used' do
before do
namespace.namespace_statistics.shared_runners_seconds = 301.minutes
end
it 'returns report with over quota' do
report = presenter.purchased_minutes_report
expect(report.limit).to eq 100
expect(report.used).to eq 101
expect(report.status).to eq :over_quota
end
end
context 'when not all monthly minutes have been used' do
before do
namespace.namespace_statistics.shared_runners_seconds = 190.minutes
end
it 'returns report with no usage' do
report = presenter.purchased_minutes_report
expect(report.limit).to eq 100
expect(report.used).to eq 0
expect(report.status).to eq :under_quota
end
end
end
context 'when no extra minutes have been purchased' do
before do
namespace.extra_shared_runners_minutes_limit = nil
end
context 'when all monthly minutes have been used' do
before do
namespace.namespace_statistics.shared_runners_seconds = 201.minutes
end
it 'returns report without usage' do
report = presenter.purchased_minutes_report
expect(report.limit).to eq 0
expect(report.used).to eq 0
expect(report.status).to eq :under_quota
end
end
context 'when not all monthly minutes have been used' do
before do
namespace.namespace_statistics.shared_runners_seconds = 190.minutes
end
it 'returns report with no usage' do
report = presenter.purchased_minutes_report
expect(report.limit).to eq 0
expect(report.used).to eq 0
expect(report.status).to eq :under_quota
end
end
end
end
end
describe '#monthly_percent_used' do
subject { presenter.monthly_percent_used }
where(:limit_enabled, :monthly_limit, :purchased_limit, :minutes_used, :result, :title) do
false | 200 | 0 | 40 | 0 | 'limit not enabled'
true | 200 | 0 | 0 | 0 | 'monthly limit set and no usage'
true | 200 | 0 | 40 | 20 | 'monthly limit set and usage lower than 100%'
true | 200 | 0 | 200 | 100 | 'monthly limit set and usage at 100%'
true | 200 | 0 | 210 | 105 | 'monthly limit set and usage above 100%'
true | 0 | 0 | 0 | 0 | 'monthly limit not set and no usage'
true | 0 | 0 | 40 | 0 | 'monthly limit not set and some usage'
true | 200 | 100 | 0 | 0 | 'monthly and purchased limits set and no usage'
true | 200 | 100 | 40 | 20 | 'monthly and purchased limits set and low usage'
true | 200 | 100 | 210 | 100 | 'usage capped to 100% and overflows into purchased minutes'
end
with_them do
before do
allow(quota).to receive(:enabled?).and_return(limit_enabled)
allow(namespace).to receive(:any_project_with_shared_runners_enabled?).and_return(true)
namespace.shared_runners_minutes_limit = monthly_limit
namespace.extra_shared_runners_minutes_limit = purchased_limit
namespace.namespace_statistics.shared_runners_seconds = minutes_used.minutes
end
it 'returns the percentage' do
is_expected.to eq result
end
end
end
describe '#purchased_percent_used' do
subject { presenter.purchased_percent_used }
where(:limit_enabled, :monthly_limit, :purchased_limit, :minutes_used, :result, :title) do
false | 0 | 0 | 40 | 0 | 'limit not enabled'
true | 0 | 200 | 40 | 20 | 'monthly limit not set and purchased limit set and low usage'
true | 200 | 0 | 40 | 0 | 'monthly limit set and purchased limit not set and usage below monthly'
true | 200 | 0 | 240 | 0 | 'monthly limit set and purchased limit not set and usage above monthly'
true | 200 | 200 | 0 | 0 | 'monthly and purchased limits set and no usage'
true | 200 | 200 | 40 | 0 | 'monthly and purchased limits set and usage below monthly'
true | 200 | 200 | 200 | 0 | 'monthly and purchased limits set and monthly minutes maxed out'
true | 200 | 200 | 300 | 50 | 'monthly and purchased limits set and some purchased minutes used'
true | 200 | 200 | 400 | 100 | 'monthly and purchased limits set and all minutes used'
true | 200 | 200 | 430 | 115 | 'monthly and purchased limits set and usage beyond all limits'
end
with_them do
before do
allow(quota).to receive(:enabled?).and_return(limit_enabled)
namespace.shared_runners_minutes_limit = monthly_limit
namespace.extra_shared_runners_minutes_limit = purchased_limit
namespace.namespace_statistics.shared_runners_seconds = minutes_used.minutes
end
it 'returns the percentage' do
is_expected.to eq result
end
end
end
describe '#any_project_enabled?' do
let_it_be(:project) { create(:project, namespace: namespace) }
context 'when namespace has any project with shared runners enabled' do
before do
project.update!(shared_runners_enabled: true)
end
it 'returns true' do
expect(presenter.any_project_enabled?).to be_truthy
end
end
context 'when namespace has no projects with shared runners enabled' do
before do
project.update!(shared_runners_enabled: false)
end
it 'returns false' do
expect(presenter.any_project_enabled?).to be_falsey
end
end
it 'does not trigger additional queries when called multiple times' do
# memoizes the result
presenter.any_project_enabled?
# count
actual = ActiveRecord::QueryRecorder.new do
presenter.any_project_enabled?
end
expect(actual.count).to eq(0)
end
end
describe '#display_shared_runners_data?' do
let_it_be(:project) { create(:project, namespace: namespace, shared_runners_enabled: true) }
subject { presenter.send('display_shared_runners_data?') }
context 'when the namespace is root and it has a project with shared runners enabled' do
it { is_expected.to be_truthy }
end
context 'when the namespace is not root' do
let(:namespace) { create(:group, :nested) }
it { is_expected.to be_falsey }
end
context 'when the namespaces has no project with shared runners enabled' do
before do
project.update!(shared_runners_enabled: false)
end
it { is_expected.to be_falsey }
end
end
end
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