karavan-vscode/webview/topology/TopologyApi.tsx (315 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { ComponentFactory, EdgeAnimationSpeed, EdgeModel, EdgeStyle, GraphComponent, Model, ModelKind, NodeModel, NodeShape, NodeStatus, withPanZoom, withSelection } from '@patternfly/react-topology'; import CustomNode from "./CustomNode"; import {Integration, IntegrationFile} from "core/model/IntegrationDefinition"; import {CamelDefinitionYaml} from "core/api/CamelDefinitionYaml"; import {TopologyUtils} from "core/api/TopologyUtils"; import {TopologyIncomingNode, TopologyOutgoingNode, TopologyRestNode, TopologyRouteConfigurationNode, TopologyRouteNode} from "core/model/TopologyDefinition"; import CustomEdge from "./CustomEdge"; import CustomGroup from "./CustomGroup"; import {INTERNAL_COMPONENTS} from "core/api/ComponentApi"; const NODE_DIAMETER = 60; export function getIntegrations(files: IntegrationFile[]): Integration[] { return files.filter((file) => file.name.endsWith(".camel.yaml")).map((file) => { return CamelDefinitionYaml.yamlToIntegration(file.name, file.code); }) } export function getIncomingNodes(tins: TopologyIncomingNode[]): NodeModel[] { return tins.filter(tin => tin.type === 'external').map(tin => { return { id: tin.id, type: 'node', label: tin.title, width: NODE_DIAMETER, height: NODE_DIAMETER, shape: NodeShape.ellipse, status: NodeStatus.default, data: { isAlternate: false, badge: tin.connectorType, icon: 'element', type: 'step', step: tin.from, fileName: tin.fileName } } }); } export function getRoutes(tins: TopologyRouteNode[]): NodeModel[] { return tins.map(tin => { const node: NodeModel = { id: tin.id, type: 'node', label: tin.title, width: NODE_DIAMETER, height: NODE_DIAMETER, shape: NodeShape.rect, status: NodeStatus.default, data: { isAlternate: false, badge: tin.templateId !== undefined ? 'RT' : 'R', type: 'route', icon: 'route', step: tin.route, routeId: tin.routeId, fileName: tin.fileName, templateId: tin.templateId, templateTitle: tin.templateTitle, } } return node; }); } export function getRouteConfigurations(trcs: TopologyRouteConfigurationNode[]): NodeModel[] { return trcs.map(tin => { const node: NodeModel = { id: tin.id, type: 'node', label: tin.title, width: NODE_DIAMETER, height: NODE_DIAMETER, shape: NodeShape.rect, status: NodeStatus.default, data: { isAlternate: false, type: 'routeConfiguration', icon: 'routeConfiguration', step: tin.routeConfiguration, routeConfigurationId: tin.routeConfigurationId, fileName: tin.fileName, } } return node; }); } export function getOutgoingNodes(tons: TopologyOutgoingNode[]): NodeModel[] { return tons.filter(tin => tin.type === 'external').map(tin => { const node: NodeModel = { id: tin.id, type: 'node', label: tin.title, width: NODE_DIAMETER, height: NODE_DIAMETER, shape: NodeShape.ellipse, status: NodeStatus.default, data: { isAlternate: false, icon: 'element', type: 'step', step: tin.step, badge: tin.connectorType, fileName: tin.fileName } } return node; }); } export function getIncomingEdges(tins: TopologyIncomingNode[]): EdgeModel[] { return tins.filter(tin => tin.type === 'external').map(tin => { const node: EdgeModel = { id: 'edge-incoming-' + tin.routeId, type: 'edge', source: tin.id, target: 'route-' + tin.routeId, edgeStyle: tin.type === 'external' ? EdgeStyle.dashedMd : EdgeStyle.solid, animationSpeed: tin.type === 'external' ? EdgeAnimationSpeed.medium : EdgeAnimationSpeed.none } return node; }); } export function getOutgoingEdges(tons: TopologyOutgoingNode[]): EdgeModel[] { return tons.filter(tin => tin.type === 'external').map(tin => { const node: EdgeModel = { id: 'edge-outgoing-' + tin.routeId + '-' + (tin.step as any).id, type: 'edge', source: 'route-' + tin.routeId, target: tin.id, edgeStyle: tin.type === 'external' ? EdgeStyle.dashedMd : EdgeStyle.solid, animationSpeed: tin.type === 'external' ? EdgeAnimationSpeed.medium : EdgeAnimationSpeed.none } return node; }); } 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) { TopologyUtils.getNodeIdByUniqueUri(tins, uniqueUri).forEach(target => { const node: EdgeModel = { id: 'external-' + ton.id + '-' + target, type: 'edge', source: ton.id, target: target, edgeStyle: EdgeStyle.dotted, animationSpeed: EdgeAnimationSpeed.medium, data : {groupName: uniqueUri} } result.push(node); }); } }); return result; } export function getRestNodes(tins: TopologyRestNode[]): NodeModel[] { return tins.map(tin => { return { id: tin.id, type: 'node', label: tin.title, width: NODE_DIAMETER, height: NODE_DIAMETER, shape: NodeShape.hexagon, status: NodeStatus.default, data: { isAlternate: false, badge: 'API', icon: 'rest', type: 'rest', step: tin.rest, fileName: tin.fileName, secondaryLabel: tin.title } } }); } export function getRestEdges(rest: TopologyRestNode[], tins: TopologyIncomingNode[]): EdgeModel[] { const result: EdgeModel[] = []; rest.forEach(tin => { tin.uris.forEach((uri, index) => { const target = TopologyUtils.getRouteIdByUri(tins, uri); const node: EdgeModel = { id: 'incoming-' + tin.id + '-' + index, type: 'edge', source: tin.id, target: target, edgeStyle: EdgeStyle.solid, animationSpeed: EdgeAnimationSpeed.medium } if (target) result.push(node); }) }); return result; } export function getInternalEdges(tons: TopologyOutgoingNode[], tins: TopologyIncomingNode[]): EdgeModel[] { const result: EdgeModel[] = []; tons.filter(ton => ton.type === 'internal').forEach((ton, index) => { const step = (ton.step as any); if (step?.dslName === 'DeadLetterChannelDefinition') { const parts = step.deadLetterUri?.split(":"); const uri: string = parts[0]; const name: string = parts[1]; const target = TopologyUtils.getRouteIdByUriAndName(tins, uri, name); const node: EdgeModel = { id: 'internal-' + ton.id + '-' + index, type: 'edge', source: 'route-' + ton.routeId, target: target, edgeStyle: EdgeStyle.solid, animationSpeed: EdgeAnimationSpeed.medium } if (target) result.push(node); } else { const uri: string = (ton.step as any).uri; const component = uri?.split(":")?.[0]; if (INTERNAL_COMPONENTS.includes(component)) { const step = (ton.step as any); const name = step.parameters.name || step.parameters.address; const target = TopologyUtils.getRouteIdByUriAndName(tins, uri, name); const node: EdgeModel = { id: 'internal-' + ton.id + '-' + index, type: 'edge', source: 'route-' + ton.routeId, target: target, edgeStyle: EdgeStyle.solid, animationSpeed: EdgeAnimationSpeed.medium } if (target) result.push(node); } } }); return result; } export function getModel(files: IntegrationFile[], grouping?: boolean): Model { const integrations = getIntegrations(files); const tins = TopologyUtils.findTopologyIncomingNodes(integrations); const troutes = TopologyUtils.findTopologyRouteNodes(integrations); const tons = TopologyUtils.findTopologyRouteOutgoingNodes(integrations); const trestns = TopologyUtils.findTopologyRestNodes(integrations); const trcs = TopologyUtils.findTopologyRouteConfigurationNodes(integrations); const trcons = TopologyUtils.findTopologyRouteConfigurationOutgoingNodes(integrations); const nodes: NodeModel[] = []; nodes.push(...getRestNodes(trestns)) nodes.push(...getIncomingNodes(tins)) nodes.push(...getRoutes(troutes)) nodes.push(...getRouteConfigurations(trcs)) nodes.push(...getOutgoingNodes(tons)) nodes.push(...getOutgoingNodes(trcons)) const edges: EdgeModel[] = []; edges.push(...getIncomingEdges(tins)); edges.push(...getOutgoingEdges(tons)); edges.push(...getRestEdges(trestns, tins)); edges.push(...getInternalEdges(tons, tins)); edges.push(...getInternalEdges(trcons, tins)); // Groups const groups: NodeModel[] = []; if (grouping === true) { const children1: string[] = []; children1.push(...tins.filter(i => i.type === 'external').map(i => i.id)); children1.push(...trestns.map(i => i.id)); groups.push({ id: 'consumer-group', children: children1, type: 'group', group: true, label: 'Consumer group', style: { padding: 20, } }) const children2 = [...tons.filter(i => i.type === 'external').map(i => i.id)]; groups.push({ id: 'producer-group', children: children2, type: 'group', group: true, label: 'Producer group', style: { padding: 20, }, }) } else { const externalEdges = getExternalEdges(tons,tins); edges.push(...externalEdges); // const uniqueGroups: Map<string, string[]> = new Map(); // // externalEdges.forEach(edge => { // const groupName = edge.data.groupName; // const children = uniqueGroups.get(groupName) || []; // if (edge.source) children.push(edge.source) // if (edge.target) children.push(edge.target) // uniqueGroups.set(groupName, [...new Set(children)]); // }); // // uniqueGroups.forEach((children, groupName) => { // groups.push({ // id: groupName + '-group', // children: children, // type: 'group', // group: true, // // label: edge.id + ' group', // style: { // padding: 20, // } // }) // }) } nodes.push(...groups) return {nodes: nodes, edges: edges, graph: {id: 'g1', type: 'graph', layout: 'Dagre'}}; } export const customComponentFactory: ComponentFactory = (kind: ModelKind, type: string) => { switch (type) { case 'group': return withSelection()(CustomGroup); default: switch (kind) { case ModelKind.graph: return withPanZoom()(GraphComponent); case ModelKind.node: return (withSelection()(CustomNode)); case ModelKind.edge: return (withSelection()(CustomEdge)); default: return undefined; } } }