This is an automated email from the ASF dual-hosted git repository. marat pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-karavan.git
commit e1cc5377fd22c3c1d065ed4a441ee53bcb32ca4a Author: Marat Gubaidullin <[email protected]> AuthorDate: Fri Apr 19 12:09:02 2024 -0400 Upload --- karavan-app/src/main/webui/src/api/KaravanApi.tsx | 29 ++- .../src/main/webui/src/api/ProjectService.ts | 5 - .../src/main/webui/src/project/files/FilesTab.tsx | 2 +- .../webui/src/project/files/UploadFileModal.tsx | 198 ++++++++++----------- 4 files changed, 114 insertions(+), 120 deletions(-) diff --git a/karavan-app/src/main/webui/src/api/KaravanApi.tsx b/karavan-app/src/main/webui/src/api/KaravanApi.tsx index 39f53f4a..7e97b1f6 100644 --- a/karavan-app/src/main/webui/src/api/KaravanApi.tsx +++ b/karavan-app/src/main/webui/src/api/KaravanApi.tsx @@ -63,7 +63,7 @@ export class KaravanApi { static setAuthType(authType: string) { console.log("SetAuthType", authType) KaravanApi.authType = authType; - switch (authType){ + switch (authType) { case "public": { KaravanApi.setPublicAuthentication(); break; @@ -78,6 +78,7 @@ export class KaravanApi { } } } + static setPublicAuthentication() { } @@ -236,7 +237,7 @@ export class KaravanApi { .then(res => { if (res.status === 200) { after(res.data); - } else if (res.status === 204){ + } else if (res.status === 204) { after(undefined); } }).catch(err => { @@ -349,7 +350,6 @@ export class KaravanApi { } static async saveProjectFile(file: ProjectFile, after: (result: boolean, file: ProjectFile | any) => void) { - console.log(file) try { instance.post('/ui/file', file) .then(res => { @@ -400,7 +400,7 @@ export class KaravanApi { }); } - static async getTemplatesFiles( after: (files: []) => void) { + static async getTemplatesFiles(after: (files: []) => void) { instance.get('/ui/file/templates') .then(res => { if (res.status === 200) { @@ -411,7 +411,7 @@ export class KaravanApi { }); } - static async getBeanTemplatesFiles( after: (files: ProjectFile []) => void) { + static async getBeanTemplatesFiles(after: (files: ProjectFile []) => void) { instance.get('/ui/file/templates/beans') .then(res => { if (res.status === 200) { @@ -468,7 +468,7 @@ export class KaravanApi { } static async deleteDevModeContainer(name: string, deletePVC: boolean, after: (res: AxiosResponse<any>) => void) { - instance.delete('/ui/devmode/' + name + "/" + deletePVC) + instance.delete('/ui/devmode/' + name + "/" + deletePVC) .then(res => { after(res); }).catch(err => { @@ -718,8 +718,19 @@ export class KaravanApi { }); } - static async postOpenApi(file: ProjectFile, generateRest: boolean, generateRoutes: boolean, integrationName: string) { - const uri = `/ui/file/openapi/${generateRest}/${generateRoutes}/${integrationName}`; - return instance.post(encodeURI(uri), file); + static async postOpenApi(file: ProjectFile, generateRest: boolean, generateRoutes: boolean, integrationName: string, after: (result: boolean, file: ProjectFile | any) => void) { + try { + const uri = `/ui/file/openapi/${generateRest}/${generateRoutes}/${integrationName}`; + instance.post(encodeURI(uri), file) + .then(res => { + if (res.status === 200) { + after(true, res.data); + } + }).catch(err => { + after(false, err); + }); + } catch (error: any) { + after(false, error); + } } } diff --git a/karavan-app/src/main/webui/src/api/ProjectService.ts b/karavan-app/src/main/webui/src/api/ProjectService.ts index 952b95fd..f8f0207f 100644 --- a/karavan-app/src/main/webui/src/api/ProjectService.ts +++ b/karavan-app/src/main/webui/src/api/ProjectService.ts @@ -235,11 +235,6 @@ export class ProjectService { }); } - public static async createOpenApiFile(file: ProjectFile, generateRest: boolean, generateRoutes: boolean, integrationName: string) { - const result = await KaravanApi.postOpenApi(file, generateRest, generateRoutes, integrationName); - return result.data; - } - public static deleteFile(file: ProjectFile) { KaravanApi.deleteProjectFile(file, res => { if (res.status === 204) { diff --git a/karavan-app/src/main/webui/src/project/files/FilesTab.tsx b/karavan-app/src/main/webui/src/project/files/FilesTab.tsx index a686bab0..681b67bc 100644 --- a/karavan-app/src/main/webui/src/project/files/FilesTab.tsx +++ b/karavan-app/src/main/webui/src/project/files/FilesTab.tsx @@ -162,7 +162,7 @@ export function FilesTab () { </Tbody> </Table> </div> - <UploadFileModal projectId={project.projectId}/> + <UploadFileModal/> <CreateFileModal/> <DeleteFileModal /> </PageSection> diff --git a/karavan-app/src/main/webui/src/project/files/UploadFileModal.tsx b/karavan-app/src/main/webui/src/project/files/UploadFileModal.tsx index e3c11a5c..4c4f3e15 100644 --- a/karavan-app/src/main/webui/src/project/files/UploadFileModal.tsx +++ b/karavan-app/src/main/webui/src/project/files/UploadFileModal.tsx @@ -16,121 +16,111 @@ */ import React, {useEffect, useState} from 'react'; import { - TextInput, - Button, Modal, FormGroup, ModalVariant, Switch, Form, FileUpload, Radio, Alert, Divider, Grid, Text + Button, + Modal, + FormGroup, + ModalVariant, + Switch, + Form, + FileUpload, + Radio, + FormAlert, + Alert, + TextInputGroupMain, Text, TextVariants, TextInputGroup } from '@patternfly/react-core'; import '../../designer/karavan.css'; -import {ProjectFile} from "../../api/ProjectModels"; -import {useFileStore} from "../../api/ProjectStore"; +import { ProjectFile} from "../../api/ProjectModels"; +import {useFileStore, useProjectStore} from "../../api/ProjectStore"; import {Accept, DropEvent, FileRejection} from "react-dropzone"; import {EventBus} from "../../designer/utils/EventBus"; import {shallow} from "zustand/shallow"; import {ProjectService} from "../../api/ProjectService"; -import {useForm} from "react-hook-form"; -import {useResponseErrorHandler} from "../../shared/error/UseResponseErrorHandler"; -import {AxiosError} from "axios"; +import {SubmitHandler, useForm} from "react-hook-form"; +import {KaravanApi} from "../../api/KaravanApi"; +import {AxiosResponse} from "axios"; +import {useFormUtil} from "../../util/useFormUtil"; -interface Props { - projectId: string, -} - -export function UploadFileModal(props: Props) { - - const defaultFormValues = { - upload: "" - }; - - const responseToFormErrorFields = new Map<string, string>([ - ["name", "upload"] - ]); - - const { - register, - setError, - handleSubmit, - formState: { errors }, - reset, - clearErrors - } = useForm({ - mode: "onChange", - defaultValues: defaultFormValues - }); +export function UploadFileModal() { + const integrationExtension = '.camel.yaml' + const [project] = useProjectStore((s) => [s.project], shallow); const [operation, setFile] = useFileStore((s) => [s.operation, s.setFile], shallow); const [type, setType] = useState<'integration' | 'openapi' | 'other'>('integration'); - const [filename, setFilename] = useState(''); const [integrationName, setIntegrationName] = useState(''); - const [data, setData] = useState(''); const [isLoading, setIsLoading] = useState(false); const [isRejected, setIsRejected] = useState(false); const [generateRest, setGenerateRest] = useState(true); const [generateRoutes, setGenerateRoutes] = useState(true); - const [globalErrors, registerResponseErrors, resetGlobalErrors] = useResponseErrorHandler( - responseToFormErrorFields, - setError - ); + const [isReset, setReset] = React.useState(false); + const [backendError, setBackendError] = React.useState<string>(); + const formContext = useForm<ProjectFile>({mode: "all"}); + const { + formState: {errors}, + handleSubmit, + reset, + trigger, + setValue, + getValues + } = formContext; useEffect(() => { - setFilename('') - setData('') + reset(new ProjectFile('', project.projectId, '', 0)); + setBackendError(undefined); + setReset(true); setType('integration') - }, []); - - function resetForm() { - resetGlobalErrors(); - } - - function closeModal () { - setFile("none"); - setFilename(''); - setData(''); - resetForm(); - } + }, [reset, operation]); - function handleFormSubmit() { - const file = new ProjectFile(filename, props.projectId, data, Date.now()); + React.useEffect(() => { + isReset && trigger(); + }, [trigger, isReset]); + const onSubmit: SubmitHandler<ProjectFile> = (data) => { + data.projectId = project.projectId; if (type === "openapi") { - return ProjectService.createOpenApiFile(file, generateRest, generateRoutes, integrationName) - .then(() => handleOnFormSubmitSuccess()) - .catch((error) => handleOnFormSubmitFailure(error)); + KaravanApi.postOpenApi(data, generateRest, generateRoutes, integrationName + integrationExtension, after) } else { - // return ProjectService.createFile(file) - // .then(() => handleOnFormSubmitSuccess()) - // .catch((error) => handleOnFormSubmitFailure(error)); + KaravanApi.saveProjectFile(data, after) } } - function handleOnFormSubmitSuccess () { - const message = "File successfully uploaded."; - EventBus.sendAlert( "Success", message, "success"); + function after (result: boolean, file: AxiosResponse<ProjectFile> | any) { + if (result) { + onSuccess(file); + } else { + setBackendError(file?.response?.data); + } + } - closeModal(); - ProjectService.refreshProjectData(props.projectId); + function onSuccess (file: ProjectFile) { + EventBus.sendAlert( "Success", "File successfully created", "success"); + ProjectService.refreshProjectData(project.projectId); + setFile('select', file); } - function handleOnFormSubmitFailure(error: AxiosError) { - registerResponseErrors(error); + function closeModal() { + setFile("none"); } - const handleFileInputChange = (file: File) => setFilename(file.name); + const handleFileInputChange = (file: File) => setValue('name',file.name); const handleFileReadStarted = (fileHandle: File) => setIsLoading(true); const handleFileReadFinished = (fileHandle: File) => setIsLoading(false); - const handleTextOrDataChange = (data: string) => setData(data); + const handleTextOrDataChange = (data: string) => setValue('code', data); const handleFileRejected = (fileRejections: FileRejection[], event: DropEvent) => setIsRejected(true); const handleClear = (event: React.MouseEvent<HTMLButtonElement>) => { - setFilename(''); - setData(''); + setValue('name', ''); + setValue('code', ''); setIsRejected(false); - resetGlobalErrors(); - reset(defaultFormValues); + setBackendError(undefined); + reset(new ProjectFile('', project.projectId, '', 0)); }; - - const fileNotUploaded = (filename === '' || data === ''); + const fileNotUploaded = (getValues('name') === '' || getValues('code') === ''); const accept : Accept = type === 'integration' ? {'application/yaml': ['.yaml', '.yml']} - : {'application/yaml': ['.yaml', '.yml'], 'application/json': ['.json'], 'plain/text': ['.sql'], 'text/x-java': ['.java']}; + : ( type === 'openapi' + ? {'application/yaml': ['.yaml', '.yml'], 'application/json': ['.json']} + : {} + ); return ( <Modal title="Upload" @@ -138,7 +128,12 @@ export function UploadFileModal(props: Props) { isOpen={operation === 'upload'} onClose={closeModal} actions={[ - <Button key="confirm" variant="primary" onClick={handleSubmit(handleFormSubmit)} isDisabled={fileNotUploaded}>Save</Button>, + <Button key="confirm" variant="primary" + onClick={handleSubmit(onSubmit)} + isDisabled={Object.getOwnPropertyNames(errors).length > 0 || fileNotUploaded || (type === "openapi" && integrationName?.length < 3)} + > + Save + </Button>, <Button key="cancel" variant="secondary" onClick={closeModal}>Cancel</Button> ]} > @@ -146,22 +141,16 @@ export function UploadFileModal(props: Props) { <FormGroup fieldId="type"> <Radio value="Integration" label="Integration yaml" name="Integration" id="Integration" isChecked={type === 'integration'} onChange={(event, _) => { - resetGlobalErrors(); - clearErrors("upload"); setType(_ ? 'integration': 'openapi' ); }} />{' '} <Radio value="OpenAPI" label="OpenAPI json/yaml" name="OpenAPI" id="OpenAPI" isChecked={type === 'openapi'} onChange={(event, _) => { - resetGlobalErrors(); - clearErrors("upload"); setType( _ ? 'openapi' : 'integration' ); }} /> <Radio value="Other" label="Other" name="Other" id="Other" isChecked={type === 'other'} onChange={(event, _) => { - resetGlobalErrors(); - clearErrors("upload"); setType( _ ? 'other' : 'integration' ); }} /> @@ -169,8 +158,8 @@ export function UploadFileModal(props: Props) { <FormGroup fieldId="upload"> <FileUpload id="file-upload" - value={data} - filename={filename} + value={getValues('code')} + filename={getValues('name')} type="text" hideDefaultPreview browseButtonText="Upload" @@ -182,17 +171,14 @@ export function UploadFileModal(props: Props) { handleTextOrDataChange(data); }} onTextChange={(_event, text) => { - handleTextOrDataChange(data); + handleTextOrDataChange(text); }} onReadStarted={(_event, fileHandle: File) => handleFileReadStarted(fileHandle)} onReadFinished={(_event, fileHandle: File) => handleFileReadFinished(fileHandle)} allowEditingUploadedText={false} onClearClick={handleClear} dropzoneProps={{accept: accept, onDropRejected: handleFileRejected}} - validated={!!errors.upload && isRejected ? 'error' : 'default'} - {...register('upload')} /> - {!!errors.upload && <Text style={{ color: 'red', fontStyle: 'italic'}}>{errors?.upload?.message}</Text>} </FormGroup> {type === 'openapi' && <FormGroup fieldId="generateRest"> <Switch @@ -212,22 +198,24 @@ export function UploadFileModal(props: Props) { onChange={(_, checked) => setGenerateRoutes(checked)} /> </FormGroup>} - {type === 'openapi' && generateRest && <FormGroup fieldId="integrationName" label="Integration name"> - <TextInput autoComplete="off" - id="integrationName" - type="text" - placeholder="Integration file name with yaml extension" - required - onChange={(_, value) => setIntegrationName(value)} - /> - </FormGroup>} - <Grid> - {globalErrors && - globalErrors.map((error) => ( - <Alert title={error} key={error} variant="danger"></Alert> - ))} - <Divider role="presentation" /> - </Grid> + {type === 'openapi' && generateRest && + <FormGroup fieldId="integrationName" label="Integration name"> + <TextInputGroup className="text-field-with-suffix"> + <TextInputGroupMain type="text" id={"integrationName"} autoComplete="off" + value={integrationName} + required + onChange={(_, value) => setIntegrationName(value)} + > + </TextInputGroupMain> + <Text className='text-field-suffix' component={TextVariants.p}>{integrationExtension}</Text> + </TextInputGroup> + </FormGroup> + } + {backendError && + <FormAlert> + <Alert variant="danger" title={backendError} aria-live="polite" isInline /> + </FormAlert> + } </Form> </Modal> )
