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'; ...@@ -8,6 +8,7 @@ import NotificationsForm from '~/notifications_form';
import ProjectsList from '~/projects_list'; import ProjectsList from '~/projects_list';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation'; import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import GroupTabs from './group_tabs'; import GroupTabs from './group_tabs';
import initNamespaceStorageLimitAlert from '~/namespace_storage_limit_alert';
export default function initGroupDetails(actionName = 'show') { export default function initGroupDetails(actionName = 'show') {
const newGroupChildWrapper = document.querySelector('.js-new-project-subgroup'); const newGroupChildWrapper = document.querySelector('.js-new-project-subgroup');
...@@ -27,4 +28,6 @@ export default function initGroupDetails(actionName = 'show') { ...@@ -27,4 +28,6 @@ export default function initGroupDetails(actionName = 'show') {
if (newGroupChildWrapper) { if (newGroupChildWrapper) {
new NewGroupChild(newGroupChildWrapper); new NewGroupChild(newGroupChildWrapper);
} }
initNamespaceStorageLimitAlert();
} }
...@@ -14,9 +14,11 @@ import initReadMore from '~/read_more'; ...@@ -14,9 +14,11 @@ 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 initNamespaceStorageLimitAlert from '~/namespace_storage_limit_alert';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
initReadMore(); initReadMore();
initNamespaceStorageLimitAlert();
new Star(); // eslint-disable-line no-new new Star(); // eslint-disable-line no-new
notificationsDropdown(); notificationsDropdown();
new ShortcutsNavigation(); // eslint-disable-line no-new new ShortcutsNavigation(); // eslint-disable-line no-new
......
...@@ -56,6 +56,45 @@ module NamespacesHelper ...@@ -56,6 +56,45 @@ module NamespacesHelper
namespaces_options(selected, options) namespaces_options(selected, options)
end 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 private
# Many importers create a temporary Group, so use the real # Many importers create a temporary Group, so use the real
...@@ -89,4 +128,4 @@ module NamespacesHelper ...@@ -89,4 +128,4 @@ module NamespacesHelper
end end
end end
NamespacesHelper.include_if_ee('EE::NamespacesHelper') NamespacesHelper.prepend_if_ee('EE::NamespacesHelper')
...@@ -41,7 +41,8 @@ module Namespaces ...@@ -41,7 +41,8 @@ module Namespaces
{ {
explanation_message: explanation_message, explanation_message: explanation_message,
usage_message: usage_message, usage_message: usage_message,
alert_level: alert_level alert_level: alert_level,
root_namespace: root_namespace
} }
end end
...@@ -50,7 +51,7 @@ module Namespaces ...@@ -50,7 +51,7 @@ module Namespaces
end end
def usage_message 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 end
def alert_level def alert_level
......
= content_for :flash_message do = 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/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 @@ ...@@ -9,3 +9,4 @@
= render 'shared/auto_devops_implicitly_enabled_banner', project: project = render 'shared/auto_devops_implicitly_enabled_banner', project: project
= render_if_exists 'projects/above_size_limit_warning', 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_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 @@ ...@@ -2,6 +2,8 @@
module EE module EE
module NamespacesHelper module NamespacesHelper
extend ::Gitlab::Utils::Override
def ci_minutes_report(quota_report) def ci_minutes_report(quota_report)
content_tag(:span, class: "shared_runners_limit_#{quota_report.status}") do content_tag(:span, class: "shared_runners_limit_#{quota_report.status}") do
"#{quota_report.used} / #{quota_report.limit}" "#{quota_report.used} / #{quota_report.limit}"
...@@ -29,5 +31,14 @@ module EE ...@@ -29,5 +31,14 @@ module EE
content_tag :div, nil, options content_tag :div, nil, options
end end
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
end end
...@@ -103,4 +103,20 @@ RSpec.describe EE::NamespacesHelper do ...@@ -103,4 +103,20 @@ RSpec.describe EE::NamespacesHelper do
end end
end 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 end
...@@ -13403,6 +13403,9 @@ msgstr "" ...@@ -13403,6 +13403,9 @@ msgstr ""
msgid "Manage project labels" msgid "Manage project labels"
msgstr "" msgstr ""
msgid "Manage storage usage"
msgstr ""
msgid "Manage two-factor authentication" msgid "Manage two-factor authentication"
msgstr "" msgstr ""
...@@ -25772,7 +25775,7 @@ msgstr "" ...@@ -25772,7 +25775,7 @@ msgstr ""
msgid "You need to upload a Google Takeout archive." msgid "You need to upload a Google Takeout archive."
msgstr "" 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 "" msgstr ""
msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:" msgid "You tried to fork %{link_to_the_project} but it failed for the following reason:"
......
...@@ -37,6 +37,8 @@ RSpec.describe GroupsController do ...@@ -37,6 +37,8 @@ RSpec.describe GroupsController do
end end
shared_examples 'details view' do shared_examples 'details view' do
let(:namespace) { group }
it { is_expected.to render_template('groups/show') } it { is_expected.to render_template('groups/show') }
context 'as atom' do context 'as atom' do
...@@ -50,6 +52,8 @@ RSpec.describe GroupsController do ...@@ -50,6 +52,8 @@ RSpec.describe GroupsController do
expect(assigns(:events).map(&:id)).to contain_exactly(event.id) expect(assigns(:events).map(&:id)).to contain_exactly(event.id)
end end
end end
it_behaves_like 'namespace storage limit alert'
end end
describe 'GET #show' do describe 'GET #show' do
......
...@@ -380,6 +380,15 @@ RSpec.describe ProjectsController do ...@@ -380,6 +380,15 @@ RSpec.describe ProjectsController do
end end
end 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 end
describe 'GET edit' do 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 ...@@ -174,4 +174,96 @@ describe NamespacesHelper do
end end
end 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 end
...@@ -156,4 +156,10 @@ describe Namespaces::CheckStorageSizeService, '#execute' do ...@@ -156,4 +156,10 @@ describe Namespaces::CheckStorageSizeService, '#execute' do
expect(response).to include("60%") expect(response).to include("60%")
end end
end end
describe 'payload root_namespace' do
subject(:response) { service.execute.payload[:root_namespace] }
it { is_expected.to eq(namespace) }
end
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