Skip to content

Commit 9d2f623

Browse files
authored
Merge pull request #10875 from getsentry/abhi-7.104.0-release
meta: CHANGELOG for 7.104.0
2 parents 763eb1f + 868a20e commit 9d2f623

File tree

8 files changed

+179
-10
lines changed

8 files changed

+179
-10
lines changed

CHANGELOG.md

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

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

7+
## 7.104.0
8+
9+
### Important Changes
10+
11+
- **feat(performance): create Interaction standalone spans on inp events (#10709)**
12+
13+
This release adds support for the INP web vital. This is currently only supported for Saas Sentry, and product support
14+
is released with the upcoming `24.3.0` release of self-hosted.
15+
16+
To opt-in to this feature, you can use the `enableInp` option in the `browserTracingIntegration`:
17+
18+
```js
19+
Sentry.init({
20+
integrations: [
21+
Sentry.browserTracingIntegration({
22+
enableInp: true,
23+
});
24+
]
25+
})
26+
```
27+
28+
### Other Changes
29+
30+
- feat(feedback): Flush replays when feedback form opens (#10567)
31+
- feat(profiling-node): Expose `nodeProfilingIntegration` (#10864)
32+
- fix(profiling-node): Fix dependencies to point to current versions (#10861)
33+
- fix(replay): Add `errorHandler` for replayCanvas integration (#10796)
34+
735
## 7.103.0
836

937
### Important Changes
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { expect } from '@playwright/test';
2+
3+
import { sentryTest } from '../../../../utils/fixtures';
4+
import { envelopeRequestParser, getEnvelopeType } from '../../../../utils/helpers';
5+
import { getCustomRecordingEvents, getReplayEvent, waitForReplayRequest } from '../../../../utils/replayHelpers';
6+
7+
sentryTest(
8+
'should capture feedback (@sentry-internal/feedback import)',
9+
async ({ forceFlushReplay, getLocalTestPath, page }) => {
10+
if (process.env.PW_BUNDLE) {
11+
sentryTest.skip();
12+
}
13+
14+
const reqPromise0 = waitForReplayRequest(page, 0);
15+
const reqPromise1 = waitForReplayRequest(page, 1);
16+
const reqPromise2 = waitForReplayRequest(page, 2);
17+
const feedbackRequestPromise = page.waitForResponse(res => {
18+
const req = res.request();
19+
20+
const postData = req.postData();
21+
if (!postData) {
22+
return false;
23+
}
24+
25+
try {
26+
return getEnvelopeType(req) === 'feedback';
27+
} catch (err) {
28+
return false;
29+
}
30+
});
31+
32+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
33+
return route.fulfill({
34+
status: 200,
35+
contentType: 'application/json',
36+
body: JSON.stringify({ id: 'test-id' }),
37+
});
38+
});
39+
40+
const url = await getLocalTestPath({ testDir: __dirname });
41+
42+
const [, , replayReq0] = await Promise.all([page.goto(url), page.getByText('Report a Bug').click(), reqPromise0]);
43+
44+
// Inputs are slow, these need to be serial
45+
await page.locator('[name="name"]').fill('Jane Doe');
46+
await page.locator('[name="email"]').fill('[email protected]');
47+
await page.locator('[name="message"]').fill('my example feedback');
48+
49+
// Force flush here, as inputs are slow and can cause click event to be in unpredictable segments
50+
await Promise.all([forceFlushReplay(), reqPromise1]);
51+
52+
const [, feedbackResp, replayReq2] = await Promise.all([
53+
page.getByLabel('Send Bug Report').click(),
54+
feedbackRequestPromise,
55+
reqPromise2,
56+
]);
57+
58+
const feedbackEvent = envelopeRequestParser(feedbackResp.request());
59+
const replayEvent = getReplayEvent(replayReq0);
60+
// Feedback breadcrumb is on second segment because we flush when "Report a Bug" is clicked
61+
// And then the breadcrumb is sent when feedback form is submitted
62+
const { breadcrumbs } = getCustomRecordingEvents(replayReq2);
63+
64+
expect(breadcrumbs).toEqual(
65+
expect.arrayContaining([
66+
expect.objectContaining({
67+
category: 'sentry.feedback',
68+
data: { feedbackId: expect.any(String) },
69+
timestamp: expect.any(Number),
70+
type: 'default',
71+
}),
72+
]),
73+
);
74+
75+
expect(feedbackEvent).toEqual({
76+
type: 'feedback',
77+
breadcrumbs: expect.any(Array),
78+
contexts: {
79+
feedback: {
80+
contact_email: '[email protected]',
81+
message: 'my example feedback',
82+
name: 'Jane Doe',
83+
replay_id: replayEvent.event_id,
84+
source: 'widget',
85+
url: expect.stringContaining('/dist/index.html'),
86+
},
87+
},
88+
level: 'info',
89+
timestamp: expect.any(Number),
90+
event_id: expect.stringMatching(/\w{32}/),
91+
environment: 'production',
92+
sdk: {
93+
integrations: expect.arrayContaining(['Feedback']),
94+
version: expect.any(String),
95+
name: 'sentry.javascript.browser',
96+
packages: expect.anything(),
97+
},
98+
request: {
99+
url: expect.stringContaining('/dist/index.html'),
100+
headers: {
101+
'User-Agent': expect.stringContaining(''),
102+
},
103+
},
104+
platform: 'javascript',
105+
});
106+
},
107+
);

packages/feedback/src/integration.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,17 +79,17 @@ export class Feedback implements Integration {
7979
private _hasInsertedActorStyles: boolean;
8080

8181
public constructor({
82+
autoInject = true,
8283
id = 'sentry-feedback',
84+
isEmailRequired = false,
85+
isNameRequired = false,
8386
showBranding = true,
84-
autoInject = true,
8587
showEmail = true,
8688
showName = true,
8789
useSentryUser = {
8890
email: 'email',
8991
name: 'username',
9092
},
91-
isEmailRequired = false,
92-
isNameRequired = false,
9393

9494
themeDark,
9595
themeLight,
@@ -123,9 +123,9 @@ export class Feedback implements Integration {
123123
this._hasInsertedActorStyles = false;
124124

125125
this.options = {
126-
id,
127-
showBranding,
128126
autoInject,
127+
showBranding,
128+
id,
129129
isEmailRequired,
130130
isNameRequired,
131131
showEmail,

packages/feedback/src/util/sendFeedbackRequest.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ export async function sendFeedbackRequest(
4242
}
4343

4444
const feedbackEvent = await prepareFeedbackEvent({
45-
scope,
45+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
46+
scope: scope as any,
4647
client,
4748
event: baseEvent,
4849
});

packages/feedback/src/widget/createWidget.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getCurrentScope } from '@sentry/core';
1+
import { getClient, getCurrentScope } from '@sentry/core';
22
import { logger } from '@sentry/utils';
33

44
import type { FeedbackFormData, FeedbackInternalOptions, FeedbackWidget } from '../types';
@@ -9,6 +9,8 @@ import type { DialogComponent } from './Dialog';
99
import { Dialog } from './Dialog';
1010
import { SuccessMessage } from './SuccessMessage';
1111

12+
import { DEBUG_BUILD } from '../debug-build';
13+
1214
interface CreateWidgetParams {
1315
/**
1416
* Shadow DOM to append to
@@ -124,6 +126,24 @@ export function createWidget({
124126
}
125127
}
126128

129+
/**
130+
* Internal handler when dialog is opened
131+
*/
132+
function handleOpenDialog(): void {
133+
// Flush replay if integration exists
134+
const client = getClient();
135+
const replay =
136+
client &&
137+
client.getIntegrationByName &&
138+
client.getIntegrationByName<{ name: string; flush: () => Promise<void>; setupOnce: () => void }>('Replay');
139+
if (!replay) {
140+
return;
141+
}
142+
replay.flush().catch(err => {
143+
DEBUG_BUILD && logger.error(err);
144+
});
145+
}
146+
127147
/**
128148
* Displays the default actor
129149
*/
@@ -156,6 +176,7 @@ export function createWidget({
156176
if (options.onFormOpen) {
157177
options.onFormOpen();
158178
}
179+
handleOpenDialog();
159180
return;
160181
}
161182

@@ -208,6 +229,7 @@ export function createWidget({
208229
if (options.onFormOpen) {
209230
options.onFormOpen();
210231
}
232+
handleOpenDialog();
211233
} catch (err) {
212234
// TODO: Error handling?
213235
logger.error(err);

packages/replay-canvas/src/canvas.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,20 @@ export const _replayCanvasIntegration = ((options: Partial<ReplayCanvasOptions>
7575
enableManualSnapshot,
7676
recordCanvas: true,
7777
getCanvasManager: (options: CanvasManagerOptions) => {
78-
const manager = new CanvasManager({ ...options, enableManualSnapshot });
78+
const manager = new CanvasManager({
79+
...options,
80+
enableManualSnapshot,
81+
errorHandler: (err: unknown) => {
82+
try {
83+
if (typeof err === 'object') {
84+
(err as Error & { __rrweb__?: boolean }).__rrweb__ = true;
85+
}
86+
} catch (error) {
87+
// ignore errors here
88+
// this can happen if the error is frozen or does not allow mutation for other reasons
89+
}
90+
},
91+
});
7992
canvasManagerResolve(manager);
8093
return manager;
8194
},

packages/replay/src/util/addGlobalListeners.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,6 @@ export function addGlobalListeners(replay: ReplayContainer): void {
6666
const replayId = replay.getSessionId();
6767
if (options && options.includeReplay && replay.isEnabled() && replayId) {
6868
// This should never reject
69-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
70-
replay.flush();
7169
if (feedbackEvent.contexts && feedbackEvent.contexts.feedback) {
7270
feedbackEvent.contexts.feedback.replay_id = replayId;
7371
}

0 commit comments

Comments
 (0)