Commit 376bb57f authored by Mayra Cabrera's avatar Mayra Cabrera

Merge branch 'nicolasdular/namespace-storage-limit-banner' into 'master'

Show alert for namespace storage limit

See merge request gitlab-org/gitlab!32999
parents 96cbdcaf 631fdf97
import Cookies from 'js-cookie';
const handleOnDismiss = ({ currentTarget }) => {
const {
dataset: { id, level },
} = currentTarget;
Cookies.set(`hide_storage_limit_alert_${id}_${level}`, true, { expires: 365 });
const notification = document.querySelector('.js-namespace-storage-alert');
notification.parentNode.removeChild(notification);
};
export default () => {
const alert = document.querySelector('.js-namespace-storage-alert-dismiss');
if (alert) {
alert.addEventListener('click', handleOnDismiss);
}
};
......@@ -8,6 +8,7 @@ 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');
......@@ -27,4 +28,6 @@ export default function initGroupDetails(actionName = 'show') {
if (newGroupChildWrapper) {
new NewGroupChild(newGroupChildWrapper);
}
initNamespaceStorageLimitAlert();
}
......@@ -14,9 +14,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';
document.addEventListener('DOMContentLoaded', () => {
initReadMore();
initNamespaceStorageLimitAlert();
new Star(); // eslint-disable-line no-new
notificationsDropdown();
new ShortcutsNavigation(); // eslint-disable-line no-new
......
......@@ -56,6 +56,45 @@ 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
......@@ -89,4 +128,4 @@ module NamespacesHelper
end
end
NamespacesHelper.include_if_ee('EE::NamespacesHelper')
NamespacesHelper.prepend_if_ee('EE::NamespacesHelper')
......@@ -41,7 +41,8 @@ module Namespaces
{
explanation_message: explanation_message,
usage_message: usage_message,
alert_level: alert_level
alert_level: alert_level,
root_namespace: root_namespace
}
end
......@@ -50,7 +51,7 @@ module Namespaces
end
def usage_message
s_("You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})" % current_usage_params)
s_("You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})" % current_usage_params)
end
def alert_level
......
= 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)]
......@@ -9,3 +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)]
- return unless current_user
- payload = namespace_storage_alert(namespace)
- return if payload.empty?
- alert_level = payload[:alert_level]
- root_namespace = payload[:root_namespace]
- style = namespace_storage_alert_style(alert_level)
- icon = namespace_storage_alert_icon(alert_level)
- link = namespace_storage_usage_link(root_namespace)
%div{ class: [classes, 'js-namespace-storage-alert'] }
.gl-pt-5.gl-pb-3
.gl-alert{ class: "gl-alert-#{style}", role: 'alert' }
= sprite_icon(icon, css_class: "gl-icon gl-alert-icon")
.gl-alert-title
%h4.gl-alert-title= payload[:usage_message]
- if alert_level != :error
%button.js-namespace-storage-alert-dismiss.gl-alert-dismiss.gl-cursor-pointer{ type: 'button', 'aria-label' => _('Dismiss'), data: { id: root_namespace.id, level: alert_level } }
= sprite_icon('close', size: 16, css_class: 'gl-icon')
.gl-alert-body
= payload[:explanation_message]
- if link
.gl-alert-actions
= link_to(_('Manage storage usage'), link, class: "btn gl-alert-action btn-md gl-button btn-#{style}")
......@@ -2,6 +2,8 @@
module EE
module NamespacesHelper
extend ::Gitlab::Utils::Override
def ci_minutes_report(quota_report)
content_tag(:span, class: "shared_runners_limit_#{quota_report.status}") do
"#{quota_report.used} / #{quota_report.limit}"
......@@ -29,5 +31,14 @@ module EE
content_tag :div, nil, options
end
end
override :namespace_storage_usage_link
def namespace_storage_usage_link(namespace)
if namespace.group?
group_usage_quotas_path(namespace, anchor: 'storage-quota-tab')
else
profile_usage_quotas_path(anchor: 'storage-quota-tab')
end
end
end
end
......@@ -103,4 +103,20 @@ RSpec.describe EE::NamespacesHelper do
end
end
end
describe '#namespace_storage_usage_link' do
subject { helper.namespace_storage_usage_link(namespace) }
context 'when namespace is a group' do
let(:namespace) { build(:group) }
it { is_expected.to eq(group_usage_quotas_path(namespace, anchor: 'storage-quota-tab')) }
end
context 'when namespace is a user' do
let(:namespace) { build(:namespace) }
it { is_expected.to eq(profile_usage_quotas_path(anchor: 'storage-quota-tab')) }
end
end
end
......@@ -13403,6 +13403,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
msgid "Manage storage usage"
msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
......@@ -25772,7 +25775,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
msgid "You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})"
msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
......
......@@ -37,6 +37,8 @@ RSpec.describe GroupsController do
end
shared_examples 'details view' do
let(:namespace) { group }
it { is_expected.to render_template('groups/show') }
context 'as atom' do
......@@ -50,6 +52,8 @@ 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
......
......@@ -380,6 +380,15 @@ RSpec.describe ProjectsController do
end
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
......
import Cookies from 'js-cookie';
import initNamespaceStorageLimitAlert from '~/namespace_storage_limit_alert';
describe('broadcast message on dismiss', () => {
const dismiss = () => {
const button = document.querySelector('.js-namespace-storage-alert-dismiss');
button.click();
};
beforeEach(() => {
setFixtures(`
<div class="js-namespace-storage-alert">
<button class="js-namespace-storage-alert-dismiss" data-id="1" data-level="info"></button>
</div>
`);
initNamespaceStorageLimitAlert();
});
it('removes alert', () => {
expect(document.querySelector('.js-namespace-storage-alert')).toBeTruthy();
dismiss();
expect(document.querySelector('.js-namespace-storage-alert')).toBeNull();
});
it('calls Cookies.set', () => {
jest.spyOn(Cookies, 'set');
dismiss();
expect(Cookies.set).toHaveBeenCalledWith('hide_storage_limit_alert_1_info', true, {
expires: 365,
});
});
});
......@@ -174,4 +174,96 @@ 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
......@@ -156,4 +156,10 @@ describe Namespaces::CheckStorageSizeService, '#execute' do
expect(response).to include("60%")
end
end
describe 'payload root_namespace' do
subject(:response) { service.execute.payload[:root_namespace] }
it { is_expected.to eq(namespace) }
end
end
# frozen_string_literal: true
RSpec.shared_examples 'namespace storage limit alert' do
let(:alert_level) { :info }
before do
allow_next_instance_of(Namespaces::CheckStorageSizeService, namespace, user) do |check_storage_size_service|
expect(check_storage_size_service).to receive(:execute).and_return(
ServiceResponse.success(
payload: {
alert_level: alert_level,
usage_message: "Usage",
explanation_message: "Explanation",
root_namespace: namespace
}
)
)
end
allow(controller).to receive(:current_user).and_return(user)
end
render_views
it 'does render' do
subject
expect(response.body).to match(/Explanation/)
expect(response.body).to have_css('.js-namespace-storage-alert-dismiss')
end
context 'when alert_level is error' do
let(:alert_level) { :error }
it 'does not render a dismiss button' do
subject
expect(response.body).not_to have_css('.js-namespace-storage-alert-dismiss')
end
end
context 'when cookie is set' do
before do
cookies["hide_storage_limit_alert_#{namespace.id}_info"] = 'true'
end
it 'does not render alert' do
subject
expect(response.body).not_to match(/Explanation/)
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