@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
JavaScript
;
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