Commit a4570414 authored by Nicolas Dular's avatar Nicolas Dular

Add temporary storage increase modal

It's possible to increase the storage for a certain number of
days. This MR only adds the button and a modal that pops up for
confirming this temporary increase. There is no functionality
yet, this will be part of a follow-up MR.
parent ae5b0983
<script>
import { GlLink, GlSprintf } from '@gitlab/ui';
import { GlLink, GlSprintf, GlModalDirective, GlButton } from '@gitlab/ui';
import Project from './project.vue';
import UsageGraph from './usage_graph.vue';
import query from '../queries/storage.query.graphql';
import TemporaryStorageIncreaseModal from './temporary_storage_increase_modal.vue';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import { parseBoolean } from '~/lib/utils/common_utils';
import Icon from '~/vue_shared/components/icon.vue';
export default {
components: {
Project,
GlLink,
GlButton,
GlSprintf,
Icon,
UsageGraph,
TemporaryStorageIncreaseModal,
},
directives: {
GlModalDirective,
},
props: {
namespacePath: {
......@@ -28,6 +35,11 @@ export default {
required: false,
default: null,
},
isTemporaryStorageIncreaseVisible: {
type: String,
required: false,
default: 'false',
},
},
apollo: {
namespace: {
......@@ -59,18 +71,24 @@ export default {
namespace: {},
};
},
computed: {
isStorageIncreaseModalVisible() {
return parseBoolean(this.isTemporaryStorageIncreaseVisible);
},
},
methods: {
formatSize(size) {
return numberToHumanSize(size);
},
},
modalId: 'temporary-increase-storage-modal',
};
</script>
<template>
<div>
<div class="pipeline-quota container-fluid py-4 px-2 m-0">
<div class="row py-0 d-flex align-items-center">
<div class="col-sm-8">
<div class="col-lg-6">
<gl-sprintf :message="s__('UsageQuota|You used: %{usage} %{limit}')">
<template #usage>
<span class="gl-font-weight-bold" data-testid="total-usage">
......@@ -96,10 +114,19 @@ export default {
<icon name="question" :size="12" />
</gl-link>
</div>
<div v-if="purchaseStorageUrl" class="col-sm-4 text-right">
<div class="col-lg-6 text-lg-right">
<gl-button
v-if="isStorageIncreaseModalVisible"
v-gl-modal-directive="$options.modalId"
category="secondary"
variant="success"
data-testid="temporary-storage-increase-button"
>{{ s__('UsageQuota|Increase storage temporarily') }}</gl-button
>
<gl-link
v-if="purchaseStorageUrl"
:href="purchaseStorageUrl"
class="btn btn-success"
class="btn btn-success gl-ml-2"
target="_blank"
data-testid="purchase-storage-link"
>{{ s__('UsageQuota|Purchase more storage') }}</gl-link
......@@ -131,5 +158,10 @@ export default {
<project v-for="project in namespace.projects" :key="project.id" :project="project" />
</div>
<temporary-storage-increase-modal
v-if="isStorageIncreaseModalVisible"
:limit="formatSize(namespace.limit)"
:modal-id="$options.modalId"
/>
</div>
</template>
<script>
import { GlModal, GlSprintf } from '@gitlab/ui';
import { s__, __ } from '~/locale';
export default {
components: {
GlModal,
GlSprintf,
},
props: {
limit: {
type: String,
required: true,
},
modalId: {
type: String,
required: true,
},
},
modalBody: s__(
"TemporaryStorage|GitLab allows you a %{strongStart}free, one-time storage increase%{strongEnd}. For 30 days your storage will be unlimited. This gives you time to reduce your storage usage. After 30 days, your original storage limit of %{limit} applies. If you are at maximum storage capacity, your account will be read-only. To continue using GitLab you'll have to purchase additional storage or decrease storage usage.",
),
modalTitle: s__('TemporaryStorage|Temporarily increase storage now?'),
okTitle: s__('TemporaryStorage|Increase storage temporarily'),
cancelTitle: __('Cancel'),
};
</script>
<template>
<gl-modal
size="sm"
ok-variant="success"
:title="$options.modalTitle"
:ok-title="$options.okTitle"
:cancel-title="$options.cancelTitle"
:modal-id="modalId"
>
<gl-sprintf :message="$options.modalBody">
<template #strong="{ content }">
<strong>{{ content }}</strong>
</template>
<template #limit>{{ limit }}</template>
</gl-sprintf>
</gl-modal>
</template>
......@@ -7,7 +7,12 @@ Vue.use(VueApollo);
export default () => {
const el = document.getElementById('js-storage-counter-app');
const { namespacePath, helpPagePath, purchaseStorageUrl } = el.dataset;
const {
namespacePath,
helpPagePath,
purchaseStorageUrl,
isTemporaryStorageIncreaseVisible,
} = el.dataset;
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
......@@ -22,6 +27,7 @@ export default () => {
namespacePath,
helpPagePath,
purchaseStorageUrl,
isTemporaryStorageIncreaseVisible,
},
});
},
......
......@@ -65,6 +65,13 @@ module EE
end
end
def temporary_storage_increase_visible?(namespace)
return false unless ::Gitlab.dev_env_or_com?
return false unless ::Feature.enabled?(:temporary_storage_increase, namespace)
current_user.can?(:admin_namespace, namespace.root_ancestor)
end
def namespace_storage_usage_link(namespace)
if namespace.group?
group_usage_quotas_path(namespace, anchor: 'storage-quota-tab')
......
......@@ -20,5 +20,5 @@
= render "namespaces/pipelines_quota/list",
locals: { namespace: @group, projects: @projects }
.tab-pane#storage-quota-tab
#js-storage-counter-app{ data: { namespace_path: @group.full_path, help_page_path: help_page_path('user/group/index.md', anchor: 'storage-usage-quota-starter'), purchase_storage_url: purchase_storage_url } }
#js-storage-counter-app{ data: { namespace_path: @group.full_path, help_page_path: help_page_path('user/group/index.md', anchor: 'storage-usage-quota-starter'), purchase_storage_url: purchase_storage_url, is_temporary_storage_increase_visible: temporary_storage_increase_visible?(@group).to_s } }
......@@ -21,4 +21,4 @@
= render "namespaces/pipelines_quota/list",
locals: { namespace: @namespace, projects: @projects }
.tab-pane#storage-quota-tab
#js-storage-counter-app{ data: { namespace_path: @namespace.full_path, help_page_path: help_page_path('user/group/index.md', anchor: 'storage-usage-quota-starter'), purchase_storage_url: purchase_storage_url } }
#js-storage-counter-app{ data: { namespace_path: @namespace.full_path, help_page_path: help_page_path('user/group/index.md', anchor: 'storage-usage-quota-starter'), purchase_storage_url: purchase_storage_url, is_temporary_storage_increase_visible: temporary_storage_increase_visible?(@namespace).to_s } }
import { mount } from '@vue/test-utils';
import StorageApp from 'ee/storage_counter/components/app.vue';
import Project from 'ee/storage_counter/components/project.vue';
import TemporaryStorageIncreaseModal from 'ee/storage_counter/components/temporary_storage_increase_modal.vue';
import { projects, withRootStorageStatistics } from '../data';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
const TEST_LIMIT = 1000;
describe('Storage counter app', () => {
let wrapper;
const findTotalUsage = () => wrapper.find("[data-testid='total-usage']");
const findPurchaseStorageLink = () => wrapper.find("[data-testid='purchase-storage-link']");
const findTemporaryStorageIncreaseButton = () =>
wrapper.find("[data-testid='temporary-storage-increase-button']");
function createComponent(props = {}, loading = false) {
const $apollo = {
......@@ -22,6 +29,9 @@ describe('Storage counter app', () => {
wrapper = mount(StorageApp, {
propsData: { namespacePath: 'h5bp', helpPagePath: 'help', ...props },
mocks: { $apollo },
directives: {
GlModalDirective: createMockDirective(),
},
});
}
......@@ -109,4 +119,51 @@ describe('Storage counter app', () => {
});
});
});
describe('temporary storage increase', () => {
describe.each`
props | isVisible
${{}} | ${false}
${{ isTemporaryStorageIncreaseVisible: 'false' }} | ${false}
${{ isTemporaryStorageIncreaseVisible: 'true' }} | ${true}
`('with $props', ({ props, isVisible }) => {
beforeEach(() => {
createComponent(props);
});
it(`renders button = ${isVisible}`, () => {
expect(findTemporaryStorageIncreaseButton().exists()).toBe(isVisible);
});
});
describe('when temporary storage increase is visible', () => {
beforeEach(() => {
createComponent({ isTemporaryStorageIncreaseVisible: 'true' });
wrapper.setData({
namespace: {
...projects,
limit: TEST_LIMIT,
},
});
});
it('binds button to modal', () => {
const { value } = getBinding(
findTemporaryStorageIncreaseButton().element,
'gl-modal-directive',
);
// Check for truthiness so we're assured we're not comparing two undefineds
expect(value).toBeTruthy();
expect(value).toEqual(StorageApp.modalId);
});
it('renders modal', () => {
expect(wrapper.find(TemporaryStorageIncreaseModal).props()).toEqual({
limit: numberToHumanSize(TEST_LIMIT),
modalId: StorageApp.modalId,
});
});
});
});
});
import { GlModal } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
import TemporaryStorageIncreaseModal from 'ee/storage_counter/components/temporary_storage_increase_modal.vue';
const TEST_LIMIT = '8 bytes';
const TEST_MODAL_ID = 'test-modal-id';
describe('Temporary storage increase modal', () => {
let wrapper;
const createComponent = (mountFn, props = {}) => {
wrapper = mountFn(TemporaryStorageIncreaseModal, {
propsData: {
modalId: TEST_MODAL_ID,
limit: TEST_LIMIT,
...props,
},
});
};
const findModal = () => wrapper.find(GlModal);
const showModal = () => {
findModal().vm.show();
return wrapper.vm.$nextTick();
};
const findModalText = () => document.body.innerText;
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('shows modal message', async () => {
createComponent(mount);
await showModal();
const text = findModalText();
expect(text).toContain('GitLab allows you a free, one-time storage increase.');
expect(text).toContain(`your original storage limit of ${TEST_LIMIT} applies.`);
});
it('passes along modalId', () => {
createComponent(shallowMount);
expect(findModal().attributes('modalid')).toBe(TEST_MODAL_ID);
});
});
......@@ -104,6 +104,54 @@ RSpec.describe EE::NamespacesHelper do
end
end
describe '#temporary_storage_increase_visible?' do
subject { helper.temporary_storage_increase_visible?(namespace) }
let_it_be(:namespace) { create(:namespace) }
let_it_be(:admin) { create(:user, namespace: namespace) }
let_it_be(:user) { create(:user) }
context 'when on .com' do
before do
allow(::Gitlab).to receive(:com?).and_return(true)
end
context 'when current_user is admin of namespace' do
before do
allow(helper).to receive(:current_user).and_return(admin)
end
it { is_expected.to eq(true) }
context 'when feature flag is disabled' do
before do
stub_feature_flags(temporary_storage_increase: false)
end
it { is_expected.to eq(false) }
end
end
context 'when current_user is not the admin of namespace' do
before do
allow(helper).to receive(:current_user).and_return(user)
end
it { is_expected.to eq(false) }
end
end
context 'when not on .com' do
context 'when current_user is admin of namespace' do
before do
allow(helper).to receive(:current_user).and_return(admin)
end
it { is_expected.to eq(false) }
end
end
end
describe '#namespace_storage_usage_link' do
subject { helper.namespace_storage_usage_link(namespace) }
......
......@@ -23209,6 +23209,15 @@ msgstr ""
msgid "Templates"
msgstr ""
msgid "TemporaryStorage|GitLab allows you a %{strongStart}free, one-time storage increase%{strongEnd}. For 30 days your storage will be unlimited. This gives you time to reduce your storage usage. After 30 days, your original storage limit of %{limit} applies. If you are at maximum storage capacity, your account will be read-only. To continue using GitLab you'll have to purchase additional storage or decrease storage usage."
msgstr ""
msgid "TemporaryStorage|Increase storage temporarily"
msgstr ""
msgid "TemporaryStorage|Temporarily increase storage now?"
msgstr ""
msgid "Terminal"
msgstr ""
......@@ -25671,6 +25680,9 @@ msgstr ""
msgid "UsageQuota|Current period usage"
msgstr ""
msgid "UsageQuota|Increase storage temporarily"
msgstr ""
msgid "UsageQuota|LFS Objects"
msgstr ""
......
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