@scality/data-browser-library 1.1.10 → 1.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/__tests__/BucketCorsPage.test.js +14 -6
- package/dist/components/__tests__/BucketLifecycleFormPage.test.js +129 -0
- package/dist/components/__tests__/BucketLifecycleList.test.js +78 -0
- package/dist/components/__tests__/BucketNotificationFormPage.test.js +15 -3
- package/dist/components/buckets/BucketCorsPage.js +5 -7
- package/dist/components/buckets/BucketLifecycleFormPage.js +18 -12
- package/dist/components/buckets/BucketLifecycleList.js +5 -5
- package/dist/components/buckets/BucketOverview.js +1 -1
- package/dist/components/buckets/BucketPolicyPage.js +4 -7
- package/dist/components/buckets/BucketReplicationFormPage.js +12 -6
- package/dist/components/buckets/BucketVersioning.js +1 -1
- package/dist/components/buckets/notifications/BucketNotificationFormPage.js +2 -2
- package/dist/components/buckets/notifications/BucketNotificationList.js +1 -1
- package/dist/components/objects/DeleteObjectButton.js +1 -1
- package/dist/components/objects/GetPresignedUrlButton.js +1 -1
- package/dist/components/objects/ObjectDetails/ObjectMetadata.js +1 -1
- package/dist/components/objects/ObjectDetails/ObjectSummary.js +118 -7
- package/dist/components/objects/ObjectDetails/ObjectTags.js +1 -1
- package/dist/components/objects/ObjectDetails/__tests__/ObjectSummary.test.js +142 -3
- package/dist/components/objects/ObjectList.js +42 -16
- package/dist/components/objects/ObjectLock/ObjectLockSettings.js +1 -1
- package/dist/components/objects/__tests__/GetPresignedUrlButton.test.js +30 -1
- package/dist/components/providers/QueryProvider.js +2 -1
- package/dist/config/types.d.ts +4 -0
- package/dist/hooks/bucketConfiguration.js +15 -15
- package/dist/hooks/bucketOperations.js +1 -1
- package/dist/hooks/factories/__tests__/useCreateS3FunctionMutationHook.test.js +7 -35
- package/dist/hooks/factories/__tests__/useCreateS3InfiniteQueryHook.test.js +1 -1
- package/dist/hooks/factories/__tests__/useCreateS3MutationHook.test.js +1 -1
- package/dist/hooks/factories/__tests__/useCreateS3QueryHook.test.js +1 -1
- package/dist/hooks/factories/useCreateS3InfiniteQueryHook.d.ts +1 -1
- package/dist/hooks/factories/useCreateS3InfiniteQueryHook.js +2 -2
- package/dist/hooks/factories/useCreateS3MutationHook.d.ts +3 -3
- package/dist/hooks/factories/useCreateS3MutationHook.js +20 -8
- package/dist/hooks/factories/useCreateS3QueryHook.d.ts +1 -1
- package/dist/hooks/factories/useCreateS3QueryHook.js +2 -2
- package/dist/hooks/objectOperations.js +6 -6
- package/dist/hooks/presignedOperations.js +1 -1
- package/dist/hooks/useDeleteFolder.js +1 -1
- package/dist/test/utils/errorHandling.test.js +45 -33
- package/dist/utils/__tests__/coldStorage.test.d.ts +1 -0
- package/dist/utils/__tests__/coldStorage.test.js +24 -0
- package/dist/utils/coldStorage.d.ts +12 -0
- package/dist/utils/coldStorage.js +23 -0
- package/dist/utils/errorHandling.d.ts +2 -1
- package/dist/utils/errorHandling.js +8 -7
- package/package.json +1 -1
|
@@ -5,40 +5,40 @@ const useGetBucketAcl = useCreateS3QueryHook(GetBucketAclCommand, 'GetBucketAcl'
|
|
|
5
5
|
const useSetBucketAcl = useCreateS3MutationHook(PutBucketAclCommand, 'PutBucketAcl', [
|
|
6
6
|
'GetBucketAcl',
|
|
7
7
|
'ListBuckets'
|
|
8
|
-
]);
|
|
9
|
-
const useGetBucketPolicy = useCreateS3QueryHook(GetBucketPolicyCommand, 'GetBucketPolicy');
|
|
8
|
+
], 'update bucket visibility');
|
|
9
|
+
const useGetBucketPolicy = useCreateS3QueryHook(GetBucketPolicyCommand, 'GetBucketPolicy', 'load the bucket policy');
|
|
10
10
|
const useSetBucketPolicy = useCreateS3MutationHook(PutBucketPolicyCommand, 'PutBucketPolicy', [
|
|
11
11
|
'GetBucketPolicy',
|
|
12
12
|
'ListBuckets'
|
|
13
|
-
]);
|
|
13
|
+
], 'save the bucket policy');
|
|
14
14
|
const useDeleteBucketPolicy = useCreateS3MutationHook(DeleteBucketPolicyCommand, 'DeleteBucketPolicy', [
|
|
15
15
|
'GetBucketPolicy'
|
|
16
|
-
]);
|
|
16
|
+
], 'delete the bucket policy');
|
|
17
17
|
const useGetBucketVersioning = useCreateS3QueryHook(GetBucketVersioningCommand, 'GetBucketVersioning');
|
|
18
18
|
const useSetBucketVersioning = useCreateS3MutationHook(PutBucketVersioningCommand, 'PutBucketVersioning', [
|
|
19
19
|
'GetBucketVersioning'
|
|
20
|
-
]);
|
|
21
|
-
const useGetBucketCors = useCreateS3QueryHook(GetBucketCorsCommand, 'GetBucketCors');
|
|
20
|
+
], 'update bucket versioning');
|
|
21
|
+
const useGetBucketCors = useCreateS3QueryHook(GetBucketCorsCommand, 'GetBucketCors', 'load CORS configuration');
|
|
22
22
|
const useSetBucketCors = useCreateS3MutationHook(PutBucketCorsCommand, 'PutBucketCors', [
|
|
23
23
|
'GetBucketCors',
|
|
24
24
|
'ListBuckets'
|
|
25
|
-
]);
|
|
25
|
+
], 'save the CORS configuration');
|
|
26
26
|
const useDeleteBucketCors = useCreateS3MutationHook(DeleteBucketCorsCommand, 'DeleteBucketCors', [
|
|
27
27
|
'GetBucketCors'
|
|
28
|
-
]);
|
|
28
|
+
], 'delete the CORS rule');
|
|
29
29
|
const useGetBucketLifecycle = useCreateS3QueryHook(GetBucketLifecycleConfigurationCommand, 'GetBucketLifecycleConfiguration');
|
|
30
30
|
const useSetBucketLifecycle = useCreateS3MutationHook(PutBucketLifecycleConfigurationCommand, 'PutBucketLifecycleConfiguration', [
|
|
31
31
|
'GetBucketLifecycleConfiguration',
|
|
32
32
|
'ListBuckets'
|
|
33
|
-
]);
|
|
33
|
+
], 'save the lifecycle rule');
|
|
34
34
|
const useDeleteBucketLifecycle = useCreateS3MutationHook(DeleteBucketLifecycleCommand, 'DeleteBucketLifecycle', [
|
|
35
35
|
'GetBucketLifecycleConfiguration'
|
|
36
|
-
]);
|
|
37
|
-
const useGetBucketNotification = useCreateS3QueryHook(GetBucketNotificationConfigurationCommand, 'GetBucketNotificationConfiguration');
|
|
36
|
+
], 'delete the lifecycle rule');
|
|
37
|
+
const useGetBucketNotification = useCreateS3QueryHook(GetBucketNotificationConfigurationCommand, 'GetBucketNotificationConfiguration', 'load the notification configuration');
|
|
38
38
|
const useSetBucketNotification = useCreateS3MutationHook(PutBucketNotificationConfigurationCommand, 'PutBucketNotificationConfiguration', [
|
|
39
39
|
'GetBucketNotificationConfiguration',
|
|
40
40
|
'ListBuckets'
|
|
41
|
-
]);
|
|
41
|
+
], 'save the notification configuration');
|
|
42
42
|
const useGetBucketEncryption = useCreateS3QueryHook(GetBucketEncryptionCommand, 'GetBucketEncryption');
|
|
43
43
|
const useSetBucketEncryption = useCreateS3MutationHook(PutBucketEncryptionCommand, 'PutBucketEncryption', [
|
|
44
44
|
'GetBucketEncryption',
|
|
@@ -55,15 +55,15 @@ const useDeleteBucketTagging = useCreateS3MutationHook(DeleteBucketTaggingComman
|
|
|
55
55
|
const useGetBucketObjectLockConfiguration = useCreateS3QueryHook(GetObjectLockConfigurationCommand, 'GetObjectLockConfiguration');
|
|
56
56
|
const useSetBucketObjectLockConfiguration = useCreateS3MutationHook(PutObjectLockConfigurationCommand, 'PutObjectLockConfiguration', [
|
|
57
57
|
'GetObjectLockConfiguration'
|
|
58
|
-
]);
|
|
58
|
+
], 'save Object Lock settings');
|
|
59
59
|
const useGetBucketReplication = useCreateS3QueryHook(GetBucketReplicationCommand, 'GetBucketReplication');
|
|
60
60
|
const useSetBucketReplication = useCreateS3MutationHook(PutBucketReplicationCommand, 'PutBucketReplication', [
|
|
61
61
|
'GetBucketReplication',
|
|
62
62
|
'ListBuckets'
|
|
63
|
-
]);
|
|
63
|
+
], 'save the replication rule');
|
|
64
64
|
const useDeleteBucketReplication = useCreateS3MutationHook(DeleteBucketReplicationCommand, 'DeleteBucketReplication', [
|
|
65
65
|
'GetBucketReplication'
|
|
66
|
-
]);
|
|
66
|
+
], 'delete the replication rule');
|
|
67
67
|
const useGetPublicAccessBlock = useCreateS3QueryHook(GetPublicAccessBlockCommand, 'GetPublicAccessBlock');
|
|
68
68
|
const usePutPublicAccessBlock = useCreateS3MutationHook(PutPublicAccessBlockCommand, 'PutPublicAccessBlock', [
|
|
69
69
|
'GetPublicAccessBlock',
|
|
@@ -5,7 +5,7 @@ const useBuckets = useCreateS3QueryHook(ListBucketsCommand, 'ListBuckets');
|
|
|
5
5
|
const useGetBucketLocation = useCreateS3QueryHook(GetBucketLocationCommand, 'GetBucketLocation');
|
|
6
6
|
const useCreateBucket = useCreateS3MutationHook(CreateBucketCommand, 'CreateBucket', [
|
|
7
7
|
'ListBuckets'
|
|
8
|
-
]);
|
|
8
|
+
], 'create the bucket');
|
|
9
9
|
const useDeleteBucket = useCreateS3MutationHook(DeleteBucketCommand, 'DeleteBucket', [
|
|
10
10
|
'ListBuckets'
|
|
11
11
|
]);
|
|
@@ -61,9 +61,13 @@ describe('useCreateS3FunctionMutationHook - Factory Specific', ()=>{
|
|
|
61
61
|
expect(mockOperation).toHaveBeenCalledWith(mockS3Client, inputParams);
|
|
62
62
|
expect(mockOperation).toHaveBeenCalledTimes(1);
|
|
63
63
|
});
|
|
64
|
-
it('should handle function operation errors', async ()=>{
|
|
64
|
+
it('should handle function operation errors by wrapping with createS3OperationError', async ()=>{
|
|
65
65
|
const mockError = new Error('Presigned URL generation failed');
|
|
66
|
+
const enhancedError = {
|
|
67
|
+
message: 'Enhanced error'
|
|
68
|
+
};
|
|
66
69
|
mockOperation.mockRejectedValue(mockError);
|
|
70
|
+
mockCreateS3OperationError.mockReturnValue(enhancedError);
|
|
67
71
|
const useMutation = useCreateS3FunctionMutationHook(mockOperation);
|
|
68
72
|
const { result } = renderHook(()=>useMutation(), {
|
|
69
73
|
wrapper: createTestWrapper()
|
|
@@ -75,40 +79,8 @@ describe('useCreateS3FunctionMutationHook - Factory Specific', ()=>{
|
|
|
75
79
|
await waitFor(()=>{
|
|
76
80
|
expect(result.current.isError).toBe(true);
|
|
77
81
|
});
|
|
78
|
-
expect(result.current.error).toBe(
|
|
79
|
-
expect(
|
|
80
|
-
Bucket: 'test-bucket',
|
|
81
|
-
Key: 'test-key'
|
|
82
|
-
});
|
|
83
|
-
expect(mockCreateS3OperationError).not.toHaveBeenCalled();
|
|
84
|
-
});
|
|
85
|
-
it('should handle function operation with internal error wrapping', async ()=>{
|
|
86
|
-
const mockInternalError = new Error('Internal operation error');
|
|
87
|
-
const mockWrappedError = new Error('Wrapped operation error');
|
|
88
|
-
const mockOperationWithErrorHandling = jest.fn().mockImplementation(async ()=>{
|
|
89
|
-
try {
|
|
90
|
-
throw mockInternalError;
|
|
91
|
-
} catch (error) {
|
|
92
|
-
const wrappedError = mockWrappedError;
|
|
93
|
-
throw wrappedError;
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
const useMutation = useCreateS3FunctionMutationHook(mockOperationWithErrorHandling);
|
|
97
|
-
const { result } = renderHook(()=>useMutation(), {
|
|
98
|
-
wrapper: createTestWrapper()
|
|
99
|
-
});
|
|
100
|
-
result.current.mutate({
|
|
101
|
-
Bucket: 'test-bucket',
|
|
102
|
-
Key: 'test-key'
|
|
103
|
-
});
|
|
104
|
-
await waitFor(()=>{
|
|
105
|
-
expect(result.current.isError).toBe(true);
|
|
106
|
-
});
|
|
107
|
-
expect(result.current.error).toBe(mockWrappedError);
|
|
108
|
-
expect(mockOperationWithErrorHandling).toHaveBeenCalledWith(mockS3Client, {
|
|
109
|
-
Bucket: 'test-bucket',
|
|
110
|
-
Key: 'test-key'
|
|
111
|
-
});
|
|
82
|
+
expect(result.current.error).toBe(enhancedError);
|
|
83
|
+
expect(mockCreateS3OperationError).toHaveBeenCalledWith(mockError, 'FunctionMutation', void 0, void 0, void 0);
|
|
112
84
|
});
|
|
113
85
|
it('should handle mutation callbacks correctly', async ()=>{
|
|
114
86
|
const mockResponse = {
|
|
@@ -173,7 +173,7 @@ describe('useCreateS3InfiniteQueryHook - Pagination Logic', ()=>{
|
|
|
173
173
|
expect(result.current.isError).toBe(true);
|
|
174
174
|
});
|
|
175
175
|
expect(result.current.error).toBe(enhancedError);
|
|
176
|
-
expect(mockCreateS3OperationError).toHaveBeenCalledWith(mockError, 'ListObjects', 'test-bucket');
|
|
176
|
+
expect(mockCreateS3OperationError).toHaveBeenCalledWith(mockError, 'ListObjects', 'test-bucket', void 0, void 0);
|
|
177
177
|
});
|
|
178
178
|
it('should handle fetchNextPage correctly', async ()=>{
|
|
179
179
|
const mockFirstPage = {
|
|
@@ -84,7 +84,7 @@ describe('useCreateS3MutationHook - Factory Specific', ()=>{
|
|
|
84
84
|
expect(result.current.isError).toBe(true);
|
|
85
85
|
});
|
|
86
86
|
expect(result.current.error).toBe(enhancedError);
|
|
87
|
-
expect(mockCreateS3OperationError).toHaveBeenCalledWith(mockError, 'PutObject', 'test-bucket');
|
|
87
|
+
expect(mockCreateS3OperationError).toHaveBeenCalledWith(mockError, 'PutObject', 'test-bucket', void 0, void 0);
|
|
88
88
|
});
|
|
89
89
|
it('should handle mutation callbacks correctly', async ()=>{
|
|
90
90
|
const mockResponse = {
|
|
@@ -84,7 +84,7 @@ describe('useCreateS3QueryHook - Factory Specific', ()=>{
|
|
|
84
84
|
expect(result.current.isError).toBe(true);
|
|
85
85
|
});
|
|
86
86
|
expect(result.current.error).toBe(enhancedError);
|
|
87
|
-
expect(mockCreateS3OperationError).toHaveBeenCalledWith(mockError, 'testOperation', 'test-bucket');
|
|
87
|
+
expect(mockCreateS3OperationError).toHaveBeenCalledWith(mockError, 'testOperation', 'test-bucket', void 0, void 0);
|
|
88
88
|
});
|
|
89
89
|
it('should handle parameter validation correctly', ()=>{
|
|
90
90
|
const useTestQuery = useCreateS3QueryHook(MockCommand, 'testOperation');
|
|
@@ -10,4 +10,4 @@ import { type EnhancedS3Error } from '../../utils/errorHandling';
|
|
|
10
10
|
* - AbortSignal support for cancellation
|
|
11
11
|
* - Pagination parameters and logic
|
|
12
12
|
*/
|
|
13
|
-
export declare function useCreateS3InfiniteQueryHook<TInput extends object, TOutput>(Command: new (input: TInput) => any, operationName: string): (params: TInput, options?: Partial<UseInfiniteQueryOptions<TOutput, EnhancedS3Error, TOutput, (string | TInput)[], string | undefined>>) => UseInfiniteQueryResult<TOutput, EnhancedS3Error>;
|
|
13
|
+
export declare function useCreateS3InfiniteQueryHook<TInput extends object, TOutput>(Command: new (input: TInput) => any, operationName: string, operationLabel?: string): (params: TInput, options?: Partial<UseInfiniteQueryOptions<TOutput, EnhancedS3Error, TOutput, (string | TInput)[], string | undefined>>) => UseInfiniteQueryResult<TOutput, EnhancedS3Error>;
|
|
@@ -2,7 +2,7 @@ import { useInfiniteQuery } from "@tanstack/react-query";
|
|
|
2
2
|
import { useDataBrowserContext } from "../../components/providers/DataBrowserProvider.js";
|
|
3
3
|
import { createS3OperationError, shouldRetryError } from "../../utils/errorHandling.js";
|
|
4
4
|
import { useS3Client } from "../useS3Client.js";
|
|
5
|
-
function useCreateS3InfiniteQueryHook(Command, operationName) {
|
|
5
|
+
function useCreateS3InfiniteQueryHook(Command, operationName, operationLabel) {
|
|
6
6
|
return (params, options)=>{
|
|
7
7
|
const { s3ConfigIdentifier } = useDataBrowserContext();
|
|
8
8
|
const s3Client = useS3Client();
|
|
@@ -24,7 +24,7 @@ function useCreateS3InfiniteQueryHook(Command, operationName) {
|
|
|
24
24
|
return response;
|
|
25
25
|
} catch (error) {
|
|
26
26
|
const bucketName = 'Bucket' in params ? params.Bucket : void 0;
|
|
27
|
-
throw createS3OperationError(error, operationName, bucketName);
|
|
27
|
+
throw createS3OperationError(error, operationName, bucketName, void 0, operationLabel);
|
|
28
28
|
}
|
|
29
29
|
},
|
|
30
30
|
retry: options?.retry ?? ((failureCount, error)=>shouldRetryError(error, failureCount)),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { S3Client } from '@aws-sdk/client-s3';
|
|
2
2
|
import { type UseMutationOptions, type UseMutationResult } from '@tanstack/react-query';
|
|
3
3
|
import { type EnhancedS3Error } from '../../utils/errorHandling';
|
|
4
|
-
export declare function useCreateS3MutationHook<TInput extends object, TOutput>(Command: new (input: TInput) => any, operationName: string, invalidationKeys?: string[]): (options?: Omit<UseMutationOptions<TOutput, EnhancedS3Error, TInput>, "mutationFn">) => UseMutationResult<TOutput, EnhancedS3Error, TInput>;
|
|
5
|
-
export declare function useCreateS3FunctionMutationHook<TInput, TOutput>(operation: (s3Client: S3Client, input: TInput) => Promise<TOutput>, invalidationKeys?: string[]): (options?: Omit<UseMutationOptions<TOutput, EnhancedS3Error, TInput, unknown>, "mutationFn"> | undefined) => UseMutationResult<TOutput, EnhancedS3Error, TInput>;
|
|
4
|
+
export declare function useCreateS3MutationHook<TInput extends object, TOutput>(Command: new (input: TInput) => any, operationName: string, invalidationKeys?: string[], operationLabel?: string): (options?: Omit<UseMutationOptions<TOutput, EnhancedS3Error, TInput>, "mutationFn">) => UseMutationResult<TOutput, EnhancedS3Error, TInput>;
|
|
5
|
+
export declare function useCreateS3FunctionMutationHook<TInput, TOutput>(operation: (s3Client: S3Client, input: TInput) => Promise<TOutput>, invalidationKeys?: string[], operationLabel?: string): (options?: Omit<UseMutationOptions<TOutput, EnhancedS3Error, TInput, unknown>, "mutationFn"> | undefined) => UseMutationResult<TOutput, EnhancedS3Error, TInput>;
|
|
6
6
|
export type UrlRewriter = (url: string) => string;
|
|
7
|
-
export declare function useCreatePresigningMutationHook<TInput, TOutput>(operation: (s3Client: S3Client, input: TInput, rewriteUrl: UrlRewriter) => Promise<TOutput>, invalidationKeys?: string[]): (options?: Omit<UseMutationOptions<TOutput, EnhancedS3Error, TInput>, "mutationFn">) => UseMutationResult<TOutput, EnhancedS3Error, TInput>;
|
|
7
|
+
export declare function useCreatePresigningMutationHook<TInput, TOutput>(operation: (s3Client: S3Client, input: TInput, rewriteUrl: UrlRewriter) => Promise<TOutput>, invalidationKeys?: string[], operationLabel?: string): (options?: Omit<UseMutationOptions<TOutput, EnhancedS3Error, TInput>, "mutationFn">) => UseMutationResult<TOutput, EnhancedS3Error, TInput>;
|
|
@@ -3,7 +3,7 @@ import { useDataBrowserContext } from "../../components/providers/DataBrowserPro
|
|
|
3
3
|
import { createS3OperationError } from "../../utils/errorHandling.js";
|
|
4
4
|
import { usePresigningS3Client } from "../usePresigningS3Client.js";
|
|
5
5
|
import { useS3Client } from "../useS3Client.js";
|
|
6
|
-
function useCreateS3MutationHook(Command, operationName, invalidationKeys) {
|
|
6
|
+
function useCreateS3MutationHook(Command, operationName, invalidationKeys, operationLabel) {
|
|
7
7
|
return (options)=>{
|
|
8
8
|
const { s3ConfigIdentifier } = useDataBrowserContext();
|
|
9
9
|
const s3Client = useS3Client();
|
|
@@ -15,7 +15,7 @@ function useCreateS3MutationHook(Command, operationName, invalidationKeys) {
|
|
|
15
15
|
return await s3Client.send(command);
|
|
16
16
|
} catch (error) {
|
|
17
17
|
const bucketName = 'Bucket' in params ? params.Bucket : void 0;
|
|
18
|
-
throw createS3OperationError(error, operationName, bucketName);
|
|
18
|
+
throw createS3OperationError(error, operationName, bucketName, void 0, operationLabel);
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
onSuccess: (_data, _variables)=>{
|
|
@@ -32,13 +32,19 @@ function useCreateS3MutationHook(Command, operationName, invalidationKeys) {
|
|
|
32
32
|
});
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
|
-
function createFunctionMutationHook(operation, useClient, invalidationKeys) {
|
|
35
|
+
function createFunctionMutationHook(operation, useClient, invalidationKeys, operationLabel) {
|
|
36
36
|
return (options)=>{
|
|
37
37
|
const { s3ConfigIdentifier } = useDataBrowserContext();
|
|
38
38
|
const s3Client = useClient();
|
|
39
39
|
const queryClient = useQueryClient();
|
|
40
40
|
return useMutation({
|
|
41
|
-
mutationFn: async (params)=>
|
|
41
|
+
mutationFn: async (params)=>{
|
|
42
|
+
try {
|
|
43
|
+
return await operation(s3Client, params);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
throw createS3OperationError(error, 'FunctionMutation', void 0, void 0, operationLabel);
|
|
46
|
+
}
|
|
47
|
+
},
|
|
42
48
|
onSuccess: (_data, _variables)=>{
|
|
43
49
|
if (invalidationKeys) invalidationKeys.forEach((key)=>{
|
|
44
50
|
queryClient.invalidateQueries({
|
|
@@ -53,16 +59,22 @@ function createFunctionMutationHook(operation, useClient, invalidationKeys) {
|
|
|
53
59
|
});
|
|
54
60
|
};
|
|
55
61
|
}
|
|
56
|
-
function useCreateS3FunctionMutationHook(operation, invalidationKeys) {
|
|
57
|
-
return createFunctionMutationHook(operation, useS3Client, invalidationKeys);
|
|
62
|
+
function useCreateS3FunctionMutationHook(operation, invalidationKeys, operationLabel) {
|
|
63
|
+
return createFunctionMutationHook(operation, useS3Client, invalidationKeys, operationLabel);
|
|
58
64
|
}
|
|
59
|
-
function useCreatePresigningMutationHook(operation, invalidationKeys) {
|
|
65
|
+
function useCreatePresigningMutationHook(operation, invalidationKeys, operationLabel) {
|
|
60
66
|
return (options)=>{
|
|
61
67
|
const { s3ConfigIdentifier } = useDataBrowserContext();
|
|
62
68
|
const { client, rewriteUrl } = usePresigningS3Client();
|
|
63
69
|
const queryClient = useQueryClient();
|
|
64
70
|
return useMutation({
|
|
65
|
-
mutationFn: async (params)=>
|
|
71
|
+
mutationFn: async (params)=>{
|
|
72
|
+
try {
|
|
73
|
+
return await operation(client, params, rewriteUrl);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
throw createS3OperationError(error, 'PresigningMutation', void 0, void 0, operationLabel);
|
|
76
|
+
}
|
|
77
|
+
},
|
|
66
78
|
onSuccess: (_data, _variables)=>{
|
|
67
79
|
if (invalidationKeys) invalidationKeys.forEach((key)=>{
|
|
68
80
|
queryClient.invalidateQueries({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { type QueryKey, type UseQueryOptions, type UseQueryResult } from '@tanstack/react-query';
|
|
2
2
|
import { type EnhancedS3Error } from '../../utils/errorHandling';
|
|
3
3
|
export declare function createS3QueryKey(s3ConfigIdentifier: string, operationName: string, params?: object): QueryKey;
|
|
4
|
-
export declare function useCreateS3QueryHook<TInput extends object, TOutput>(Command: new (input: TInput) => any, operationName: string): (params?: TInput, options?: Omit<UseQueryOptions<TOutput, EnhancedS3Error, TOutput, QueryKey>, "queryKey" | "queryFn">) => UseQueryResult<TOutput, EnhancedS3Error>;
|
|
4
|
+
export declare function useCreateS3QueryHook<TInput extends object, TOutput>(Command: new (input: TInput) => any, operationName: string, operationLabel?: string): (params?: TInput, options?: Omit<UseQueryOptions<TOutput, EnhancedS3Error, TOutput, QueryKey>, "queryKey" | "queryFn">) => UseQueryResult<TOutput, EnhancedS3Error>;
|
|
@@ -28,7 +28,7 @@ function createS3QueryKey(s3ConfigIdentifier, operationName, params) {
|
|
|
28
28
|
params
|
|
29
29
|
];
|
|
30
30
|
}
|
|
31
|
-
function useCreateS3QueryHook(Command, operationName) {
|
|
31
|
+
function useCreateS3QueryHook(Command, operationName, operationLabel) {
|
|
32
32
|
return (params, options)=>{
|
|
33
33
|
const { s3ConfigIdentifier } = useDataBrowserContext();
|
|
34
34
|
const s3Client = useS3Client();
|
|
@@ -47,7 +47,7 @@ function useCreateS3QueryHook(Command, operationName) {
|
|
|
47
47
|
const emptyConfig = getEmptyConfigForOperation(operationName);
|
|
48
48
|
if (null !== emptyConfig) return emptyConfig;
|
|
49
49
|
}
|
|
50
|
-
throw createS3OperationError(error, operationName, bucketName);
|
|
50
|
+
throw createS3OperationError(error, operationName, bucketName, void 0, operationLabel);
|
|
51
51
|
}
|
|
52
52
|
},
|
|
53
53
|
retry: options?.retry ?? ((failureCount, error)=>shouldRetryError(error, failureCount)),
|
|
@@ -30,12 +30,12 @@ const useDeleteObjects = useCreateS3MutationHook(DeleteObjectsCommand, 'DeleteOb
|
|
|
30
30
|
'ListObjectVersions',
|
|
31
31
|
'SearchObjects',
|
|
32
32
|
'SearchObjectsVersions'
|
|
33
|
-
]);
|
|
33
|
+
], 'delete objects');
|
|
34
34
|
const useCopyObject = useCreateS3MutationHook(CopyObjectCommand, 'CopyObject', [
|
|
35
35
|
'ListObjects',
|
|
36
36
|
'SearchObjects',
|
|
37
37
|
'HeadObject'
|
|
38
|
-
]);
|
|
38
|
+
], 'save metadata');
|
|
39
39
|
const useObjectRetention = useCreateS3QueryHook(GetObjectRetentionCommand, 'GetObjectRetention');
|
|
40
40
|
const useSetObjectRetention = useCreateS3MutationHook(PutObjectRetentionCommand, 'PutObjectRetention', [
|
|
41
41
|
'GetObjectRetention'
|
|
@@ -44,18 +44,18 @@ const useObjectLegalHold = useCreateS3QueryHook(GetObjectLegalHoldCommand, 'GetO
|
|
|
44
44
|
const useSetObjectLegalHold = useCreateS3MutationHook(PutObjectLegalHoldCommand, 'PutObjectLegalHold', [
|
|
45
45
|
'GetObjectLegalHold',
|
|
46
46
|
'BatchObjectLegalHold'
|
|
47
|
-
]);
|
|
47
|
+
], 'update legal hold');
|
|
48
48
|
const useObjectTagging = useCreateS3QueryHook(GetObjectTaggingCommand, 'GetObjectTagging');
|
|
49
49
|
const useSetObjectTagging = useCreateS3MutationHook(PutObjectTaggingCommand, 'PutObjectTagging', [
|
|
50
50
|
'GetObjectTagging'
|
|
51
|
-
]);
|
|
51
|
+
], 'save tags');
|
|
52
52
|
const useDeleteObjectTagging = useCreateS3MutationHook(DeleteObjectTaggingCommand, 'DeleteObjectTagging', [
|
|
53
53
|
'GetObjectTagging'
|
|
54
|
-
]);
|
|
54
|
+
], 'save tags');
|
|
55
55
|
const useObjectAcl = useCreateS3QueryHook(GetObjectAclCommand, 'GetObjectAcl');
|
|
56
56
|
const useSetObjectAcl = useCreateS3MutationHook(PutObjectAclCommand, 'PutObjectAcl', [
|
|
57
57
|
'GetObjectAcl'
|
|
58
|
-
]);
|
|
58
|
+
], 'update object visibility');
|
|
59
59
|
const useGetObjectAttributes = useCreateS3QueryHook(GetObjectAttributesCommand, 'GetObjectAttributes');
|
|
60
60
|
const useGetObjectTorrent = useCreateS3QueryHook(GetObjectTorrentCommand, 'GetObjectTorrent');
|
|
61
61
|
const useRestoreObject = useCreateS3MutationHook(RestoreObjectCommand, 'RestoreObject', [
|
|
@@ -67,7 +67,7 @@ const generatePresignedPost = async (client, config, rewriteUrl)=>{
|
|
|
67
67
|
throw createS3OperationError(error, 'GeneratePresignedPost', config.Bucket, config.Key);
|
|
68
68
|
}
|
|
69
69
|
};
|
|
70
|
-
const useGetPresignedDownload = useCreatePresigningMutationHook(generatePresignedDownloadUrl);
|
|
70
|
+
const useGetPresignedDownload = useCreatePresigningMutationHook(generatePresignedDownloadUrl, void 0, 'generate the presigned URL');
|
|
71
71
|
const useGetPresignedUpload = useCreatePresigningMutationHook(generatePresignedUploadUrl);
|
|
72
72
|
const useGetPresignedPost = useCreatePresigningMutationHook(generatePresignedPost);
|
|
73
73
|
export { useGetPresignedDownload, useGetPresignedPost, useGetPresignedUpload };
|
|
@@ -36,5 +36,5 @@ async function deleteFolder(s3Client, input) {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
-
const useDeleteFolder = useCreateS3FunctionMutationHook(deleteFolder);
|
|
39
|
+
const useDeleteFolder = useCreateS3FunctionMutationHook(deleteFolder, void 0, 'delete the folder');
|
|
40
40
|
export { useDeleteFolder };
|
|
@@ -117,6 +117,18 @@ describe('EnhancedS3Error', ()=>{
|
|
|
117
117
|
expect(TestEnhancedErrors.unknown().shouldRetry()).toBe(false);
|
|
118
118
|
});
|
|
119
119
|
});
|
|
120
|
+
describe('getUserMessage', ()=>{
|
|
121
|
+
test('returns permission message for AUTHORIZATION errors', ()=>{
|
|
122
|
+
expect(TestEnhancedErrors.auth().getUserMessage('save the bucket policy')).toBe("You don't have permission to save the bucket policy. Contact your administrator.");
|
|
123
|
+
});
|
|
124
|
+
test('returns original message for non-authorization errors', ()=>{
|
|
125
|
+
expect(TestEnhancedErrors.server().getUserMessage('save tags')).toBe('Server error');
|
|
126
|
+
});
|
|
127
|
+
test('returns fallback when message is empty', ()=>{
|
|
128
|
+
const error = new EnhancedS3Error('', 'EmptyError', ErrorCategory.SERVER_ERROR, new Error());
|
|
129
|
+
expect(error.getUserMessage('save tags')).toBe('Failed to save tags');
|
|
130
|
+
});
|
|
131
|
+
});
|
|
120
132
|
});
|
|
121
133
|
describe('Type Guards', ()=>{
|
|
122
134
|
describe('isS3ServiceException', ()=>{
|
|
@@ -272,39 +284,15 @@ describe('createS3Error', ()=>{
|
|
|
272
284
|
});
|
|
273
285
|
});
|
|
274
286
|
describe('createS3OperationError', ()=>{
|
|
275
|
-
test('message
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
{
|
|
285
|
-
name: 'enhances message with operation context',
|
|
286
|
-
error: new Error(TEST_CONSTANTS.MESSAGES.ACCESS_DENIED),
|
|
287
|
-
operation: 'GetObject',
|
|
288
|
-
bucket: TEST_CONSTANTS.BUCKET,
|
|
289
|
-
key: TEST_CONSTANTS.KEY,
|
|
290
|
-
expectedMessage: `GetObject failed for bucket '${TEST_CONSTANTS.BUCKET}', key '${TEST_CONSTANTS.KEY}': ${TEST_CONSTANTS.MESSAGES.ACCESS_DENIED}`
|
|
291
|
-
},
|
|
292
|
-
{
|
|
293
|
-
name: 'handles operation without bucket',
|
|
294
|
-
error: new Error(TEST_CONSTANTS.MESSAGES.SERVICE_UNAVAILABLE),
|
|
295
|
-
operation: 'ListBuckets',
|
|
296
|
-
expectedMessage: `ListBuckets failed: ${TEST_CONSTANTS.MESSAGES.SERVICE_UNAVAILABLE}`
|
|
297
|
-
}
|
|
298
|
-
];
|
|
299
|
-
testCases.forEach(({ error, operation, bucket, key, expectedMessage })=>{
|
|
300
|
-
const result = createS3OperationError(error, operation, bucket, key);
|
|
301
|
-
expect(result.message).toBe(expectedMessage);
|
|
302
|
-
expect(result.context).toEqual({
|
|
303
|
-
operation,
|
|
304
|
-
bucketName: bucket || void 0,
|
|
305
|
-
objectKey: key || void 0,
|
|
306
|
-
timestamp: expect.any(String)
|
|
307
|
-
});
|
|
287
|
+
test('preserves original error message without operationLabel', ()=>{
|
|
288
|
+
const error = new Error(TEST_CONSTANTS.MESSAGES.ACCESS_DENIED);
|
|
289
|
+
const result = createS3OperationError(error, 'GetObject', TEST_CONSTANTS.BUCKET, TEST_CONSTANTS.KEY);
|
|
290
|
+
expect(result.message).toBe(TEST_CONSTANTS.MESSAGES.ACCESS_DENIED);
|
|
291
|
+
expect(result.context).toEqual({
|
|
292
|
+
operation: 'GetObject',
|
|
293
|
+
bucketName: TEST_CONSTANTS.BUCKET,
|
|
294
|
+
objectKey: TEST_CONSTANTS.KEY,
|
|
295
|
+
timestamp: expect.any(String)
|
|
308
296
|
});
|
|
309
297
|
});
|
|
310
298
|
test('preserves all error properties', ()=>{
|
|
@@ -411,6 +399,30 @@ describe('isNotFoundError', ()=>{
|
|
|
411
399
|
expect(result).toBe(false);
|
|
412
400
|
});
|
|
413
401
|
});
|
|
402
|
+
describe('createS3OperationError with operationLabel', ()=>{
|
|
403
|
+
test('embeds permission message for 403 errors', ()=>{
|
|
404
|
+
const error = TestErrors.accessDenied();
|
|
405
|
+
const result = createS3OperationError(error, 'PutBucketPolicy', 'my-bucket', void 0, 'save the bucket policy');
|
|
406
|
+
expect(result.message).toBe("You don't have permission to save the bucket policy. Contact your administrator.");
|
|
407
|
+
});
|
|
408
|
+
test('preserves original message for non-403 errors', ()=>{
|
|
409
|
+
const error = TestErrors.internalError();
|
|
410
|
+
const result = createS3OperationError(error, 'PutBucketPolicy', 'my-bucket', void 0, 'save the bucket policy');
|
|
411
|
+
expect(result.message).toBe('Internal error');
|
|
412
|
+
});
|
|
413
|
+
test('uses fallback when error has no message', ()=>{
|
|
414
|
+
const error = new Error('');
|
|
415
|
+
const result = createS3OperationError(error, 'PutBucketPolicy', 'my-bucket', void 0, 'save the bucket policy');
|
|
416
|
+
expect(result.message).toBe('Failed to save the bucket policy');
|
|
417
|
+
});
|
|
418
|
+
test('preserves category when double-wrapping an EnhancedS3Error', ()=>{
|
|
419
|
+
const inner = createS3OperationError(TestErrors.accessDenied(), 'GeneratePresignedDownload', 'my-bucket');
|
|
420
|
+
expect(inner.category).toBe(ErrorCategory.AUTHORIZATION);
|
|
421
|
+
const outer = createS3OperationError(inner, 'PresigningMutation', void 0, void 0, 'generate the presigned URL');
|
|
422
|
+
expect(outer.category).toBe(ErrorCategory.AUTHORIZATION);
|
|
423
|
+
expect(outer.message).toBe("You don't have permission to generate the presigned URL. Contact your administrator.");
|
|
424
|
+
});
|
|
425
|
+
});
|
|
414
426
|
describe('Integration Tests', ()=>{
|
|
415
427
|
test('complete error processing workflow', ()=>{
|
|
416
428
|
const testCases = [
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { parseRestoreStatus } from "../coldStorage.js";
|
|
2
|
+
describe('parseRestoreStatus', ()=>{
|
|
3
|
+
it('returns "cold" when restore header is undefined', ()=>{
|
|
4
|
+
expect(parseRestoreStatus(void 0)).toEqual({
|
|
5
|
+
status: 'cold'
|
|
6
|
+
});
|
|
7
|
+
});
|
|
8
|
+
it('returns "restoring" when ongoing-request="true"', ()=>{
|
|
9
|
+
expect(parseRestoreStatus('ongoing-request="true"')).toEqual({
|
|
10
|
+
status: 'restoring'
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
it('returns "restored" with expiry date when ongoing-request="false"', ()=>{
|
|
14
|
+
const header = 'ongoing-request="false", expiry-date="Fri, 02 May 2026 00:00:00 GMT"';
|
|
15
|
+
const result = parseRestoreStatus(header);
|
|
16
|
+
expect(result.status).toBe('restored');
|
|
17
|
+
if ('restored' === result.status) expect(result.expiryDate).toEqual(new Date('Fri, 02 May 2026 00:00:00 GMT'));
|
|
18
|
+
});
|
|
19
|
+
it('returns "restored" without expiry if date is missing', ()=>{
|
|
20
|
+
expect(parseRestoreStatus('ongoing-request="false"')).toEqual({
|
|
21
|
+
status: 'restored'
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
export declare const COLD_STORAGE_TOOLTIP = "The Temperature of this Location is Cold.\n\nYou can move your data in this Location through a Transition rule.\n\nOnce your data are in this Location, you can only trigger a request for restoration to get a temporary access to the object.";
|
|
3
|
+
export declare const COLD_STORAGE_TOOLTIP_STYLE: React.CSSProperties;
|
|
4
|
+
export type ColdStorageStatus = {
|
|
5
|
+
status: 'cold';
|
|
6
|
+
} | {
|
|
7
|
+
status: 'restoring';
|
|
8
|
+
} | {
|
|
9
|
+
status: 'restored';
|
|
10
|
+
expiryDate?: Date;
|
|
11
|
+
};
|
|
12
|
+
export declare function parseRestoreStatus(restoreHeader: string | undefined): ColdStorageStatus;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const COLD_STORAGE_TOOLTIP = 'The Temperature of this Location is Cold.\n\nYou can move your data in this Location through a Transition rule.\n\nOnce your data are in this Location, you can only trigger a request for restoration to get a temporary access to the object.';
|
|
2
|
+
const COLD_STORAGE_TOOLTIP_STYLE = {
|
|
3
|
+
textWrap: 'wrap',
|
|
4
|
+
width: '24rem',
|
|
5
|
+
textAlign: 'left'
|
|
6
|
+
};
|
|
7
|
+
function parseRestoreStatus(restoreHeader) {
|
|
8
|
+
if (!restoreHeader) return {
|
|
9
|
+
status: 'cold'
|
|
10
|
+
};
|
|
11
|
+
if (restoreHeader.includes('ongoing-request="true"')) return {
|
|
12
|
+
status: 'restoring'
|
|
13
|
+
};
|
|
14
|
+
const expiryMatch = restoreHeader.match(/expiry-date="([^"]+)"/);
|
|
15
|
+
if (expiryMatch) return {
|
|
16
|
+
status: 'restored',
|
|
17
|
+
expiryDate: new Date(expiryMatch[1])
|
|
18
|
+
};
|
|
19
|
+
return {
|
|
20
|
+
status: 'restored'
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export { COLD_STORAGE_TOOLTIP, COLD_STORAGE_TOOLTIP_STYLE, parseRestoreStatus };
|
|
@@ -29,6 +29,7 @@ export declare class EnhancedS3Error extends Error {
|
|
|
29
29
|
* Server errors, network issues, and expired credentials are retryable.
|
|
30
30
|
*/
|
|
31
31
|
shouldRetry(): boolean;
|
|
32
|
+
getUserMessage(operationLabel: string): string;
|
|
32
33
|
}
|
|
33
34
|
/**
|
|
34
35
|
* Type guard to check if an error is an AWS S3 service exception.
|
|
@@ -47,7 +48,7 @@ export declare function createS3Error(error: unknown, context?: Record<string, u
|
|
|
47
48
|
* Creates an enhanced S3 error with operation context and enriched error messages.
|
|
48
49
|
* Automatically adds operation details to error messages for better debugging.
|
|
49
50
|
*/
|
|
50
|
-
export declare function createS3OperationError(error: unknown, operation: string, bucketName?: string, objectKey?: string): EnhancedS3Error;
|
|
51
|
+
export declare function createS3OperationError(error: unknown, operation: string, bucketName?: string, objectKey?: string, operationLabel?: string): EnhancedS3Error;
|
|
51
52
|
/**
|
|
52
53
|
* Determines whether an error should be retried based on its classification.
|
|
53
54
|
* Implements unified retry policy for all S3 operations.
|
|
@@ -29,6 +29,10 @@ class EnhancedS3Error extends Error {
|
|
|
29
29
|
shouldRetry() {
|
|
30
30
|
return "SERVER_ERROR" === this.category || "NETWORK_ERROR" === this.category || "EXPIRED_CREDENTIALS" === this.category;
|
|
31
31
|
}
|
|
32
|
+
getUserMessage(operationLabel) {
|
|
33
|
+
if ("AUTHORIZATION" === this.category) return `You don't have permission to ${operationLabel}. Contact your administrator.`;
|
|
34
|
+
return this.message || `Failed to ${operationLabel}`;
|
|
35
|
+
}
|
|
32
36
|
}
|
|
33
37
|
const EXPIRED_CREDENTIAL_ERROR_NAMES = new Set([
|
|
34
38
|
'ExpiredToken',
|
|
@@ -68,19 +72,16 @@ function createS3Error(error, context) {
|
|
|
68
72
|
const message = 'string' == typeof error ? error : 'Unknown error occurred';
|
|
69
73
|
return new EnhancedS3Error(message, 'UnknownError', "UNKNOWN", new Error(message), void 0, void 0, context);
|
|
70
74
|
}
|
|
71
|
-
function createS3OperationError(error, operation, bucketName, objectKey) {
|
|
75
|
+
function createS3OperationError(error, operation, bucketName, objectKey, operationLabel) {
|
|
72
76
|
const context = {
|
|
73
77
|
operation,
|
|
74
78
|
bucketName,
|
|
75
79
|
objectKey,
|
|
76
80
|
timestamp: new Date().toISOString()
|
|
77
81
|
};
|
|
78
|
-
const enhancedError = createS3Error(error, context);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return new EnhancedS3Error(`${prefix}: ${enhancedError.message}`, enhancedError.name, enhancedError.category, enhancedError.originalError, enhancedError.statusCode, enhancedError.metadata, context);
|
|
82
|
-
}
|
|
83
|
-
return enhancedError;
|
|
82
|
+
const enhancedError = isEnhancedS3Error(error) ? error : createS3Error(error, context);
|
|
83
|
+
const message = operationLabel ? enhancedError.getUserMessage(operationLabel) : enhancedError.message;
|
|
84
|
+
return new EnhancedS3Error(message, enhancedError.name, enhancedError.category, enhancedError.originalError, enhancedError.statusCode, enhancedError.metadata, context);
|
|
84
85
|
}
|
|
85
86
|
function shouldRetryError(error, failureCount, maxRetries = 3) {
|
|
86
87
|
if (failureCount >= maxRetries) return false;
|