@scality/data-browser-library 1.0.0-preview.7 → 1.0.0-preview.9

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 (90) hide show
  1. package/dist/components/__tests__/BucketCreate.test.d.ts +1 -0
  2. package/dist/components/__tests__/BucketCreate.test.js +408 -0
  3. package/dist/components/__tests__/BucketLifecycleFormPage.test.d.ts +1 -0
  4. package/dist/components/__tests__/BucketLifecycleFormPage.test.js +618 -0
  5. package/dist/components/__tests__/BucketLifecycleList.test.d.ts +1 -0
  6. package/dist/components/__tests__/BucketLifecycleList.test.js +325 -0
  7. package/dist/components/__tests__/BucketList.test.js +190 -0
  8. package/dist/components/__tests__/BucketOverview.test.js +298 -8
  9. package/dist/components/__tests__/BucketReplicationFormPage.test.d.ts +1 -0
  10. package/dist/components/__tests__/BucketReplicationFormPage.test.js +1757 -0
  11. package/dist/components/__tests__/BucketReplicationList.test.d.ts +1 -0
  12. package/dist/components/__tests__/BucketReplicationList.test.js +344 -0
  13. package/dist/components/__tests__/DeleteBucketConfigRuleButton.test.d.ts +1 -0
  14. package/dist/components/__tests__/DeleteBucketConfigRuleButton.test.js +196 -0
  15. package/dist/components/__tests__/EmptyBucketButton.test.d.ts +1 -0
  16. package/dist/components/__tests__/EmptyBucketButton.test.js +302 -0
  17. package/dist/components/buckets/BucketCreate.d.ts +49 -0
  18. package/dist/components/buckets/BucketCreate.js +237 -0
  19. package/dist/components/buckets/BucketDetails.js +62 -10
  20. package/dist/components/buckets/BucketLifecycleFormPage.d.ts +15 -0
  21. package/dist/components/buckets/BucketLifecycleFormPage.js +1070 -0
  22. package/dist/components/buckets/BucketLifecycleList.d.ts +10 -0
  23. package/dist/components/buckets/BucketLifecycleList.js +270 -0
  24. package/dist/components/buckets/BucketList.d.ts +5 -2
  25. package/dist/components/buckets/BucketList.js +38 -28
  26. package/dist/components/buckets/BucketOverview.d.ts +65 -4
  27. package/dist/components/buckets/BucketOverview.js +261 -179
  28. package/dist/components/buckets/BucketPage.js +1 -1
  29. package/dist/components/buckets/BucketReplicationFormPage.d.ts +1 -0
  30. package/dist/components/buckets/BucketReplicationFormPage.js +834 -0
  31. package/dist/components/buckets/BucketReplicationList.d.ts +11 -0
  32. package/dist/components/buckets/BucketReplicationList.js +189 -0
  33. package/dist/components/buckets/DeleteBucketConfigRuleButton.d.ts +18 -0
  34. package/dist/components/buckets/DeleteBucketConfigRuleButton.js +53 -0
  35. package/dist/components/buckets/EmptyBucketButton.d.ts +5 -0
  36. package/dist/components/buckets/EmptyBucketButton.js +232 -0
  37. package/dist/components/buckets/EmptyBucketSummary.d.ts +9 -0
  38. package/dist/components/buckets/EmptyBucketSummary.js +60 -0
  39. package/dist/components/buckets/EmptyBucketSummaryList.d.ts +13 -0
  40. package/dist/components/buckets/EmptyBucketSummaryList.js +140 -0
  41. package/dist/components/buckets/notifications/BucketNotificationCreatePage.js +8 -8
  42. package/dist/components/index.d.ts +8 -1
  43. package/dist/components/index.js +9 -2
  44. package/dist/components/objects/ObjectLock/EditRetentionButton.d.ts +4 -0
  45. package/dist/components/objects/ObjectLock/EditRetentionButton.js +32 -0
  46. package/dist/components/objects/ObjectLock/ObjectLockRetentionSettings.d.ts +3 -0
  47. package/dist/components/objects/ObjectLock/ObjectLockRetentionSettings.js +211 -0
  48. package/dist/components/objects/ObjectLock/ObjectLockSettings.d.ts +9 -0
  49. package/dist/components/objects/ObjectLock/ObjectLockSettings.js +158 -0
  50. package/dist/components/objects/ObjectLock/ObjectLockSettingsUtils.d.ts +8 -0
  51. package/dist/components/objects/ObjectLock/ObjectLockSettingsUtils.js +39 -0
  52. package/dist/components/objects/ObjectLock/__tests__/EditRetentionButton.test.d.ts +1 -0
  53. package/dist/components/objects/ObjectLock/__tests__/EditRetentionButton.test.js +204 -0
  54. package/dist/components/objects/ObjectLock/__tests__/ObjectLockSettings.test.d.ts +1 -0
  55. package/dist/components/objects/ObjectLock/__tests__/ObjectLockSettings.test.js +374 -0
  56. package/dist/components/ui/ArrayFieldActions.d.ts +36 -0
  57. package/dist/components/ui/ArrayFieldActions.js +38 -0
  58. package/dist/components/ui/ConfirmDeleteRuleModal.d.ts +16 -0
  59. package/dist/components/ui/ConfirmDeleteRuleModal.js +43 -0
  60. package/dist/components/ui/FilterFormSection.d.ts +44 -0
  61. package/dist/components/ui/FilterFormSection.js +159 -0
  62. package/dist/config/factory.d.ts +13 -2
  63. package/dist/config/factory.js +9 -6
  64. package/dist/hooks/__tests__/useISVBucketDetection.test.d.ts +1 -0
  65. package/dist/hooks/__tests__/useISVBucketDetection.test.js +188 -0
  66. package/dist/hooks/factories/__tests__/useCreateS3QueryHook.test.js +44 -1
  67. package/dist/hooks/factories/useCreateS3QueryHook.js +22 -1
  68. package/dist/hooks/index.d.ts +4 -0
  69. package/dist/hooks/index.js +5 -1
  70. package/dist/hooks/useDeleteBucketConfigRule.d.ts +26 -0
  71. package/dist/hooks/useDeleteBucketConfigRule.js +46 -0
  72. package/dist/hooks/useEmptyBucket.d.ts +27 -0
  73. package/dist/hooks/useEmptyBucket.js +116 -0
  74. package/dist/hooks/useISVBucketDetection.d.ts +15 -0
  75. package/dist/hooks/useISVBucketDetection.js +27 -0
  76. package/dist/hooks/useTableRowSelection.d.ts +9 -0
  77. package/dist/hooks/useTableRowSelection.js +45 -0
  78. package/dist/test/setup.js +8 -0
  79. package/dist/test/testUtils.d.ts +99 -17
  80. package/dist/test/testUtils.js +64 -16
  81. package/dist/test/utils/errorHandling.test.js +39 -1
  82. package/dist/utils/constants.d.ts +12 -0
  83. package/dist/utils/constants.js +9 -0
  84. package/dist/utils/errorHandling.d.ts +9 -0
  85. package/dist/utils/errorHandling.js +6 -1
  86. package/dist/utils/index.d.ts +2 -0
  87. package/dist/utils/index.js +2 -0
  88. package/dist/utils/s3RuleUtils.d.ts +53 -0
  89. package/dist/utils/s3RuleUtils.js +101 -0
  90. package/package.json +1 -1
@@ -0,0 +1,38 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { Button } from "@scality/core-ui/dist/next";
3
+ import { Icon } from "@scality/core-ui";
4
+ function ArrayFieldActions({ onRemove, onAdd, canRemove, canAdd = true, showAdd, removeLabel = "Remove", addLabel = "Add" }) {
5
+ return /*#__PURE__*/ jsxs(Fragment, {
6
+ children: [
7
+ /*#__PURE__*/ jsx(Button, {
8
+ icon: /*#__PURE__*/ jsx(Icon, {
9
+ name: "Remove-minus"
10
+ }),
11
+ variant: "danger",
12
+ type: "button",
13
+ onClick: onRemove,
14
+ "aria-label": removeLabel,
15
+ disabled: !canRemove,
16
+ tooltip: {
17
+ overlay: removeLabel,
18
+ placement: "top"
19
+ }
20
+ }),
21
+ showAdd && /*#__PURE__*/ jsx(Button, {
22
+ icon: /*#__PURE__*/ jsx(Icon, {
23
+ name: "Add-plus"
24
+ }),
25
+ variant: "outline",
26
+ type: "button",
27
+ onClick: onAdd,
28
+ "aria-label": addLabel,
29
+ tooltip: {
30
+ overlay: addLabel,
31
+ placement: "top"
32
+ },
33
+ disabled: !canAdd
34
+ })
35
+ ]
36
+ });
37
+ }
38
+ export { ArrayFieldActions };
@@ -0,0 +1,16 @@
1
+ interface ConfirmDeleteRuleModalProps {
2
+ /** Whether the modal is open */
3
+ isOpen: boolean;
4
+ /** Rule ID to display in confirmation message */
5
+ ruleId: string;
6
+ /** Type of rule (e.g., "lifecycle", "replication") */
7
+ ruleType: "lifecycle" | "replication";
8
+ /** Whether deletion is in progress */
9
+ isDeleting: boolean;
10
+ /** Callback when user confirms deletion */
11
+ onConfirm: () => void;
12
+ /** Callback when user cancels deletion */
13
+ onCancel: () => void;
14
+ }
15
+ export declare function ConfirmDeleteRuleModal({ isOpen, ruleId, ruleType, isDeleting, onConfirm, onCancel, }: ConfirmDeleteRuleModalProps): import("react/jsx-runtime").JSX.Element;
16
+ export {};
@@ -0,0 +1,43 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Loader, Modal, Stack, Wrap } from "@scality/core-ui";
3
+ import { Button } from "@scality/core-ui/dist/next";
4
+ function ConfirmDeleteRuleModal({ isOpen, ruleId, ruleType, isDeleting, onConfirm, onCancel }) {
5
+ const ruleTypeLabel = "lifecycle" === ruleType ? "Lifecycle" : "Replication";
6
+ return /*#__PURE__*/ jsxs(Modal, {
7
+ close: onCancel,
8
+ isOpen: isOpen,
9
+ footer: /*#__PURE__*/ jsxs(Wrap, {
10
+ children: [
11
+ /*#__PURE__*/ jsx("p", {}),
12
+ /*#__PURE__*/ jsxs(Stack, {
13
+ children: [
14
+ /*#__PURE__*/ jsx(Button, {
15
+ variant: "outline",
16
+ onClick: onCancel,
17
+ label: "Cancel",
18
+ disabled: isDeleting
19
+ }),
20
+ /*#__PURE__*/ jsx(Button, {
21
+ variant: "danger",
22
+ onClick: onConfirm,
23
+ icon: isDeleting && /*#__PURE__*/ jsx(Loader, {
24
+ size: "larger"
25
+ }),
26
+ label: "Delete",
27
+ disabled: isDeleting
28
+ })
29
+ ]
30
+ })
31
+ ]
32
+ }),
33
+ title: `Delete ${ruleTypeLabel} Rule`,
34
+ children: [
35
+ "Are you sure you want to delete the ",
36
+ ruleType,
37
+ ' rule "',
38
+ ruleId,
39
+ '"?'
40
+ ]
41
+ });
42
+ }
43
+ export { ConfirmDeleteRuleModal };
@@ -0,0 +1,44 @@
1
+ import { UseFormRegisterReturn } from "react-hook-form";
2
+ export interface FilterFormValues {
3
+ filterType: "none" | "prefix" | "tags" | "and";
4
+ prefix: string;
5
+ tags: Array<{
6
+ key: string;
7
+ value: string;
8
+ }>;
9
+ }
10
+ interface FilterFormSectionProps {
11
+ filterType: string;
12
+ onFilterTypeChange: (value: string) => void;
13
+ prefixRegister: UseFormRegisterReturn;
14
+ tagFields: Array<{
15
+ id: string;
16
+ key: string;
17
+ value: string;
18
+ }>;
19
+ tagKeyRegister: (index: number) => UseFormRegisterReturn;
20
+ tagValueRegister: (index: number) => UseFormRegisterReturn;
21
+ getTagKeyValue: (index: number) => string;
22
+ getTagValueValue: (index: number) => string;
23
+ appendTag: (value: {
24
+ key: string;
25
+ value: string;
26
+ }) => void;
27
+ removeTag: (index: number) => void;
28
+ errors?: {
29
+ prefix?: {
30
+ message?: string;
31
+ };
32
+ tags?: {
33
+ message?: string;
34
+ };
35
+ };
36
+ forceLabelWidth?: number;
37
+ }
38
+ export declare function FilterFormSection({ filterType, onFilterTypeChange, prefixRegister, tagFields, tagKeyRegister, tagValueRegister, getTagKeyValue, getTagValueValue, appendTag, removeTag, errors, forceLabelWidth, }: FilterFormSectionProps): import("react/jsx-runtime").JSX.Element;
39
+ export declare const createFilterValidationSchema: (Joi: typeof import("joi")) => {
40
+ filterType: import("joi").StringSchema<string>;
41
+ prefix: import("joi").AlternativesSchema<any>;
42
+ tags: import("joi").AlternativesSchema<any>;
43
+ };
44
+ export {};
@@ -0,0 +1,159 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { FormGroup, FormSection, Stack, Text } from "@scality/core-ui";
3
+ import { convertRemToPixels } from "@scality/core-ui/dist/components/tablev2/TableUtils";
4
+ import { Box, Input, Select } from "@scality/core-ui/dist/next";
5
+ import { ArrayFieldActions } from "./ArrayFieldActions.js";
6
+ const filterTypeOptions = [
7
+ {
8
+ value: "none",
9
+ label: "All objects"
10
+ },
11
+ {
12
+ value: "prefix",
13
+ label: "Prefix filter"
14
+ },
15
+ {
16
+ value: "tags",
17
+ label: "Tags filter"
18
+ },
19
+ {
20
+ value: "and",
21
+ label: "Prefix and tags filter"
22
+ }
23
+ ];
24
+ const shouldShowPrefix = (filterType)=>"prefix" === filterType || "and" === filterType;
25
+ const shouldShowTags = (filterType)=>"tags" === filterType || "and" === filterType;
26
+ function FilterFormSection({ filterType, onFilterTypeChange, prefixRegister, tagFields, tagKeyRegister, tagValueRegister, getTagKeyValue, getTagValueValue, appendTag, removeTag, errors, forceLabelWidth = convertRemToPixels(15) }) {
27
+ return /*#__PURE__*/ jsxs(FormSection, {
28
+ title: {
29
+ name: "Filter"
30
+ },
31
+ forceLabelWidth: forceLabelWidth,
32
+ children: [
33
+ /*#__PURE__*/ jsx(FormGroup, {
34
+ label: "Filter",
35
+ id: "filterType",
36
+ direction: "horizontal",
37
+ content: /*#__PURE__*/ jsx(Select, {
38
+ id: "filterType",
39
+ value: filterType,
40
+ onChange: onFilterTypeChange,
41
+ children: filterTypeOptions.map((option)=>/*#__PURE__*/ jsx(Select.Option, {
42
+ value: option.value,
43
+ children: option.label
44
+ }, option.value))
45
+ })
46
+ }),
47
+ shouldShowPrefix(filterType) ? /*#__PURE__*/ jsx(FormGroup, {
48
+ label: "Prefix",
49
+ id: "prefix",
50
+ direction: "horizontal",
51
+ error: errors?.prefix?.message,
52
+ helpErrorPosition: "bottom",
53
+ required: "prefix" === filterType,
54
+ content: /*#__PURE__*/ jsx(Input, {
55
+ id: "prefix",
56
+ placeholder: "folder/",
57
+ ...prefixRegister
58
+ })
59
+ }) : /*#__PURE__*/ jsx(Fragment, {}),
60
+ shouldShowTags(filterType) ? /*#__PURE__*/ jsx(FormGroup, {
61
+ label: "Tags",
62
+ id: "tags",
63
+ direction: "horizontal",
64
+ error: errors?.tags?.message,
65
+ helpErrorPosition: "bottom",
66
+ required: true,
67
+ content: /*#__PURE__*/ jsxs(Stack, {
68
+ direction: "vertical",
69
+ gap: "r8",
70
+ children: [
71
+ /*#__PURE__*/ jsxs(Stack, {
72
+ gap: "r8",
73
+ children: [
74
+ /*#__PURE__*/ jsx(Box, {
75
+ flex: "1",
76
+ children: /*#__PURE__*/ jsx(Text, {
77
+ color: "textSecondary",
78
+ children: "Key"
79
+ })
80
+ }),
81
+ /*#__PURE__*/ jsx(Box, {
82
+ flex: "1",
83
+ children: /*#__PURE__*/ jsx(Text, {
84
+ color: "textSecondary",
85
+ children: "Value"
86
+ })
87
+ }),
88
+ /*#__PURE__*/ jsx(Box, {
89
+ width: "80px"
90
+ })
91
+ ]
92
+ }),
93
+ tagFields.map((field, index)=>/*#__PURE__*/ jsxs(Stack, {
94
+ gap: "r8",
95
+ children: [
96
+ /*#__PURE__*/ jsx(Input, {
97
+ id: `tag-key-${index}`,
98
+ placeholder: "Key",
99
+ size: "1/2",
100
+ ...tagKeyRegister(index)
101
+ }),
102
+ /*#__PURE__*/ jsx(Input, {
103
+ id: `tag-value-${index}`,
104
+ placeholder: "Value",
105
+ size: "1/2",
106
+ ...tagValueRegister(index)
107
+ }),
108
+ /*#__PURE__*/ jsx(ArrayFieldActions, {
109
+ showAdd: index === tagFields.length - 1,
110
+ onRemove: ()=>removeTag(index),
111
+ onAdd: ()=>appendTag({
112
+ key: "",
113
+ value: ""
114
+ }),
115
+ canRemove: tagFields.length > 1,
116
+ canAdd: !!getTagKeyValue(index) && !!getTagValueValue(index),
117
+ removeLabel: "Remove tag",
118
+ addLabel: "Add tag"
119
+ })
120
+ ]
121
+ }, field.id))
122
+ ]
123
+ })
124
+ }) : /*#__PURE__*/ jsx(Fragment, {})
125
+ ]
126
+ });
127
+ }
128
+ const createFilterValidationSchema = (Joi)=>({
129
+ filterType: Joi.string().valid("none", "prefix", "tags", "and").required(),
130
+ prefix: Joi.when("filterType", {
131
+ is: "prefix",
132
+ then: Joi.string().required().min(1).messages({
133
+ "string.empty": "Prefix is required when using prefix filter",
134
+ "string.min": "Prefix is required when using prefix filter"
135
+ }),
136
+ otherwise: Joi.when("filterType", {
137
+ is: "and",
138
+ then: Joi.string().allow("").optional(),
139
+ otherwise: Joi.any()
140
+ })
141
+ }),
142
+ tags: Joi.when("filterType", {
143
+ is: Joi.valid("tags", "and"),
144
+ then: Joi.array().items(Joi.object({
145
+ key: Joi.string().required().max(128).messages({
146
+ "string.empty": "Tag key is required",
147
+ "string.max": "Tag key must not exceed 128 characters"
148
+ }),
149
+ value: Joi.string().required().max(256).messages({
150
+ "string.empty": "Tag value is required",
151
+ "string.max": "Tag value must not exceed 256 characters"
152
+ })
153
+ })).min(1).messages({
154
+ "array.min": "At least one tag is required when using tag filter"
155
+ }),
156
+ otherwise: Joi.array().optional()
157
+ })
158
+ });
159
+ export { FilterFormSection, createFilterValidationSchema };
@@ -1,5 +1,15 @@
1
1
  import type { S3ClientConfiguration } from "./types";
2
2
  import type { S3BrowserConfig, S3Credentials } from "../types";
3
+ import { CoreUIThemeName } from "@scality/core-ui/dist/style/theme";
4
+ interface RuntimeConfig {
5
+ s3: {
6
+ endpoint?: string;
7
+ region: string;
8
+ forcePathStyle?: boolean;
9
+ };
10
+ theme?: CoreUIThemeName;
11
+ basePath?: string;
12
+ }
3
13
  /**
4
14
  * Configuration factory that uses build-time constants
5
15
  * No runtime environment detection or hardcoded values
@@ -9,7 +19,7 @@ export declare class S3ConfigurationFactory {
9
19
  * Load runtime configuration from config.json
10
20
  * Should be called at app startup
11
21
  */
12
- static loadRuntimeConfig(): Promise<void>;
22
+ static loadRuntimeConfig(configUrl: string): Promise<RuntimeConfig | null>;
13
23
  private static getBuildTimeConfig;
14
24
  /**
15
25
  * Create S3 client configuration based on build-time settings
@@ -49,4 +59,5 @@ export declare const getBuildInfo: () => {
49
59
  s3Endpoint: string;
50
60
  proxyEndpoint: string | undefined;
51
61
  };
52
- export declare const loadRuntimeConfig: () => Promise<void>;
62
+ export declare const loadRuntimeConfig: (configUrl: string) => Promise<RuntimeConfig | null>;
63
+ export {};
@@ -1,15 +1,18 @@
1
1
  let runtimeConfig = null;
2
2
  class S3ConfigurationFactory {
3
- static async loadRuntimeConfig() {
3
+ static async loadRuntimeConfig(configUrl) {
4
4
  try {
5
- const response = await fetch("/_/s3-browser/config.json");
5
+ const response = await fetch(configUrl);
6
6
  if (response.ok) {
7
7
  const data = await response.json();
8
- runtimeConfig = data.s3;
9
- console.log("Loaded runtime S3 configuration:", runtimeConfig);
8
+ runtimeConfig = data;
9
+ console.log("Loaded runtime configuration:", runtimeConfig);
10
+ return data;
10
11
  }
12
+ return null;
11
13
  } catch (error) {
12
14
  console.warn("Could not load runtime config, using build-time config:", error);
15
+ return null;
13
16
  }
14
17
  }
15
18
  static getBuildTimeConfig() {
@@ -22,7 +25,7 @@ class S3ConfigurationFactory {
22
25
  }
23
26
  static createClientConfiguration(credentials) {
24
27
  const buildConfig = this.getBuildTimeConfig();
25
- const s3Config = buildConfig.isProduction && runtimeConfig ? runtimeConfig : buildConfig.s3;
28
+ const s3Config = buildConfig.isProduction && runtimeConfig?.s3 ? runtimeConfig.s3 : buildConfig.s3;
26
29
  const baseConfig = {
27
30
  credentials,
28
31
  region: s3Config.region,
@@ -66,5 +69,5 @@ const createS3Config = (credentials)=>S3ConfigurationFactory.createClientConfigu
66
69
  const shouldUseProxy = ()=>S3ConfigurationFactory.shouldUseProxyMiddleware();
67
70
  const getProxyConfig = ()=>S3ConfigurationFactory.createProxyConfiguration();
68
71
  const getBuildInfo = ()=>S3ConfigurationFactory.getBuildInfo();
69
- const loadRuntimeConfig = ()=>S3ConfigurationFactory.loadRuntimeConfig();
72
+ const loadRuntimeConfig = (configUrl)=>S3ConfigurationFactory.loadRuntimeConfig(configUrl);
70
73
  export { S3ConfigurationFactory, createS3Config, getBuildInfo, getProxyConfig, loadRuntimeConfig, shouldUseProxy };
@@ -0,0 +1,188 @@
1
+ import { renderHook } from "@testing-library/react";
2
+ import { useISVBucketStatus } from "../useISVBucketDetection.js";
3
+ import { useGetBucketTagging } from "../bucketConfiguration.js";
4
+ import { useFeatures } from "../../utils/useFeatures.js";
5
+ import { BUCKET_TAG_APPLICATION, BUCKET_TAG_VEEAM_APPLICATION, COMMVAULT_APPLICATION, VEEAM_BACKUP_REPLICATION, VEEAM_OFFICE_365, VEEAM_OFFICE_365_V8, VEEAM_VBO_APPLICATION } from "../../utils/constants.js";
6
+ import { createTestWrapper } from "../../test/testUtils.js";
7
+ jest.mock("../bucketConfiguration");
8
+ jest.mock("../../utils/useFeatures");
9
+ const mockUseGetBucketTagging = useGetBucketTagging;
10
+ const mockUseFeatures = useFeatures;
11
+ describe("useISVBucketStatus", ()=>{
12
+ beforeEach(()=>{
13
+ jest.clearAllMocks();
14
+ mockUseFeatures.mockReturnValue(true);
15
+ });
16
+ it("should return false for all flags when ISV feature is disabled", ()=>{
17
+ mockUseFeatures.mockReturnValue(false);
18
+ mockUseGetBucketTagging.mockReturnValue({
19
+ data: void 0,
20
+ status: "success"
21
+ });
22
+ const { result } = renderHook(()=>useISVBucketStatus("test-bucket"), {
23
+ wrapper: createTestWrapper()
24
+ });
25
+ expect(result.current.isVeeamBucket).toBe(false);
26
+ expect(result.current.isCommvaultBucket).toBe(false);
27
+ expect(result.current.isISVManaged).toBe(false);
28
+ expect(result.current.isvApplication).toBeUndefined();
29
+ expect(result.current.isLoading).toBe(false);
30
+ });
31
+ it("should detect Veeam Backup & Replication bucket", ()=>{
32
+ mockUseGetBucketTagging.mockReturnValue({
33
+ data: {
34
+ TagSet: [
35
+ {
36
+ Key: BUCKET_TAG_VEEAM_APPLICATION,
37
+ Value: VEEAM_BACKUP_REPLICATION
38
+ }
39
+ ]
40
+ },
41
+ status: "success"
42
+ });
43
+ const { result } = renderHook(()=>useISVBucketStatus("test-bucket"), {
44
+ wrapper: createTestWrapper()
45
+ });
46
+ expect(result.current.isVeeamBucket).toBe(true);
47
+ expect(result.current.isCommvaultBucket).toBe(false);
48
+ expect(result.current.isISVManaged).toBe(true);
49
+ expect(result.current.isvApplication).toBe("Veeam");
50
+ });
51
+ it("should detect Veeam Office 365 v6/v7 bucket", ()=>{
52
+ mockUseGetBucketTagging.mockReturnValue({
53
+ data: {
54
+ TagSet: [
55
+ {
56
+ Key: BUCKET_TAG_VEEAM_APPLICATION,
57
+ Value: VEEAM_OFFICE_365
58
+ }
59
+ ]
60
+ },
61
+ status: "success"
62
+ });
63
+ const { result } = renderHook(()=>useISVBucketStatus("test-bucket"), {
64
+ wrapper: createTestWrapper()
65
+ });
66
+ expect(result.current.isVeeamBucket).toBe(true);
67
+ expect(result.current.isISVManaged).toBe(true);
68
+ expect(result.current.isvApplication).toBe("Veeam");
69
+ });
70
+ it("should detect Veeam Office 365 v8+ bucket", ()=>{
71
+ mockUseGetBucketTagging.mockReturnValue({
72
+ data: {
73
+ TagSet: [
74
+ {
75
+ Key: BUCKET_TAG_VEEAM_APPLICATION,
76
+ Value: VEEAM_OFFICE_365_V8
77
+ }
78
+ ]
79
+ },
80
+ status: "success"
81
+ });
82
+ const { result } = renderHook(()=>useISVBucketStatus("test-bucket"), {
83
+ wrapper: createTestWrapper()
84
+ });
85
+ expect(result.current.isVeeamBucket).toBe(true);
86
+ expect(result.current.isISVManaged).toBe(true);
87
+ expect(result.current.isvApplication).toBe("Veeam");
88
+ });
89
+ it("should detect ISV bucket tagged as Veeam Backup & Replication", ()=>{
90
+ mockUseGetBucketTagging.mockReturnValue({
91
+ data: {
92
+ TagSet: [
93
+ {
94
+ Key: BUCKET_TAG_APPLICATION,
95
+ Value: VEEAM_BACKUP_REPLICATION
96
+ }
97
+ ]
98
+ },
99
+ status: "success"
100
+ });
101
+ const { result } = renderHook(()=>useISVBucketStatus("test-bucket"), {
102
+ wrapper: createTestWrapper()
103
+ });
104
+ expect(result.current.isVeeamBucket).toBe(true);
105
+ expect(result.current.isISVManaged).toBe(true);
106
+ expect(result.current.isvApplication).toBe("Veeam");
107
+ });
108
+ it("should detect ISV bucket tagged as Veeam VBO", ()=>{
109
+ mockUseGetBucketTagging.mockReturnValue({
110
+ data: {
111
+ TagSet: [
112
+ {
113
+ Key: BUCKET_TAG_APPLICATION,
114
+ Value: VEEAM_VBO_APPLICATION
115
+ }
116
+ ]
117
+ },
118
+ status: "success"
119
+ });
120
+ const { result } = renderHook(()=>useISVBucketStatus("test-bucket"), {
121
+ wrapper: createTestWrapper()
122
+ });
123
+ expect(result.current.isVeeamBucket).toBe(true);
124
+ expect(result.current.isISVManaged).toBe(true);
125
+ expect(result.current.isvApplication).toBe("Veeam");
126
+ });
127
+ it("should detect Commvault bucket", ()=>{
128
+ mockUseGetBucketTagging.mockReturnValue({
129
+ data: {
130
+ TagSet: [
131
+ {
132
+ Key: BUCKET_TAG_APPLICATION,
133
+ Value: COMMVAULT_APPLICATION
134
+ }
135
+ ]
136
+ },
137
+ status: "success"
138
+ });
139
+ const { result } = renderHook(()=>useISVBucketStatus("test-bucket"), {
140
+ wrapper: createTestWrapper()
141
+ });
142
+ expect(result.current.isVeeamBucket).toBe(false);
143
+ expect(result.current.isCommvaultBucket).toBe(true);
144
+ expect(result.current.isISVManaged).toBe(true);
145
+ expect(result.current.isvApplication).toBe("Commvault");
146
+ });
147
+ it("should return false for non-ISV bucket", ()=>{
148
+ mockUseGetBucketTagging.mockReturnValue({
149
+ data: {
150
+ TagSet: [
151
+ {
152
+ Key: "SomeOtherTag",
153
+ Value: "SomeValue"
154
+ }
155
+ ]
156
+ },
157
+ status: "success"
158
+ });
159
+ const { result } = renderHook(()=>useISVBucketStatus("test-bucket"), {
160
+ wrapper: createTestWrapper()
161
+ });
162
+ expect(result.current.isVeeamBucket).toBe(false);
163
+ expect(result.current.isCommvaultBucket).toBe(false);
164
+ expect(result.current.isISVManaged).toBe(false);
165
+ expect(result.current.isvApplication).toBeUndefined();
166
+ });
167
+ it("should return loading state when fetching bucket tags", ()=>{
168
+ mockUseGetBucketTagging.mockReturnValue({
169
+ data: void 0,
170
+ status: "pending"
171
+ });
172
+ const { result } = renderHook(()=>useISVBucketStatus("test-bucket"), {
173
+ wrapper: createTestWrapper()
174
+ });
175
+ expect(result.current.isLoading).toBe(true);
176
+ });
177
+ it("should not be loading when ISV feature is disabled", ()=>{
178
+ mockUseFeatures.mockReturnValue(false);
179
+ mockUseGetBucketTagging.mockReturnValue({
180
+ data: void 0,
181
+ status: "pending"
182
+ });
183
+ const { result } = renderHook(()=>useISVBucketStatus("test-bucket"), {
184
+ wrapper: createTestWrapper()
185
+ });
186
+ expect(result.current.isLoading).toBe(false);
187
+ });
188
+ });
@@ -1,12 +1,13 @@
1
1
  import { renderHook, waitFor } from "@testing-library/react";
2
2
  import { useCreateS3QueryHook } from "../useCreateS3QueryHook.js";
3
3
  import { useS3Client } from "../../useS3Client.js";
4
- import { createS3OperationError } from "../../../utils/errorHandling.js";
4
+ import { createS3OperationError, isNotFoundError } from "../../../utils/errorHandling.js";
5
5
  import { createQueryWrapper, validateFactoryHook, validateHookResult } from "../../../test/testUtils.js";
6
6
  jest.mock("../../useS3Client");
7
7
  jest.mock("../../../utils/errorHandling");
8
8
  const mockUseS3Client = useS3Client;
9
9
  const mockCreateS3OperationError = createS3OperationError;
10
+ const mockIsNotFoundError = isNotFoundError;
10
11
  class MockCommand {
11
12
  input;
12
13
  constructor(input){
@@ -133,4 +134,46 @@ describe("useCreateS3QueryHook - Factory Specific", ()=>{
133
134
  expect(capturedAbortSignal).toBeInstanceOf(AbortSignal);
134
135
  expect(capturedAbortSignal?.aborted).toBe(false);
135
136
  });
137
+ it("should return empty config for supported operations when NOT_FOUND error occurs", async ()=>{
138
+ const error = new Error("Configuration does not exist");
139
+ error.name = "NoSuchConfiguration";
140
+ error.$metadata = {
141
+ httpStatusCode: 404
142
+ };
143
+ mockS3Client.send.mockRejectedValueOnce(error);
144
+ mockIsNotFoundError.mockReturnValueOnce(true);
145
+ const useTestQuery = useCreateS3QueryHook(MockCommand, "GetBucketLifecycleConfiguration");
146
+ const { result } = renderHook(()=>useTestQuery({
147
+ Bucket: "test-bucket"
148
+ }), {
149
+ wrapper: createQueryWrapper()
150
+ });
151
+ await waitFor(()=>{
152
+ expect(result.current.isSuccess).toBe(true);
153
+ });
154
+ expect(result.current.data).toBeDefined();
155
+ expect(mockIsNotFoundError).toHaveBeenCalledWith(error);
156
+ });
157
+ it("should throw error when NOT_FOUND occurs for unsupported operations", async ()=>{
158
+ const error = new Error("Bucket not found");
159
+ error.name = "NoSuchBucket";
160
+ error.$metadata = {
161
+ httpStatusCode: 404
162
+ };
163
+ const enhancedError = new Error("Enhanced error");
164
+ mockS3Client.send.mockRejectedValueOnce(error);
165
+ mockIsNotFoundError.mockReturnValueOnce(true);
166
+ mockCreateS3OperationError.mockReturnValueOnce(enhancedError);
167
+ const useTestQuery = useCreateS3QueryHook(MockCommand, "GetBucketAcl");
168
+ const { result } = renderHook(()=>useTestQuery({
169
+ Bucket: "test-bucket"
170
+ }), {
171
+ wrapper: createQueryWrapper()
172
+ });
173
+ await waitFor(()=>{
174
+ expect(result.current.isError).toBe(true);
175
+ });
176
+ expect(result.current.error).toBe(enhancedError);
177
+ expect(mockIsNotFoundError).toHaveBeenCalledWith(error);
178
+ });
136
179
  });
@@ -1,6 +1,23 @@
1
1
  import { useQuery } from "@tanstack/react-query";
2
2
  import { useS3Client } from "../useS3Client.js";
3
- import { createS3OperationError, shouldRetryError } from "../../utils/errorHandling.js";
3
+ import { createS3OperationError, isNotFoundError, shouldRetryError } from "../../utils/errorHandling.js";
4
+ function getEmptyConfigForOperation(operationName) {
5
+ switch(operationName){
6
+ case "GetBucketLifecycleConfiguration":
7
+ return {
8
+ Rules: []
9
+ };
10
+ case "GetBucketReplication":
11
+ return {
12
+ ReplicationConfiguration: {
13
+ Rules: [],
14
+ Role: ""
15
+ }
16
+ };
17
+ default:
18
+ return null;
19
+ }
20
+ }
4
21
  function useCreateS3QueryHook(Command, operationName) {
5
22
  return (params, options)=>{
6
23
  const queryKey = [
@@ -18,6 +35,10 @@ function useCreateS3QueryHook(Command, operationName) {
18
35
  });
19
36
  } catch (error) {
20
37
  const bucketName = params && "Bucket" in params ? params.Bucket : void 0;
38
+ if (isNotFoundError(error)) {
39
+ const emptyConfig = getEmptyConfigForOperation(operationName);
40
+ if (null !== emptyConfig) return emptyConfig;
41
+ }
21
42
  throw createS3OperationError(error, operationName, bucketName);
22
43
  }
23
44
  },