UNPKG

@zendesk/retrace

Version:

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

272 lines 13.5 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const vitest_1 = require("vitest"); const trace_flon2op03qt_ticket_activated_json_1 = __importDefault(require("../__fixtures__/trace-flon2op03qt-ticket.activated.json")); const mapOperationForVisualization_1 = require("../mapOperationForVisualization"); const buildSpanHierarchy_1 = require("./buildSpanHierarchy"); // Mock span data for testing const createMockSpan = (id, parentSpanId, startTime = 0, duration = 100) => ({ span: { id, type: 'measure', name: `span-${id}`, startTime: { now: startTime }, duration, attributes: {}, parentSpanId, }, annotation: { id: `annotation-${id}`, occurrence: 1, operationRelativeStartTime: startTime, operationRelativeEndTime: startTime + duration, }, groupName: `group-${id}`, type: 'measure', }); (0, vitest_1.describe)('buildSpanHierarchy', () => { (0, vitest_1.it)('should build a simple parent-child hierarchy', () => { const spans = [ createMockSpan('parent'), createMockSpan('child1', 'parent'), createMockSpan('child2', 'parent'), ]; const result = (0, buildSpanHierarchy_1.buildSpanHierarchy)(spans); (0, vitest_1.expect)(result).toHaveLength(1); (0, vitest_1.assert)(result[0]); (0, vitest_1.expect)(result[0].span.id).toBe('parent'); (0, vitest_1.expect)(result[0].children).toHaveLength(2); (0, vitest_1.assert)(result[0].children[0]); (0, vitest_1.assert)(result[0].children[1]); (0, vitest_1.expect)(result[0].children[0].span.id).toBe('child1'); (0, vitest_1.expect)(result[0].children[1].span.id).toBe('child2'); (0, vitest_1.expect)(result[0].depth).toBe(0); (0, vitest_1.expect)(result[0].children[0].depth).toBe(1); (0, vitest_1.expect)(result[0].children[1].depth).toBe(1); }); (0, vitest_1.it)('should handle nested hierarchies', () => { const spans = [ createMockSpan('root'), createMockSpan('level1', 'root'), createMockSpan('level2', 'level1'), createMockSpan('level3', 'level2'), ]; const result = (0, buildSpanHierarchy_1.buildSpanHierarchy)(spans); (0, vitest_1.expect)(result).toHaveLength(1); (0, vitest_1.expect)(result[0]?.span.id).toBe('root'); (0, vitest_1.expect)(result[0]?.depth).toBe(0); (0, vitest_1.expect)(result[0]?.children[0]?.span.id).toBe('level1'); (0, vitest_1.expect)(result[0]?.children[0]?.depth).toBe(1); (0, vitest_1.expect)(result[0]?.children[0]?.children[0]?.span.id).toBe('level2'); (0, vitest_1.expect)(result[0]?.children[0]?.children[0]?.depth).toBe(2); (0, vitest_1.expect)(result[0]?.children[0]?.children[0]?.children[0]?.span.id).toBe('level3'); (0, vitest_1.expect)(result[0]?.children[0]?.children[0]?.children[0]?.depth).toBe(3); }); (0, vitest_1.it)('should handle multiple root spans', () => { const spans = [ createMockSpan('root1'), createMockSpan('root2'), createMockSpan('child1', 'root1'), createMockSpan('child2', 'root2'), ]; const result = (0, buildSpanHierarchy_1.buildSpanHierarchy)(spans); (0, vitest_1.expect)(result).toHaveLength(2); (0, vitest_1.expect)(result[0]?.span.id).toBe('root1'); (0, vitest_1.expect)(result[1]?.span.id).toBe('root2'); (0, vitest_1.expect)(result[0]?.children).toHaveLength(1); (0, vitest_1.expect)(result[1]?.children).toHaveLength(1); (0, vitest_1.expect)(result[0]?.children[0]?.span.id).toBe('child1'); (0, vitest_1.expect)(result[1]?.children[0]?.span.id).toBe('child2'); }); (0, vitest_1.it)('should handle orphaned spans by adding them as roots', () => { const spans = [ createMockSpan('orphan', 'non-existent-parent'), createMockSpan('root'), ]; const result = (0, buildSpanHierarchy_1.buildSpanHierarchy)(spans); (0, vitest_1.expect)(result).toHaveLength(2); // Should include both the root span and the orphaned span const spanIds = result.map((s) => s.span.id).sort(); (0, vitest_1.expect)(spanIds).toEqual(['orphan', 'root']); }); (0, vitest_1.it)('should sort children by start time', () => { const spans = [ createMockSpan('parent'), createMockSpan('child-late', 'parent', 200), createMockSpan('child-early', 'parent', 100), createMockSpan('child-middle', 'parent', 150), ]; const result = (0, buildSpanHierarchy_1.buildSpanHierarchy)(spans); (0, vitest_1.assert)(result[0]); (0, vitest_1.expect)(result[0].children).toHaveLength(3); (0, vitest_1.expect)(result[0].children[0]?.span.id).toBe('child-early'); (0, vitest_1.expect)(result[0].children[1]?.span.id).toBe('child-middle'); (0, vitest_1.expect)(result[0].children[2]?.span.id).toBe('child-late'); }); (0, vitest_1.it)('should set isExpanded to false by default', () => { const spans = [ createMockSpan('parent'), createMockSpan('child', 'parent'), ]; const result = (0, buildSpanHierarchy_1.buildSpanHierarchy)(spans); (0, vitest_1.expect)(result[0]?.isExpanded).toBe(false); (0, vitest_1.expect)(result[0]?.children[0]?.isExpanded).toBe(false); }); }); (0, vitest_1.describe)('flattenHierarchicalSpans', () => { (0, vitest_1.it)('should flatten expanded spans only', () => { const spans = [ createMockSpan('parent'), createMockSpan('child1', 'parent'), createMockSpan('grandchild', 'child1'), createMockSpan('child2', 'parent'), ]; const hierarchical = (0, buildSpanHierarchy_1.buildSpanHierarchy)(spans); const expandedSpanIds = new Set(['parent']); // Only parent is expanded const result = (0, buildSpanHierarchy_1.flattenHierarchicalSpans)(hierarchical, expandedSpanIds); (0, vitest_1.expect)(result).toHaveLength(3); // parent + 2 children, but not grandchild (0, vitest_1.expect)(result[0]?.span.id).toBe('parent'); (0, vitest_1.expect)(result[1]?.span.id).toBe('child1'); (0, vitest_1.expect)(result[2]?.span.id).toBe('child2'); }); (0, vitest_1.it)('should include deeply nested spans when all parents are expanded', () => { const spans = [ createMockSpan('parent'), createMockSpan('child', 'parent'), createMockSpan('grandchild', 'child'), ]; const hierarchical = (0, buildSpanHierarchy_1.buildSpanHierarchy)(spans); const expandedSpanIds = new Set(['parent', 'child']); // Both levels expanded const result = (0, buildSpanHierarchy_1.flattenHierarchicalSpans)(hierarchical, expandedSpanIds); (0, vitest_1.expect)(result).toHaveLength(3); (0, vitest_1.expect)(result[0]?.span.id).toBe('parent'); (0, vitest_1.expect)(result[1]?.span.id).toBe('child'); (0, vitest_1.expect)(result[2]?.span.id).toBe('grandchild'); }); (0, vitest_1.it)('should only show roots when nothing is expanded', () => { const spans = [ createMockSpan('root1'), createMockSpan('root2'), createMockSpan('child1', 'root1'), createMockSpan('child2', 'root2'), ]; const hierarchical = (0, buildSpanHierarchy_1.buildSpanHierarchy)(spans); const expandedSpanIds = new Set(); // Nothing expanded const result = (0, buildSpanHierarchy_1.flattenHierarchicalSpans)(hierarchical, expandedSpanIds); (0, vitest_1.expect)(result).toHaveLength(2); (0, vitest_1.expect)(result[0]?.span.id).toBe('root1'); (0, vitest_1.expect)(result[1]?.span.id).toBe('root2'); }); }); (0, vitest_1.describe)('validateSpanHierarchy', () => { (0, vitest_1.it)('should validate a correct hierarchy', () => { const spans = [ createMockSpan('parent'), createMockSpan('child', 'parent'), ]; const result = (0, buildSpanHierarchy_1.validateSpanHierarchy)(spans); (0, vitest_1.expect)(result.isValid).toBe(true); (0, vitest_1.expect)(result.errors).toHaveLength(0); }); (0, vitest_1.it)('should detect circular references', () => { const spans = [ createMockSpan('a', 'b'), createMockSpan('b', 'a'), // Circular reference ]; const result = (0, buildSpanHierarchy_1.validateSpanHierarchy)(spans); (0, vitest_1.expect)(result.isValid).toBe(false); (0, vitest_1.expect)(result.errors.some((error) => error.includes('Circular reference'))).toBe(true); }); (0, vitest_1.it)('should detect invalid parent references', () => { const spans = [ createMockSpan('child', 'non-existent-parent'), ]; const result = (0, buildSpanHierarchy_1.validateSpanHierarchy)(spans); (0, vitest_1.expect)(result.isValid).toBe(true); (0, vitest_1.expect)(result.missingParentIds.includes('non-existent-parent')).toBe(true); }); (0, vitest_1.it)('should handle self-referencing spans', () => { const spans = [ createMockSpan('self', 'self'), // Self-reference ]; const result = (0, buildSpanHierarchy_1.validateSpanHierarchy)(spans); (0, vitest_1.expect)(result.isValid).toBe(false); (0, vitest_1.expect)(result.errors.some((error) => error.includes('Circular reference'))).toBe(true); }); }); (0, vitest_1.describe)('getDescendantSpanIds', () => { (0, vitest_1.it)('should return all descendant IDs', () => { const spans = [ createMockSpan('root'), createMockSpan('child1', 'root'), createMockSpan('child2', 'root'), createMockSpan('grandchild', 'child1'), ]; const hierarchical = (0, buildSpanHierarchy_1.buildSpanHierarchy)(spans); const result = (0, buildSpanHierarchy_1.getDescendantSpanIds)(hierarchical[0]); (0, vitest_1.expect)(result).toEqual(['child1', 'grandchild', 'child2']); }); (0, vitest_1.it)('should return empty array for leaf spans', () => { const spans = [createMockSpan('leaf')]; const hierarchical = (0, buildSpanHierarchy_1.buildSpanHierarchy)(spans); const result = (0, buildSpanHierarchy_1.getDescendantSpanIds)(hierarchical[0]); (0, vitest_1.expect)(result).toEqual([]); }); (0, vitest_1.it)('should handle deeply nested hierarchies', () => { const spans = [ createMockSpan('root'), createMockSpan('level1', 'root'), createMockSpan('level2', 'level1'), createMockSpan('level3', 'level2'), ]; const hierarchical = (0, buildSpanHierarchy_1.buildSpanHierarchy)(spans); const result = (0, buildSpanHierarchy_1.getDescendantSpanIds)(hierarchical[0]); (0, vitest_1.expect)(result).toEqual(['level1', 'level2', 'level3']); }); }); (0, vitest_1.describe)('buildSpanHierarchy with real trace data', () => { (0, vitest_1.it)('should build hierarchy for fixture trace data', () => { const mappedOperation = (0, mapOperationForVisualization_1.mapOperationForVisualization)(trace_flon2op03qt_ticket_activated_json_1.default); (0, vitest_1.expect)(mappedOperation).not.toBeNull(); if (!mappedOperation) return; const hierarchicalSpans = (0, buildSpanHierarchy_1.buildSpanHierarchy)(mappedOperation.spans); // Take a snapshot of the hierarchy structure const hierarchySnapshot = hierarchicalSpans.map((span) => ({ spanId: span.span.id, name: span.span.name, type: span.type, depth: span.depth, parentId: span.parentId, childrenCount: span.children.length, children: span.children.map((child) => ({ spanId: child.span.id, name: child.span.name, type: child.type, depth: child.depth, parentId: child.parentId, childrenCount: child.children.length, })), })); (0, vitest_1.expect)(hierarchySnapshot).toMatchSnapshot(); }); (0, vitest_1.it)('should validate hierarchy for fixture trace data', () => { const mappedOperation = (0, mapOperationForVisualization_1.mapOperationForVisualization)(trace_flon2op03qt_ticket_activated_json_1.default); (0, vitest_1.expect)(mappedOperation).not.toBeNull(); if (!mappedOperation) return; const validation = (0, buildSpanHierarchy_1.validateSpanHierarchy)(mappedOperation.spans); // Should be valid or have specific expected errors if (!validation.isValid) { (0, vitest_1.expect)(validation.errors).toMatchSnapshot(); } else { (0, vitest_1.expect)(validation.isValid).toBe(true); (0, vitest_1.expect)(validation.errors).toHaveLength(0); } }); }); //# sourceMappingURL=buildSpanHierarchy.test.js.map