Skip to content

Commit

Permalink
Merge branch 'bugfix/ARTESCA-13547-veeam-capacity-granularity' into q…
Browse files Browse the repository at this point in the history
…/3.0
  • Loading branch information
bert-e committed Oct 2, 2024
2 parents 8d43478 + ccd2510 commit d97219c
Show file tree
Hide file tree
Showing 10 changed files with 174 additions and 27 deletions.
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
"@hapi/joi-date": "^2.0.1",
"@hookform/resolvers": "^2.8.8",
"@monaco-editor/react": "^4.4.5",
"@scality/core-ui": "^0.142.0",
"@scality/core-ui": "^0.145.0",
"@scality/module-federation": "^1.3.2",
"@types/react-table": "^7.7.10",
"@types/react-virtualized": "^9.21.20",
Expand Down
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
92 changes: 84 additions & 8 deletions src/react/ui-elements/Veeam/VeeamConfiguration.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,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 @@ -151,7 +151,7 @@ describe('Veeam Configuration UI', () => {
accountName: 'Veeam',
application: 'Veeam Backup for Microsoft 365 (v6, v7)',
bucketName: 'veeam-bucket',
capacityBytes: '4294967296',
capacityBytes: '4005057004',
enableImmutableBackup: false,
});
});
Expand All @@ -164,15 +164,22 @@ describe('Veeam Configuration UI', () => {

await selectClick(selectors.veeamApplicationSelect());
await userEvent.click(selectors.veeamVBOV8());

expect(
screen.queryByText(/Max Veeam Repository Capacity/i),
).not.toBeInTheDocument();
expect(screen.queryByText(/Immutable Backup/i)).toBeInTheDocument();

await userEvent.type(selectors.accountNameInput(), 'Veeam');
await userEvent.type(selectors.repositoryInput(), 'veeam-bucket');

await userEvent.click(selectors.continueButton());

expect(SUT).toHaveBeenCalledWith({
accountName: 'Veeam',
application: 'Veeam Backup for Microsoft 365 (v8+)',
bucketName: 'veeam-bucket',
capacityBytes: '4294967296',
capacityBytes: '4005057004',
enableImmutableBackup: true,
});
});
Expand Down Expand Up @@ -215,17 +222,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(), '4.666');
await userEvent.type(selectors.maxCapacityInput(), '0');
//V
expect(
screen.getByText(/"capacity" must be an integer/i),
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(), 'abc');
//V
expect(
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 @@ -46,7 +46,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 d97219c

Please sign in to comment.