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

allow Veeam capacity to have decimal: #783

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/react/ui-elements/Veeam/VeeamCapacityFormSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ export const VeeamCapacityFormSection = ({
type="number"
size="1/3"
min={1}
max={999}
step={1}
max={1024}
step={0.01}
autoFocus={autoFocusEnabled}
{...register('capacity')}
/>
Expand Down
48 changes: 48 additions & 0 deletions src/react/ui-elements/Veeam/VeeamCapacityModal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,54 @@ describe('VeeamCapacityModal', () => {
);
});
});
it('should validate capacity value correctly : number less than 1', async () => {
fireEvent.click(selectors.editBtn());
fireEvent.change(selectors.capacityInput(), { target: { value: '0' } });

await waitFor(async () => {
expect(selectors.editModalBtn()).toBeDisabled();
expect(
screen.getByText(/"capacity" must be larger than or equal to 1/i),
).toBeInTheDocument();
});
});
it('should validate capacity value correctly : number greater than 1024', async () => {
fireEvent.click(selectors.editBtn());
fireEvent.change(selectors.capacityInput(), { target: { value: '1025' } });

await waitFor(async () => {
expect(selectors.editModalBtn()).toBeDisabled();
expect(
screen.getByText(/"capacity" must be less than or equal to 1024/i),
).toBeInTheDocument();
});
});
it('should validate capacity value correctly : number with more than 2 decimals', async () => {
fireEvent.click(selectors.editBtn());
fireEvent.change(selectors.capacityInput(), {
target: { value: '12.345' },
});

await waitFor(async () => {
expect(selectors.editModalBtn()).toBeDisabled();
expect(
screen.getByText(/"capacity" must have at most 2 decimals/i),
).toBeInTheDocument();
});
});
it('should validate capacity value correctly : number is required', async () => {
fireEvent.click(selectors.editBtn());
fireEvent.change(selectors.capacityInput(), {
target: { value: '' },
});

await waitFor(async () => {
expect(selectors.editModalBtn()).toBeDisabled();
expect(
screen.getByText(/"capacity" must be a number/i),
).toBeInTheDocument();
});
});

it('should display error toast if mutation failed', async () => {
server.use(
Expand Down
13 changes: 12 additions & 1 deletion src/react/ui-elements/Veeam/VeeamCapacityModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,18 @@ import {
import { getCapacityBytes, useCapacityUnit } from './useCapacityUnit';

const schema = Joi.object({
capacity: Joi.number().required().min(1).max(999).integer(),
capacity: Joi.number()
.required()
.min(1)
.max(1024)
.custom((value, helpers) => {
if (!Number.isInteger(value * 100)) {
return helpers.message({
custom: '"capacity" must have at most 2 decimals',
});
}
return value;
}),
capacityUnit: Joi.string().required(),
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ describe('VeeamCapacityOverviewRow', () => {
expect(screen.getByText('Max repository Capacity')).toBeInTheDocument();
});

expect(screen.getByText('100 GiB')).toBeInTheDocument();
expect(screen.getByText('100.00 GiB')).toBeInTheDocument();
});

it('should not render the row if SOSAPI is not enabled', () => {
Expand Down
5 changes: 2 additions & 3 deletions src/react/ui-elements/Veeam/VeeamCapacityOverviewRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,12 @@ export const VeeamCapacityOverviewRow = ({
const xml = veeamObject?.Body?.toString();
const regex = /<Capacity>([\s\S]*?)<\/Capacity>/;
const matches = xml?.match(regex);
const capacity = parseInt(
const capacity = parseFloat(
new DOMParser()
?.parseFromString(xml || '', 'application/xml')
?.querySelector('Capacity')?.textContent ||
matches?.[1] ||
'0',
10,
);

if (isSOSAPIEnabled) {
Expand All @@ -60,7 +59,7 @@ export const VeeamCapacityOverviewRow = ({
) : veeamObjectStatus === 'error' ? (
'Error'
) : (
<PrettyBytes bytes={capacity} decimals={0} />
<PrettyBytes bytes={capacity} decimals={2} />
)}
</>
{veeamObjectStatus === 'success' && (
Expand Down
83 changes: 76 additions & 7 deletions src/react/ui-elements/Veeam/VeeamConfiguration.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ describe('Veeam Configuration UI', () => {
).toBeInTheDocument();
//expect the immutable backup toogle to be active
expect(screen.getByLabelText('enableImmutableBackup')).toBeEnabled();
// verify the max capacity input is prefilled with 4 GiB
expect(selectors.maxCapacityInput()).toHaveValue(4);
// verify the max capacity input is prefilled with 80% of binary value of clusterCapacity: jestSetupAfterEnv.tsx
expect(selectors.maxCapacityInput()).toHaveValue(3.73);
expect(screen.getByText(/GiB/i)).toBeInTheDocument();
await waitFor(() => {
expect(selectors.continueButton()).toBeEnabled();
Expand Down Expand Up @@ -147,7 +147,7 @@ describe('Veeam Configuration UI', () => {
accountName: 'Veeam',
application: 'Veeam Backup for Microsoft 365',
bucketName: 'veeam-bucket',
capacityBytes: '4294967296',
capacityBytes: '4005057004',
enableImmutableBackup: false,
});
});
Expand Down Expand Up @@ -190,17 +190,86 @@ describe('Veeam Configuration UI', () => {
expect(selectors.accountNameInput()).toHaveValue('Veeam');
});
});

it('should throw validation error if the max capacity is not integer', async () => {
it('should show validation error if the max capacity is less than 1', async () => {
//S
mockUseAccountsImplementation();
renderVeeamConfigurationForm();
//E
await userEvent.clear(selectors.maxCapacityInput());
await userEvent.type(selectors.maxCapacityInput(), '0');
//V
expect(
screen.getByText(/"capacity" must be larger than or equal to 1/i),
).toBeInTheDocument();
await waitFor(() => {
expect(selectors.continueButton()).toBeDisabled();
});
});
it('should show validation error and disable continue button if the max capacity is more than 1024', async () => {
//S
mockUseAccountsImplementation();
renderVeeamConfigurationForm();
await userEvent.type(selectors.accountNameInput(), 'Veeam');
await userEvent.type(selectors.repositoryInput(), 'veeam-bucket');
//E
await userEvent.clear(selectors.maxCapacityInput());
await userEvent.type(selectors.maxCapacityInput(), '1025');
//V
expect(
screen.getByText(/"capacity" must be less than or equal to 1024/i),
).toBeInTheDocument();
await waitFor(() => {
expect(selectors.continueButton()).toBeDisabled();
});
});
it('should show validation error and disable continue button if the max capacity is not a number', async () => {
//S
mockUseAccountsImplementation();
renderVeeamConfigurationForm();
await userEvent.type(selectors.accountNameInput(), 'Veeam');
await userEvent.type(selectors.repositoryInput(), 'veeam-bucket');
//E
await userEvent.clear(selectors.maxCapacityInput());
await userEvent.type(selectors.maxCapacityInput(), '4.666');
await userEvent.type(selectors.maxCapacityInput(), 'abc');
//V
expect(
screen.getByText(/"capacity" must be an integer/i),
screen.getByText(/"capacity" must be a number/i),
).toBeInTheDocument();
await waitFor(() => {
expect(selectors.continueButton()).toBeDisabled();
});
});
it('should show validation error and disable continue button if the max capacity is empty', async () => {
//S
mockUseAccountsImplementation();
renderVeeamConfigurationForm();
await userEvent.type(selectors.accountNameInput(), 'Veeam');
await userEvent.type(selectors.repositoryInput(), 'veeam-bucket');
//E
await userEvent.clear(selectors.maxCapacityInput());
//V
expect(
screen.getByText(/"capacity" must be a number/i),
).toBeInTheDocument();
await waitFor(() => {
expect(selectors.continueButton()).toBeDisabled();
});
});
it('should show validation error if max capacity as more than 2 decimal points', async () => {
//S
mockUseAccountsImplementation();
renderVeeamConfigurationForm();
await userEvent.type(selectors.accountNameInput(), 'Veeam');
await userEvent.type(selectors.repositoryInput(), 'veeam-bucket');
//E
await userEvent.clear(selectors.maxCapacityInput());
await userEvent.type(selectors.maxCapacityInput(), '1.123');
//V
expect(
screen.getByText(/"capacity" must have at most 2 decimals/i),
).toBeInTheDocument();
await waitFor(() => {
expect(selectors.continueButton()).toBeDisabled();
});
});
});
13 changes: 12 additions & 1 deletion src/react/ui-elements/Veeam/VeeamConfiguration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,18 @@ const schema = Joi.object({
application: Joi.string().required(),
capacity: Joi.when('application', {
is: Joi.equal(VEEAM_BACKUP_REPLICATION_XML_VALUE),
then: Joi.number().required().min(1).max(999).integer(),
then: Joi.number()
.required()
.min(1)
.max(1024)
.custom((value, helpers) => {
if (!Number.isInteger(value * 100)) {
return helpers.message({
custom: '"capacity" must have at most 2 decimals',
});
}
return value;
}),
otherwise: Joi.valid(),
}),
capacityUnit: Joi.when('application', {
Expand Down
8 changes: 5 additions & 3 deletions src/react/ui-elements/Veeam/useCapacityUnit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ export const useCapacityUnit = (
const pBytesCapacity = prettyBytes(capacity, {
locale: 'en',
binary: true,
maximumFractionDigits: 0,
maximumFractionDigits: 2,
});
const capacityValue = pBytesCapacity.split(' ')[0];
const capacityValue = pBytesCapacity.split(' ')[0].replace(',', '');
const capacityUnit = `${unitChoices[pBytesCapacity.split(' ')[1] as Units]}`;
return { capacityValue, capacityUnit };
};
Expand All @@ -23,5 +23,7 @@ export const getCapacityBytes = (
capacityValue: string,
capacityUnit: string,
) => {
return (parseInt(capacityValue, 10) * parseInt(capacityUnit, 10)).toString();
return Math.round(
parseFloat(capacityValue) * parseFloat(capacityUnit),
).toString();
};
Loading