Skip to content

Commit 6895568

Browse files
antonisclaude
andcommitted
fix(tracing): Fix existing-span update for both TTID and TTFD, add combined-use test
Broaden the TTID existing-span update condition to match the TTFD fix, preventing duplicate spans when a component span with ok/undefined status exists alongside a native timestamp. Add test for combined <TimeToFullDisplay> component + reportFullyDisplayed() usage. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8978616 commit 6895568

2 files changed

Lines changed: 41 additions & 1 deletion

File tree

packages/core/src/js/tracing/integrations/timeToDisplayIntegration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ async function addTimeToInitialDisplay({
141141
const manualDurationMs = (ttidEndTimestampSeconds - transactionStartTimestampSeconds) * 1000;
142142
const manualStatus = isDeadlineExceeded(manualDurationMs) ? 'deadline_exceeded' : 'ok';
143143

144-
if (ttidSpan?.status && ttidSpan.status !== 'ok') {
144+
if (ttidSpan) {
145145
ttidSpan.status = manualStatus;
146146
ttidSpan.timestamp = ttidEndTimestampSeconds;
147147
debug.log(`[${INTEGRATION_NAME}] Updated existing ttid span.`, ttidSpan);

packages/core/test/tracing/timetodisplay.test.tsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,6 +1153,46 @@ describe('reportFullyDisplayed', () => {
11531153
expectFullDisplayMeasurementOnSpan(client.event!);
11541154
});
11551155

1156+
test('does not create duplicate span when component and imperative API are both used', async () => {
1157+
const ttidTimestamp = nowInSeconds();
1158+
const ttfdTimestamp = nowInSeconds();
1159+
1160+
startSpanManual(
1161+
{
1162+
name: 'Root Manual Span',
1163+
startTime: secondAgoTimestampMs(),
1164+
},
1165+
(activeSpan: Span | undefined) => {
1166+
startTimeToInitialDisplaySpan();
1167+
startTimeToFullDisplaySpan();
1168+
1169+
render(<TimeToInitialDisplay record={true} />);
1170+
render(<TimeToFullDisplay record={true} />);
1171+
1172+
mockRecordedTimeToDisplay({
1173+
ttid: {
1174+
[spanToJSON(activeSpan).span_id]: ttidTimestamp,
1175+
},
1176+
ttfd: {
1177+
[spanToJSON(activeSpan).span_id]: ttfdTimestamp,
1178+
},
1179+
});
1180+
1181+
reportFullyDisplayed();
1182+
1183+
activeSpan?.end();
1184+
},
1185+
);
1186+
1187+
await jest.runOnlyPendingTimersAsync();
1188+
await client.flush();
1189+
1190+
const ttfdSpans = client.event!.spans!.filter((s: SpanJSON) => s.op === 'ui.load.full_display');
1191+
expect(ttfdSpans).toHaveLength(1);
1192+
expect(ttfdSpans[0]!.status).toBe('ok');
1193+
expectFullDisplayMeasurementOnSpan(client.event!);
1194+
});
1195+
11561196
test('second call is ignored', async () => {
11571197
startSpanManual(
11581198
{

0 commit comments

Comments
 (0)