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 be1aeed6dd10ab80124354a1981aae78d7390026 Author: Marat Gubaidullin <ma...@talismancloud.io> AuthorDate: Fri Feb 2 15:32:44 2024 -0500 Wizard #1097 --- .../main/webui/src/project/beans/BeanWizard.tsx | 173 ++++++++++++++++++--- 1 file changed, 148 insertions(+), 25 deletions(-) diff --git a/karavan-web/karavan-app/src/main/webui/src/project/beans/BeanWizard.tsx b/karavan-web/karavan-app/src/main/webui/src/project/beans/BeanWizard.tsx index 1adc5d0f..55bbc040 100644 --- a/karavan-web/karavan-app/src/main/webui/src/project/beans/BeanWizard.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/project/beans/BeanWizard.tsx @@ -16,54 +16,132 @@ */ import React, {useEffect, useMemo, useState} from 'react'; import { - Badge, capitalize, Flex, - Form, FormGroup, + Form, FormGroup, FormHelperText, HelperText, HelperTextItem, Modal, ModalVariant, Radio, Text, TextInput, Wizard, - WizardHeader, WizardStep } from '@patternfly/react-core'; import {KaravanApi} from "../../api/KaravanApi"; import {RegistryBeanDefinition} from "karavan-core/lib/model/CamelDefinition"; import {CodeUtils} from "../../util/CodeUtils"; -import {ProjectFile, ProjectType} from "../../api/ProjectModels"; -import {useWizardStore} from "../../api/ProjectStore"; +import {ProjectFile} from "../../api/ProjectModels"; +import {useFilesStore, useFileStore, useProjectStore, useWizardStore} from "../../api/ProjectStore"; import {shallow} from "zustand/shallow"; +import ExclamationCircleIcon from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon'; +import {useForm} from "react-hook-form"; +import {yupResolver} from "@hookform/resolvers/yup"; +import * as yup from "yup"; import {ProjectService} from "../../api/ProjectService"; +import {EventBus} from "../../designer/utils/EventBus"; +import {useResponseErrorHandler} from "../../shared/error/UseResponseErrorHandler"; +import {Beans, Integration} from "karavan-core/lib/model/IntegrationDefinition"; +import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml"; +const CAMEL_YAML_EXT = ".camel.yaml"; +const EMPTY_BEAN = "empty"; const BEAN_TEMPLATE_SUFFIX_FILENAME = "-bean-template.camel.yaml"; export function BeanWizard() { + const formValidationSchema = yup.object().shape({ + filename: yup + .string() + .matches(/^[a-zA-Z0-9_\-.]+$/, 'Incorrect filename') + .required("File name is required"), + }); + + const { + register, + setError, + handleSubmit, + formState: { errors }, + reset + } = useForm({ + resolver: yupResolver(formValidationSchema), + mode: "onChange", + defaultValues: {filename: ''} + }); + + const responseToFormErrorFields = new Map<string, string>([ + ["filename", "filename"] + ]); + + const [project] = useProjectStore((s) => [s.project], shallow); + const [operation, setFile, designerTab] = useFileStore((s) => [s.operation, s.setFile, s.designerTab], shallow); + const [files] = useFilesStore((s) => [s.files], shallow); const [showWizard, setShowWizard] = useWizardStore((s) => [s.showWizard, s.setShowWizard], shallow) - const [files, setFiles] = useState<ProjectFile[]>([]); + const [templateFiles, setTemplateFiles] = useState<ProjectFile[]>([]); const [templateNames, setTemplateNames] = useState<string[]>([]); const [templateName, setTemplateName] = useState<string>(''); + const [templateBeanName, setTemplateBeanName] = useState<string>(''); + const [bean, setBean] = useState<RegistryBeanDefinition | undefined>(); + const [filename, setFilename] = useState<string>(''); const [beanName, setBeanName] = useState<string>(''); + const [globalErrors, registerResponseErrors, resetGlobalErrors] = useResponseErrorHandler( + responseToFormErrorFields, + setError + ); + + function handleOnFormSubmitSuccess (file: ProjectFile) { + const message = "File successfully created."; + EventBus.sendAlert( "Success", message, "success"); + ProjectService.refreshProjectData(file.projectId); + setFile('select', file, designerTab); + setShowWizard(false); + } + + function handleFormSubmit() { + console.log("!!!", bean) + let code = '{}'; + if (bean !== undefined && templateName !== EMPTY_BEAN) { + const i = Integration.createNew("temp"); + i.spec.flows?.push(new Beans({beans: [bean]})) + code = CamelDefinitionYaml.integrationToYaml(i); + } + const fullFileName = filename + CAMEL_YAML_EXT; + const file = new ProjectFile(fullFileName, project.projectId, code, Date.now()); + return ProjectService.createFile(file) + .then(() => handleOnFormSubmitSuccess(file)) + .catch((error) => registerResponseErrors(error)); + } + useEffect(() => { if (showWizard) { + console.log("useEffect", "celan") + reset({filename: ''}) + setFilename('') + setTemplateName(''); + setTemplateBeanName(''); + setBean(undefined); KaravanApi.getBeanTemplatesFiles(files => { const templateNames = files.map(file => file.name.replace(BEAN_TEMPLATE_SUFFIX_FILENAME, '')); - setFiles(prevState => { + setTemplateFiles(prevState => { return [...files]; }); setTemplateNames(prevState => { return [...templateNames]; }); - setTemplateName(''); - setBeanName(''); }); } }, [showWizard]); + useEffect(() => { + setBeanName(templateBeanName); + getBeans.filter(b=> b.name === templateBeanName).forEach(b => { + Object.getOwnPropertyNames(b.properties).forEach(prop => { + setBean(new RegistryBeanDefinition({...b})) + }) + }); + }, [templateBeanName]); + function getRegistryBeanDefinitions():RegistryBeanDefinition[] { - const fs = files + const fs = templateFiles .filter(f => f.name === templateName.concat(BEAN_TEMPLATE_SUFFIX_FILENAME)); return CodeUtils.getBeans(fs); } @@ -73,45 +151,90 @@ export function BeanWizard() { return ( <Modal title={"Bean"} onClose={_ => setShowWizard(false)} variant={ModalVariant.medium} isOpen={showWizard} onEscapePress={() => setShowWizard(false)}> - <Wizard height={600} title="Bean configuration" onClose={() => setShowWizard(false)}> + <Wizard height={600} onClose={() => setShowWizard(false)} onSubmit={event => handleFormSubmit()}> <WizardStep name={"Type"} id="type" - footer={{ isNextDisabled: !templateNames.includes(templateName) }} + footer={{ isNextDisabled: !templateNames.includes(templateName) && templateName !== EMPTY_BEAN }} > <Flex direction={{default:"column"}} gap={{default:'gapLg'}}> - {templateNames.map(n => <Radio id={n} label={capitalize(n)} name={n} isChecked={n === templateName} + <Radio key={EMPTY_BEAN} id={EMPTY_BEAN} label={capitalize(EMPTY_BEAN)} name={EMPTY_BEAN} isChecked={EMPTY_BEAN === templateName} onChange={_ => setTemplateName(EMPTY_BEAN)} /> + {templateNames.map(n => <Radio key={n} id={n} label={capitalize(n)} name={n} isChecked={n === templateName} onChange={_ => setTemplateName(n)} />)} </Flex> </WizardStep> <WizardStep name={"Template"} id="template" + isHidden={templateName === EMPTY_BEAN} isDisabled={templateName.length == 0} - footer={{ isNextDisabled: !getBeans.map(b=> b.name).includes(beanName) }} + footer={{ isNextDisabled: !getBeans.map(b=> b.name).includes(templateBeanName) }} > <Flex direction={{default:"column"}} gap={{default:'gapLg'}}> - {getBeans.map(b => <Radio id={b.name} label={b.name} name={b.name} isChecked={b.name === beanName} - onChange={_ => setBeanName(b.name)} />)} + {getBeans.map(b => <Radio key={b.name} id={b.name} label={b.name} name={b.name} isChecked={b.name === templateBeanName} + onChange={_ => setTemplateBeanName(b.name)} />)} </Flex> </WizardStep> <WizardStep name="Properties" id="properties" - isDisabled={templateName.length == 0 || beanName.length == 0} - footer={{ nextButtonText: 'Add bean' }} + isHidden={templateName === EMPTY_BEAN} + isDisabled={templateName.length == 0 || templateBeanName.length == 0} > - <Form> - {getBeans.filter(b=> b.name === beanName).map(b => ( - <> + <Form autoComplete="off"> + <FormGroup key={"beanName"} label={"Name"} fieldId={"beanName"}> + <TextInput + value={beanName} + id={"beanName"} + aria-describedby={'beanName'} + onChange={(_, value) => setBeanName(value)} + /> + </FormGroup> + <FormGroup label="Properties:" fieldId="properties"/> + {getBeans.filter(b=> b.name === templateBeanName).map(b => ( + <div key={b.name}> {Object.getOwnPropertyNames(b.properties).map(prop => ( - <FormGroup label={prop} fieldId={prop}> + <FormGroup key={prop} label={prop} fieldId={prop}> <TextInput - value={b.properties[prop]} + value={bean?.properties[prop] || ''} id={prop} aria-describedby={prop} - onChange={(_, value) => {}} + onChange={(_, value) => { + const b = new RegistryBeanDefinition({...bean}); + b.properties[prop] = value; + setBean(b); + }} /> </FormGroup> ))} - </> + </div> ))} </Form> </WizardStep> + <WizardStep name={"File"} id={"file"} + footer={{ nextButtonText: 'Save', onNext: handleSubmit(handleFormSubmit) }} + isDisabled={(templateName.length == 0 || templateBeanName.length == 0) && templateName !== EMPTY_BEAN} + > + <Form autoComplete="off"> + <FormGroup label="Filename" fieldId="filename" isRequired> + <TextInput className="text-field" type="text" id="filename" + aria-label="filename" + value={filename} + customIcon={<Text>{CAMEL_YAML_EXT}</Text>} + validated={!!errors.filename ? 'error' : 'default'} + {...register('filename')} + // validated={!!errors.name ? 'error' : 'default'} + onChange={(e, value) => { + setFilename(value); + register('filename').onChange(e); + }} + /> + {!!errors.filename && ( + <FormHelperText> + <HelperText> + <HelperTextItem icon={<ExclamationCircleIcon />} variant={"error"}> + {errors?.filename?.message} + </HelperTextItem> + </HelperText> + </FormHelperText> + )} + </FormGroup> + </Form> + </WizardStep> </Wizard> </Modal> )