-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add 'Create Bucket' form (via S3-compat API)
- Loading branch information
1 parent
eb8baf6
commit 9d783fb
Showing
11 changed files
with
333 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
173 changes: 173 additions & 0 deletions
173
packages/odf/components/s3-browser/create-bucket/CreateBucketForm.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
import * as React from 'react'; | ||
import { PutBucketTaggingCommandInput } from '@aws-sdk/client-s3'; | ||
import useS3BucketFormValidation from '@odf/core/components/s3-browser/create-bucket/useS3BucketFormValidation'; | ||
import { NoobaaS3Context } from '@odf/core/components/s3-browser/noobaa-context'; | ||
import { BUCKETS_BASE_ROUTE, LIST_BUCKET } from '@odf/core/constants'; | ||
import { | ||
ButtonBar, | ||
formSettings, | ||
TextInputWithFieldRequirements, | ||
useCustomTranslation, | ||
useYupValidationResolver, | ||
} from '@odf/shared'; | ||
import { LazyNameValueEditor } from '@odf/shared/utils/NameValueEditor'; | ||
import cn from 'classnames'; | ||
import * as _ from 'lodash-es'; | ||
import { useForm } from 'react-hook-form'; | ||
import { useNavigate } from 'react-router-dom-v5-compat'; | ||
import useSWR from 'swr'; | ||
import { | ||
ActionGroup, | ||
Alert, | ||
Button, | ||
Form, | ||
FormGroup, | ||
} from '@patternfly/react-core'; | ||
import { TagIcon } from '@patternfly/react-icons'; | ||
import './create-bucket-form.scss'; | ||
|
||
type FormData = { | ||
bucketName: string; | ||
}; | ||
|
||
const CreateBucketForm: React.FC<{}> = () => { | ||
const { t } = useCustomTranslation(); | ||
const navigate = useNavigate(); | ||
const [inProgress, setInProgress] = React.useState(false); | ||
const [errorMessage, setErrorMessage] = React.useState(''); | ||
const [tagsData, setTagsData] = React.useState<string[][]>([]); | ||
const { noobaaS3 } = React.useContext(NoobaaS3Context); | ||
const { data, error, isLoading } = useSWR(LIST_BUCKET, () => | ||
noobaaS3.listBuckets() | ||
); | ||
const buckets = data && !error && !isLoading ? data.Buckets : []; | ||
const { bucketFormSchema, fieldRequirements } = | ||
useS3BucketFormValidation(buckets); | ||
const resolver = useYupValidationResolver(bucketFormSchema); | ||
|
||
const { | ||
control, | ||
handleSubmit, | ||
formState: { isValid, isSubmitted }, | ||
} = useForm({ | ||
...formSettings, | ||
resolver, | ||
}); | ||
|
||
const save = async (formData: FormData) => { | ||
setInProgress(true); | ||
const { bucketName } = formData; | ||
try { | ||
await noobaaS3.createBucket({ Bucket: bucketName }); | ||
} catch ({ name, message }) { | ||
setErrorMessage(`Error while creating bucket: ${name}: ${message}`); | ||
} | ||
if (!errorMessage) { | ||
// Update bucket tags: any error here shouldn't prevent redirection | ||
// as the bucket has been created successfully. | ||
try { | ||
const tagSet: PutBucketTaggingCommandInput['Tagging']['TagSet'] = | ||
tagsData | ||
.filter((pair: string[]) => !_.isEmpty(pair[0])) | ||
.map((pair: string[]) => ({ Key: pair[0], Value: pair[1] })); | ||
if (!_.isEmpty(tagSet)) { | ||
await noobaaS3.updateBucketTags({ | ||
Bucket: bucketName, | ||
Tagging: { TagSet: tagSet }, | ||
}); | ||
} | ||
} catch ({ name, message }) { | ||
// @TODO: add a toast warning that is visible after redirection to bucket details. | ||
// eslint-disable-next-line no-console | ||
console.error(`Error while updating bucket tags: ${name}: ${message}`); | ||
} | ||
} | ||
|
||
if (errorMessage) { | ||
setInProgress(false); | ||
return; | ||
} | ||
navigate(`${BUCKETS_BASE_ROUTE}/${bucketName}`); | ||
}; | ||
|
||
return ( | ||
<Form onSubmit={handleSubmit(save)} className="pf-v5-u-w-50"> | ||
<TextInputWithFieldRequirements | ||
control={control} | ||
fieldRequirements={fieldRequirements} | ||
popoverProps={{ | ||
headerContent: t('Name requirements'), | ||
footerContent: `${t('Example')}: my-bucket`, | ||
}} | ||
formGroupProps={{ | ||
label: t('Bucket Name'), | ||
fieldId: 'bucket-name', | ||
isRequired: true, | ||
className: 'control-label', | ||
}} | ||
textInputProps={{ | ||
id: 'bucket-name', | ||
name: 'bucketName', | ||
className: 'pf-v5-c-form-control', | ||
type: 'text', | ||
placeholder: t('my-bucket'), | ||
'aria-describedby': 'bucket-name-help', | ||
'data-test': 'bucket-name', | ||
}} | ||
helperText={t('A unique name for your bucket.')} | ||
/> | ||
<FormGroup | ||
label={t('Tags')} | ||
labelInfo={t('Use different criteria for tagging your bucket.')} | ||
className={cn('odf-create-s3-bucket-form__tags', { | ||
'odf-create-s3-bucket-form__tags--empty': _.isEmpty(tagsData), | ||
})} | ||
> | ||
{_.isEmpty(tagsData) && ( | ||
<div className="pf-v5-u-disabled-color-100 pf-v5-u-mr-sm"> | ||
{t('No tags are attached to this bucket.')} | ||
</div> | ||
)} | ||
<LazyNameValueEditor | ||
className="pf-v5-u-font-weight-bold pf-v5-u-font-size-sm" | ||
addString={t('Add tag')} | ||
valueString={t('Value (optional)')} | ||
nameValuePairs={tagsData} | ||
updateParentData={({ nameValuePairs }) => { | ||
setTagsData(nameValuePairs); | ||
}} | ||
hideHeaderWhenNoItems={true} | ||
IconComponent={TagIcon} | ||
/> | ||
</FormGroup> | ||
{!isValid && isSubmitted && ( | ||
<Alert | ||
variant="danger" | ||
isInline | ||
title={t('Address form errors to proceed')} | ||
/> | ||
)} | ||
<ButtonBar errorMessage={errorMessage} inProgress={inProgress}> | ||
<ActionGroup className="pf-v5-c-form"> | ||
<Button | ||
id="create-s3-bucket-btn" | ||
type="submit" | ||
variant="primary" | ||
data-test="obc-create" | ||
> | ||
{t('Create')} | ||
</Button> | ||
<Button | ||
onClick={() => navigate(-1)} | ||
type="button" | ||
variant="secondary" | ||
> | ||
{t('Cancel')} | ||
</Button> | ||
</ActionGroup> | ||
</ButtonBar> | ||
</Form> | ||
); | ||
}; | ||
|
||
export default CreateBucketForm; |
17 changes: 17 additions & 0 deletions
17
packages/odf/components/s3-browser/create-bucket/create-bucket-form.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
.odf-create-s3-bucket-form__tags { | ||
// Display 'Tags' label description under the title. | ||
.pf-v5-c-form__group-label { | ||
display: block; | ||
} | ||
.pf-v5-c-form__group-label-info { | ||
margin-left: 0; | ||
} | ||
} | ||
|
||
.odf-create-s3-bucket-form__tags--empty { | ||
// Align NameValueEditor icon with 'no tags' text when only icon is shown. | ||
.pf-v5-c-form__group-control { | ||
display: flex; | ||
align-items: center; | ||
} | ||
} |
64 changes: 64 additions & 0 deletions
64
packages/odf/components/s3-browser/create-bucket/useS3BucketFormValidation.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import * as React from 'react'; | ||
import { ListBucketsCommandOutput } from '@aws-sdk/client-s3'; | ||
import { | ||
BUCKET_NAME_MAX_LENGTH, | ||
BUCKET_NAME_MIN_LENGTH, | ||
} from '@odf/core/constants'; | ||
import { fieldRequirementsTranslations } from '@odf/shared/constants'; | ||
import { useCustomTranslation } from '@odf/shared/useCustomTranslationHook'; | ||
import validationRegEx from '@odf/shared/utils/validation'; | ||
import * as _ from 'lodash-es'; | ||
import * as Yup from 'yup'; | ||
|
||
export type S3BucketFormSchema = Yup.ObjectSchema<{ | ||
bucketName: Yup.StringSchema; | ||
}>; | ||
|
||
export type S3BucketFormValidation = { | ||
bucketFormSchema: S3BucketFormSchema; | ||
fieldRequirements: string[]; | ||
}; | ||
|
||
const useS3BucketFormValidation = ( | ||
buckets: ListBucketsCommandOutput['Buckets'] | ||
): S3BucketFormValidation => { | ||
const { t } = useCustomTranslation(); | ||
|
||
return React.useMemo(() => { | ||
const existingNames = !_.isEmpty(buckets) | ||
? buckets.map((bucket) => bucket.Name) | ||
: []; | ||
|
||
const fieldRequirements = [ | ||
fieldRequirementsTranslations.maxChars(t, BUCKET_NAME_MAX_LENGTH), | ||
fieldRequirementsTranslations.minChars(t, BUCKET_NAME_MIN_LENGTH), | ||
fieldRequirementsTranslations.startAndEndName(t), | ||
fieldRequirementsTranslations.alphaNumericPeriodAdnHyphen(t), | ||
fieldRequirementsTranslations.cannotBeUsedBefore(t), | ||
]; | ||
|
||
const bucketFormSchema = Yup.object({ | ||
bucketName: Yup.string() | ||
.max(BUCKET_NAME_MAX_LENGTH, fieldRequirements[0]) | ||
.min(BUCKET_NAME_MIN_LENGTH, fieldRequirements[1]) | ||
.matches( | ||
validationRegEx.startAndEndsWithAlphanumerics, | ||
fieldRequirements[2] | ||
) | ||
.matches( | ||
validationRegEx.alphaNumericsPeriodsHyphensNonConsecutive, | ||
fieldRequirements[3] | ||
) | ||
.test( | ||
'unique-name', | ||
fieldRequirements[4], | ||
(value: string) => !existingNames.includes(value) | ||
) | ||
.transform((value: string) => (!!value ? value : '')), | ||
}); | ||
|
||
return { bucketFormSchema, fieldRequirements }; | ||
}, [buckets, t]); | ||
}; | ||
|
||
export default useS3BucketFormValidation; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.