karavan-space/src/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 "karavan-core/lib/model/IntegrationDefinition";
import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml";
import {TopologyUtils} from "karavan-core/lib/api/TopologyUtils";
import {TopologyIncomingNode, TopologyOutgoingNode, TopologyRestNode, TopologyRouteConfigurationNode, TopologyRouteNode} from "karavan-core/lib/model/TopologyDefinition";
import CustomEdge from "./CustomEdge";
import CustomGroup from "./CustomGroup";
import {INTERNAL_COMPONENTS} from "karavan-core/lib/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;
}
}
}