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>
     )

Reply via email to