diff --git a/integration/access_rules_test.go b/integration/access_rules_test.go index f39f03156..fa10d9891 100644 --- a/integration/access_rules_test.go +++ b/integration/access_rules_test.go @@ -31,7 +31,7 @@ import ( func Test_AddAccessRuleAPI(t *testing.T) { assert := assert.New(t) - AddBucket("testaccessruleadd", false, false, nil, nil) + AddBucket("testaccessruleadd", false, nil, nil, nil) type args struct { bucket string @@ -111,7 +111,7 @@ func Test_AddAccessRuleAPI(t *testing.T) { func Test_GetAccessRulesAPI(t *testing.T) { assert := assert.New(t) - AddBucket("testaccessruleget", false, false, nil, nil) + AddBucket("testaccessruleget", false, nil, nil, nil) type args struct { bucket string @@ -161,7 +161,7 @@ func Test_GetAccessRulesAPI(t *testing.T) { func Test_DeleteAccessRuleAPI(t *testing.T) { assert := assert.New(t) - AddBucket("testaccessruledelete", false, false, nil, nil) + AddBucket("testaccessruledelete", false, nil, nil, nil) type args struct { prefix string diff --git a/integration/user_api_bucket_test.go b/integration/user_api_bucket_test.go index 9890143ba..6758066fd 100644 --- a/integration/user_api_bucket_test.go +++ b/integration/user_api_bucket_test.go @@ -42,14 +42,14 @@ import ( type AddBucketOps struct { Name string Locking bool - Versioning bool + Versioning map[string]interface{} Quota map[string]interface{} Retention map[string]interface{} Endpoint *string UseToken *string } -func AddBucket(name string, locking, versioning bool, quota, retention map[string]interface{}) (*http.Response, error) { +func AddBucket(name string, locking bool, versioning, quota, retention map[string]interface{}) (*http.Response, error) { return AddBucketWithOpts(&AddBucketOps{ Name: name, Locking: locking, @@ -142,11 +142,11 @@ func getTokenForEndpoint(endpoint string) string { return loginToken } -func setupBucket(name string, locking, versioning bool, quota, retention map[string]interface{}, assert *assert.Assertions, expected int) bool { +func setupBucket(name string, locking bool, versioning, quota, retention map[string]interface{}, assert *assert.Assertions, expected int) bool { return setupBucketForEndpoint(name, locking, versioning, quota, retention, assert, expected, nil, nil) } -func setupBucketForEndpoint(name string, locking, versioning bool, quota, retention map[string]interface{}, assert *assert.Assertions, expected int, endpoint, endpointToken *string) bool { +func setupBucketForEndpoint(name string, locking bool, versioning, quota, retention map[string]interface{}, assert *assert.Assertions, expected int, endpoint, endpointToken *string) bool { /* The intention of this function is to return either true or false to reduce the code by performing the verification in one place only. @@ -751,7 +751,7 @@ func TestPutObjectsLegalholdStatus(t *testing.T) { status := "enabled" // 1. Create bucket - if !setupBucket(bucketName, true, true, nil, nil, assert, 200) { + if !setupBucket(bucketName, true, map[string]interface{}{"enabled": true}, nil, nil, assert, 200) { return } @@ -838,7 +838,7 @@ func TestGetBucketQuota(t *testing.T) { validBucket := "testgetbucketquota" // 1. Create bucket - if !setupBucket(validBucket, true, true, nil, nil, assert, 200) { + if !setupBucket(validBucket, true, map[string]interface{}{"enabled": true}, nil, nil, assert, 200) { return } @@ -915,7 +915,7 @@ func TestPutBucketQuota(t *testing.T) { validBucket := "testputbucketquota" // 1. Create bucket - if !setupBucket(validBucket, true, true, nil, nil, assert, 200) { + if !setupBucket(validBucket, true, map[string]interface{}{"enabled": true}, nil, nil, assert, 200) { return } @@ -974,7 +974,7 @@ func TestListBucketEvents(t *testing.T) { validBucket := "testlistbucketevents" // 1. Create bucket - if !setupBucket(validBucket, true, true, nil, nil, assert, 200) { + if !setupBucket(validBucket, true, map[string]interface{}{"enabled": true}, nil, nil, assert, 200) { return } @@ -1032,7 +1032,7 @@ func TestDeleteObjectsRetentionStatus(t *testing.T) { validPrefix := encodeBase64(fileName) // 1. Create bucket - if !setupBucket(bucketName, true, true, nil, nil, assert, 200) { + if !setupBucket(bucketName, true, map[string]interface{}{"enabled": true}, nil, nil, assert, 200) { return } @@ -1139,7 +1139,7 @@ func TestBucketSetPolicy(t *testing.T) { validBucketName := "testbucketsetpolicy" // 1. Create bucket - if !setupBucket(validBucketName, true, true, nil, nil, assert, 200) { + if !setupBucket(validBucketName, true, map[string]interface{}{"enabled": true}, nil, nil, assert, 200) { return } @@ -1200,7 +1200,7 @@ func TestRestoreObjectToASelectedVersion(t *testing.T) { validPrefix := encodeBase64(fileName) // 1. Create bucket - if !setupBucket(bucketName, true, true, nil, nil, assert, 200) { + if !setupBucket(bucketName, true, map[string]interface{}{"enabled": true}, nil, nil, assert, 200) { return } @@ -1288,7 +1288,7 @@ func TestPutBucketsTags(t *testing.T) { // 1. Create the bucket assert := assert.New(t) validBucketName := "testputbuckettags1" - if !setupBucket(validBucketName, false, false, nil, nil, assert, 200) { + if !setupBucket(validBucketName, false, nil, nil, nil, assert, 200) { return } @@ -1346,7 +1346,7 @@ func TestGetsTheMetadataOfAnObject(t *testing.T) { tags["tag"] = "testputobjecttagbucketonetagone" // 1. Create the bucket - if !setupBucket(bucketName, false, false, nil, nil, assert, 200) { + if !setupBucket(bucketName, false, nil, nil, nil, assert, 200) { return } @@ -1417,7 +1417,7 @@ func TestPutObjectsRetentionStatus(t *testing.T) { prefix := encodeBase64(fileName) // 1. Create bucket - if !setupBucket(bucketName, true, true, nil, nil, assert, 200) { + if !setupBucket(bucketName, true, map[string]interface{}{"enabled": true}, nil, nil, assert, 200) { return } @@ -1515,7 +1515,7 @@ func TestShareObjectOnURL(t *testing.T) { versionID := "null" // 1. Create the bucket - if !setupBucket(bucketName, false, false, nil, nil, assert, 200) { + if !setupBucket(bucketName, false, nil, nil, nil, assert, 200) { return } @@ -1589,7 +1589,7 @@ func TestListObjects(t *testing.T) { fileName := "testlistobjecttobucket1.txt" // 1. Create the bucket - if !setupBucket(bucketName, false, false, nil, nil, assert, 200) { + if !setupBucket(bucketName, false, nil, nil, nil, assert, 200) { return } @@ -1637,7 +1637,7 @@ func TestDeleteObject(t *testing.T) { numberOfFiles := 2 // 1. Create bucket - if !setupBucket(bucketName, true, true, nil, nil, assert, 200) { + if !setupBucket(bucketName, true, map[string]interface{}{"enabled": true}, nil, nil, assert, 200) { return } @@ -1703,7 +1703,7 @@ func TestUploadObjectToBucket(t *testing.T) { fileName := "sample.txt" // 1. Create the bucket - if !setupBucket(bucketName, false, false, nil, nil, assert, 200) { + if !setupBucket(bucketName, false, nil, nil, nil, assert, 200) { return } @@ -1738,7 +1738,7 @@ func TestDownloadObject(t *testing.T) { } // 1. Create the bucket - if !setupBucket(bucketName, true, true, nil, nil, assert, 200) { + if !setupBucket(bucketName, true, map[string]interface{}{"enabled": true}, nil, nil, assert, 200) { return } @@ -1800,7 +1800,7 @@ func TestDeleteMultipleObjects(t *testing.T) { fileName := "testdeletemultipleobjs" // 1. Create a bucket for this particular test - if !setupBucket(bucketName, false, false, nil, nil, assert, 200) { + if !setupBucket(bucketName, false, nil, nil, nil, assert, 200) { return } @@ -1877,7 +1877,7 @@ func TestPutObjectTag(t *testing.T) { versionID := "null" // 1. Create the bucket - if !setupBucket(bucketName, false, false, nil, nil, assert, 200) { + if !setupBucket(bucketName, false, nil, nil, nil, assert, 200) { return } @@ -1951,7 +1951,7 @@ func TestBucketRetention(t *testing.T) { retention["mode"] = "compliance" retention["unit"] = "years" retention["validity"] = 2 - if !setupBucket("setbucketretention1", true, true, nil, retention, assert, 200) { + if !setupBucket("setbucketretention1", true, map[string]interface{}{"enabled": true}, nil, retention, assert, 200) { return } @@ -2002,7 +2002,7 @@ func TestBucketInformationGenericErrorResponse(t *testing.T) { // 1. Create the bucket assert := assert.New(t) - if !setupBucket("bucketinformation2", false, false, nil, nil, assert, 200) { + if !setupBucket("bucketinformation2", false, nil, nil, nil, assert, 200) { return } @@ -2047,7 +2047,7 @@ func TestBucketInformationSuccessfulResponse(t *testing.T) { // 1. Create the bucket assert := assert.New(t) - if !setupBucket("bucketinformation1", false, false, nil, nil, assert, 200) { + if !setupBucket("bucketinformation1", false, nil, nil, nil, assert, 200) { return } @@ -2160,7 +2160,7 @@ func TestListBuckets(t *testing.T) { // 1. Create buckets numberOfBuckets := 3 for i := 1; i <= numberOfBuckets; i++ { - if !setupBucket("testlistbuckets"+strconv.Itoa(i), false, false, nil, nil, assert, 200) { + if !setupBucket("testlistbuckets"+strconv.Itoa(i), false, nil, nil, nil, assert, 200) { return } } @@ -2267,7 +2267,7 @@ func TestBucketVersioning(t *testing.T) { requestDataBody := bytes.NewReader(requestDataJSON) - if !setupBucket("test2", true, false, nil, nil, assert, 200) { + if !setupBucket("test2", true, nil, nil, nil, assert, 200) { return } @@ -2335,7 +2335,7 @@ func TestSetBucketTags(t *testing.T) { } // put bucket - if !setupBucket("test4", false, false, nil, nil, assert, 200) { + if !setupBucket("test4", false, nil, nil, nil, assert, 200) { return } @@ -2402,7 +2402,7 @@ func TestGetBucket(t *testing.T) { Timeout: 2 * time.Second, } - if !setupBucket("test3", false, false, nil, nil, assert, 200) { + if !setupBucket("test3", false, nil, nil, nil, assert, 200) { return } @@ -2455,7 +2455,7 @@ func TestAddBucket(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if !setupBucket(tt.args.bucketName, false, false, nil, nil, assert, tt.expectedStatus) { + if !setupBucket(tt.args.bucketName, false, nil, nil, nil, assert, tt.expectedStatus) { return } }) @@ -3000,7 +3000,7 @@ func TestReturnsTheStatusOfObjectLockingSupportOnTheBucket(t *testing.T) { ) } -func SetBucketVersioning(bucketName string, versioning bool, endpoint, useToken *string) (*http.Response, error) { +func SetBucketVersioning(bucketName string, versioning map[string]interface{}, endpoint, useToken *string) (*http.Response, error) { /* Helper function to set Bucket Versioning */ @@ -3037,7 +3037,7 @@ func TestSetBucketVersioning(t *testing.T) { assert := assert.New(t) bucket := "test-set-bucket-versioning" locking := false - versioning := true + versioning := map[string]interface{}{"enabled": true} // 1. Create bucket with versioning as true and locking as false if !setupBucket(bucket, locking, versioning, nil, nil, assert, 200) { @@ -3045,7 +3045,7 @@ func TestSetBucketVersioning(t *testing.T) { } // 2. Set versioning as False i.e Suspend versioning - response, err := SetBucketVersioning(bucket, false, nil, nil) + response, err := SetBucketVersioning(bucket, map[string]interface{}{"enabled": false}, nil, nil) assert.Nil(err) if err != nil { log.Println(err) @@ -3118,12 +3118,11 @@ func TestEnableBucketEncryption(t *testing.T) { assert := assert.New(t) bucketName := "test-enable-bucket-encryption" locking := false - versioning := false encType := "sse-s3" kmsKeyID := "" // 1. Add bucket - if !setupBucket(bucketName, locking, versioning, nil, nil, assert, 200) { + if !setupBucket(bucketName, locking, nil, nil, nil, assert, 200) { return } @@ -3381,7 +3380,6 @@ func TestBucketLifeCycle(t *testing.T) { assert := assert.New(t) bucketName := "test-bucket-life-cycle" locking := false - versioning := false ltype := "expiry" prefix := "" tags := "" @@ -3392,7 +3390,7 @@ func TestBucketLifeCycle(t *testing.T) { var noncurrentversionExpirationDays int64 // 1. Add bucket - if !setupBucket(bucketName, locking, versioning, nil, nil, assert, 200) { + if !setupBucket(bucketName, locking, nil, nil, nil, assert, 200) { return } @@ -3597,12 +3595,11 @@ func TestAccessRule(t *testing.T) { assert := assert.New(t) bucketName := "test-access-rule-bucket" locking := false - versioning := false prefix := "prefix" access := "readonly" // 1. Add bucket - if !setupBucket(bucketName, locking, versioning, nil, nil, assert, 200) { + if !setupBucket(bucketName, locking, nil, nil, nil, assert, 200) { return } @@ -3846,16 +3843,16 @@ func TestAddRemoteBucket(t *testing.T) { fmt.Println("targetBucket: ", targetBucket) // 1. Create bucket - if !setupBucket("source", true, true, nil, nil, assert, 200) { + if !setupBucket("source", true, map[string]interface{}{"enabled": true}, nil, nil, assert, 200) { return } // 1.1. Create target bucket targetEndpoint := "http://localhost:9092" targetToken := getTokenForEndpoint(targetEndpoint) - if !setupBucketForEndpoint(targetBucket, true, true, nil, nil, assert, 200, &targetEndpoint, &targetToken) { + if !setupBucketForEndpoint(targetBucket, true, map[string]interface{}{"enabled": true}, nil, nil, assert, 200, &targetEndpoint, &targetToken) { log.Println("bucket already exists") } - _, err := SetBucketVersioning(targetBucket, false, &targetURL, &targetToken) + _, err := SetBucketVersioning(targetBucket, map[string]interface{}{"enabled": false}, &targetURL, &targetToken) if err != nil { log.Println("bucket already has versioning") } @@ -3905,16 +3902,16 @@ func TestDeleteRemoteBucket(t *testing.T) { fmt.Println("targetBucket: ", targetBucket) // 1. Create bucket - if !setupBucket("deletesource", true, true, nil, nil, assert, 200) { + if !setupBucket("deletesource", true, map[string]interface{}{"enabled": true}, nil, nil, assert, 200) { return } // 1.1. Create target bucket targetEndpoint := "http://localhost:9092" targetToken := getTokenForEndpoint(targetEndpoint) - if !setupBucketForEndpoint(targetBucket, true, true, nil, nil, assert, 200, &targetEndpoint, &targetToken) { + if !setupBucketForEndpoint(targetBucket, true, map[string]interface{}{"enabled": true}, nil, nil, assert, 200, &targetEndpoint, &targetToken) { log.Println("bucket already exists") } - _, err := SetBucketVersioning(targetBucket, false, &targetURL, &targetToken) + _, err := SetBucketVersioning(targetBucket, map[string]interface{}{"enabled": false}, &targetURL, &targetToken) if err != nil { log.Println("bucket already has versioning") } diff --git a/models/make_bucket_request.go b/models/make_bucket_request.go index 92d9bfe1e..369652e01 100644 --- a/models/make_bucket_request.go +++ b/models/make_bucket_request.go @@ -50,7 +50,7 @@ type MakeBucketRequest struct { Retention *PutBucketRetentionRequest `json:"retention,omitempty"` // versioning - Versioning bool `json:"versioning,omitempty"` + Versioning *SetBucketVersioning `json:"versioning,omitempty"` } // Validate validates this make bucket request @@ -69,6 +69,10 @@ func (m *MakeBucketRequest) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateVersioning(formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { return errors.CompositeValidationError(res...) } @@ -122,6 +126,25 @@ func (m *MakeBucketRequest) validateRetention(formats strfmt.Registry) error { return nil } +func (m *MakeBucketRequest) validateVersioning(formats strfmt.Registry) error { + if swag.IsZero(m.Versioning) { // not required + return nil + } + + if m.Versioning != nil { + if err := m.Versioning.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("versioning") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("versioning") + } + return err + } + } + + return nil +} + // ContextValidate validate this make bucket request based on the context it is used func (m *MakeBucketRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error { var res []error @@ -134,6 +157,10 @@ func (m *MakeBucketRequest) ContextValidate(ctx context.Context, formats strfmt. res = append(res, err) } + if err := m.contextValidateVersioning(ctx, formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { return errors.CompositeValidationError(res...) } @@ -172,6 +199,22 @@ func (m *MakeBucketRequest) contextValidateRetention(ctx context.Context, format return nil } +func (m *MakeBucketRequest) contextValidateVersioning(ctx context.Context, formats strfmt.Registry) error { + + if m.Versioning != nil { + if err := m.Versioning.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("versioning") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("versioning") + } + return err + } + } + + return nil +} + // MarshalBinary interface implementation func (m *MakeBucketRequest) MarshalBinary() ([]byte, error) { if m == nil { diff --git a/models/set_bucket_versioning.go b/models/set_bucket_versioning.go index 17194d47f..7003f8ad9 100644 --- a/models/set_bucket_versioning.go +++ b/models/set_bucket_versioning.go @@ -34,8 +34,14 @@ import ( // swagger:model setBucketVersioning type SetBucketVersioning struct { - // versioning - Versioning bool `json:"versioning,omitempty"` + // enabled + Enabled bool `json:"enabled,omitempty"` + + // exclude folders + ExcludeFolders bool `json:"excludeFolders,omitempty"` + + // exclude prefixes + ExcludePrefixes []string `json:"excludePrefixes"` } // Validate validates this set bucket versioning diff --git a/portal-ui/src/api/consoleApi.ts b/portal-ui/src/api/consoleApi.ts index 1d9ed7abe..ec3c01f58 100644 --- a/portal-ui/src/api/consoleApi.ts +++ b/portal-ui/src/api/consoleApi.ts @@ -126,7 +126,7 @@ export interface BucketObject { export interface MakeBucketRequest { name: string; locking?: boolean; - versioning?: boolean; + versioning?: SetBucketVersioning; quota?: SetBucketQuota; retention?: PutBucketRetentionRequest; } @@ -802,7 +802,10 @@ export interface BucketVersioningResponse { } export interface SetBucketVersioning { - versioning?: boolean; + enabled?: boolean; + /** @maxLength 10 */ + excludePrefixes?: string[]; + excludeFolders?: boolean; } export interface BucketObLockingResponse { diff --git a/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketSummaryPanel.tsx b/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketSummaryPanel.tsx index 2342f3ccb..f18e05ca7 100644 --- a/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketSummaryPanel.tsx +++ b/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketSummaryPanel.tsx @@ -407,6 +407,7 @@ const BucketSummary = () => { modalOpen={enableVersioningOpen} selectedBucket={bucketName} versioningInfo={versioningInfo} + objectLockingEnabled={!!hasObjectLocking} /> )} @@ -581,6 +582,7 @@ const BucketSummary = () => { } onEdit={setBucketVersioning} isLoading={loadingVersioning} + disabled={hasObjectLocking} /> {versioningInfo?.status === "Enabled" ? ( diff --git a/portal-ui/src/screens/Console/Buckets/BucketDetails/EnableVersioningModal.tsx b/portal-ui/src/screens/Console/Buckets/BucketDetails/EnableVersioningModal.tsx index f7441ece1..9664e5aa9 100644 --- a/portal-ui/src/screens/Console/Buckets/BucketDetails/EnableVersioningModal.tsx +++ b/portal-ui/src/screens/Console/Buckets/BucketDetails/EnableVersioningModal.tsx @@ -15,32 +15,54 @@ // along with this program. If not, see . import React, { Fragment, useState } from "react"; -import { Box, ConfirmModalIcon } from "mds"; +import { Box, Button, FormLayout, ModalBox, Switch } from "mds"; import { BucketVersioningResponse } from "api/consoleApi"; import { api } from "api"; import { errorToHandler } from "api/errors"; import { setErrorSnackMessage } from "../../../../systemSlice"; import { useAppDispatch } from "../../../../store"; -import VersioningInfo from "../VersioningInfo"; -import ConfirmDialog from "../../Common/ModalWrapper/ConfirmDialog"; +import CSVMultiSelector from "../../Common/FormComponents/CSVMultiSelector/CSVMultiSelector"; +import { modalStyleUtils } from "../../Common/FormComponents/common/styleLibrary"; interface IVersioningEventProps { closeVersioningModalAndRefresh: (refresh: boolean) => void; modalOpen: boolean; selectedBucket: string; versioningInfo: BucketVersioningResponse | undefined; + objectLockingEnabled: boolean; } +const parseExcludedPrefixes = ( + bucketVersioning: BucketVersioningResponse | undefined, +) => { + const excludedPrefixes = bucketVersioning?.excludedPrefixes; + + if (excludedPrefixes) { + return excludedPrefixes.map((item) => item.prefix).join(","); + } + + return ""; +}; + const EnableVersioningModal = ({ closeVersioningModalAndRefresh, modalOpen, selectedBucket, versioningInfo = {}, + objectLockingEnabled, }: IVersioningEventProps) => { - const isVersioningEnabled = versioningInfo.status === "Enabled"; - const dispatch = useAppDispatch(); + const [versioningLoading, setVersioningLoading] = useState(false); + const [versionState, setVersionState] = useState( + versioningInfo?.status === "Enabled", + ); + const [excludeFolders, setExcludeFolders] = useState( + !!versioningInfo?.excludeFolders, + ); + const [excludedPrefixes, setExcludedPrefixes] = useState( + parseExcludedPrefixes(versioningInfo), + ); const enableVersioning = () => { if (versioningLoading) { @@ -50,7 +72,11 @@ const EnableVersioningModal = ({ api.buckets .setBucketVersioning(selectedBucket, { - versioning: !isVersioningEnabled, + enabled: versionState, + excludeFolders: versionState ? excludeFolders : false, + excludePrefixes: versionState + ? excludedPrefixes.split(",").filter((item) => item.trim() !== "") + : [], }) .then(() => { setVersioningLoading(false); @@ -62,44 +88,76 @@ const EnableVersioningModal = ({ }); }; + const resetForm = () => { + setExcludedPrefixes(""); + setExcludeFolders(false); + setVersionState(false); + }; + return ( - closeVersioningModalAndRefresh(false)} + open={modalOpen} title={`Versioning on Bucket`} - confirmText={isVersioningEnabled ? "Suspend" : "Enable"} - isOpen={modalOpen} - isLoading={versioningLoading} - titleIcon={} - onConfirm={enableVersioning} - confirmButtonProps={{ - variant: "callAction", - }} - onClose={() => { - closeVersioningModalAndRefresh(false); - }} - confirmationContent={ - - Are you sure you want to{" "} - {isVersioningEnabled ? "suspend" : "enable"}{" "} - versioning for this bucket? - {isVersioningEnabled && ( - -
-
- File versions won't be automatically deleted. -
- )} - - {isVersioningEnabled ? ( - - ) : null} - + > + + ) => { + setVersionState(e.target.checked); + }} + indicatorLabels={["Enabled", "Disabled"]} + /> + {versionState && !objectLockingEnabled && ( + + ) => { + setExcludeFolders(e.target.checked); + }} + indicatorLabels={["Enabled", "Disabled"]} + /> + { + let valCh = ""; + + if (Array.isArray(value)) { + valCh = value.join(","); + } else { + valCh = value; + } + setExcludedPrefixes(valCh); + }} + withBorder={true} + /> + + )} + +