s3-artifact-storage-ui/src/App/App.tsx (200 lines of code) (raw):

import { React, utils } from '@jetbrains/teamcity-api'; import { FormProvider, useFormContext } from 'react-hook-form'; import Button from '@jetbrains/ring-ui/components/button/button'; import { errorMessage, FieldColumn, FieldRow, Option, ReadOnlyContextProvider, useErrorService, useJspContainer, useReadOnlyContext, } from '@jetbrains-internal/tcci-react-ui-components'; import { ControlsHeight, ControlsHeightContext, } from '@jetbrains/ring-ui/components/global/controls-height'; import { ResponseErrors } from '@jetbrains-internal/tcci-react-ui-components/dist/types'; import { BaseSyntheticEvent, useCallback } from 'react'; import { displayErrorsFromResponseIfAny } from '../Utilities/responseParser'; import { serializeParameters } from '../Utilities/parametersUtils'; import { post } from '../Utilities/fetchHelper'; import useS3Form from '../hooks/useS3Form'; import { ConfigWrapper, IFormInput } from '../types'; import useStorageOptions from '../hooks/useStorageOptions'; import { AppContextProvider, useAppContext } from '../contexts/AppContext'; import { BucketsContextProvider } from '../contexts/BucketsContext'; import useBucketOptions from '../hooks/useBucketOptions'; import { errorIdToFieldName, FormFields } from './appConstants'; import styles from './styles.css'; import { AWS_S3, S3_COMPATIBLE } from './Storage/components/StorageType'; import StorageSection from './Storage/StorageSection'; import S3Section from './S3Compatible/S3Section'; import AwsS3 from './S3/AwsS3'; import MultipartUploadSection from './MultipartUpload/MultipartUploadSection'; import ProtocolSettings from './ProtocolSettings/ProtocolSettings'; import StorageTypeChangedWarningDialog from './components/StorageTypeChangedWarningDialog'; const formId = 'S3ProfileForm'; function MainFormComponent(props: { onReset: (option: Option | null) => void; onClose: () => void; }) { const config = useAppContext(); const { handleSubmit, watch, setError, clearErrors } = useFormContext<IFormInput>(); const { showErrorsOnForm, showErrorAlert } = useErrorService({ setError, errorKeyToFieldNameConvertor: errorIdToFieldName, }); const { reload } = useBucketOptions(); const [isSaving, setIsSaving] = React.useState(false); const onSubmit = useCallback( async (data: IFormInput, event?: BaseSyntheticEvent) => { if (event?.target.id !== formId) { return; } setIsSaving(true); clearErrors(); try { // validate bucket try { const bucketsFromCredentials = await reload(); const currentlySelectedBucket = data[FormFields.S3_BUCKET_NAME]; const bucketFound = bucketsFromCredentials.some((bucket) => { if (typeof currentlySelectedBucket === 'string') { return bucket.key === currentlySelectedBucket; } else { return bucket.key === currentlySelectedBucket?.key; } }); if (!bucketFound) { setError(FormFields.S3_BUCKET_NAME, { type: 'custom', message: 'Bucket not found. Check your S3 credentials.', }); return; } } catch (e) { showErrorAlert(errorMessage(e)); return; } const payload = serializeParameters(data, config); const parameters = { projectId: config.projectId, newStorage: config.isNewStorage.toString(), [FormFields.STORAGE_TYPE]: data[FormFields.STORAGE_TYPE]!.key, [FormFields.STORAGE_ID]: data[FormFields.STORAGE_ID]!, }; const queryComponents = new URLSearchParams(parameters).toString(); const resp = await post( `/admin/storageParams.html?${queryComponents}`, payload ); const response = window.$j(resp); const errors: ResponseErrors | null = displayErrorsFromResponseIfAny(response); if (errors) { showErrorsOnForm(errors); } else { props.onClose(); } } finally { setIsSaving(false); } }, [ clearErrors, config, props, reload, setError, showErrorAlert, showErrorsOnForm, ] ); const currentType = watch(FormFields.STORAGE_TYPE); const isS3Compatible = currentType?.key === S3_COMPATIBLE; const isAwsS3 = currentType?.key === AWS_S3; const isReadOnly = useReadOnlyContext(); return ( <form className="ring-form" onSubmit={handleSubmit(onSubmit)} autoComplete="off" id={formId} > <StorageSection onReset={props.onReset} /> {isS3Compatible && <S3Section />} {isAwsS3 && <AwsS3 />} <MultipartUploadSection /> <ProtocolSettings /> <div className={styles.formControlButtons}> <FieldRow> <FieldColumn> <Button disabled={isReadOnly} loader={isSaving} type="submit" primary > {'Save'} </Button> </FieldColumn> <FieldColumn> <Button onClick={props.onClose}>{'Cancel'}</Button> </FieldColumn> </FieldRow> </div> </form> ); } function Main() { const config = useAppContext(); // console.log(config); const resetUI = useJspContainer( '#storageParamsInner table.runnerFormTable, #saveButtons' ); const formMethods = useS3Form(); const storageOptions = useStorageOptions(); const doReset = useCallback( (option: Option | null) => { resetUI(); let ind = 0; if (option) { ind = storageOptions.findIndex((e) => e.key === option.key); ind = ind < 0 ? 0 : ind; } // @ts-ignore $('editStorageForm').storageType.selectedIndex = ind; // @ts-ignore $('editStorageForm')['-ufd-teamcity-ui-storageType'].value = option?.label || storageOptions[0].label; // @ts-ignore $('storageParams').updateContainer(); }, [resetUI, storageOptions] ); const close = useCallback(() => { document.location.href = `${utils.resolveRelativeURL( '/admin/editProject.html' )}?projectId=${config.projectId}&tab=artifactsStorage`; }, [config.projectId]); return ( <FormProvider {...formMethods}> <ControlsHeightContext.Provider value={ControlsHeight.S}> <BucketsContextProvider> <MainFormComponent onReset={doReset} onClose={close} /> <StorageTypeChangedWarningDialog /> {/*<DevTool control={formMethods.control} />*/} </BucketsContextProvider> </ControlsHeightContext.Provider> </FormProvider> ); } function App({ config }: ConfigWrapper) { useJspContainer('#storageParamsInner table.runnerFormTable, #saveButtons'); return ( <ReadOnlyContextProvider value={config.readOnly}> <AppContextProvider value={config}> <Main /> </AppContextProvider> </ReadOnlyContextProvider> ); } export default React.memo(App);