@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,8 @@
1
+ import type { GetObjectRetentionCommandOutput } from "@aws-sdk/client-s3";
2
+ export declare function getDefaultRetention(retentionData: GetObjectRetentionCommandOutput | null | undefined): {
3
+ isDefaultRetentionEnabled: boolean;
4
+ defaultRetentionUntilDate: Date;
5
+ defaultRetentionMode: import("@aws-sdk/client-s3").ObjectLockRetentionMode;
6
+ };
7
+ export declare function getDefaultMinRetainUntilDate(d: Date | null | undefined, mode: string): string;
8
+ export declare function getRetainUntilDateHint(dateString: string): string;
@@ -0,0 +1,39 @@
1
+ function getDefaultRetention(retentionData) {
2
+ const isDefaultRetentionEnabled = retentionData?.Retention !== void 0 && null !== retentionData.Retention;
3
+ const defaultRetentionUntilDate = retentionData?.Retention?.RetainUntilDate ? new Date(retentionData.Retention.RetainUntilDate) : new Date();
4
+ const defaultRetentionMode = retentionData?.Retention?.Mode || "GOVERNANCE";
5
+ return {
6
+ isDefaultRetentionEnabled,
7
+ defaultRetentionUntilDate,
8
+ defaultRetentionMode
9
+ };
10
+ }
11
+ function getDefaultMinRetainUntilDate(d, mode) {
12
+ const tomorrow = new Date();
13
+ tomorrow.setDate(tomorrow.getDate() + 1);
14
+ const futureDate = formatDateForInput(tomorrow);
15
+ if (!d) return futureDate;
16
+ const previousRetainUntilDate = "GOVERNANCE" === mode ? formatDateForInput(d) : formatDateForInput(addDays(d, 1));
17
+ return previousRetainUntilDate > futureDate ? previousRetainUntilDate : futureDate;
18
+ }
19
+ function formatDateForInput(date) {
20
+ const year = date.getFullYear();
21
+ const month = String(date.getMonth() + 1).padStart(2, "0");
22
+ const day = String(date.getDate()).padStart(2, "0");
23
+ return `${year}-${month}-${day}`;
24
+ }
25
+ function addDays(date, days) {
26
+ const result = new Date(date);
27
+ result.setDate(result.getDate() + days);
28
+ return result;
29
+ }
30
+ function getRetainUntilDateHint(dateString) {
31
+ const now = new Date();
32
+ const retainUntilDate = new Date(dateString);
33
+ const diffInMs = retainUntilDate.getTime() - now.getTime();
34
+ const diffInDays = Math.floor(diffInMs / 86400000);
35
+ if (diffInDays < 1) return "(within 24 hours from now)";
36
+ if (1 === diffInDays) return "(1 day from now)";
37
+ return `(${diffInDays} days from now)`;
38
+ }
39
+ export { getDefaultMinRetainUntilDate, getDefaultRetention, getRetainUntilDateHint };
@@ -0,0 +1,204 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { fireEvent, render, screen } from "@testing-library/react";
3
+ import { MemoryRouter, useNavigate } from "react-router-dom";
4
+ import { createTestWrapper } from "../../../../test/testUtils.js";
5
+ import { EditRetentionButton } from "../EditRetentionButton.js";
6
+ import { useISVBucketStatus } from "../../../../hooks/index.js";
7
+ jest.mock("../../../../hooks");
8
+ jest.mock("react-router-dom", ()=>({
9
+ ...jest.requireActual("react-router-dom"),
10
+ useNavigate: jest.fn()
11
+ }));
12
+ const mockUseISVBucketStatus = jest.mocked(useISVBucketStatus);
13
+ const mockUseNavigate = jest.mocked(useNavigate);
14
+ const renderEditRetentionButton = (props = {})=>{
15
+ const Wrapper = createTestWrapper();
16
+ const mockNavigate = jest.fn();
17
+ mockUseNavigate.mockReturnValue(mockNavigate);
18
+ const result = render(/*#__PURE__*/ jsx(MemoryRouter, {
19
+ children: /*#__PURE__*/ jsx(Wrapper, {
20
+ children: /*#__PURE__*/ jsx(EditRetentionButton, {
21
+ bucketName: "test-bucket",
22
+ ...props
23
+ })
24
+ })
25
+ }));
26
+ return {
27
+ ...result,
28
+ navigate: mockNavigate
29
+ };
30
+ };
31
+ beforeEach(()=>{
32
+ jest.clearAllMocks();
33
+ mockUseISVBucketStatus.mockReturnValue({
34
+ isVeeamBucket: false,
35
+ isCommvaultBucket: false,
36
+ isISVManaged: false,
37
+ isvApplication: void 0,
38
+ isLoading: false,
39
+ bucketTagsStatus: "success"
40
+ });
41
+ });
42
+ it("renders edit button", ()=>{
43
+ renderEditRetentionButton();
44
+ const editButton = screen.getByRole("button", {
45
+ name: /edit default retention/i
46
+ });
47
+ expect(editButton).toBeInTheDocument();
48
+ });
49
+ it("navigates to object lock settings page when clicked", ()=>{
50
+ const { navigate } = renderEditRetentionButton();
51
+ const editButton = screen.getByRole("button", {
52
+ name: /edit default retention/i
53
+ });
54
+ fireEvent.click(editButton);
55
+ expect(navigate).toHaveBeenCalledWith("/buckets/test-bucket/objects/object-lock-settings");
56
+ });
57
+ it("navigates with correct bucket name", ()=>{
58
+ const { navigate } = renderEditRetentionButton();
59
+ const editButton = screen.getByRole("button", {
60
+ name: /edit default retention/i
61
+ });
62
+ fireEvent.click(editButton);
63
+ expect(navigate).toHaveBeenCalledWith("/buckets/test-bucket/objects/object-lock-settings");
64
+ });
65
+ it("is enabled for regular buckets without ISV tags", ()=>{
66
+ renderEditRetentionButton();
67
+ const editButton = screen.getByRole("button", {
68
+ name: /edit default retention/i
69
+ });
70
+ expect(editButton).not.toBeDisabled();
71
+ });
72
+ it("is disabled for Veeam Backup & Replication buckets", ()=>{
73
+ mockUseISVBucketStatus.mockReturnValue({
74
+ isVeeamBucket: true,
75
+ isCommvaultBucket: false,
76
+ isISVManaged: true,
77
+ isvApplication: "Veeam",
78
+ isLoading: false,
79
+ bucketTagsStatus: "success"
80
+ });
81
+ renderEditRetentionButton();
82
+ const editButton = screen.getByRole("button", {
83
+ name: /edit default retention/i
84
+ });
85
+ expect(editButton).toBeDisabled();
86
+ });
87
+ it("is disabled for Veeam Office 365 v6/v7 buckets", ()=>{
88
+ mockUseISVBucketStatus.mockReturnValue({
89
+ isVeeamBucket: true,
90
+ isCommvaultBucket: false,
91
+ isISVManaged: true,
92
+ isvApplication: "Veeam",
93
+ isLoading: false,
94
+ bucketTagsStatus: "success"
95
+ });
96
+ renderEditRetentionButton();
97
+ const editButton = screen.getByRole("button", {
98
+ name: /edit default retention/i
99
+ });
100
+ expect(editButton).toBeDisabled();
101
+ });
102
+ it("is disabled for Veeam Office 365 v8+ buckets", ()=>{
103
+ mockUseISVBucketStatus.mockReturnValue({
104
+ isVeeamBucket: true,
105
+ isCommvaultBucket: false,
106
+ isISVManaged: true,
107
+ isvApplication: "Veeam",
108
+ isLoading: false,
109
+ bucketTagsStatus: "success"
110
+ });
111
+ renderEditRetentionButton();
112
+ const editButton = screen.getByRole("button", {
113
+ name: /edit default retention/i
114
+ });
115
+ expect(editButton).toBeDisabled();
116
+ });
117
+ it("is disabled for Commvault buckets", ()=>{
118
+ mockUseISVBucketStatus.mockReturnValue({
119
+ isVeeamBucket: false,
120
+ isCommvaultBucket: true,
121
+ isISVManaged: true,
122
+ isvApplication: "Commvault",
123
+ isLoading: false,
124
+ bucketTagsStatus: "success"
125
+ });
126
+ renderEditRetentionButton();
127
+ const editButton = screen.getByRole("button", {
128
+ name: /edit default retention/i
129
+ });
130
+ expect(editButton).toBeDisabled();
131
+ });
132
+ it("is disabled for ISV buckets tagged as Veeam Backup for Microsoft 365", ()=>{
133
+ mockUseISVBucketStatus.mockReturnValue({
134
+ isVeeamBucket: true,
135
+ isCommvaultBucket: false,
136
+ isISVManaged: true,
137
+ isvApplication: "Veeam",
138
+ isLoading: false,
139
+ bucketTagsStatus: "success"
140
+ });
141
+ renderEditRetentionButton();
142
+ const editButton = screen.getByRole("button", {
143
+ name: /edit default retention/i
144
+ });
145
+ expect(editButton).toBeDisabled();
146
+ });
147
+ it("is disabled for ISV buckets tagged as Veeam Backup & Replication", ()=>{
148
+ mockUseISVBucketStatus.mockReturnValue({
149
+ isVeeamBucket: true,
150
+ isCommvaultBucket: false,
151
+ isISVManaged: true,
152
+ isvApplication: "Veeam",
153
+ isLoading: false,
154
+ bucketTagsStatus: "success"
155
+ });
156
+ renderEditRetentionButton();
157
+ const editButton = screen.getByRole("button", {
158
+ name: /edit default retention/i
159
+ });
160
+ expect(editButton).toBeDisabled();
161
+ });
162
+ it("handles missing bucket tags gracefully", ()=>{
163
+ renderEditRetentionButton();
164
+ const editButton = screen.getByRole("button", {
165
+ name: /edit default retention/i
166
+ });
167
+ expect(editButton).not.toBeDisabled();
168
+ });
169
+ it("handles empty TagSet gracefully", ()=>{
170
+ renderEditRetentionButton();
171
+ const editButton = screen.getByRole("button", {
172
+ name: /edit default retention/i
173
+ });
174
+ expect(editButton).not.toBeDisabled();
175
+ });
176
+ it("is enabled for buckets with non-ISV tags", ()=>{
177
+ renderEditRetentionButton();
178
+ const editButton = screen.getByRole("button", {
179
+ name: /edit default retention/i
180
+ });
181
+ expect(editButton).not.toBeDisabled();
182
+ });
183
+ it("handles bucket tagging query error gracefully", ()=>{
184
+ renderEditRetentionButton();
185
+ const editButton = screen.getByRole("button", {
186
+ name: /edit default retention/i
187
+ });
188
+ expect(editButton).not.toBeDisabled();
189
+ });
190
+ it("shows loading state when fetching bucket tags", ()=>{
191
+ mockUseISVBucketStatus.mockReturnValue({
192
+ isVeeamBucket: false,
193
+ isCommvaultBucket: false,
194
+ isISVManaged: false,
195
+ isvApplication: void 0,
196
+ isLoading: true,
197
+ bucketTagsStatus: "pending"
198
+ });
199
+ renderEditRetentionButton();
200
+ const editButton = screen.getByRole("button", {
201
+ name: /edit default retention/i
202
+ });
203
+ expect(editButton).toBeInTheDocument();
204
+ });
@@ -0,0 +1,374 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { fireEvent, render, screen, waitFor } from "@testing-library/react";
3
+ import user_event from "@testing-library/user-event";
4
+ import { MemoryRouter, Route, Routes } from "react-router-dom";
5
+ import { createTestWrapper } from "../../../../test/testUtils.js";
6
+ import { ObjectLockSettings } from "../ObjectLockSettings.js";
7
+ import { useGetBucketObjectLockConfiguration, useSetBucketObjectLockConfiguration } from "../../../../hooks/index.js";
8
+ jest.mock("../../../../hooks", ()=>({
9
+ useGetBucketObjectLockConfiguration: jest.fn(),
10
+ useSetBucketObjectLockConfiguration: jest.fn()
11
+ }));
12
+ const mockUseGetBucketObjectLockConfiguration = jest.mocked(useGetBucketObjectLockConfiguration);
13
+ const mockUseSetBucketObjectLockConfiguration = jest.mocked(useSetBucketObjectLockConfiguration);
14
+ const mockNavigate = jest.fn();
15
+ jest.mock("react-router-dom", ()=>({
16
+ ...jest.requireActual("react-router-dom"),
17
+ useNavigate: ()=>mockNavigate
18
+ }));
19
+ const renderObjectLockSettings = (bucketName = "test-bucket")=>{
20
+ const Wrapper = createTestWrapper();
21
+ return render(/*#__PURE__*/ jsx(MemoryRouter, {
22
+ initialEntries: [
23
+ `/buckets/${bucketName}/objects/settings`
24
+ ],
25
+ children: /*#__PURE__*/ jsx(Wrapper, {
26
+ children: /*#__PURE__*/ jsx(Routes, {
27
+ children: /*#__PURE__*/ jsx(Route, {
28
+ path: "/buckets/:bucketName/objects/settings",
29
+ element: /*#__PURE__*/ jsx(ObjectLockSettings, {})
30
+ })
31
+ })
32
+ })
33
+ }));
34
+ };
35
+ describe("ObjectLockSettings", ()=>{
36
+ const mockMutate = jest.fn();
37
+ beforeEach(()=>{
38
+ jest.clearAllMocks();
39
+ mockNavigate.mockClear();
40
+ mockUseGetBucketObjectLockConfiguration.mockReturnValue({
41
+ data: void 0,
42
+ status: "success"
43
+ });
44
+ mockUseSetBucketObjectLockConfiguration.mockReturnValue({
45
+ mutate: mockMutate,
46
+ isPending: false
47
+ });
48
+ });
49
+ it("displays loading state while fetching bucket configuration", ()=>{
50
+ mockUseGetBucketObjectLockConfiguration.mockReturnValue({
51
+ data: void 0,
52
+ status: "pending"
53
+ });
54
+ renderObjectLockSettings();
55
+ expect(screen.getByText("Loading retention settings...")).toBeInTheDocument();
56
+ });
57
+ it("renders form with Object Lock checkbox", async ()=>{
58
+ renderObjectLockSettings();
59
+ await waitFor(()=>{
60
+ expect(screen.getByText("Object-lock settings")).toBeInTheDocument();
61
+ });
62
+ expect(screen.getByLabelText(/object-lock/i)).toBeInTheDocument();
63
+ });
64
+ it("disables save button when Object Lock is not enabled", async ()=>{
65
+ renderObjectLockSettings();
66
+ await waitFor(()=>{
67
+ const saveButton = screen.getByRole("button", {
68
+ name: /save/i
69
+ });
70
+ expect(saveButton).toBeDisabled();
71
+ });
72
+ });
73
+ it("shows Default Retention fields when Object Lock is enabled", async ()=>{
74
+ mockUseGetBucketObjectLockConfiguration.mockReturnValue({
75
+ data: {
76
+ ObjectLockConfiguration: {
77
+ ObjectLockEnabled: "Enabled"
78
+ }
79
+ },
80
+ status: "success"
81
+ });
82
+ renderObjectLockSettings();
83
+ await waitFor(()=>{
84
+ expect(screen.getByText("Default Retention")).toBeInTheDocument();
85
+ expect(screen.getByText("Retention mode")).toBeInTheDocument();
86
+ expect(screen.getByText("Retention period")).toBeInTheDocument();
87
+ }, {
88
+ timeout: 3000
89
+ });
90
+ });
91
+ it("enables save button when Object Lock is enabled", async ()=>{
92
+ mockUseGetBucketObjectLockConfiguration.mockReturnValue({
93
+ data: {
94
+ ObjectLockConfiguration: {
95
+ ObjectLockEnabled: "Enabled"
96
+ }
97
+ },
98
+ status: "success"
99
+ });
100
+ renderObjectLockSettings();
101
+ await waitFor(()=>{
102
+ const saveButton = screen.getByRole("button", {
103
+ name: /save/i
104
+ });
105
+ expect(saveButton).toBeEnabled();
106
+ }, {
107
+ timeout: 3000
108
+ });
109
+ });
110
+ it("enables retention fields when Default Retention is checked", async ()=>{
111
+ mockUseGetBucketObjectLockConfiguration.mockReturnValue({
112
+ data: {
113
+ ObjectLockConfiguration: {
114
+ ObjectLockEnabled: "Enabled"
115
+ }
116
+ },
117
+ status: "success"
118
+ });
119
+ renderObjectLockSettings();
120
+ await waitFor(()=>{
121
+ expect(screen.getByText("Default Retention")).toBeInTheDocument();
122
+ });
123
+ const defaultRetentionCheckbox = screen.getByLabelText(/default retention/i);
124
+ expect(defaultRetentionCheckbox).not.toBeChecked();
125
+ fireEvent.click(defaultRetentionCheckbox);
126
+ await waitFor(()=>{
127
+ const governanceRadio = screen.getByLabelText(/governance/i);
128
+ expect(governanceRadio).not.toBeDisabled();
129
+ }, {
130
+ timeout: 3000
131
+ });
132
+ });
133
+ it("disables retention fields when Default Retention is unchecked", async ()=>{
134
+ mockUseGetBucketObjectLockConfiguration.mockReturnValue({
135
+ data: {
136
+ ObjectLockConfiguration: {
137
+ ObjectLockEnabled: "Enabled",
138
+ Rule: {
139
+ DefaultRetention: {
140
+ Mode: "GOVERNANCE",
141
+ Days: 30
142
+ }
143
+ }
144
+ }
145
+ },
146
+ status: "success"
147
+ });
148
+ renderObjectLockSettings();
149
+ await waitFor(()=>{
150
+ const defaultRetentionCheckbox = screen.getByLabelText(/default retention/i);
151
+ fireEvent.click(defaultRetentionCheckbox);
152
+ });
153
+ await waitFor(()=>{
154
+ const governanceRadio = screen.getByLabelText(/governance/i);
155
+ expect(governanceRadio).toBeDisabled();
156
+ });
157
+ });
158
+ it("shows validation error when retention period is invalid", async ()=>{
159
+ mockUseGetBucketObjectLockConfiguration.mockReturnValue({
160
+ data: {
161
+ ObjectLockConfiguration: {
162
+ ObjectLockEnabled: "Enabled"
163
+ }
164
+ },
165
+ status: "success"
166
+ });
167
+ renderObjectLockSettings();
168
+ await waitFor(()=>{
169
+ expect(screen.getByText("Default Retention")).toBeInTheDocument();
170
+ });
171
+ const defaultRetentionCheckbox = screen.getByLabelText(/default retention/i);
172
+ fireEvent.click(defaultRetentionCheckbox);
173
+ await waitFor(()=>{
174
+ const retentionPeriodInput = screen.getByLabelText(/retention period/i);
175
+ expect(retentionPeriodInput).toBeInTheDocument();
176
+ });
177
+ const retentionPeriodInput = screen.getByLabelText(/retention period/i);
178
+ await user_event.clear(retentionPeriodInput);
179
+ await user_event.type(retentionPeriodInput, "0");
180
+ await waitFor(()=>{
181
+ const saveButton = screen.getByRole("button", {
182
+ name: /save/i
183
+ });
184
+ expect(saveButton).toBeDisabled();
185
+ }, {
186
+ timeout: 3000
187
+ });
188
+ });
189
+ it("saves Object Lock configuration with Default Retention and navigates on success", async ()=>{
190
+ mockUseGetBucketObjectLockConfiguration.mockReturnValue({
191
+ data: {
192
+ ObjectLockConfiguration: {
193
+ ObjectLockEnabled: "Enabled"
194
+ }
195
+ },
196
+ status: "success"
197
+ });
198
+ renderObjectLockSettings();
199
+ await waitFor(()=>{
200
+ expect(screen.getByText("Default Retention")).toBeInTheDocument();
201
+ });
202
+ const defaultRetentionCheckbox = screen.getByLabelText(/default retention/i);
203
+ fireEvent.click(defaultRetentionCheckbox);
204
+ await waitFor(()=>{
205
+ const retentionPeriodInput = screen.getByLabelText(/retention period/i);
206
+ expect(retentionPeriodInput).toBeInTheDocument();
207
+ });
208
+ const retentionPeriodInput = screen.getByLabelText(/retention period/i);
209
+ await user_event.clear(retentionPeriodInput);
210
+ await user_event.type(retentionPeriodInput, "30");
211
+ const governanceRadio = screen.getByLabelText(/governance/i);
212
+ fireEvent.click(governanceRadio);
213
+ mockMutate.mockImplementation((_, options)=>{
214
+ options?.onSuccess?.();
215
+ });
216
+ const saveButton = screen.getByRole("button", {
217
+ name: /save/i
218
+ });
219
+ await waitFor(()=>expect(saveButton).toBeEnabled(), {
220
+ timeout: 3000
221
+ });
222
+ fireEvent.click(saveButton);
223
+ await waitFor(()=>{
224
+ expect(mockMutate).toHaveBeenCalled();
225
+ const callArgs = mockMutate.mock.calls[0][0];
226
+ expect(callArgs.Bucket).toBe("test-bucket");
227
+ expect(callArgs.ObjectLockConfiguration.ObjectLockEnabled).toBe("Enabled");
228
+ expect(callArgs.ObjectLockConfiguration.Rule.DefaultRetention.Mode).toBe("GOVERNANCE");
229
+ expect(callArgs.ObjectLockConfiguration.Rule.DefaultRetention.Days || callArgs.ObjectLockConfiguration.Rule.DefaultRetention.Years).toBe(30);
230
+ expect(mockNavigate).toHaveBeenCalledWith("/buckets/test-bucket");
231
+ });
232
+ });
233
+ it("saves Object Lock configuration with Years frequency", async ()=>{
234
+ mockUseGetBucketObjectLockConfiguration.mockReturnValue({
235
+ data: {
236
+ ObjectLockConfiguration: {
237
+ ObjectLockEnabled: "Enabled"
238
+ }
239
+ },
240
+ status: "success"
241
+ });
242
+ renderObjectLockSettings();
243
+ await waitFor(()=>{
244
+ expect(screen.getByText("Default Retention")).toBeInTheDocument();
245
+ });
246
+ const defaultRetentionCheckbox = screen.getByLabelText(/default retention/i);
247
+ fireEvent.click(defaultRetentionCheckbox);
248
+ await waitFor(()=>{
249
+ const retentionPeriodInput = screen.getByLabelText(/retention period/i);
250
+ expect(retentionPeriodInput).toBeInTheDocument();
251
+ });
252
+ const retentionPeriodInput = screen.getByLabelText(/retention period/i);
253
+ await user_event.clear(retentionPeriodInput);
254
+ await user_event.type(retentionPeriodInput, "2");
255
+ mockMutate.mockImplementation((_, options)=>{
256
+ options?.onSuccess?.();
257
+ });
258
+ const saveButton = screen.getByRole("button", {
259
+ name: /save/i
260
+ });
261
+ await waitFor(()=>expect(saveButton).toBeEnabled(), {
262
+ timeout: 3000
263
+ });
264
+ fireEvent.click(saveButton);
265
+ await waitFor(()=>{
266
+ expect(mockMutate).toHaveBeenCalled();
267
+ const callArgs = mockMutate.mock.calls[0][0];
268
+ expect(callArgs.Bucket).toBe("test-bucket");
269
+ expect(callArgs.ObjectLockConfiguration.ObjectLockEnabled).toBe("Enabled");
270
+ expect(callArgs.ObjectLockConfiguration.Rule.DefaultRetention.Mode).toBe("GOVERNANCE");
271
+ expect(callArgs.ObjectLockConfiguration.Rule.DefaultRetention.Days || callArgs.ObjectLockConfiguration.Rule.DefaultRetention.Years).toBe(2);
272
+ });
273
+ });
274
+ it("saves Object Lock configuration without Default Retention", async ()=>{
275
+ mockUseGetBucketObjectLockConfiguration.mockReturnValue({
276
+ data: {
277
+ ObjectLockConfiguration: {
278
+ ObjectLockEnabled: "Enabled"
279
+ }
280
+ },
281
+ status: "success"
282
+ });
283
+ renderObjectLockSettings();
284
+ await waitFor(()=>{
285
+ const saveButton = screen.getByRole("button", {
286
+ name: /save/i
287
+ });
288
+ expect(saveButton).toBeEnabled();
289
+ }, {
290
+ timeout: 3000
291
+ });
292
+ mockMutate.mockImplementation((_, options)=>{
293
+ options?.onSuccess?.();
294
+ });
295
+ const saveButton = screen.getByRole("button", {
296
+ name: /save/i
297
+ });
298
+ fireEvent.click(saveButton);
299
+ await waitFor(()=>{
300
+ expect(mockMutate).toHaveBeenCalled();
301
+ const callArgs = mockMutate.mock.calls[0][0];
302
+ expect(callArgs.Bucket).toBe("test-bucket");
303
+ expect(callArgs.ObjectLockConfiguration.ObjectLockEnabled).toBe("Enabled");
304
+ expect(callArgs.ObjectLockConfiguration.Rule).toBeUndefined();
305
+ });
306
+ });
307
+ it("displays error toast when save fails", async ()=>{
308
+ mockUseGetBucketObjectLockConfiguration.mockReturnValue({
309
+ data: {
310
+ ObjectLockConfiguration: {
311
+ ObjectLockEnabled: "Enabled"
312
+ }
313
+ },
314
+ status: "success"
315
+ });
316
+ renderObjectLockSettings();
317
+ await waitFor(()=>{
318
+ const saveButton = screen.getByRole("button", {
319
+ name: /save/i
320
+ });
321
+ expect(saveButton).toBeEnabled();
322
+ }, {
323
+ timeout: 3000
324
+ });
325
+ const error = new Error("Access Denied");
326
+ mockMutate.mockImplementation((_, options)=>{
327
+ options?.onError?.(error);
328
+ });
329
+ const saveButton = screen.getByRole("button", {
330
+ name: /save/i
331
+ });
332
+ fireEvent.click(saveButton);
333
+ await waitFor(()=>{
334
+ expect(mockNavigate).not.toHaveBeenCalled();
335
+ });
336
+ });
337
+ it("navigates back when cancel button is clicked", async ()=>{
338
+ renderObjectLockSettings();
339
+ await waitFor(()=>{
340
+ const cancelButton = screen.getByRole("button", {
341
+ name: /cancel/i
342
+ });
343
+ fireEvent.click(cancelButton);
344
+ });
345
+ expect(mockNavigate).toHaveBeenCalledWith("/buckets/test-bucket");
346
+ });
347
+ it("populates form with existing bucket configuration", async ()=>{
348
+ mockUseGetBucketObjectLockConfiguration.mockReturnValue({
349
+ data: {
350
+ ObjectLockConfiguration: {
351
+ ObjectLockEnabled: "Enabled",
352
+ Rule: {
353
+ DefaultRetention: {
354
+ Mode: "COMPLIANCE",
355
+ Days: 60
356
+ }
357
+ }
358
+ }
359
+ },
360
+ status: "success"
361
+ });
362
+ renderObjectLockSettings();
363
+ await waitFor(()=>{
364
+ const objectLockCheckbox = screen.getByLabelText(/object-lock/i);
365
+ expect(objectLockCheckbox).toBeChecked();
366
+ });
367
+ const defaultRetentionCheckbox = screen.getByLabelText(/default retention/i);
368
+ expect(defaultRetentionCheckbox).toBeChecked();
369
+ const complianceRadio = screen.getByLabelText(/compliance/i);
370
+ expect(complianceRadio).toBeChecked();
371
+ const retentionPeriodInput = screen.getByLabelText(/retention period/i);
372
+ expect(retentionPeriodInput).toHaveValue(60);
373
+ });
374
+ });
@@ -0,0 +1,36 @@
1
+ export type ArrayFieldActionsProps = {
2
+ /**
3
+ * Callback when remove button is clicked
4
+ */
5
+ onRemove: () => void;
6
+ /**
7
+ * Callback when add button is clicked
8
+ * Only called if showAdd is true
9
+ */
10
+ onAdd: () => void;
11
+ /**
12
+ * Whether the remove button should be enabled
13
+ */
14
+ canRemove: boolean;
15
+ /**
16
+ * Whether the add button should be enabled
17
+ * @default true
18
+ */
19
+ canAdd?: boolean;
20
+ /**
21
+ * Whether to show the add button
22
+ * Typically true only for the last item in the array
23
+ */
24
+ showAdd: boolean;
25
+ /**
26
+ * Aria label for remove button
27
+ * @default "Remove"
28
+ */
29
+ removeLabel?: string;
30
+ /**
31
+ * Aria label for add button
32
+ * @default "Add"
33
+ */
34
+ addLabel?: string;
35
+ };
36
+ export declare function ArrayFieldActions({ onRemove, onAdd, canRemove, canAdd, showAdd, removeLabel, addLabel, }: ArrayFieldActionsProps): import("react/jsx-runtime").JSX.Element;