Skip to content

Commit

Permalink
feat: change message for overquota
Browse files Browse the repository at this point in the history
Refs: SHELL-200 (#452)
  • Loading branch information
rodleyorosa authored Jun 28, 2024
1 parent 04c6e58 commit 8ef4eb8
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 44 deletions.
79 changes: 53 additions & 26 deletions src/settings/components/general-settings/user-quota.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,53 +6,80 @@

import React from 'react';

import { faker } from '@faker-js/faker';
import type { QuotaProps } from '@zextras/carbonio-design-system';
import { produce } from 'immer';

import UserQuota from './user-quota';
import { useAccountStore } from '../../../store/account';
import { screen, setup } from '../../../tests/utils';
import { humanFileSize } from '../utils';

const quotaMax = 100;
function setupAccountStore(usedQuota = 0): void {
function setupAccountStore(usedQuota: number, quotaMax: number): void {
useAccountStore.setState(
produce((state) => {
state.usedQuota = usedQuota;
state.settings.attrs.zimbraMailQuota = quotaMax;
})
);
}

const mockQuota = jest.fn().mockReturnValue(<div>mock Quota</div>);

jest.mock('@zextras/carbonio-design-system', () => ({
...jest.requireActual('@zextras/carbonio-design-system'),
Quota: (props: QuotaProps): unknown => mockQuota(props)
}));

describe('User Quota', () => {
describe('Quota description', () => {
it.each([
['even if it is 0', 0],
['if it is less than zimbraMailQuota', quotaMax - 1],
['if it is higher than zimbraMailQuota', quotaMax + 1]
])('should render the % of quota used message %s', (description, quotaUsed) => {
setupAccountStore(quotaUsed);
it.each([0, -1])(
'should show the string "[used] of unlimited space" if zimbraMailQuota is %s',
(quota) => {
const quotaUsed = faker.number.int();
setupAccountStore(quotaUsed, quota);
setup(<UserQuota mobileView={false} />);
expect(screen.getByText(/user's quota/i)).toBeVisible();
expect(
screen.getByText(`You have filled ${quotaUsed}% of the available space`)
).toBeVisible();
});
const quotaString = `${humanFileSize(quotaUsed)} of unlimited space`;
expect(screen.getByText(quotaString)).toBeVisible();
}
);

it('should show the string "[used] of [limit] used”', () => {
const quotaUsed = faker.number.int();
const quotaMax = 100;
setupAccountStore(quotaUsed, quotaMax);
setup(<UserQuota mobileView={false} />);
const quotaString = `${humanFileSize(quotaUsed)} of ${humanFileSize(quotaMax)} used`;
expect(screen.getByText(quotaString)).toBeVisible();
});

it.each([0, -1])(
'should render "You have unlimited space available" message when zimbraMailQuota is %s',
(quota) => {
useAccountStore.setState(
produce((state) => {
state.settings.attrs.zimbraMailQuota = quota;
})
);
describe('Quota component', () => {
it.each([
['primary', 0, 89],
['warning', 90, 94],
['error', 95, undefined]
])(
'should render Quota component with props fillBackground %s when the used quota is >= %s',
(fillBackground, minQuotaUsed, maxQuotaUsed) => {
const quotaUsed = faker.number.int({ min: minQuotaUsed, max: maxQuotaUsed });
const quotaMax = 100;
setupAccountStore(quotaUsed, quotaMax);
setup(<UserQuota mobileView={false} />);
expect(screen.getByText(`You have unlimited space available`)).toBeVisible();
expect(mockQuota).toHaveBeenCalledWith({
fill: Math.min(100, Math.round((quotaUsed / quotaMax) * 100)),
fillBackground
});
}
);

it('should render "It seems that all available space is full" message when the quota used is equal to zimbraMailQuota', () => {
setupAccountStore(quotaMax);
it('should render Quota component with props fillBackground gray4 when the quota is unlimited', () => {
const quotaUsed = faker.number.int();
const quotaMax = -1;
setupAccountStore(quotaUsed, quotaMax);
setup(<UserQuota mobileView={false} />);
expect(screen.getByText(`It seems that all available space is full`)).toBeVisible();
expect(mockQuota).toHaveBeenCalledWith({
fill: 100,
fillBackground: 'gray4'
});
});
});
});
37 changes: 20 additions & 17 deletions src/settings/components/general-settings/user-quota.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import React, { useMemo } from 'react';

import { Container, FormSubSection, Quota, Text, Tooltip } from '@zextras/carbonio-design-system';

import { useUserSettings } from '../../../store/account/hooks';
import { useAccountStore } from '../../../store/account/store';
import { useAccountStore, useUserSettings } from '../../../store/account';
import { getT } from '../../../store/i18n/hooks';
import { quotaSubSection } from '../../general-settings-sub-sections';
import { humanFileSize } from '../utils';

interface UserQuotaProps {
mobileView: boolean;
Expand All @@ -23,13 +23,13 @@ const UserQuota: FC<UserQuotaProps> = ({ mobileView }) => {

const settings = useUserSettings();
const used = useAccountStore((s) => s.usedQuota);
const limit = Number(settings?.attrs?.zimbraMailQuota);
const quota = useMemo(() => {
const userQuota = Number(settings?.attrs?.zimbraMailQuota);
if (userQuota > 0) {
return Math.round((used / userQuota) * 100);
if (limit > 0) {
return Math.round((used / limit) * 100);
}
return -1;
}, [settings?.attrs?.zimbraMailQuota, used]);
}, [limit, used]);

const filledQuota = useMemo(() => {
if (quota === -1 || quota >= 100) {
Expand All @@ -39,18 +39,21 @@ const UserQuota: FC<UserQuotaProps> = ({ mobileView }) => {
}, [quota]);

const description = useMemo(() => {
switch (true) {
case quota < 0:
return t('user_quota.unlimited', 'You have unlimited space available');
case quota === 100:
return t('user_quota.space_full', 'It seems that all available space is full');
default:
return t('user_quota.limited', {
defaultValue: 'You have filled {{quota}}% of the available space',
quota
});
if (quota < 0) {
return t('user_quota.unlimited', '{{used}} of unlimited space', {
replace: {
used: humanFileSize(used)
}
});
}
}, [quota, t]);
return t('user_quota.limited', {
defaultValue: '{{used}} of {{limit}} used',
replace: {
used: humanFileSize(used),
limit: humanFileSize(limit)
}
});
}, [limit, quota, t, used]);

const fillBackground = useMemo(() => {
switch (true) {
Expand Down
73 changes: 72 additions & 1 deletion src/settings/components/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { dateToGenTime, genTimeToDate } from './utils';
import { dateToGenTime, genTimeToDate, humanFileSize } from './utils';
import type { GeneralizedTime } from '../../types/account';

describe('dateToGenTime function', () => {
Expand Down Expand Up @@ -32,3 +32,74 @@ describe('genTimeToDate function', () => {
expect(genTimeToDate(dateStr)).toEqual(dateUTC);
});
});

describe('humanFileSize function', () => {
it('should return 0 B if input is 0', () => {
const result = humanFileSize(0);
expect(result).toBe('0 B');
});

it('should return x if input is max safe integer', () => {
const result = humanFileSize(Number.MAX_SAFE_INTEGER);
expect(result).toBe('8.00 PB');
});

it.each([
['B', 0],
['KB', 1],
['MB', 2],
['GB', 3],
['TB', 4],
['PB', 5],
['EB', 6],
['ZB', 7],
['YB', 8]
])('should return %s unit if input pow is %s', (unit, pow) => {
const result = humanFileSize(1024 ** pow);
expect(result).toBe(`1.00 ${unit}`);
});

it.each([
['B', 1],
['KB', 2],
['MB', 3],
['GB', 4],
['TB', 5],
['PB', 6],
['EB', 7],
['ZB', 8]
])(
'should return %s unit measure if input is one unit lower than the next unit measure',
(unit, pow) => {
const result = humanFileSize(1024 ** pow - 1024 ** (pow - 1));
expect(result).toBe(`1023.00 ${unit}`);
}
);

it('should change unit from KB to B when removing 1 B from 1024 B', () => {
expect(humanFileSize(1024 - 1)).toBe('1023.00 B');
});

it.each([
['KB', 2],
['MB', 3],
['GB', 4]
])('should return 1024.00 %s if input is 1024 ** %s - 1', (unit, pow) => {
const result = humanFileSize(1024 ** pow - 1);
expect(result).toBe(`1024.00 ${unit}`);
});

it.each([
['PB', 5],
['EB', 6],
['ZB', 7],
['YB', 8]
])('should return %s unit if input pow is %s - 1B', (unit, pow) => {
const result = humanFileSize(1024 ** pow - 1);
expect(result).toBe(`1.00 ${unit}`);
});

it('should throw an error if inputSize is equal or greater than 1024 YB', () => {
expect(() => humanFileSize(1024 ** 9)).toThrow('Unsupported inputSize');
});
});
15 changes: 15 additions & 0 deletions src/settings/components/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1383,3 +1383,18 @@ export function calculateNewIdentitiesState(
filteredAndModified.splice(-1, 0, ...addedIdentities);
return filteredAndModified;
}

/**
* Format a size in byte as human-readable
*/
export const humanFileSize = (inputSize: number): string => {
if (inputSize === 0) {
return '0 B';
}
const i = Math.floor(Math.log(inputSize) / Math.log(1024));
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
if (i >= units.length) {
throw new Error('Unsupported inputSize');
}
return `${(inputSize / 1024 ** i).toFixed(2).toString()} ${units[i]}`;
};

0 comments on commit 8ef4eb8

Please sign in to comment.