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 798b8e2 Internal links (#195) 798b8e2 is described below commit 798b8e2d5ceca2a60a70c4bad7070a1b1e4d442f Author: Marat Gubaidullin <marat.gubaidul...@gmail.com> AuthorDate: Tue Feb 15 17:27:10 2022 -0500 Internal links (#195) * Autocreate Direct and Seda routes for Components * Autocreate Direct and Seda for WireTap * Autocreate Direct and Seda for Saga --- karavan-designer/src/App.tsx | 10 +++- karavan-designer/src/designer/KaravanDesigner.tsx | 1 + .../src/designer/route/DslProperties.tsx | 14 ++--- .../src/designer/route/RouteDesigner.tsx | 25 ++++++-- .../route/property/ComponentParameterField.tsx | 67 +++++++++++++++++++-- .../designer/route/property/DslPropertyField.tsx | 69 +++++++++++++++++++--- karavan-designer/src/designer/utils/CamelUi.ts | 23 +++++++- 7 files changed, 181 insertions(+), 28 deletions(-) diff --git a/karavan-designer/src/App.tsx b/karavan-designer/src/App.tsx index 519e08e..dacc878 100644 --- a/karavan-designer/src/App.tsx +++ b/karavan-designer/src/App.tsx @@ -55,6 +55,10 @@ class App extends React.Component<Props, State> { ' name: http-sink\n' + ' - kamelet:\n' + ' name: kafka-sink\n' + + ' - to:\n' + + ' uri: direct\n' + + ' - to:\n' + + ' uri: seda\n' + ' id: Main Route\n' + ' - route:\n' + ' from:\n' + @@ -64,6 +68,10 @@ class App extends React.Component<Props, State> { ' from:\n' + ' uri: direct:compensation\n' + ' id: Compensation\n' + + ' - route:\n' + + ' from:\n' + + ' uri: seda:demo\n' + + ' id: seda\n' + // ' - choice:\n' + // ' when:\n' + // ' - expression:\n' + @@ -192,7 +200,7 @@ class App extends React.Component<Props, State> { save(filename: string, yaml: string) { // console.log(filename); - // console.log(yaml); + console.log(yaml); } public render() { diff --git a/karavan-designer/src/designer/KaravanDesigner.tsx b/karavan-designer/src/designer/KaravanDesigner.tsx index 6498a50..de59a2b 100644 --- a/karavan-designer/src/designer/KaravanDesigner.tsx +++ b/karavan-designer/src/designer/KaravanDesigner.tsx @@ -59,6 +59,7 @@ export class KaravanDesigner extends React.Component<Props, State> { componentDidUpdate = (prevProps: Readonly<Props>, prevState: Readonly<State>, snapshot?: any) => { if (prevState.key !== this.state.key) { + this.props.onSave?.call(this, this.state.integration.metadata.name, this.getCode(this.state.integration)); } } diff --git a/karavan-designer/src/designer/route/DslProperties.tsx b/karavan-designer/src/designer/route/DslProperties.tsx index 8786278..1c863e6 100644 --- a/karavan-designer/src/designer/route/DslProperties.tsx +++ b/karavan-designer/src/designer/route/DslProperties.tsx @@ -35,7 +35,7 @@ import {Integration, CamelElement} from "karavan-core/lib/model/IntegrationDefin import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt"; import {ComponentApi} from "karavan-core/lib/api/ComponentApi"; import {CamelUtil} from "karavan-core/lib/api/CamelUtil"; -import {CamelUi} from "../utils/CamelUi"; +import {CamelUi, RouteToCreate} from "../utils/CamelUi"; import {CamelMetadataApi, PropertyMeta} from "karavan-core/lib/model/CamelMetadata"; import {IntegrationHeader} from "../utils/KaravanComponents"; @@ -43,7 +43,7 @@ interface Props { integration: Integration, step?: CamelElement, onIntegrationUpdate?: any, - onPropertyUpdate?: any, + onPropertyUpdate?: (element: CamelElement, updatedUuid: string, newRoute?: RouteToCreate) => void } interface State { @@ -58,19 +58,19 @@ export class DslProperties extends React.Component<Props, State> { selectStatus: new Map<string, boolean>(), }; - propertyChanged = (fieldId: string, value: string | number | boolean | any) => { + propertyChanged = (fieldId: string, value: string | number | boolean | any, newRoute?: RouteToCreate) => { if (this.state.step) { const clone = CamelUtil.cloneStep(this.state.step); (clone as any)[fieldId] = value; this.setStep(clone) - this.props.onPropertyUpdate?.call(this, clone, this.state.step.uuid); + this.props.onPropertyUpdate?.call(this, clone, this.state.step.uuid, newRoute); } } dataFormatChanged = (value: DataFormatDefinition) => { value.uuid = this.state.step?.uuid ? this.state.step?.uuid : value.uuid; this.setStep(value); - this.props.onPropertyUpdate?.call(this, value, this.state.step?.uuid); + this.props.onPropertyUpdate?.call(this, value, value.uuid); } expressionChanged = (exp:ExpressionDefinition) => { @@ -82,11 +82,11 @@ export class DslProperties extends React.Component<Props, State> { } } - parametersChanged = (parameter: string, value: string | number | boolean | any, pathParameter?: boolean) => { + parametersChanged = (parameter: string, value: string | number | boolean | any, pathParameter?: boolean, newRoute?: RouteToCreate) => { if (this.state.step && this.state.step) { if (pathParameter) { const uri = ComponentApi.buildComponentUri((this.state.step as any).uri, parameter, value); - this.propertyChanged("uri", uri); + this.propertyChanged("uri", uri, newRoute); } else { const clone = (CamelUtil.cloneStep(this.state.step)); const parameters: any = {...(clone as any).parameters}; diff --git a/karavan-designer/src/designer/route/RouteDesigner.tsx b/karavan-designer/src/designer/route/RouteDesigner.tsx index e7c5160..1d59b79 100644 --- a/karavan-designer/src/designer/route/RouteDesigner.tsx +++ b/karavan-designer/src/designer/route/RouteDesigner.tsx @@ -32,7 +32,7 @@ import {DslConnections} from "./DslConnections"; import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon"; import {DslElement} from "./DslElement"; import {EventBus} from "../utils/EventBus"; -import {CamelUi} from "../utils/CamelUi"; +import {CamelUi, RouteToCreate} from "../utils/CamelUi"; interface Props { onSave?: (integration: Integration) => void @@ -97,10 +97,25 @@ export class RouteDesigner extends React.Component<Props, State> { } }; - onPropertyUpdate = (element: CamelElement, updatedUuid: string) => { - const clone = CamelUtil.cloneIntegration(this.state.integration); - const i = CamelDefinitionApiExt.updateIntegration(clone, element, updatedUuid); - this.setState({integration: i, key: Math.random().toString()}); + onPropertyUpdate = (element: CamelElement, updatedUuid: string, newRoute?: RouteToCreate) => { + if (newRoute) { + let i = CamelDefinitionApiExt.updateIntegration(this.state.integration, element, updatedUuid); + const f = CamelDefinitionApi.createFromDefinition({uri: newRoute.componentName + ":" + newRoute.name}) + const r = CamelDefinitionApi.createRouteDefinition({from: f, id: newRoute.name}) + i = CamelDefinitionApiExt.addStepToIntegration(i, r, ''); + const clone = CamelUtil.cloneIntegration(i); + this.setState({ + integration: clone, + key: Math.random().toString(), + showSelector: false, + selectedStep: element, + selectedUuid: element.uuid + }); + } else { + const clone = CamelUtil.cloneIntegration(this.state.integration); + const i = CamelDefinitionApiExt.updateIntegration(clone, element, updatedUuid); + this.setState({integration: i, key: Math.random().toString()}); + } } showDeleteConfirmation = (id: string) => { diff --git a/karavan-designer/src/designer/route/property/ComponentParameterField.tsx b/karavan-designer/src/designer/route/property/ComponentParameterField.tsx index ab5c907..606ea9d 100644 --- a/karavan-designer/src/designer/route/property/ComponentParameterField.tsx +++ b/karavan-designer/src/designer/route/property/ComponentParameterField.tsx @@ -29,8 +29,9 @@ import '../../karavan.css'; import "@patternfly/patternfly/patternfly.css"; import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon"; import {ComponentProperty} from "karavan-core/lib/model/ComponentModels"; -import {CamelUi} from "../../utils/CamelUi"; -import {Integration} from "karavan-core/lib/model/IntegrationDefinition"; +import {CamelUi, RouteToCreate} from "../../utils/CamelUi"; +import {CamelElement, Integration} from "karavan-core/lib/model/IntegrationDefinition"; +import {ToDefinition} from "karavan-core/lib/model/CamelDefinition"; const prefix = "parameters"; const beanPrefix = "#bean:"; @@ -38,8 +39,9 @@ const beanPrefix = "#bean:"; interface Props { property: ComponentProperty, integration: Integration, + element?: CamelElement, value: any, - onParameterChange?: (parameter: string, value: string | number | boolean | any, pathParameter?: boolean) => void + onParameterChange?: (parameter: string, value: string | number | boolean | any, pathParameter?: boolean, newRoute?: RouteToCreate) => void } interface State { @@ -52,8 +54,8 @@ export class ComponentParameterField extends React.Component<Props, State> { selectStatus: new Map<string, boolean>(), } - parametersChanged = (parameter: string, value: string | number | boolean | any, pathParameter?: boolean) => { - this.props.onParameterChange?.call(this, parameter, value, pathParameter); + parametersChanged = (parameter: string, value: string | number | boolean | any, pathParameter?: boolean, newRoute?: RouteToCreate) => { + this.props.onParameterChange?.call(this, parameter, value, pathParameter, newRoute); this.setState({selectStatus: new Map<string, boolean>([[parameter, false]])}); } @@ -90,6 +92,58 @@ export class ComponentParameterField extends React.Component<Props, State> { ) } + canBeInternalUri = (property: ComponentProperty): boolean => { + if (this.props.element && this.props.element.dslName === 'ToDefinition' && property.name === 'name') { + const uri:string = (this.props.element as ToDefinition).uri || ''; + return uri.startsWith("direct") || uri.startsWith("seda"); + } else { + return false; + } + } + + getInternalComponentName = (property: ComponentProperty): string => { + if (this.props.element && this.props.element.dslName === 'ToDefinition' && property.name === 'name') { + const uri:string = (this.props.element as ToDefinition).uri || ''; + if (uri.startsWith("direct")) return "direct"; + if (uri.startsWith("seda")) return "seda"; + return ''; + } else { + return ''; + } + } + + getInternalUriSelect = (property: ComponentProperty, value: any) => { + const selectOptions: JSX.Element[] = []; + const componentName = this.getInternalComponentName(property); + const urls = CamelUi.getInternalRouteUris(this.props.integration, componentName, false); + if (urls && urls.length > 0) { + selectOptions.push(...urls.map((value: string) => + <SelectOption key={value} value={value.trim()}/>)); + } + return ( + <Select + placeholderText="Select or type an URI" + variant={SelectVariant.typeahead} + aria-label={property.name} + onToggle={isExpanded => { + this.openSelect(property.name) + }} + onSelect={(e, value, isPlaceholder) => { + const newRoute = !urls.includes(value.toString()) ? new RouteToCreate(componentName, value.toString()) : undefined; + this.parametersChanged(property.name, (!isPlaceholder ? value : undefined), property.kind === 'path', newRoute); + }} + selections={value} + isOpen={this.isSelectOpen(property.name)} + isCreatable={true} + isInputFilterPersisted={true} + aria-labelledby={property.name} + direction={SelectDirection.down} + > + {selectOptions} + </Select> + ) + } + getTextInput = (property: ComponentProperty, value: any) => { const id = prefix + "-" + property.name; return ( @@ -160,7 +214,8 @@ export class ComponentParameterField extends React.Component<Props, State> { </button> </Popover> }> - {['string', 'duration', 'integer', 'int', 'number'].includes(property.type) && property.enum === undefined + {this.canBeInternalUri(property) && this.getInternalUriSelect(property, value)} + {['string', 'duration', 'integer', 'int', 'number'].includes(property.type) && property.enum === undefined && !this.canBeInternalUri(property) && this.getTextInput(property, value)} {['object'].includes(property.type) && !property.enum && this.getSelectBean(property, value)} diff --git a/karavan-designer/src/designer/route/property/DslPropertyField.tsx b/karavan-designer/src/designer/route/property/DslPropertyField.tsx index 8e4e63c..051a409 100644 --- a/karavan-designer/src/designer/route/property/DslPropertyField.tsx +++ b/karavan-designer/src/designer/route/property/DslPropertyField.tsx @@ -33,9 +33,9 @@ import {CamelUtil} from "karavan-core/lib/api/CamelUtil"; import { PropertyMeta} from "karavan-core/lib/model/CamelMetadata"; import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt"; import {ExpressionField} from "./ExpressionField"; -import {CamelUi} from "../../utils/CamelUi"; +import {CamelUi, RouteToCreate} from "../../utils/CamelUi"; import {ComponentParameterField} from "./ComponentParameterField"; -import {DataFormatDefinition} from "karavan-core/lib/model/CamelDefinition"; +import {DataFormatDefinition, ToDefinition} from "karavan-core/lib/model/CamelDefinition"; import {Integration, CamelElement} from "karavan-core/lib/model/IntegrationDefinition"; import {KameletPropertyField} from "./KameletPropertyField"; import {ExpressionDefinition} from "karavan-core/lib/model/CamelDefinition"; @@ -47,10 +47,10 @@ import AddIcon from "@patternfly/react-icons/dist/js/icons/plus-circle-icon"; interface Props { property: PropertyMeta, value: any, - onChange?: (fieldId: string, value: string | number | boolean | any) => void, + onChange?: (fieldId: string, value: string | number | boolean | any, newRoute?: RouteToCreate) => void, onExpressionChange?: (value: ExpressionDefinition) => void, onDataFormatChange?: (value: DataFormatDefinition) => void, - onParameterChange?: (parameter: string, value: string | number | boolean | any, pathParameter?: boolean) => void, + onParameterChange?: (parameter: string, value: string | number | boolean | any, pathParameter?: boolean, newRoute?: RouteToCreate) => void, element?: CamelElement integration: Integration, } @@ -77,8 +77,8 @@ export class DslPropertyField extends React.Component<Props, State> { return this.state.selectStatus.has(propertyName) && this.state.selectStatus.get(propertyName) === true; } - propertyChanged = (fieldId: string, value: string | number | boolean | any) => { - this.props.onChange?.call(this, fieldId, value); + propertyChanged = (fieldId: string, value: string | number | boolean | any, newRoute?: RouteToCreate) => { + this.props.onChange?.call(this, fieldId, value, newRoute); this.setState({selectStatus: new Map<string, boolean>([[fieldId, false]])}); } @@ -128,10 +128,15 @@ export class DslPropertyField extends React.Component<Props, State> { } } + isUriReadOnly = (property: PropertyMeta): boolean => { + const dslName:string = this.props.element?.dslName || ''; + return property.name === 'uri' && !['ToDynamicDefinition', 'WireTapDefinition'].includes(dslName) + } + getTextField = (property: PropertyMeta, value: any) => { return ( <TextInput - className="text-field" isRequired + className="text-field" isRequired isReadOnly={this.isUriReadOnly(property)} type={['integer', 'number'].includes(property.type) ? 'number' : (property.secret ? "password" : "text")} id={property.name} name={property.name} value={value?.toString()} @@ -229,6 +234,49 @@ export class DslPropertyField extends React.Component<Props, State> { ) } + canBeInternalUri = (property: PropertyMeta, element?: CamelElement): boolean => { + if (element?.dslName === 'WireTapDefinition' && property.name === 'uri') { + return true; + } else if (element?.dslName === 'SagaDefinition' && ['compensation', 'completion'].includes(property.name)) { + return true; + } else { + return false; + } + } + + getInternalUriSelect = (property: PropertyMeta, value: any) => { + const selectOptions: JSX.Element[] = []; + const urls = CamelUi.getInternalRouteUris(this.props.integration, "direct"); + urls.push(...CamelUi.getInternalRouteUris(this.props.integration, "seda")); + if (urls && urls.length > 0) { + selectOptions.push(...urls.map((value: string) => + <SelectOption key={value} value={value.trim()}/>)); + } + return ( + <Select + placeholderText="Select or type an URI" + variant={SelectVariant.typeahead} + aria-label={property.name} + onToggle={isExpanded => { + this.openSelect(property.name) + }} + onSelect={(e, value, isPlaceholder) => { + const url = value.toString().split(":"); + const newRoute = !urls.includes(value.toString()) ? new RouteToCreate(url[0], url[1]) : undefined; + this.propertyChanged(property.name, (!isPlaceholder ? value : undefined), newRoute) + }} + selections={value} + isOpen={this.isSelectOpen(property.name)} + isCreatable={true} + isInputFilterPersisted={true} + aria-labelledby={property.name} + direction={SelectDirection.down} + > + {selectOptions} + </Select> + ) + } + getMultiValueObjectField = (property: PropertyMeta, value: any) => { return ( <div> @@ -269,6 +317,7 @@ export class DslPropertyField extends React.Component<Props, State> { <ComponentParameterField key={kp.name} property={kp} + element={this.props.element} integration={this.props.integration} value={CamelDefinitionApiExt.getParametersValue(this.props.element, kp.name, kp.kind === 'path')} onParameterChange={this.props.onParameterChange} @@ -341,7 +390,11 @@ export class DslPropertyField extends React.Component<Props, State> { && this.getMultiValueObjectField(property, value)} {property.name === 'expression' && property.type === "string" && !property.isArray && this.getTextArea(property, value)} - {['string', 'duration', 'integer', 'number'].includes(property.type) && property.name !== 'expression' && !property.name.endsWith("Ref") && !property.isArray && !property.enumVals + {this.canBeInternalUri(property, this.props.element) + && this.getInternalUriSelect(property, value)} + {['string', 'duration', 'integer', 'number'].includes(property.type) && property.name !== 'expression' && !property.name.endsWith("Ref") + && !property.isArray && !property.enumVals + && !this.canBeInternalUri(property, this.props.element) && this.getTextField(property, value)} {['string'].includes(property.type) && property.name.endsWith("Ref") && !property.isArray && !property.enumVals && this.getSelectBean(property, value)} diff --git a/karavan-designer/src/designer/utils/CamelUi.ts b/karavan-designer/src/designer/utils/CamelUi.ts index 3b63f6e..bfbd43d 100644 --- a/karavan-designer/src/designer/utils/CamelUi.ts +++ b/karavan-designer/src/designer/utils/CamelUi.ts @@ -19,7 +19,7 @@ import {KameletModel, Property} from "karavan-core/lib/model/KameletModels"; import {DslMetaModel} from "./DslMetaModel"; import {ComponentApi} from "karavan-core/lib/api/ComponentApi"; import {ComponentProperty} from "karavan-core/lib/model/ComponentModels"; -import {CamelMetadataApi} from "karavan-core/lib/model/CamelMetadata"; +import {CamelMetadataApi, PropertyMeta} from "karavan-core/lib/model/CamelMetadata"; import {CamelUtil} from "karavan-core/lib/api/CamelUtil"; import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt"; import {KameletDefinition, NamedBeanDefinition, RouteDefinition, SagaDefinition} from "karavan-core/lib/model/CamelDefinition"; @@ -65,6 +65,16 @@ export const camelIcon = export const externalIcon = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32px' height='32px' viewBox='0 0 32 32' id='icon'%3E%3Cdefs%3E%3Cstyle%3E.cls-1%7Bfill:none;%7D%3C/style%3E%3C/defs%3E%3Ctitle%3Efog%3C/title%3E%3Cpath d='M25.8289,13.1155A10.02,10.02,0,0,0,16,5.0005V7a8.0233,8.0233,0,0,1,7.8649,6.4934l.2591,1.346,1.3488.2441A5.5019,5.5019,0,0,1,24.5076,26H16v2h8.5076a7.5019,7.5019,0,0,0,1.3213-14.8845Z'/%3E%3Crect x='8' y='24' width='6' height='2'/%3E%3Crect x='4' y='24' width='2' [...] +export class RouteToCreate { + componentName: string = '' + name: string = '' + + constructor(componentName: string, name: string) { + this.componentName = componentName; + this.name = name; + } +} + export class CamelUi { static getSelectorModelTypes = (parentDsl: string | undefined, showSteps: boolean = true): string[] => { @@ -220,6 +230,17 @@ export class CamelUi { } } + static getInternalRouteUris = (integration: Integration, componentName: string, showComponentName: boolean = true): string[] => { + const result:string[] = []; + integration.spec.flows?.filter(f => f.dslName === 'RouteDefinition') + .filter((r: RouteDefinition) => r.from.uri.startsWith(componentName)) + .forEach((r: RouteDefinition) => { + if (showComponentName) result.push(r.from.uri) + else result.push(r.from.uri.replace(componentName+":", "")); + }); + return result; + } + static getKameletProperties = (element: any): Property[] => { const kamelet = CamelUi.getKamelet(element) return kamelet