UNPKG

@zendesk/retrace

Version:

define and capture Product Operation Traces along with computed metrics with an optional friendly React beacon API

157 lines 5.48 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.buildSpanHierarchy = buildSpanHierarchy; exports.flattenHierarchicalSpans = flattenHierarchicalSpans; exports.validateSpanHierarchy = validateSpanHierarchy; exports.getDescendantSpanIds = getDescendantSpanIds; /** * Builds a hierarchical tree structure from flat spans using parentSpanId relationships */ function buildSpanHierarchy(spans) { // Create a map for quick lookup by span id const spanMap = new Map(); const rootSpans = []; const orphanedSpans = []; // First pass: Convert all spans to hierarchical format and build lookup map for (const spAnn of spans) { const hierarchicalSpan = { ...spAnn, children: [], isExpanded: false, // Default to collapsed depth: 0, // Will be calculated later parentId: spAnn.span.parentSpanId, }; spanMap.set(spAnn.span.id, hierarchicalSpan); } // Second pass: Build parent-child relationships for (const span of spanMap.values()) { if (span.parentId && spanMap.has(span.parentId)) { // This span has a valid parent const parent = spanMap.get(span.parentId); parent.children.push(span); } else if (span.parentId) { // Parent ID exists but parent span not found - orphaned span orphanedSpans.push(span); } else { // No parent ID - root span rootSpans.push(span); } } // Third pass: Calculate depths and sort children function calculateDepthsAndSort(spansToProcess, depth = 0) { for (const span of spansToProcess) { span.depth = depth; // Sort children by start time span.children.sort((a, b) => a.span.startTime.now - b.span.startTime.now); // Recursively calculate depths for children calculateDepthsAndSort(span.children, depth + 1); } } // Sort root spans by start time rootSpans.sort((a, b) => a.span.startTime.now - b.span.startTime.now); calculateDepthsAndSort(rootSpans); // Handle orphaned spans - add them as root spans with a warning if (orphanedSpans.length > 0) { // console.warn( // `Found ${orphanedSpans.length} orphaned spans with invalid parentSpanId:`, // orphanedSpans.map((s) => ({ id: s.span.id, parentId: s.parentId })), // ) // Add orphaned spans as root spans for (const orphan of orphanedSpans) { orphan.depth = 0; calculateDepthsAndSort([orphan]); } rootSpans.push(...orphanedSpans); } return rootSpans; } /** * Flattens a hierarchical span tree into a flat array, respecting expansion states */ function flattenHierarchicalSpans(hierarchicalSpans, expandedSpanIds) { const result = []; function traverse(spans) { for (const span of spans) { result.push(span); // Only include children if this span is expanded if (span.children.length > 0 && expandedSpanIds.has(span.span.id)) { traverse(span.children); } } } traverse(hierarchicalSpans); return result; } /** * Validates span hierarchy for circular references and other issues */ function validateSpanHierarchy(spans) { const errors = []; const visited = new Set(); const recursionStack = new Set(); // Build parent-child map const childrenMap = new Map(); for (const span of spans) { if (span.span.parentSpanId) { const children = childrenMap.get(span.span.parentSpanId) ?? []; children.push(span.span.id); childrenMap.set(span.span.parentSpanId, children); } } // Check for circular references using DFS function detectCycle(spanId) { if (recursionStack.has(spanId)) { errors.push(`Circular reference detected involving span: ${spanId}`); return true; } if (visited.has(spanId)) { return false; } visited.add(spanId); recursionStack.add(spanId); const children = childrenMap.get(spanId) ?? []; for (const childId of children) { if (detectCycle(childId)) { return true; } } recursionStack.delete(spanId); return false; } // Check all spans for cycles (not just roots, as cycles can exist anywhere) const allSpanIds = new Set(spans.map((s) => s.span.id)); for (const span of spans) { if (!visited.has(span.span.id)) { detectCycle(span.span.id); } } const missingParentIds = []; // Check for invalid parent references for (const span of spans) { if (span.span.parentSpanId && !allSpanIds.has(span.span.parentSpanId)) { missingParentIds.push(span.span.parentSpanId); } } return { isValid: errors.length === 0, errors, missingParentIds, }; } /** * Gets all descendant span IDs for a given span */ function getDescendantSpanIds(span) { const descendants = []; function traverse(currentSpan) { for (const child of currentSpan.children) { descendants.push(child.span.id); traverse(child); } } traverse(span); return descendants; } //# sourceMappingURL=buildSpanHierarchy.js.map