Skip to content

Commit

Permalink
ZKUI-392 & ZKUI-395: Add useChainedMutation hook and implement the Ve…
Browse files Browse the repository at this point in the history
…eam action table and test
  • Loading branch information
ChengYanJin committed Dec 1, 2023
1 parent 19fb7f6 commit ffe1a90
Show file tree
Hide file tree
Showing 22 changed files with 798 additions and 229 deletions.
10 changes: 5 additions & 5 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 @@ -76,7 +76,7 @@
"@hookform/resolvers": "^2.8.8",
"@js-temporal/polyfill": "^0.4.3",
"@monaco-editor/react": "^4.4.5",
"@scality/core-ui": "github:scality/core-ui#0.105.0",
"@scality/core-ui": "github:scality/core-ui#0.106.0",
"@scality/module-federation": "github:scality/module-federation#1.1.0",
"@types/react-table": "^7.7.10",
"@types/react-virtualized": "^9.21.20",
Expand Down
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
6 changes: 5 additions & 1 deletion src/js/mock/S3ClientMSWHandlers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { rest } from 'msw';
import { zenkoUITestConfig } from '../../react/utils/testUtil';
import {
BUCKET_TAG_VEEAM_APPLICATION,
VEEAM_BACKUP_REPLICATION_XML_VALUE,
} from '../../react/ui-elements/Veeam/VeeamConstants';

export const frozenDate = new Date('2021-01-01T00:00:00.000Z');
export const defaultMockedBuckets = [
Expand Down Expand Up @@ -226,7 +230,7 @@ export const mockGetBucketTagging = (bucketName: string) => {
<?xml version="1.0" encoding="UTF-8"?>
<Tagging>
<TagSet>
<Tag><Key>X-Scality-Usecase</Key><Value>Veeam 12</Value></Tag>
<Tag><Key>${BUCKET_TAG_VEEAM_APPLICATION}</Key><Value>${VEEAM_BACKUP_REPLICATION_XML_VALUE}</Value></Tag>
</TagSet>
</Tagging>`),
);
Expand Down
134 changes: 115 additions & 19 deletions src/js/mutations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
useCreatePolicyMutation,
usePutObjectMutation,
usePutBucketTaggingMutation,
useCreateUserAccessKeyMutation,
} from './mutations';
import { NewWrapper, TEST_API_BASE_URL } from '../react/utils/testUtil';
import {
Expand All @@ -17,15 +18,16 @@ import {
VEEAM_IMMUTABLE_POLICY_NAME,
VEEAM_XML_PREFIX,
} from '../react/ui-elements/Veeam/VeeamConstants';
import { INSTANCE_ID } from '../react/actions/__tests__/utils/testUtil';

//Subject Under Testing
const SUT = jest.fn();
const instanceId = 'a5c1ad24-27a2-4aaf-a609-26b708729363';
const accountName = 'Veeam-Account';
const instanceId = INSTANCE_ID;
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 All @@ -43,7 +45,7 @@ const veeamPolicyArn = `arn:aws:iam::${accountId}:policy/${veeamPolicyName}`;
const policyNameWithErrorTriggered = `${VEEAM_IMMUTABLE_POLICY_NAME}-${bucketNameWithErrorTriggered}`;
const veeamObjectKey = `${VEEAM_XML_PREFIX}/system.xml`;

const server = setupServer(
export const getVeeamMutationHandler = () => [
// create endpoint
rest.post(
`${TEST_API_BASE_URL}/api/v1/config/${instanceId}/endpoint`,
Expand Down Expand Up @@ -85,11 +87,18 @@ const server = setupServer(
);
},
),

rest.post(`${TEST_API_BASE_URL}/`, (req, res, ctx) => {
//@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 @@ -123,6 +132,26 @@ const server = setupServer(
);
}
if (params.get('Action') === 'AttachUserPolicy') {
if (params.get('UserName') === userNameWithErrorTriggered) {
return res(
ctx.status(400),
ctx.xml(
`<ErrorResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/"><Error><Code>ValidationError</Code><Message>The specified value is invalid.</Message></Error><RequestId>caeb1338404c9f821a2d</RequestId></ErrorResponse>`,
),
);
}
return res(
ctx.status(200),
ctx.xml(`
<AttachUserPolicyResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
<ResponseMetadata>
<RequestId>2e30c3c68e45ad7122f7</RequestId>
</ResponseMetadata>
</AttachUserPolicyResponse>;
`),
);
}
if (params.get('Action') === 'CreateAccessKey') {
if (params.get('UserName') === userNameWithErrorTriggered) {
return res(
ctx.status(400),
Expand All @@ -133,29 +162,41 @@ const server = setupServer(
}
return res(
ctx.xml(`
<AttachUserPolicyResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
<ResponseMetadata>
<RequestId>2e30c3c68e45ad7122f7</RequestId>
</ResponseMetadata>
</AttachUserPolicyResponse>;
`),
<CreateAccessKeyResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
<CreateAccessKeyResult>
<AccessKey>
<UserName>${userName}</UserName>
<AccessKeyId>AKIAIOSFODNN7EXAMPLE</AccessKeyId>
<Status>Active</Status>
<SecretAccessKey>wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY</SecretAccessKey>
<CreateDate>2023-11-15T14:29:06Z</CreateDate>
</AccessKey>
</CreateAccessKeyResult>
<ResponseMetadata>
<RequestId>2e30c3c68e45ad7122f7</RequestId>
</ResponseMetadata>
</CreateAccessKeyResponse>;
`),
);
}
}),
// putBucketTagging

rest.put(`${TEST_API_BASE_URL}/${bucketName}`, (req, res, ctx) => {
// putBucketTagging
if (req.url.searchParams.get('tagging') === '') {
SUT(req.body);
return res(
ctx.xml(`
<PutBucketTaggingResponse xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<ResponseMetadata>
<RequestId>2e30c3c68e45ad7122f7</RequestId>
</ResponseMetadata>
</PutBucketTaggingResponse>;
`),
<PutBucketTaggingResponse xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<ResponseMetadata>
<RequestId>2e30c3c68e45ad7122f7</RequestId>
</ResponseMetadata>
</PutBucketTaggingResponse>;
`),
);
}
//create bucket
return res(ctx.status(200));
}),
rest.put(
`${TEST_API_BASE_URL}/${bucketNameWithErrorTriggered}`,
Expand All @@ -172,6 +213,20 @@ const server = setupServer(
},
),
// 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 All @@ -191,7 +246,8 @@ const server = setupServer(
);
},
),
);
];
const server = setupServer(...getVeeamMutationHandler());

beforeAll(() => server.listen());
afterEach(() => {
Expand Down Expand Up @@ -536,4 +592,44 @@ describe('mutations', () => {
});
expect(SUT).toHaveBeenCalledWith(SYSTEM_XML_CONTENT);
});
it('should handle the useCreateUserAccessKeyMutation', async () => {
//Setup
const { result, waitFor } = renderHook(
() => useCreateUserAccessKeyMutation(),
{ wrapper: NewWrapper() },
);
//Exercise
result.current.mutate({ userName });
//Verify
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
expect(SUT).toHaveBeenCalledWith(
new URLSearchParams({
Action: 'CreateAccessKey',
UserName: userName,
Version: '2010-05-08',
}),
);
});
it('should handle the error case of useCreateUserAccessKeyMutation', async () => {
//Setup
const { result, waitFor } = renderHook(
() => useCreateUserAccessKeyMutation(),
{ wrapper: NewWrapper() },
);
//Exercise
result.current.mutate({ userName: userNameWithErrorTriggered });
//Verify
await waitFor(() => {
expect(result.current.isError).toBe(true);
});
expect(SUT).toHaveBeenCalledWith(
new URLSearchParams({
Action: 'CreateAccessKey',
UserName: userNameWithErrorTriggered,
Version: '2010-05-08',
}),
);
});
});
19 changes: 17 additions & 2 deletions src/js/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useIAMClient } from '../react/IAMProvider';
import { TagSetItem } from '../types/s3';
import { S3 } from 'aws-sdk';
import { notFalsyTypeGuard } from '../types/typeGuards';
import { MULTIPART_UPLOAD } from './S3Client';

const useCreateEndpointMutation = () => {
const managementClient = useManagementClient();
Expand Down Expand Up @@ -103,11 +104,24 @@ const usePutBucketTaggingMutation = () => {

const usePutObjectMutation = () => {
const s3Client = useS3Client();
return useMutation(({ Bucket, Key, Body }: S3.PutObjectRequest) =>
s3Client.putObject({ Bucket, Key, Body }).promise(),
const options = {
partSize: MULTIPART_UPLOAD.partSize,
queueSize: MULTIPART_UPLOAD.queueSize,
};
return useMutation(
({ Bucket, Key, Body, ContentType }: S3.PutObjectRequest) =>
s3Client.upload({ Bucket, Key, Body, ContentType }, options).promise(),
);
};

const useCreateUserAccessKeyMutation = () => {
const IAMClient = useIAMClient();
return useMutation({
mutationFn: ({ userName }: { userName: string }) =>
IAMClient.createAccessKey(userName),
});
};

export {
useCreateEndpointMutation,
useCreateIAMUserMutation,
Expand All @@ -116,4 +130,5 @@ export {
useAttachPolicyToUserMutation,
usePutBucketTaggingMutation,
usePutObjectMutation,
useCreateUserAccessKeyMutation,
};
56 changes: 56 additions & 0 deletions src/js/useChainedMutations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { useRef } from 'react';

type MinimalMutation<TVariables = unknown, TData = unknown> = {
mutate: (
variables: TVariables,
mutationOptions: { onSuccess: (data: TData) => void },
) => void;
isSuccess: boolean;
data: TData;
};

export const useChainedMutations = ({
mutations,
}: {
mutations: MinimalMutation[];
}): {
mutate: (
computeVariablesForNext: (results: unknown[]) => unknown,
index?: number,
) => unknown;
mutationsWithRetry: (MinimalMutation & {
status: 'loading' | 'success' | 'error';
retry: () => void;
})[];
} => {
const mutationsWithRetry = useRef<
(MinimalMutation & {
status: 'loading' | 'success' | 'error';
retry: () => void;
})[]
//@ts-expect-error initial value
>(mutations);
const go = (
computeVariablesForNext: (results: unknown[]) => unknown,
results: unknown[] = [],
) => {
const index = results.length;
mutationsWithRetry.current[index].retry = () => {
mutations[index].mutate(computeVariablesForNext(results), {
onSuccess: (data) => {
if (index < mutations.length - 1) {
go(computeVariablesForNext, [...results, data]);
}
},
});
};
mutations[index].mutate(computeVariablesForNext(results), {
onSuccess: (data) => {
if (index < mutations.length - 1) {
go(computeVariablesForNext, [...results, data]);
}
},
});
};
return { mutate: go, mutationsWithRetry: mutationsWithRetry.current };
};
Loading

0 comments on commit ffe1a90

Please sign in to comment.