Skip to content

Commit a493aa6

Browse files
authored
feat(tracing): Add interaction transaction as an experiment (#6210)
1 parent d5648c0 commit a493aa6

File tree

7 files changed

+148
-4
lines changed

7 files changed

+148
-4
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
(() => {
2+
const startTime = Date.now();
3+
4+
function getElasped() {
5+
const time = Date.now();
6+
return time - startTime;
7+
}
8+
9+
while (getElasped() < 105) {
10+
//
11+
}
12+
})();
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import * as Sentry from '@sentry/browser';
2+
import { Integrations } from '@sentry/tracing';
3+
4+
window.Sentry = Sentry;
5+
6+
Sentry.init({
7+
dsn: 'https://[email protected]/1337',
8+
integrations: [
9+
new Integrations.BrowserTracing({
10+
idleTimeout: 1000,
11+
_experiments: {
12+
enableInteractions: true,
13+
},
14+
}),
15+
],
16+
tracesSampleRate: 1,
17+
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<html>
2+
<head>
3+
<meta charset="utf-8" />
4+
</head>
5+
<body>
6+
<div>Rendered Before Long Task</div>
7+
<script src="https://example.com/path/to/script.js"></script>
8+
<button data-test-id="interaction-button">Click Me</button>
9+
</body>
10+
</html>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { expect, Route } from '@playwright/test';
2+
import { Event } from '@sentry/types';
3+
4+
import { sentryTest } from '../../../../utils/fixtures';
5+
import { getFirstSentryEnvelopeRequest, getMultipleSentryEnvelopeRequests } from '../../../../utils/helpers';
6+
7+
sentryTest('should capture interaction transaction.', async ({ browserName, getLocalTestPath, page }) => {
8+
if (browserName !== 'chromium') {
9+
sentryTest.skip();
10+
}
11+
12+
await page.route('**/path/to/script.js', (route: Route) => route.fulfill({ path: `${__dirname}/assets/script.js` }));
13+
14+
const url = await getLocalTestPath({ testDir: __dirname });
15+
16+
await getFirstSentryEnvelopeRequest<Event>(page, url);
17+
18+
await page.locator('[data-test-id=interaction-button]').click();
19+
20+
const envelopes = await getMultipleSentryEnvelopeRequests<Event>(page, 1);
21+
const eventData = envelopes[0];
22+
23+
expect(eventData).toEqual(
24+
expect.objectContaining({
25+
contexts: expect.objectContaining({
26+
trace: expect.objectContaining({
27+
op: 'ui.action.click',
28+
}),
29+
}),
30+
platform: 'javascript',
31+
spans: [],
32+
tags: {},
33+
type: 'transaction',
34+
}),
35+
);
36+
});

packages/integration-tests/utils/generatePlugin.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ const BUNDLE_PATHS: Record<string, Record<string, string>> = {
4949
function generateSentryAlias(): Record<string, string> {
5050
const packageNames = readdirSync(PACKAGES_DIR, { withFileTypes: true })
5151
.filter(dirent => dirent.isDirectory())
52+
.filter(dir => !['apm', 'minimal', 'next-plugin-sentry'].includes(dir.name))
5253
.map(dir => dir.name);
5354

5455
return Object.fromEntries(

packages/tracing/src/browser/browsertracing.ts

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
/* eslint-disable max-lines */
22
import { Hub } from '@sentry/core';
3-
import { EventProcessor, Integration, Transaction, TransactionContext } from '@sentry/types';
3+
import { EventProcessor, Integration, Transaction, TransactionContext, TransactionSource } from '@sentry/types';
44
import { baggageHeaderToDynamicSamplingContext, getDomElement, logger } from '@sentry/utils';
55

66
import { startIdleTransaction } from '../hubextensions';
7-
import { DEFAULT_FINAL_TIMEOUT, DEFAULT_HEARTBEAT_INTERVAL, DEFAULT_IDLE_TIMEOUT } from '../idletransaction';
7+
import {
8+
DEFAULT_FINAL_TIMEOUT,
9+
DEFAULT_HEARTBEAT_INTERVAL,
10+
DEFAULT_IDLE_TIMEOUT,
11+
IdleTransaction,
12+
} from '../idletransaction';
813
import { extractTraceparentData } from '../utils';
914
import { registerBackgroundTabDetection } from './backgroundtab';
1015
import { addPerformanceEntries, startTrackingLongTasks, startTrackingWebVitals } from './metrics';
@@ -92,7 +97,7 @@ export interface BrowserTracingOptions extends RequestInstrumentationOptions {
9297
*
9398
* Default: undefined
9499
*/
95-
_experiments?: Partial<{ enableLongTask: boolean }>;
100+
_experiments?: Partial<{ enableLongTask: boolean; enableInteractions: boolean }>;
96101

97102
/**
98103
* beforeNavigate is called before a pageload/navigation transaction is created and allows users to modify transaction
@@ -125,7 +130,7 @@ const DEFAULT_BROWSER_TRACING_OPTIONS: BrowserTracingOptions = {
125130
routingInstrumentation: instrumentRoutingWithDefaults,
126131
startTransactionOnLocationChange: true,
127132
startTransactionOnPageLoad: true,
128-
_experiments: { enableLongTask: true },
133+
_experiments: { enableLongTask: true, enableInteractions: false },
129134
...defaultRequestInstrumentationOptions,
130135
};
131136

@@ -152,6 +157,9 @@ export class BrowserTracing implements Integration {
152157

153158
private _getCurrentHub?: () => Hub;
154159

160+
private _latestRouteName?: string;
161+
private _latestRouteSource?: TransactionSource;
162+
155163
public constructor(_options?: Partial<BrowserTracingOptions>) {
156164
this.options = {
157165
...DEFAULT_BROWSER_TRACING_OPTIONS,
@@ -189,6 +197,7 @@ export class BrowserTracing implements Integration {
189197
traceXHR,
190198
tracePropagationTargets,
191199
shouldCreateSpanForRequest,
200+
_experiments,
192201
} = this.options;
193202

194203
instrumentRouting(
@@ -201,6 +210,10 @@ export class BrowserTracing implements Integration {
201210
registerBackgroundTabDetection();
202211
}
203212

213+
if (_experiments?.enableInteractions) {
214+
this._registerInteractionListener();
215+
}
216+
204217
instrumentOutgoingRequests({
205218
traceFetch,
206219
traceXHR,
@@ -252,6 +265,9 @@ export class BrowserTracing implements Integration {
252265
? { ...finalContext.metadata, source: 'custom' }
253266
: finalContext.metadata;
254267

268+
this._latestRouteName = finalContext.name;
269+
this._latestRouteSource = finalContext.metadata?.source;
270+
255271
if (finalContext.sampled === false) {
256272
__DEBUG_BUILD__ &&
257273
logger.log(`[Tracing] Will not send ${finalContext.op} transaction because of beforeNavigate.`);
@@ -277,6 +293,57 @@ export class BrowserTracing implements Integration {
277293

278294
return idleTransaction as Transaction;
279295
}
296+
297+
/** Start listener for interaction transactions */
298+
private _registerInteractionListener(): void {
299+
let inflightInteractionTransaction: IdleTransaction | undefined;
300+
const registerInteractionTransaction = (): void => {
301+
const { idleTimeout, finalTimeout, heartbeatInterval } = this.options;
302+
303+
const op = 'ui.action.click';
304+
if (inflightInteractionTransaction) {
305+
inflightInteractionTransaction.finish();
306+
inflightInteractionTransaction = undefined;
307+
}
308+
309+
if (!this._getCurrentHub) {
310+
__DEBUG_BUILD__ && logger.warn(`[Tracing] Did not create ${op} transaction because _getCurrentHub is invalid.`);
311+
return undefined;
312+
}
313+
314+
if (!this._latestRouteName) {
315+
__DEBUG_BUILD__ &&
316+
logger.warn(`[Tracing] Did not create ${op} transaction because _latestRouteName is missing.`);
317+
return undefined;
318+
}
319+
320+
const hub = this._getCurrentHub();
321+
const { location } = WINDOW;
322+
323+
const context: TransactionContext = {
324+
name: this._latestRouteName,
325+
op,
326+
trimEnd: true,
327+
metadata: {
328+
source: this._latestRouteSource ?? 'url',
329+
},
330+
};
331+
332+
inflightInteractionTransaction = startIdleTransaction(
333+
hub,
334+
context,
335+
idleTimeout,
336+
finalTimeout,
337+
true,
338+
{ location }, // for use in the tracesSampler
339+
heartbeatInterval,
340+
);
341+
};
342+
343+
['click'].forEach(type => {
344+
addEventListener(type, registerInteractionTransaction, { once: false, capture: true });
345+
});
346+
}
280347
}
281348

282349
/** Returns the value of a meta tag */

packages/tracing/test/browser/browsertracing.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ describe('BrowserTracing', () => {
8989
expect(browserTracing.options).toEqual({
9090
_experiments: {
9191
enableLongTask: true,
92+
enableInteractions: false,
9293
},
9394
idleTimeout: DEFAULT_IDLE_TIMEOUT,
9495
finalTimeout: DEFAULT_FINAL_TIMEOUT,

0 commit comments

Comments
 (0)