@zendesk/react-measure-timing-hooks
Version:
react hooks for measuring time to interactive and time to render of components
390 lines • 19.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
require("./testUtility/asciiTimelineSerializer");
const vitest_1 = require("vitest");
const makeTimeline_1 = require("./testUtility/makeTimeline");
const processSpans_1 = require("./testUtility/processSpans");
const TraceManager_1 = require("./TraceManager");
(0, vitest_1.describe)('Tracer', () => {
let reportFn;
// TS doesn't like that reportFn is wrapped in Mock<> type
const getReportFn = () => reportFn;
let generateId;
let reportErrorFn;
(0, vitest_1.beforeEach)(() => {
reportFn = vitest_1.vitest.fn();
generateId = vitest_1.vitest.fn().mockReturnValue('trace-id');
reportErrorFn = vitest_1.vitest.fn();
vitest_1.vitest.useFakeTimers({ now: 0 });
});
(0, vitest_1.describe)('variants', () => {
(0, vitest_1.it)('uses additional required spans from variant', () => {
const traceManager = new TraceManager_1.TraceManager({
relationSchemas: { test: { id: String } },
reportFn: getReportFn(),
generateId,
reportErrorFn,
});
const tracer = traceManager.createTracer({
name: 'test.operation',
type: 'operation',
relationSchemaName: 'test',
requiredSpans: [{ name: 'base-required' }],
variants: {
variant_a: {
timeout: 1_000,
additionalRequiredSpans: [{ name: 'extra-required' }],
},
variant_b: {
timeout: 1_000,
},
},
});
// Start trace with variant_a - should require both spans
tracer.start({
relatedTo: { id: '1' },
variant: 'variant_a',
});
// Only see base-required span - should not complete
// prettier-ignore
const { spans: firstSpans } = (0, makeTimeline_1.getSpansFromTimeline) `
Events: ${(0, makeTimeline_1.Render)('start', 0)}-----${(0, makeTimeline_1.Render)('base-required', 0)}-----${makeTimeline_1.Check}
Time: ${0} ${50} ${100}
`;
(0, processSpans_1.processSpans)(firstSpans, traceManager);
(0, vitest_1.expect)(reportFn).not.toHaveBeenCalled();
// See both required spans - should complete
// prettier-ignore
const { spans: secondSpans } = (0, makeTimeline_1.getSpansFromTimeline) `
Events: ${(0, makeTimeline_1.Render)('base-required', 0)}-----${(0, makeTimeline_1.Render)('extra-required', 0)}
Time: ${150} ${200}
`;
(0, processSpans_1.processSpans)(secondSpans, traceManager);
(0, vitest_1.expect)(reportFn).toHaveBeenCalled();
const report = reportFn.mock.calls[0][0];
(0, vitest_1.expect)(report.status).toBe('ok');
(0, vitest_1.expect)(report.duration).toBe(200);
});
(0, vitest_1.it)('uses additional interrupt on spans from variant', () => {
const traceManager = new TraceManager_1.TraceManager({
relationSchemas: { test: { id: String } },
reportFn: getReportFn(),
generateId,
reportErrorFn,
});
const tracer = traceManager.createTracer({
name: 'test.operation',
type: 'operation',
relationSchemaName: 'test',
requiredSpans: [{ name: 'required' }],
debounceWindow: 100,
variants: {
variant_a: {
timeout: 1_000,
additionalInterruptOnSpans: [{ name: 'variant-a-interrupt' }],
},
},
});
tracer.start({
relatedTo: { id: '1' },
variant: 'variant_a',
});
// prettier-ignore
const { spans } = (0, makeTimeline_1.getSpansFromTimeline) `
Events: ${(0, makeTimeline_1.Render)('span', 0)}------${(0, makeTimeline_1.Render)('variant-a-interrupt', 0)}---${(0, makeTimeline_1.Render)('required', 0)}
Time: ${0} ${100} ${250}
`;
(0, processSpans_1.processSpans)(spans, traceManager);
(0, vitest_1.expect)(reportFn).toHaveBeenCalled();
const report = reportFn.mock.calls[0][0];
(0, vitest_1.expect)(report.status).toBe('interrupted');
(0, vitest_1.expect)(report.duration).toBe(null);
});
(0, vitest_1.it)('uses additional debounce spans from variant', () => {
const traceManager = new TraceManager_1.TraceManager({
relationSchemas: { test: { id: String } },
reportFn: getReportFn(),
generateId,
reportErrorFn,
});
const tracer = traceManager.createTracer({
name: 'test.operation',
type: 'operation',
relationSchemaName: 'test',
requiredSpans: [{ name: 'required' }],
debounceOnSpans: [{ name: 'base-debounce' }],
debounceWindow: 100,
variants: {
variant_a: {
timeout: 1_000,
additionalDebounceOnSpans: [{ name: 'extra-debounce' }],
},
},
});
tracer.start({
relatedTo: { id: '1' },
variant: 'variant_a',
});
// prettier-ignore
const { spans } = (0, makeTimeline_1.getSpansFromTimeline) `
Events: ${(0, makeTimeline_1.Render)('required', 0)}---${(0, makeTimeline_1.Render)('base-debounce', 0)}---${(0, makeTimeline_1.Render)('extra-debounce', 0)}---${makeTimeline_1.Check}
Time: ${0} ${50} ${100} ${250}
`;
(0, processSpans_1.processSpans)(spans, traceManager);
(0, vitest_1.expect)(reportFn).toHaveBeenCalled();
const report = reportFn.mock.calls[0][0];
(0, vitest_1.expect)(report.status).toBe('ok');
(0, vitest_1.expect)(report.duration).toBe(100);
});
(0, vitest_1.it)('different variants can have different additional spans', () => {
const traceManager = new TraceManager_1.TraceManager({
relationSchemas: { test: { id: String } },
reportFn: getReportFn(),
generateId,
reportErrorFn,
});
const tracer = traceManager.createTracer({
name: 'test.operation',
type: 'operation',
relationSchemaName: 'test',
requiredSpans: [{ name: 'base-required' }],
variants: {
variant_a: {
timeout: 1_000,
additionalRequiredSpans: [{ name: 'extra-required-a' }],
},
variant_b: {
timeout: 1_000,
additionalRequiredSpans: [{ name: 'extra-required-b' }],
},
},
});
// Start trace with variant_a
tracer.start({
relatedTo: { id: '1' },
variant: 'variant_a',
});
// Complete variant_a requirements
// prettier-ignore
const { spans: variantASpans } = (0, makeTimeline_1.getSpansFromTimeline) `
Events: ${(0, makeTimeline_1.Render)('base-required', 0)}-----${(0, makeTimeline_1.Render)('extra-required-a', 0)}
Time: ${0} ${50}
`;
(0, processSpans_1.processSpans)(variantASpans, traceManager);
(0, vitest_1.expect)(reportFn).toHaveBeenCalled();
(0, vitest_1.expect)(reportFn.mock.calls[0][0].status).toBe('ok');
reportFn.mockClear();
// Start new trace with variant_b
tracer.start({
relatedTo: { id: '1' },
variant: 'variant_b',
});
// Complete variant_b requirements
// prettier-ignore
const { spans: variantBSpans } = (0, makeTimeline_1.getSpansFromTimeline) `
Events: ${(0, makeTimeline_1.Render)('base-required', 0)}-----${(0, makeTimeline_1.Render)('extra-required-b', 0)}
Time: ${100} ${150}
`;
(0, processSpans_1.processSpans)(variantBSpans, traceManager);
(0, vitest_1.expect)(reportFn).toHaveBeenCalled();
(0, vitest_1.expect)(reportFn.mock.calls[0][0].status).toBe('ok');
});
});
(0, vitest_1.describe)('addRequiredSpansToCurrentTrace', () => {
(0, vitest_1.it)('adds required spans to an existing trace', () => {
const traceManager = new TraceManager_1.TraceManager({
relationSchemas: { test: { id: String } },
reportFn: getReportFn(),
generateId,
reportErrorFn,
});
const tracer = traceManager.createTracer({
name: 'test.operation',
type: 'operation',
relationSchemaName: 'test',
requiredSpans: [{ name: 'hello' }, { name: 'initial-required' }],
variants: {
default: { timeout: 1_000 },
},
});
// Start trace
tracer.start({
relatedTo: { id: '1' },
variant: 'default',
});
// @ts-expect-error internal prop
const trace = tracer.traceUtilities.getCurrentTrace();
(0, vitest_1.expect)(trace?.stateMachine.successfullyMatchedRequiredSpanMatchers.size).toBe(0);
(0, vitest_1.expect)(trace?.definition.requiredSpans).toHaveLength(2);
(0, vitest_1.expect)(trace?.definition.interruptOnSpans).toHaveLength(2);
// See initial required span - should not complete yet
// prettier-ignore
const { spans: firstSpans } = (0, makeTimeline_1.getSpansFromTimeline) `
Events: ${(0, makeTimeline_1.Render)('hello', 0)}
Time: ${50}
`;
(0, processSpans_1.processSpans)(firstSpans, traceManager);
(0, vitest_1.expect)(reportFn).not.toHaveBeenCalled();
// Now add an additional required span
tracer.addRequirementsToCurrentTraceOnly({
additionalRequiredSpans: [{ name: 'added-required' }],
});
// @ts-expect-error internal prop
const traceRecreated = tracer.traceUtilities.getCurrentTrace();
(0, vitest_1.expect)(traceRecreated).not.toBe(trace);
(0, vitest_1.expect)(traceRecreated?.definition.requiredSpans).toHaveLength(3);
(0, vitest_1.expect)(traceRecreated?.definition.interruptOnSpans).toHaveLength(3);
// two required spans left:
(0, vitest_1.expect)(traceRecreated?.stateMachine.successfullyMatchedRequiredSpanMatchers
.size).toBe(1);
// See the added required span - now should complete
// prettier-ignore
const { spans: secondSpans } = (0, makeTimeline_1.getSpansFromTimeline) `
Events: ${(0, makeTimeline_1.Render)('initial-required', 50)}----${(0, makeTimeline_1.Render)('added-required', 0)}
Time: ${100} ${150}
`;
(0, processSpans_1.processSpans)(secondSpans, traceManager);
(0, vitest_1.expect)(traceRecreated?.stateMachine.successfullyMatchedRequiredSpanMatchers
.size).toBe(3);
// Verify trace completed
(0, vitest_1.expect)(reportFn).toHaveBeenCalled();
const report = reportFn.mock.calls[0][0];
(0, vitest_1.expect)(report.status).toBe('ok');
(0, vitest_1.expect)(report.duration).toBe(150);
// Verify that previous spans were preserved
const recordedSpanNames = report.entries.map((s) => s.span.name);
(0, vitest_1.expect)(recordedSpanNames).toEqual([
'hello',
'initial-required',
'added-required',
]);
(0, vitest_1.expect)(traceManager.currentTracerContext).toBeUndefined();
});
});
(0, vitest_1.describe)('adding requiredSpans', () => {
(0, vitest_1.it)('adds requiredSpans when starting a trace', () => {
const traceManager = new TraceManager_1.TraceManager({
relationSchemas: { test: { id: String } },
reportFn: getReportFn(),
generateId,
reportErrorFn,
});
const tracer = traceManager.createTracer({
name: 'ticket.basic-operation',
type: 'operation',
relationSchemaName: 'test',
requiredSpans: [{ name: 'orig-end' }],
variants: {
cold_boot: { timeout: 10_000 },
},
});
const traceId = tracer.start({
relatedTo: { id: '1' },
variant: 'cold_boot',
}, {
additionalRequiredSpans: [{ name: 'additional-end' }],
});
(0, vitest_1.expect)(traceId).toBe('trace-id');
// @ts-expect-error internals
const trace = tracer.traceUtilities.getCurrentTrace();
(0, vitest_1.expect)(trace?.definition.requiredSpans).toHaveLength(2);
(0, vitest_1.expect)(trace?.stateMachine.successfullyMatchedRequiredSpanMatchers.size).toBe(0);
(0, vitest_1.expect)(trace?.definition.interruptOnSpans).toHaveLength(2);
// prettier-ignore
const { spans } = (0, makeTimeline_1.getSpansFromTimeline) `
Events: ${(0, makeTimeline_1.Render)('start', 0)}-----${(0, makeTimeline_1.Render)('middle', 0)}-----${(0, makeTimeline_1.Render)('orig-end', 0)}----${(0, makeTimeline_1.Render)('additional-end', 0)}
Time: ${0} ${50} ${100} ${150}
`;
(0, processSpans_1.processSpans)(spans, traceManager);
(0, vitest_1.expect)(reportFn).toHaveBeenCalled();
(0, vitest_1.expect)(trace?.stateMachine.successfullyMatchedRequiredSpanMatchers.size).toBe(2);
const report = reportFn.mock.calls[0][0];
(0, vitest_1.expect)(report.entries.map((spanAndAnnotation) => spanAndAnnotation.span.performanceEntry)).toMatchInlineSnapshot(`
events | start middle orig-end additional-end
timeline | |-<⋯ +50 ⋯>-|-<⋯ +50 ⋯>-|-<⋯ +50 ⋯>-|
time (ms) | 0 50 100 150
`);
(0, vitest_1.expect)(report.name).toBe('ticket.basic-operation');
(0, vitest_1.expect)(report.duration).toBe(150);
(0, vitest_1.expect)(report.status).toBe('ok');
(0, vitest_1.expect)(report.interruptionReason).toBeUndefined();
});
(0, vitest_1.it)('adds requiredSpans when creating a trace, and then again after transitioning to active, and again after start', () => {
const traceManager = new TraceManager_1.TraceManager({
relationSchemas: { test: { id: String } },
reportFn: getReportFn(),
generateId,
reportErrorFn,
});
const tracer = traceManager.createTracer({
name: 'ticket.basic-operation',
type: 'operation',
relationSchemaName: 'test',
requiredSpans: [
function origEnd({ span }) {
return span.name === 'orig-end';
},
],
variants: {
cold_boot: {
timeout: 10_000,
additionalRequiredSpans: [
function variantEnd({ span }) {
return span.name === 'variant-end';
},
],
},
},
});
const traceId = tracer.createDraft({
variant: 'cold_boot',
}, {
additionalRequiredSpans: [
function draftEnd({ span }) {
return span.name === 'draft-end';
},
],
});
tracer.transitionDraftToActive({
relatedTo: { id: '1' },
additionalRequiredSpans: [
function transitionToActiveEnd({ span }) {
return span.name === 'transition-to-active-end';
},
],
});
tracer.addRequirementsToCurrentTraceOnly({
additionalRequiredSpans: [
function activeEnd({ span }) {
return span.name === 'active-end';
},
],
});
// @ts-expect-error internals
const trace = tracer.traceUtilities.getCurrentTrace();
(0, vitest_1.expect)(trace?.definition.requiredSpans).toHaveLength(5);
(0, vitest_1.expect)(trace?.stateMachine.successfullyMatchedRequiredSpanMatchers.size).toBe(0);
(0, vitest_1.expect)(trace?.definition.interruptOnSpans).toHaveLength(5);
// prettier-ignore
const { spans } = (0, makeTimeline_1.getSpansFromTimeline) `
Events: ${(0, makeTimeline_1.Render)('start', 0)}-----${(0, makeTimeline_1.Render)('orig-end', 0)}----${(0, makeTimeline_1.Render)('variant-end', 0)}----${(0, makeTimeline_1.Render)('draft-end', 0)}----${(0, makeTimeline_1.Render)('transition-to-active-end', 0)}---${(0, makeTimeline_1.Render)('active-end', 0)}
Time: ${0} ${50} ${100} ${150} ${200} ${250}
`;
(0, processSpans_1.processSpans)(spans, traceManager);
(0, vitest_1.expect)(reportFn).toHaveBeenCalled();
(0, vitest_1.expect)(trace?.stateMachine.successfullyMatchedRequiredSpanMatchers.size).toBe(5);
const report = reportFn.mock.calls[0][0];
(0, vitest_1.expect)(report.entries.map((spanAndAnnotation) => spanAndAnnotation.span.performanceEntry)).toMatchInlineSnapshot(`
events | active-end
events | start orig-end variant-end draft-end transition-to-active-end
timeline | |-----------|------------|------------|------------|------------|-
time (ms) | 0 50 100 150 200 250
`);
(0, vitest_1.expect)(report.name).toBe('ticket.basic-operation');
(0, vitest_1.expect)(report.duration).toBe(250);
(0, vitest_1.expect)(report.status).toBe('ok');
(0, vitest_1.expect)(report.interruptionReason).toBeUndefined();
});
});
});
//# sourceMappingURL=Tracer.test.js.map