@@ -8,11 +8,45 @@ export class FlowService {
88 static nodes : CanvasNode [ ] = [ ] ;
99 static edges : CanvasEdge [ ] = [ ] ;
1010 private static visitedNodes : string [ ] = [ ] ;
11+ private static consumersByEndpoint : Map < string , string [ ] > = new Map ( ) ;
12+ private static outgoingByEntity : Map < string , string [ ] > = new Map ( ) ;
13+ private static PRODUCER_KEYS = [ 'to' , 'toD' , 'wireTap' , 'enrich' , 'pollEnrich' ] as const ;
14+ private static IN_VM_ENDPOINT_PREFIXES = [ 'direct:' , 'seda:' , 'vm:' , 'direct-vm:' ] ;
15+
16+ private static normalizeEndpoint = ( uri : string | undefined ) : string | undefined => {
17+ if ( ! uri ) {
18+ return undefined ;
19+ }
20+ const stripped = uri . split ( '?' ) [ 0 ] ;
21+ return this . IN_VM_ENDPOINT_PREFIXES . some ( ( p ) => stripped . startsWith ( p ) ) ? stripped : undefined ;
22+ } ;
23+
24+ private static getUri = ( value : unknown ) : string | undefined => {
25+ if ( typeof value === 'string' ) {
26+ return value ;
27+ }
28+ if ( value && typeof value === 'object' ) {
29+ const obj = value as { uri ?: unknown ; parameters ?: { name ?: unknown } } ;
30+ const rawUri = obj . uri ;
31+ if ( typeof rawUri === 'string' ) {
32+ // Camel allows `uri: direct` + `parameters.name: foo` as an equivalent of `uri: direct:foo`.
33+ // Compose the canonical form here so endpoint matching works for both spellings.
34+ if ( ! rawUri . includes ( ':' ) ) {
35+ const name = obj . parameters ?. name ;
36+ if ( typeof name === 'string' ) {
37+ return `${ rawUri } :${ name } ` ;
38+ }
39+ }
40+ return rawUri ;
41+ }
42+ }
43+ return undefined ;
44+ } ;
1145
1246 static getFlowDiagram (
1347 scope : string ,
1448 vizNode : IVisualizationNode ,
15- options : { removePlaceholder ?: boolean } = { } ,
49+ options : { removePlaceholder ?: boolean ; isTopologyView ?: boolean } = { } ,
1650 ) : CanvasNodesAndEdges {
1751 this . nodes = [ ] ;
1852 this . edges = [ ] ;
@@ -37,7 +71,7 @@ export class FlowService {
3771 /** Method for iterating over all the IVisualizationNode and its children using a depth-first algorithm */
3872 private static appendNodesAndEdges (
3973 vizNodeParam : IVisualizationNode ,
40- options : { removePlaceholder ?: boolean } = { } ,
74+ options : { removePlaceholder ?: boolean ; isTopologyView ?: boolean } = { } ,
4175 ) : void {
4276 const removePlaceholder = options . removePlaceholder ?? false ;
4377 if ( this . visitedNodes . includes ( vizNodeParam . id ) || ( removePlaceholder && vizNodeParam . data . isPlaceholder ) ) {
@@ -77,6 +111,81 @@ export class FlowService {
77111 this . edges . push ( ...this . getEdgesFromVizNode ( vizNodeParam , options ) ) ;
78112 }
79113
114+ private static appendTopologyNodesAndEdges (
115+ vizNodeParam : IVisualizationNode ,
116+ options : { isTopologyView ?: boolean } = { } ,
117+ ) : void {
118+ let node : CanvasNode ;
119+
120+ let children = vizNodeParam . getChildren ( ) ?? [ ] ;
121+ children = children . filter ( ( child ) => ! child . data . isPlaceholder ) ;
122+ const hasRealChildren = children . length > 0 ;
123+
124+ if ( vizNodeParam . data . isGroup && vizNodeParam . getParentNode ( ) === undefined ) {
125+ node = this . getTopologyNode ( vizNodeParam ) ;
126+ /** Add node */
127+ this . nodes . push ( node ) ;
128+ this . visitedNodes . push ( node . id ) ;
129+ }
130+
131+ if ( vizNodeParam . data . isGroup && hasRealChildren ) {
132+ children . forEach ( ( child ) => {
133+ this . appendTopologyNodesAndEdges ( child , options ) ;
134+ } ) ;
135+ } else {
136+ const processorName = vizNodeParam . data . processorName ;
137+ const def = vizNodeParam . getNodeDefinition ( ) ;
138+ if ( processorName === 'from' ) {
139+ const endpoint = this . normalizeEndpoint ( this . getUri ( def ) ) ;
140+ if ( endpoint ) this . consumersByEndpoint . set ( endpoint , [ vizNodeParam . getId ( ) ?? vizNodeParam . id ] ) ;
141+ }
142+ if ( this . PRODUCER_KEYS . includes ( processorName as ( typeof this . PRODUCER_KEYS ) [ number ] ) ) {
143+ const rawUri = this . getUri ( def ) ;
144+ const endpoint = this . normalizeEndpoint ( rawUri ) ;
145+ if ( endpoint ) {
146+ const producerId = vizNodeParam . getId ( ) ?? vizNodeParam . id ;
147+ const existingEndpoints = this . outgoingByEntity . get ( producerId ) ?? [ ] ;
148+ this . outgoingByEntity . set ( producerId , [ ...existingEndpoints , endpoint ] ) ;
149+ }
150+ }
151+ }
152+ }
153+
154+ static getTopologyFlowDiagram ( vizNodes : IVisualizationNode [ ] ) : CanvasNodesAndEdges {
155+ this . nodes = [ ] ;
156+ this . edges = [ ] ;
157+ this . visitedNodes = [ ] ;
158+
159+ vizNodes . forEach ( ( vizNode ) => {
160+ this . appendTopologyNodesAndEdges ( vizNode , { isTopologyView : true } ) ;
161+ } ) ;
162+
163+ this . outgoingByEntity . forEach ( ( endpoints , producerId ) => {
164+ const producerTop = this . nodes . map ( ( node ) => node . id . split ( '|' ) [ 0 ] ) . includes ( producerId ) ;
165+ if ( ! producerTop ) {
166+ return ;
167+ }
168+ endpoints . forEach ( ( endpoint ) => {
169+ const consumerIds = this . consumersByEndpoint . get ( endpoint ) ;
170+
171+ // The endpoint is consumed somewhere in this file (possibly only by the producer itself).
172+ // Don't treat it as external; emit edges to any non-self consumer.
173+ if ( consumerIds && consumerIds . length > 0 ) {
174+ consumerIds . forEach ( ( consumerId ) => {
175+ if ( consumerId === producerId ) {
176+ return ;
177+ }
178+ if ( this . nodes . map ( ( node ) => node . id . split ( '|' ) [ 0 ] ) . includes ( consumerId ) ) {
179+ this . edges . push ( this . getEdge ( producerId , consumerId , true ) ) ;
180+ }
181+ } ) ;
182+ }
183+ } ) ;
184+ } ) ;
185+
186+ return { nodes : this . nodes , edges : this . edges } ;
187+ }
188+
80189 private static getCanvasNode ( vizNodeParam : IVisualizationNode ) : CanvasNode {
81190 /** Join the parent if exist to form a group */
82191 const parentNode =
@@ -158,10 +267,22 @@ export class FlowService {
158267 } ;
159268 }
160269
161- private static getEdge ( source : string , target : string ) : CanvasEdge {
270+ private static getTopologyNode ( vizNodeParam : IVisualizationNode ) : CanvasNode {
271+ return {
272+ id : vizNodeParam . getId ( ) ?? vizNodeParam . id ,
273+ type : 'topology-node' ,
274+ label : vizNodeParam . getId ( ) ,
275+ data : { vizNode : vizNodeParam } ,
276+ width : CanvasDefaults . DEFAULT_NODE_WIDTH ,
277+ height : CanvasDefaults . DEFAULT_NODE_HEIGHT ,
278+ shape : CanvasDefaults . DEFAULT_NODE_SHAPE ,
279+ } ;
280+ }
281+
282+ private static getEdge ( source : string , target : string , isTopologyEdge ?: boolean ) : CanvasEdge {
162283 return {
163284 id : `${ source } >>> ${ target } ` ,
164- type : 'edge' ,
285+ type : isTopologyEdge ? 'topology-edge' : 'edge' ,
165286 source,
166287 target,
167288 edgeStyle : EdgeStyle . solid ,
0 commit comments