@scality/data-browser-library 1.0.5 → 1.0.7
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__/BucketCreate.test.js +60 -20
- package/dist/components/__tests__/BucketLifecycleFormPage.test.js +72 -0
- package/dist/components/__tests__/BucketList.test.js +19 -7
- package/dist/components/__tests__/BucketNotificationFormPage.test.js +54 -19
- package/dist/components/__tests__/BucketPolicyPage.test.js +5 -0
- package/dist/components/__tests__/BucketReplicationFormPage.test.js +259 -61
- package/dist/components/__tests__/MetadataSearch.test.js +18 -12
- package/dist/components/buckets/BucketLifecycleFormPage.js +50 -3
- package/dist/components/buckets/BucketReplicationFormPage.js +50 -4
- package/dist/components/buckets/__tests__/BucketVersioning.test.js +2 -0
- package/dist/components/objects/ObjectLock/__tests__/EditRetentionButton.test.js +8 -0
- package/dist/components/objects/ObjectLock/__tests__/ObjectLockSettings.test.js +78 -26
- package/dist/config/__tests__/factory.test.js +29 -1
- package/dist/config/factory.d.ts +2 -0
- package/dist/config/factory.js +3 -1
- package/dist/hooks/__tests__/useISVBucketDetection.test.js +22 -1
- package/dist/hooks/useISVBucketDetection.d.ts +1 -0
- package/dist/hooks/useISVBucketDetection.js +5 -3
- package/dist/test/testUtils.d.ts +1 -0
- package/dist/test/testUtils.js +1 -0
- package/dist/utils/constants.d.ts +2 -0
- package/dist/utils/constants.js +2 -1
- package/package.json +4 -3
|
@@ -47,27 +47,33 @@ describe('MetadataSearch', ()=>{
|
|
|
47
47
|
await user_event.type(input, 'test query');
|
|
48
48
|
expect(searchButton).toBeEnabled();
|
|
49
49
|
});
|
|
50
|
-
it('shows search icon by default', ()=>{
|
|
50
|
+
it('shows search icon by default', async ()=>{
|
|
51
51
|
renderMetadataSearch();
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
await waitFor(()=>{
|
|
53
|
+
expect(screen.getByRole('img', {
|
|
54
|
+
hidden: true
|
|
55
|
+
})).toBeInTheDocument();
|
|
56
|
+
});
|
|
55
57
|
});
|
|
56
|
-
it('shows check icon when metadata search is active', ()=>{
|
|
58
|
+
it('shows check icon when metadata search is active', async ()=>{
|
|
57
59
|
renderMetadataSearch({}, [
|
|
58
60
|
'/bucket/test-bucket?metadatasearch=test'
|
|
59
61
|
]);
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
await waitFor(()=>{
|
|
63
|
+
expect(screen.getByRole('img', {
|
|
64
|
+
hidden: true
|
|
65
|
+
})).toBeInTheDocument();
|
|
66
|
+
});
|
|
63
67
|
});
|
|
64
|
-
it('shows error icon when error prop is true', ()=>{
|
|
68
|
+
it('shows error icon when error prop is true', async ()=>{
|
|
65
69
|
renderMetadataSearch({
|
|
66
70
|
isError: true
|
|
67
71
|
});
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
await waitFor(()=>{
|
|
73
|
+
expect(screen.getByRole('img', {
|
|
74
|
+
hidden: true
|
|
75
|
+
})).toBeInTheDocument();
|
|
76
|
+
});
|
|
71
77
|
});
|
|
72
78
|
it('populates input with existing search query from URL', ()=>{
|
|
73
79
|
renderMetadataSearch({}, [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { joiResolver } from "@hookform/resolvers/joi";
|
|
3
|
-
import { Form, FormGroup, FormSection, Icon, Loader, Stack, Text, Toggle, spacing, useToast } from "@scality/core-ui";
|
|
3
|
+
import { Banner, Checkbox, Form, FormGroup, FormSection, Icon, Loader, Stack, Text, Toggle, spacing, useToast } from "@scality/core-ui";
|
|
4
4
|
import { convertRemToPixels } from "@scality/core-ui/dist/components/tablev2/TableUtils";
|
|
5
5
|
import { Box, Button, Input, Select } from "@scality/core-ui/dist/next";
|
|
6
6
|
import joi from "joi";
|
|
@@ -9,6 +9,7 @@ import { Controller, FormProvider, useFieldArray, useForm } from "react-hook-for
|
|
|
9
9
|
import { useParams } from "react-router";
|
|
10
10
|
import { useDataBrowserUICustomization } from "../../contexts/DataBrowserUICustomizationContext.js";
|
|
11
11
|
import { useGetBucketLifecycle, useSetBucketLifecycle } from "../../hooks/bucketConfiguration.js";
|
|
12
|
+
import { useISVBucketStatus } from "../../hooks/useISVBucketDetection.js";
|
|
12
13
|
import { useDataBrowserNavigate } from "../../hooks/useDataBrowserNavigate.js";
|
|
13
14
|
import { AWS_RULE_LIMITS, STATUS_OPTIONS, buildS3Filter } from "../../utils/s3RuleUtils.js";
|
|
14
15
|
import { ArrayFieldActions } from "../ui/ArrayFieldActions.js";
|
|
@@ -151,6 +152,9 @@ const createSchema = (hasCustomStorageClassSelector)=>{
|
|
|
151
152
|
'number.min': `Days must be at least ${LIFECYCLE_LIMITS.ABORT_MPU_MIN_DAYS}`
|
|
152
153
|
}),
|
|
153
154
|
otherwise: joi.any()
|
|
155
|
+
}),
|
|
156
|
+
understandISVRisk: joi.boolean().invalid(false).messages({
|
|
157
|
+
'any.invalid': 'You must acknowledge the risk'
|
|
154
158
|
})
|
|
155
159
|
}).custom((value, helpers)=>{
|
|
156
160
|
const hasAtLeastOneAction = value.transitionsEnabled || value.expirationEnabled || value.expiredObjectDeleteMarker || value.noncurrentTransitionsEnabled || value.noncurrentExpirationEnabled || value.abortMpuEnabled;
|
|
@@ -291,6 +295,7 @@ function BucketLifecycleFormPage() {
|
|
|
291
295
|
const navigate = useDataBrowserNavigate();
|
|
292
296
|
const { showToast } = useToast();
|
|
293
297
|
const isEditMode = !!ruleId;
|
|
298
|
+
const { isISVManaged, isvApplication, isLoading: isISVLoading } = useISVBucketStatus(bucketName);
|
|
294
299
|
const { data: lifecycleData, status: lifecycleStatus } = useGetBucketLifecycle({
|
|
295
300
|
Bucket: bucketName
|
|
296
301
|
});
|
|
@@ -338,7 +343,8 @@ function BucketLifecycleFormPage() {
|
|
|
338
343
|
noncurrentExpirationDays: 30,
|
|
339
344
|
noncurrentNewerVersions: 0,
|
|
340
345
|
abortMpuEnabled: false,
|
|
341
|
-
abortMpuDays: 7
|
|
346
|
+
abortMpuDays: 7,
|
|
347
|
+
understandISVRisk: true
|
|
342
348
|
}
|
|
343
349
|
});
|
|
344
350
|
const { handleSubmit, register, control, watch, reset, formState: { isValid, isDirty, errors } } = methods;
|
|
@@ -391,6 +397,14 @@ function BucketLifecycleFormPage() {
|
|
|
391
397
|
existingRule,
|
|
392
398
|
reset
|
|
393
399
|
]);
|
|
400
|
+
useEffect(()=>{
|
|
401
|
+
methods.setValue('understandISVRisk', !isISVManaged, {
|
|
402
|
+
shouldValidate: true
|
|
403
|
+
});
|
|
404
|
+
}, [
|
|
405
|
+
isISVManaged,
|
|
406
|
+
methods
|
|
407
|
+
]);
|
|
394
408
|
const prevTransitionsEnabledRef = useRef(null);
|
|
395
409
|
const prevNoncurrentTransitionsEnabledRef = useRef(null);
|
|
396
410
|
useEffect(()=>{
|
|
@@ -532,7 +546,7 @@ function BucketLifecycleFormPage() {
|
|
|
532
546
|
isEditMode,
|
|
533
547
|
existingRule
|
|
534
548
|
]);
|
|
535
|
-
if ('pending' === lifecycleStatus) return /*#__PURE__*/ jsx(Loader, {
|
|
549
|
+
if ('pending' === lifecycleStatus || isISVLoading) return /*#__PURE__*/ jsx(Loader, {
|
|
536
550
|
centered: true,
|
|
537
551
|
children: /*#__PURE__*/ jsx(Text, {
|
|
538
552
|
children: "Loading..."
|
|
@@ -578,6 +592,39 @@ function BucketLifecycleFormPage() {
|
|
|
578
592
|
]
|
|
579
593
|
}),
|
|
580
594
|
children: [
|
|
595
|
+
isISVManaged && /*#__PURE__*/ jsxs(Stack, {
|
|
596
|
+
direction: "vertical",
|
|
597
|
+
gap: "r16",
|
|
598
|
+
children: [
|
|
599
|
+
/*#__PURE__*/ jsxs(Banner, {
|
|
600
|
+
variant: "warning",
|
|
601
|
+
title: `Bucket used for external integration with
|
|
602
|
+
${isvApplication}`,
|
|
603
|
+
icon: /*#__PURE__*/ jsx(Icon, {
|
|
604
|
+
color: "statusWarning",
|
|
605
|
+
name: "Exclamation-circle"
|
|
606
|
+
}),
|
|
607
|
+
children: [
|
|
608
|
+
"This bucket was provisioned specifically for ",
|
|
609
|
+
isvApplication,
|
|
610
|
+
", which manages its own data retention.",
|
|
611
|
+
/*#__PURE__*/ jsx("br", {}),
|
|
612
|
+
"Configuring manual lifecycle rules here may conflict with ",
|
|
613
|
+
isvApplication,
|
|
614
|
+
"'s backup strategy, potentially leading to data corruption or unintended deletion."
|
|
615
|
+
]
|
|
616
|
+
}),
|
|
617
|
+
/*#__PURE__*/ jsx(Controller, {
|
|
618
|
+
control: control,
|
|
619
|
+
name: "understandISVRisk",
|
|
620
|
+
render: ({ field: { onChange, value } })=>/*#__PURE__*/ jsx(Checkbox, {
|
|
621
|
+
label: "I understand what I'm doing",
|
|
622
|
+
checked: !!value,
|
|
623
|
+
onChange: (e)=>onChange(e.target.checked)
|
|
624
|
+
})
|
|
625
|
+
})
|
|
626
|
+
]
|
|
627
|
+
}),
|
|
581
628
|
/*#__PURE__*/ jsxs(FormSection, {
|
|
582
629
|
title: {
|
|
583
630
|
name: 'Rule Scope'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { joiResolver } from "@hookform/resolvers/joi";
|
|
3
|
-
import { Form, FormGroup, FormSection, Icon, Loader, Stack, Text, Toggle, spacing, useToast } from "@scality/core-ui";
|
|
3
|
+
import { Banner, Checkbox, Form, FormGroup, FormSection, Icon, Loader, Stack, Text, Toggle, spacing, useToast } from "@scality/core-ui";
|
|
4
4
|
import { convertRemToPixels } from "@scality/core-ui/dist/components/tablev2/TableUtils";
|
|
5
5
|
import { Box, Button, Input, Select } from "@scality/core-ui/dist/next";
|
|
6
6
|
import joi from "joi";
|
|
@@ -10,6 +10,7 @@ import { useParams } from "react-router";
|
|
|
10
10
|
import { useDataBrowserUICustomization } from "../../contexts/DataBrowserUICustomizationContext.js";
|
|
11
11
|
import { useGetBucketReplication, useSetBucketReplication } from "../../hooks/bucketConfiguration.js";
|
|
12
12
|
import { useBuckets } from "../../hooks/bucketOperations.js";
|
|
13
|
+
import { useISVBucketStatus } from "../../hooks/useISVBucketDetection.js";
|
|
13
14
|
import { useDataBrowserNavigate } from "../../hooks/useDataBrowserNavigate.js";
|
|
14
15
|
import { AWS_RULE_LIMITS, STATUS_OPTIONS, buildS3Filter } from "../../utils/s3RuleUtils.js";
|
|
15
16
|
import { FilterFormSection, createFilterValidationSchema } from "../ui/FilterFormSection.js";
|
|
@@ -94,7 +95,10 @@ const createSchema = (hasExistingRules)=>joi.object({
|
|
|
94
95
|
otherwise: joi.boolean()
|
|
95
96
|
}),
|
|
96
97
|
deleteMarkerReplication: joi.boolean(),
|
|
97
|
-
switchObjectOwnership: joi.boolean()
|
|
98
|
+
switchObjectOwnership: joi.boolean(),
|
|
99
|
+
understandISVRisk: joi.boolean().invalid(false).messages({
|
|
100
|
+
'any.invalid': 'You must acknowledge the risk'
|
|
101
|
+
})
|
|
98
102
|
});
|
|
99
103
|
const ruleToFormValues = (rule, role)=>{
|
|
100
104
|
const formValues = {
|
|
@@ -252,6 +256,7 @@ function BucketReplicationFormPage() {
|
|
|
252
256
|
const navigate = useDataBrowserNavigate();
|
|
253
257
|
const { showToast } = useToast();
|
|
254
258
|
const isEditMode = !!ruleId;
|
|
259
|
+
const { isISVManaged, isvApplication, isLoading: isISVLoading } = useISVBucketStatus(bucketName);
|
|
255
260
|
const { data: replicationData, status: replicationStatus } = useGetBucketReplication({
|
|
256
261
|
Bucket: bucketName
|
|
257
262
|
});
|
|
@@ -312,7 +317,8 @@ function BucketReplicationFormPage() {
|
|
|
312
317
|
enforceRTC: false,
|
|
313
318
|
enableRTCNotification: false,
|
|
314
319
|
deleteMarkerReplication: false,
|
|
315
|
-
switchObjectOwnership: false
|
|
320
|
+
switchObjectOwnership: false,
|
|
321
|
+
understandISVRisk: true
|
|
316
322
|
}
|
|
317
323
|
});
|
|
318
324
|
const { handleSubmit, register, control, watch, reset, formState: { isValid, isDirty, errors } } = methods;
|
|
@@ -403,6 +409,14 @@ function BucketReplicationFormPage() {
|
|
|
403
409
|
enforceRTC,
|
|
404
410
|
methods
|
|
405
411
|
]);
|
|
412
|
+
useEffect(()=>{
|
|
413
|
+
methods.setValue('understandISVRisk', !isISVManaged, {
|
|
414
|
+
shouldValidate: true
|
|
415
|
+
});
|
|
416
|
+
}, [
|
|
417
|
+
isISVManaged,
|
|
418
|
+
methods
|
|
419
|
+
]);
|
|
406
420
|
const handleCancel = useCallback(()=>{
|
|
407
421
|
navigate(`/buckets/${bucketName}?tab=replication`);
|
|
408
422
|
}, [
|
|
@@ -450,7 +464,7 @@ function BucketReplicationFormPage() {
|
|
|
450
464
|
isEditMode,
|
|
451
465
|
existingRule
|
|
452
466
|
]);
|
|
453
|
-
if ('pending' === replicationStatus) return /*#__PURE__*/ jsx(Loader, {
|
|
467
|
+
if ('pending' === replicationStatus || isISVLoading) return /*#__PURE__*/ jsx(Loader, {
|
|
454
468
|
centered: true,
|
|
455
469
|
children: /*#__PURE__*/ jsx(Text, {
|
|
456
470
|
children: "Loading..."
|
|
@@ -496,6 +510,38 @@ function BucketReplicationFormPage() {
|
|
|
496
510
|
]
|
|
497
511
|
}),
|
|
498
512
|
children: [
|
|
513
|
+
isISVManaged && /*#__PURE__*/ jsxs(Stack, {
|
|
514
|
+
direction: "vertical",
|
|
515
|
+
gap: "r16",
|
|
516
|
+
children: [
|
|
517
|
+
/*#__PURE__*/ jsxs(Banner, {
|
|
518
|
+
variant: "warning",
|
|
519
|
+
title: `Bucket used for external integration with ${isvApplication}`,
|
|
520
|
+
icon: /*#__PURE__*/ jsx(Icon, {
|
|
521
|
+
color: "statusWarning",
|
|
522
|
+
name: "Exclamation-circle"
|
|
523
|
+
}),
|
|
524
|
+
children: [
|
|
525
|
+
"This bucket was provisioned specifically for ",
|
|
526
|
+
isvApplication,
|
|
527
|
+
". Manual replication workflows created here may be redundant or invisible to the application, rendering the replicated data unusable for recovery.",
|
|
528
|
+
/*#__PURE__*/ jsx("br", {}),
|
|
529
|
+
"To ensure data consistency, manage all replication tasks directly within ",
|
|
530
|
+
isvApplication,
|
|
531
|
+
"."
|
|
532
|
+
]
|
|
533
|
+
}),
|
|
534
|
+
/*#__PURE__*/ jsx(Controller, {
|
|
535
|
+
control: control,
|
|
536
|
+
name: "understandISVRisk",
|
|
537
|
+
render: ({ field: { onChange, value } })=>/*#__PURE__*/ jsx(Checkbox, {
|
|
538
|
+
label: "I understand what I'm doing",
|
|
539
|
+
checked: !!value,
|
|
540
|
+
onChange: (e)=>onChange(e.target.checked)
|
|
541
|
+
})
|
|
542
|
+
})
|
|
543
|
+
]
|
|
544
|
+
}),
|
|
499
545
|
/*#__PURE__*/ jsx(FormSection, {
|
|
500
546
|
title: {
|
|
501
547
|
name: 'Role'
|
|
@@ -54,6 +54,7 @@ const mockHookDefaults = ()=>{
|
|
|
54
54
|
mockUseISVBucketStatus.mockReturnValue({
|
|
55
55
|
isVeeamBucket: false,
|
|
56
56
|
isCommvaultBucket: false,
|
|
57
|
+
isKastenBucket: false,
|
|
57
58
|
isISVManaged: false,
|
|
58
59
|
isvApplication: void 0,
|
|
59
60
|
isLoading: false,
|
|
@@ -141,6 +142,7 @@ describe('BucketVersioning', ()=>{
|
|
|
141
142
|
mockUseISVBucketStatus.mockReturnValue({
|
|
142
143
|
isVeeamBucket: true,
|
|
143
144
|
isCommvaultBucket: false,
|
|
145
|
+
isKastenBucket: false,
|
|
144
146
|
isISVManaged: true,
|
|
145
147
|
isvApplication: 'Veeam',
|
|
146
148
|
isLoading: false,
|
|
@@ -33,6 +33,7 @@ beforeEach(()=>{
|
|
|
33
33
|
mockUseISVBucketStatus.mockReturnValue({
|
|
34
34
|
isVeeamBucket: false,
|
|
35
35
|
isCommvaultBucket: false,
|
|
36
|
+
isKastenBucket: false,
|
|
36
37
|
isISVManaged: false,
|
|
37
38
|
isvApplication: void 0,
|
|
38
39
|
isLoading: false,
|
|
@@ -73,6 +74,7 @@ it('is disabled for Veeam Backup & Replication buckets', ()=>{
|
|
|
73
74
|
mockUseISVBucketStatus.mockReturnValue({
|
|
74
75
|
isVeeamBucket: true,
|
|
75
76
|
isCommvaultBucket: false,
|
|
77
|
+
isKastenBucket: false,
|
|
76
78
|
isISVManaged: true,
|
|
77
79
|
isvApplication: 'Veeam',
|
|
78
80
|
isLoading: false,
|
|
@@ -88,6 +90,7 @@ it('is disabled for Veeam Office 365 v6/v7 buckets', ()=>{
|
|
|
88
90
|
mockUseISVBucketStatus.mockReturnValue({
|
|
89
91
|
isVeeamBucket: true,
|
|
90
92
|
isCommvaultBucket: false,
|
|
93
|
+
isKastenBucket: false,
|
|
91
94
|
isISVManaged: true,
|
|
92
95
|
isvApplication: 'Veeam',
|
|
93
96
|
isLoading: false,
|
|
@@ -103,6 +106,7 @@ it('is disabled for Veeam Office 365 v8+ buckets', ()=>{
|
|
|
103
106
|
mockUseISVBucketStatus.mockReturnValue({
|
|
104
107
|
isVeeamBucket: true,
|
|
105
108
|
isCommvaultBucket: false,
|
|
109
|
+
isKastenBucket: false,
|
|
106
110
|
isISVManaged: true,
|
|
107
111
|
isvApplication: 'Veeam',
|
|
108
112
|
isLoading: false,
|
|
@@ -118,6 +122,7 @@ it('is disabled for Commvault buckets', ()=>{
|
|
|
118
122
|
mockUseISVBucketStatus.mockReturnValue({
|
|
119
123
|
isVeeamBucket: false,
|
|
120
124
|
isCommvaultBucket: true,
|
|
125
|
+
isKastenBucket: false,
|
|
121
126
|
isISVManaged: true,
|
|
122
127
|
isvApplication: 'Commvault',
|
|
123
128
|
isLoading: false,
|
|
@@ -133,6 +138,7 @@ it('is disabled for ISV buckets tagged as Veeam Backup for Microsoft 365', ()=>{
|
|
|
133
138
|
mockUseISVBucketStatus.mockReturnValue({
|
|
134
139
|
isVeeamBucket: true,
|
|
135
140
|
isCommvaultBucket: false,
|
|
141
|
+
isKastenBucket: false,
|
|
136
142
|
isISVManaged: true,
|
|
137
143
|
isvApplication: 'Veeam',
|
|
138
144
|
isLoading: false,
|
|
@@ -148,6 +154,7 @@ it('is disabled for ISV buckets tagged as Veeam Backup & Replication', ()=>{
|
|
|
148
154
|
mockUseISVBucketStatus.mockReturnValue({
|
|
149
155
|
isVeeamBucket: true,
|
|
150
156
|
isCommvaultBucket: false,
|
|
157
|
+
isKastenBucket: false,
|
|
151
158
|
isISVManaged: true,
|
|
152
159
|
isvApplication: 'Veeam',
|
|
153
160
|
isLoading: false,
|
|
@@ -191,6 +198,7 @@ it('shows loading state when fetching bucket tags', ()=>{
|
|
|
191
198
|
mockUseISVBucketStatus.mockReturnValue({
|
|
192
199
|
isVeeamBucket: false,
|
|
193
200
|
isCommvaultBucket: false,
|
|
201
|
+
isKastenBucket: false,
|
|
194
202
|
isISVManaged: false,
|
|
195
203
|
isvApplication: void 0,
|
|
196
204
|
isLoading: true,
|
|
@@ -59,7 +59,9 @@ describe('ObjectLockSettings', ()=>{
|
|
|
59
59
|
await waitFor(()=>{
|
|
60
60
|
expect(screen.getByText('Object-lock settings')).toBeInTheDocument();
|
|
61
61
|
});
|
|
62
|
-
expect(screen.
|
|
62
|
+
expect(screen.getByRole('checkbox', {
|
|
63
|
+
name: /object-lock/i
|
|
64
|
+
})).toBeInTheDocument();
|
|
63
65
|
});
|
|
64
66
|
it('disables save button when Object Lock is not enabled', async ()=>{
|
|
65
67
|
renderObjectLockSettings();
|
|
@@ -81,9 +83,15 @@ describe('ObjectLockSettings', ()=>{
|
|
|
81
83
|
});
|
|
82
84
|
renderObjectLockSettings();
|
|
83
85
|
await waitFor(()=>{
|
|
84
|
-
expect(screen.
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
expect(screen.getByRole('checkbox', {
|
|
87
|
+
name: /default retention/i
|
|
88
|
+
})).toBeInTheDocument();
|
|
89
|
+
expect(screen.getByRole('radio', {
|
|
90
|
+
name: /governance/i
|
|
91
|
+
})).toBeInTheDocument();
|
|
92
|
+
expect(screen.getByRole('spinbutton', {
|
|
93
|
+
name: /retention period/i
|
|
94
|
+
})).toBeInTheDocument();
|
|
87
95
|
}, {
|
|
88
96
|
timeout: 3000
|
|
89
97
|
});
|
|
@@ -118,13 +126,19 @@ describe('ObjectLockSettings', ()=>{
|
|
|
118
126
|
});
|
|
119
127
|
renderObjectLockSettings();
|
|
120
128
|
await waitFor(()=>{
|
|
121
|
-
expect(screen.
|
|
129
|
+
expect(screen.getByRole('checkbox', {
|
|
130
|
+
name: /default retention/i
|
|
131
|
+
})).toBeInTheDocument();
|
|
132
|
+
});
|
|
133
|
+
const defaultRetentionCheckbox = screen.getByRole('checkbox', {
|
|
134
|
+
name: /default retention/i
|
|
122
135
|
});
|
|
123
|
-
const defaultRetentionCheckbox = screen.getByLabelText(/default retention/i);
|
|
124
136
|
expect(defaultRetentionCheckbox).not.toBeChecked();
|
|
125
137
|
fireEvent.click(defaultRetentionCheckbox);
|
|
126
138
|
await waitFor(()=>{
|
|
127
|
-
const governanceRadio = screen.
|
|
139
|
+
const governanceRadio = screen.getByRole('radio', {
|
|
140
|
+
name: /governance/i
|
|
141
|
+
});
|
|
128
142
|
expect(governanceRadio).not.toBeDisabled();
|
|
129
143
|
}, {
|
|
130
144
|
timeout: 3000
|
|
@@ -147,11 +161,15 @@ describe('ObjectLockSettings', ()=>{
|
|
|
147
161
|
});
|
|
148
162
|
renderObjectLockSettings();
|
|
149
163
|
await waitFor(()=>{
|
|
150
|
-
const defaultRetentionCheckbox = screen.
|
|
164
|
+
const defaultRetentionCheckbox = screen.getByRole('checkbox', {
|
|
165
|
+
name: /default retention/i
|
|
166
|
+
});
|
|
151
167
|
fireEvent.click(defaultRetentionCheckbox);
|
|
152
168
|
});
|
|
153
169
|
await waitFor(()=>{
|
|
154
|
-
const governanceRadio = screen.
|
|
170
|
+
const governanceRadio = screen.getByRole('radio', {
|
|
171
|
+
name: /governance/i
|
|
172
|
+
});
|
|
155
173
|
expect(governanceRadio).toBeDisabled();
|
|
156
174
|
});
|
|
157
175
|
});
|
|
@@ -166,15 +184,23 @@ describe('ObjectLockSettings', ()=>{
|
|
|
166
184
|
});
|
|
167
185
|
renderObjectLockSettings();
|
|
168
186
|
await waitFor(()=>{
|
|
169
|
-
expect(screen.
|
|
187
|
+
expect(screen.getByRole('checkbox', {
|
|
188
|
+
name: /default retention/i
|
|
189
|
+
})).toBeInTheDocument();
|
|
190
|
+
});
|
|
191
|
+
const defaultRetentionCheckbox = screen.getByRole('checkbox', {
|
|
192
|
+
name: /default retention/i
|
|
170
193
|
});
|
|
171
|
-
const defaultRetentionCheckbox = screen.getByLabelText(/default retention/i);
|
|
172
194
|
fireEvent.click(defaultRetentionCheckbox);
|
|
173
195
|
await waitFor(()=>{
|
|
174
|
-
const retentionPeriodInput = screen.
|
|
196
|
+
const retentionPeriodInput = screen.getByRole('spinbutton', {
|
|
197
|
+
name: /retention period/i
|
|
198
|
+
});
|
|
175
199
|
expect(retentionPeriodInput).toBeInTheDocument();
|
|
176
200
|
});
|
|
177
|
-
const retentionPeriodInput = screen.
|
|
201
|
+
const retentionPeriodInput = screen.getByRole('spinbutton', {
|
|
202
|
+
name: /retention period/i
|
|
203
|
+
});
|
|
178
204
|
await user_event.clear(retentionPeriodInput);
|
|
179
205
|
await user_event.type(retentionPeriodInput, '0');
|
|
180
206
|
await waitFor(()=>{
|
|
@@ -197,18 +223,28 @@ describe('ObjectLockSettings', ()=>{
|
|
|
197
223
|
});
|
|
198
224
|
renderObjectLockSettings();
|
|
199
225
|
await waitFor(()=>{
|
|
200
|
-
expect(screen.
|
|
226
|
+
expect(screen.getByRole('checkbox', {
|
|
227
|
+
name: /default retention/i
|
|
228
|
+
})).toBeInTheDocument();
|
|
229
|
+
});
|
|
230
|
+
const defaultRetentionCheckbox = screen.getByRole('checkbox', {
|
|
231
|
+
name: /default retention/i
|
|
201
232
|
});
|
|
202
|
-
const defaultRetentionCheckbox = screen.getByLabelText(/default retention/i);
|
|
203
233
|
fireEvent.click(defaultRetentionCheckbox);
|
|
204
234
|
await waitFor(()=>{
|
|
205
|
-
const retentionPeriodInput = screen.
|
|
235
|
+
const retentionPeriodInput = screen.getByRole('spinbutton', {
|
|
236
|
+
name: /retention period/i
|
|
237
|
+
});
|
|
206
238
|
expect(retentionPeriodInput).toBeInTheDocument();
|
|
207
239
|
});
|
|
208
|
-
const retentionPeriodInput = screen.
|
|
240
|
+
const retentionPeriodInput = screen.getByRole('spinbutton', {
|
|
241
|
+
name: /retention period/i
|
|
242
|
+
});
|
|
209
243
|
await user_event.clear(retentionPeriodInput);
|
|
210
244
|
await user_event.type(retentionPeriodInput, '30');
|
|
211
|
-
const governanceRadio = screen.
|
|
245
|
+
const governanceRadio = screen.getByRole('radio', {
|
|
246
|
+
name: /governance/i
|
|
247
|
+
});
|
|
212
248
|
fireEvent.click(governanceRadio);
|
|
213
249
|
mockMutate.mockImplementation((_, options)=>{
|
|
214
250
|
options?.onSuccess?.();
|
|
@@ -241,15 +277,23 @@ describe('ObjectLockSettings', ()=>{
|
|
|
241
277
|
});
|
|
242
278
|
renderObjectLockSettings();
|
|
243
279
|
await waitFor(()=>{
|
|
244
|
-
expect(screen.
|
|
280
|
+
expect(screen.getByRole('checkbox', {
|
|
281
|
+
name: /default retention/i
|
|
282
|
+
})).toBeInTheDocument();
|
|
283
|
+
});
|
|
284
|
+
const defaultRetentionCheckbox = screen.getByRole('checkbox', {
|
|
285
|
+
name: /default retention/i
|
|
245
286
|
});
|
|
246
|
-
const defaultRetentionCheckbox = screen.getByLabelText(/default retention/i);
|
|
247
287
|
fireEvent.click(defaultRetentionCheckbox);
|
|
248
288
|
await waitFor(()=>{
|
|
249
|
-
const retentionPeriodInput = screen.
|
|
289
|
+
const retentionPeriodInput = screen.getByRole('spinbutton', {
|
|
290
|
+
name: /retention period/i
|
|
291
|
+
});
|
|
250
292
|
expect(retentionPeriodInput).toBeInTheDocument();
|
|
251
293
|
});
|
|
252
|
-
const retentionPeriodInput = screen.
|
|
294
|
+
const retentionPeriodInput = screen.getByRole('spinbutton', {
|
|
295
|
+
name: /retention period/i
|
|
296
|
+
});
|
|
253
297
|
await user_event.clear(retentionPeriodInput);
|
|
254
298
|
await user_event.type(retentionPeriodInput, '2');
|
|
255
299
|
mockMutate.mockImplementation((_, options)=>{
|
|
@@ -361,14 +405,22 @@ describe('ObjectLockSettings', ()=>{
|
|
|
361
405
|
});
|
|
362
406
|
renderObjectLockSettings();
|
|
363
407
|
await waitFor(()=>{
|
|
364
|
-
const objectLockCheckbox = screen.
|
|
408
|
+
const objectLockCheckbox = screen.getByRole('checkbox', {
|
|
409
|
+
name: /object-lock/i
|
|
410
|
+
});
|
|
365
411
|
expect(objectLockCheckbox).toBeChecked();
|
|
366
412
|
});
|
|
367
|
-
const defaultRetentionCheckbox = screen.
|
|
413
|
+
const defaultRetentionCheckbox = screen.getByRole('checkbox', {
|
|
414
|
+
name: /default retention/i
|
|
415
|
+
});
|
|
368
416
|
expect(defaultRetentionCheckbox).toBeChecked();
|
|
369
|
-
const complianceRadio = screen.
|
|
417
|
+
const complianceRadio = screen.getByRole('radio', {
|
|
418
|
+
name: /compliance/i
|
|
419
|
+
});
|
|
370
420
|
expect(complianceRadio).toBeChecked();
|
|
371
|
-
const retentionPeriodInput = screen.
|
|
421
|
+
const retentionPeriodInput = screen.getByRole('spinbutton', {
|
|
422
|
+
name: /retention period/i
|
|
423
|
+
});
|
|
372
424
|
expect(retentionPeriodInput).toHaveValue(60);
|
|
373
425
|
});
|
|
374
426
|
});
|
|
@@ -212,7 +212,9 @@ describe('s3RuntimeConfigSchema', ()=>{
|
|
|
212
212
|
endpoint: 'https://s3.amazonaws.com',
|
|
213
213
|
region: 'us-east-1',
|
|
214
214
|
forcePathStyle: true
|
|
215
|
-
}
|
|
215
|
+
},
|
|
216
|
+
allowCustomEndpoint: true,
|
|
217
|
+
allowSessionToken: true
|
|
216
218
|
};
|
|
217
219
|
const { error } = s3RuntimeConfigSchema.validate(config);
|
|
218
220
|
expect(error).toBeUndefined();
|
|
@@ -282,6 +284,32 @@ describe('s3RuntimeConfigSchema', ()=>{
|
|
|
282
284
|
expect(error).toBeDefined();
|
|
283
285
|
expect(error?.message).toContain('boolean');
|
|
284
286
|
});
|
|
287
|
+
it('should reject config with wrong type for allowCustomEndpoint', ()=>{
|
|
288
|
+
const config = {
|
|
289
|
+
s3: {
|
|
290
|
+
region: 'us-east-1'
|
|
291
|
+
},
|
|
292
|
+
allowCustomEndpoint: 'yes'
|
|
293
|
+
};
|
|
294
|
+
const { error } = s3RuntimeConfigSchema.validate(config, {
|
|
295
|
+
convert: false
|
|
296
|
+
});
|
|
297
|
+
expect(error).toBeDefined();
|
|
298
|
+
expect(error?.message).toContain('boolean');
|
|
299
|
+
});
|
|
300
|
+
it('should reject config with wrong type for allowSessionToken', ()=>{
|
|
301
|
+
const config = {
|
|
302
|
+
s3: {
|
|
303
|
+
region: 'us-east-1'
|
|
304
|
+
},
|
|
305
|
+
allowSessionToken: 'yes'
|
|
306
|
+
};
|
|
307
|
+
const { error } = s3RuntimeConfigSchema.validate(config, {
|
|
308
|
+
convert: false
|
|
309
|
+
});
|
|
310
|
+
expect(error).toBeDefined();
|
|
311
|
+
expect(error?.message).toContain('boolean');
|
|
312
|
+
});
|
|
285
313
|
});
|
|
286
314
|
describe('Schema behavior', ()=>{
|
|
287
315
|
it('should return all validation errors when abortEarly is false', ()=>{
|
package/dist/config/factory.d.ts
CHANGED
package/dist/config/factory.js
CHANGED
|
@@ -4,7 +4,9 @@ const s3RuntimeConfigSchema = joi.object({
|
|
|
4
4
|
endpoint: joi.string().optional().allow('origin'),
|
|
5
5
|
region: joi.string().min(1).required(),
|
|
6
6
|
forcePathStyle: joi.boolean().optional()
|
|
7
|
-
}).required()
|
|
7
|
+
}).required(),
|
|
8
|
+
allowCustomEndpoint: joi.boolean().optional(),
|
|
9
|
+
allowSessionToken: joi.boolean().optional()
|
|
8
10
|
});
|
|
9
11
|
async function loadRuntimeConfig(configUrl) {
|
|
10
12
|
const response = await fetch(configUrl);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { renderHook } from "@testing-library/react";
|
|
2
2
|
import { createTestWrapper } from "../../test/testUtils.js";
|
|
3
|
-
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";
|
|
3
|
+
import { BUCKET_TAG_APPLICATION, BUCKET_TAG_VEEAM_APPLICATION, COMMVAULT_APPLICATION, KASTEN_APPLICATION, VEEAM_BACKUP_REPLICATION, VEEAM_OFFICE_365, VEEAM_OFFICE_365_V8, VEEAM_VBO_APPLICATION } from "../../utils/constants.js";
|
|
4
4
|
import { useGetBucketTagging } from "../bucketConfiguration.js";
|
|
5
5
|
import { useFeatures } from "../useFeatures.js";
|
|
6
6
|
import { useISVBucketStatus } from "../useISVBucketDetection.js";
|
|
@@ -144,6 +144,27 @@ describe('useISVBucketStatus', ()=>{
|
|
|
144
144
|
expect(result.current.isISVManaged).toBe(true);
|
|
145
145
|
expect(result.current.isvApplication).toBe('Commvault');
|
|
146
146
|
});
|
|
147
|
+
it('should detect Kasten bucket', ()=>{
|
|
148
|
+
mockUseGetBucketTagging.mockReturnValue({
|
|
149
|
+
data: {
|
|
150
|
+
TagSet: [
|
|
151
|
+
{
|
|
152
|
+
Key: BUCKET_TAG_APPLICATION,
|
|
153
|
+
Value: KASTEN_APPLICATION
|
|
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.isKastenBucket).toBe(true);
|
|
165
|
+
expect(result.current.isISVManaged).toBe(true);
|
|
166
|
+
expect(result.current.isvApplication).toBe('Kasten');
|
|
167
|
+
});
|
|
147
168
|
it('should return false for non-ISV bucket', ()=>{
|
|
148
169
|
mockUseGetBucketTagging.mockReturnValue({
|
|
149
170
|
data: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
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";
|
|
1
|
+
import { BUCKET_TAG_APPLICATION, BUCKET_TAG_VEEAM_APPLICATION, COMMVAULT_APPLICATION, KASTEN_APPLICATION, VEEAM_BACKUP_REPLICATION, VEEAM_OFFICE_365, VEEAM_OFFICE_365_V8, VEEAM_VBO_APPLICATION } from "../utils/constants.js";
|
|
2
2
|
import { useGetBucketTagging } from "./bucketConfiguration.js";
|
|
3
3
|
import { useFeatures } from "./useFeatures.js";
|
|
4
4
|
const useISVBucketStatus = (bucketName)=>{
|
|
@@ -13,13 +13,15 @@ const useISVBucketStatus = (bucketName)=>{
|
|
|
13
13
|
const isVeeamBucket = veeamTagApplication === VEEAM_BACKUP_REPLICATION || veeamTagApplication === VEEAM_OFFICE_365 || veeamTagApplication === VEEAM_OFFICE_365_V8;
|
|
14
14
|
const isISVBucketTagAsVeeam = ISVApplicationTag === VEEAM_BACKUP_REPLICATION || ISVApplicationTag === VEEAM_VBO_APPLICATION;
|
|
15
15
|
const isCommvaultBucket = ISVApplicationTag === COMMVAULT_APPLICATION;
|
|
16
|
+
const isKastenBucket = ISVApplicationTag === KASTEN_APPLICATION;
|
|
16
17
|
const isVeeam = isVeeamBucket || isISVBucketTagAsVeeam;
|
|
17
|
-
const isISVManaged = isVeeam || isCommvaultBucket;
|
|
18
|
+
const isISVManaged = isVeeam || isCommvaultBucket || isKastenBucket;
|
|
18
19
|
return {
|
|
19
20
|
isVeeamBucket: isVeeam,
|
|
20
21
|
isCommvaultBucket,
|
|
22
|
+
isKastenBucket,
|
|
21
23
|
isISVManaged,
|
|
22
|
-
isvApplication: isCommvaultBucket ? 'Commvault' : isVeeam ? 'Veeam' : void 0,
|
|
24
|
+
isvApplication: isKastenBucket ? 'Kasten' : isCommvaultBucket ? 'Commvault' : isVeeam ? 'Veeam' : void 0,
|
|
23
25
|
isLoading: isISVFeatureEnabled && 'pending' === bucketTagsStatus,
|
|
24
26
|
bucketTagsStatus
|
|
25
27
|
};
|