Skip to content

meta(changelog): Update changelog for 7.51.1 #8063

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 11 commits into from
May 8, 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
53 changes: 41 additions & 12 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,25 @@

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

## 7.51.1

- feat(replay): Add event to capture options on checkouts (#8011)
- feat(replay): Improve click target detection (#8026)
- fix(node): Make sure we use same ID for checkIns (#8050)
- fix(replay: Keep session active on key press (#8037)
- fix(replay): Move error sampling to before send (#8057)
- fix(sveltekit): Wrap `load` when typed explicitly (#8049)

**Replay `rrweb` changes:**

`@sentry-internal/rrweb` was updated from 1.106.0 to 1.108.0:

- fix: Fix some input masking (esp for radio buttons) ([#85](https://github.com/getsentry/rrweb/pull/85))
- fix: Unescaped `:` in CSS rule from Safari ([#86](https://github.com/getsentry/rrweb/pull/86))
- feat: Define custom elements (web components) ([#87](https://github.com/getsentry/rrweb/pull/87))

Work in this release contributed by @sreetamdas. Thank you for your contribution!

## 7.51.0

### Important Changes
Expand All @@ -26,30 +45,40 @@ Note that `@sentry/angular` _does not_ support Angular 16.

- **feat(node): Add ability to send cron monitor check ins (#8039)**

**Note: This release contains a bug with generating cron monitors. We recommend you upgrade the JS SDK to 7.51.1 or above to use cron monitoring functionality**

This release adds [Sentry cron monitoring](https://docs.sentry.io/product/crons/) support to the Node SDK.

To monitor your cron jobs, send check-ins everytime you execute your cron jobs to Sentry. You can do this with the `captureCheckIn` method exported from the SDK. First you must send an `in_progress`, checkin, then you can send one with status `ok` or `error` based on what happened with your cron job.
Check-in monitoring allows you to track a job's progress by completing two check-ins: one at the start of your job and another at the end of your job. This two-step process allows Sentry to notify you if your job didn't start when expected (missed) or if it exceeded its maximum runtime (failed).

```ts
const Sentry = require('@sentry/node');

// ...

Sentry.captureCheckIn({
// make sure this is the same slug as what you set up your
// Sentry cron monitor with.
monitorSlug: 'dailyEmail',
// 🟡 Notify Sentry your job is running:
const checkInId = Sentry.captureCheckIn({
monitorSlug: '<monitor-slug>',
status: 'in_progress',
});

const startTime = timeInSeconds();

runTask();
// Execute your scheduled task here...

// 🟢 Notify Sentry your job has completed successfully:
Sentry.captureCheckIn({
monitorSlug: 'dailyEmail',
// make sure you pass in the checkInId generated by the first call to captureCheckIn
checkInId,
monitorSlug: '<monitor-slug>',
status: 'ok',
duration: timeInSeconds() - startTime,
});
```

If your job execution fails, you can notify Sentry about the failure:

```javascript
// 🔴 Notify Sentry your job has failed:
Sentry.captureCheckIn({
checkInId,
monitorSlug: '<monitor-slug>',
status: 'error',
});
```

Expand Down
224 changes: 114 additions & 110 deletions packages/browser-integration-tests/suites/replay/bufferMode/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ sentryTest(

expect(event0).toEqual(
getExpectedReplayEvent({
contexts: { replay: { error_sample_rate: 0, session_sample_rate: 0 } },
error_ids: [errorEventId!],
replay_type: 'buffer',
}),
Expand Down Expand Up @@ -150,7 +149,6 @@ sentryTest(

expect(event1).toEqual(
getExpectedReplayEvent({
contexts: { replay: { error_sample_rate: 0, session_sample_rate: 0 } },
replay_type: 'buffer', // although we're in session mode, we still send 'buffer' as replay_type
segment_id: 1,
urls: [],
Expand All @@ -162,7 +160,6 @@ sentryTest(

expect(event2).toEqual(
getExpectedReplayEvent({
contexts: { replay: { error_sample_rate: 0, session_sample_rate: 0 } },
replay_type: 'buffer', // although we're in session mode, we still send 'buffer' as replay_type
segment_id: 2,
urls: [],
Expand Down Expand Up @@ -266,7 +263,6 @@ sentryTest(

expect(event0).toEqual(
getExpectedReplayEvent({
contexts: { replay: { error_sample_rate: 0, session_sample_rate: 0 } },
error_ids: [errorEventId!],
replay_type: 'buffer',
}),
Expand Down Expand Up @@ -303,118 +299,126 @@ sentryTest(

// Doing this in buffer mode to test changing error sample rate after first
// error happens.
sentryTest('[buffer-mode] can sample on each error event', async ({ getLocalTestPath, page, browserName }) => {
// This was sometimes flaky on firefox/webkit, so skipping for now
if (shouldSkipReplayTest() || ['firefox', 'webkit'].includes(browserName)) {
sentryTest.skip();
}

let callsToSentry = 0;
const errorEventIds: string[] = [];
const reqPromise0 = waitForReplayRequest(page, 0);
const reqErrorPromise = waitForErrorRequest(page);

await page.route('https://dsn.ingest.sentry.io/**/*', route => {
const event = envelopeRequestParser(route.request());
// error events have no type field
if (event && !event.type && event.event_id) {
errorEventIds.push(event.event_id);
}
// We only want to count errors & replays here
if (event && (!event.type || isReplayEvent(event))) {
callsToSentry++;
sentryTest(
'[buffer-mode] can sample on each error event',
async ({ getLocalTestPath, page, browserName, enableConsole }) => {
// This was sometimes flaky on firefox/webkit, so skipping for now
if (shouldSkipReplayTest() || ['firefox', 'webkit'].includes(browserName)) {
sentryTest.skip();
}

return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ id: 'test-id' }),
enableConsole();

let callsToSentry = 0;
const errorEventIds: string[] = [];
const reqPromise0 = waitForReplayRequest(page, 0);
const reqErrorPromise0 = waitForErrorRequest(page);

await page.route('https://dsn.ingest.sentry.io/**/*', route => {
const event = envelopeRequestParser(route.request());
// error events have no type field
if (event && !event.type && event.event_id) {
errorEventIds.push(event.event_id);
}
// We only want to count errors & replays here
if (event && (!event.type || isReplayEvent(event))) {
callsToSentry++;
}

return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ id: 'test-id' }),
});
});

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

await page.goto(url);
// Start buffering and assert that it is enabled
expect(
await page.evaluate(() => {
const replayIntegration = (window as unknown as Window & { Replay: InstanceType<typeof Replay> }).Replay;
const replay = replayIntegration['_replay'];
replayIntegration.startBuffering();
return replay.isEnabled();
}),
).toBe(true);

await page.click('#go-background');
await page.click('#error');
await new Promise(resolve => setTimeout(resolve, 1000));

// 1 unsampled error, no replay
const reqError0 = await reqErrorPromise0;
const errorEvent0 = envelopeRequestParser(reqError0);
expect(callsToSentry).toEqual(1);
expect(errorEvent0.tags?.replayId).toBeUndefined();

await page.evaluate(async () => {
const replayIntegration = (window as unknown as Window & { Replay: Replay }).Replay;
replayIntegration['_replay'].getOptions().errorSampleRate = 1.0;
});
});

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

await page.goto(url);
// Start buffering and assert that it is enabled
expect(
await page.evaluate(() => {
const replayIntegration = (window as unknown as Window & { Replay: InstanceType<typeof Replay> }).Replay;
const replay = replayIntegration['_replay'];
replayIntegration.startBuffering();
return replay.isEnabled();
}),
).toBe(true);

await page.click('#go-background');
await page.click('#error');
await new Promise(resolve => setTimeout(resolve, 1000));

// 1 error, no replay
await reqErrorPromise;
expect(callsToSentry).toEqual(1);

await page.evaluate(async () => {
const replayIntegration = (window as unknown as Window & { Replay: Replay }).Replay;
replayIntegration['_replay'].getOptions().errorSampleRate = 1.0;
});

// Error sample rate is now at 1.0, this error should create a replay
await page.click('#error2');

const req0 = await reqPromise0;

// 2 errors, 1 flush
await reqErrorPromise;
expect(callsToSentry).toEqual(3);

const event0 = getReplayEvent(req0);
const content0 = getReplayRecordingContent(req0);

expect(event0).toEqual(
getExpectedReplayEvent({
contexts: { replay: { error_sample_rate: 1, session_sample_rate: 0 } },
error_ids: errorEventIds,
replay_type: 'buffer',
}),
);

// The first event should have both, full and incremental snapshots,
// as we recorded and kept all events in the buffer
expect(content0.fullSnapshots).toHaveLength(1);
// We want to make sure that the event that triggered the error was
// recorded, as well as the first error that did not get sampled.
expect(content0.breadcrumbs).toEqual(
expect.arrayContaining([
{
...expectedClickBreadcrumb,
message: 'body > button#error',
data: {
nodeId: expect.any(Number),
node: {
attributes: {
id: 'error',

// Error sample rate is now at 1.0, this error should create a replay
const reqErrorPromise1 = waitForErrorRequest(page);
await page.click('#error2');
// 1 unsampled error, 1 sampled error -> 1 flush
const req0 = await reqPromise0;
const reqError1 = await reqErrorPromise1;
const errorEvent1 = envelopeRequestParser(reqError1);
expect(callsToSentry).toEqual(3);
expect(errorEvent0.event_id).not.toEqual(errorEvent1.event_id);
expect(errorEvent1.tags?.replayId).toBeDefined();

const event0 = getReplayEvent(req0);
const content0 = getReplayRecordingContent(req0);

expect(event0).toEqual(
getExpectedReplayEvent({
error_ids: errorEventIds,
replay_type: 'buffer',
}),
);

// The first event should have both, full and incremental snapshots,
// as we recorded and kept all events in the buffer
expect(content0.fullSnapshots).toHaveLength(1);
// We want to make sure that the event that triggered the error was
// recorded, as well as the first error that did not get sampled.
expect(content0.breadcrumbs).toEqual(
expect.arrayContaining([
{
...expectedClickBreadcrumb,
message: 'body > button#error',
data: {
nodeId: expect.any(Number),
node: {
attributes: {
id: 'error',
},
id: expect.any(Number),
tagName: 'button',
textContent: '***** *****',
},
id: expect.any(Number),
tagName: 'button',
textContent: '***** *****',
},
},
},
{
...expectedClickBreadcrumb,
message: 'body > button#error2',
data: {
nodeId: expect.any(Number),
node: {
attributes: {
id: 'error2',
{
...expectedClickBreadcrumb,
message: 'body > button#error2',
data: {
nodeId: expect.any(Number),
node: {
attributes: {
id: 'error2',
},
id: expect.any(Number),
tagName: 'button',
textContent: '******* *****',
},
id: expect.any(Number),
tagName: 'button',
textContent: '******* *****',
},
},
},
]),
);
});
]),
);
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ sentryTest('should capture replays (@sentry/browser export)', async ({ getLocalT
},
},
platform: 'javascript',
contexts: { replay: { session_sample_rate: 1, error_sample_rate: 0 } },
});

expect(replayEvent1).toBeDefined();
Expand Down Expand Up @@ -103,6 +102,5 @@ sentryTest('should capture replays (@sentry/browser export)', async ({ getLocalT
},
},
platform: 'javascript',
contexts: { replay: { session_sample_rate: 1, error_sample_rate: 0 } },
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ sentryTest('should capture replays (@sentry/replay export)', async ({ getLocalTe
},
},
platform: 'javascript',
contexts: { replay: { session_sample_rate: 1, error_sample_rate: 0 } },
});

expect(replayEvent1).toBeDefined();
Expand Down Expand Up @@ -103,6 +102,5 @@ sentryTest('should capture replays (@sentry/replay export)', async ({ getLocalTe
},
},
platform: 'javascript',
contexts: { replay: { session_sample_rate: 1, error_sample_rate: 0 } },
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,9 @@
</head>
<body>
<div role="button" id="error" class="btn btn-error" aria-label="An Error">An Error</div>
<button>
<img id="img"
alt="Alt Text"
/>
</button>
<button class="sentry-unmask" aria-label="Unmasked label">
Unmasked
<button title="Button title">
<img id="img" alt="Alt Text" />
</button>
<button class="sentry-unmask" aria-label="Unmasked label">Unmasked</button>
</body>
</html>
Loading