From 9ffaddb43e9038bce4d57e65aafd3236aeb63ae9 Mon Sep 17 00:00:00 2001 From: ByteBaker <42913098+ByteBaker@users.noreply.github.com> Date: Fri, 30 Jan 2026 11:03:34 +0530 Subject: [PATCH] fix: use edge-specific metrics in service graph tree view - Calculate tree node metrics from edges instead of global node totals - Add Connections field to tooltips - Reposition labels (left for horizontal, top for vertical) Fixes bidirectional edges showing incorrect request counts. --- web/src/utils/traces/convertTraceData.ts | 43 +++++++++++++++++------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/web/src/utils/traces/convertTraceData.ts b/web/src/utils/traces/convertTraceData.ts index 34d16e786f..b0c3ebe710 100644 --- a/web/src/utils/traces/convertTraceData.ts +++ b/web/src/utils/traces/convertTraceData.ts @@ -232,7 +232,8 @@ export const convertServiceGraphToTree = ( const globalVisited = new Set(); // Helper to build tree recursively - const buildTree = (nodeId: string, visited = new Set()): any => { + // incomingEdge: the edge that led to this node (for direction-aware metrics) + const buildTree = (nodeId: string, visited = new Set(), incomingEdge: any = null): any => { if (visited.has(nodeId)) return null; // Prevent cycles visited.add(nodeId); globalVisited.add(nodeId); @@ -242,14 +243,29 @@ export const convertServiceGraphToTree = ( const outgoingEdges = edgesMap.get(nodeId) || []; const children = outgoingEdges - .map((edge: any) => buildTree(edge.to, new Set(visited))) + .map((edge: any) => buildTree(edge.to, new Set(visited), edge)) .filter((child: any) => child !== null); - // Calculate node metrics - use node's own request count, not just outgoing edges - // This ensures leaf nodes show their actual traffic instead of 0 - const totalRequests = node.requests || 0; - const failedRequests = node.errors || 0; - const errorRate = node.error_rate || 0; + // Calculate node metrics - direction-aware based on tree position + let totalRequests: number; + let failedRequests: number; + let errorRate: number; + + if (incomingEdge) { + // Child node: use the incoming edge's metrics (traffic via this specific edge) + totalRequests = incomingEdge.total_requests ?? 0; + failedRequests = incomingEdge.failed_requests ?? 0; + errorRate = incomingEdge.error_rate ?? 0; + } else { + // Root node: sum of all outgoing edges (traffic flowing from this node) + totalRequests = outgoingEdges.reduce((sum: number, edge: any) => sum + (edge.total_requests ?? 0), 0); + failedRequests = outgoingEdges.reduce((sum: number, edge: any) => sum + (edge.failed_requests ?? 0), 0); + errorRate = totalRequests > 0 ? (failedRequests / totalRequests) * 100 : 0; + } + + // Calculate connections count (incoming + outgoing edges) + const incomingEdges = incomingEdgesMap.get(nodeId) || []; + const connectionCount = incomingEdges.length + outgoingEdges.length; // Determine node color based on error rate let nodeColor = "#4CAF50"; // Green for healthy @@ -268,7 +284,7 @@ export const convertServiceGraphToTree = ( }, label: { show: true, - position: layoutType === 'vertical' ? 'bottom' : 'right', + position: layoutType === 'vertical' ? 'top' : 'left', formatter: (params: any) => { return `${params.name}\n${formatNumber(totalRequests)} req`; }, @@ -279,7 +295,8 @@ export const convertServiceGraphToTree = ( ${params.name}
Requests: ${formatNumber(totalRequests)}
Errors: ${failedRequests}
- Error Rate: ${errorRate.toFixed(2)}% + Error Rate: ${errorRate.toFixed(2)}%
+ Connections: ${connectionCount} `; }, }, @@ -358,16 +375,16 @@ export const convertServiceGraphToTree = ( symbol: 'circle', symbolSize: 20, label: { - position: layoutType === 'vertical' ? 'bottom' : 'right', - verticalAlign: layoutType === 'vertical' ? 'top' : 'middle', + position: layoutType === 'vertical' ? 'top' : 'left', + verticalAlign: layoutType === 'vertical' ? 'bottom' : 'middle', distance: 15, fontSize: 12, rotate: 0, // Keep text horizontal, no rotation }, leaves: { label: { - position: layoutType === 'vertical' ? 'bottom' : 'right', - verticalAlign: layoutType === 'vertical' ? 'top' : 'middle', + position: layoutType === 'vertical' ? 'top' : 'left', + verticalAlign: layoutType === 'vertical' ? 'bottom' : 'middle', distance: 15, rotate: 0, // Keep text horizontal, no rotation },