Skip to content

Commit 0f3f211

Browse files
authored
Merge pull request #8318 from getsentry/prepare-release/7.55.0
2 parents 937cbd0 + f81981d commit 0f3f211

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+979
-406
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@
44

55
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
66

7+
## 7.55.0
8+
9+
- feat(replay): Capture slow clicks (GA) (#8298)
10+
- feat(replay): Improve types for replay recording events (#8224)
11+
- fix(nextjs): Strip query params from transaction names of navigations to unknown routes (#8278)
12+
- fix(replay): Ignore max session life for buffered sessions (#8258)
13+
- fix(sveltekit): Export captureCheckIn (#8313)
14+
- ref(svelte): Add Svelte 4 as a peer dependency (#8280)
15+
716
## 7.54.0
817

918
### Important Changes
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+
3+
window.Sentry = Sentry;
4+
window.Replay = new Sentry.Replay({
5+
flushMinDelay: 500,
6+
flushMaxDelay: 500,
7+
slowClickTimeout: 0,
8+
});
9+
10+
Sentry.init({
11+
dsn: 'https://[email protected]/1337',
12+
sampleRate: 0,
13+
replaysSessionSampleRate: 1.0,
14+
replaysOnErrorSampleRate: 0.0,
15+
16+
integrations: [window.Replay],
17+
});
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { expect } from '@playwright/test';
2+
3+
import { sentryTest } from '../../../../utils/fixtures';
4+
import { getCustomRecordingEvents, shouldSkipReplayTest, waitForReplayRequest } from '../../../../utils/replayHelpers';
5+
6+
sentryTest('does not capture slow click when slowClickTimeout === 0', async ({ getLocalTestUrl, page }) => {
7+
if (shouldSkipReplayTest()) {
8+
sentryTest.skip();
9+
}
10+
11+
const reqPromise0 = waitForReplayRequest(page, 0);
12+
13+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
14+
return route.fulfill({
15+
status: 200,
16+
contentType: 'application/json',
17+
body: JSON.stringify({ id: 'test-id' }),
18+
});
19+
});
20+
21+
const url = await getLocalTestUrl({ testDir: __dirname });
22+
23+
await page.goto(url);
24+
await reqPromise0;
25+
26+
const reqPromise1 = waitForReplayRequest(page, (event, res) => {
27+
const { breadcrumbs } = getCustomRecordingEvents(res);
28+
29+
return breadcrumbs.some(breadcrumb => breadcrumb.category === 'ui.click');
30+
});
31+
32+
await page.click('#mutationButton');
33+
34+
const { breadcrumbs } = getCustomRecordingEvents(await reqPromise1);
35+
36+
expect(breadcrumbs).toEqual([
37+
{
38+
category: 'ui.click',
39+
data: {
40+
node: {
41+
attributes: {
42+
id: 'mutationButton',
43+
},
44+
id: expect.any(Number),
45+
tagName: 'button',
46+
textContent: '******* ********',
47+
},
48+
nodeId: expect.any(Number),
49+
},
50+
message: 'body > button#mutationButton',
51+
timestamp: expect.any(Number),
52+
type: 'default',
53+
},
54+
]);
55+
});

packages/browser-integration-tests/suites/replay/slowClick/ignore/test.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,54 @@ sentryTest('click is ignored on ignoreSelectors', async ({ getLocalTestUrl, page
5454
},
5555
]);
5656
});
57+
58+
sentryTest('click is ignored on div', async ({ getLocalTestUrl, page }) => {
59+
if (shouldSkipReplayTest()) {
60+
sentryTest.skip();
61+
}
62+
63+
const reqPromise0 = waitForReplayRequest(page, 0);
64+
65+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
66+
return route.fulfill({
67+
status: 200,
68+
contentType: 'application/json',
69+
body: JSON.stringify({ id: 'test-id' }),
70+
});
71+
});
72+
73+
const url = await getLocalTestUrl({ testDir: __dirname });
74+
75+
await page.goto(url);
76+
await reqPromise0;
77+
78+
const reqPromise1 = waitForReplayRequest(page, (event, res) => {
79+
const { breadcrumbs } = getCustomRecordingEvents(res);
80+
81+
return breadcrumbs.some(breadcrumb => breadcrumb.category === 'ui.click');
82+
});
83+
84+
await page.click('#mutationDiv');
85+
86+
const { breadcrumbs } = getCustomRecordingEvents(await reqPromise1);
87+
88+
expect(breadcrumbs).toEqual([
89+
{
90+
category: 'ui.click',
91+
data: {
92+
node: {
93+
attributes: {
94+
id: 'mutationDiv',
95+
},
96+
id: expect.any(Number),
97+
tagName: 'div',
98+
textContent: '******* ********',
99+
},
100+
nodeId: expect.any(Number),
101+
},
102+
message: 'body > div#mutationDiv',
103+
timestamp: expect.any(Number),
104+
type: 'default',
105+
},
106+
]);
107+
});

packages/browser-integration-tests/suites/replay/slowClick/init.js

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,8 @@ window.Sentry = Sentry;
44
window.Replay = new Sentry.Replay({
55
flushMinDelay: 500,
66
flushMaxDelay: 500,
7-
_experiments: {
8-
slowClicks: {
9-
threshold: 300,
10-
scrollThreshold: 300,
11-
timeout: 2000,
12-
ignoreSelectors: ['.ignore-class', '[ignore-attribute]'],
13-
},
14-
},
7+
slowClickTimeout: 3100,
8+
slowClickIgnoreSelectors: ['.ignore-class', '[ignore-attribute]'],
159
});
1610

1711
Sentry.init({

packages/browser-integration-tests/suites/replay/slowClick/mutation/test.ts

Lines changed: 2 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ sentryTest('mutation after threshold results in slow click', async ({ getLocalTe
5959
},
6060
]);
6161

62-
expect(slowClickBreadcrumbs[0]?.data?.timeAfterClickMs).toBeGreaterThan(300);
63-
expect(slowClickBreadcrumbs[0]?.data?.timeAfterClickMs).toBeLessThan(2000);
62+
expect(slowClickBreadcrumbs[0]?.data?.timeAfterClickMs).toBeGreaterThan(3000);
63+
expect(slowClickBreadcrumbs[0]?.data?.timeAfterClickMs).toBeLessThan(3100);
6464
});
6565

6666
sentryTest('immediate mutation does not trigger slow click', async ({ browserName, getLocalTestUrl, page }) => {
@@ -165,56 +165,3 @@ sentryTest('inline click handler does not trigger slow click', async ({ getLocal
165165
},
166166
]);
167167
});
168-
169-
sentryTest('click is not ignored on div', async ({ getLocalTestUrl, page }) => {
170-
if (shouldSkipReplayTest()) {
171-
sentryTest.skip();
172-
}
173-
174-
const reqPromise0 = waitForReplayRequest(page, 0);
175-
176-
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
177-
return route.fulfill({
178-
status: 200,
179-
contentType: 'application/json',
180-
body: JSON.stringify({ id: 'test-id' }),
181-
});
182-
});
183-
184-
const url = await getLocalTestUrl({ testDir: __dirname });
185-
186-
await page.goto(url);
187-
await reqPromise0;
188-
189-
const reqPromise1 = waitForReplayRequest(page, (event, res) => {
190-
const { breadcrumbs } = getCustomRecordingEvents(res);
191-
192-
return breadcrumbs.some(breadcrumb => breadcrumb.category === 'ui.slowClickDetected');
193-
});
194-
195-
await page.click('#mutationDiv');
196-
197-
const { breadcrumbs } = getCustomRecordingEvents(await reqPromise1);
198-
199-
expect(breadcrumbs.filter(({ category }) => category === 'ui.slowClickDetected')).toEqual([
200-
{
201-
category: 'ui.slowClickDetected',
202-
data: {
203-
endReason: 'mutation',
204-
node: {
205-
attributes: {
206-
id: 'mutationDiv',
207-
},
208-
id: expect.any(Number),
209-
tagName: 'div',
210-
textContent: '******* ********',
211-
},
212-
nodeId: expect.any(Number),
213-
timeAfterClickMs: expect.any(Number),
214-
url: 'http://sentry-test.io/index.html',
215-
},
216-
message: 'body > div#mutationDiv',
217-
timestamp: expect.any(Number),
218-
},
219-
]);
220-
});

packages/browser-integration-tests/suites/replay/slowClick/template.html

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,22 +36,22 @@ <h1 id="h2">Bottom</h1>
3636
document.getElementById('mutationButton').addEventListener('click', () => {
3737
setTimeout(() => {
3838
document.getElementById('out').innerHTML += 'mutationButton clicked<br>';
39-
}, 400);
39+
}, 3001);
4040
});
4141
document.getElementById('mutationIgnoreButton').addEventListener('click', () => {
4242
setTimeout(() => {
4343
document.getElementById('out').innerHTML += 'mutationIgnoreButton clicked<br>';
44-
}, 400);
44+
}, 3001);
4545
});
4646
document.getElementById('mutationDiv').addEventListener('click', () => {
4747
setTimeout(() => {
4848
document.getElementById('out').innerHTML += 'mutationDiv clicked<br>';
49-
}, 400);
49+
}, 3001);
5050
});
5151
document.getElementById('mutationButtonLate').addEventListener('click', () => {
5252
setTimeout(() => {
5353
document.getElementById('out').innerHTML += 'mutationButtonLate clicked<br>';
54-
}, 3000);
54+
}, 3101);
5555
});
5656
document.getElementById('mutationButtonImmediately').addEventListener('click', () => {
5757
document.getElementById('out').innerHTML += 'mutationButtonImmediately clicked<br>';
@@ -62,12 +62,12 @@ <h1 id="h2">Bottom</h1>
6262
document.getElementById('scrollLateButton').addEventListener('click', () => {
6363
setTimeout(() => {
6464
document.getElementById('h2').scrollIntoView({ behavior: 'smooth' });
65-
}, 400);
65+
}, 3001);
6666
});
6767
document.getElementById('consoleLogButton').addEventListener('click', () => {
6868
setTimeout(() => {
6969
console.log('DONE');
70-
}, 400);
70+
}, 3001);
7171
});
7272

7373
// Do nothing on these elements

packages/browser-integration-tests/suites/replay/slowClick/timeout/test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ sentryTest('mutation after timeout results in slow click', async ({ getLocalTest
4949
textContent: '******* ******** ****',
5050
},
5151
nodeId: expect.any(Number),
52-
timeAfterClickMs: 2000,
52+
timeAfterClickMs: 3100,
5353
url: 'http://sentry-test.io/index.html',
5454
},
5555
message: 'body > button#mutationButtonLate',
@@ -104,7 +104,7 @@ sentryTest('console.log results in slow click', async ({ getLocalTestUrl, page }
104104
textContent: '******* ******* ***',
105105
},
106106
nodeId: expect.any(Number),
107-
timeAfterClickMs: 2000,
107+
timeAfterClickMs: 3100,
108108
url: 'http://sentry-test.io/index.html',
109109
},
110110
message: 'body > button#consoleLogButton',

packages/e2e-tests/lib/runAllTestApps.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ export async function runAllTestApps(
88
recipePaths: string[],
99
envVarsToInject: Record<string, string | undefined>,
1010
): Promise<void> {
11-
const maxParallel = process.env.CI ? 3 : 6;
11+
const maxParallel = process.env.CANARY_E2E_TEST
12+
? 1 // TODO: figure out why concurrent tests fail for Next.js and remove this concurrency limitation
13+
: process.env.CI
14+
? 3
15+
: 6;
1216

1317
const recipeInstances = constructRecipeInstances(recipePaths);
1418

packages/nextjs/src/client/performance.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,8 @@ export function nextRouterInstrumentation(
143143

144144
if (startTransactionOnLocationChange) {
145145
Router.events.on('routeChangeStart', (navigationTarget: string) => {
146-
const matchedRoute = getNextRouteFromPathname(stripUrlQueryAndFragment(navigationTarget));
146+
const strippedNavigationTarget = stripUrlQueryAndFragment(navigationTarget);
147+
const matchedRoute = getNextRouteFromPathname(strippedNavigationTarget);
147148

148149
let transactionName: string;
149150
let transactionSource: TransactionSource;
@@ -152,7 +153,7 @@ export function nextRouterInstrumentation(
152153
transactionName = matchedRoute;
153154
transactionSource = 'route';
154155
} else {
155-
transactionName = navigationTarget;
156+
transactionName = strippedNavigationTarget;
156157
transactionSource = 'url';
157158
}
158159

packages/nextjs/test/performance/client.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ describe('nextRouterInstrumentation', () => {
231231
['/news', '/news', 'route'],
232232
['/news/', '/news', 'route'],
233233
['/some-route-that-is-not-defined-12332', '/some-route-that-is-not-defined-12332', 'url'], // unknown route
234+
['/some-route-that-is-not-defined-12332?q=42', '/some-route-that-is-not-defined-12332', 'url'], // unknown route w/ query param
234235
['/posts/42', '/posts/[id]', 'route'],
235236
['/posts/42/', '/posts/[id]', 'route'],
236237
['/posts/42?someParam=1', '/posts/[id]', 'route'], // query params are ignored

packages/replay/src/constants.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,8 @@ export const NETWORK_BODY_MAX_SIZE = 150_000;
3737

3838
/* The max size of a single console arg that is captured. Any arg larger than this will be truncated. */
3939
export const CONSOLE_ARG_MAX_SIZE = 5_000;
40+
41+
/* Min. time to wait before we consider something a slow click. */
42+
export const SLOW_CLICK_THRESHOLD = 3_000;
43+
/* For scroll actions after a click, we only look for a very short time period to detect programmatic scrolling. */
44+
export const SLOW_CLICK_SCROLL_TIMEOUT = 300;

packages/replay/src/coreHandlers/handleDom.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { NodeType } from '@sentry-internal/rrweb-snapshot';
33
import type { Breadcrumb } from '@sentry/types';
44
import { htmlTreeAsString } from '@sentry/utils';
55

6+
import { SLOW_CLICK_SCROLL_TIMEOUT, SLOW_CLICK_THRESHOLD } from '../constants';
67
import type { ReplayContainer, SlowClickConfig } from '../types';
78
import { createBreadcrumb } from '../util/createBreadcrumb';
89
import { detectSlowClick } from './handleSlowClick';
@@ -17,14 +18,14 @@ export interface DomHandlerData {
1718
export const handleDomListener: (replay: ReplayContainer) => (handlerData: DomHandlerData) => void = (
1819
replay: ReplayContainer,
1920
) => {
20-
const slowClickExperiment = replay.getOptions()._experiments.slowClicks;
21+
const { slowClickTimeout, slowClickIgnoreSelectors } = replay.getOptions();
2122

22-
const slowClickConfig: SlowClickConfig | undefined = slowClickExperiment
23+
const slowClickConfig: SlowClickConfig | undefined = slowClickTimeout
2324
? {
24-
threshold: slowClickExperiment.threshold,
25-
timeout: slowClickExperiment.timeout,
26-
scrollTimeout: slowClickExperiment.scrollTimeout,
27-
ignoreSelector: slowClickExperiment.ignoreSelectors ? slowClickExperiment.ignoreSelectors.join(',') : '',
25+
threshold: Math.min(SLOW_CLICK_THRESHOLD, slowClickTimeout),
26+
timeout: slowClickTimeout,
27+
scrollTimeout: SLOW_CLICK_SCROLL_TIMEOUT,
28+
ignoreSelector: slowClickIgnoreSelectors ? slowClickIgnoreSelectors.join(',') : '',
2829
}
2930
: undefined;
3031

packages/replay/src/coreHandlers/handleKeyboardEvent.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ export function handleKeyboardEvent(replay: ReplayContainer, event: KeyboardEven
1212
return;
1313
}
1414

15-
replay.triggerUserActivity();
15+
// Update user activity, but do not restart recording as it can create
16+
// noisy/low-value replays (e.g. user comes back from idle, hits alt-tab, new
17+
// session with a single "keydown" breadcrumb is created)
18+
replay.updateUserActivity();
1619

1720
const breadcrumb = getKeyboardBreadcrumb(event);
1821

0 commit comments

Comments
 (0)