You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ma...@apache.org on 2024/01/29 14:42:29 UTC

(camel-karavan) branch main updated: Import file validation (#1085)

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


The following commit(s) were added to refs/heads/main by this push:
     new da5a7ddb Import file validation (#1085)
da5a7ddb is described below

commit da5a7ddb8a5bbc623e8c3a7fd9117d8abab013a0
Author: Mario Volf <mv...@users.noreply.github.com>
AuthorDate: Mon Jan 29 15:42:24 2024 +0100

    Import file validation (#1085)
    
    * mvolf - Validate file existence on file import. Use the same validation on file create. Fix for case when email doesn't exist. Fix for quarkus quinoa properties after upgrade.
    
    * mvolf - Revert changes to TopologyToolbar made by build
---
 .../org/apache/camel/karavan/git/GitService.java   |   7 +-
 .../karavan/infinispan/InfinispanService.java      |   7 ++
 .../project/ProjectFileCreateValidator.java        |  35 ++++++
 .../src/main/resources/application.properties      |   3 +-
 .../src/main/webui/src/api/KaravanApi.tsx          |  15 ++-
 .../src/main/webui/src/api/ProjectService.ts       |  17 ++-
 .../webui/src/project/files/CreateFileModal.tsx    | 131 +++++++++++++++------
 .../webui/src/project/files/UploadFileModal.tsx    | 130 ++++++++++++++------
 8 files changed, 253 insertions(+), 92 deletions(-)

diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/git/GitService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/git/GitService.java
index da93a157..25e37750 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/git/GitService.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/git/GitService.java
@@ -309,11 +309,14 @@ public class GitService {
     }
 
     private PersonIdent getPersonIdent() {
+        String defaultEmailAddress = "karavan@test.org";
+
         if (securityIdentity != null && securityIdentity.getAttributes().get("userinfo") != null) {
             UserInfo userInfo = (UserInfo) securityIdentity.getAttributes().get("userinfo");
-            return new PersonIdent(securityIdentity.getPrincipal().getName(), userInfo.getEmail());
+            String email = Objects.isNull(userInfo.getEmail()) || userInfo.getEmail().isBlank() ? defaultEmailAddress : userInfo.getEmail();
+            return new PersonIdent(securityIdentity.getPrincipal().getName(), email);
         }
-        return new PersonIdent("karavan", "karavan@test.org");
+        return new PersonIdent("karavan", defaultEmailAddress);
     }
 
     public Git init(String dir, String uri, String branch) throws GitAPIException, IOException, URISyntaxException {
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java
index fca6c20e..594dde7b 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java
@@ -17,8 +17,10 @@
 package org.apache.camel.karavan.infinispan;
 
 import jakarta.enterprise.inject.Default;
+import jakarta.inject.Inject;
 import jakarta.inject.Singleton;
 import org.apache.camel.karavan.infinispan.model.*;
+import org.apache.camel.karavan.validation.project.ProjectFileCreateValidator;
 import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.eclipse.microprofile.faulttolerance.Retry;
 import org.eclipse.microprofile.health.HealthCheck;
@@ -63,6 +65,9 @@ public class InfinispanService implements HealthCheck {
     @ConfigProperty(name = "karavan.infinispan.password")
     String infinispanPassword;
 
+    @Inject
+    ProjectFileCreateValidator projectFileCreateValidator;
+
     private RemoteCache<GroupedKey, Project> projects;
     private RemoteCache<GroupedKey, ProjectFile> files;
     private RemoteCache<GroupedKey, DeploymentStatus> deploymentStatuses;
@@ -174,6 +179,8 @@ public class InfinispanService implements HealthCheck {
     }
 
     public void saveProjectFile(ProjectFile file) {
+        projectFileCreateValidator.validate(file).failOnError();
+
         files.put(GroupedKey.create(file.getProjectId(), DEFAULT_ENVIRONMENT, file.getName()), file);
     }
 
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/validation/project/ProjectFileCreateValidator.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/validation/project/ProjectFileCreateValidator.java
new file mode 100644
index 00000000..686c3db1
--- /dev/null
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/validation/project/ProjectFileCreateValidator.java
@@ -0,0 +1,35 @@
+package org.apache.camel.karavan.validation.project;
+
+import java.util.List;
+
+import org.apache.camel.karavan.infinispan.InfinispanService;
+import org.apache.camel.karavan.infinispan.model.ProjectFile;
+import org.apache.camel.karavan.shared.validation.SimpleValidator;
+import org.apache.camel.karavan.shared.validation.ValidationError;
+import org.apache.camel.karavan.shared.validation.Validator;
+
+import jakarta.enterprise.context.ApplicationScoped;
+
+@ApplicationScoped
+public class ProjectFileCreateValidator extends Validator<ProjectFile> {
+
+    private final SimpleValidator simpleValidator;
+    private final InfinispanService infinispanService;
+
+    public ProjectFileCreateValidator(SimpleValidator simpleValidator, InfinispanService infinispanService) {
+        this.simpleValidator = simpleValidator;
+        this.infinispanService = infinispanService;
+    }
+
+
+    @Override
+    protected void validationRules(ProjectFile value, List<ValidationError> errors) {
+        simpleValidator.validate(value, errors);
+
+        boolean projectFileExists = infinispanService.getProjectFile(value.getProjectId(), value.getName()) != null;
+
+        if(projectFileExists) {
+            errors.add(new ValidationError("name", "File with given name already exists"));
+        }
+    }
+}
diff --git a/karavan-web/karavan-app/src/main/resources/application.properties b/karavan-web/karavan-app/src/main/resources/application.properties
index 553da3ce..2efec5fd 100644
--- a/karavan-web/karavan-app/src/main/resources/application.properties
+++ b/karavan-web/karavan-app/src/main/resources/application.properties
@@ -145,8 +145,9 @@ quarkus.kubernetes-client.devservices.enabled=false
 
 quarkus.swagger-ui.always-include=true
 
-quarkus.quinoa.frozen-lockfile=false
+quarkus.quinoa.ci=false
 quarkus.quinoa.package-manager-install=false
 quarkus.quinoa.package-manager-install.node-version=18.12.1
 quarkus.quinoa.dev-server.port=3003
 quarkus.quinoa.dev-server.check-timeout=60000
+quarkus.quinoa.ignored-path-prefixes=/api,/public
diff --git a/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx b/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx
index 95ad0698..840e0234 100644
--- a/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx
@@ -267,12 +267,16 @@ export class KaravanApi {
         });
     }
 
+    static async saveProjectFile(file: ProjectFile) {
+        return instance.post('/api/file', file);
+    }
+
     static async postProjectFile(file: ProjectFile, after: (res: AxiosResponse<any>) => void) {
         instance.post('/api/file', file)
             .then(res => {
                 after(res);
             }).catch(err => {
-            after(err);
+                after(err);
         });
     }
 
@@ -621,14 +625,9 @@ export class KaravanApi {
         });
     }
 
-    static async postOpenApi(file: ProjectFile, generateRest: boolean, generateRoutes: boolean, integrationName: string, after: (res: AxiosResponse<any>) => void) {
+    static async postOpenApi(file: ProjectFile, generateRest: boolean, generateRoutes: boolean, integrationName: string) {
         const uri = `/api/file/openapi/${generateRest}/${generateRoutes}/${integrationName}`;
-        instance.post(encodeURI(uri), file)
-            .then(res => {
-                after(res);
-            }).catch(err => {
-            after(err);
-        });
+        return instance.post(encodeURI(uri), file);
     }
 
     static async fetchData(type: 'container' | 'build' | 'none', podName: string, controller: AbortController) {
diff --git a/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts b/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts
index b3d925d8..d0d03034 100644
--- a/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts
+++ b/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts
@@ -245,15 +245,14 @@ export class ProjectService {
         return result.data;
     }
 
-    public static createFile(file: ProjectFile) {
-        KaravanApi.postProjectFile(file, res => {
-            if (res.status === 200) {
-                // console.log(res) //TODO show notification
-                ProjectService.refreshProjectData(file.projectId);
-            } else {
-                // console.log(res) //TODO show notification
-            }
-        })
+    public static async createFile(file: ProjectFile) {
+        const result = await KaravanApi.saveProjectFile(file);
+        return result.data;
+    }
+
+    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) {
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/files/CreateFileModal.tsx b/karavan-web/karavan-app/src/main/webui/src/project/files/CreateFileModal.tsx
index c5c99037..8c08be7f 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/files/CreateFileModal.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/files/CreateFileModal.tsx
@@ -22,12 +22,12 @@ import {
     FormGroup,
     ModalVariant,
     Form,
-    ToggleGroupItem, ToggleGroup, FormHelperText, HelperText, HelperTextItem, TextInput,
+    ToggleGroupItem, ToggleGroup, TextInput, Alert, Divider, Grid, Text,
 } from '@patternfly/react-core';
 import '../../designer/karavan.css';
 import {Integration, KameletTypes, MetadataLabels} from "karavan-core/lib/model/IntegrationDefinition";
 import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml";
-import {useFilesStore, useFileStore, useProjectStore} from "../../api/ProjectStore";
+import {useFileStore, useProjectStore} from "../../api/ProjectStore";
 import {ProjectFile, ProjectFileTypes} from "../../api/ProjectModels";
 import {CamelUi} from "../../designer/utils/CamelUi";
 import {ProjectService} from "../../api/ProjectService";
@@ -35,6 +35,12 @@ import {shallow} from "zustand/shallow";
 import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
 import {KameletApi} from "karavan-core/lib/api/KameletApi";
 import {TypeaheadSelect, Value} from "../../designer/ui/TypeaheadSelect";
+import * as yup from "yup";
+import {useForm} from "react-hook-form";
+import {yupResolver} from "@hookform/resolvers/yup";
+import {useResponseErrorHandler} from "../../shared/error/UseResponseErrorHandler";
+import {EventBus} from "../../designer/utils/EventBus";
+import {AxiosError} from "axios";
 
 interface Props {
     types: string[],
@@ -43,14 +49,44 @@ interface Props {
 
 export function CreateFileModal(props: Props) {
 
+    const formValidationSchema = yup.object().shape({
+        name: yup
+            .string()
+            .required("File name is required"),
+    });
+
+    const defaultFormValues = {
+        name: ""
+    };
+
+    const responseToFormErrorFields = new Map<string, string>([
+        ["name", "name"]
+    ]);
+
+    const {
+        register,
+        setError,
+        handleSubmit,
+        formState: { errors },
+        reset,
+        clearErrors
+    } = useForm({
+        resolver: yupResolver(formValidationSchema),
+        mode: "onChange",
+        defaultValues: defaultFormValues
+    });
+
+
     const [project] = useProjectStore((s) => [s.project], shallow);
-    const [files] = useFilesStore((s) => [s.files], shallow);
     const [operation, setFile, designerTab] = useFileStore((s) => [s.operation, s.setFile, s.designerTab], shallow);
     const [name, setName] = useState<string>('');
-    const [nameAvailable, setNameAvailable] = useState<boolean>(true);
     const [fileType, setFileType] = useState<string>();
     const [kameletType, setKameletType] = useState<KameletTypes>('source');
     const [selectedKamelet, setSelectedKamelet] = useState<string>();
+    const [globalErrors, registerResponseErrors, resetGlobalErrors] = useResponseErrorHandler(
+        responseToFormErrorFields,
+        setError
+    );
 
     useEffect(() => {
         if (props.types.length > 0) {
@@ -58,14 +94,44 @@ export function CreateFileModal(props: Props) {
         }
     }, [props]);
 
-    function cleanValues() {
-        setName("");
+    function resetForm() {
+        resetGlobalErrors();
+        reset(defaultFormValues);
+        setName("")
         setFileType(props.types.at(0) || 'INTEGRATION');
     }
 
     function closeModal() {
         setFile("none");
-        cleanValues();
+        resetForm();
+    }
+
+    function handleFormSubmit() {
+        const code = getCode();
+        const fullFileName = getFullFileName(name, fileType);
+        const file = new ProjectFile(fullFileName, project.projectId, code, Date.now());
+
+        return ProjectService.createFile(file)
+            .then(() => handleOnFormSubmitSuccess(code, file))
+            .catch((error) => handleOnFormSubmitFailure(error));
+    }
+
+    function handleOnFormSubmitSuccess (code: string, file: ProjectFile) {
+        const message = "File successfully created.";
+        EventBus.sendAlert( "Success", message, "success");
+
+        ProjectService.refreshProjectData(file.projectId);
+
+        resetForm();
+        if (code) {
+            setFile('select', file, designerTab);
+        } else {
+            setFile("none");
+        }
+    }
+
+    function handleOnFormSubmitFailure(error: AxiosError) {
+        registerResponseErrors(error);
     }
 
     function getCode(): string {
@@ -92,23 +158,6 @@ export function CreateFileModal(props: Props) {
         }
     }
 
-    function confirmAndCloseModal() {
-        const code = getCode();
-        const fullFileName = getFullFileName(name, fileType);
-        const file = new ProjectFile(fullFileName, project.projectId, code, Date.now());
-        ProjectService.createFile(file);
-        cleanValues();
-        if (code) {
-            setFile('select', file, designerTab);
-        } else {
-            setFile("none");
-        }
-    }
-
-    function getExistingFilenames(): string[] {
-        return files.map(f => f.name);
-    }
-
     function fileNameCheck(title: string) {
         return title.replace(/[^0-9a-zA-Z.]+/gi, "-").toLowerCase();
     }
@@ -133,8 +182,6 @@ export function CreateFileModal(props: Props) {
 
     function update(value: string, type?: string) {
         setName(value);
-        const exists = getExistingFilenames().findIndex(f => f === getFullFileName(value, type)) === -1;
-        setNameAvailable(exists);
         setFileType(type);
     }
 
@@ -145,9 +192,8 @@ export function CreateFileModal(props: Props) {
             isOpen={["create", "copy"].includes(operation)}
             onClose={closeModal}
             actions={[
-                <Button key="confirm" variant="primary" isDisabled={!nameAvailable || name === undefined || name.trim().length === 0}
-                        onClick={event => confirmAndCloseModal()}>Save</Button>,
-                <Button key="cancel" variant="secondary" onClick={event => closeModal()}>Cancel</Button>
+                <Button key="confirm" variant="primary" onClick={handleSubmit(handleFormSubmit)}>Save</Button>,
+                <Button key="cancel" variant="secondary" onClick={closeModal}>Cancel</Button>
             ]}
         >
             <Form autoComplete="off" isHorizontal className="create-file-form">
@@ -159,6 +205,8 @@ export function CreateFileModal(props: Props) {
                                 return <ToggleGroupItem key={title} text={title} buttonId={p.name}
                                                         isSelected={fileType === p.name}
                                                         onChange={(_, selected) => {
+                                                            resetGlobalErrors();
+                                                            clearErrors('name');
                                                             update(name, p.name);
                                                         }}/>
                             })}
@@ -178,23 +226,30 @@ export function CreateFileModal(props: Props) {
                     </ToggleGroup>
                 </FormGroup>}
                 <FormGroup label="Name" fieldId="name" isRequired>
-                    <TextInput id="name"
+                    <TextInput className="text-field" type="text" id="name"
                                aria-label="name"
                                value={name}
-                               onChange={(_, value) => update(value, fileType)}/>
-                    <FormHelperText>
-                        <HelperText id="helper-text1">
-                            <HelperTextItem variant={nameAvailable ? 'default' : 'error'}>
-                                {!nameAvailable ? 'File ': ''}{getFullFileName(name, fileType)}{!nameAvailable ? ' already exists': ''}
-                            </HelperTextItem>
-                        </HelperText>
-                    </FormHelperText>
+                               validated={!!errors.name ? 'error' : 'default'}
+                               {...register('name')}
+                               onChange={(e, value) => {
+                                   update(value, fileType);
+                                   register('name').onChange(e);
+                               }}
+                    />
+                    {!!errors.name && <Text  style={{ color: 'red', fontStyle: 'italic'}}>{errors?.name?.message}</Text>}
                 </FormGroup>
                 {isKamelet && <FormGroup label="Copy from" fieldId="kamelet">
                     <TypeaheadSelect listOfValues={listOfValues} onSelect={value => {
                         setSelectedKamelet(value)
                     }}/>
                 </FormGroup>}
+                <Grid>
+                    {globalErrors &&
+                        globalErrors.map((error) => (
+                            <Alert title={error} key={error} variant="danger"></Alert>
+                        ))}
+                    <Divider role="presentation" />
+                </Grid>
             </Form>
         </Modal>
     )
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/files/UploadFileModal.tsx b/karavan-web/karavan-app/src/main/webui/src/project/files/UploadFileModal.tsx
index dfe1a198..70ba3715 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/files/UploadFileModal.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/files/UploadFileModal.tsx
@@ -17,16 +17,18 @@
 import React, {useEffect, useState} from 'react';
 import {
     TextInput,
-    Button, Modal, FormGroup, ModalVariant, Switch, Form, FileUpload, Radio
+    Button, Modal, FormGroup, ModalVariant, Switch, Form, FileUpload, Radio, Alert, Divider, Grid, Text
 } from '@patternfly/react-core';
 import '../../designer/karavan.css';
 import {ProjectFile} from "../../api/ProjectModels";
-import {KaravanApi} from "../../api/KaravanApi";
 import {useFileStore} 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";
 
 interface Props {
     projectId: string,
@@ -34,6 +36,26 @@ interface Props {
 
 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
+    });
+
     const [operation, setFile] = useFileStore((s) => [s.operation, s.setFile], shallow);
     const [type, setType] = useState<'integration' | 'openapi' | 'other'>('integration');
     const [filename, setFilename] = useState('');
@@ -43,6 +65,10 @@ export function UploadFileModal(props: Props) {
     const [isRejected, setIsRejected] = useState(false);
     const [generateRest, setGenerateRest] = useState(true);
     const [generateRoutes, setGenerateRoutes] = useState(true);
+    const [globalErrors, registerResponseErrors, resetGlobalErrors] = useResponseErrorHandler(
+        responseToFormErrorFields,
+        setError
+    );
 
     useEffect(() => {
         setFilename('')
@@ -50,37 +76,41 @@ export function UploadFileModal(props: Props) {
         setType('integration')
     }, []);
 
+    function resetForm() {
+        resetGlobalErrors();
+    }
+
     function closeModal () {
-        setFile("none")
+        setFile("none");
+        resetForm();
     }
 
-    function saveAndCloseModal () {
+    function handleFormSubmit() {
         const file = new ProjectFile(filename, props.projectId, data, Date.now());
-        if (type === "openapi"){
-            KaravanApi.postOpenApi(file, generateRest, generateRoutes, integrationName, res => {
-                if (res.status === 200) {
-                    EventBus.sendAlert("File uploaded", "", "info")
-                    closeModal();
-                    ProjectService.refreshProjectData(props.projectId);
-                } else {
-                    closeModal();
-                    EventBus.sendAlert("Error", res.statusText, "warning")
-                }
-            })
+
+        if (type === "openapi") {
+            return ProjectService.createOpenApiFile(file, generateRest, generateRoutes, integrationName)
+                .then(() => handleOnFormSubmitSuccess())
+                .catch((error) => handleOnFormSubmitFailure(error));
         } else {
-            KaravanApi.postProjectFile(file, res => {
-                if (res.status === 200) {
-                    EventBus.sendAlert("File uploaded", "", "info")
-                    closeModal();
-                    ProjectService.refreshProjectData(props.projectId);
-                } else {
-                    closeModal();
-                    EventBus.sendAlert("Error", res.statusText, "warning")
-                }
-            })
+            return ProjectService.createFile(file)
+                .then(() => handleOnFormSubmitSuccess())
+                .catch((error) => handleOnFormSubmitFailure(error));
         }
     }
 
+    function handleOnFormSubmitSuccess () {
+        const message = "File successfully uploaded.";
+        EventBus.sendAlert( "Success", message, "success");
+
+        closeModal();
+        ProjectService.refreshProjectData(props.projectId);
+    }
+
+    function handleOnFormSubmitFailure(error: AxiosError) {
+        registerResponseErrors(error);
+    }
+
     const handleFileInputChange = (file: File) => setFilename(file.name);
     const handleFileReadStarted = (fileHandle: File) => setIsLoading(true);
     const handleFileReadFinished = (fileHandle: File) => setIsLoading(false);
@@ -90,6 +120,8 @@ export function UploadFileModal(props: Props) {
         setFilename('');
         setData('');
         setIsRejected(false);
+        resetGlobalErrors();
+        reset(defaultFormValues);
     };
 
 
@@ -104,20 +136,32 @@ export function UploadFileModal(props: Props) {
             isOpen={operation === 'upload'}
             onClose={closeModal}
             actions={[
-                <Button key="confirm" variant="primary" onClick={saveAndCloseModal} isDisabled={fileNotUploaded}>Save</Button>,
+                <Button key="confirm" variant="primary" onClick={handleSubmit(handleFormSubmit)} isDisabled={fileNotUploaded}>Save</Button>,
                 <Button key="cancel" variant="secondary" onClick={closeModal}>Cancel</Button>
             ]}
         >
             <Form>
                 <FormGroup fieldId="type">
                     <Radio value="Integration" label="Integration yaml" name="Integration" id="Integration" isChecked={type === 'integration'}
-                           onChange={(event, _) => setType(_ ? 'integration': 'openapi' )}
+                           onChange={(event, _) => {
+                               resetGlobalErrors();
+                               clearErrors("upload");
+                               setType(_ ? 'integration': 'openapi' );
+                           }}
                     />{' '}
                     <Radio value="OpenAPI" label="OpenAPI json/yaml" name="OpenAPI" id="OpenAPI" isChecked={type === 'openapi'}
-                           onChange={(event, _) => setType( _ ? 'openapi' : 'integration' )}
+                           onChange={(event, _) => {
+                               resetGlobalErrors();
+                               clearErrors("upload");
+                               setType( _ ? 'openapi' : 'integration' );
+                           }}
                     />
                     <Radio value="Other" label="Other" name="Other" id="Other" isChecked={type === 'other'}
-                           onChange={(event, _) => setType( _ ? 'other' : 'integration' )}
+                           onChange={(event, _) => {
+                               resetGlobalErrors();
+                               clearErrors("upload");
+                               setType( _ ? 'other' : 'integration' );
+                           }}
                     />
                 </FormGroup>
                 <FormGroup fieldId="upload">
@@ -129,16 +173,27 @@ export function UploadFileModal(props: Props) {
                         hideDefaultPreview
                         browseButtonText="Upload"
                         isLoading={isLoading}
-                        onFileInputChange={(_event, fileHandle: File) => handleFileInputChange(fileHandle)}
-                        onDataChange={(_event, data) => handleTextOrDataChange(data)}
-                        onTextChange={(_event, text) => handleTextOrDataChange(text)}
+                        onFileInputChange={(_event, fileHandle: File) => {
+                            handleFileInputChange(fileHandle);
+                            register('upload').onChange(_event);
+                        }}
+                        onDataChange={(_event, data) => {
+                            handleTextOrDataChange(data);
+                            register('upload').onChange(_event);
+                        }}
+                        onTextChange={(_event, text) => {
+                            handleTextOrDataChange(data);
+                            register('upload').onChange(_event);
+                        }}
                         onReadStarted={(_event, fileHandle: File) => handleFileReadStarted(fileHandle)}
                         onReadFinished={(_event, fileHandle: File) => handleFileReadFinished(fileHandle)}
                         allowEditingUploadedText={false}
                         onClearClick={handleClear}
                         dropzoneProps={{accept: accept, onDropRejected: handleFileRejected}}
-                        validated={isRejected ? 'error' : 'default'}
+                        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
@@ -167,7 +222,14 @@ export function UploadFileModal(props: Props) {
                                onChange={(_, value) => setIntegrationName(value)}
                     />
                 </FormGroup>}
+                <Grid>
+                    {globalErrors &&
+                        globalErrors.map((error) => (
+                            <Alert title={error} key={error} variant="danger"></Alert>
+                        ))}
+                    <Divider role="presentation" />
+                </Grid>
             </Form>
         </Modal>
     )
-};
\ No newline at end of file
+}
\ No newline at end of file