@zendesk/retrace
Version:
define and capture Product Operation Traces along with computed metrics with an optional friendly React beacon API
198 lines • 7.54 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractTimingOffsets = void 0;
exports.isSuppressedError = isSuppressedError;
exports.formatMs = formatMs;
exports.getConfigSummary = getConfigSummary;
exports.getComputedResults = getComputedResults;
exports.getMatcherLabelFromCombinator = getMatcherLabelFromCombinator;
exports.formatMatcher = formatMatcher;
const constants_1 = require("./constants");
const recordingComputeUtils_1 = require("./recordingComputeUtils");
// Helper to check if error is suppressed
function isSuppressedError(trace, spanAndAnnotation) {
return !!trace.definition.suppressErrorStatusPropagationOnSpans?.some((fn) => fn(spanAndAnnotation, trace));
}
// Helper to format ms
function formatMs(ms) {
if (ms == null)
return 'n/a';
if (ms < 1_000)
return `${ms.toFixed(0)}ms`;
return `${(ms / 1_000).toFixed(2)}s`;
}
// Helper to get config summary from traceContext or definition
function getConfigSummary(traceContext) {
const def = traceContext.definition;
const variant = def.variants[traceContext.input.variant];
const timeout = variant?.timeout;
const debounce = (def.debounceOnSpans ?? []).length > 0
? def.debounceWindow ?? constants_1.DEFAULT_DEBOUNCE_DURATION
: undefined;
const interactive = typeof def.captureInteractive === 'object'
? def.captureInteractive.timeout
: def.captureInteractive
? constants_1.DEFAULT_INTERACTIVE_TIMEOUT_DURATION
: undefined;
return { timeout, debounce, interactive };
}
// Helper to get computed values/spans for completed/interrupted traces
function getComputedResults(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
traceContext, finalTransition) {
try {
const recording = (0, recordingComputeUtils_1.createTraceRecording)(traceContext, finalTransition);
return recording;
}
catch {
return undefined;
}
}
/**
* Extract timing offsets from a transition object
*/
const extractTimingOffsets = (transition) => {
let lastRequiredSpanOffset;
let completeSpanOffset;
let cpuIdleSpanOffset;
if ('lastRequiredSpanAndAnnotation' in transition &&
transition.lastRequiredSpanAndAnnotation) {
lastRequiredSpanOffset =
transition.lastRequiredSpanAndAnnotation.annotation
.operationRelativeEndTime;
}
if ('completeSpanAndAnnotation' in transition &&
transition.completeSpanAndAnnotation) {
completeSpanOffset =
transition.completeSpanAndAnnotation.annotation.operationRelativeEndTime;
}
if ('cpuIdleSpanAndAnnotation' in transition &&
transition.cpuIdleSpanAndAnnotation) {
cpuIdleSpanOffset =
transition.cpuIdleSpanAndAnnotation.annotation.operationRelativeEndTime;
}
return { lastRequiredSpanOffset, completeSpanOffset, cpuIdleSpanOffset };
};
exports.extractTimingOffsets = extractTimingOffsets;
/**
* Attempt to create a more descriptive name from the definition
* This part needs customization based on how 'fromDefinition' is structured.
* Example: Check for specific properties like 'name', 'type', 'label' etc.
*/
function getMatcherLabelFromCombinator(def, index) {
if ('fromDefinition' in def && def.fromDefinition) {
return getMatcherLabelFromCombinator(def.fromDefinition, index);
}
const parts = [];
// Example: Prioritize 'label' if it exists
if ('label' in def && typeof def.label === 'string') {
return `label="${def.label}"`;
}
// Example: Use 'name' if it exists
if ('name' in def) {
if (typeof def.name === 'string') {
parts.push(`name="${def.name}"`);
}
else if (def.name instanceof RegExp) {
parts.push(`name=/${def.name.source}/${def.name.flags}`);
}
}
// Example: Add type if present
if ('type' in def && typeof def.type === 'string') {
parts.push(`type="${def.type}"`);
}
// Add other relevant properties from your definition structure
if ('oneOf' in def && Array.isArray(def.oneOf)) {
parts.push(`( ${def.oneOf
.map((item, i) => getMatcherLabelFromCombinator(item, i))
.join(' OR ')} )`);
}
// performanceEntryName
if ('performanceEntryName' in def && def.performanceEntryName !== undefined) {
if (typeof def.performanceEntryName === 'string') {
parts.push(`performanceEntryName="${def.performanceEntryName}"`);
}
else if (def.performanceEntryName instanceof RegExp) {
parts.push(`performanceEntryName=/${def.performanceEntryName.source}/${def.performanceEntryName.flags}`);
}
}
// status
if ('status' in def && typeof def.status === 'string') {
parts.push(`status="${def.status}"`);
}
// attributes
if ('attributes' in def &&
typeof def.attributes === 'object' &&
def.attributes !== null) {
const attrStr = Object.entries(def.attributes)
.map(([k, v]) => `${k}:${JSON.stringify(v)}`)
.join(', ');
if (attrStr)
parts.push(`attributes={${attrStr}}`);
}
// matchingRelations
if ('matchingRelations' in def && def.matchingRelations !== undefined) {
if (Array.isArray(def.matchingRelations)) {
parts.push(`matchingRelations=[${def.matchingRelations.join(', ')}]`);
}
else if (typeof def.matchingRelations === 'boolean' &&
def.matchingRelations) {
parts.push(`matchingRelations`);
}
}
// occurrence
if ('occurrence' in def && def.occurrence !== undefined) {
if (typeof def.occurrence === 'number') {
parts.push(`occurrence=${def.occurrence}`);
}
else if (typeof def.occurrence === 'function') {
parts.push('occurrence=<fn>');
}
}
// isIdle
if ('isIdle' in def && typeof def.isIdle === 'boolean' && def.isIdle) {
parts.push(`isIdle`);
}
// fn
if ('fn' in def && typeof def.fn === 'function') {
parts.push('<customMatcherFn>');
}
// nthMatch
if ('nthMatch' in def && typeof def.nthMatch === 'number') {
parts.push(`nthMatch=${def.nthMatch}`);
}
if (parts.length > 0) {
return parts.join(' AND ');
}
// Fallback: Stringify the definition (can be verbose)
try {
const defString = JSON.stringify(def);
// Limit length to avoid overly long strings
return defString.length > 100 ? `${defString.slice(0, 97)}...` : defString;
}
catch {
// Fallback if stringify fails
return `<matcher#${index ?? '?'}>`;
}
}
/**
* Formats a SpanMatcherFn into a more readable string representation.
* This is a basic implementation and can be significantly improved
* based on the actual structure of `matcher.fromDefinition`.
*
* @param matcher The matcher function to format.
* @param index Optional index for generic naming.
* @returns A string representation of the matcher.
*/
function formatMatcher(matcher, index) {
// Check if the matcher has attached definition info
if (matcher.fromDefinition) {
const def = matcher.fromDefinition;
if (def && typeof def === 'object') {
return getMatcherLabelFromCombinator(def, index);
}
}
// Fallback if no definition info is available
return `<matcher#${index ?? '?'}>`;
}
//# sourceMappingURL=debugUtils.js.map