Skip to content

Commit

Permalink
ZKUI-392: Veeam table Happy path
Browse files Browse the repository at this point in the history
  • Loading branch information
ChengYanJin committed Nov 29, 2023
1 parent cb7ab32 commit 67f7177
Show file tree
Hide file tree
Showing 9 changed files with 133 additions and 55 deletions.
2 changes: 1 addition & 1 deletion src/js/S3Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import OriginalV4Signer from 'aws-sdk/lib/signers/v4';

import { chunkArray } from './utils';
import { isVersioning } from '../react/utils';
const MULTIPART_UPLOAD = {
export const MULTIPART_UPLOAD = {
partSize: 1024 * 1024 * 6,
queueSize: 1,
};
Expand Down
32 changes: 29 additions & 3 deletions src/js/mutations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ import { INSTANCE_ID } from '../react/actions/__tests__/utils/testUtil';
//Subject Under Testing
const SUT = jest.fn();
const instanceId = INSTANCE_ID;
const accountName = 'Veeam-Account';
const accountName = 'Veeam';
const accountNameAlreadyExist = 'Veeam-Account-Error';
const accountEmail = '[email protected]';
const accountId = '749861052561';
const bucketName = 'Veeam-Bucket';
export const bucketName = 'veeam';
const bucketNameWithErrorTriggered = 'Veeam-Bucket-Error';
const userName = 'Veeam-User';
const userNameWithErrorTriggered = 'Veeam-User-Error';
Expand Down Expand Up @@ -91,6 +91,14 @@ export const getVeeamMutationHandler = () => [
//@ts-ignore
const params = new URLSearchParams(req.body);
SUT(params);
// Assume Role
if (params.get('Action') === 'AssumeRoleWithWebIdentity') {
return res(
ctx.status(200),
ctx.xml(`
<AssumeRoleWithWebIdentityResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/"><AssumeRoleWithWebIdentityResult><AssumedRoleUser><Arn>arn:aws:sts::${accountId}:assumed-role/storage-manager-role/ui-9160673b-2c2a-4a6f-a1ef-a3cb6ce25d7f</Arn><AssumedRoleId>OES3SPDIYW4L92S8K1QE6MINE31LQG04:ui-9160673b-2c2a-4a6f-a1ef-a3cb6ce25d7f</AssumedRoleId></AssumedRoleUser><Credentials><SecretAccessKey>v/0Nq1YMw4nNbvtgQlgi0l6m/PXWjlk1VLmn2I5q</SecretAccessKey><AccessKeyId>72SPRZFF71WPWXXUG6XF</AccessKeyId><SessionToken>eyJzYWx0IjoicVIvVGdIdS9FVjJ4TjN5RmtXSnVLZGE0M0krK0g1L3lFVDU5UkV0enpYYz0iLCJ0YWciOiI4d05WRTIwTlQxWTVKbWtZemo2ZGJ3PT0iLCJjaXBoZXJ0ZXh0IjoiQVNIanI0M0VZc3dzK0QwWDFkVXRXQ2JMbzlFOVZ5SzF5WWt6a21lRjRXOUpCU3hwbmNxS21zWnpIU3ZvYlZEYjNKaDRNTm16bW1yVUd6dTU1bmRwMTk0eTVlVjFSVWMzaHZnSTFxZTRuYmJxNHBPdit5V3VZQ3RtSExUbE5BTHpDK3VhYW1tZDdzWk9BVXNKQlhRcmVHUG5sTFphb0kySTFveXJjbk10QlVpb1AvYnNjNUd6RHFqdTFWMjVQRE9PQWgzM2JFSktHdmorbEoyL2lWV0x5UHBQU1pLZmdZUnd1QjRXczdGaG81dHhaem9uWWhpaG9ocnFtdmFnNUJSNytiN2lGN3ZxZjBVSnFPZXI5Wm9ldDk1dlpqL01qTU04aGhGQXI1MmZnTHpzOHAzVlN3dHV0OENFSTBoVEJJNlVycUY4SWxiUmhFOUtlaHo0cnRiZHRKQzVmVHFRSkVPZWltb0RIbGpZZXZqOVlIZzZPVFhDR2ZhVzRIWDc3T0g5M1BRa0dHc1RCSjVpRTEyZEdYQjhYWWdSM1VackIwUzdQejdLQnpvSUVodTZOWUkrK1NPZ2pwMlFaUmhaWGtkbDdDdU5EMWg2UE9qN2twREY0QXhHbWdwcjBMbmpOdVp1UzJaWlJTck5OZG1WL3B5dWpUM3BtcFNJNUZkNW5Wby9SV1dTSGhoR0FVcWRJS0EyV00xdVJ2TkVFS25rb25keWNuVHRrSHpDVUwrN0RtTXNuL202eTcyZjFReHY2VFQyejRzRVFSUDFhWUcwdnBWSTlXbUpWdW5yTFFVNmxSUmpsb1VFSFVkZ2xCMGd1eTZGZTNYR29YQjdVc1J5UUpxbEJ0elpvdFdkR1AvSjZaMllNODFDSy8zZjJZTXVnNTZlbXQxTmJJZ0hrVWxnaGxpclRsNVdrckVRbG5XTW4zT2dzRk9wMjJKSTV1UGoxSENUMlNhTlBXZEQrY0VCcTZycC9tc1FDOW02Q3prSkMwTWMyclZ0RmdnZitaSEV5dVZvdEVzeUFtY1V5QTdFZzJtY3BXd1pnbHZrYkZQQmI5M2NDN0ZhZGhpNEUzQ0hQbm9BczF5eVNKNkxIOTZZTHJoaXk1Q1h3VWloSTlRdmxuSDNlV3EwaElBZTFGc2N6bThzVWRCTGU2SWlVc3ZJVDlpMjJzcTZnaVpmdld5czVlaU9NZzRQMFBaZCtPK0VDRmdmd3dxdUhYcFdEL1F5RnR1RENVb0xxblNyMU9lOHdCQ2lXNDFYaGtacmEzWTdtVW1QYXlNWTN6MXpOZm1XRllJV0dWZzlBNVFUaUZLWGlZTngxdUtWZGJ3Qk54SmowVmxlTTE4azlDNFR3Z2U3dVYyKzEzcWVkTW5xOUpLa2Uwa1NsNmMxMWM5N1RUbGJ4TUx5YS9WY1JLWkNkbHJaTGZNK0hjSTJWaGdkSzNzWHJIVEN6UENFRC9lMVBRTkg1RVZBVThLRlNHWGEzb1dPWm9VcmlSYlk1L3R5eTQvbHRKTkVhNnV3R2hra0ljR3JLMjltUndkaDJHSE94R1laYmdGL0VVUDYreUs5cjQrVzc5Y1RYc3NRcEpSM1M1bkZpUHE2bHR6NXM1ZlNYalNkcUxSM0gvTVZlcXV6K3RON0czMk1ieW9halZvcVJxcks2WjZIVm1vM3pDZ1M4TURQQk9jVkY3Ymc0QmhXaXFUTjc5a0ZqV0xkWWZSVlB5Qk1VaXBHNmZCcGlBdUZCZEV1S2lLMHBwVkhQNUpZL0h5ZXRBbVgxMzdVK1U5d3prbmw3eXhyOEQ0TkdNL05yaVhBT21hSDN4YVEifQ==</SessionToken><Expiration>2023-11-28T10:16:13Z</Expiration></Credentials><Provider>www.scality.com</Provider></AssumeRoleWithWebIdentityResult><ResponseMetadata><RequestId>8e94c64ebf4486567b0e</RequestId></ResponseMetadata></AssumeRoleWithWebIdentityResponse>`),
);
}
if (params.get('Action') === 'CreateUser') {
if (params.get('UserName') === userNameWithErrorTriggered) {
return res(
Expand Down Expand Up @@ -133,6 +141,7 @@ export const getVeeamMutationHandler = () => [
);
}
return res(
ctx.status(200),
ctx.xml(`
<AttachUserPolicyResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
<ResponseMetadata>
Expand Down Expand Up @@ -171,8 +180,9 @@ export const getVeeamMutationHandler = () => [
);
}
}),
// putBucketTagging

rest.put(`${TEST_API_BASE_URL}/${bucketName}`, (req, res, ctx) => {
// putBucketTagging
if (req.url.searchParams.get('tagging') === '') {
SUT(req.body);
return res(
Expand All @@ -185,6 +195,8 @@ export const getVeeamMutationHandler = () => [
`),
);
}
//create bucket
return res(ctx.status(200));
}),
rest.put(
`${TEST_API_BASE_URL}/${bucketNameWithErrorTriggered}`,
Expand All @@ -201,6 +213,20 @@ export const getVeeamMutationHandler = () => [
},
),
// putObject
rest.put(
`${TEST_API_BASE_URL}/${bucketName}/${VEEAM_XML_PREFIX}`,
(req, res, ctx) => {
SUT(req.body);
return res(ctx.status(200));
},
),
rest.put(
`${TEST_API_BASE_URL}/${bucketName}/${VEEAM_XML_PREFIX}/capacity.xml`,
(req, res, ctx) => {
SUT(req.body);
return res(ctx.status(200));
},
),
rest.put(
`${TEST_API_BASE_URL}/${bucketName}/${veeamObjectKey}`,
(req, res, ctx) => {
Expand Down
11 changes: 4 additions & 7 deletions src/react/DataServiceRoleProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,17 +133,14 @@ const DataServiceRoleProvider = ({ children }: { children: JSX.Element }) => {
const { getS3Config } = useS3ConfigFromAssumeRoleResult();

const setRole = (role: { roleArn: string }) => {
setRoleArnStored(role.roleArn);
setRoleState(role);
if (role.roleArn) {
assumeRoleMutation.mutate(role.roleArn, {
onSuccess: () => {
setRoleArnStored(role.roleArn);
setRoleState(role);
},
});
assumeRoleMutation.mutate(role.roleArn, {});
}
};

const setRolePromise = (role: { roleArn: string }) => {
const setRolePromise = async (role: { roleArn: string }) => {
if (!role.roleArn) {
return Promise.reject('Invalid role arn');
}
Expand Down
8 changes: 6 additions & 2 deletions src/react/ui-elements/Veeam/VeeamConfiguration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ import { VEEAM_STEPS, VeeamStepsIndexes } from './VeeamSteps';
import { useXCoreLibrary } from '../../next-architecture/ui/XCoreLibraryProvider';
import { useXcoreConfig } from '../../next-architecture/ui/ConfigProvider';
import prettyBytes from 'pretty-bytes';
import { VEEAM_BACKUP_REPLICATION, VEEAM_OFFICE_365 } from './VeeamConstants';
import {
VEEAM_BACKUP_REPLICATION,
VEEAM_DEFAULT_ACCOUNT_NAME,
VEEAM_OFFICE_365,
} from './VeeamConstants';
import { useOutsideClick } from '../../utils/hooks';
import { useDispatch, useSelector } from 'react-redux';
import { AppState } from '../../../types/state';
Expand Down Expand Up @@ -190,7 +194,7 @@ const Configuration = () => {
error={errors.bucketName?.message ?? ''}
content={
<Input
id="name"
id="bucketName"
type="text"
autoComplete="off"
placeholder="Veeam bucket name"
Expand Down
5 changes: 4 additions & 1 deletion src/react/ui-elements/Veeam/VeeamSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Clipboard } from '../Clipboard';
import { HideCredential } from '../Hide';
import Table, * as T from '../TableKeyValue';
import { useGetS3ServicePoint } from './useGetS3ServicePoint';
import { VEEAM_DEFAULT_ACCOUNT_NAME } from './VeeamConstants';

type VeeamSummaryProps = {
bucketName: string;
Expand Down Expand Up @@ -52,7 +53,9 @@ export const VeeamSummary = ({
label={'Complete'}
icon={<Icon name="Arrow-right" />}
onClick={() => {
history.push(`/accounts/Veeam/buckets/${bucketName}`);
history.push(
`/accounts/${VEEAM_DEFAULT_ACCOUNT_NAME}/buckets/${bucketName}`,
);
}}
/>
</Stack>
Expand Down
76 changes: 52 additions & 24 deletions src/react/ui-elements/Veeam/VeeamTable.test.tsx
Original file line number Diff line number Diff line change
@@ -1,70 +1,98 @@
import { render, screen, waitFor, within } from '@testing-library/react';
import VeeamTable from './VeeamTable';
import VeeamConfiguration from './VeeamConfiguration';
import { QueryClient, QueryClientProvider } from 'react-query';
import { NewWrapper, mockOffsetSize } from '../../utils/testUtil';
import {
NewWrapper,
TEST_API_BASE_URL,
mockOffsetSize,
} from '../../utils/testUtil';
import { Stepper } from '@scality/core-ui';
import { actions } from './useMutationTableData';
import { setupServer } from 'msw/node';
import { getVeeamMutationHandler } from '../../../js/mutations.test';
import { debug } from 'jest-preview';
jest.setTimeout(30000);
import {
bucketName,
getVeeamMutationHandler,
} from '../../../js/mutations.test';
import { getConfigOverlay } from '../../../js/mock/managementClientMSWHandlers';
import { INSTANCE_ID } from '../../actions/__tests__/utils/testUtil';
import Configuration from './VeeamConfiguration';
import userEvent from '@testing-library/user-event';

jest.setTimeout(30_000);
describe('VeeamTable', () => {
const server = setupServer(...getVeeamMutationHandler());
const server = setupServer(
...getVeeamMutationHandler(),
getConfigOverlay(TEST_API_BASE_URL, INSTANCE_ID),
);
beforeAll(() => {
server.listen();
mockOffsetSize(500, 500);
server.listen({ onUnhandledRequest: 'error' });
mockOffsetSize(600, 800);
});
afterEach(() => {
server.resetHandlers();
});
afterAll(() => server.close());

const selectors = {
setBucketName: () => screen.getByLabelText(/Bucket name*/i),
cancelButton: () => screen.getByRole('button', { name: /Exit/i }),
continueButton: () => screen.getByRole('button', { name: /Continue/i }),
veeamConfigActionTable: () =>
screen.getByText('Configure ARTESCA for Veeam'),
allRows: () => screen.getAllByRole('row').filter((_, index) => index > 0),
};

it.only('should render the Veeam table', async () => {
render(
<QueryClientProvider client={new QueryClient()}>
<Stepper
steps={[
{
label: 'Action Status',
Component: VeeamTable,
},
]}
/>
</QueryClientProvider>,
<Stepper
steps={[
{
label: 'Configuration',
Component: Configuration,
},
{
label: 'Action Status',
Component: VeeamTable,
},
]}
/>,
{ wrapper: NewWrapper() },
);

expect(selectors.allRows()).toHaveLength(actions.length);
//E
//type the bucket name in configuration form
userEvent.type(selectors.setBucketName(), bucketName);
await waitFor(() => {
expect(selectors.continueButton()).toBeEnabled();
});
userEvent.click(selectors.continueButton());

//V
// Veeam action table
await waitFor(() => {
expect(selectors.veeamConfigActionTable()).toBeInTheDocument();
});

// initial state
selectors.allRows().forEach((row, index) => {
const cells = within(row).getAllByRole('gridcell');
expect(cells).toHaveLength(3);
expect(cells[0]).toHaveTextContent(`${index + 1}`);
expect(cells[1]).toHaveTextContent(actions[index]);
expect(cells[2]).toHaveTextContent(/Pending.../i);
//TODO: Sometimes it's too fast and the status is already success
});
expect(selectors.allRows()).toHaveLength(actions.length);
expect(selectors.cancelButton()).toBeDisabled();
expect(selectors.continueButton()).toBeDisabled();

// wait for all the actions to be completed
for (let i = 0; i < actions.length; i++) {
console.log('index', 1);
await waitFor(() => {
expect(
within(selectors.allRows()[i]).getAllByRole('gridcell')[2],
).toHaveTextContent('Success');
});
}
debug();
// expect(selectors.cancelButton()).toBeDisabled();
// expect(selectors.continueButton()).toBeEnabled();
});

it('should retry the failed actions', async () => {});
Expand Down
12 changes: 8 additions & 4 deletions src/react/ui-elements/Veeam/VeeamTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useHistory } from 'react-router-dom';
import { useTheme } from 'styled-components';
import { VEEAM_STEPS, VeeamStepsIndexes } from './VeeamSteps';
import { useMutationTableData } from './useMutationTableData';
import { useQueryClient } from 'react-query';

export type VeeamTableProps = {
accountName: string;
Expand All @@ -25,9 +26,11 @@ export default function VeeamTable(propsConfiguration: VeeamTableProps) {

const theme = useTheme();
const history = useHistory();
const queryClient = useQueryClient();
const { next } = useStepper(VeeamStepsIndexes.Table, VEEAM_STEPS);
const { data } = useMutationTableData({
propsConfiguration: propsConfiguration,
const { bucketName, enableImmutableBackup } = propsConfiguration;
const { data, accessKey, secretKey } = useMutationTableData({
propsConfiguration,
});

const columns: Column<(typeof data)[number]>[] = [
Expand Down Expand Up @@ -81,7 +84,7 @@ export default function VeeamTable(propsConfiguration: VeeamTableProps) {
},
];

const isCancellable = data.some((row) => row.status !== undefined);
const isCancellable = data.some((row) => row.status === 'error');
const isContinue = data.every((row) => row.status === 'success');

if (confirmCancel) {
Expand Down Expand Up @@ -140,7 +143,8 @@ export default function VeeamTable(propsConfiguration: VeeamTableProps) {
label={'Continue'}
icon={<Icon name="Arrow-right" />}
onClick={() => {
next({}); //TODO
queryClient.invalidateQueries(['WebIdentityRoles']);
next({ bucketName, enableImmutableBackup, accessKey, secretKey });
}}
/>
</Stack>
Expand Down
34 changes: 22 additions & 12 deletions src/react/ui-elements/Veeam/useMutationTableData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,22 @@ export const actions = [
'Set SOS API capacity',
] as const;

export const useMutationTableData = ({
propsConfiguration,
}: {
propsConfiguration: VeeamTableProps;
}): {
type Result = {
data: {
step: number;
action: string;
status: 'success' | 'error' | 'loading' | 'idle';
retry: () => void;
}[];
} => {
accessKey: string;
secretKey: string;
};

export const useMutationTableData = ({
propsConfiguration,
}: {
propsConfiguration: VeeamTableProps;
}): Result => {
const setRolePromise = useSetAssumedRolePromise();

const assumeRoleMutation = useMutation({
Expand Down Expand Up @@ -152,22 +156,20 @@ export const useMutationTableData = ({
return {
Bucket: propsConfiguration.bucketName,
Key: `${VEEAM_XML_PREFIX}/`,
Body: new Blob(['']),
Body: '',
};
} else if (isStep('Enable SOS API support')) {
return {
Bucket: propsConfiguration.bucketName,
Key: `${VEEAM_XML_PREFIX}/system.xml`,
Body: new Blob([SYSTEM_XML_CONTENT]),
Body: SYSTEM_XML_CONTENT,
ContentType: 'text/xml',
};
} else if (isStep('Set SOS API capacity')) {
return {
Bucket: propsConfiguration.bucketName,
Key: `${VEEAM_XML_PREFIX}/capacity.xml`,
Body: new Blob([
GET_CAPACITY_XML_CONTENT(propsConfiguration.capacity),
]),
Body: GET_CAPACITY_XML_CONTENT(propsConfiguration.capacity),
ContentType: 'text/xml',
};
}
Expand All @@ -190,5 +192,13 @@ export const useMutationTableData = ({
retry: mutationsWithRetry[index].retry,
};
});
return { data };
const accessKeyMutationIndex = actions.findIndex(
(action) => action === 'Generate Access key and Secret key',
);
return {
data,
accessKey: mutations[accessKeyMutationIndex].data?.AccessKey?.AccessKeyId,
secretKey:
mutations[accessKeyMutationIndex].data?.AccessKey?.SecretAccessKey,
};
};
8 changes: 7 additions & 1 deletion src/react/utils/testUtil.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -529,8 +529,14 @@ const DataServiceProvider = ({ children }) => {
const role = {
roleArn: TEST_ROLE_ARN,
};
const setRole = (role: { roleArn: string }) => {
assumeRoleMutation.mutate(role.roleArn, {});
};
const setRolePromise = async (role: { roleArn: string }) => {
return getQuery(role.roleArn).queryFn();
};
return (
<_DataServiceRoleContext.Provider value={{ role, assumeRoleMutation }}>
<_DataServiceRoleContext.Provider value={{ role, setRole, setRolePromise }}>
{children}
</_DataServiceRoleContext.Provider>
);
Expand Down

0 comments on commit 67f7177

Please sign in to comment.