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 edb92d34 Bean Wizard
edb92d34 is described below
commit edb92d343f6cd8bf3ec121aa412451d82ea26be2
Author: Marat Gubaidullin <[email protected]>
AuthorDate: Thu Apr 25 11:22:46 2024 -0400
Bean Wizard
---
.../main/webui/src/designer/KaravanDesigner.tsx | 9 +-
.../main/webui/src/designer/editor/CodeEditor.tsx | 4 +-
.../webui/src/designer/property/DslProperties.tsx | 2 +-
.../webui/src/designer/route/DslConnections.tsx | 92 ++++++++--------
.../main/webui/src/project/beans/BeanWizard.tsx | 64 ++++-------
.../src/main/webui/src/util/useFormUtil.tsx | 119 ++++++++++++++-------
6 files changed, 161 insertions(+), 129 deletions(-)
diff --git a/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx
b/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx
index cee6fbc4..ea97a7e1 100644
--- a/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx
+++ b/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx
@@ -128,8 +128,13 @@ export function KaravanDesigner(props: Props) {
}
function getCode(integration: Integration): string {
- const clone = CamelUtil.cloneIntegration(integration);
- return CamelDefinitionYaml.integrationToYaml(clone);
+ try {
+ const clone = CamelUtil.cloneIntegration(integration);
+ return CamelDefinitionYaml.integrationToYaml(clone);
+ } catch (e) {
+ EventBus.sendAlert('Error parsing Yaml', (e as Error).message,
'danger');
+ return '';
+ }
}
function getTab(title: string, tooltip: string, icon: string, showBadge:
boolean = false) {
diff --git a/karavan-app/src/main/webui/src/designer/editor/CodeEditor.tsx
b/karavan-app/src/main/webui/src/designer/editor/CodeEditor.tsx
index d833148e..4bb4e3c5 100644
--- a/karavan-app/src/main/webui/src/designer/editor/CodeEditor.tsx
+++ b/karavan-app/src/main/webui/src/designer/editor/CodeEditor.tsx
@@ -20,6 +20,7 @@ import Editor from "@monaco-editor/react";
import {shallow} from "zustand/shallow";
import {useDesignerStore, useIntegrationStore} from "../DesignerStore";
import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml";
+import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
export function CodeEditor () {
@@ -29,7 +30,8 @@ export function CodeEditor () {
useEffect(() => {
try {
- const c = CamelDefinitionYaml.integrationToYaml(integration);
+ const clone = CamelUtil.cloneIntegration(integration);
+ const c = CamelDefinitionYaml.integrationToYaml(clone);
setCode(c);
} catch (e: any) {
const message: string = e?.message ? e.message : e.reason;
diff --git a/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx
b/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx
index 7448351c..a8ce0076 100644
--- a/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx
+++ b/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx
@@ -62,7 +62,7 @@ export function DslProperties(props: Props) {
const [showAdvanced, setShowAdvanced] = useState<boolean>(false);
- function getClonableElementHeader(): JSX.Element {
+ function getClonableElementHeader(): React.JSX.Element {
const title = selectedStep && CamelDisplayUtil.getTitle(selectedStep);
const description = selectedStep?.dslName ?
CamelMetadataApi.getCamelModelMetadataByClassName(selectedStep?.dslName)?.description
: title;
const descriptionLines: string [] = description ?
description?.split("\n") : [""];
diff --git a/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx
b/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx
index a711780b..0b5dead4 100644
--- a/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx
+++ b/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx
@@ -47,7 +47,7 @@ export function DslConnections() {
setTons(prevState => {
const data = new Map<string, string[]>();
TopologyUtils.findTopologyOutgoingNodes(integrations).forEach(t =>
{
- const key = (t.step as any)?.uri + ':' + (t.step as
any)?.parameters.name;
+ const key = (t.step as any)?.uri + ':' + (t.step as
any)?.parameters?.name;
if (data.has(key)) {
const list = data.get(key) || [];
list.push(t.routeId);
@@ -253,7 +253,7 @@ export function DslConnections() {
{name !== undefined &&
<Tooltip content={`Go to ${uri}:${name}`}
position={"left"}>
<Button style={{position: 'absolute', right: 27,
top: -12, whiteSpace: 'nowrap', zIndex: 300, padding: 0}}
- variant={'link'}
+ variant={'link'}
aria-label="Goto"
onClick={_ =>
InfrastructureAPI.onInternalConsumerClick(uri, name, undefined)}>
{name}
@@ -306,7 +306,7 @@ export function DslConnections() {
function getArrow(pos: DslPosition): JSX.Element[] {
const list: JSX.Element[] = [];
- if (pos.parent && pos.parent.dslName === 'TryDefinition' &&
pos.position === 0) {
+ if (pos.parent && pos.parent.dslName === 'TryDefinition' &&
pos.position === 0) {
const parent = steps.get(pos.parent.uuid);
list.push(...addArrowToList(list, parent, pos, true, false))
} else if (pos.parent && ['RouteConfigurationDefinition',
'MulticastDefinition'].includes(pos.parent.dslName)) {
@@ -320,7 +320,7 @@ export function DslConnections() {
const parent = steps.get(pos.parent.uuid);
list.push(...addArrowToList(list, parent, pos, true, false))
} else if (pos.parent && ['WhenDefinition', 'OtherwiseDefinition',
'CatchDefinition', 'FinallyDefinition',
'TryDefinition'].includes(pos.parent.dslName)) {
- if (pos.position === 0) {
+ if (pos.position === 0) {
const parent = steps.get(pos.parent.uuid);
list.push(...addArrowToList(list, parent, pos, true, false))
}
@@ -395,45 +395,45 @@ export function DslConnections() {
}
function getComplexArrow(key: string, rect1: DOMRect, rect2: DOMRect,
toHeader: boolean) {
- const startX = rect1.x + rect1.width / 2 - left;
- const startY = rect1.y + rect1.height - top - 2;
- const endX = rect2.x + rect2.width / 2 - left;
- const endTempY = rect2.y - top - 9;
-
- const gapX = Math.abs(endX - startX);
- const gapY = Math.abs(endTempY - startY);
-
- const radX = gapX > 30 ? 20 : gapX/2;
- const radY = gapY > 30 ? 20 : gapY/2;
- const endY = rect2.y - top - radY - (toHeader ? 9 : 6);
-
- const iRadX = startX > endX ? -1 * radX : radX;
- const iRadY = startY > endY ? -1 * radY : radY;
-
- const LX1 = startX;
- const LY1 = endY - radY;
-
- const Q1_X1 = startX;
- const Q1_Y1 = LY1 + radY;
- const Q1_X2 = startX + iRadX;
- const Q1_Y2 = LY1 + radY;
-
- const LX2 = startX + (endX - startX) - iRadX;
- const LY2 = LY1 + radY;
-
- const Q2_X1 = LX2 + iRadX;
- const Q2_Y1 = endY;
- const Q2_X2 = LX2 + iRadX;
- const Q2_Y2 = endY + radY;
-
- const path = `M ${startX} ${startY}`
- + ` L ${LX1} ${LY1} `
- + ` Q ${Q1_X1} ${Q1_Y1} ${Q1_X2} ${Q1_Y2}`
- + ` L ${LX2} ${LY2}`
- + ` Q ${Q2_X1} ${Q2_Y1} ${Q2_X2} ${Q2_Y2}`
- return (
- <path key={uuidv4()} name={key} d={path} className="path"
markerEnd="url(#arrowhead)"/>
- )
+ const startX = rect1.x + rect1.width / 2 - left;
+ const startY = rect1.y + rect1.height - top - 2;
+ const endX = rect2.x + rect2.width / 2 - left;
+ const endTempY = rect2.y - top - 9;
+
+ const gapX = Math.abs(endX - startX);
+ const gapY = Math.abs(endTempY - startY);
+
+ const radX = gapX > 30 ? 20 : gapX/2;
+ const radY = gapY > 30 ? 20 : gapY/2;
+ const endY = rect2.y - top - radY - (toHeader ? 9 : 6);
+
+ const iRadX = startX > endX ? -1 * radX : radX;
+ const iRadY = startY > endY ? -1 * radY : radY;
+
+ const LX1 = startX;
+ const LY1 = endY - radY;
+
+ const Q1_X1 = startX;
+ const Q1_Y1 = LY1 + radY;
+ const Q1_X2 = startX + iRadX;
+ const Q1_Y2 = LY1 + radY;
+
+ const LX2 = startX + (endX - startX) - iRadX;
+ const LY2 = LY1 + radY;
+
+ const Q2_X1 = LX2 + iRadX;
+ const Q2_Y1 = endY;
+ const Q2_X2 = LX2 + iRadX;
+ const Q2_Y2 = endY + radY;
+
+ const path = `M ${startX} ${startY}`
+ + ` L ${LX1} ${LY1} `
+ + ` Q ${Q1_X1} ${Q1_Y1} ${Q1_X2} ${Q1_Y2}`
+ + ` L ${LX2} ${LY2}`
+ + ` Q ${Q2_X1} ${Q2_Y1} ${Q2_X2} ${Q2_Y2}`
+ return (
+ <path key={uuidv4()} name={key} d={path} className="path"
markerEnd="url(#arrowhead)"/>
+ )
}
function getSvg() {
@@ -442,8 +442,8 @@ export function DslConnections() {
const uniqueArrows = [...new Map(arrows.map(item => [(item as
any).key, item])).values()]
return (
<svg key={svgKey}
- style={{width: width, height: height, position: "absolute",
left: 0, top: 0}}
- viewBox={"0 0 " + (width) + " " + (height)}>
+ style={{width: width, height: height, position: "absolute",
left: 0, top: 0}}
+ viewBox={"0 0 " + (width) + " " + (height)}>
<defs>
<marker id="arrowhead" markerWidth="9" markerHeight="6"
refX="0" refY="3" orient="auto" className="arrow">
<polygon points="0 0, 9 3, 0 6"/>
@@ -464,4 +464,4 @@ export function DslConnections() {
{getOutgoings().map(p => getOutgoingIcons(p))}
</div>
)
-}
+}
\ No newline at end of file
diff --git a/karavan-app/src/main/webui/src/project/beans/BeanWizard.tsx
b/karavan-app/src/main/webui/src/project/beans/BeanWizard.tsx
index 9c599762..8f13949d 100644
--- a/karavan-app/src/main/webui/src/project/beans/BeanWizard.tsx
+++ b/karavan-app/src/main/webui/src/project/beans/BeanWizard.tsx
@@ -16,9 +16,10 @@
*/
import React, {useEffect, useMemo, useState} from 'react';
import {
+ Alert,
capitalize,
Flex,
- Form, FormGroup, FormHelperText, HelperText, HelperTextItem, InputGroup,
InputGroupItem,
+ Form, FormAlert, FormGroup, FormHelperText, HelperText, HelperTextItem,
InputGroup, InputGroupItem,
Modal,
ModalVariant,
Radio, Text, TextInput,
@@ -47,21 +48,6 @@ const BEAN_TEMPLATE_SUFFIX_FILENAME =
"-bean-template.camel.yaml";
export function BeanWizard() {
- const {
- register,
- setError,
- handleSubmit,
- formState: {errors},
- reset,
- setValue
- } = useForm({
- mode: "onChange",
- defaultValues: {filename: ''}
- });
-
- const responseToFormErrorFields = new Map<string, string>([
- ["filename", "filename"]
- ]);
const [project] = useProjectStore((s) => [s.project], shallow);
const [setFile, designerTab] = useFileStore((s) => [s.setFile,
s.designerTab], shallow);
@@ -74,11 +60,7 @@ export function BeanWizard() {
const [bean, setBean] = useState<RegistryBeanDefinition | undefined>();
const [filename, setFilename] = useState<string>('');
const [beanName, setBeanName] = useState<string>('');
-
- const [globalErrors, registerResponseErrors, resetGlobalErrors] =
useResponseErrorHandler(
- responseToFormErrorFields,
- setError
- );
+ const [backendError, setBackendError] = React.useState<string>();
function handleOnFormSubmitSuccess(file: ProjectFile) {
const message = "File successfully created.";
@@ -106,15 +88,19 @@ export function BeanWizard() {
}
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));
+ KaravanApi.saveProjectFile(file, (result, file) => {
+ if (result) {
+ handleOnFormSubmitSuccess(file);
+ } else {
+ setBackendError(file?.response?.data);
+ }
+ })
}
}
useEffect(() => {
if (showWizard) {
- reset({filename: ''})
+ setBackendError(undefined);
setFilename('')
setTemplateName('');
setTemplateBeanName('');
@@ -210,7 +196,7 @@ export function BeanWizard() {
</Form>
</WizardStep>
<WizardStep name={"File"} id={"file"}
- footer={{nextButtonText: 'Save', onNext:
handleSubmit(handleFormSubmit)}}
+ footer={{nextButtonText: 'Save', onNext: event =>
handleFormSubmit()}}
isDisabled={(templateName.length == 0 ||
templateBeanName.length == 0) && templateName !== EMPTY_BEAN}
>
<Form autoComplete="off">
@@ -221,30 +207,24 @@ export function BeanWizard() {
aria-label="filename"
value={filename}
customIcon={<Text>{CAMEL_YAML_EXT}</Text>}
- validated={!!errors.filename ?
'error' : 'default'}
- {...register('filename')}
onChange={(e, value) => {
setFilename(value);
-
register('filename').onChange(e);
}}
/>
</InputGroupItem>
{templateName !== EMPTY_BEAN &&
<InputGroupItem>
- <BeanFilesDropdown
{...register('filename')} onSelect={(fn, event) => {
- setFilename(fn);
- setValue('filename', fn,
{shouldValidate: true});
- }}/>
+ <BeanFilesDropdown
+ onSelect={(fn, event) => {
+ setFilename(fn);
+ }}
+ />
</InputGroupItem>}
</InputGroup>
- {!!errors.filename && (
- <FormHelperText>
- <HelperText>
- <HelperTextItem
icon={<ExclamationCircleIcon/>} variant={"error"}>
- {errors?.filename?.message}
- </HelperTextItem>
- </HelperText>
- </FormHelperText>
- )}
+ {backendError &&
+ <FormAlert>
+ <Alert variant="danger"
title={backendError} aria-live="polite" isInline/>
+ </FormAlert>
+ }
</FormGroup>
</Form>
</WizardStep>
diff --git a/karavan-app/src/main/webui/src/util/useFormUtil.tsx
b/karavan-app/src/main/webui/src/util/useFormUtil.tsx
index 6e14da3e..7b06e843 100644
--- a/karavan-app/src/main/webui/src/util/useFormUtil.tsx
+++ b/karavan-app/src/main/webui/src/util/useFormUtil.tsx
@@ -1,5 +1,12 @@
import React from 'react';
-import {Controller, FieldError, UseFormReturn} from "react-hook-form";
+import {
+ Controller,
+ ControllerFieldState, ControllerRenderProps,
+ FieldError,
+ FieldValues,
+ UseFormReturn,
+ UseFormStateReturn
+} from "react-hook-form";
import {
Flex,
FormGroup,
@@ -7,7 +14,7 @@ import {
FormSelect,
FormSelectOption,
HelperText,
- HelperTextItem, Switch, Text,
+ HelperTextItem, Switch, Text, TextArea,
TextInput, TextInputGroup, TextInputGroupMain, TextVariants
} from "@patternfly/react-core";
import "./form-util.css"
@@ -29,16 +36,48 @@ export function useFormUtil(formContext:
UseFormReturn<any>) {
}
function getTextField(fieldName: string, label: string, validate?:
((value: string, formValues: any) => boolean | string) | Record<string, (value:
string, formValues: any) => boolean | string>) {
- const {setValue, getValues, register, formState: {errors}} =
formContext;
+ const {control, setValue, getValues, formState: {errors}} =
formContext;
+ return (
+ <FormGroup label={label} fieldId={fieldName} isRequired>
+ <Controller
+ rules={{required: "Required field", validate: validate}}
+ control={control}
+ name={fieldName}
+ render={() => (
+ <TextInput className="text-field" type="text"
id={fieldName}
+ value={getValues(fieldName)}
+ validated={!!errors[fieldName] ? 'error' :
'default'}
+ onChange={(_, v) => {
+ setValue(fieldName, v, {shouldValidate:
true});
+ }}
+ />
+ )}
+ />
+ {getHelper((errors as any)[fieldName])}
+ </FormGroup>
+ )
+ }
+
+ function getTextArea(fieldName: string, label: string, validate?: ((value:
string, formValues: any) => boolean | string) | Record<string, (value: string,
formValues: any) => boolean | string>) {
+ const {setValue, getValues, control, formState: {errors}} =
formContext;
return (
<FormGroup label={label} fieldId={fieldName} isRequired>
- <TextInput className="text-field" type="text" id={fieldName}
- value={getValues()[fieldName]}
- validated={!!errors[fieldName] ? 'error' :
'default'}
- {...register(fieldName, {required: "Required
field", validate: validate})}
- onChange={(e, v) => {
- setValue(fieldName, v, {shouldValidate: true});
- }}
+ <Controller
+ rules={{required: "Required field", validate: validate}}
+ control={control}
+ name={fieldName}
+ render={() => (
+ <TextArea type="text"
+ id={fieldName}
+ value={getValues(fieldName)}
+ validated={!!errors[fieldName] ? 'error' :
'default'}
+ // ref={ref}
+ onChange={(e, v) => {
+ setValue(fieldName, v, {shouldValidate:
true});
+ }}
+ autoResize
+ />
+ )}
/>
{getHelper((errors as any)[fieldName])}
</FormGroup>
@@ -53,8 +92,11 @@ export function useFormUtil(formContext: UseFormReturn<any>)
{
<FormGroup label={label} fieldId={fieldName} isRequired>
<TextInputGroup>
<TextInputGroupMain className="text-field-with-prefix"
type="text" id={fieldName}
- value={getValues()[fieldName]}
- {...register(fieldName, {required:
(required ? "Required field" : false), validate: validate})}
+ value={getValues(fieldName)}
+ {...register(fieldName, {
+ required: (required ? "Required
field" : false),
+ validate: validate
+ })}
onChange={(e, v) => {
setValue(fieldName, v,
{shouldValidate: true});
}}
@@ -75,8 +117,11 @@ export function useFormUtil(formContext:
UseFormReturn<any>) {
<FormGroup label={label} fieldId={fieldName} isRequired>
<TextInputGroup className="text-field-with-suffix">
<TextInputGroupMain type="text" id={fieldName}
- value={getValues()[fieldName]}
- {...register(fieldName, {required:
(required ? "Required field" : false), validate: validate})}
+ value={getValues(fieldName)}
+ {...register(fieldName, {
+ required: (required ? "Required
field" : false),
+ validate: validate
+ })}
onChange={(e, v) => {
setValue(fieldName, v,
{shouldValidate: true});
}}
@@ -119,33 +164,33 @@ export function useFormUtil(formContext:
UseFormReturn<any>) {
return (
<FormGroup label={label} fieldId={fieldName} isRequired
{...register(fieldName)}>
<Flex direction={{default: 'column'}}>
- {options.map((option, index) => {
- const key = option[0];
- const label = option[0];
- return (<Switch
- id={key}
- label={label}
- labelOff={label}
- isChecked={watch(fieldName) !== undefined &&
watch(fieldName).includes(key)}
- onChange={(e, v) => {
- const vals: string[] = watch(fieldName);
- const idx = vals.findIndex(x => x === key);
- if (idx > -1 && !v) {
- vals.splice(idx, 1);
- setValue(fieldName, [...vals]);
- } else if (idx === -1 && v) {
- vals.push(key);
- setValue(fieldName, [...vals]);
- }
- }}
- ouiaId={option[0]}
- />)
- })}
+ {options.map((option, index) => {
+ const key = option[0];
+ const label = option[0];
+ return (<Switch
+ id={key}
+ label={label}
+ labelOff={label}
+ isChecked={watch(fieldName) !== undefined &&
watch(fieldName).includes(key)}
+ onChange={(e, v) => {
+ const vals: string[] = watch(fieldName);
+ const idx = vals.findIndex(x => x === key);
+ if (idx > -1 && !v) {
+ vals.splice(idx, 1);
+ setValue(fieldName, [...vals]);
+ } else if (idx === -1 && v) {
+ vals.push(key);
+ setValue(fieldName, [...vals]);
+ }
+ }}
+ ouiaId={option[0]}
+ />)
+ })}
</Flex>
</FormGroup>
)
}
- return {getFormSelect, getTextField, getSwitches, getTextFieldPrefix,
getTextFieldSuffix}
+ return {getFormSelect, getTextField, getSwitches, getTextFieldPrefix,
getTextFieldSuffix, getTextArea}
}