@scality/data-browser-library 1.1.9 → 1.1.11

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.
Files changed (41) hide show
  1. package/dist/components/__tests__/BucketCorsPage.test.js +14 -6
  2. package/dist/components/__tests__/BucketLifecycleFormPage.test.js +131 -0
  3. package/dist/components/__tests__/BucketNotificationFormPage.test.js +15 -3
  4. package/dist/components/buckets/BucketCorsPage.js +5 -7
  5. package/dist/components/buckets/BucketLifecycleFormPage.js +32 -44
  6. package/dist/components/buckets/BucketLifecycleList.js +8 -4
  7. package/dist/components/buckets/BucketOverview.js +1 -1
  8. package/dist/components/buckets/BucketPolicyPage.js +4 -7
  9. package/dist/components/buckets/BucketReplicationFormPage.js +12 -6
  10. package/dist/components/buckets/BucketReplicationList.js +12 -9
  11. package/dist/components/buckets/BucketVersioning.js +1 -1
  12. package/dist/components/buckets/notifications/BucketNotificationFormPage.js +2 -2
  13. package/dist/components/buckets/notifications/BucketNotificationList.js +1 -1
  14. package/dist/components/objects/DeleteObjectButton.js +1 -1
  15. package/dist/components/objects/GetPresignedUrlButton.js +1 -1
  16. package/dist/components/objects/ObjectDetails/ObjectMetadata.js +1 -1
  17. package/dist/components/objects/ObjectDetails/ObjectSummary.js +2 -2
  18. package/dist/components/objects/ObjectDetails/ObjectTags.js +1 -1
  19. package/dist/components/objects/ObjectDetails/__tests__/ObjectSummary.test.js +37 -1
  20. package/dist/components/objects/ObjectLock/ObjectLockSettings.js +1 -1
  21. package/dist/components/objects/__tests__/GetPresignedUrlButton.test.js +30 -1
  22. package/dist/components/providers/QueryProvider.js +2 -1
  23. package/dist/hooks/bucketConfiguration.js +15 -15
  24. package/dist/hooks/bucketOperations.js +1 -1
  25. package/dist/hooks/factories/__tests__/useCreateS3FunctionMutationHook.test.js +7 -35
  26. package/dist/hooks/factories/__tests__/useCreateS3InfiniteQueryHook.test.js +1 -1
  27. package/dist/hooks/factories/__tests__/useCreateS3MutationHook.test.js +1 -1
  28. package/dist/hooks/factories/__tests__/useCreateS3QueryHook.test.js +1 -1
  29. package/dist/hooks/factories/useCreateS3InfiniteQueryHook.d.ts +1 -1
  30. package/dist/hooks/factories/useCreateS3InfiniteQueryHook.js +2 -2
  31. package/dist/hooks/factories/useCreateS3MutationHook.d.ts +3 -3
  32. package/dist/hooks/factories/useCreateS3MutationHook.js +20 -8
  33. package/dist/hooks/factories/useCreateS3QueryHook.d.ts +1 -1
  34. package/dist/hooks/factories/useCreateS3QueryHook.js +2 -2
  35. package/dist/hooks/objectOperations.js +6 -6
  36. package/dist/hooks/presignedOperations.js +1 -1
  37. package/dist/hooks/useDeleteFolder.js +1 -1
  38. package/dist/test/utils/errorHandling.test.js +45 -33
  39. package/dist/utils/errorHandling.d.ts +2 -1
  40. package/dist/utils/errorHandling.js +8 -7
  41. package/package.json +2 -2
@@ -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 modification scenarios', ()=>{
276
- const testCases = [
277
- {
278
- name: 'preserves existing operation in message',
279
- error: new Error('ListObjectsV2 failed: Access denied'),
280
- operation: 'ListObjectsV2',
281
- bucket: TEST_CONSTANTS.BUCKET,
282
- expectedMessage: 'ListObjectsV2 failed: Access denied'
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 = [
@@ -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
- if (!enhancedError.message.includes(operation)) {
80
- const prefix = bucketName ? `${operation} failed for bucket '${bucketName}'${objectKey ? `, key '${objectKey}'` : ''}` : `${operation} failed`;
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scality/data-browser-library",
3
- "version": "1.1.9",
3
+ "version": "1.1.11",
4
4
  "description": "A modular React component library for browsing S3 buckets and objects",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
@@ -61,7 +61,7 @@
61
61
  "jest": "^30.0.5",
62
62
  "jest-environment-jsdom": "^30.0.5",
63
63
  "msw": "^2.12.3",
64
- "ts-jest": "^29.4.1"
64
+ "ts-jest": "^29.4.6"
65
65
  },
66
66
  "repository": {
67
67
  "type": "git",