Skip to content

meta(changelog): Update changelog for 7.55.0 #8318

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jun 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@

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

## 7.55.0

- feat(replay): Capture slow clicks (GA) (#8298)
- feat(replay): Improve types for replay recording events (#8224)
- fix(nextjs): Strip query params from transaction names of navigations to unknown routes (#8278)
- fix(replay): Ignore max session life for buffered sessions (#8258)
- fix(sveltekit): Export captureCheckIn (#8313)
- ref(svelte): Add Svelte 4 as a peer dependency (#8280)

## 7.54.0

### Important Changes
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;
window.Replay = new Sentry.Replay({
flushMinDelay: 500,
flushMaxDelay: 500,
slowClickTimeout: 0,
});

Sentry.init({
dsn: 'https://[email protected]/1337',
sampleRate: 0,
replaysSessionSampleRate: 1.0,
replaysOnErrorSampleRate: 0.0,

integrations: [window.Replay],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { expect } from '@playwright/test';

import { sentryTest } from '../../../../utils/fixtures';
import { getCustomRecordingEvents, shouldSkipReplayTest, waitForReplayRequest } from '../../../../utils/replayHelpers';

sentryTest('does not capture slow click when slowClickTimeout === 0', async ({ getLocalTestUrl, page }) => {
if (shouldSkipReplayTest()) {
sentryTest.skip();
}

const reqPromise0 = waitForReplayRequest(page, 0);

await page.route('https://dsn.ingest.sentry.io/**/*', route => {
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ id: 'test-id' }),
});
});

const url = await getLocalTestUrl({ testDir: __dirname });

await page.goto(url);
await reqPromise0;

const reqPromise1 = waitForReplayRequest(page, (event, res) => {
const { breadcrumbs } = getCustomRecordingEvents(res);

return breadcrumbs.some(breadcrumb => breadcrumb.category === 'ui.click');
});

await page.click('#mutationButton');

const { breadcrumbs } = getCustomRecordingEvents(await reqPromise1);

expect(breadcrumbs).toEqual([
{
category: 'ui.click',
data: {
node: {
attributes: {
id: 'mutationButton',
},
id: expect.any(Number),
tagName: 'button',
textContent: '******* ********',
},
nodeId: expect.any(Number),
},
message: 'body > button#mutationButton',
timestamp: expect.any(Number),
type: 'default',
},
]);
});
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,54 @@ sentryTest('click is ignored on ignoreSelectors', async ({ getLocalTestUrl, page
},
]);
});

sentryTest('click is ignored on div', async ({ getLocalTestUrl, page }) => {
if (shouldSkipReplayTest()) {
sentryTest.skip();
}

const reqPromise0 = waitForReplayRequest(page, 0);

await page.route('https://dsn.ingest.sentry.io/**/*', route => {
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ id: 'test-id' }),
});
});

const url = await getLocalTestUrl({ testDir: __dirname });

await page.goto(url);
await reqPromise0;

const reqPromise1 = waitForReplayRequest(page, (event, res) => {
const { breadcrumbs } = getCustomRecordingEvents(res);

return breadcrumbs.some(breadcrumb => breadcrumb.category === 'ui.click');
});

await page.click('#mutationDiv');

const { breadcrumbs } = getCustomRecordingEvents(await reqPromise1);

expect(breadcrumbs).toEqual([
{
category: 'ui.click',
data: {
node: {
attributes: {
id: 'mutationDiv',
},
id: expect.any(Number),
tagName: 'div',
textContent: '******* ********',
},
nodeId: expect.any(Number),
},
message: 'body > div#mutationDiv',
timestamp: expect.any(Number),
type: 'default',
},
]);
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,8 @@ window.Sentry = Sentry;
window.Replay = new Sentry.Replay({
flushMinDelay: 500,
flushMaxDelay: 500,
_experiments: {
slowClicks: {
threshold: 300,
scrollThreshold: 300,
timeout: 2000,
ignoreSelectors: ['.ignore-class', '[ignore-attribute]'],
},
},
slowClickTimeout: 3100,
slowClickIgnoreSelectors: ['.ignore-class', '[ignore-attribute]'],
});

Sentry.init({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ sentryTest('mutation after threshold results in slow click', async ({ getLocalTe
},
]);

expect(slowClickBreadcrumbs[0]?.data?.timeAfterClickMs).toBeGreaterThan(300);
expect(slowClickBreadcrumbs[0]?.data?.timeAfterClickMs).toBeLessThan(2000);
expect(slowClickBreadcrumbs[0]?.data?.timeAfterClickMs).toBeGreaterThan(3000);
expect(slowClickBreadcrumbs[0]?.data?.timeAfterClickMs).toBeLessThan(3100);
});

sentryTest('immediate mutation does not trigger slow click', async ({ browserName, getLocalTestUrl, page }) => {
Expand Down Expand Up @@ -165,56 +165,3 @@ sentryTest('inline click handler does not trigger slow click', async ({ getLocal
},
]);
});

sentryTest('click is not ignored on div', async ({ getLocalTestUrl, page }) => {
if (shouldSkipReplayTest()) {
sentryTest.skip();
}

const reqPromise0 = waitForReplayRequest(page, 0);

await page.route('https://dsn.ingest.sentry.io/**/*', route => {
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ id: 'test-id' }),
});
});

const url = await getLocalTestUrl({ testDir: __dirname });

await page.goto(url);
await reqPromise0;

const reqPromise1 = waitForReplayRequest(page, (event, res) => {
const { breadcrumbs } = getCustomRecordingEvents(res);

return breadcrumbs.some(breadcrumb => breadcrumb.category === 'ui.slowClickDetected');
});

await page.click('#mutationDiv');

const { breadcrumbs } = getCustomRecordingEvents(await reqPromise1);

expect(breadcrumbs.filter(({ category }) => category === 'ui.slowClickDetected')).toEqual([
{
category: 'ui.slowClickDetected',
data: {
endReason: 'mutation',
node: {
attributes: {
id: 'mutationDiv',
},
id: expect.any(Number),
tagName: 'div',
textContent: '******* ********',
},
nodeId: expect.any(Number),
timeAfterClickMs: expect.any(Number),
url: 'http://sentry-test.io/index.html',
},
message: 'body > div#mutationDiv',
timestamp: expect.any(Number),
},
]);
});
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,22 @@ <h1 id="h2">Bottom</h1>
document.getElementById('mutationButton').addEventListener('click', () => {
setTimeout(() => {
document.getElementById('out').innerHTML += 'mutationButton clicked<br>';
}, 400);
}, 3001);
});
document.getElementById('mutationIgnoreButton').addEventListener('click', () => {
setTimeout(() => {
document.getElementById('out').innerHTML += 'mutationIgnoreButton clicked<br>';
}, 400);
}, 3001);
});
document.getElementById('mutationDiv').addEventListener('click', () => {
setTimeout(() => {
document.getElementById('out').innerHTML += 'mutationDiv clicked<br>';
}, 400);
}, 3001);
});
document.getElementById('mutationButtonLate').addEventListener('click', () => {
setTimeout(() => {
document.getElementById('out').innerHTML += 'mutationButtonLate clicked<br>';
}, 3000);
}, 3101);
});
document.getElementById('mutationButtonImmediately').addEventListener('click', () => {
document.getElementById('out').innerHTML += 'mutationButtonImmediately clicked<br>';
Expand All @@ -62,12 +62,12 @@ <h1 id="h2">Bottom</h1>
document.getElementById('scrollLateButton').addEventListener('click', () => {
setTimeout(() => {
document.getElementById('h2').scrollIntoView({ behavior: 'smooth' });
}, 400);
}, 3001);
});
document.getElementById('consoleLogButton').addEventListener('click', () => {
setTimeout(() => {
console.log('DONE');
}, 400);
}, 3001);
});

// Do nothing on these elements
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ sentryTest('mutation after timeout results in slow click', async ({ getLocalTest
textContent: '******* ******** ****',
},
nodeId: expect.any(Number),
timeAfterClickMs: 2000,
timeAfterClickMs: 3100,
url: 'http://sentry-test.io/index.html',
},
message: 'body > button#mutationButtonLate',
Expand Down Expand Up @@ -104,7 +104,7 @@ sentryTest('console.log results in slow click', async ({ getLocalTestUrl, page }
textContent: '******* ******* ***',
},
nodeId: expect.any(Number),
timeAfterClickMs: 2000,
timeAfterClickMs: 3100,
url: 'http://sentry-test.io/index.html',
},
message: 'body > button#consoleLogButton',
Expand Down
6 changes: 5 additions & 1 deletion packages/e2e-tests/lib/runAllTestApps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ export async function runAllTestApps(
recipePaths: string[],
envVarsToInject: Record<string, string | undefined>,
): Promise<void> {
const maxParallel = process.env.CI ? 3 : 6;
const maxParallel = process.env.CANARY_E2E_TEST
? 1 // TODO: figure out why concurrent tests fail for Next.js and remove this concurrency limitation
: process.env.CI
? 3
: 6;

const recipeInstances = constructRecipeInstances(recipePaths);

Expand Down
5 changes: 3 additions & 2 deletions packages/nextjs/src/client/performance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@ export function nextRouterInstrumentation(

if (startTransactionOnLocationChange) {
Router.events.on('routeChangeStart', (navigationTarget: string) => {
const matchedRoute = getNextRouteFromPathname(stripUrlQueryAndFragment(navigationTarget));
const strippedNavigationTarget = stripUrlQueryAndFragment(navigationTarget);
const matchedRoute = getNextRouteFromPathname(strippedNavigationTarget);

let transactionName: string;
let transactionSource: TransactionSource;
Expand All @@ -152,7 +153,7 @@ export function nextRouterInstrumentation(
transactionName = matchedRoute;
transactionSource = 'route';
} else {
transactionName = navigationTarget;
transactionName = strippedNavigationTarget;
transactionSource = 'url';
}

Expand Down
1 change: 1 addition & 0 deletions packages/nextjs/test/performance/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ describe('nextRouterInstrumentation', () => {
['/news', '/news', 'route'],
['/news/', '/news', 'route'],
['/some-route-that-is-not-defined-12332', '/some-route-that-is-not-defined-12332', 'url'], // unknown route
['/some-route-that-is-not-defined-12332?q=42', '/some-route-that-is-not-defined-12332', 'url'], // unknown route w/ query param
['/posts/42', '/posts/[id]', 'route'],
['/posts/42/', '/posts/[id]', 'route'],
['/posts/42?someParam=1', '/posts/[id]', 'route'], // query params are ignored
Expand Down
5 changes: 5 additions & 0 deletions packages/replay/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,8 @@ export const NETWORK_BODY_MAX_SIZE = 150_000;

/* The max size of a single console arg that is captured. Any arg larger than this will be truncated. */
export const CONSOLE_ARG_MAX_SIZE = 5_000;

/* Min. time to wait before we consider something a slow click. */
export const SLOW_CLICK_THRESHOLD = 3_000;
/* For scroll actions after a click, we only look for a very short time period to detect programmatic scrolling. */
export const SLOW_CLICK_SCROLL_TIMEOUT = 300;
13 changes: 7 additions & 6 deletions packages/replay/src/coreHandlers/handleDom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { NodeType } from '@sentry-internal/rrweb-snapshot';
import type { Breadcrumb } from '@sentry/types';
import { htmlTreeAsString } from '@sentry/utils';

import { SLOW_CLICK_SCROLL_TIMEOUT, SLOW_CLICK_THRESHOLD } from '../constants';
import type { ReplayContainer, SlowClickConfig } from '../types';
import { createBreadcrumb } from '../util/createBreadcrumb';
import { detectSlowClick } from './handleSlowClick';
Expand All @@ -17,14 +18,14 @@ export interface DomHandlerData {
export const handleDomListener: (replay: ReplayContainer) => (handlerData: DomHandlerData) => void = (
replay: ReplayContainer,
) => {
const slowClickExperiment = replay.getOptions()._experiments.slowClicks;
const { slowClickTimeout, slowClickIgnoreSelectors } = replay.getOptions();

const slowClickConfig: SlowClickConfig | undefined = slowClickExperiment
const slowClickConfig: SlowClickConfig | undefined = slowClickTimeout
? {
threshold: slowClickExperiment.threshold,
timeout: slowClickExperiment.timeout,
scrollTimeout: slowClickExperiment.scrollTimeout,
ignoreSelector: slowClickExperiment.ignoreSelectors ? slowClickExperiment.ignoreSelectors.join(',') : '',
threshold: Math.min(SLOW_CLICK_THRESHOLD, slowClickTimeout),
timeout: slowClickTimeout,
scrollTimeout: SLOW_CLICK_SCROLL_TIMEOUT,
ignoreSelector: slowClickIgnoreSelectors ? slowClickIgnoreSelectors.join(',') : '',
}
: undefined;

Expand Down
5 changes: 4 additions & 1 deletion packages/replay/src/coreHandlers/handleKeyboardEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ export function handleKeyboardEvent(replay: ReplayContainer, event: KeyboardEven
return;
}

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

const breadcrumb = getKeyboardBreadcrumb(event);

Expand Down
Loading