Skip to content

Commit

Permalink
feat(feedback): Add level and remove breadcrumbs from feedback event (
Browse files Browse the repository at this point in the history
#9533)

Also adds a browser integration test as well
  • Loading branch information
billyvg committed Nov 16, 2023
1 parent ed2b201 commit 13aaf3c
Show file tree
Hide file tree
Showing 13 changed files with 311 additions and 81 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as Sentry from '@sentry/browser';
import { Feedback } from '@sentry-internal/feedback';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [new Feedback()],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body></body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { expect } from '@playwright/test';

import { sentryTest } from '../../../utils/fixtures';
import { envelopeRequestParser, getEnvelopeType } from '../../../utils/helpers';

sentryTest('should capture feedback (@sentry-internal/feedback import)', async ({ getLocalTestPath, page }) => {
if (process.env.PW_BUNDLE) {
sentryTest.skip();
}

const feedbackRequestPromise = page.waitForResponse(res => {
const req = res.request();

const postData = req.postData();
if (!postData) {
return false;
}

try {
return getEnvelopeType(req) === 'feedback';
} catch (err) {
return false;
}
});

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 getLocalTestPath({ testDir: __dirname });

await page.goto(url);
await page.getByText('Report a Bug').click();
expect(await page.locator(':visible:text-is("Report a Bug")').count()).toEqual(1);
await page.locator('[name="name"]').fill('Jane Doe');
await page.locator('[name="email"]').fill('janedoe@example.org');
await page.locator('[name="message"]').fill('my example feedback');
await page.getByLabel('Send Bug Report').click();

const feedbackEvent = envelopeRequestParser((await feedbackRequestPromise).request());
expect(feedbackEvent).toEqual({
type: 'feedback',
contexts: {
feedback: {
contact_email: 'janedoe@example.org',
message: 'my example feedback',
name: 'Jane Doe',
source: 'widget',
url: expect.stringContaining('/dist/index.html'),
},
},
level: 'info',
timestamp: expect.any(Number),
event_id: expect.stringMatching(/\w{32}/),
environment: 'production',
sdk: {
integrations: expect.arrayContaining(['Feedback']),
version: expect.any(String),
name: 'sentry.javascript.browser',
packages: expect.anything(),
},
request: {
url: expect.stringContaining('/dist/index.html'),
headers: {
'User-Agent': expect.stringContaining(''),
},
},
platform: 'javascript',
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as Sentry from '@sentry/browser';
import { Feedback } from '@sentry-internal/feedback';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
replaysOnErrorSampleRate: 1.0,
replaysSessionSampleRate: 0,
integrations: [
new Sentry.Replay({
flushMinDelay: 200,
flushMaxDelay: 200,
minReplayDuration: 0,
}),
new Feedback(),
],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body></body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { expect } from '@playwright/test';

import { sentryTest } from '../../../utils/fixtures';
import { envelopeRequestParser, getEnvelopeType } from '../../../utils/helpers';
import { getCustomRecordingEvents, getReplayEvent, waitForReplayRequest } from '../../../utils/replayHelpers';

sentryTest('should capture feedback (@sentry-internal/feedback import)', async ({ getLocalTestPath, page }) => {
if (process.env.PW_BUNDLE) {
sentryTest.skip();
}

const reqPromise0 = waitForReplayRequest(page, 0);
const feedbackRequestPromise = page.waitForResponse(res => {
const req = res.request();

const postData = req.postData();
if (!postData) {
return false;
}

try {
return getEnvelopeType(req) === 'feedback';
} catch (err) {
return false;
}
});

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 getLocalTestPath({ testDir: __dirname });

await page.goto(url);
await page.getByText('Report a Bug').click();
await page.locator('[name="name"]').fill('Jane Doe');
await page.locator('[name="email"]').fill('janedoe@example.org');
await page.locator('[name="message"]').fill('my example feedback');
await page.getByLabel('Send Bug Report').click();

const [feedbackResp, replayReq] = await Promise.all([feedbackRequestPromise, reqPromise0]);

const feedbackEvent = envelopeRequestParser(feedbackResp.request());
const replayEvent = getReplayEvent(replayReq);
const { breadcrumbs } = getCustomRecordingEvents(replayReq);

expect(breadcrumbs).toEqual(
expect.arrayContaining([
{
category: 'sentry.feedback',
data: { feedbackId: expect.any(String) },
},
]),
);

expect(feedbackEvent).toEqual({
type: 'feedback',
contexts: {
feedback: {
contact_email: 'janedoe@example.org',
message: 'my example feedback',
name: 'Jane Doe',
replay_id: replayEvent.event_id,
source: 'widget',
url: expect.stringContaining('/dist/index.html'),
},
},
level: 'info',
timestamp: expect.any(Number),
event_id: expect.stringMatching(/\w{32}/),
environment: 'production',
sdk: {
integrations: expect.arrayContaining(['Feedback']),
version: expect.any(String),
name: 'sentry.javascript.browser',
packages: expect.anything(),
},
request: {
url: expect.stringContaining('/dist/index.html'),
headers: {
'User-Agent': expect.stringContaining(''),
},
},
platform: 'javascript',
});
});
3 changes: 3 additions & 0 deletions packages/feedback/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,6 @@ export const MESSAGE_LABEL = 'Description';
export const NAME_PLACEHOLDER = 'Your Name';
export const NAME_LABEL = 'Name';
export const SUCCESS_MESSAGE_TEXT = 'Thank you for your report!';

export const FEEDBACK_WIDGET_SOURCE = 'widget';
export const FEEDBACK_API_SOURCE = 'api';
3 changes: 2 additions & 1 deletion packages/feedback/src/sendFeedback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { BrowserClient, Replay } from '@sentry/browser';
import { getCurrentHub } from '@sentry/core';
import { getLocationHref } from '@sentry/utils';

import { FEEDBACK_API_SOURCE } from './constants';
import type { SendFeedbackOptions } from './types';
import { sendFeedbackRequest } from './util/sendFeedbackRequest';

Expand All @@ -17,7 +18,7 @@ interface SendFeedbackParams {
* Public API to send a Feedback item to Sentry
*/
export function sendFeedback(
{ name, email, message, source = 'api', url = getLocationHref() }: SendFeedbackParams,
{ name, email, message, source = FEEDBACK_API_SOURCE, url = getLocationHref() }: SendFeedbackParams,
{ includeReplay = true }: SendFeedbackOptions = {},
): ReturnType<typeof sendFeedbackRequest> {
const client = getCurrentHub().getClient<BrowserClient>();
Expand Down
3 changes: 2 additions & 1 deletion packages/feedback/src/util/handleFeedbackSubmit.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { TransportMakeRequestResponse } from '@sentry/types';
import { logger } from '@sentry/utils';

import { FEEDBACK_WIDGET_SOURCE } from '../constants';
import { sendFeedback } from '../sendFeedback';
import type { FeedbackFormData, SendFeedbackOptions } from '../types';
import type { DialogComponent } from '../widget/Dialog';
Expand Down Expand Up @@ -29,7 +30,7 @@ export async function handleFeedbackSubmit(
dialog.hideError();

try {
const resp = await sendFeedback({ ...feedback, source: 'widget' }, options);
const resp = await sendFeedback({ ...feedback, source: FEEDBACK_WIDGET_SOURCE }, options);

// Success!
return resp;
Expand Down
1 change: 0 additions & 1 deletion packages/feedback/src/util/prepareFeedbackEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ export async function prepareFeedbackEvent({
scope,
client,
)) as FeedbackEvent | null;

if (preparedEvent === null) {
// Taken from baseclient's `_processEvent` method, where this is handled for errors/transactions
client.recordDroppedEvent('event_processor', 'feedback', event);
Expand Down

0 comments on commit 13aaf3c

Please sign in to comment.