From 817eac82107f668f71ab51e8d071ca62c472e62d Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Wed, 27 Nov 2024 14:00:17 +0200 Subject: [PATCH 01/68] Update the client implementation to use the new capture feedback js api --- packages/core/src/js/client.ts | 16 +++----- packages/core/test/client.test.ts | 64 ++++++++++++++----------------- packages/core/test/testutils.ts | 9 +---- 3 files changed, 34 insertions(+), 55 deletions(-) diff --git a/packages/core/src/js/client.ts b/packages/core/src/js/client.ts index b9fe2ad27d..d2f7b1ab71 100644 --- a/packages/core/src/js/client.ts +++ b/packages/core/src/js/client.ts @@ -1,4 +1,4 @@ -import { eventFromException, eventFromMessage } from '@sentry/browser'; +import { captureFeedback as captureFeedbackApi, eventFromException, eventFromMessage } from '@sentry/browser'; import { BaseClient } from '@sentry/core'; import type { ClientReportEnvelope, @@ -7,9 +7,9 @@ import type { Event, EventHint, Outcome, + SendFeedbackParams, SeverityLevel, TransportMakeRequestResponse, - UserFeedback, } from '@sentry/types'; import { dateTimestampInSeconds, logger, SentryError } from '@sentry/utils'; import { Alert } from 'react-native'; @@ -20,7 +20,7 @@ import { getDefaultSidecarUrl } from './integrations/spotlight'; import type { ReactNativeClientOptions } from './options'; import type { mobileReplayIntegration } from './replay/mobilereplay'; import { MOBILE_REPLAY_INTEGRATION_NAME } from './replay/mobilereplay'; -import { createUserFeedbackEnvelope, items } from './utils/envelope'; +import { items } from './utils/envelope'; import { ignoreRequireCycleLogs } from './utils/ignorerequirecyclelogs'; import { mergeOutcomes } from './utils/outcome'; import { ReactNativeLibraries } from './utils/rnlibraries'; @@ -86,14 +86,8 @@ export class ReactNativeClient extends BaseClient { /** * Sends user feedback to Sentry. */ - public captureUserFeedback(feedback: UserFeedback): void { - const envelope = createUserFeedbackEnvelope(feedback, { - metadata: this._options._metadata, - dsn: this.getDsn(), - tunnel: undefined, - }); - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.sendEnvelope(envelope); + public captureFeedback(feedback: SendFeedbackParams): void { + captureFeedbackApi(feedback); } /** diff --git a/packages/core/test/client.test.ts b/packages/core/test/client.test.ts index a0e5b13201..9d4a430089 100644 --- a/packages/core/test/client.test.ts +++ b/packages/core/test/client.test.ts @@ -1,8 +1,15 @@ import * as mockedtimetodisplaynative from './tracing/mockedtimetodisplaynative'; jest.mock('../src/js/tracing/timetodisplaynative', () => mockedtimetodisplaynative); -import { defaultStackParser } from '@sentry/browser'; -import type { Envelope, Event, Outcome, Transport, TransportMakeRequestResponse } from '@sentry/types'; +import { captureFeedback as captureFeedbackApi, defaultStackParser } from '@sentry/browser'; +import type { + Envelope, + Event, + Outcome, + SendFeedbackParams, + Transport, + TransportMakeRequestResponse, +} from '@sentry/types'; import { rejectedSyncPromise, SentryError } from '@sentry/utils'; import * as RN from 'react-native'; @@ -19,7 +26,6 @@ import { envelopeItems, firstArg, getMockSession, - getMockUserFeedback, getSyncPromiseRejectOnFirstCall, } from './testutils'; @@ -76,6 +82,14 @@ jest.mock( }), ); +jest.mock('@sentry/browser', () => { + const actual = jest.requireActual('@sentry/browser'); + return { + ...actual, + captureFeedback: jest.fn(), + }; +}); + const EXAMPLE_DSN = 'https://6890c2f6677340daa4804f8194804ea2@o19635.ingest.sentry.io/148053'; const DEFAULT_OPTIONS: ReactNativeClientOptions = { @@ -187,15 +201,6 @@ describe('Tests ReactNativeClient', () => { expect(mockTransport.send).not.toBeCalled(); }); - test('captureUserFeedback does not call transport when enabled false', () => { - const mockTransport = createMockTransport(); - const client = createDisabledClientWith(mockTransport); - - client.captureUserFeedback(getMockUserFeedback()); - - expect(mockTransport.send).not.toBeCalled(); - }); - function createDisabledClientWith(transport: Transport) { return new ReactNativeClient({ ...DEFAULT_OPTIONS, @@ -290,34 +295,26 @@ describe('Tests ReactNativeClient', () => { }); describe('UserFeedback', () => { - test('sends UserFeedback to native Layer', () => { + test('sends UserFeedback', () => { const mockTransportSend: jest.Mock = jest.fn(() => Promise.resolve()); const client = new ReactNativeClient({ ...DEFAULT_OPTIONS, dsn: EXAMPLE_DSN, - transport: () => ({ - send: mockTransportSend, - flush: jest.fn(), - }), }); + jest.mock('@sentry/browser', () => ({ + captureFeedback: jest.fn(), + })); - client.captureUserFeedback({ - comments: 'Test Comments', + const feedback: SendFeedbackParams = { + message: 'Test Comments', email: 'test@email.com', name: 'Test User', - event_id: 'testEvent123', - }); + associatedEventId: 'testEvent123', + }; - expect(mockTransportSend.mock.calls[0][firstArg][envelopeHeader].event_id).toEqual('testEvent123'); - expect(mockTransportSend.mock.calls[0][firstArg][envelopeItems][0][envelopeItemHeader].type).toEqual( - 'user_report', - ); - expect(mockTransportSend.mock.calls[0][firstArg][envelopeItems][0][envelopeItemPayload]).toEqual({ - comments: 'Test Comments', - email: 'test@email.com', - name: 'Test User', - event_id: 'testEvent123', - }); + client.captureFeedback(feedback); + + expect(captureFeedbackApi).toHaveBeenCalledWith(feedback); }); }); @@ -417,11 +414,6 @@ describe('Tests ReactNativeClient', () => { client.captureSession(getMockSession()); expect(getSdkInfoFrom(mockTransportSend)).toStrictEqual(expectedSdkInfo); }); - - test('send SdkInfo in the user feedback envelope header', () => { - client.captureUserFeedback(getMockUserFeedback()); - expect(getSdkInfoFrom(mockTransportSend)).toStrictEqual(expectedSdkInfo); - }); }); describe('event data enhancement', () => { diff --git a/packages/core/test/testutils.ts b/packages/core/test/testutils.ts index 4548eef092..593a51f838 100644 --- a/packages/core/test/testutils.ts +++ b/packages/core/test/testutils.ts @@ -1,4 +1,4 @@ -import type { Session, Transport, UserFeedback } from '@sentry/types'; +import type { Session, Transport } from '@sentry/types'; import { rejectedSyncPromise } from '@sentry/utils'; export type MockInterface = { @@ -36,13 +36,6 @@ export const getMockSession = (): Session => ({ }), }); -export const getMockUserFeedback = (): UserFeedback => ({ - comments: 'comments_test_value', - email: 'email_test_value', - name: 'name_test_value', - event_id: 'event_id_test_value', -}); - export const getSyncPromiseRejectOnFirstCall = (reason: unknown): jest.Mock => { let shouldSyncReject = true; return jest.fn((..._args: Y) => { From 5370a990006b5d53b738ebc250c7e240a7bffa36 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Wed, 27 Nov 2024 14:00:54 +0200 Subject: [PATCH 02/68] Updates SDK API --- packages/core/src/js/index.ts | 13 ++++++++++++- packages/core/src/js/sdk.tsx | 18 ++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/packages/core/src/js/index.ts b/packages/core/src/js/index.ts index f62a8624eb..079ec253d4 100644 --- a/packages/core/src/js/index.ts +++ b/packages/core/src/js/index.ts @@ -4,6 +4,7 @@ export type { SdkInfo, Event, Exception, + SendFeedbackParams, SeverityLevel, StackFrame, Stacktrace, @@ -59,7 +60,17 @@ export { SDK_NAME, SDK_VERSION } from './version'; export type { ReactNativeOptions } from './options'; export { ReactNativeClient } from './client'; -export { init, wrap, nativeCrash, flush, close, captureUserFeedback, withScope, crashedLastRun } from './sdk'; +export { + init, + wrap, + nativeCrash, + flush, + close, + captureFeedback, + captureUserFeedback, + withScope, + crashedLastRun, +} from './sdk'; export { TouchEventBoundary, withTouchEventBoundary } from './touchevents'; export { diff --git a/packages/core/src/js/sdk.tsx b/packages/core/src/js/sdk.tsx index 039b44850d..f17a6ef058 100644 --- a/packages/core/src/js/sdk.tsx +++ b/packages/core/src/js/sdk.tsx @@ -4,7 +4,7 @@ import { defaultStackParser, makeFetchTransport, } from '@sentry/react'; -import type { Breadcrumb, BreadcrumbHint, Integration, Scope, UserFeedback } from '@sentry/types'; +import type { Breadcrumb, BreadcrumbHint, Integration, Scope, SendFeedbackParams, UserFeedback } from '@sentry/types'; import { logger, stackParserFromStackParserOptions } from '@sentry/utils'; import * as React from 'react'; @@ -219,9 +219,23 @@ export async function close(): Promise { /** * Captures user feedback and sends it to Sentry. + * @deprecated Use `Sentry.captureFeedback` instead. */ export function captureUserFeedback(feedback: UserFeedback): void { - getClient()?.captureUserFeedback(feedback); + const feedbackParams = { + name: feedback.name, + email: feedback.email, + message: feedback.comments, + associatedEventId: feedback.event_id, + }; + captureFeedback(feedbackParams); +} + +/** + * Captures user feedback and sends it to Sentry. + */ +export function captureFeedback(feedbackParams: SendFeedbackParams): void { + getClient()?.captureFeedback(feedbackParams); } /** From 42e2fa14823f067a6b4088770f5ff0aea9a22b69 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Wed, 27 Nov 2024 14:01:18 +0200 Subject: [PATCH 03/68] Adds new feedback button in the sample --- .../src/components/UserFeedbackModal.tsx | 19 ++++++++++++++++++- .../src/components/UserFeedbackModal.tsx | 19 ++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/samples/react-native-macos/src/components/UserFeedbackModal.tsx b/samples/react-native-macos/src/components/UserFeedbackModal.tsx index 60e17af757..1f8dd6a4a3 100644 --- a/samples/react-native-macos/src/components/UserFeedbackModal.tsx +++ b/samples/react-native-macos/src/components/UserFeedbackModal.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { View, StyleSheet, Text, TextInput, Image, Button } from 'react-native'; import * as Sentry from '@sentry/react-native'; -import { UserFeedback } from '@sentry/react-native'; +import { SendFeedbackParams, UserFeedback } from '@sentry/react-native'; export const DEFAULT_COMMENTS = "It's broken again! Please fix it."; @@ -48,6 +48,23 @@ export function UserFeedbackModal(props: { onDismiss: () => void }) { }} /> +