Skip to content

Commit

Permalink
Merge pull request #783 from scality/bugfix/ARTESCA-13547-veeam-capac…
Browse files Browse the repository at this point in the history
…ity-granularity-cherry-pick

allow Veeam capacity to have decimal:
  • Loading branch information
xaf-scality authored Oct 7, 2024
2 parents 97cd46c + b572b07 commit a9b562d
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 18 deletions.
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();
};

0 comments on commit a9b562d

Please sign in to comment.