@scality/data-browser-library 1.0.6 → 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.
@@ -3,14 +3,19 @@ import { fireEvent, render, screen, waitFor } from "@testing-library/react";
3
3
  import user_event from "@testing-library/user-event";
4
4
  import { MemoryRouter, Route, Routes } from "react-router";
5
5
  import { useGetBucketLifecycle, useSetBucketLifecycle } from "../../hooks/bucketConfiguration.js";
6
+ import { useISVBucketStatus } from "../../hooks/useISVBucketDetection.js";
6
7
  import { createTestWrapper, findToggleByLabel, mockErrorSubmit, mockOffsetSize, mockSuccessSubmit, submitForm } from "../../test/testUtils.js";
7
8
  import { BucketLifecycleFormPage } from "../buckets/BucketLifecycleFormPage.js";
8
9
  jest.mock('../../hooks/bucketConfiguration', ()=>({
9
10
  useGetBucketLifecycle: jest.fn(),
10
11
  useSetBucketLifecycle: jest.fn()
11
12
  }));
13
+ jest.mock('../../hooks/useISVBucketDetection', ()=>({
14
+ useISVBucketStatus: jest.fn()
15
+ }));
12
16
  const mockUseGetBucketLifecycle = jest.mocked(useGetBucketLifecycle);
13
17
  const mockUseSetBucketLifecycle = jest.mocked(useSetBucketLifecycle);
18
+ const mockUseISVBucketStatus = jest.mocked(useISVBucketStatus);
14
19
  const mockNavigate = jest.fn();
15
20
  const mockShowToast = jest.fn();
16
21
  jest.mock('react-router', ()=>({
@@ -69,6 +74,15 @@ describe('BucketLifecycleFormPage', ()=>{
69
74
  mutate: mockMutate,
70
75
  isPending: false
71
76
  });
77
+ mockUseISVBucketStatus.mockReturnValue({
78
+ isVeeamBucket: false,
79
+ isCommvaultBucket: false,
80
+ isKastenBucket: false,
81
+ isISVManaged: false,
82
+ isvApplication: void 0,
83
+ isLoading: false,
84
+ bucketTagsStatus: 'success'
85
+ });
72
86
  });
73
87
  describe('Page Rendering', ()=>{
74
88
  it('renders create mode with correct title', ()=>{
@@ -615,4 +629,62 @@ describe('BucketLifecycleFormPage', ()=>{
615
629
  });
616
630
  });
617
631
  });
632
+ describe('ISV Bucket Warning', ()=>{
633
+ const setupISVMock = (isvApplication = 'Veeam')=>{
634
+ mockUseISVBucketStatus.mockReturnValue({
635
+ isVeeamBucket: 'Veeam' === isvApplication,
636
+ isCommvaultBucket: 'Commvault' === isvApplication,
637
+ isKastenBucket: 'Kasten' === isvApplication,
638
+ isISVManaged: true,
639
+ isvApplication,
640
+ isLoading: false,
641
+ bucketTagsStatus: 'success'
642
+ });
643
+ };
644
+ it('renders ISV warning banner and checkbox when bucket is ISV-managed', ()=>{
645
+ setupISVMock('Veeam');
646
+ renderBucketLifecycleFormPage();
647
+ expect(screen.getByText(/bucket used for external integration with Veeam/i)).toBeInTheDocument();
648
+ expect(screen.getByText(/may conflict with Veeam's backup strategy/i)).toBeInTheDocument();
649
+ expect(screen.getByLabelText(/i understand what i'm doing/i)).toBeInTheDocument();
650
+ });
651
+ it('does not render ISV warning for non-ISV buckets', ()=>{
652
+ renderBucketLifecycleFormPage();
653
+ expect(screen.queryByText(/bucket used for external integration with/i)).not.toBeInTheDocument();
654
+ expect(screen.queryByLabelText(/i understand what i'm doing/i)).not.toBeInTheDocument();
655
+ });
656
+ it('blocks form submission when ISV checkbox is unchecked', async ()=>{
657
+ setupISVMock('Veeam');
658
+ renderBucketLifecycleFormPage();
659
+ await fillRequiredFields('isv-rule');
660
+ await waitFor(()=>{
661
+ const createButton = screen.getByRole('button', {
662
+ name: /create/i
663
+ });
664
+ expect(createButton).toBeDisabled();
665
+ });
666
+ });
667
+ it('allows form submission when ISV checkbox is checked', async ()=>{
668
+ setupISVMock('Veeam');
669
+ renderBucketLifecycleFormPage();
670
+ await fillRequiredFields('isv-rule');
671
+ const checkbox = screen.getByLabelText(/i understand what i'm doing/i);
672
+ await user_event.click(checkbox);
673
+ mockSuccessSubmit(mockMutate);
674
+ await submitForm('create');
675
+ await waitFor(()=>{
676
+ expect(mockMutate).toHaveBeenCalled();
677
+ });
678
+ });
679
+ it('displays correct application name for Commvault ISV bucket', ()=>{
680
+ setupISVMock('Commvault');
681
+ renderBucketLifecycleFormPage();
682
+ expect(screen.getByText(/bucket used for external integration with Commvault/i)).toBeInTheDocument();
683
+ });
684
+ it('displays correct application name for Kasten ISV bucket', ()=>{
685
+ setupISVMock('Kasten');
686
+ renderBucketLifecycleFormPage();
687
+ expect(screen.getByText(/bucket used for external integration with Kasten/i)).toBeInTheDocument();
688
+ });
689
+ });
618
690
  });
@@ -73,6 +73,7 @@ describe('BucketPolicyPage', ()=>{
73
73
  mockUseISVBucketStatus.mockReturnValue({
74
74
  isVeeamBucket: false,
75
75
  isCommvaultBucket: false,
76
+ isKastenBucket: false,
76
77
  isISVManaged: false,
77
78
  isvApplication: void 0,
78
79
  isLoading: false,
@@ -204,6 +205,7 @@ describe('BucketPolicyPage', ()=>{
204
205
  mockUseISVBucketStatus.mockReturnValue({
205
206
  isVeeamBucket: false,
206
207
  isCommvaultBucket: false,
208
+ isKastenBucket: false,
207
209
  isISVManaged: false,
208
210
  isvApplication: void 0,
209
211
  isLoading: true,
@@ -217,6 +219,7 @@ describe('BucketPolicyPage', ()=>{
217
219
  mockUseISVBucketStatus.mockReturnValue({
218
220
  isVeeamBucket: true,
219
221
  isCommvaultBucket: false,
222
+ isKastenBucket: false,
220
223
  isISVManaged: true,
221
224
  isvApplication: 'Veeam',
222
225
  isLoading: false,
@@ -234,6 +237,7 @@ describe('BucketPolicyPage', ()=>{
234
237
  mockUseISVBucketStatus.mockReturnValue({
235
238
  isVeeamBucket: false,
236
239
  isCommvaultBucket: true,
240
+ isKastenBucket: false,
237
241
  isISVManaged: true,
238
242
  isvApplication: 'Commvault',
239
243
  isLoading: false,
@@ -251,6 +255,7 @@ describe('BucketPolicyPage', ()=>{
251
255
  mockUseISVBucketStatus.mockReturnValue({
252
256
  isVeeamBucket: false,
253
257
  isCommvaultBucket: false,
258
+ isKastenBucket: false,
254
259
  isISVManaged: false,
255
260
  isvApplication: void 0,
256
261
  isLoading: false,
@@ -4,6 +4,7 @@ import user_event from "@testing-library/user-event";
4
4
  import { MemoryRouter, Route, Routes } from "react-router";
5
5
  import { useGetBucketReplication, useSetBucketReplication } from "../../hooks/bucketConfiguration.js";
6
6
  import { useBuckets } from "../../hooks/bucketOperations.js";
7
+ import { useISVBucketStatus } from "../../hooks/useISVBucketDetection.js";
7
8
  import { createTestWrapper, findToggleByLabel, mockErrorSubmit, mockOffsetSize, mockSuccessSubmit, submitForm } from "../../test/testUtils.js";
8
9
  import { BucketReplicationFormPage } from "../buckets/BucketReplicationFormPage.js";
9
10
  jest.mock('../../hooks/bucketConfiguration', ()=>({
@@ -13,9 +14,13 @@ jest.mock('../../hooks/bucketConfiguration', ()=>({
13
14
  jest.mock('../../hooks/bucketOperations', ()=>({
14
15
  useBuckets: jest.fn()
15
16
  }));
17
+ jest.mock('../../hooks/useISVBucketDetection', ()=>({
18
+ useISVBucketStatus: jest.fn()
19
+ }));
16
20
  const mockUseGetBucketReplication = jest.mocked(useGetBucketReplication);
17
21
  const mockUseSetBucketReplication = jest.mocked(useSetBucketReplication);
18
22
  const mockUseBuckets = jest.mocked(useBuckets);
23
+ const mockUseISVBucketStatus = jest.mocked(useISVBucketStatus);
19
24
  const mockNavigate = jest.fn();
20
25
  const mockShowToast = jest.fn();
21
26
  jest.mock('react-router', ()=>({
@@ -96,6 +101,15 @@ describe('BucketReplicationFormPage', ()=>{
96
101
  },
97
102
  status: 'success'
98
103
  });
104
+ mockUseISVBucketStatus.mockReturnValue({
105
+ isVeeamBucket: false,
106
+ isCommvaultBucket: false,
107
+ isKastenBucket: false,
108
+ isISVManaged: false,
109
+ isvApplication: void 0,
110
+ isLoading: false,
111
+ bucketTagsStatus: 'success'
112
+ });
99
113
  });
100
114
  describe('Page Rendering', ()=>{
101
115
  it('renders create mode with correct title', ()=>{
@@ -1876,4 +1890,66 @@ describe('BucketReplicationFormPage', ()=>{
1876
1890
  });
1877
1891
  });
1878
1892
  });
1893
+ describe('ISV Bucket Warning', ()=>{
1894
+ const setupISVMock = (isvApplication = 'Veeam')=>{
1895
+ mockUseISVBucketStatus.mockReturnValue({
1896
+ isVeeamBucket: 'Veeam' === isvApplication,
1897
+ isCommvaultBucket: 'Commvault' === isvApplication,
1898
+ isKastenBucket: 'Kasten' === isvApplication,
1899
+ isISVManaged: true,
1900
+ isvApplication,
1901
+ isLoading: false,
1902
+ bucketTagsStatus: 'success'
1903
+ });
1904
+ };
1905
+ it('renders ISV warning banner and checkbox when bucket is ISV-managed', ()=>{
1906
+ setupISVMock('Veeam');
1907
+ renderBucketReplicationFormPage();
1908
+ expect(screen.getByText(/bucket used for external integration with Veeam/i)).toBeInTheDocument();
1909
+ expect(screen.getByText(/rendering the replicated data unusable for recovery/i)).toBeInTheDocument();
1910
+ expect(screen.getByLabelText(/i understand what i'm doing/i)).toBeInTheDocument();
1911
+ });
1912
+ it('does not render ISV warning for non-ISV buckets', ()=>{
1913
+ renderBucketReplicationFormPage();
1914
+ expect(screen.queryByText(/bucket used for external integration with/i)).not.toBeInTheDocument();
1915
+ expect(screen.queryByLabelText(/i understand what i'm doing/i)).not.toBeInTheDocument();
1916
+ });
1917
+ it('blocks form submission when ISV checkbox is unchecked', async ()=>{
1918
+ setupISVMock('Veeam');
1919
+ renderBucketReplicationFormPage();
1920
+ await fillRequiredFields({
1921
+ ruleId: 'isv-rule'
1922
+ });
1923
+ await waitFor(()=>{
1924
+ const createButton = screen.getByRole('button', {
1925
+ name: /create/i
1926
+ });
1927
+ expect(createButton).toBeDisabled();
1928
+ });
1929
+ });
1930
+ it('allows form submission when ISV checkbox is checked', async ()=>{
1931
+ setupISVMock('Veeam');
1932
+ renderBucketReplicationFormPage();
1933
+ await fillRequiredFields({
1934
+ ruleId: 'isv-rule'
1935
+ });
1936
+ const checkbox = screen.getByLabelText(/i understand what i'm doing/i);
1937
+ await user_event.click(checkbox);
1938
+ mockSuccessSubmit(mockMutate);
1939
+ await submitForm('create');
1940
+ await waitFor(()=>{
1941
+ expect(mockMutate).toHaveBeenCalled();
1942
+ });
1943
+ });
1944
+ it('displays correct application name for Commvault ISV bucket', ()=>{
1945
+ setupISVMock('Commvault');
1946
+ renderBucketReplicationFormPage();
1947
+ expect(screen.getByText(/bucket used for external integration with Commvault/i)).toBeInTheDocument();
1948
+ });
1949
+ it('displays correct application name for Kasten ISV bucket', ()=>{
1950
+ setupISVMock('Kasten');
1951
+ renderBucketReplicationFormPage();
1952
+ expect(screen.getByText(/bucket used for external integration with Kasten/i)).toBeInTheDocument();
1953
+ });
1954
+ });
1879
1955
  });
@@ -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,
@@ -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: {
@@ -8,6 +8,7 @@
8
8
  export declare const useISVBucketStatus: (bucketName: string) => {
9
9
  isVeeamBucket: boolean;
10
10
  isCommvaultBucket: boolean;
11
+ isKastenBucket: boolean;
11
12
  isISVManaged: boolean;
12
13
  isvApplication: string | undefined;
13
14
  isLoading: boolean | undefined;
@@ -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
  };
@@ -158,6 +158,7 @@ export type MockQueryResult<T> = {
158
158
  export type ISVBucketStatusMock = {
159
159
  isVeeamBucket: boolean;
160
160
  isCommvaultBucket: boolean;
161
+ isKastenBucket: boolean;
161
162
  isISVManaged: boolean;
162
163
  isvApplication: string | undefined;
163
164
  isLoading: boolean;
@@ -321,6 +321,7 @@ const createDefaultBucketMocks = ()=>({
321
321
  isvBucketStatus: {
322
322
  isVeeamBucket: false,
323
323
  isCommvaultBucket: false,
324
+ isKastenBucket: false,
324
325
  isISVManaged: false,
325
326
  isvApplication: void 0,
326
327
  isLoading: false,
@@ -10,6 +10,8 @@ export declare const VEEAM_OFFICE_365 = "Veeam Backup for Microsoft 365 (v6, v7)
10
10
  export declare const VEEAM_OFFICE_365_V8 = "Veeam Backup for Microsoft 365 (v8+)";
11
11
  /** Generic identifier for Commvault */
12
12
  export declare const COMMVAULT_APPLICATION = "Commvault";
13
+ /** Generic identifier for Kasten */
14
+ export declare const KASTEN_APPLICATION = "Kasten";
13
15
  export declare const BUCKET_ROUTES: {
14
16
  readonly bucketPolicy: (bucketName: string) => string;
15
17
  readonly bucketCors: (bucketName: string) => string;
@@ -6,6 +6,7 @@ const VEEAM_VBO_APPLICATION = 'Veeam Backup for Microsoft 365';
6
6
  const VEEAM_OFFICE_365 = 'Veeam Backup for Microsoft 365 (v6, v7)';
7
7
  const VEEAM_OFFICE_365_V8 = 'Veeam Backup for Microsoft 365 (v8+)';
8
8
  const COMMVAULT_APPLICATION = 'Commvault';
9
+ const KASTEN_APPLICATION = 'Kasten';
9
10
  const BUCKET_ROUTES = {
10
11
  bucketPolicy: (bucketName)=>`/buckets/${bucketName}/policy`,
11
12
  bucketCors: (bucketName)=>`/buckets/${bucketName}/cors`,
@@ -16,4 +17,4 @@ const BUCKET_ROUTES = {
16
17
  notificationCreate: (bucketName)=>`/buckets/${bucketName}/notifications/create`,
17
18
  notificationEdit: (bucketName, ruleId)=>`/buckets/${bucketName}/notifications/edit/${encodeURIComponent(ruleId)}`
18
19
  };
19
- export { BUCKET_ROUTES, BUCKET_TAG_APPLICATION, BUCKET_TAG_VEEAM_APPLICATION, COMMVAULT_APPLICATION, VEEAM_BACKUP_REPLICATION, VEEAM_IMMUTABLE_POLICY_NAME, VEEAM_OFFICE_365, VEEAM_OFFICE_365_V8, VEEAM_VBO_APPLICATION };
20
+ export { BUCKET_ROUTES, BUCKET_TAG_APPLICATION, BUCKET_TAG_VEEAM_APPLICATION, COMMVAULT_APPLICATION, KASTEN_APPLICATION, VEEAM_BACKUP_REPLICATION, VEEAM_IMMUTABLE_POLICY_NAME, VEEAM_OFFICE_365, VEEAM_OFFICE_365_V8, VEEAM_VBO_APPLICATION };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scality/data-browser-library",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
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",