Skip to content

Commit 13aaf3c

Browse files
authored
feat(feedback): Add level and remove breadcrumbs from feedback event (#9533)
Also adds a browser integration test as well
1 parent ed2b201 commit 13aaf3c

File tree

13 files changed

+311
-81
lines changed

13 files changed

+311
-81
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import * as Sentry from '@sentry/browser';
2+
import { Feedback } from '@sentry-internal/feedback';
3+
4+
window.Sentry = Sentry;
5+
6+
Sentry.init({
7+
dsn: 'https://[email protected]/1337',
8+
integrations: [new Feedback()],
9+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
</head>
6+
<body></body>
7+
</html>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { expect } from '@playwright/test';
2+
3+
import { sentryTest } from '../../../utils/fixtures';
4+
import { envelopeRequestParser, getEnvelopeType } from '../../../utils/helpers';
5+
6+
sentryTest('should capture feedback (@sentry-internal/feedback import)', async ({ getLocalTestPath, page }) => {
7+
if (process.env.PW_BUNDLE) {
8+
sentryTest.skip();
9+
}
10+
11+
const feedbackRequestPromise = page.waitForResponse(res => {
12+
const req = res.request();
13+
14+
const postData = req.postData();
15+
if (!postData) {
16+
return false;
17+
}
18+
19+
try {
20+
return getEnvelopeType(req) === 'feedback';
21+
} catch (err) {
22+
return false;
23+
}
24+
});
25+
26+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
27+
return route.fulfill({
28+
status: 200,
29+
contentType: 'application/json',
30+
body: JSON.stringify({ id: 'test-id' }),
31+
});
32+
});
33+
34+
const url = await getLocalTestPath({ testDir: __dirname });
35+
36+
await page.goto(url);
37+
await page.getByText('Report a Bug').click();
38+
expect(await page.locator(':visible:text-is("Report a Bug")').count()).toEqual(1);
39+
await page.locator('[name="name"]').fill('Jane Doe');
40+
await page.locator('[name="email"]').fill('[email protected]');
41+
await page.locator('[name="message"]').fill('my example feedback');
42+
await page.getByLabel('Send Bug Report').click();
43+
44+
const feedbackEvent = envelopeRequestParser((await feedbackRequestPromise).request());
45+
expect(feedbackEvent).toEqual({
46+
type: 'feedback',
47+
contexts: {
48+
feedback: {
49+
contact_email: '[email protected]',
50+
message: 'my example feedback',
51+
name: 'Jane Doe',
52+
source: 'widget',
53+
url: expect.stringContaining('/dist/index.html'),
54+
},
55+
},
56+
level: 'info',
57+
timestamp: expect.any(Number),
58+
event_id: expect.stringMatching(/\w{32}/),
59+
environment: 'production',
60+
sdk: {
61+
integrations: expect.arrayContaining(['Feedback']),
62+
version: expect.any(String),
63+
name: 'sentry.javascript.browser',
64+
packages: expect.anything(),
65+
},
66+
request: {
67+
url: expect.stringContaining('/dist/index.html'),
68+
headers: {
69+
'User-Agent': expect.stringContaining(''),
70+
},
71+
},
72+
platform: 'javascript',
73+
});
74+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import * as Sentry from '@sentry/browser';
2+
import { Feedback } from '@sentry-internal/feedback';
3+
4+
window.Sentry = Sentry;
5+
6+
Sentry.init({
7+
dsn: 'https://[email protected]/1337',
8+
replaysOnErrorSampleRate: 1.0,
9+
replaysSessionSampleRate: 0,
10+
integrations: [
11+
new Sentry.Replay({
12+
flushMinDelay: 200,
13+
flushMaxDelay: 200,
14+
minReplayDuration: 0,
15+
}),
16+
new Feedback(),
17+
],
18+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
</head>
6+
<body></body>
7+
</html>
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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('should capture feedback (@sentry-internal/feedback import)', async ({ getLocalTestPath, page }) => {
8+
if (process.env.PW_BUNDLE) {
9+
sentryTest.skip();
10+
}
11+
12+
const reqPromise0 = waitForReplayRequest(page, 0);
13+
const feedbackRequestPromise = page.waitForResponse(res => {
14+
const req = res.request();
15+
16+
const postData = req.postData();
17+
if (!postData) {
18+
return false;
19+
}
20+
21+
try {
22+
return getEnvelopeType(req) === 'feedback';
23+
} catch (err) {
24+
return false;
25+
}
26+
});
27+
28+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
29+
return route.fulfill({
30+
status: 200,
31+
contentType: 'application/json',
32+
body: JSON.stringify({ id: 'test-id' }),
33+
});
34+
});
35+
36+
const url = await getLocalTestPath({ testDir: __dirname });
37+
38+
await page.goto(url);
39+
await page.getByText('Report a Bug').click();
40+
await page.locator('[name="name"]').fill('Jane Doe');
41+
await page.locator('[name="email"]').fill('[email protected]');
42+
await page.locator('[name="message"]').fill('my example feedback');
43+
await page.getByLabel('Send Bug Report').click();
44+
45+
const [feedbackResp, replayReq] = await Promise.all([feedbackRequestPromise, reqPromise0]);
46+
47+
const feedbackEvent = envelopeRequestParser(feedbackResp.request());
48+
const replayEvent = getReplayEvent(replayReq);
49+
const { breadcrumbs } = getCustomRecordingEvents(replayReq);
50+
51+
expect(breadcrumbs).toEqual(
52+
expect.arrayContaining([
53+
{
54+
category: 'sentry.feedback',
55+
data: { feedbackId: expect.any(String) },
56+
},
57+
]),
58+
);
59+
60+
expect(feedbackEvent).toEqual({
61+
type: 'feedback',
62+
contexts: {
63+
feedback: {
64+
contact_email: '[email protected]',
65+
message: 'my example feedback',
66+
name: 'Jane Doe',
67+
replay_id: replayEvent.event_id,
68+
source: 'widget',
69+
url: expect.stringContaining('/dist/index.html'),
70+
},
71+
},
72+
level: 'info',
73+
timestamp: expect.any(Number),
74+
event_id: expect.stringMatching(/\w{32}/),
75+
environment: 'production',
76+
sdk: {
77+
integrations: expect.arrayContaining(['Feedback']),
78+
version: expect.any(String),
79+
name: 'sentry.javascript.browser',
80+
packages: expect.anything(),
81+
},
82+
request: {
83+
url: expect.stringContaining('/dist/index.html'),
84+
headers: {
85+
'User-Agent': expect.stringContaining(''),
86+
},
87+
},
88+
platform: 'javascript',
89+
});
90+
});

packages/feedback/src/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,6 @@ export const MESSAGE_LABEL = 'Description';
6060
export const NAME_PLACEHOLDER = 'Your Name';
6161
export const NAME_LABEL = 'Name';
6262
export const SUCCESS_MESSAGE_TEXT = 'Thank you for your report!';
63+
64+
export const FEEDBACK_WIDGET_SOURCE = 'widget';
65+
export const FEEDBACK_API_SOURCE = 'api';

packages/feedback/src/sendFeedback.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { BrowserClient, Replay } from '@sentry/browser';
22
import { getCurrentHub } from '@sentry/core';
33
import { getLocationHref } from '@sentry/utils';
44

5+
import { FEEDBACK_API_SOURCE } from './constants';
56
import type { SendFeedbackOptions } from './types';
67
import { sendFeedbackRequest } from './util/sendFeedbackRequest';
78

@@ -17,7 +18,7 @@ interface SendFeedbackParams {
1718
* Public API to send a Feedback item to Sentry
1819
*/
1920
export function sendFeedback(
20-
{ name, email, message, source = 'api', url = getLocationHref() }: SendFeedbackParams,
21+
{ name, email, message, source = FEEDBACK_API_SOURCE, url = getLocationHref() }: SendFeedbackParams,
2122
{ includeReplay = true }: SendFeedbackOptions = {},
2223
): ReturnType<typeof sendFeedbackRequest> {
2324
const client = getCurrentHub().getClient<BrowserClient>();

packages/feedback/src/util/handleFeedbackSubmit.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { TransportMakeRequestResponse } from '@sentry/types';
22
import { logger } from '@sentry/utils';
33

4+
import { FEEDBACK_WIDGET_SOURCE } from '../constants';
45
import { sendFeedback } from '../sendFeedback';
56
import type { FeedbackFormData, SendFeedbackOptions } from '../types';
67
import type { DialogComponent } from '../widget/Dialog';
@@ -29,7 +30,7 @@ export async function handleFeedbackSubmit(
2930
dialog.hideError();
3031

3132
try {
32-
const resp = await sendFeedback({ ...feedback, source: 'widget' }, options);
33+
const resp = await sendFeedback({ ...feedback, source: FEEDBACK_WIDGET_SOURCE }, options);
3334

3435
// Success!
3536
return resp;

packages/feedback/src/util/prepareFeedbackEvent.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ export async function prepareFeedbackEvent({
2727
scope,
2828
client,
2929
)) as FeedbackEvent | null;
30-
3130
if (preparedEvent === null) {
3231
// Taken from baseclient's `_processEvent` method, where this is handled for errors/transactions
3332
client.recordDroppedEvent('event_processor', 'feedback', event);

0 commit comments

Comments
 (0)