Commit cd2aa237 authored by James Lopez's avatar James Lopez

Merge branch 'nicolasdular/expose-purchased-limits' into 'master'

Use plan and purchased storage for limit

See merge request gitlab-org/gitlab!35561
parents 892054eb c5e729f6
......@@ -8,7 +8,6 @@ import NotificationsForm from '~/notifications_form';
import ProjectsList from '~/projects_list';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import GroupTabs from './group_tabs';
import initNamespaceStorageLimitAlert from '~/namespace_storage_limit_alert';
export default function initGroupDetails(actionName = 'show') {
const newGroupChildWrapper = document.querySelector('.js-new-project-subgroup');
......@@ -28,6 +27,4 @@ export default function initGroupDetails(actionName = 'show') {
if (newGroupChildWrapper) {
new NewGroupChild(newGroupChildWrapper);
}
initNamespaceStorageLimitAlert();
}
......@@ -8,13 +8,11 @@ import initReadMore from '~/read_more';
import leaveByUrl from '~/namespaces/leave_by_url';
import Star from '../../../star';
import notificationsDropdown from '../../../notifications_dropdown';
import initNamespaceStorageLimitAlert from '~/namespace_storage_limit_alert';
import { showLearnGitLabProjectPopover } from '~/onboarding_issues';
import initTree from 'ee_else_ce/repository';
document.addEventListener('DOMContentLoaded', () => {
initReadMore();
initNamespaceStorageLimitAlert();
new Star(); // eslint-disable-line no-new
notificationsDropdown();
new ShortcutsNavigation(); // eslint-disable-line no-new
......
......@@ -27,9 +27,6 @@ module Types
description: 'Indicates if Large File Storage (LFS) is enabled for namespace'
field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates if users can request access to namespace'
field :storage_size_limit, GraphQL::FLOAT_TYPE, null: true,
description: 'Total storage limit of the root namespace in bytes',
resolve: -> (obj, _args, _ctx) { Namespace::RootStorageSize.new(obj).limit }
field :root_storage_statistics, Types::RootStorageStatisticsType,
null: true,
......@@ -41,3 +38,5 @@ module Types
resolver: ::Resolvers::NamespaceProjectsResolver
end
end
Types::NamespaceType.prepend_if_ee('EE::Types::NamespaceType')
......@@ -56,45 +56,6 @@ module NamespacesHelper
namespaces_options(selected, options)
end
def namespace_storage_alert(namespace)
return {} if current_user.nil?
payload = Namespaces::CheckStorageSizeService.new(namespace, current_user).execute.payload
return {} if payload.empty?
alert_level = payload[:alert_level]
root_namespace = payload[:root_namespace]
return {} if cookies["hide_storage_limit_alert_#{root_namespace.id}_#{alert_level}"] == 'true'
payload
end
def namespace_storage_alert_style(alert_level)
if alert_level == :error || alert_level == :alert
'danger'
else
alert_level.to_s
end
end
def namespace_storage_alert_icon(alert_level)
if alert_level == :error || alert_level == :alert
'error'
elsif alert_level == :info
'information-o'
else
alert_level.to_s
end
end
def namespace_storage_usage_link(namespace)
# The usage quota page is only available in EE. This will be changed in
# the future, see https://gitlab.com/gitlab-org/gitlab/-/issues/220042.
nil
end
private
# Many importers create a temporary Group, so use the real
......
# frozen_string_literal: true
class Namespace::RootStorageSize
def initialize(root_namespace)
@root_namespace = root_namespace
end
def above_size_limit?
return false if limit == 0
usage_ratio > 1
end
def usage_ratio
return 0 if limit == 0
current_size.to_f / limit.to_f
end
def current_size
@current_size ||= root_namespace.root_storage_statistics&.storage_size
end
def limit
@limit ||= Gitlab::CurrentSettings.namespace_storage_size_limit.megabytes
end
private
attr_reader :root_namespace
end
......@@ -29,8 +29,6 @@ class PostReceiveService
response.add_alert_message(message)
end
response.add_alert_message(storage_size_limit_alert)
broadcast_message = BroadcastMessage.current_banner_messages&.last&.message
response.add_alert_message(broadcast_message)
......@@ -76,19 +74,6 @@ class PostReceiveService
::MergeRequests::GetUrlsService.new(project).execute(params[:changes])
end
private
def storage_size_limit_alert
return unless repository&.repo_type&.project?
payload = Namespaces::CheckStorageSizeService.new(project.namespace, user).execute.payload
return unless payload.present?
alert_level = "##### #{payload[:alert_level].to_s.upcase} #####"
[alert_level, payload[:usage_message], payload[:explanation_message]].join("\n")
end
end
PostReceiveService.prepend_if_ee('EE::PostReceiveService')
= content_for :flash_message do
= render_if_exists 'shared/shared_runners_minutes_limit', namespace: @group, classes: [container_class, ("limit-container-width" unless fluid_layout)]
= render 'shared/namespace_storage_limit_alert', namespace: @group, classes: [container_class, ("limit-container-width" unless fluid_layout)]
= render_if_exists 'shared/namespace_storage_limit_alert', namespace: @group, classes: [container_class, ("limit-container-width" unless fluid_layout)]
......@@ -9,4 +9,4 @@
= render 'shared/auto_devops_implicitly_enabled_banner', project: project
= render_if_exists 'projects/above_size_limit_warning', project: project
= render_if_exists 'shared/shared_runners_minutes_limit', project: project, classes: [container_class, ("limit-container-width" unless fluid_layout)]
= render 'shared/namespace_storage_limit_alert', namespace: project.namespace, classes: [container_class, ("limit-container-width" unless fluid_layout)]
= render_if_exists 'shared/namespace_storage_limit_alert', namespace: project.namespace, classes: [container_class, ("limit-container-width" unless fluid_layout)]
......@@ -3,6 +3,7 @@ import leaveByUrl from '~/namespaces/leave_by_url';
import initGroupDetails from '~/pages/groups/shared/group_details';
import initGroupAnalytics from 'ee/analytics/group_analytics/group_analytics_bundle';
import initVueAlerts from '~/vue_alerts';
import initNamespaceStorageLimitAlert from 'ee/namespace_storage_limit_alert';
document.addEventListener('DOMContentLoaded', () => {
leaveByUrl('group');
......@@ -15,4 +16,5 @@ document.addEventListener('DOMContentLoaded', () => {
initGroupAnalytics();
initVueAlerts();
initNamespaceStorageLimitAlert();
});
import '~/pages/projects/show/index';
import initVueAlerts from '~/vue_alerts';
import initNamespaceStorageLimitAlert from 'ee/namespace_storage_limit_alert';
document.addEventListener('DOMContentLoaded', initVueAlerts);
document.addEventListener('DOMContentLoaded', () => {
initVueAlerts();
initNamespaceStorageLimitAlert();
});
# frozen_string_literal: true
module EE
module Types
module NamespaceType
extend ActiveSupport::Concern
prepended do
field :storage_size_limit,
GraphQL::FLOAT_TYPE, null: true,
description: 'Total storage limit of the root namespace in bytes',
resolve: -> (obj, _args, _ctx) { EE::Namespace::RootStorageSize.new(obj).limit }
end
end
end
end
......@@ -32,7 +32,39 @@ module EE
end
end
override :namespace_storage_usage_link
def namespace_storage_alert(namespace)
return {} if current_user.nil?
payload = Namespaces::CheckStorageSizeService.new(namespace, current_user).execute.payload
return {} if payload.empty?
alert_level = payload[:alert_level]
root_namespace = payload[:root_namespace]
return {} if cookies["hide_storage_limit_alert_#{root_namespace.id}_#{alert_level}"] == 'true'
payload
end
def namespace_storage_alert_style(alert_level)
if alert_level == :error || alert_level == :alert
'danger'
else
alert_level.to_s
end
end
def namespace_storage_alert_icon(alert_level)
if alert_level == :error || alert_level == :alert
'error'
elsif alert_level == :info
'information-o'
else
alert_level.to_s
end
end
def namespace_storage_usage_link(namespace)
if namespace.group?
group_usage_quotas_path(namespace, anchor: 'storage-quota-tab')
......
# frozen_string_literal: true
module EE
class Namespace::RootStorageSize
def initialize(root_namespace)
@root_namespace = root_namespace
end
def above_size_limit?
usage_ratio > 1
end
def usage_ratio
return 0 if limit == 0
current_size.to_f / limit.to_f
end
def current_size
@current_size ||= root_namespace.root_storage_statistics&.storage_size
end
def limit
@limit ||= root_namespace.actual_limits.storage_size_limit.megabytes +
root_namespace.additional_purchased_storage_size.megabytes
end
private
attr_reader :root_namespace
end
end
......@@ -13,6 +13,8 @@ module EE
response.add_basic_message(geo_redirect_to_primary_message) if display_geo_redirect_to_primary_message?
response.add_basic_message(geo_secondary_lag_message) if geo_display_secondary_lag_message?
response.add_alert_message(storage_size_limit_alert)
response
end
......@@ -50,5 +52,16 @@ module EE
def geo_display_secondary_lag_message?
::Gitlab::Geo.primary? && geo_current_replication_lag.to_i > 0
end
def storage_size_limit_alert
return unless repository&.repo_type&.project?
payload = Namespaces::CheckStorageSizeService.new(project.namespace, user).execute.payload
return unless payload.present?
alert_level = "##### #{payload[:alert_level].to_s.upcase} #####"
[alert_level, payload[:usage_message], payload[:explanation_message]].join("\n")
end
end
end
......@@ -8,7 +8,7 @@ module Namespaces
def initialize(namespace, user)
@root_namespace = namespace.root_ancestor
@root_storage_size = Namespace::RootStorageSize.new(root_namespace)
@root_storage_size = EE::Namespace::RootStorageSize.new(root_namespace)
@user = user
end
......
= content_for :flash_message do
= render_if_exists 'shared/shared_runners_minutes_limit', namespace: @group, classes: [container_class, ("limit-container-width" unless fluid_layout)]
= render_if_exists 'shared/namespace_storage_limit_alert', namespace: @group, classes: [container_class, ("limit-container-width" unless fluid_layout)]
......@@ -11,6 +11,14 @@ RSpec.describe GroupsController do
let_it_be(:subgroup) { create(:group, :private, parent: group) }
let_it_be(:subgroup2) { create(:group, :private, parent: subgroup) }
describe 'GET #show' do
let(:namespace) { group }
subject { get :show, params: { id: group.to_param } }
it_behaves_like 'namespace storage limit alert'
end
describe 'GET #activity' do
render_views
......
......@@ -16,22 +16,34 @@ RSpec.describe ProjectsController do
render_views
subject { get :show, params: { namespace_id: public_project.namespace.path, id: public_project.path } }
it 'shows the over size limit warning message if above_size_limit' do
allow_next_instance_of(Gitlab::RepositorySizeChecker) do |checker|
expect(checker).to receive(:above_size_limit?).and_return(true)
end
allow(controller).to receive(:current_user).and_return(user)
get :show, params: { namespace_id: public_project.namespace.path, id: public_project.path }
subject
expect(response.body).to match(/The size of this repository.+exceeds the limit/)
end
it 'does not show an over size warning if not above_size_limit' do
get :show, params: { namespace_id: public_project.namespace.path, id: public_project.path }
subject
expect(response.body).not_to match(/The size of this repository.+exceeds the limit/)
end
context 'namespace storage limit' do
let(:namespace) { public_project.namespace }
before do
allow(controller).to receive(:current_user).and_return(user)
end
it_behaves_like 'namespace storage limit alert'
end
end
describe 'GET edit' do
......
import Cookies from 'js-cookie';
import initNamespaceStorageLimitAlert from '~/namespace_storage_limit_alert';
import initNamespaceStorageLimitAlert from 'ee/namespace_storage_limit_alert';
describe('broadcast message on dismiss', () => {
const dismiss = () => {
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['Namespace'] do
it { expect(described_class).to have_graphql_field(:storage_size_limit) }
end
......@@ -147,4 +147,96 @@ RSpec.describe EE::NamespacesHelper do
it { is_expected.to be_nil }
end
end
describe '#namespace_storage_alert' do
subject { helper.namespace_storage_alert(namespace) }
let(:namespace) { build(:namespace) }
let(:payload) do
{
alert_level: :info,
usage_message: "Usage",
explanation_message: "Explanation",
root_namespace: namespace
}
end
before do
allow(helper).to receive(:current_user).and_return(admin)
allow_next_instance_of(Namespaces::CheckStorageSizeService, namespace, admin) do |check_storage_size_service|
expect(check_storage_size_service).to receive(:execute).and_return(ServiceResponse.success(payload: payload))
end
end
context 'when payload is not empty and no cookie is set' do
it { is_expected.to eq(payload) }
end
context 'when there is no current_user' do
before do
allow(helper).to receive(:current_user).and_return(nil)
end
it { is_expected.to eq({}) }
end
context 'when payload is empty' do
let(:payload) { {} }
it { is_expected.to eq({}) }
end
context 'when cookie is set' do
before do
helper.request.cookies["hide_storage_limit_alert_#{namespace.id}_info"] = 'true'
end
it { is_expected.to eq({}) }
end
context 'when payload is empty and cookie is set' do
let(:payload) { {} }
before do
helper.request.cookies["hide_storage_limit_alert_#{namespace.id}_info"] = 'true'
end
it { is_expected.to eq({}) }
end
end
describe '#namespace_storage_alert_style' do
using RSpec::Parameterized::TableSyntax
subject { helper.namespace_storage_alert_style(alert_level) }
where(:alert_level, :result) do
:info | 'info'
:warning | 'warning'
:error | 'danger'
:alert | 'danger'
end
with_them do
it { is_expected.to eq(result) }
end
end
describe '#namespace_storage_alert_icon' do
using RSpec::Parameterized::TableSyntax
subject { helper.namespace_storage_alert_icon(alert_level) }
where(:alert_level, :result) do
:info | 'information-o'
:warning | 'warning'
:error | 'error'
:alert | 'error'
end
with_them do
it { is_expected.to eq(result) }
end
end
end
......@@ -8,6 +8,9 @@ RSpec.describe Namespace::RootStorageSize, type: :model do
let(:limit) { 100 }
let(:model) { described_class.new(namespace) }
let(:create_statistics) { create(:namespace_root_storage_statistics, namespace: namespace, storage_size: current_size)}
let_it_be(:gold_plan, reload: true) { create(:gold_plan) }
let_it_be(:plan_limits, reload: true) { create(:plan_limits, plan: gold_plan, storage_size_limit: 100) }
let!(:subscription) { create(:gitlab_subscription, namespace: namespace, hosted_plan: gold_plan) }
before do
create_statistics
......@@ -41,7 +44,9 @@ RSpec.describe Namespace::RootStorageSize, type: :model do
it { is_expected.to eq(0.5) }
context 'when limit is 0' do
let(:limit) { 0 }
before do
plan_limits.update!(storage_size_limit: 0)
end
it { is_expected.to eq(0) }
end
......@@ -62,6 +67,31 @@ RSpec.describe Namespace::RootStorageSize, type: :model do
describe '#limit' do
subject { model.limit }
it { is_expected.to eq(limit.megabytes) }
context 'when there is additional purchased storage and a plan' do
before do
plan_limits.update!(storage_size_limit: 15_000)
namespace.update!(additional_purchased_storage_size: 10_000)
end
it { is_expected.to eq(25_000.megabytes) }
end
context 'when there is no additionl purchased storage' do
before do
plan_limits.update!(storage_size_limit: 15_000)
namespace.update!(additional_purchased_storage_size: 0)
end
it { is_expected.to eq(15_000.megabytes) }
end
context 'when there is no additional purchased storage or plan limit set' do
before do
plan_limits.update!(storage_size_limit: 0)
namespace.update!(additional_purchased_storage_size: 0)
end
it { is_expected.to eq(0) }
end
end
end
......@@ -31,6 +31,7 @@ RSpec.describe PostReceiveService, :geo do
service.execute.messages.as_json
end
describe 'Geo' do
before do
stub_current_geo_node(primary_node)
......@@ -100,4 +101,40 @@ RSpec.describe PostReceiveService, :geo do
expect(subject).not_to include({ 'message' => a_string_matching('replication lag'), 'type' => anything })
end
end
end
describe 'storage size limit alerts' do
let(:check_storage_size_response) { ServiceResponse.success }
before do
expect_next_instance_of(Namespaces::CheckStorageSizeService, project.namespace, user) do |check_storage_size_service|
expect(check_storage_size_service).to receive(:execute).and_return(check_storage_size_response)
end
end
context 'when there is no payload' do
it 'adds no alert' do
expect(subject.size).to eq(0)
end
end
context 'when there is payload' do
let(:check_storage_size_response) do
ServiceResponse.success(
payload: {
alert_level: :info,
usage_message: "Usage",
explanation_message: "Explanation"
}
)
end
it 'adds an alert' do
response = subject
expect(response.size).to eq(1)
expect(response).to include({ 'type' => 'alert', 'message' => "##### INFO #####\nUsage\nExplanation" })
end
end
end
end
......@@ -21,7 +21,7 @@ RSpec.describe Namespaces::CheckStorageSizeService, '#execute' do
above_size_limit?: current_size > limit
)
expect(Namespace::RootStorageSize).to receive(:new).and_return(root_storage_size)
expect(EE::Namespace::RootStorageSize).to receive(:new).and_return(root_storage_size)
end
context 'feature flag' do
......
......@@ -52,8 +52,6 @@ RSpec.describe GroupsController do
expect(assigns(:events).map(&:id)).to contain_exactly(event.id)
end
end
it_behaves_like 'namespace storage limit alert'
end
describe 'GET #show' do
......
......@@ -385,15 +385,6 @@ RSpec.describe ProjectsController do
.not_to exceed_query_limit(2).for_query(expected_query)
end
end
context 'namespace storage limit' do
let_it_be(:project) { create(:project, :public, :repository ) }
let(:namespace) { project.namespace }
subject { get :show, params: { namespace_id: namespace, id: project } }
it_behaves_like 'namespace storage limit alert'
end
end
describe 'GET edit' do
......
......@@ -8,10 +8,10 @@ RSpec.describe GitlabSchema.types['Namespace'] do
it 'has the expected fields' do
expected_fields = %w[
id name path full_name full_path description description_html visibility
lfs_enabled request_access_enabled storage_size_limit projects root_storage_statistics
lfs_enabled request_access_enabled projects root_storage_statistics
]
expect(described_class).to have_graphql_fields(*expected_fields)
expect(described_class).to include_graphql_fields(*expected_fields)
end
specify { expect(described_class).to require_graphql_authorizations(:read_namespace) }
......
......@@ -174,96 +174,4 @@ RSpec.describe NamespacesHelper do
end
end
end
describe '#namespace_storage_alert' do
subject { helper.namespace_storage_alert(namespace) }
let(:namespace) { build(:namespace) }
let(:payload) do
{
alert_level: :info,
usage_message: "Usage",
explanation_message: "Explanation",
root_namespace: namespace
}
end
before do
allow(helper).to receive(:current_user).and_return(admin)
allow_next_instance_of(Namespaces::CheckStorageSizeService, namespace, admin) do |check_storage_size_service|
expect(check_storage_size_service).to receive(:execute).and_return(ServiceResponse.success(payload: payload))
end
end
context 'when payload is not empty and no cookie is set' do
it { is_expected.to eq(payload) }
end
context 'when there is no current_user' do
before do
allow(helper).to receive(:current_user).and_return(nil)
end
it { is_expected.to eq({}) }
end
context 'when payload is empty' do
let(:payload) { {} }
it { is_expected.to eq({}) }
end
context 'when cookie is set' do
before do
helper.request.cookies["hide_storage_limit_alert_#{namespace.id}_info"] = 'true'
end
it { is_expected.to eq({}) }
end
context 'when payload is empty and cookie is set' do
let(:payload) { {} }
before do
helper.request.cookies["hide_storage_limit_alert_#{namespace.id}_info"] = 'true'
end
it { is_expected.to eq({}) }
end
end
describe '#namespace_storage_alert_style' do
using RSpec::Parameterized::TableSyntax
subject { helper.namespace_storage_alert_style(alert_level) }
where(:alert_level, :result) do
:info | 'info'
:warning | 'warning'
:error | 'danger'
:alert | 'danger'
end
with_them do
it { is_expected.to eq(result) }
end
end
describe '#namespace_storage_alert_icon' do
using RSpec::Parameterized::TableSyntax
subject { helper.namespace_storage_alert_icon(alert_level) }
where(:alert_level, :result) do
:info | 'information-o'
:warning | 'warning'
:error | 'error'
:alert | 'error'
end
with_them do
it { is_expected.to eq(result) }
end
end
end
......@@ -166,41 +166,6 @@ RSpec.describe PostReceiveService do
expect(subject).to include(build_alert_message(message))
end
end
context 'storage size limit alerts' do
let(:check_storage_size_response) { ServiceResponse.success }
before do
expect_next_instance_of(Namespaces::CheckStorageSizeService, project.namespace, user) do |check_storage_size_service|
expect(check_storage_size_service).to receive(:execute).and_return(check_storage_size_response)
end
end
context 'when there is no payload' do
it 'adds no alert' do
expect(subject.size).to eq(1)
end
end
context 'when there is payload' do
let(:check_storage_size_response) do
ServiceResponse.success(
payload: {
alert_level: :info,
usage_message: "Usage",
explanation_message: "Explanation"
}
)
end
it 'adds an alert' do
response = subject
expect(response.size).to eq(2)
expect(response).to include(build_alert_message("##### INFO #####\nUsage\nExplanation"))
end
end
end
end
context 'with PersonalSnippet' do
......
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