Commit e0a7b6d0 authored by Doug Stull's avatar Doug Stull Committed by Dmytro Zaporozhets

Redesign the runners minutes for enhanced messaging

- have a 30% notification
- have a 5% notification
- have a none left notification
parent 00605603
...@@ -10,6 +10,7 @@ import { __ } from '~/locale'; ...@@ -10,6 +10,7 @@ import { __ } from '~/locale';
import PipelinesStore from '../../../../pipelines/stores/pipelines_store'; import PipelinesStore from '../../../../pipelines/stores/pipelines_store';
import pipelinesComponent from '../../../../pipelines/components/pipelines.vue'; import pipelinesComponent from '../../../../pipelines/components/pipelines.vue';
import Translate from '../../../../vue_shared/translate'; import Translate from '../../../../vue_shared/translate';
import initVueAlerts from '../../../../vue_alerts';
Vue.use(Translate); Vue.use(Translate);
Vue.use(GlToast); Vue.use(GlToast);
...@@ -55,3 +56,5 @@ document.addEventListener( ...@@ -55,3 +56,5 @@ document.addEventListener(
}, },
}), }),
); );
document.addEventListener('DOMContentLoaded', initVueAlerts);
...@@ -12,8 +12,10 @@ import initReadMore from '~/read_more'; ...@@ -12,8 +12,10 @@ import initReadMore from '~/read_more';
import leaveByUrl from '~/namespaces/leave_by_url'; import leaveByUrl from '~/namespaces/leave_by_url';
import Star from '../../../star'; import Star from '../../../star';
import notificationsDropdown from '../../../notifications_dropdown'; import notificationsDropdown from '../../../notifications_dropdown';
import initVueAlerts from '../../../vue_alerts';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
initVueAlerts();
initReadMore(); initReadMore();
new Star(); // eslint-disable-line no-new new Star(); // eslint-disable-line no-new
notificationsDropdown(); notificationsDropdown();
......
...@@ -82,6 +82,14 @@ ...@@ -82,6 +82,14 @@
width: px-to-rem(16px); width: px-to-rem(16px);
} }
.gl-shim-pb-3 {
padding-bottom: 8px;
}
.gl-shim-pt-5 {
padding-top: 16px;
}
.gl-text-purple { color: $purple; } .gl-text-purple { color: $purple; }
.gl-text-gray-800 { color: $gray-800; } .gl-text-gray-800 { color: $gray-800; }
.gl-bg-purple-light { background-color: $purple-light; } .gl-bg-purple-light { background-color: $purple-light; }
......
...@@ -3,20 +3,6 @@ module EE ...@@ -3,20 +3,6 @@ module EE
module RunnersHelper module RunnersHelper
include ::Gitlab::Utils::StrongMemoize include ::Gitlab::Utils::StrongMemoize
def ci_usage_warning_message(namespace, project)
message = [ci_usage_base_message(namespace)]
return unless message.any?
if ::Gitlab.com? && can?(current_user, :admin_project, project)
message << purchase_shared_runner_minutes_link
elsif namespace.shared_runners_minutes_used?
message << s_('Pipelines|Pipelines will not run anymore on shared Runners.')
end
message.join(' ').html_safe
end
def show_buy_ci_minutes?(project, namespace) def show_buy_ci_minutes?(project, namespace)
return false unless experiment_enabled?(:ci_notification_dot) || experiment_enabled?(:buy_ci_minutes_version_a) return false unless experiment_enabled?(:ci_notification_dot) || experiment_enabled?(:buy_ci_minutes_version_a)
...@@ -35,22 +21,7 @@ module EE ...@@ -35,22 +21,7 @@ module EE
strong_memoize(:show_out_of_ci_minutes_notification) do strong_memoize(:show_out_of_ci_minutes_notification) do
next unless project&.persisted? || namespace&.persisted? next unless project&.persisted? || namespace&.persisted?
context = ::Ci::Minutes::Context.new(current_user, project, namespace) ::Ci::Minutes::Notification.new(current_user, project, namespace).show?
::Ci::Minutes::Threshold.new(context).warning_reached?
end
end
def purchase_shared_runner_minutes_link
link = link_to(_("Click here"), EE::SUBSCRIPTIONS_PLANS_URL, target: '_blank', rel: 'noopener')
link + s_("Pipelines| to purchase more minutes.")
end
def ci_usage_base_message(namespace)
if namespace.shared_runners_minutes_used?
s_("Pipelines|%{namespace_name} has exceeded its pipeline minutes quota.") % { namespace_name: namespace.name }
elsif namespace.shared_runners_remaining_minutes_below_threshold?
s_("Pipelines|%{namespace_name} has less than %{notification_level}%% of CI minutes available.") % { namespace_name: namespace.name, notification_level: namespace.last_ci_minutes_usage_notification_level }
end end
end end
end end
......
...@@ -3,11 +3,12 @@ ...@@ -3,11 +3,12 @@
module Ci module Ci
module Minutes module Minutes
class Context class Context
attr_reader :namespace
delegate :shared_runners_remaining_minutes_below_threshold?, delegate :shared_runners_remaining_minutes_below_threshold?,
:shared_runners_minutes_used?, :shared_runners_minutes_used?,
:shared_runners_minutes_limit_enabled?, to: :level :shared_runners_minutes_limit_enabled?, to: :level
delegate :name, to: :namespace, prefix: true
delegate :last_ci_minutes_usage_notification_level,
:shared_runners_remaining_minutes_percent, to: :namespace
def initialize(user, project, namespace) def initialize(user, project, namespace)
@user = user @user = user
...@@ -26,7 +27,7 @@ module Ci ...@@ -26,7 +27,7 @@ module Ci
private private
attr_reader :project, :user, :level attr_reader :project, :user, :level, :namespace
end end
end end
end end
# frozen_string_literal: true
module Ci
module Minutes
class Notification
PERCENTAGES = {
warning: 30,
danger: 5,
exceeded: 0
}.freeze
def initialize(user, project, namespace)
@context = Ci::Minutes::Context.new(user, project, namespace)
@level = calculate_level if eligible_for_notifications?
end
def show?
level.present?
end
def text
contextual_map.dig(level, :text)
end
def style
contextual_map.dig(level, :style)
end
private
attr_reader :context, :level
def eligible_for_notifications?
context.shared_runners_minutes_limit_enabled? && context.can_see_status?
end
def calculate_level
percentage = context.shared_runners_remaining_minutes_percent.to_i
if percentage <= PERCENTAGES[:exceeded]
:exceeded
elsif percentage <= PERCENTAGES[:danger]
:danger
elsif percentage <= PERCENTAGES[:warning]
:warning
end
end
def contextual_map
{
warning: {
style: :warning,
text: threshold_message
},
danger: {
style: :danger,
text: threshold_message
},
exceeded: {
style: :danger,
text: exceeded_message
}
}
end
def exceeded_message
s_("Pipelines|Group %{namespace_name} has exceeded its pipeline minutes quota. " \
"Unless you buy additional pipeline minutes, no new jobs or pipelines in its projects will run.") %
{ namespace_name: context.namespace_name }
end
def threshold_message
s_("Pipelines|Group %{namespace_name} has %{percentage}%% or less Shared Runner Pipeline" \
" minutes remaining. Once it runs out, no new jobs or pipelines in its projects will run.") %
{
namespace_name: context.namespace_name,
percentage: PERCENTAGES[level]
}
end
end
end
end
# frozen_string_literal: true
module Ci
module Minutes
class Threshold
include ::Gitlab::Utils::StrongMemoize
def initialize(context)
@context = context
end
def warning_reached?
show_limit? && context.shared_runners_remaining_minutes_below_threshold?
end
def alert_reached?
show_limit? && context.shared_runners_minutes_used?
end
private
attr_reader :context
def show_limit?
strong_memoize(:show_limit) do
context.shared_runners_minutes_limit_enabled? && context.can_see_status?
end
end
end
end
end
- context = ::Ci::Minutes::Context.new(current_user, local_assigns.dig(:project), local_assigns.dig(:namespace)) - notification = ::Ci::Minutes::Notification.new(current_user, local_assigns.dig(:project), local_assigns.dig(:namespace))
- threshold = ::Ci::Minutes::Threshold.new(context) - return unless notification.show?
- if threshold.warning_reached? || threshold.alert_reached? %div{ class: [(classes if defined? classes)] }
%div{ class: ["pt-2", (classes if defined? classes)] } .shared-runner-quota-message.gl-shim-pt-5.gl-shim-pb-3
.bs-callout.shared-runner-quota-message.d-none.d-sm-block.bs-callout-danger .js-vue-alert{ 'v-cloak': true, data: { variant: notification.style,
%p secondary_button_text: _('Buy more Pipeline minutes'),
= ci_usage_warning_message(context.namespace, project) secondary_button_link: ::EE::SUBSCRIPTIONS_MORE_MINUTES_URL,
= link_to _('Purchase more minutes'), ::EE::SUBSCRIPTIONS_MORE_MINUTES_URL, class: "btn btn-danger btn-inverted" dismissible: 'false' } }
= notification.text
---
title: Modify existing out of runner minutes banner to handle 3 different warning levels
merge_request: 30088
author:
type: changed
...@@ -11,6 +11,24 @@ describe 'CI shared runner limits' do ...@@ -11,6 +11,24 @@ describe 'CI shared runner limits' do
sign_in(user) sign_in(user)
end end
shared_examples 'threshold breached' do
before do
group.update(shared_runners_minutes_limit: 20)
end
it 'displays a warning message on pipelines page' do
visit project_pipelines_path(project)
expect_quota_exceeded_alert(message)
end
it 'displays a warning message on project homepage' do
visit project_path(project)
expect_quota_exceeded_alert(message)
end
end
context 'when project member' do context 'when project member' do
before do before do
group.add_developer(user) group.add_developer(user)
...@@ -18,49 +36,62 @@ describe 'CI shared runner limits' do ...@@ -18,49 +36,62 @@ describe 'CI shared runner limits' do
context 'without limit' do context 'without limit' do
it 'does not display a warning message on project homepage' do it 'does not display a warning message on project homepage' do
visit_project_home visit project_path(project)
expect_no_quota_exceeded_alert expect_no_quota_exceeded_alert
end end
it 'does not display a warning message on pipelines page' do it 'does not display a warning message on pipelines page' do
visit_project_pipelines visit project_pipelines_path(project)
expect_no_quota_exceeded_alert expect_no_quota_exceeded_alert
end end
end end
context 'when limit is defined' do context 'when limit is defined' do
before do context 'when usage has reached a warning level', :js do
stub_const("EE::Namespace::CI_USAGE_ALERT_LEVELS", [30, 5]) it_behaves_like 'threshold breached' do
end let(:message) do
"Group #{group.name} has 30% or less Shared Runner Pipeline minutes remaining. " \
context 'when usage has reached a notification level' do "Once it runs out, no new jobs or pipelines in its projects will run."
before do end
group.update(last_ci_minutes_usage_notification_level: 30, shared_runners_minutes_limit: 10)
allow_any_instance_of(EE::Namespace).to receive(:shared_runners_remaining_minutes).and_return(2) before do
allow_any_instance_of(EE::Namespace).to receive(:shared_runners_remaining_minutes).and_return(4)
end
end end
end
it 'displays a warning message on pipelines page' do context 'when usage has reached a danger level', :js do
visit_project_pipelines it_behaves_like 'threshold breached' do
expect_quota_exceeded_alert("#{group.name} has less than 30% of CI minutes available.") let(:message) do
end "Group #{group.name} has 5% or less Shared Runner Pipeline minutes remaining. " \
"Once it runs out, no new jobs or pipelines in its projects will run."
end
it 'displays a warning message on project homepage' do before do
visit_project_home allow_any_instance_of(EE::Namespace).to receive(:shared_runners_remaining_minutes).and_return(1)
expect_quota_exceeded_alert("#{group.name} has less than 30% of CI minutes available.") end
end end
end end
context 'when limit is exceeded' do context 'when limit is exceeded', :js do
let(:group) { create(:group, :with_used_build_minutes_limit) } let(:group) { create(:group, :with_used_build_minutes_limit) }
let(:message) do
"Group #{group.name} has exceeded its pipeline minutes quota. " \
"Unless you buy additional pipeline minutes, no new jobs or pipelines in its projects will run."
end
it 'displays a warning message on project homepage' do it 'displays a warning message on project homepage' do
visit_project_home visit project_path(project)
expect_quota_exceeded_alert("#{group.name} has exceeded its pipeline minutes quota.")
expect_quota_exceeded_alert(message)
end end
it 'displays a warning message on pipelines page' do it 'displays a warning message on pipelines page' do
visit_project_pipelines visit project_pipelines_path(project)
expect_quota_exceeded_alert("#{group.name} has exceeded its pipeline minutes quota.")
expect_quota_exceeded_alert(message)
end end
end end
...@@ -68,12 +99,14 @@ describe 'CI shared runner limits' do ...@@ -68,12 +99,14 @@ describe 'CI shared runner limits' do
let(:group) { create(:group, :with_not_used_build_minutes_limit) } let(:group) { create(:group, :with_not_used_build_minutes_limit) }
it 'does not display a warning message on project homepage' do it 'does not display a warning message on project homepage' do
visit_project_home visit project_path(project)
expect_no_quota_exceeded_alert expect_no_quota_exceeded_alert
end end
it 'does not display a warning message on pipelines page' do it 'does not display a warning message on pipelines page' do
visit_project_pipelines visit project_pipelines_path(project)
expect_no_quota_exceeded_alert expect_no_quota_exceeded_alert
end end
end end
...@@ -82,12 +115,14 @@ describe 'CI shared runner limits' do ...@@ -82,12 +115,14 @@ describe 'CI shared runner limits' do
let(:group) { create(:group, :with_build_minutes_limit) } let(:group) { create(:group, :with_build_minutes_limit) }
it 'does not display a warning message on project homepage' do it 'does not display a warning message on project homepage' do
visit_project_home visit project_path(project)
expect_no_quota_exceeded_alert expect_no_quota_exceeded_alert
end end
it 'does not display a warning message on pipelines page' do it 'does not display a warning message on pipelines page' do
visit_project_pipelines visit project_pipelines_path(project)
expect_no_quota_exceeded_alert expect_no_quota_exceeded_alert
end end
end end
...@@ -99,31 +134,27 @@ describe 'CI shared runner limits' do ...@@ -99,31 +134,27 @@ describe 'CI shared runner limits' do
context 'when limit is defined and limit is exceeded' do context 'when limit is defined and limit is exceeded' do
it 'does not display a warning message on project homepage' do it 'does not display a warning message on project homepage' do
visit_project_home visit project_path(project)
expect_no_quota_exceeded_alert expect_no_quota_exceeded_alert
end end
it 'does not display a warning message on pipelines page' do it 'does not display a warning message on pipelines page' do
visit_project_pipelines visit project_pipelines_path(project)
expect_no_quota_exceeded_alert expect_no_quota_exceeded_alert
end end
end end
end end
def visit_project_home
visit project_path(project)
end
def visit_project_pipelines
visit project_pipelines_path(project)
end
def expect_quota_exceeded_alert(message = nil) def expect_quota_exceeded_alert(message = nil)
expect(page).to have_selector('.shared-runner-quota-message', count: 1) expect(page).to have_selector('.shared-runner-quota-message', count: 1)
if message if message
element = page.find('.shared-runner-quota-message') page.within('.shared-runner-quota-message') do
expect(element).to have_content(message) expect(page).to have_content(message)
expect(page).to have_link 'Buy more Pipeline minutes'
end
end end
end end
......
...@@ -9,152 +9,15 @@ describe EE::RunnersHelper do ...@@ -9,152 +9,15 @@ describe EE::RunnersHelper do
allow(helper).to receive(:current_user).and_return(user) allow(helper).to receive(:current_user).and_return(user)
end end
describe '.ci_usage_warning_message' do
let(:project) { create(:project, namespace: namespace) }
let(:minutes_used) { 0 }
let(:namespace) do
create(:group, shared_runners_minutes_limit: 100)
end
let!(:statistics) do
create(:namespace_statistics, namespace: namespace, shared_runners_seconds: minutes_used * 60)
end
before do
allow(::Gitlab).to receive(:com?).and_return(true)
allow(helper).to receive(:current_user).and_return(user)
allow(helper).to receive(:can?).with(user, :admin_project, project) { false }
stub_const("EE::Namespace::CI_USAGE_ALERT_LEVELS", [50])
end
subject { helper.ci_usage_warning_message(namespace, project) }
context 'when CI minutes quota is above the warning limits' do
let(:minutes_used) { 40 }
it 'returns nil' do
expect(subject).to be_nil
end
end
context 'when the last_ci_minutes_usage_notification_level field is set' do
before do
namespace.update_attribute(:last_ci_minutes_usage_notification_level, 50)
end
context 'when there are minutes used but remaining minutes percent is still below the notification threshold' do
let(:minutes_used) { 51 }
it 'returns the partial usage notification message' do
expect(subject).to match("#{namespace.name} has less than 50% of CI minutes available.")
end
end
context 'when limit is increased so there are now more remaining minutes percentage than the notification threshold' do
before do
namespace.update(shared_runners_minutes_limit: 200)
end
it 'returns nil' do
expect(subject).to be_nil
end
end
context 'when there are no more remaining minutes' do
let(:minutes_used) { 100 }
it 'returns the exceeded usage message' do
expect(subject).to match("#{namespace.name} has exceeded its pipeline minutes quota.")
end
end
end
context 'when current user is an owner' do
before do
allow(helper).to receive(:can?).with(user, :admin_project, project) { true }
end
context 'when base message is not present' do
before do
allow(helper).to receive(:ci_usage_base_message).with(namespace).and_return(nil)
end
it 'returns nil' do
expect(subject).to be_nil
end
end
context 'when usage has reached first level of notification' do
let(:minutes_used) { 50 }
before do
namespace.update_attribute(:last_ci_minutes_usage_notification_level, 50)
end
it 'shows the partial usage message' do
expect(subject).to match("#{namespace.name} has less than 50% of CI minutes available.")
expect(subject).to match('to purchase more minutes')
end
end
context 'when usage is above the quota' do
let(:minutes_used) { 120 }
it 'shows the total usage message' do
expect(subject).to match("#{namespace.name} has exceeded its pipeline minutes quota.")
expect(subject).to match('to purchase more minutes')
end
end
end
context 'when current user is not an owner' do
context 'when base message is not present' do
before do
allow(helper).to receive(:ci_usage_base_message).with(namespace).and_return(nil)
end
it 'returns nil' do
expect(subject).to be_nil
end
end
context 'when usage has reached first level of notification' do
let(:minutes_used) { 50 }
before do
namespace.update_attribute(:last_ci_minutes_usage_notification_level, 50)
end
it 'shows the partial usage message without the purchase link' do
expect(subject).to match("#{namespace.name} has less than 50% of CI minutes available.")
expect(subject).not_to match('to purchase more minutes')
end
end
context 'when usage is above the quota' do
let(:minutes_used) { 120 }
it 'shows the total usage message without the purchase link' do
expect(subject).to match("#{namespace.name} has exceeded its pipeline minutes quota.")
expect(subject).not_to match('to purchase more minutes')
end
end
end
end
shared_examples_for 'minutes notification' do shared_examples_for 'minutes notification' do
let_it_be(:namespace) { create(:namespace, owner: user) } let_it_be(:namespace) { create(:namespace, owner: user) }
let_it_be(:project) { create(:project, namespace: namespace) } let_it_be(:project) { create(:project, namespace: namespace) }
let(:show_warning) { true } let(:show_warning) { true }
let(:context_level) { project } let(:context_level) { project }
let(:context) { double('Ci::Minutes::Context', namespace: namespace) } let(:threshold) { double('Ci::Minutes::Notification', show?: show_warning) }
let(:threshold) { double('Ci::Minutes::Threshold', warning_reached?: show_warning) }
before do before do
allow(::Ci::Minutes::Context).to receive(:new).and_return(context) allow(::Ci::Minutes::Notification).to receive(:new).and_return(threshold)
allow(::Ci::Minutes::Threshold).to receive(:new).and_return(threshold)
end end
context 'with a project and namespace' do context 'with a project and namespace' do
...@@ -188,7 +51,7 @@ describe EE::RunnersHelper do ...@@ -188,7 +51,7 @@ describe EE::RunnersHelper do
context 'when show_ci_minutes_notification_dot? has been called before' do context 'when show_ci_minutes_notification_dot? has been called before' do
it 'does not do all the notification and query work again' do it 'does not do all the notification and query work again' do
expect(threshold).not_to receive(:warning_reached?) expect(threshold).not_to receive(:show?)
expect(project).to receive(:persisted?).once expect(project).to receive(:persisted?).once
helper.show_ci_minutes_notification_dot?(project, namespace) helper.show_ci_minutes_notification_dot?(project, namespace)
...@@ -206,7 +69,7 @@ describe EE::RunnersHelper do ...@@ -206,7 +69,7 @@ describe EE::RunnersHelper do
context 'when show_ci_minutes_notification_dot? has been called before' do context 'when show_ci_minutes_notification_dot? has been called before' do
it 'does not do all the notification and query work again' do it 'does not do all the notification and query work again' do
expect(threshold).to receive(:warning_reached?).once expect(threshold).to receive(:show?).once
expect(project).to receive(:persisted?).once expect(project).to receive(:persisted?).once
helper.show_ci_minutes_notification_dot?(project, namespace) helper.show_ci_minutes_notification_dot?(project, namespace)
......
...@@ -13,21 +13,13 @@ describe Ci::Minutes::Context do ...@@ -13,21 +13,13 @@ describe Ci::Minutes::Context do
it { is_expected.to delegate_method(:shared_runners_remaining_minutes_below_threshold?).to(:level) } it { is_expected.to delegate_method(:shared_runners_remaining_minutes_below_threshold?).to(:level) }
it { is_expected.to delegate_method(:shared_runners_minutes_used?).to(:level) } it { is_expected.to delegate_method(:shared_runners_minutes_used?).to(:level) }
it { is_expected.to delegate_method(:shared_runners_minutes_limit_enabled?).to(:level) } it { is_expected.to delegate_method(:shared_runners_minutes_limit_enabled?).to(:level) }
end it { is_expected.to delegate_method(:name).to(:namespace).with_prefix }
it { is_expected.to delegate_method(:last_ci_minutes_usage_notification_level).to(:namespace) }
shared_examples 'captures root namespace' do
describe '#namespace' do
it 'assigns the namespace' do
expect(subject.namespace).to eq group
end
end
end end
context 'when at project level' do context 'when at project level' do
subject { described_class.new(user, project, nil) } subject { described_class.new(user, project, nil) }
it_behaves_like 'captures root namespace'
describe '#can_see_status' do describe '#can_see_status' do
context 'when eligible to see status' do context 'when eligible to see status' do
before do before do
...@@ -50,8 +42,6 @@ describe Ci::Minutes::Context do ...@@ -50,8 +42,6 @@ describe Ci::Minutes::Context do
context 'when at namespace level' do context 'when at namespace level' do
subject { described_class.new(user, nil, group) } subject { described_class.new(user, nil, group) }
it_behaves_like 'captures root namespace'
describe '#can_see_status' do describe '#can_see_status' do
context 'when eligible to see status' do context 'when eligible to see status' do
before do before do
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
describe Ci::Minutes::Threshold do describe Ci::Minutes::Notification do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let(:shared_runners_enabled) { true } let(:shared_runners_enabled) { true }
let!(:project) { create(:project, :repository, namespace: group, shared_runners_enabled: shared_runners_enabled) } let!(:project) { create(:project, :repository, namespace: group, shared_runners_enabled: shared_runners_enabled) }
...@@ -10,35 +10,12 @@ describe Ci::Minutes::Threshold do ...@@ -10,35 +10,12 @@ describe Ci::Minutes::Threshold do
let(:injected_group) { group } let(:injected_group) { group }
let(:injected_project) { project } let(:injected_project) { project }
shared_examples 'queries for warning being reached' do shared_examples 'queries for notifications' do
context 'without limit' do context 'without limit' do
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
end end
context 'when limit is defined' do context 'when limit is defined' do
context 'when usage has reached a notification level' do
before do
group.shared_runners_minutes_limit = 10
allow(group).to receive(:shared_runners_remaining_minutes).and_return(2)
end
context 'when over the limit' do
before do
group.last_ci_minutes_usage_notification_level = 30
end
it { is_expected.to be_truthy }
end
context 'when right at the limit for notification' do
before do
group.last_ci_minutes_usage_notification_level = 20
end
it { is_expected.to be_truthy }
end
end
context 'when limit not yet exceeded' do context 'when limit not yet exceeded' do
let(:group) { create(:group, :with_not_used_build_minutes_limit) } let(:group) { create(:group, :with_not_used_build_minutes_limit) }
...@@ -50,20 +27,6 @@ describe Ci::Minutes::Threshold do ...@@ -50,20 +27,6 @@ describe Ci::Minutes::Threshold do
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
end end
end
end
shared_examples 'queries for alert being reached' do
context 'without limit' do
it { is_expected.to be_falsey }
end
context 'when limit is defined' do
context 'when usage has exceeded the limit' do
let(:group) { create(:group, :with_used_build_minutes_limit) }
it { is_expected.to be_truthy }
end
context 'when limit not yet exceeded' do context 'when limit not yet exceeded' do
let(:group) { create(:group, :with_not_used_build_minutes_limit) } let(:group) { create(:group, :with_not_used_build_minutes_limit) }
...@@ -79,129 +42,128 @@ describe Ci::Minutes::Threshold do ...@@ -79,129 +42,128 @@ describe Ci::Minutes::Threshold do
end end
end end
shared_examples 'cannot see if warning reached' do shared_examples 'has notifications' do
before do context 'when usage has reached a notification level' do
group.last_ci_minutes_usage_notification_level = 30 before do
group.shared_runners_minutes_limit = 10 group.shared_runners_minutes_limit = 20
allow(group).to receive(:shared_runners_remaining_minutes).and_return(2) end
end
context 'when usage has not reached a warning level' do
it { is_expected.to be_falsey }
end
end
shared_examples 'cannot see if alert reached' do
let(:group) { create(:group, :with_used_build_minutes_limit) }
context 'when usage has reached an alert level' do
it { is_expected.to be_falsey }
end
end
context 'when at project level' do context 'when at the warning level' do
let(:context) { ::Ci::Minutes::Context.new(user, injected_project, nil) } before do
allow(group).to receive(:shared_runners_remaining_minutes).and_return(4)
end
describe '#warning_reached?' do it 'has warning notification' do
subject do expect(subject.show?).to be_truthy
threshold = described_class.new(context) expect(subject.text).to match(/.*\shas 30% or less Shared Runner Pipeline minutes remaining/)
threshold.warning_reached? expect(subject.style).to eq :warning
end
end end
context 'when eligible to see warnings' do context 'when at the danger level' do
it_behaves_like 'queries for warning being reached' do before do
before do allow(group).to receive(:shared_runners_remaining_minutes).and_return(1)
group.add_developer(user)
end
end end
end
context 'when not eligible to see warnings' do it 'has danger notification' do
it_behaves_like 'cannot see if warning reached' expect(subject.show?).to be_truthy
expect(subject.text).to match(/.*\shas 5% or less Shared Runner Pipeline minutes remaining/)
expect(subject.style).to eq :danger
end
end end
end
describe '#alert_reached?' do context 'when right at the limit for notification' do
subject do before do
threshold = described_class.new(context) allow(group).to receive(:shared_runners_remaining_minutes).and_return(6)
threshold.alert_reached? end
end
context 'when eligible to see alerts' do it 'has warning notification' do
it_behaves_like 'queries for alert being reached' do expect(subject.show?).to be_truthy
before do expect(subject.text).to match(/.*\shas 30% or less Shared Runner Pipeline minutes remaining/)
group.add_developer(user) expect(subject.style).to eq :warning
end
end end
end end
context 'when not eligible to see alerts' do context 'when usage has exceeded the limit' do
it_behaves_like 'cannot see if alert reached' let(:group) { create(:group, :with_used_build_minutes_limit) }
it 'has exceeded notification' do
expect(subject.show?).to be_truthy
expect(subject.text).to match(/.*\shas exceeded its pipeline minutes quota/)
expect(subject.style).to eq :danger
end
end end
end end
end end
context 'when at namespace level' do shared_examples 'not eligible to see notifications' do
let(:context) { ::Ci::Minutes::Context.new(user, nil, injected_group) } before do
group.shared_runners_minutes_limit = 10
allow(group).to receive(:shared_runners_remaining_minutes).and_return(2)
end
describe '#warning_reached?' do context 'when not permitted to see notifications' do
subject do it 'has no notifications set' do
threshold = described_class.new(context) expect(subject.show?).to be_falsey
threshold.warning_reached? expect(subject.text).to be_nil
expect(subject.style).to be_nil
end end
end
end
context 'when eligible to see warnings' do context 'when at project level' do
let!(:user_pipeline) { create(:ci_pipeline, user: user, project: project) } describe '#show?' do
context 'when eligible to see notifications' do
context 'with a project that has runners enabled inside namespace' do before do
it_behaves_like 'queries for warning being reached' group.add_developer(user)
end end
context 'with no projects that have runners enabled inside namespace' do it_behaves_like 'queries for notifications' do
it_behaves_like 'cannot see if warning reached' do subject do
let(:shared_runners_enabled) { false } threshold = described_class.new(user, injected_project, nil)
threshold.show?
end end
end end
end
context 'when not eligible to see warnings' do it_behaves_like 'has notifications' do
it_behaves_like 'cannot see if warning reached' subject { described_class.new(user, injected_project, nil) }
end
end end
end
describe '#alert_reached?' do it_behaves_like 'not eligible to see notifications' do
subject do subject { described_class.new(user, injected_project, nil) }
threshold = described_class.new(context)
threshold.alert_reached?
end end
end
end
context 'when eligible to see warnings' do context 'when at namespace level' do
describe '#show?' do
context 'when eligible to see notifications' do
let!(:user_pipeline) { create(:ci_pipeline, user: user, project: project) } let!(:user_pipeline) { create(:ci_pipeline, user: user, project: project) }
context 'with a project that has runners enabled inside namespace' do context 'with a project that has runners enabled inside namespace' do
it_behaves_like 'queries for alert being reached' it_behaves_like 'queries for notifications' do
subject do
threshold = described_class.new(user, nil, injected_group)
threshold.show?
end
end
it_behaves_like 'has notifications' do
subject { described_class.new(user, nil, injected_group) }
end
end end
context 'with no projects that have runners enabled inside namespace' do context 'with no projects that have runners enabled inside namespace' do
it_behaves_like 'cannot see if alert reached' do it_behaves_like 'not eligible to see notifications' do
let(:shared_runners_enabled) { false } let(:shared_runners_enabled) { false }
subject { described_class.new(user, nil, injected_group) }
end end
end end
end end
context 'when not eligible to see warnings' do it_behaves_like 'not eligible to see notifications' do
it_behaves_like 'cannot see if warning reached' subject { described_class.new(user, nil, injected_group) }
end
end
context 'when we have already checked to see if we can show the limit' do
subject { described_class.new(context) }
it 'does not do all the verification work again' do
expect(context).to receive(:shared_runners_minutes_limit_enabled?).once
subject.warning_reached?
subject.alert_reached?
end end
end end
end end
......
...@@ -3357,6 +3357,9 @@ msgstr "" ...@@ -3357,6 +3357,9 @@ msgstr ""
msgid "Buy GitLab Enterprise Edition" msgid "Buy GitLab Enterprise Edition"
msgstr "" msgstr ""
msgid "Buy more Pipeline minutes"
msgstr ""
msgid "By %{user_name}" msgid "By %{user_name}"
msgstr "" msgstr ""
...@@ -4152,9 +4155,6 @@ msgstr "" ...@@ -4152,9 +4155,6 @@ msgstr ""
msgid "Click any <strong>project name</strong> in the project list below to navigate to the project milestone." msgid "Click any <strong>project name</strong> in the project list below to navigate to the project milestone."
msgstr "" msgstr ""
msgid "Click here"
msgstr ""
msgid "Click the <strong>Download</strong> button and wait for downloading to complete." msgid "Click the <strong>Download</strong> button and wait for downloading to complete."
msgstr "" msgstr ""
...@@ -14923,15 +14923,6 @@ msgstr "" ...@@ -14923,15 +14923,6 @@ msgstr ""
msgid "Pipelines settings for '%{project_name}' were successfully updated." msgid "Pipelines settings for '%{project_name}' were successfully updated."
msgstr "" msgstr ""
msgid "Pipelines| to purchase more minutes."
msgstr ""
msgid "Pipelines|%{namespace_name} has exceeded its pipeline minutes quota."
msgstr ""
msgid "Pipelines|%{namespace_name} has less than %{notification_level}%% of CI minutes available."
msgstr ""
msgid "Pipelines|API" msgid "Pipelines|API"
msgstr "" msgstr ""
...@@ -14953,10 +14944,13 @@ msgstr "" ...@@ -14953,10 +14944,13 @@ msgstr ""
msgid "Pipelines|Get started with Pipelines" msgid "Pipelines|Get started with Pipelines"
msgstr "" msgstr ""
msgid "Pipelines|Loading Pipelines" msgid "Pipelines|Group %{namespace_name} has %{percentage}%% or less Shared Runner Pipeline minutes remaining. Once it runs out, no new jobs or pipelines in its projects will run."
msgstr "" msgstr ""
msgid "Pipelines|Pipelines will not run anymore on shared Runners." msgid "Pipelines|Group %{namespace_name} has exceeded its pipeline minutes quota. Unless you buy additional pipeline minutes, no new jobs or pipelines in its projects will run."
msgstr ""
msgid "Pipelines|Loading Pipelines"
msgstr "" msgstr ""
msgid "Pipelines|Project cache successfully reset." msgid "Pipelines|Project cache successfully reset."
......
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