Commit 435f875b authored by Vitaly Slobodin's avatar Vitaly Slobodin

Merge branch 'revert-9a00cb7d' into 'master'

Revert "Merge branch '350646-use-statistics-block' into 'master'"

See merge request gitlab-org/gitlab!84635
parents 534524f2 af16dd47
......@@ -7,7 +7,7 @@
.col-sm-12
= s_('UsageQuota|Usage of project resources across the %{strong_start}%{project_name}%{strong_end} project').html_safe % { strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe, project_name: @project.name } + '.'
%a{ href: help_page_path('user/usage_quotas.md'), target: '_blank', rel: 'noopener noreferrer' }
= s_('UsageQuota|Learn more about usage quotas.')
= s_('UsageQuota|Learn more about usage quotas') + '.'
= gl_tabs_nav do
= gl_tab_link_to '#storage-quota-tab', item_active: true do
......
......@@ -55,6 +55,11 @@ export default {
required: false,
default: null,
},
cssClass: {
type: String,
required: false,
default: null,
},
},
};
</script>
......@@ -63,6 +68,7 @@ export default {
<div
class="gl-bg-white gl-border-1 gl-border-gray-100 gl-border-solid gl-p-5 gl-rounded-base"
data-testid="container"
:class="cssClass"
>
<div class="gl-display-flex gl-justify-content-space-between">
<p
......
<script>
import { GlProgressBar } from '@gitlab/ui';
import { formatSizeAndSplit } from 'ee/usage_quotas/storage/utils';
export default {
name: 'StorageStatisticsCard',
components: { GlProgressBar },
props: {
totalStorage: {
type: Number,
required: false,
default: null,
},
usedStorage: {
type: Number,
required: false,
default: null,
},
},
computed: {
formattedUsage() {
// we want to show the usage only if there's purchased storage
if (this.totalStorage === null) {
return null;
}
return this.formatSizeAndSplit(this.usedStorage);
},
formattedTotal() {
return this.formatSizeAndSplit(this.totalStorage);
},
percentage() {
// don't show the progress bar if there's no total storage
if (!this.totalStorage || this.usedStorage === null) {
return null;
}
return Math.min(Math.round((this.usedStorage / this.totalStorage) * 100), 100);
},
usageValue() {
if (!this.totalStorage && !this.usedStorage) {
// if there is no total storage and no used storage, we want
// to show `0` instead of the formatted `0.0`
return '0';
}
return this.formattedUsage?.value;
},
usageUnit() {
return this.formattedUsage?.unit;
},
totalValue() {
return this.formattedTotal?.value;
},
totalUnit() {
return this.formattedTotal?.unit;
},
shouldRenderTotalBlock() {
// only show the total block if the used and total storage are not 0
return this.usedStorage && this.totalStorage;
},
},
methods: {
formatSizeAndSplit,
},
};
</script>
<template>
<div
class="gl-bg-white gl-border-1 gl-border-gray-100 gl-border-solid gl-p-5 gl-rounded-base"
data-testid="container"
>
<div class="gl-display-flex gl-justify-content-space-between">
<p class="gl-font-size-h-display gl-font-weight-bold gl-mb-3" data-testid="denominator">
{{ usageValue }}
<span class="gl-font-lg">{{ usageUnit }}</span>
<span v-if="shouldRenderTotalBlock" data-testid="denominator-total">
/
{{ totalValue }}
<span class="gl-font-lg">{{ totalUnit }}</span>
</span>
</p>
<div data-testid="actions">
<slot name="actions"></slot>
</div>
</div>
<p class="gl-font-weight-bold" data-testid="description">
<slot name="description"></slot>
</p>
<gl-progress-bar v-if="percentage !== null" :value="percentage" />
</div>
</template>
<script>
import { GlIcon, GlLink, GlButton } from '@gitlab/ui';
import { GlButton } from '@gitlab/ui';
import { helpPagePath } from '~/helpers/help_page_helper';
import { s__ } from '~/locale';
import StorageStatisticsCard from 'ee/usage_quotas/components/storage_statistics_card.vue';
import { formatUsageSize } from '../utils';
import UsageStatisticsCard from './usage_statistics_card.vue';
export default {
components: {
GlIcon,
GlLink,
GlButton,
StorageStatisticsCard,
UsageStatisticsCard,
},
inject: ['purchaseStorageUrl', 'buyAddonTargetAttr'],
props: {
......@@ -18,93 +17,115 @@ export default {
type: Object,
},
},
i18n: {
purchasedUsageHelpLink: helpPagePath('user/usage_quotas'),
purchasedUsageHelpText: s__('UsageQuota|Learn more about usage quotas.'),
usedUsageHelpLink: helpPagePath('user/usage_quotas'),
usedUsageHelpText: s__('UsageQuota|Learn more about usage quotas.'),
purchaseButtonText: s__('UsageQuota|Buy storage'),
totalUsageDescription: s__('UsageQuota|Namespace storage used'),
},
computed: {
usedStorageAmount() {
const {
additionalPurchasedStorageSize,
actualRepositorySizeLimit,
totalRepositorySize,
} = this.rootStorageStatistics;
if (additionalPurchasedStorageSize && totalRepositorySize > actualRepositorySizeLimit) {
return actualRepositorySizeLimit;
}
return totalRepositorySize;
formattedActualRepoSizeLimit() {
return formatUsageSize(this.rootStorageStatistics.actualRepositorySizeLimit);
},
purchasedUsageDescription() {
if (this.rootStorageStatistics.additionalPurchasedStorageSize) {
return s__('UsageQuota|Purchased storage used');
}
return s__('UsageQuota|Purchased storage');
totalUsage() {
return {
usage: this.formatSizeAndSplit(this.rootStorageStatistics.totalRepositorySize),
description: s__('UsageQuota|Total namespace storage used'),
footerNote: s__(
'UsageQuota|This is the total amount of storage used across your projects within this namespace.',
),
link: {
text: `${s__('UsageQuota|Learn more about usage quotas')}.`,
url: helpPagePath('user/usage_quotas'),
},
};
},
repositorySizeLimit() {
return Number(this.rootStorageStatistics.actualRepositorySizeLimit);
excessUsage() {
return {
usage: this.formatSizeAndSplit(this.rootStorageStatistics.totalRepositorySizeExcess),
description: s__('UsageQuota|Total excess storage used'),
footerNote: s__(
'UsageQuota|This is the total amount of storage used by projects above the free %{actualRepositorySizeLimit} storage limit.',
),
link: {
text: s__('UsageQuota|Learn more about excess storage usage'),
url: helpPagePath('user/usage_quotas', { anchor: 'excess-storage-usage' }),
},
};
},
purchasedTotalStorage() {
return Number(this.rootStorageStatistics.additionalPurchasedStorageSize);
purchasedUsage() {
const {
totalRepositorySizeExcess,
additionalPurchasedStorageSize,
} = this.rootStorageStatistics;
return this.purchaseStorageUrl
? {
usage: this.formatSizeAndSplit(
Math.max(0, additionalPurchasedStorageSize - totalRepositorySizeExcess),
),
usageTotal: this.formatSizeAndSplit(additionalPurchasedStorageSize),
description: s__('UsageQuota|Purchased storage available'),
link: {
text: s__('UsageQuota|Purchase more storage'),
url: this.purchaseStorageUrl,
target: this.buyAddonTargetAttr,
},
}
: null;
},
purchasedUsedStorage() {
// we don't want to show the used value if there's no purchased storage
return this.rootStorageStatistics.additionalPurchasedStorageSize
? Number(this.rootStorageStatistics.totalRepositorySizeExcess)
: 0;
},
methods: {
/**
* The formatUsageSize method returns
* value along with the unit. However, the unit
* and the value needs to be separated so that
* they can have different styles. The method
* splits the value into value and unit.
*
* @params {Number} size size in bytes
* @returns {Object} value and unit of formatted size
*/
formatSizeAndSplit(size) {
const formattedSize = formatUsageSize(size);
return {
value: formattedSize.slice(0, -3),
unit: formattedSize.slice(-3),
};
},
},
};
</script>
<template>
<div class="gl-display-flex gl-sm-flex-direction-column gl-py-5">
<storage-statistics-card
:used-storage="usedStorageAmount"
:total-storage="repositorySizeLimit"
data-testid="namespace-usage-card"
class="gl-w-half gl-md-mr-5"
<div class="gl-display-flex gl-sm-flex-direction-column">
<usage-statistics-card
data-testid="total-usage"
:usage="totalUsage.usage"
:link="totalUsage.link"
:description="totalUsage.description"
css-class="gl-mr-4"
/>
<usage-statistics-card
data-testid="excess-usage"
:usage="excessUsage.usage"
:link="excessUsage.link"
:description="excessUsage.description"
css-class="gl-mx-4"
/>
<usage-statistics-card
v-if="purchasedUsage"
data-testid="purchased-usage"
:usage="purchasedUsage.usage"
:usage-total="purchasedUsage.usageTotal"
:link="purchasedUsage.link"
:description="purchasedUsage.description"
css-class="gl-ml-4"
>
<template #description>
{{ $options.i18n.totalUsageDescription }}
<gl-link
:href="$options.i18n.usedUsageHelpLink"
target="_blank"
class="gl-ml-2"
:aria-label="$options.i18n.usedUsageHelpText"
<template #footer="{ link }">
<gl-button
:target="link.target"
:href="link.url"
class="mb-0"
variant="confirm"
category="primary"
block
>
<gl-icon name="question-o" />
</gl-link>
</template>
</storage-statistics-card>
<storage-statistics-card
v-if="purchaseStorageUrl"
:used-storage="purchasedUsedStorage"
:total-storage="purchasedTotalStorage"
data-testid="purchased-usage-card"
class="gl-w-half"
>
<template #actions>
<gl-button :href="purchaseStorageUrl" target="_blank" category="primary" variant="confirm">
{{ $options.i18n.purchaseButtonText }}
{{ link.text }}
</gl-button>
</template>
<template #description>
{{ purchasedUsageDescription }}
<gl-link
:href="$options.purchasedUsageHelpLink"
target="_blank"
class="gl-ml-2"
:aria-label="$options.i18n.purchasedUsageHelpText"
>
<gl-icon name="question-o" />
</gl-link>
</template>
</storage-statistics-card>
</usage-statistics-card>
</div>
</template>
<script>
import { GlLink, GlIcon, GlSprintf } from '@gitlab/ui';
export default {
components: {
GlIcon,
GlLink,
GlSprintf,
},
props: {
link: {
type: Object,
required: false,
default: () => ({ text: '', url: '' }),
},
description: {
type: String,
required: true,
},
usage: {
type: Object,
required: true,
},
usageTotal: {
type: Object,
required: false,
default: null,
},
cssClass: {
type: String,
required: false,
default: '',
},
},
};
</script>
<template>
<div class="gl-p-5 gl-my-5 gl-bg-gray-10 gl-flex-grow-1 gl-white-space-nowrap" :class="cssClass">
<p class="mb-2">
<gl-sprintf :message="__('%{size} %{unit}')">
<template #size>
<span class="gl-font-size-h-display gl-font-weight-bold">{{ usage.value }}</span>
</template>
<template #unit>
<span class="gl-font-lg gl-font-weight-bold">{{ usage.unit }}</span>
</template>
</gl-sprintf>
<template v-if="usageTotal">
<span class="gl-font-size-h-display gl-font-weight-bold">/</span>
<gl-sprintf :message="__('%{size} %{unit}')">
<template #size>
<span
data-qa-selector="purchased_usage_total"
class="gl-font-size-h-display gl-font-weight-bold"
>{{ usageTotal.value }}</span
>
</template>
<template #unit>
<span class="gl-font-lg gl-font-weight-bold">{{ usageTotal.unit }}</span>
</template>
</gl-sprintf>
</template>
</p>
<p class="gl-border-b-2 gl-border-b-solid gl-border-b-gray-100 gl-font-weight-bold gl-pb-3">
{{ description }}
</p>
<p
class="gl-mb-0 gl-text-gray-900 gl-font-sm gl-white-space-normal"
data-testid="statistics-card-footer"
>
<slot v-bind="{ link }" name="footer">
<gl-link target="_blank" :href="link.url">
<span class="text-truncate">{{ link.text }}</span>
<gl-icon name="external-link" class="gl-ml-2 gl-flex-shrink-0 gl-text-black-normal" />
</gl-link>
</slot>
</p>
</div>
</template>
import { numberToHumanSize, bytesToKiB } from '~/lib/utils/number_utils';
import { gibibytes, kibibytes } from '~/lib/utils/unit_format';
import { kibibytes } from '~/lib/utils/unit_format';
import { PROJECT_STORAGE_TYPES, STORAGE_USAGE_THRESHOLDS } from './constants';
export function usageRatioToThresholdLevel(currentUsageRatio) {
......@@ -19,12 +19,11 @@ export function usageRatioToThresholdLevel(currentUsageRatio) {
* converting bytesToKiB before passing it to
* `getFormatter`
* @param {Number} size size in bytes
* @returns {String}
* @param sizeInBytes
* @param {String} unitSeparator
*/
export const formatUsageSize = (sizeInBytes, unitSeparator = '') => {
return kibibytes(bytesToKiB(sizeInBytes), 1, { unitSeparator });
export const formatUsageSize = (sizeInBytes) => {
return kibibytes(bytesToKiB(sizeInBytes), 1);
};
/**
......@@ -188,27 +187,3 @@ export const parseGetProjectStorageResults = (data, helpLinks) => {
export function descendingStorageUsageSort(storageUsageKey) {
return (a, b) => b[storageUsageKey] - a[storageUsageKey];
}
/**
* The formatUsageSize method returns
* value along with the unit. However, the unit
* and the value needs to be separated so that
* they can have different styles. The method
* splits the value into value and unit.
*
* @params {Number} size size in bytes
* @returns {Object} value and unit of formatted size
*/
export function formatSizeAndSplit(sizeInBytes) {
if (sizeInBytes === null) {
return null;
}
/**
* we're using a special separator to help us split the formatted value properly,
* the separator won't be shown in the output
*/
const unitSeparator = '@';
const format = sizeInBytes === 0 ? gibibytes : kibibytes;
const [value, unit] = format(bytesToKiB(sizeInBytes), 1, { unitSeparator }).split(unitSeparator);
return { value, unit };
}
......@@ -30,6 +30,12 @@ describe('StatisticsCard', () => {
const findHelpLink = () => wrapper.findComponent(GlLink);
const findProgressBar = () => wrapper.findComponent(GlProgressBar);
it('passes cssClass to container div', () => {
const cssClass = 'awesome-css-class';
createComponent({ cssClass });
expect(wrapper.find('[data-testid="container"]').classes()).toContain(cssClass);
});
describe('denominator block', () => {
it('renders denominator block with all elements when all props are passed', () => {
createComponent(defaultProps);
......
import { GlProgressBar } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import StorageStatisticsCard from 'ee/usage_quotas/components/storage_statistics_card.vue';
describe('StorageStatisticsCard', () => {
let wrapper;
const defaultProps = {
totalStorage: 100 * 1024,
usedStorage: 50 * 1024,
};
const createComponent = (props = {}) => {
wrapper = shallowMountExtended(StorageStatisticsCard, {
propsData: { ...defaultProps, ...props },
slots: {
description: 'storage-statistics-card description slot',
actions: 'storage-statistics-card actions slot',
},
});
};
const findDenominatorBlock = () => wrapper.findByTestId('denominator');
const findTotalBlock = () => wrapper.findByTestId('denominator-total');
const findDescriptionBlock = () => wrapper.findByTestId('description');
const findActionsBlock = () => wrapper.findByTestId('actions');
const findProgressBar = () => wrapper.findComponent(GlProgressBar);
describe('denominator block', () => {
it('renders denominator block with all elements when all props are passed', () => {
createComponent();
expect(findDenominatorBlock().text()).toMatchInterpolatedText('50.0 KiB / 100.0 KiB');
});
it('does not render total block if totalStorage and usedStorage are not passed', () => {
createComponent({
usedStorage: null,
totalStorage: null,
});
expect(findTotalBlock().exists()).toBe(false);
});
it('renders the denominator block as 0 GiB if totalStorage and usedStorage are passed as 0', () => {
createComponent({
usedStorage: 0,
totalStorage: 0,
});
expect(findDenominatorBlock().text()).toMatchInterpolatedText('0 GiB');
});
});
describe('slots', () => {
it('renders description slot', () => {
createComponent();
expect(findDescriptionBlock().text()).toBe('storage-statistics-card description slot');
});
it('renders actions slot', () => {
createComponent();
expect(findActionsBlock().text()).toBe('storage-statistics-card actions slot');
});
});
describe('progress bar', () => {
it('does not render progress bar if there is no totalStorage', () => {
createComponent({ totalStorage: null });
expect(wrapper.findComponent(GlProgressBar).exists()).toBe(false);
});
it('renders progress bar if percentage is greater than 0', () => {
createComponent({ totalStorage: 10, usedStorage: 5 });
expect(findProgressBar().exists()).toBe(true);
expect(findProgressBar().attributes('value')).toBe(String(50));
});
it('renders the progress bar if percentage is 0', () => {
createComponent({ totalStorage: 10, usedStorage: 0 });
expect(findProgressBar().exists()).toBe(true);
expect(findProgressBar().attributes('value')).toBe(String(0));
});
});
});
import { GlLink, GlSprintf, GlProgressBar, GlButton } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import StorageStatisticsCard from 'ee/usage_quotas/components/storage_statistics_card.vue';
import { GlButton, GlLink, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import UsageStatistics from 'ee/usage_quotas/storage/components/usage_statistics.vue';
import UsageStatisticsCard from 'ee/usage_quotas/storage/components/usage_statistics_card.vue';
import { withRootStorageStatistics } from '../mock_data';
describe('UsageStatistics', () => {
let wrapper;
const createComponent = ({ props = {}, provide = {} } = {}) => {
wrapper = shallowMountExtended(UsageStatistics, {
wrapper = shallowMount(UsageStatistics, {
propsData: {
rootStorageStatistics: {
totalRepositorySize: withRootStorageStatistics.totalRepositorySize,
......@@ -19,15 +19,12 @@ describe('UsageStatistics', () => {
...props,
},
provide: {
purchaseStorageUrl: 'some-fancy-url',
buyAddonTargetAttr: '_self',
...provide,
},
stubs: {
StorageStatisticsCard,
UsageStatisticsCard,
GlSprintf,
GlLink,
GlProgressBar,
},
});
};
......@@ -36,124 +33,79 @@ describe('UsageStatistics', () => {
wrapper.destroy();
});
const findAllStorageStatisticsCards = () => wrapper.findAllComponents(StorageStatisticsCard);
const findNamespaceStorageCard = () => wrapper.findByTestId('namespace-usage-card');
const findPurchasedStorageCard = () => wrapper.findByTestId('purchased-usage-card');
const getStatisticsCards = () => wrapper.findAllComponents(UsageStatisticsCard);
const getStatisticsCard = (testId) => wrapper.find(`[data-testid="${testId}"]`);
const findGlLinkInCard = (cardName) =>
getStatisticsCard(cardName)
.find('[data-testid="statistics-card-footer"]')
.findComponent(GlLink);
const findPurchasedUsageButton = () =>
getStatisticsCard('purchased-usage').findComponent(GlButton);
describe('with purchaseStorageUrl passed', () => {
beforeEach(() => {
createComponent();
});
it('renders two statistics cards', () => {
expect(findAllStorageStatisticsCards()).toHaveLength(2);
});
});
describe('with no purchaseStorageUrl', () => {
beforeEach(() => {
createComponent({
provide: {
purchaseStorageUrl: null,
purchaseStorageUrl: 'some-fancy-url',
buyAddonTargetAttr: '_self',
},
});
});
it('renders one statistics cards', () => {
expect(findAllStorageStatisticsCards()).toHaveLength(1);
it('renders three statistics cards', () => {
expect(getStatisticsCards()).toHaveLength(3);
});
});
describe('namespace storage used', () => {
beforeEach(() => {
createComponent();
});
it('renders URL in total usage card footer', () => {
const url = findGlLinkInCard('total-usage');
it('renders progress bar with correct percentage', () => {
expect(findNamespaceStorageCard().findComponent(GlProgressBar).attributes('value')).toBe(
'100',
);
expect(url.attributes('href')).toBe('/help/user/usage_quotas');
});
});
describe('purchase storage used', () => {
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
it('renders the denominator and units correctly', () => {
expect(findPurchasedStorageCard().text().replace(/\s+/g, ' ')).toContain('2.3 KiB / 0.3 KiB');
});
it('renders URL in excess usage card footer', () => {
const url = findGlLinkInCard('excess-usage');
it('renders purchase more storage button', () => {
const purchaseButton = findPurchasedStorageCard().findComponent(GlButton);
expect(purchaseButton.exists()).toBe(true);
expect(purchaseButton.attributes('href')).toBe('some-fancy-url');
expect(url.attributes('href')).toBe('/help/user/usage_quotas#excess-storage-usage');
});
it('renders the percentage bar', () => {
expect(findPurchasedStorageCard().findComponent(GlProgressBar).attributes('value')).toBe(
'100',
);
it('renders button in purchased usage card footer with correct link', () => {
expect(findPurchasedUsageButton().attributes()).toMatchObject({
href: 'some-fancy-url',
target: '_self',
});
});
});
describe('when limit is exceeded', () => {
describe('with purchased storage', () => {
beforeEach(() => {
createComponent({
props: {
rootStorageStatistics: {
totalRepositorySize: 60 * 1024,
actualRepositorySizeLimit: 50 * 1024,
totalRepositorySizeExcess: 1024,
additionalPurchasedStorageSize: 10 * 1024,
},
},
});
});
it('shows only the limit in the namespace storage card', () => {
expect(findNamespaceStorageCard().text().replace(/\s+/g, ' ')).toContain(
'50.0 KiB / 50.0 KiB',
);
});
it('shows the excess amount in the purchased storage card', () => {
expect(findPurchasedStorageCard().text().replace(/\s+/g, ' ')).toContain(
'1.0 KiB / 10.0 KiB',
);
describe('with buyAddonTargetAttr passed as _blank', () => {
beforeEach(() => {
createComponent({
provide: {
purchaseStorageUrl: 'some-fancy-url',
buyAddonTargetAttr: '_blank',
},
});
});
describe('without purchased storage', () => {
beforeEach(() => {
createComponent({
props: {
rootStorageStatistics: {
totalRepositorySize: 502642,
actualRepositorySizeLimit: 500321,
totalRepositorySizeExcess: 2321,
additionalPurchasedStorageSize: 0,
},
},
});
it('renders button in purchased usage card footer with correct target', () => {
expect(findPurchasedUsageButton().attributes()).toMatchObject({
href: 'some-fancy-url',
target: '_blank',
});
});
});
it('shows the total of limit and excess in the namespace storage card', () => {
expect(findNamespaceStorageCard().text().replace(/\s+/g, ' ')).toContain(
'490.9 KiB / 488.6 KiB',
);
describe('with no purchaseStorageUrl', () => {
beforeEach(() => {
createComponent({
provide: {
purchaseStorageUrl: null,
buyAddonTargetAttr: '_self',
},
});
});
it('shows 0 GiB in the purchased storage card', () => {
expect(findPurchasedStorageCard().text().replace(/\s+/g, ' ')).toContain('0 GiB');
});
it('does not render purchased usage card if purchaseStorageUrl is not provided', () => {
expect(getStatisticsCard('purchased-usage').exists()).toBe(false);
});
});
});
......@@ -5,7 +5,6 @@ import {
calculateUsedAndRemStorage,
parseGetProjectStorageResults,
descendingStorageUsageSort,
formatSizeAndSplit,
} from 'ee/usage_quotas/storage/utils';
import {
projectData,
......@@ -79,11 +78,6 @@ describe('formatUsageSize', () => {
`('returns $expected from $input', ({ input, expected }) => {
expect(formatUsageSize(input)).toBe(expected);
});
it('render the output with unit separator when unitSeparator param is passed', () => {
expect(formatUsageSize(1000, '-')).toBe('1.0-KiB');
expect(formatUsageSize(1000, ' ')).toBe('1.0 KiB');
});
});
describe('calculateUsedAndRemStorage', () => {
......@@ -138,13 +132,3 @@ describe('descendingStorageUsageSort', () => {
expect(sorted).toEqual(expectedSorted);
});
});
describe('formatSizeAndSplit', () => {
it('returns null if passed parameter is null', () => {
expect(formatSizeAndSplit(null)).toBe(null);
});
it('returns formatted size as object { value, unit }', () => {
expect(formatSizeAndSplit(1000)).toEqual({ value: '1.0', unit: 'KiB' });
});
});
......@@ -969,6 +969,9 @@ msgstr[1] ""
msgid "%{service_ping_link_start}What information is shared with GitLab Inc.?%{service_ping_link_end}"
msgstr ""
msgid "%{size} %{unit}"
msgstr ""
msgid "%{size} GiB"
msgstr ""
......@@ -40507,9 +40510,6 @@ msgstr ""
msgid "UsageQuota|Buy additional minutes"
msgstr ""
msgid "UsageQuota|Buy storage"
msgstr ""
msgid "UsageQuota|CI minutes usage by month"
msgstr ""
......@@ -40540,10 +40540,10 @@ msgstr ""
msgid "UsageQuota|LFS storage"
msgstr ""
msgid "UsageQuota|Learn more about usage quotas."
msgid "UsageQuota|Learn more about excess storage usage"
msgstr ""
msgid "UsageQuota|Namespace storage used"
msgid "UsageQuota|Learn more about usage quotas"
msgstr ""
msgid "UsageQuota|No CI minutes usage data available."
......@@ -40564,10 +40564,7 @@ msgstr ""
msgid "UsageQuota|Purchase more storage"
msgstr ""
msgid "UsageQuota|Purchased storage"
msgstr ""
msgid "UsageQuota|Purchased storage used"
msgid "UsageQuota|Purchased storage available"
msgstr ""
msgid "UsageQuota|Repository"
......@@ -40594,12 +40591,24 @@ msgstr ""
msgid "UsageQuota|Storage used"
msgstr ""
msgid "UsageQuota|This is the total amount of storage used across your projects within this namespace."
msgstr ""
msgid "UsageQuota|This is the total amount of storage used by projects above the free %{actualRepositorySizeLimit} storage limit."
msgstr ""
msgid "UsageQuota|This namespace contains locked projects"
msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
msgstr ""
msgid "UsageQuota|Total excess storage used"
msgstr ""
msgid "UsageQuota|Total namespace storage used"
msgstr ""
msgid "UsageQuota|Unlimited"
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