Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: change message for overquota #452

Merged
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',
rodleyorosa marked this conversation as resolved.
Show resolved Hide resolved
(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
rodleyorosa marked this conversation as resolved.
Show resolved Hide resolved
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
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 => {
rodleyorosa marked this conversation as resolved.
Show resolved Hide resolved
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]}`;
};