From 8807c3efd6761b62a764c7673c8472c2079bb869 Mon Sep 17 00:00:00 2001 From: Marat Gubaidullin Date: Fri, 20 Oct 2023 12:57:10 -0400 Subject: [PATCH] Topology creates edge between consumer-producer --- .../src/core/api/CamelDefinitionApiExt.ts | 6 +- karavan-core/src/core/api/CamelUtil.ts | 16 ++-- karavan-core/src/core/api/TopologyUtils.ts | 62 +++++++++++++++- .../src/core/model/TopologyDefinition.ts | 18 ++++- .../public/example/demo.camel.yaml | 74 ++++++++++++++++--- karavan-designer/src/topology/CustomNode.tsx | 7 +- karavan-designer/src/topology/TopologyApi.tsx | 31 ++++++-- karavan-designer/src/topology/TopologyTab.tsx | 1 - 8 files changed, 177 insertions(+), 38 deletions(-) diff --git a/karavan-core/src/core/api/CamelDefinitionApiExt.ts b/karavan-core/src/core/api/CamelDefinitionApiExt.ts index 6ec8ffe5949..f9372830fc9 100644 --- a/karavan-core/src/core/api/CamelDefinitionApiExt.ts +++ b/karavan-core/src/core/api/CamelDefinitionApiExt.ts @@ -690,11 +690,7 @@ export class CamelDefinitionApiExt { return []; }; - static getParametersValue = ( - element: CamelElement | undefined, - propertyName: string, - pathParameter?: boolean, - ): any => { + static getParametersValue = (element: CamelElement | undefined, propertyName: string, pathParameter?: boolean): any => { if (element && (element as any).parameters) { return (element as any).parameters[propertyName]; } diff --git a/karavan-core/src/core/api/CamelUtil.ts b/karavan-core/src/core/api/CamelUtil.ts index 7178150f560..9354426322b 100644 --- a/karavan-core/src/core/api/CamelUtil.ts +++ b/karavan-core/src/core/api/CamelUtil.ts @@ -159,9 +159,15 @@ export class CamelUtil { } }; - static getKameletProperties = (element: any): Property[] => { + static getKameletProperties = (element: any, requiredOnly: boolean = false): Property[] => { const kamelet = CamelUtil.getKamelet(element); - return kamelet ? KameletApi.getKameletProperties(kamelet?.metadata.name) : []; + const props:Property[] = kamelet ? KameletApi.getKameletProperties(kamelet?.metadata.name) : []; + if (requiredOnly) { + const required = kamelet?.spec.definition.required; + return props.filter(value => required?.includes(value.id)); + } else { + return props; + } }; static getKameletRequiredParameters = (element: any): string[] => { @@ -229,11 +235,7 @@ export class CamelUtil { if (!CamelUtil.isKameletComponent(element)) { const requiredProperties = CamelUtil.getComponentProperties(element).filter(p => p.required); for (const property of requiredProperties) { - const value = CamelDefinitionApiExt.getParametersValue( - element, - property.name, - property.kind === 'path', - ); + const value = CamelDefinitionApiExt.getParametersValue(element, property.name, property.kind === 'path'); if (value === undefined || (property.type === 'string' && value.trim().length === 0)) { result[0] = false; result[1].push(`${property.displayName} is required`); diff --git a/karavan-core/src/core/api/TopologyUtils.ts b/karavan-core/src/core/api/TopologyUtils.ts index 4f99f2959e2..d3ae1671d10 100644 --- a/karavan-core/src/core/api/TopologyUtils.ts +++ b/karavan-core/src/core/api/TopologyUtils.ts @@ -37,6 +37,8 @@ import { import { ComponentApi } from './ComponentApi'; import { CamelDefinitionApiExt } from './CamelDefinitionApiExt'; import { CamelDisplayUtil } from './CamelDisplayUtil'; +import { CamelMetadataApi } from '../model/CamelMetadata'; +import { CamelUtil } from './CamelUtil'; const outgoingDefinitions: string[] = ['ToDefinition', 'KameletDefinition', 'ToDynamicDefinition', "PollEnrichDefinition", "EnrichDefinition", "WireTapDefinition", "SagaDefinition"]; @@ -58,6 +60,48 @@ export class TopologyUtils { (TopologyUtils.isComponentInternal(component.component.label) || TopologyUtils.hasInternalUri(element)); } + static getConnectorType = (element: CamelElement): 'component' | 'kamelet' => { + return CamelUtil.isKameletComponent(element) ? 'kamelet' : 'component'; + } + + static cutKameletUriSuffix = (uri: string): string => { + if (uri.endsWith("-sink")) { + return uri.substring(0, uri.length - 5); + } else if (uri.endsWith("-source")) { + return uri.substring(0, uri.length - 7); + } else if (uri.endsWith("-action")) { + return uri.substring(0, uri.length - 7); + } else { + return uri; + } + } + + static getUniqueUri = (element: CamelElement): string => { + const uri:string = (element as any).uri || ''; + let result = uri.startsWith("kamelet") ? TopologyUtils.cutKameletUriSuffix(uri).concat(":") : uri.concat(":"); + const className = element.dslName; + if (className === 'FromDefinition' || className === 'ToDefinition') { + if (!CamelUtil.isKameletComponent(element)) { + const requiredProperties = CamelUtil.getComponentProperties(element).filter(p => p.required); + for (const property of requiredProperties) { + const value = CamelDefinitionApiExt.getParametersValue(element, property.name, property.kind === 'path'); + if (value !== undefined && property.type === 'string' && value.trim().length > 0) { + result = result + property.name + "=" + value + "&"; + } + } + } else { + const requiredProperties = CamelUtil.getKameletProperties(element, true); + for (const property of requiredProperties) { + const value = CamelDefinitionApiExt.getParametersValue(element, property.id); + if (value !== undefined && property.type === 'string' && value.trim().length > 0) { + result = result + property.id + "=" + value + "&"; + } + } + } + } + return result; + } + static isComponentInternal = (label: string): boolean => { const labels = label.split(","); if (labels.includes('core') && ( @@ -143,7 +187,9 @@ export class TopologyUtils { const id = 'incoming-' + r.id; const title = CamelDisplayUtil.getTitle(r.from); const type = TopologyUtils.isElementInternalComponent(r.from) ? 'internal' : 'external'; - return new TopologyIncomingNode(id, type, r.id, title, filename, r.from); + const connectorType = TopologyUtils.getConnectorType(r.from); + const uniqueUri = TopologyUtils.getUniqueUri(r.from); + return new TopologyIncomingNode(id, type, connectorType, r.id, title, filename, r.from, uniqueUri); }) || []; result.push(...routeElements) }) @@ -177,7 +223,9 @@ export class TopologyUtils { const id = 'outgoing-' + route.id + '-' + e.id; const title = CamelDisplayUtil.getTitle(e); const type = TopologyUtils.isElementInternalComponent(e) ? 'internal' : 'external'; - result.push(new TopologyOutgoingNode(id, type, route.id, title, filename, e)); + const connectorType = TopologyUtils.getConnectorType(e); + const uniqueUri = TopologyUtils.getUniqueUri(e); + result.push(new TopologyOutgoingNode(id, type, connectorType, route.id, title, filename, e, uniqueUri)); }) }) @@ -213,8 +261,6 @@ export class TopologyUtils { return result; } - - static getNodeIdByUriAndName(tins: TopologyIncomingNode[], uri: string, name: string): string | undefined { if (uri && name) { const node = tins @@ -242,6 +288,14 @@ export class TopologyUtils { } } + static getNodeIdByUniqueUri(tins: TopologyIncomingNode[], uniqueUri: string): string | undefined { + const node = tins + .filter(r => r.uniqueUri === uniqueUri).at(0); + if (node) { + return node.id; + } + } + static getRouteIdByUri(tins: TopologyIncomingNode[], uri: string): string | undefined { const parts = uri.split(":"); if (parts.length > 1) { diff --git a/karavan-core/src/core/model/TopologyDefinition.ts b/karavan-core/src/core/model/TopologyDefinition.ts index 9b3cfe40c0d..f238f5b97ce 100644 --- a/karavan-core/src/core/model/TopologyDefinition.ts +++ b/karavan-core/src/core/model/TopologyDefinition.ts @@ -39,18 +39,23 @@ export class TopologyRestNode { export class TopologyIncomingNode { id: string; type: 'internal' | 'external'; + connectorType: 'component' | 'kamelet'; routeId: string; title: string; fileName: string; from: FromDefinition; - - constructor(id: string, type: 'internal' | 'external', routeId: string, title: string, fileName: string, from: FromDefinition) { + uniqueUri?: string; + + + constructor(id: string, type: "internal" | "external", connectorType: "component" | "kamelet", routeId: string, title: string, fileName: string, from: FromDefinition, uniqueUri: string) { this.id = id; this.type = type; + this.connectorType = connectorType; this.routeId = routeId; this.title = title; this.fileName = fileName; this.from = from; + this.uniqueUri = uniqueUri; } } @@ -75,17 +80,22 @@ export class TopologyRouteNode { export class TopologyOutgoingNode { id: string; type: 'internal' | 'external'; + connectorType: 'component' | 'kamelet'; routeId: string; title: string; fileName: string; step: CamelElement; + uniqueUri?: string; - constructor(id: string, type: 'internal' | 'external', routeId: string, title: string, fileName: string, step: CamelElement) { + + constructor(id: string, type: "internal" | "external", connectorType: "component" | "kamelet", routeId: string, title: string, fileName: string, step: CamelElement, uniqueUri: string) { this.id = id; - this.type = type + this.type = type; + this.connectorType = connectorType; this.routeId = routeId; this.title = title; this.fileName = fileName; this.step = step; + this.uniqueUri = uniqueUri; } } \ No newline at end of file diff --git a/karavan-designer/public/example/demo.camel.yaml b/karavan-designer/public/example/demo.camel.yaml index 3c682878b96..69ff1495841 100644 --- a/karavan-designer/public/example/demo.camel.yaml +++ b/karavan-designer/public/example/demo.camel.yaml @@ -1,20 +1,72 @@ - route: - id: route-fbf9 + id: route-605c + from: + uri: activemq + id: from-3131 + parameters: + destinationName: test + steps: + - marshal: + id: marshal-1452 + - log: + message: ${body} + id: log-c8e7 +- route: + id: route-e4bb from: - uri: kamelet:beer-source - id: from-9d5b + uri: kamelet:timer-source + id: from-dd68 + parameters: + message: '1111' steps: - to: uri: activemq - id: to-0fd2 + id: to-4fca + parameters: + destinationName: test +- route: + id: route-0a30 + from: + uri: amqp + id: from-ca7d + parameters: + destinationName: hello + steps: + - log: + message: ${body} + id: log-d0f4 - to: - uri: asterisk - id: to-b9ca + uri: kamelet:kafka-not-secured-sink + id: to-c86f + parameters: + topic: topic1 + bootstrapServers: localhost:9092 +- route: + id: route-e39c + from: + uri: kamelet:kafka-not-secured-source + id: from-d419 + parameters: + bootstrapServers: localhost:9092 + topic: topic1 + steps: - to: - uri: aws2-ec2 - id: to-3c6a + uri: direct + id: to-7401 + parameters: + name: hello - route: - id: route-605c + id: route-be79 from: - uri: activemq - id: from-3131 \ No newline at end of file + uri: kamelet:kafka-not-secured-source + id: from-27e8 + parameters: + bootstrapServers: localhost:9092 + topic: topic2 +- route: + id: hello + from: + uri: direct + id: from-db43 + parameters: + name: hello diff --git a/karavan-designer/src/topology/CustomNode.tsx b/karavan-designer/src/topology/CustomNode.tsx index fb517ae93d8..d683d04f916 100644 --- a/karavan-designer/src/topology/CustomNode.tsx +++ b/karavan-designer/src/topology/CustomNode.tsx @@ -43,11 +43,16 @@ function getIcon(data: any) { const CustomNode: React.FC = observer(({ element, ...rest }) => { const data = element.getData(); + const badge:string = data.badge?.substring(0,1).toUpperCase(); return ( {getIcon(data)} diff --git a/karavan-designer/src/topology/TopologyApi.tsx b/karavan-designer/src/topology/TopologyApi.tsx index 44ff57a2c1f..c60c197b2fe 100644 --- a/karavan-designer/src/topology/TopologyApi.tsx +++ b/karavan-designer/src/topology/TopologyApi.tsx @@ -38,9 +38,9 @@ import { TopologyRestNode, TopologyRouteNode } from "karavan-core/lib/model/TopologyDefinition"; -import CustomGroup from "./CustomGroup"; import CustomEdge from "./CustomEdge"; import {IntegrationFile} from "./TopologyStore"; +import CustomGroup from "./CustomGroup"; const NODE_DIAMETER = 60; @@ -62,7 +62,7 @@ export function getIncomingNodes(tins: TopologyIncomingNode[]): NodeModel[] { status: NodeStatus.default, data: { isAlternate: false, - badge: tin.type, + badge: tin.connectorType, icon: 'element', type: 'step', step: tin.from, @@ -110,7 +110,7 @@ export function getOutgoingNodes(tons: TopologyOutgoingNode[]): NodeModel[] { icon: 'element', type: 'step', step: tin.step, - badge: tin.type, + badge: tin.connectorType, fileName: tin.fileName } } @@ -146,6 +146,26 @@ export function getOutgoingEdges(tons: TopologyOutgoingNode[]): EdgeModel[] { }); } +export function getExternalEdges(tons: TopologyOutgoingNode[], tins: TopologyIncomingNode[]): EdgeModel[] { + const result: EdgeModel[]= []; + tons.filter(ton => ton.type === 'external').forEach((ton, index) => { + const uniqueUri = ton.uniqueUri; + if (uniqueUri) { + const target = TopologyUtils.getNodeIdByUniqueUri(tins, uniqueUri); + const node: EdgeModel = { + id: 'external-' + ton.id + '-' + index, + type: 'edge', + source: ton.id, + target: target, + edgeStyle: EdgeStyle.dotted, + animationSpeed: EdgeAnimationSpeed.slow + } + if (target) result.push(node); + } + }); + return result; +} + export function getRestNodes(tins: TopologyRestNode[]): NodeModel[] { return tins.map(tin => { return { @@ -217,8 +237,8 @@ export function getModel(files: IntegrationFile[]): Model { const nodes: NodeModel[] = []; const groups: NodeModel[] = troutes.map(r => { const children = [r.id] - children.push(... tins.filter(i => i.routeId === r.routeId && i.type === 'external').map(i => i.id)); - children.push(... tons.filter(i => i.routeId === r.routeId && i.type === 'external').map(i => i.id)); + children.push(...tins.filter(i => i.routeId === r.routeId && i.type === 'external').map(i => i.id)); + children.push(...tons.filter(i => i.routeId === r.routeId && i.type === 'external').map(i => i.id)); return { id: 'group-' + r.routeId, children: children, @@ -242,6 +262,7 @@ export function getModel(files: IntegrationFile[]): Model { edges.push(...getOutgoingEdges(tons)); edges.push(...getRestEdges(trestns, tins)); edges.push(...getInternalEdges(tons, tins)); + edges.push(...getExternalEdges(tons,tins)); return {nodes: nodes, edges: edges, graph: {id: 'g1', type: 'graph', layout: 'Dagre'}}; } diff --git a/karavan-designer/src/topology/TopologyTab.tsx b/karavan-designer/src/topology/TopologyTab.tsx index 306ca8fb6e3..520bf8d22a7 100644 --- a/karavan-designer/src/topology/TopologyTab.tsx +++ b/karavan-designer/src/topology/TopologyTab.tsx @@ -68,7 +68,6 @@ export function TopologyTab (props: Props) { } const controller = React.useMemo(() => { - console.log(props.files) const model = getModel(props.files); const newController = new Visualization(); newController.registerLayoutFactory((_, graph) => new DagreLayout(graph));