Skip to content

Commit f154394

Browse files
committed
ref(browser): Remove showReportDialog on browser client (#4973)
Remove `showReportDialog` as a client method so it can be tree-shaken out if not used. Also allow for hub to be explicitly passed in to `showReportDialog`.
1 parent b2a3ef0 commit f154394

File tree

6 files changed

+68
-92
lines changed

6 files changed

+68
-92
lines changed

packages/browser/src/client.ts

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
import { BaseClient, Scope, SDK_VERSION } from '@sentry/core';
22
import { ClientOptions, Event, EventHint, Options, Severity, SeverityLevel } from '@sentry/types';
3-
import { getGlobalObject, logger } from '@sentry/utils';
43

54
import { eventFromException, eventFromMessage } from './eventbuilder';
6-
import { IS_DEBUG_BUILD } from './flags';
7-
import { injectReportDialog, ReportDialogOptions } from './helpers';
85
import { Breadcrumbs } from './integrations';
96

107
export interface BaseBrowserOptions {
@@ -62,29 +59,6 @@ export class BrowserClient extends BaseClient<BrowserClientOptions> {
6259
super(options);
6360
}
6461

65-
/**
66-
* Show a report dialog to the user to send feedback to a specific event.
67-
*
68-
* @param options Set individual options for the dialog
69-
*/
70-
public showReportDialog(options: ReportDialogOptions = {}): void {
71-
// doesn't work without a document (React Native)
72-
const document = getGlobalObject<Window>().document;
73-
if (!document) {
74-
return;
75-
}
76-
77-
if (!this._isEnabled()) {
78-
IS_DEBUG_BUILD && logger.error('Trying to call showReportDialog with Sentry Client disabled');
79-
return;
80-
}
81-
82-
injectReportDialog({
83-
...options,
84-
dsn: options.dsn || this.getDsn(),
85-
});
86-
}
87-
8862
/**
8963
* @inheritDoc
9064
*/

packages/browser/src/exports.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,5 @@ export {
5252
opera11StackParser,
5353
winjsStackParser,
5454
} from './stack-parsers';
55-
export { injectReportDialog } from './helpers';
5655
export { defaultIntegrations, forceLoad, init, lastEventId, onLoad, showReportDialog, flush, close, wrap } from './sdk';
5756
export { SDK_NAME } from './version';

packages/browser/src/helpers.ts

Lines changed: 1 addition & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
1-
import { captureException, getReportDialogEndpoint, withScope } from '@sentry/core';
1+
import { captureException, withScope } from '@sentry/core';
22
import { DsnLike, Event as SentryEvent, Mechanism, Scope, WrappedFunction } from '@sentry/types';
33
import {
44
addExceptionMechanism,
55
addExceptionTypeValue,
66
addNonEnumerableProperty,
7-
getGlobalObject,
87
getOriginalFunction,
9-
logger,
108
markFunctionWrapped,
119
} from '@sentry/utils';
1210

13-
import { IS_DEBUG_BUILD } from './flags';
14-
15-
const global = getGlobalObject<Window>();
1611
let ignoreOnError: number = 0;
1712

1813
/**
@@ -182,38 +177,3 @@ export interface ReportDialogOptions {
182177
/** Callback after reportDialog showed up */
183178
onLoad?(): void;
184179
}
185-
186-
/**
187-
* Injects the Report Dialog script
188-
* @hidden
189-
*/
190-
export function injectReportDialog(options: ReportDialogOptions = {}): void {
191-
if (!global.document) {
192-
return;
193-
}
194-
195-
if (!options.eventId) {
196-
IS_DEBUG_BUILD && logger.error('Missing eventId option in showReportDialog call');
197-
return;
198-
}
199-
200-
if (!options.dsn) {
201-
IS_DEBUG_BUILD && logger.error('Missing dsn option in showReportDialog call');
202-
return;
203-
}
204-
205-
const script = global.document.createElement('script');
206-
script.async = true;
207-
script.src = getReportDialogEndpoint(options.dsn, options);
208-
209-
if (options.onLoad) {
210-
// eslint-disable-next-line @typescript-eslint/unbound-method
211-
script.onload = options.onLoad;
212-
}
213-
214-
const injectionPoint = global.document.head || global.document.body;
215-
216-
if (injectionPoint) {
217-
injectionPoint.appendChild(script);
218-
}
219-
}

packages/browser/src/sdk.ts

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1-
import { getCurrentHub, getIntegrationsToSetup, initAndBind, Integrations as CoreIntegrations } from '@sentry/core';
2-
import { Hub } from '@sentry/types';
1+
import {
2+
getCurrentHub,
3+
getIntegrationsToSetup,
4+
getReportDialogEndpoint,
5+
Hub,
6+
initAndBind,
7+
Integrations as CoreIntegrations,
8+
} from '@sentry/core';
39
import {
410
addInstrumentationHandler,
511
getGlobalObject,
@@ -121,9 +127,21 @@ export function init(options: BrowserOptions = {}): void {
121127
*
122128
* @param options Everything is optional, we try to fetch all info need from the global scope.
123129
*/
124-
export function showReportDialog(options: ReportDialogOptions = {}): void {
125-
const hub = getCurrentHub();
126-
const scope = hub.getScope();
130+
export function showReportDialog(options: ReportDialogOptions = {}, hub: Hub = getCurrentHub()): void {
131+
// doesn't work without a document (React Native)
132+
const global = getGlobalObject<Window>();
133+
if (!global.document) {
134+
IS_DEBUG_BUILD && logger.error('Global document not defined in showReportDialog call');
135+
return;
136+
}
137+
138+
const { client, scope } = hub.getStackTop();
139+
const dsn = options.dsn || (client && client.getDsn());
140+
if (!dsn) {
141+
IS_DEBUG_BUILD && logger.error('DSN not configured for showReportDialog call');
142+
return;
143+
}
144+
127145
if (scope) {
128146
options.user = {
129147
...scope.getUser(),
@@ -134,9 +152,21 @@ export function showReportDialog(options: ReportDialogOptions = {}): void {
134152
if (!options.eventId) {
135153
options.eventId = hub.lastEventId();
136154
}
137-
const client = hub.getClient<BrowserClient>();
138-
if (client) {
139-
client.showReportDialog(options);
155+
156+
const script = global.document.createElement('script');
157+
script.async = true;
158+
script.src = getReportDialogEndpoint(dsn, options);
159+
160+
if (options.onLoad) {
161+
// eslint-disable-next-line @typescript-eslint/unbound-method
162+
script.onload = options.onLoad;
163+
}
164+
165+
const injectionPoint = global.document.head || global.document.body;
166+
if (injectionPoint) {
167+
injectionPoint.appendChild(script);
168+
} else {
169+
IS_DEBUG_BUILD && logger.error('Not injecting report dialog. No injection point found in HTML');
140170
}
141171
}
142172

packages/browser/test/unit/index.test.ts

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { SDK_VERSION } from '@sentry/core';
1+
import { getReportDialogEndpoint, SDK_VERSION } from '@sentry/core';
22

33
import {
44
addBreadcrumb,
@@ -24,6 +24,14 @@ const dsn = 'https://[email protected]/4291';
2424
// eslint-disable-next-line no-var
2525
declare var global: any;
2626

27+
jest.mock('@sentry/core', () => {
28+
const original = jest.requireActual('@sentry/core');
29+
return {
30+
...original,
31+
getReportDialogEndpoint: jest.fn(),
32+
};
33+
});
34+
2735
describe('SentryBrowser', () => {
2836
const beforeSend = jest.fn();
2937

@@ -74,16 +82,14 @@ describe('SentryBrowser', () => {
7482
});
7583

7684
describe('showReportDialog', () => {
85+
beforeEach(() => {
86+
(getReportDialogEndpoint as jest.Mock).mockReset();
87+
});
88+
7789
describe('user', () => {
7890
const EX_USER = { email: '[email protected]' };
7991
const options = getDefaultBrowserClientOptions({ dsn });
8092
const client = new BrowserClient(options);
81-
const reportDialogSpy = jest.spyOn(client, 'showReportDialog');
82-
83-
beforeEach(() => {
84-
reportDialogSpy.mockReset();
85-
});
86-
8793
it('uses the user on the scope', () => {
8894
configureScope(scope => {
8995
scope.setUser(EX_USER);
@@ -92,8 +98,11 @@ describe('SentryBrowser', () => {
9298

9399
showReportDialog();
94100

95-
expect(reportDialogSpy).toBeCalled();
96-
expect(reportDialogSpy.mock.calls[0][0]!.user!.email).toBe(EX_USER.email);
101+
expect(getReportDialogEndpoint).toHaveBeenCalledTimes(1);
102+
expect(getReportDialogEndpoint).toHaveBeenCalledWith(
103+
expect.any(Object),
104+
expect.objectContaining({ user: { email: EX_USER.email } }),
105+
);
97106
});
98107

99108
it('prioritizes options user over scope user', () => {
@@ -105,8 +114,11 @@ describe('SentryBrowser', () => {
105114
const DIALOG_OPTION_USER = { email: '[email protected]' };
106115
showReportDialog({ user: DIALOG_OPTION_USER });
107116

108-
expect(reportDialogSpy).toBeCalled();
109-
expect(reportDialogSpy.mock.calls[0][0]!.user!.email).toBe(DIALOG_OPTION_USER.email);
117+
expect(getReportDialogEndpoint).toHaveBeenCalledTimes(1);
118+
expect(getReportDialogEndpoint).toHaveBeenCalledWith(
119+
expect.any(Object),
120+
expect.objectContaining({ user: { email: DIALOG_OPTION_USER.email } }),
121+
);
110122
});
111123
});
112124
});

packages/core/src/api.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,15 @@ export function getReportDialogEndpoint(
5353
}
5454

5555
if (key === 'user') {
56-
if (!dialogOptions.user) {
56+
const user = dialogOptions.user;
57+
if (!user) {
5758
continue;
5859
}
59-
if (dialogOptions.user.name) {
60-
encodedOptions += `&name=${encodeURIComponent(dialogOptions.user.name)}`;
60+
if (user.name) {
61+
encodedOptions += `&name=${encodeURIComponent(user.name)}`;
6162
}
62-
if (dialogOptions.user.email) {
63-
encodedOptions += `&email=${encodeURIComponent(dialogOptions.user.email)}`;
63+
if (user.email) {
64+
encodedOptions += `&email=${encodeURIComponent(user.email)}`;
6465
}
6566
} else {
6667
encodedOptions += `&${encodeURIComponent(key)}=${encodeURIComponent(dialogOptions[key] as string)}`;

0 commit comments

Comments
 (0)