Skip to content

Commit

Permalink
feat(browser): Add captureUserFeedback (#7729)
Browse files Browse the repository at this point in the history
Add new API `captureUserFeedback` to Browser SDKs, allowing Sentry users to send feedback programmatically without opening and using the feedback dialog.

Co-authored-by: Luca Forstner <luca.forstner@sentry.io>
  • Loading branch information
krystofwoldrich and lforst authored Apr 5, 2023
1 parent 367f779 commit c90a60f
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Sentry.captureUserFeedback({
eventId: 'test_event_id',
email: 'test_email',
comments: 'test_comments',
name: 'test_name',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { expect } from '@playwright/test';
import type { UserFeedback } from '@sentry/types';

import { sentryTest } from '../../../../utils/fixtures';
import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers';

sentryTest('should capture simple user feedback', async ({ getLocalTestPath, page }) => {
const url = await getLocalTestPath({ testDir: __dirname });

const eventData = await getFirstSentryEnvelopeRequest<UserFeedback>(page, url);

expect(eventData).toMatchObject({
eventId: 'test_event_id',
email: 'test_email',
comments: 'test_comments',
name: 'test_name',
});
});
19 changes: 19 additions & 0 deletions packages/browser/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
Options,
Severity,
SeverityLevel,
UserFeedback,
} from '@sentry/types';
import { createClientReportEnvelope, dsnToString, getSDKSource, logger } from '@sentry/utils';

Expand All @@ -16,6 +17,7 @@ import { WINDOW } from './helpers';
import type { Breadcrumbs } from './integrations';
import { BREADCRUMB_INTEGRATION_ID } from './integrations/breadcrumbs';
import type { BrowserTransportOptions } from './transports/types';
import { createUserFeedbackEnvelope } from './userfeedback';

/**
* Configuration options for the Sentry Browser SDK.
Expand Down Expand Up @@ -106,6 +108,23 @@ export class BrowserClient extends BaseClient<BrowserClientOptions> {
super.sendEvent(event, hint);
}

/**
* Sends user feedback to Sentry.
*/
public captureUserFeedback(feedback: UserFeedback): void {
if (!this._isEnabled()) {
__DEBUG_BUILD__ && logger.warn('SDK not enabled, will not capture user feedback.');
return;
}

const envelope = createUserFeedbackEnvelope(feedback, {
metadata: this.getSdkMetadata(),
dsn: this.getDsn(),
tunnel: this.getOptions().tunnel,
});
void this._sendEnvelope(envelope);
}

/**
* @inheritDoc
*/
Expand Down
14 changes: 13 additions & 1 deletion packages/browser/src/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,17 @@ export {
winjsStackLineParser,
} from './stack-parsers';
export { eventFromException, eventFromMessage } from './eventbuilder';
export { defaultIntegrations, forceLoad, init, lastEventId, onLoad, showReportDialog, flush, close, wrap } from './sdk';
export { createUserFeedbackEnvelope } from './userfeedback';
export {
defaultIntegrations,
forceLoad,
init,
lastEventId,
onLoad,
showReportDialog,
flush,
close,
wrap,
captureUserFeedback,
} from './sdk';
export { GlobalHandlers, TryCatch, Breadcrumbs, LinkedErrors, HttpContext, Dedupe } from './integrations';
11 changes: 11 additions & 0 deletions packages/browser/src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
initAndBind,
Integrations as CoreIntegrations,
} from '@sentry/core';
import type { UserFeedback } from '@sentry/types';
import {
addInstrumentationHandler,
logger,
Expand Down Expand Up @@ -289,3 +290,13 @@ function startSessionTracking(): void {
}
});
}

/**
* Captures user feedback and sends it to Sentry.
*/
export function captureUserFeedback(feedback: UserFeedback): void {
const client = getCurrentHub().getClient<BrowserClient>();
if (client) {
client.captureUserFeedback(feedback);
}
}
41 changes: 41 additions & 0 deletions packages/browser/src/userfeedback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { DsnComponents, EventEnvelope, SdkMetadata, UserFeedback, UserFeedbackItem } from '@sentry/types';
import { createEnvelope, dsnToString } from '@sentry/utils';

/**
* Creates an envelope from a user feedback.
*/
export function createUserFeedbackEnvelope(
feedback: UserFeedback,
{
metadata,
tunnel,
dsn,
}: {
metadata: SdkMetadata | undefined;
tunnel: string | undefined;
dsn: DsnComponents | undefined;
},
): EventEnvelope {
const headers: EventEnvelope[0] = {
event_id: feedback.event_id,
sent_at: new Date().toISOString(),
...(metadata &&
metadata.sdk && {
sdk: {
name: metadata.sdk.name,
version: metadata.sdk.version,
},
}),
...(!!tunnel && !!dsn && { dsn: dsnToString(dsn) }),
};
const item = createUserFeedbackEnvelopeItem(feedback);

return createEnvelope(headers, [item]);
}

function createUserFeedbackEnvelopeItem(feedback: UserFeedback): UserFeedbackItem {
const feedbackHeaders: UserFeedbackItem[0] = {
type: 'user_report',
};
return [feedbackHeaders, feedback];
}
68 changes: 68 additions & 0 deletions packages/browser/test/unit/userfeedback.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { createUserFeedbackEnvelope } from '../../src/userfeedback';

describe('userFeedback', () => {
test('creates user feedback envelope header', () => {
const envelope = createUserFeedbackEnvelope(
{
comments: 'Test Comments',
email: 'test@email.com',
name: 'Test User',
event_id: 'testEvent123',
},
{
metadata: {
sdk: {
name: 'testSdkName',
version: 'testSdkVersion',
},
},
tunnel: 'testTunnel',
dsn: {
host: 'testHost',
projectId: 'testProjectId',
protocol: 'http',
},
},
);

expect(envelope[0]).toEqual({
dsn: 'http://undefined@testHost/undefinedtestProjectId',
event_id: 'testEvent123',
sdk: {
name: 'testSdkName',
version: 'testSdkVersion',
},
sent_at: expect.any(String),
});
});

test('creates user feedback envelope item', () => {
const envelope = createUserFeedbackEnvelope(
{
comments: 'Test Comments',
email: 'test@email.com',
name: 'Test User',
event_id: 'testEvent123',
},
{
metadata: undefined,
tunnel: undefined,
dsn: undefined,
},
);

expect(envelope[1]).toEqual([
[
{
type: 'user_report',
},
{
comments: 'Test Comments',
email: 'test@email.com',
name: 'Test User',
event_id: 'testEvent123',
},
],
]);
});
});

0 comments on commit c90a60f

Please sign in to comment.