Skip to content

Commit b2f7f82

Browse files
authored
Merge 9ea5496 into 8ab11b6
2 parents 8ab11b6 + 9ea5496 commit b2f7f82

File tree

8 files changed

+116
-61
lines changed

8 files changed

+116
-61
lines changed

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,24 @@
88
99
## Unreleased
1010

11+
### Features
12+
13+
- Adds new `captureFeedback` and deprecates the `captureUserFeedback` API ([#4320](https://github.com/getsentry/sentry-react-native/pull/4320))
14+
15+
16+
```jsx
17+
import * as Sentry from "@sentry/react-native";
18+
19+
const eventId = Sentry.lastEventId();
20+
21+
Sentry.captureFeedback({
22+
name: "John Doe",
23+
24+
message: "Hello World!",
25+
associatedEventId: eventId, // optional
26+
});
27+
```
28+
1129
### Fixes
1230

1331
- Return `lastEventId` export from `@sentry/core` ([#4315](https://github.com/getsentry/sentry-react-native/pull/4315))

packages/core/src/js/client.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { eventFromException, eventFromMessage } from '@sentry/browser';
1+
import { captureFeedback as captureFeedbackApi, eventFromException, eventFromMessage } from '@sentry/browser';
22
import { BaseClient } from '@sentry/core';
33
import type {
44
ClientReportEnvelope,
@@ -7,9 +7,9 @@ import type {
77
Event,
88
EventHint,
99
Outcome,
10+
SendFeedbackParams,
1011
SeverityLevel,
1112
TransportMakeRequestResponse,
12-
UserFeedback,
1313
} from '@sentry/types';
1414
import { dateTimestampInSeconds, logger, SentryError } from '@sentry/utils';
1515
import { Alert } from 'react-native';
@@ -20,7 +20,7 @@ import { getDefaultSidecarUrl } from './integrations/spotlight';
2020
import type { ReactNativeClientOptions } from './options';
2121
import type { mobileReplayIntegration } from './replay/mobilereplay';
2222
import { MOBILE_REPLAY_INTEGRATION_NAME } from './replay/mobilereplay';
23-
import { createUserFeedbackEnvelope, items } from './utils/envelope';
23+
import { items } from './utils/envelope';
2424
import { ignoreRequireCycleLogs } from './utils/ignorerequirecyclelogs';
2525
import { mergeOutcomes } from './utils/outcome';
2626
import { ReactNativeLibraries } from './utils/rnlibraries';
@@ -86,14 +86,8 @@ export class ReactNativeClient extends BaseClient<ReactNativeClientOptions> {
8686
/**
8787
* Sends user feedback to Sentry.
8888
*/
89-
public captureUserFeedback(feedback: UserFeedback): void {
90-
const envelope = createUserFeedbackEnvelope(feedback, {
91-
metadata: this._options._metadata,
92-
dsn: this.getDsn(),
93-
tunnel: undefined,
94-
});
95-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
96-
this.sendEnvelope(envelope);
89+
public captureFeedback(feedback: SendFeedbackParams): void {
90+
captureFeedbackApi(feedback);
9791
}
9892

9993
/**

packages/core/src/js/index.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export type {
44
SdkInfo,
55
Event,
66
Exception,
7+
SendFeedbackParams,
78
SeverityLevel,
89
StackFrame,
910
Stacktrace,
@@ -59,7 +60,17 @@ export { SDK_NAME, SDK_VERSION } from './version';
5960
export type { ReactNativeOptions } from './options';
6061
export { ReactNativeClient } from './client';
6162

62-
export { init, wrap, nativeCrash, flush, close, captureUserFeedback, withScope, crashedLastRun } from './sdk';
63+
export {
64+
init,
65+
wrap,
66+
nativeCrash,
67+
flush,
68+
close,
69+
captureFeedback,
70+
captureUserFeedback,
71+
withScope,
72+
crashedLastRun,
73+
} from './sdk';
6374
export { TouchEventBoundary, withTouchEventBoundary } from './touchevents';
6475

6576
export {

packages/core/src/js/sdk.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
defaultStackParser,
55
makeFetchTransport,
66
} from '@sentry/react';
7-
import type { Breadcrumb, BreadcrumbHint, Integration, Scope, UserFeedback } from '@sentry/types';
7+
import type { Breadcrumb, BreadcrumbHint, Integration, Scope, SendFeedbackParams, UserFeedback } from '@sentry/types';
88
import { logger, stackParserFromStackParserOptions } from '@sentry/utils';
99
import * as React from 'react';
1010

@@ -219,9 +219,23 @@ export async function close(): Promise<void> {
219219

220220
/**
221221
* Captures user feedback and sends it to Sentry.
222+
* @deprecated Use `Sentry.captureFeedback` instead.
222223
*/
223224
export function captureUserFeedback(feedback: UserFeedback): void {
224-
getClient<ReactNativeClient>()?.captureUserFeedback(feedback);
225+
const feedbackParams = {
226+
name: feedback.name,
227+
email: feedback.email,
228+
message: feedback.comments,
229+
associatedEventId: feedback.event_id,
230+
};
231+
captureFeedback(feedbackParams);
232+
}
233+
234+
/**
235+
* Captures user feedback and sends it to Sentry.
236+
*/
237+
export function captureFeedback(feedbackParams: SendFeedbackParams): void {
238+
getClient<ReactNativeClient>()?.captureFeedback(feedbackParams);
225239
}
226240

227241
/**

packages/core/test/client.test.ts

Lines changed: 28 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
import * as mockedtimetodisplaynative from './tracing/mockedtimetodisplaynative';
22
jest.mock('../src/js/tracing/timetodisplaynative', () => mockedtimetodisplaynative);
33

4-
import { defaultStackParser } from '@sentry/browser';
5-
import type { Envelope, Event, Outcome, Transport, TransportMakeRequestResponse } from '@sentry/types';
4+
import { captureFeedback as captureFeedbackApi, defaultStackParser } from '@sentry/browser';
5+
import type {
6+
Envelope,
7+
Event,
8+
Outcome,
9+
SendFeedbackParams,
10+
Transport,
11+
TransportMakeRequestResponse,
12+
} from '@sentry/types';
613
import { rejectedSyncPromise, SentryError } from '@sentry/utils';
714
import * as RN from 'react-native';
815

@@ -19,7 +26,6 @@ import {
1926
envelopeItems,
2027
firstArg,
2128
getMockSession,
22-
getMockUserFeedback,
2329
getSyncPromiseRejectOnFirstCall,
2430
} from './testutils';
2531

@@ -76,6 +82,14 @@ jest.mock(
7682
}),
7783
);
7884

85+
jest.mock('@sentry/browser', () => {
86+
const actual = jest.requireActual('@sentry/browser');
87+
return {
88+
...actual,
89+
captureFeedback: jest.fn(),
90+
};
91+
});
92+
7993
const EXAMPLE_DSN = 'https://[email protected]/148053';
8094

8195
const DEFAULT_OPTIONS: ReactNativeClientOptions = {
@@ -187,15 +201,6 @@ describe('Tests ReactNativeClient', () => {
187201
expect(mockTransport.send).not.toBeCalled();
188202
});
189203

190-
test('captureUserFeedback does not call transport when enabled false', () => {
191-
const mockTransport = createMockTransport();
192-
const client = createDisabledClientWith(mockTransport);
193-
194-
client.captureUserFeedback(getMockUserFeedback());
195-
196-
expect(mockTransport.send).not.toBeCalled();
197-
});
198-
199204
function createDisabledClientWith(transport: Transport) {
200205
return new ReactNativeClient({
201206
...DEFAULT_OPTIONS,
@@ -290,34 +295,25 @@ describe('Tests ReactNativeClient', () => {
290295
});
291296

292297
describe('UserFeedback', () => {
293-
test('sends UserFeedback to native Layer', () => {
294-
const mockTransportSend: jest.Mock = jest.fn(() => Promise.resolve());
298+
test('sends UserFeedback', () => {
295299
const client = new ReactNativeClient({
296300
...DEFAULT_OPTIONS,
297301
dsn: EXAMPLE_DSN,
298-
transport: () => ({
299-
send: mockTransportSend,
300-
flush: jest.fn(),
301-
}),
302302
});
303+
jest.mock('@sentry/browser', () => ({
304+
captureFeedback: jest.fn(),
305+
}));
303306

304-
client.captureUserFeedback({
305-
comments: 'Test Comments',
307+
const feedback: SendFeedbackParams = {
308+
message: 'Test Comments',
306309
307310
name: 'Test User',
308-
event_id: 'testEvent123',
309-
});
311+
associatedEventId: 'testEvent123',
312+
};
310313

311-
expect(mockTransportSend.mock.calls[0][firstArg][envelopeHeader].event_id).toEqual('testEvent123');
312-
expect(mockTransportSend.mock.calls[0][firstArg][envelopeItems][0][envelopeItemHeader].type).toEqual(
313-
'user_report',
314-
);
315-
expect(mockTransportSend.mock.calls[0][firstArg][envelopeItems][0][envelopeItemPayload]).toEqual({
316-
comments: 'Test Comments',
317-
318-
name: 'Test User',
319-
event_id: 'testEvent123',
320-
});
314+
client.captureFeedback(feedback);
315+
316+
expect(captureFeedbackApi).toHaveBeenCalledWith(feedback);
321317
});
322318
});
323319

@@ -417,11 +413,6 @@ describe('Tests ReactNativeClient', () => {
417413
client.captureSession(getMockSession());
418414
expect(getSdkInfoFrom(mockTransportSend)).toStrictEqual(expectedSdkInfo);
419415
});
420-
421-
test('send SdkInfo in the user feedback envelope header', () => {
422-
client.captureUserFeedback(getMockUserFeedback());
423-
expect(getSdkInfoFrom(mockTransportSend)).toStrictEqual(expectedSdkInfo);
424-
});
425416
});
426417

427418
describe('event data enhancement', () => {

packages/core/test/testutils.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Session, Transport, UserFeedback } from '@sentry/types';
1+
import type { Session, Transport } from '@sentry/types';
22
import { rejectedSyncPromise } from '@sentry/utils';
33

44
export type MockInterface<T> = {
@@ -36,13 +36,6 @@ export const getMockSession = (): Session => ({
3636
}),
3737
});
3838

39-
export const getMockUserFeedback = (): UserFeedback => ({
40-
comments: 'comments_test_value',
41-
email: 'email_test_value',
42-
name: 'name_test_value',
43-
event_id: 'event_id_test_value',
44-
});
45-
4639
export const getSyncPromiseRejectOnFirstCall = <Y extends any[]>(reason: unknown): jest.Mock => {
4740
let shouldSyncReject = true;
4841
return jest.fn((..._args: Y) => {

samples/react-native-macos/src/components/UserFeedbackModal.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22
import { View, StyleSheet, Text, TextInput, Image, Button } from 'react-native';
33
import * as Sentry from '@sentry/react-native';
4-
import { UserFeedback } from '@sentry/react-native';
4+
import { SendFeedbackParams, UserFeedback } from '@sentry/react-native';
55

66
export const DEFAULT_COMMENTS = "It's broken again! Please fix it.";
77

@@ -48,6 +48,23 @@ export function UserFeedbackModal(props: { onDismiss: () => void }) {
4848
}}
4949
/>
5050
<View style={styles.buttonSpacer} />
51+
<Button
52+
title="Send feedback without event"
53+
color="#6C5FC7"
54+
onPress={async () => {
55+
onDismiss();
56+
57+
const userFeedback: SendFeedbackParams = {
58+
message: comments,
59+
name: 'John Doe',
60+
61+
};
62+
63+
Sentry.captureFeedback(userFeedback);
64+
clearComments();
65+
}}
66+
/>
67+
<View style={styles.buttonSpacer} />
5168
<Button
5269
title="Close"
5370
color="#6C5FC7"

samples/react-native/src/components/UserFeedbackModal.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22
import { View, StyleSheet, Text, TextInput, Image, Button } from 'react-native';
33
import * as Sentry from '@sentry/react-native';
4-
import { UserFeedback } from '@sentry/react-native';
4+
import { SendFeedbackParams, UserFeedback } from '@sentry/react-native';
55

66
export const DEFAULT_COMMENTS = "It's broken again! Please fix it.";
77

@@ -48,6 +48,23 @@ export function UserFeedbackModal(props: { onDismiss: () => void }) {
4848
}}
4949
/>
5050
<View style={styles.buttonSpacer} />
51+
<Button
52+
title="Send feedback without event"
53+
color="#6C5FC7"
54+
onPress={async () => {
55+
onDismiss();
56+
57+
const userFeedback: SendFeedbackParams = {
58+
message: comments,
59+
name: 'John Doe',
60+
61+
};
62+
63+
Sentry.captureFeedback(userFeedback);
64+
clearComments();
65+
}}
66+
/>
67+
<View style={styles.buttonSpacer} />
5168
<Button
5269
title="Close"
5370
color="#6C5FC7"

0 commit comments

Comments
 (0)