@zendesk/retrace
Version:
define and capture Product Operation Traces along with computed metrics with an optional friendly React beacon API
287 lines (266 loc) • 8.21 kB
text/typescript
import { assert, describe, expect, it } from 'vitest'
import { convertTraceToRUM } from './convertToRum'
import { createTraceRecording } from './recordingComputeUtils'
import type { ActiveTraceInput } from './spanTypes'
import {
createMockSpanAndAnnotation,
createTimestamp,
} from './testUtility/createMockFactory'
import type { TicketIdRelationSchemasFixture } from './testUtility/fixtures/relationSchemas'
import type { CompleteTraceDefinition, MapTypesToSchema } from './types'
describe('convertTraceToRUM', () => {
it('should round all numeric values in the trace recording', () => {
const definition: CompleteTraceDefinition<
'ticket',
TicketIdRelationSchemasFixture,
'origin'
> = {
name: 'test-trace',
relationSchemaName: 'ticket',
relationSchema: { ticketId: String },
requiredSpans: [() => true],
computedSpanDefinitions: {},
computedValueDefinitions: {},
variants: {
origin: { timeout: 45_000 },
},
}
const input: ActiveTraceInput<
MapTypesToSchema<TicketIdRelationSchemasFixture['ticket']>,
'origin'
> = {
id: 'test',
startTime: createTimestamp(0),
relatedTo: { ticketId: '74' },
variant: 'origin',
}
const recordedItems = new Map([
createMockSpanAndAnnotation(100.501, {
name: 'test-component',
type: 'component-render',
relatedTo: {},
duration: 50.499,
isIdle: false,
renderCount: 1,
renderedOutput: 'loading',
}),
createMockSpanAndAnnotation(
200.001,
{
name: 'test-component',
type: 'component-render',
relatedTo: {},
duration: 50.999,
isIdle: true,
renderCount: 2,
renderedOutput: 'content',
},
{ occurrence: 2 },
),
])
const traceRecording = createTraceRecording(
{
definition,
input,
recordedItemsByLabel: {},
recordedItems,
},
{
transitionFromState: 'active',
lastRelevantSpanAndAnnotation: undefined,
transitionToState: 'complete',
completeSpanAndAnnotation: undefined,
cpuIdleSpanAndAnnotation: undefined,
lastRequiredSpanAndAnnotation: undefined,
},
)
const context = {
definition,
input,
recordedItemsByLabel: {},
recordedItems,
}
const result = convertTraceToRUM({ traceRecording, context })
// Check rounded values in embeddedSpans
const embeddedSpan = result.embeddedSpans['component-render|test-component']
if (embeddedSpan) {
expect(Number.isInteger(embeddedSpan.totalDuration)).toBe(true)
expect(Number.isInteger(embeddedSpan.spans[0]!.startOffset)).toBe(true)
expect(Number.isInteger(embeddedSpan.spans[0]!.duration)).toBe(true)
expect(Number.isInteger(embeddedSpan.spans[1]!.startOffset)).toBe(true)
expect(Number.isInteger(embeddedSpan.spans[1]!.duration)).toBe(true)
// Check specific rounded values
expect(embeddedSpan.spans[0]!.startOffset).toBe(101) // 100.501 rounded
expect(embeddedSpan.spans[0]!.duration).toBe(50) // 50.499 rounded
expect(embeddedSpan.spans[1]!.startOffset).toBe(200) // 200.001 rounded
expect(embeddedSpan.spans[1]!.duration).toBe(51) // 50.999 rounded
expect(result.nonEmbeddedSpans).toEqual([])
}
})
it('should return correct non embedded spans', () => {
const definition: CompleteTraceDefinition<
'ticket',
TicketIdRelationSchemasFixture,
'origin'
> = {
name: 'test-trace',
relationSchemaName: 'ticket',
relationSchema: { ticketId: String },
requiredSpans: [() => true],
computedSpanDefinitions: {},
computedValueDefinitions: {},
variants: {
origin: { timeout: 45_000 },
},
}
const input: ActiveTraceInput<
MapTypesToSchema<TicketIdRelationSchemasFixture['ticket']>,
'origin'
> = {
id: 'test',
startTime: createTimestamp(0),
relatedTo: { ticketId: '74' },
variant: 'origin',
}
const recordedItems = new Map([
createMockSpanAndAnnotation(100.501, {
name: 'test-component',
type: 'component-render',
relatedTo: {},
duration: 50.499,
isIdle: false,
renderCount: 1,
renderedOutput: 'loading',
}),
createMockSpanAndAnnotation(
200.001,
{
name: 'test-component',
type: 'component-render',
relatedTo: {},
duration: 50.999,
isIdle: true,
renderCount: 2,
renderedOutput: 'content',
},
{ occurrence: 2 },
),
])
const traceRecording = createTraceRecording(
{
definition,
input,
recordedItemsByLabel: {},
recordedItems,
},
{
transitionFromState: 'active',
lastRelevantSpanAndAnnotation: undefined,
transitionToState: 'complete',
completeSpanAndAnnotation: undefined,
cpuIdleSpanAndAnnotation: undefined,
lastRequiredSpanAndAnnotation: undefined,
},
)
const context = {
definition,
input,
recordedItemsByLabel: {},
recordedItems,
}
// we dont want to return any embedded spans
const result = convertTraceToRUM({
traceRecording,
context,
embedSpanSelector: () => false,
})
expect(Object.keys(result.embeddedSpans)).toHaveLength(0)
expect(result.nonEmbeddedSpans).toEqual(['component-render|test-component'])
expect(result.nonEmbeddedSpans).toHaveLength(1)
})
it('should identify the longest span correctly', () => {
const definition: CompleteTraceDefinition<
'ticket',
TicketIdRelationSchemasFixture,
'origin'
> = {
name: 'test-trace',
relationSchemaName: 'ticket',
relationSchema: { ticketId: String },
requiredSpans: [() => true],
computedSpanDefinitions: {},
computedValueDefinitions: {},
variants: {
origin: { timeout: 45_000 },
},
}
const input: ActiveTraceInput<
MapTypesToSchema<TicketIdRelationSchemasFixture['ticket']>,
'origin'
> = {
id: 'test',
startTime: createTimestamp(0),
relatedTo: { ticketId: '74' },
variant: 'origin',
}
const recordedItems = new Map([
createMockSpanAndAnnotation(100, {
name: 'short-component',
type: 'component-render',
relatedTo: {},
duration: 30,
isIdle: false,
renderCount: 1,
renderedOutput: 'loading',
}),
createMockSpanAndAnnotation(200, {
name: 'long-component',
type: 'component-render',
relatedTo: {},
duration: 150,
isIdle: true,
renderCount: 1,
renderedOutput: 'content',
}),
createMockSpanAndAnnotation(300, {
name: 'medium-component',
type: 'component-render',
relatedTo: {},
duration: 75,
isIdle: false,
renderCount: 1,
renderedOutput: 'error',
}),
])
const traceRecording = createTraceRecording(
{
definition,
input,
recordedItemsByLabel: {},
recordedItems,
},
{
transitionFromState: 'active',
lastRelevantSpanAndAnnotation: undefined,
transitionToState: 'complete',
completeSpanAndAnnotation: undefined,
cpuIdleSpanAndAnnotation: undefined,
lastRequiredSpanAndAnnotation: undefined,
},
)
const context = {
definition,
input,
recordedItemsByLabel: {},
recordedItems,
}
const result = convertTraceToRUM({ traceRecording, context })
// Should identify the longest span
expect(result.longestSpan).toBeDefined()
assert(result.longestSpan)
expect(result.longestSpan.span.name).toBe('long-component')
expect(result.longestSpan.span.duration).toBe(150)
expect(result.longestSpan.span.type).toBe('component-render')
expect(result.longestSpan.key).toBe('component-render|long-component')
})
})