diff --git a/CHANGELOG.md b/CHANGELOG.md index a4015a30a9..37b7ffd5db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## Unreleased + +- feat: Add `wrap` wrapper method with profiler and touch event boundary #1728 +- feat: App-start measurements, if using the `wrap` wrapper, will now finish on the root component mount #1728 + ## 3.0.0-beta.2 - feat: Native slow/frozen frames measurements #1711 diff --git a/package.json b/package.json index cdcf1a00fe..4d2d16a163 100644 --- a/package.json +++ b/package.json @@ -40,19 +40,19 @@ "react-native": ">=0.56.0" }, "dependencies": { - "@sentry/browser": "6.7.1", - "@sentry/core": "6.7.1", - "@sentry/hub": "6.7.1", - "@sentry/integrations": "6.7.1", - "@sentry/react": "6.7.1", - "@sentry/tracing": "6.7.1", - "@sentry/types": "6.7.1", - "@sentry/utils": "6.7.1", + "@sentry/browser": "6.12.0-beta.2", + "@sentry/core": "6.12.0-beta.2", + "@sentry/hub": "6.12.0-beta.2", + "@sentry/integrations": "6.12.0-beta.2", + "@sentry/react": "6.12.0-beta.2", + "@sentry/tracing": "6.12.0-beta.2", + "@sentry/types": "6.12.0-beta.2", + "@sentry/utils": "6.12.0-beta.2", "@sentry/wizard": "^1.2.2" }, "devDependencies": { - "@sentry-internal/eslint-config-sdk": "6.7.1", - "@sentry-internal/eslint-plugin-sdk": "6.7.1", + "@sentry-internal/eslint-config-sdk": "6.12.0-beta.2", + "@sentry-internal/eslint-plugin-sdk": "6.12.0-beta.2", "@sentry/typescript": "^5.20.0", "@types/jest": "^26.0.15", "@types/react": "^16.9.49", diff --git a/sample/src/App.tsx b/sample/src/App.tsx index 3ff246f63f..d8628d2681 100644 --- a/sample/src/App.tsx +++ b/sample/src/App.tsx @@ -25,7 +25,6 @@ const reactNavigationV5Instrumentation = new Sentry.ReactNavigationV5Instrumenta routeChangeTimeoutMs: 500, // How long it will wait for the route change to complete. Default is 1000ms }, ); - Sentry.init({ // Replace the example DSN below with your own DSN: dsn: SENTRY_INTERNAL_DSN, @@ -71,38 +70,29 @@ const App = () => { const navigation = React.useRef(); return ( - - - { - reactNavigationV5Instrumentation.registerNavigationContainer( - navigation, - ); - }}> - - - - - - - - - - - + + { + reactNavigationV5Instrumentation.registerNavigationContainer( + navigation, + ); + }}> + + + + + + + + + + ); }; -export default Sentry.withTouchEventBoundary(App, { - ignoreNames: ['Provider', 'UselessName', /^SomeRegex/], -}); +// Wrap your app to get more features out of the box such as auto performance monitoring. +export default Sentry.wrap(App); diff --git a/src/js/index.ts b/src/js/index.ts index 8a9bd9dd8b..efedd78ea1 100644 --- a/src/js/index.ts +++ b/src/js/index.ts @@ -59,8 +59,18 @@ import { SDK_NAME, SDK_VERSION } from "./version"; export { ReactNativeBackend } from "./backend"; export { ReactNativeOptions } from "./options"; export { ReactNativeClient } from "./client"; -// eslint-disable-next-line deprecation/deprecation -export { init, setDist, setRelease, nativeCrash, flush, close } from "./sdk"; + +export { + init, + wrap, + // eslint-disable-next-line deprecation/deprecation + setDist, + // eslint-disable-next-line deprecation/deprecation + setRelease, + nativeCrash, + flush, + close, +} from "./sdk"; export { TouchEventBoundary, withTouchEventBoundary } from "./touchevents"; export { diff --git a/src/js/integrations/index.ts b/src/js/integrations/index.ts index 51f0baee77..ba83c2f408 100644 --- a/src/js/integrations/index.ts +++ b/src/js/integrations/index.ts @@ -2,4 +2,3 @@ export { DebugSymbolicator } from "./debugsymbolicator"; export { DeviceContext } from "./devicecontext"; export { ReactNativeErrorHandlers } from "./reactnativeerrorhandlers"; export { Release } from "./release"; -export { StallTracking } from "./stalltracking"; diff --git a/src/js/measurements.ts b/src/js/measurements.ts index 5b20f135dc..7cb64dc4c7 100644 --- a/src/js/measurements.ts +++ b/src/js/measurements.ts @@ -1,9 +1,7 @@ import { getCurrentHub, getMainCarrier, Hub } from "@sentry/hub"; import { Transaction } from "@sentry/tracing"; import { CustomSamplingContext, TransactionContext } from "@sentry/types"; -import { logger } from "@sentry/utils"; -import { StallTracking } from "./integrations"; import { ReactNativeTracing } from "./tracing"; /** @@ -58,40 +56,20 @@ const _patchStartTransaction = ( const reactNativeTracing = getCurrentHub().getIntegration( ReactNativeTracing ); - const stallTracking = getCurrentHub().getIntegration(StallTracking); if (reactNativeTracing) { reactNativeTracing.onTransactionStart(transaction); - } - - if (stallTracking) { - if (!transactionContext.startTimestamp) { - const finishStallTracking = stallTracking.registerTransactionStart( - transaction - ); - - // eslint-disable-next-line @typescript-eslint/unbound-method - const originalFinish = transaction.finish; - - transaction.finish = (endTimestamp: number | undefined) => { - const stallMeasurements = finishStallTracking(); - // Sometimes the measurements will not be tracked due to some underlying reason, we don't add them in that case. - if (stallMeasurements) { - transaction.setMeasurements(stallMeasurements); - } + // eslint-disable-next-line @typescript-eslint/unbound-method + const originalFinish = transaction.finish; - if (reactNativeTracing) { - reactNativeTracing.onTransactionFinish(transaction); - } + transaction.finish = (endTimestamp: number | undefined) => { + if (reactNativeTracing) { + reactNativeTracing.onTransactionFinish(transaction); + } - return originalFinish.apply(transaction, [endTimestamp]); - }; - } else { - logger.log( - "[StallTracking] Stalls will not be tracked due to `startTimestamp` being set." - ); - } + return originalFinish.apply(transaction, [endTimestamp]); + }; } return transaction; diff --git a/src/js/options.ts b/src/js/options.ts index 149ba8070e..914ab09424 100644 --- a/src/js/options.ts +++ b/src/js/options.ts @@ -1,4 +1,7 @@ import { BrowserOptions } from "@sentry/react"; +import { ProfilerProps } from "@sentry/react/dist/profiler"; + +import { TouchEventBoundaryProps } from "./touchevents"; /** * Configuration options for the Sentry ReactNative SDK. @@ -66,9 +69,14 @@ export interface ReactNativeOptions extends BrowserOptions { didCallNativeInit: boolean; }) => void; - /** Enable JS event loop stall tracking. Enabled by default. */ - enableStallTracking?: boolean; - /** Enable auto performance tracking by default. */ enableAutoPerformanceTracking?: boolean; } + +export interface ReactNativeWrapperOptions { + /** Props for the root React profiler */ + profilerProps?: ProfilerProps; + + /** Props for the root touch event boundary */ + touchEventBoundaryProps?: TouchEventBoundaryProps; +} diff --git a/src/js/sdk.ts b/src/js/sdk.tsx similarity index 80% rename from src/js/sdk.ts rename to src/js/sdk.tsx index ad89d719df..9f6f7eff6c 100644 --- a/src/js/sdk.ts +++ b/src/js/sdk.tsx @@ -4,6 +4,7 @@ import { RewriteFrames } from "@sentry/integrations"; import { defaultIntegrations, getCurrentHub } from "@sentry/react"; import { StackFrame } from "@sentry/types"; import { getGlobalObject, logger } from "@sentry/utils"; +import * as React from "react"; import { ReactNativeClient } from "./client"; import { @@ -11,11 +12,11 @@ import { DeviceContext, ReactNativeErrorHandlers, Release, - StallTracking, } from "./integrations"; -import { ReactNativeOptions } from "./options"; +import { ReactNativeOptions, ReactNativeWrapperOptions } from "./options"; import { ReactNativeScope } from "./scope"; -import { ReactNativeTracing } from "./tracing"; +import { TouchEventBoundary } from "./touchevents"; +import { ReactNativeProfiler, ReactNativeTracing } from "./tracing"; const IGNORED_DEFAULT_INTEGRATIONS = [ "GlobalHandlers", // We will use the react-native internal handlers @@ -26,12 +27,11 @@ const DEFAULT_OPTIONS: ReactNativeOptions = { enableNativeCrashHandling: true, enableNativeNagger: true, autoInitializeNativeSdk: true, - enableStallTracking: true, enableAutoPerformanceTracking: true, }; /** - * Inits the SDK + * Inits the SDK and returns the final options. */ export function init(passedOptions: ReactNativeOptions): void { const reactNativeHub = new Hub(undefined, new ReactNativeScope()); @@ -89,10 +89,6 @@ export function init(passedOptions: ReactNativeOptions): void { if (tracingEnabled) { if (options.enableAutoPerformanceTracking) { options.defaultIntegrations.push(new ReactNativeTracing()); - - if (options.enableStallTracking) { - options.defaultIntegrations.push(new StallTracking()); - } } } } @@ -108,6 +104,36 @@ export function init(passedOptions: ReactNativeOptions): void { } } +/** + * Inits the Sentry React Native SDK with automatic instrumentation and wrapped features. + */ +export function wrap

( + RootComponent: React.ComponentType

, + options?: ReactNativeWrapperOptions +): React.ComponentType

{ + const tracingIntegration = getCurrentHub().getIntegration(ReactNativeTracing); + if (tracingIntegration) { + tracingIntegration.useAppStartWithProfiler = true; + } + + const profilerProps = { + ...(options?.profilerProps ?? {}), + name: RootComponent.displayName ?? "Root", + }; + + const RootApp: React.FC

= (appProps) => { + return ( + + + + + + ); + }; + + return RootApp; +} + /** * Deprecated. Sets the release on the event. * NOTE: Does not set the release on sessions. diff --git a/src/js/tracing/index.ts b/src/js/tracing/index.ts index 09ea506d3d..f3b15948ce 100644 --- a/src/js/tracing/index.ts +++ b/src/js/tracing/index.ts @@ -12,3 +12,4 @@ export { ReactNavigationRoute, ReactNavigationTransactionContext, } from "./types"; +export { ReactNativeProfiler } from "./reactnativeprofiler"; diff --git a/src/js/tracing/reactnativeprofiler.tsx b/src/js/tracing/reactnativeprofiler.tsx new file mode 100644 index 0000000000..018e43cc03 --- /dev/null +++ b/src/js/tracing/reactnativeprofiler.tsx @@ -0,0 +1,26 @@ +import { getCurrentHub, Profiler } from "@sentry/react"; + +import { ReactNativeTracing } from "./reactnativetracing"; + +/** + * Custom profiler for the React Native app root. + */ +export class ReactNativeProfiler extends Profiler { + /** + * Get the app root mount time. + */ + public componentDidMount(): void { + super.componentDidMount(); + + const tracingIntegration = getCurrentHub().getIntegration( + ReactNativeTracing + ); + + if (this._mountSpan && tracingIntegration) { + if (typeof this._mountSpan.endTimestamp !== "undefined") { + // The first root component mount is the app start finish. + tracingIntegration.onAppStartFinish(this._mountSpan.endTimestamp); + } + } + } +} diff --git a/src/js/tracing/reactnativetracing.ts b/src/js/tracing/reactnativetracing.ts index e396c13d6c..4ae0c2dbe3 100644 --- a/src/js/tracing/reactnativetracing.ts +++ b/src/js/tracing/reactnativetracing.ts @@ -17,11 +17,15 @@ import { import { logger } from "@sentry/utils"; import { NativeAppStartResponse } from "../definitions"; -import { StallTracking } from "../integrations"; import { RoutingInstrumentationInstance } from "../tracing/routingInstrumentation"; import { NATIVE } from "../wrapper"; import { NativeFramesInstrumentation } from "./nativeframes"; -import { adjustTransactionDuration, getTimeOriginMilliseconds } from "./utils"; +import { StallTrackingInstrumentation } from "./stalltracking"; +import { + adjustTransactionDuration, + getTimeOriginMilliseconds, + isNearToNow, +} from "./utils"; export type BeforeNavigate = ( context: TransactionContext @@ -83,6 +87,11 @@ export interface ReactNativeTracingOptions * Track slow/frozen frames from the native layer and adds them as measurements to all transactions. */ enableNativeFramesTracking: boolean; + + /** + * Track when and how long the JS event loop stalls for. Adds stalls as measurements to all transactions. + */ + enableStallTracking: boolean; } const defaultReactNativeTracingOptions: ReactNativeTracingOptions = { @@ -93,6 +102,7 @@ const defaultReactNativeTracingOptions: ReactNativeTracingOptions = { beforeNavigate: (context) => context, enableAppStartTracking: true, enableNativeFramesTracking: true, + enableStallTracking: true, }; /** @@ -112,9 +122,12 @@ export class ReactNativeTracing implements Integration { public options: ReactNativeTracingOptions; public nativeFramesInstrumentation?: NativeFramesInstrumentation; + public stallTrackingInstrumentation?: StallTrackingInstrumentation; + public useAppStartWithProfiler: boolean = false; private _getCurrentHub?: () => Hub; private _awaitingAppStartData?: NativeAppStartResponse; + private _appStartFinishTimestamp?: number; public constructor(options: Partial = {}) { this.options = { @@ -139,12 +152,16 @@ export class ReactNativeTracing implements Integration { // @ts-ignore TODO shouldCreateSpanForRequest, routingInstrumentation, + enableAppStartTracking, enableNativeFramesTracking, + enableStallTracking, } = this.options; this._getCurrentHub = getCurrentHub; - void this._instrumentAppStart(); + if (enableAppStartTracking) { + void this._instrumentAppStart(); + } if (enableNativeFramesTracking) { this.nativeFramesInstrumentation = new NativeFramesInstrumentation( @@ -163,6 +180,10 @@ export class ReactNativeTracing implements Integration { NATIVE.disableNativeFramesTracking(); } + if (enableStallTracking) { + this.stallTrackingInstrumentation = new StallTrackingInstrumentation(); + } + if (routingInstrumentation) { routingInstrumentation.registerRoutingInstrumentation( this._onRouteWillChange.bind(this), @@ -186,14 +207,32 @@ export class ReactNativeTracing implements Integration { * To be called on a transaction start. Can have async methods */ public onTransactionStart(transaction: Transaction): void { - this.nativeFramesInstrumentation?.onTransactionStart(transaction); + if (isNearToNow(transaction.startTimestamp)) { + // Only if this method is called at or within margin of error to the start timestamp. + this.nativeFramesInstrumentation?.onTransactionStart(transaction); + this.stallTrackingInstrumentation?.onTransactionStart(transaction); + } } /** * To be called on a transaction finish. Cannot have async methods. */ - public onTransactionFinish(transaction: Transaction): void { + public onTransactionFinish( + transaction: Transaction, + endTimestamp?: number + ): void { this.nativeFramesInstrumentation?.onTransactionFinish(transaction); + this.stallTrackingInstrumentation?.onTransactionFinish( + transaction, + endTimestamp + ); + } + + /** + * Called by the ReactNativeProfiler component on first component mount. + */ + public onAppStartFinish(endTimestamp: number): void { + this._appStartFinishTimestamp = endTimestamp; } /** @@ -211,6 +250,10 @@ export class ReactNativeTracing implements Integration { return; } + if (!this.useAppStartWithProfiler) { + this._appStartFinishTimestamp = getTimeOriginMilliseconds() / 1000; + } + if (this.options.routingInstrumentation) { this._awaitingAppStartData = appStart; } else { @@ -235,18 +278,22 @@ export class ReactNativeTracing implements Integration { transaction: IdleTransaction, appStart: NativeAppStartResponse ): void { + if (!this._appStartFinishTimestamp) { + logger.warn("App start was never finished."); + return; + } + const appStartTimeSeconds = appStart.appStartTime / 1000; - const timeOriginSeconds = getTimeOriginMilliseconds() / 1000; transaction.startChild({ description: appStart.isColdStart ? "Cold App Start" : "Warm App Start", op: appStart.isColdStart ? "app.start.cold" : "app.start.warm", startTimestamp: appStartTimeSeconds, - endTimestamp: timeOriginSeconds, + endTimestamp: this._appStartFinishTimestamp, }); const appStartDurationMilliseconds = - getTimeOriginMilliseconds() - appStart.appStartTime; + this._appStartFinishTimestamp * 1000 - appStart.appStartTime; transaction.setMeasurements( appStart.isColdStart @@ -304,9 +351,11 @@ export class ReactNativeTracing implements Integration { `[ReactNativeTracing] Starting ${context.op} transaction "${context.name}" on scope` ); - idleTransaction.registerBeforeFinishCallback((transaction) => { - this.onTransactionFinish(transaction); - }); + idleTransaction.registerBeforeFinishCallback( + (transaction, endTimestamp) => { + this.onTransactionFinish(transaction, endTimestamp); + } + ); idleTransaction.registerBeforeFinishCallback((transaction) => { if (this.options.enableAppStartTracking && this._awaitingAppStartData) { @@ -320,24 +369,6 @@ export class ReactNativeTracing implements Integration { } }); - const stallTracking = this._getCurrentHub().getIntegration(StallTracking); - - if (stallTracking) { - const stallTrackingFinish = stallTracking.registerTransactionStart( - idleTransaction - ); - - idleTransaction.registerBeforeFinishCallback( - (transaction, endTimestamp) => { - const stallMeasurements = stallTrackingFinish(endTimestamp); - - if (stallMeasurements) { - transaction.setMeasurements(stallMeasurements); - } - } - ); - } - idleTransaction.registerBeforeFinishCallback( (transaction, endTimestamp) => { adjustTransactionDuration( diff --git a/src/js/integrations/stalltracking.ts b/src/js/tracing/stalltracking.ts similarity index 93% rename from src/js/integrations/stalltracking.ts rename to src/js/tracing/stalltracking.ts index 5e8c07dcf8..889a575ea2 100644 --- a/src/js/integrations/stalltracking.ts +++ b/src/js/tracing/stalltracking.ts @@ -1,6 +1,6 @@ /* eslint-disable max-lines */ import { IdleTransaction, Span, Transaction } from "@sentry/tracing"; -import { Integration, Measurements } from "@sentry/types"; +import { Measurements } from "@sentry/types"; import { logger, timestampInSeconds } from "@sentry/utils"; export interface StallMeasurements extends Measurements { @@ -31,17 +31,7 @@ const MAX_RUNNING_TRANSACTIONS = 10; * However, we modified the interval implementation to instead have a fixed loop timeout interval of `LOOP_TIMEOUT_INTERVAL_MS`. * We then would consider that iteration a stall when the total time for that interval to run is greater than `LOOP_TIMEOUT_INTERVAL_MS + minimumStallThreshold` */ -export class StallTracking implements Integration { - /** - * @inheritDoc - */ - public static id: string = "StallTracking"; - - /** - * @inheritDoc - */ - public name: string = StallTracking.id; - +export class StallTrackingInstrumentation { public isTracking: boolean = false; private _minimumStallThreshold: number; @@ -59,6 +49,7 @@ export class StallTracking implements Integration { Transaction, { longestStallTime: number; + atStart: StallMeasurements; atTimestamp: { timestamp: number; stats: StallMeasurements; @@ -84,21 +75,20 @@ export class StallTracking implements Integration { * Register a transaction as started. Starts stall tracking if not already running. * @returns A finish method that returns the stall measurements. */ - public registerTransactionStart( - transaction: Transaction - ): (endTimestamp?: number) => StallMeasurements | null { + public onTransactionStart(transaction: Transaction): void { if (this._statsByTransaction.has(transaction)) { logger.error( "[StallTracking] Tried to start stall tracking on a transaction already being tracked. Measurements might be lost." ); - return () => null; + return; } this._startTracking(); this._statsByTransaction.set(transaction, { longestStallTime: 0, atTimestamp: null, + atStart: this._getCurrentStats(transaction), }); this._flushLeakedTransactions(); @@ -123,11 +113,6 @@ export class StallTracking implements Integration { }; }; } - - const statsOnStart = this._getCurrentStats(transaction); - - return (endTimestamp?: number) => - this._onFinish(transaction, statsOnStart, endTimestamp); } /** @@ -135,11 +120,10 @@ export class StallTracking implements Integration { * Stops stall tracking if no more transactions are running. * @returns The stall measurements */ - private _onFinish( + public onTransactionFinish( transaction: Transaction | IdleTransaction, - statsOnStart: StallMeasurements, passedEndTimestamp?: number - ): StallMeasurements | null { + ): void { const transactionStats = this._statsByTransaction.get(transaction); if (!transactionStats) { @@ -151,7 +135,7 @@ export class StallTracking implements Integration { this._statsByTransaction.delete(transaction); this._shouldStopTracking(); - return null; + return; } const endTimestamp = passedEndTimestamp ?? transaction.endTimestamp; @@ -224,20 +208,24 @@ export class StallTracking implements Integration { ); } - return null; + return; } - return { + const measurements = { stall_count: { - value: statsOnFinish.stall_count.value - statsOnStart.stall_count.value, + value: + statsOnFinish.stall_count.value - + transactionStats.atStart.stall_count.value, }, stall_total_time: { value: statsOnFinish.stall_total_time.value - - statsOnStart.stall_total_time.value, + transactionStats.atStart.stall_total_time.value, }, stall_longest_time: statsOnFinish.stall_longest_time, }; + + transaction.setMeasurements(measurements); } /** diff --git a/src/js/tracing/utils.ts b/src/js/tracing/utils.ts index ce48886879..9c2fff755d 100644 --- a/src/js/tracing/utils.ts +++ b/src/js/tracing/utils.ts @@ -4,6 +4,13 @@ import { SpanStatus, Transaction, } from "@sentry/tracing"; +import { timestampInSeconds } from "@sentry/utils"; + +/** + * A margin of error of 50ms is allowed for the async native bridge call. + * Anything larger would reduce the accuracy of our frames measurements. + */ +export const MARGIN_OF_ERROR_SECONDS = 0.05; const timeOriginMilliseconds = Date.now(); @@ -64,3 +71,10 @@ export function instrumentChildSpanFinish( }; } } + +/** + * Determines if the timestamp is now or within the specified margin of error from now. + */ +export function isNearToNow(timestamp: number): boolean { + return Math.abs(timestampInSeconds() - timestamp) <= MARGIN_OF_ERROR_SECONDS; +} diff --git a/src/js/transports/native.ts b/src/js/transports/native.ts index 28bc571d83..aaa0f3d771 100644 --- a/src/js/transports/native.ts +++ b/src/js/transports/native.ts @@ -1,8 +1,6 @@ -// import { Transports } from "@sentry/react"; import { Event, Response, Transport } from "@sentry/types"; import { PromiseBuffer, SentryError } from "@sentry/utils"; -// import { Platform } from "react-native"; import { NATIVE } from "../wrapper"; /** Native Transport class implementation */ @@ -19,7 +17,7 @@ export class NativeTransport implements Transport { new SentryError("Not adding Promise due to buffer limit reached.") ); } - return this._buffer.add(NATIVE.sendEvent(event)); + return this._buffer.add(() => NATIVE.sendEvent(event)); } /** diff --git a/test/sdk.test.ts b/test/sdk.test.ts index 68739b246b..94e4381847 100644 --- a/test/sdk.test.ts +++ b/test/sdk.test.ts @@ -1,11 +1,14 @@ import { logger } from "@sentry/utils"; jest.mock("@sentry/react", () => { + const actualModule = jest.requireActual("@sentry/react"); + const mockClient = { flush: jest.fn(() => Promise.resolve(true)), }; return { + ...actualModule, getCurrentHub: jest.fn(() => ({ getClient: jest.fn(() => mockClient), setTag: jest.fn(), @@ -47,7 +50,6 @@ jest.spyOn(logger, "error"); import { initAndBind } from "@sentry/core"; import { getCurrentHub } from "@sentry/react"; -import { StallTracking } from "../src/js/integrations"; import { flush, init } from "../src/js/sdk"; import { ReactNativeTracing } from "../src/js/tracing"; @@ -57,8 +59,8 @@ afterEach(() => { describe("Tests the SDK functionality", () => { describe("init", () => { - describe("enableStallTracking", () => { - const stallTrackingIsEnabled = (): boolean => { + describe("enableAutoPerformanceTracking", () => { + const autoPerformanceIsEnabled = (): boolean => { const mockCall = (initAndBind as jest.MockedFunction< typeof initAndBind >).mock.calls[0]; @@ -68,7 +70,7 @@ describe("Tests the SDK functionality", () => { if (options.defaultIntegrations) { return options.defaultIntegrations?.some( - (integration) => integration.name === StallTracking.id + (integration) => integration.name === ReactNativeTracing.id ); } } @@ -76,129 +78,58 @@ describe("Tests the SDK functionality", () => { return false; }; - it("Stall Tracking is not enabled when tracing is disabled", () => { - init({ - enableStallTracking: true, - }); - - expect(stallTrackingIsEnabled()).toBe(false); - }); - - it("Stall Tracking is enabled when tracing is enabled (tracesSampler)", () => { + it("Auto Performance is enabled when tracing is enabled (tracesSampler)", () => { init({ tracesSampler: () => true, - enableStallTracking: true, - }); - - expect(stallTrackingIsEnabled()).toBe(true); - }); - - it("Stall Tracking is enabled when tracing is enabled (tracesSampleRate)", () => { - init({ - tracesSampleRate: 0.5, - enableStallTracking: true, - }); - - expect(stallTrackingIsEnabled()).toBe(true); - }); - - it("Stall Tracking is disabled when Auto performance tracking is disabled", () => { - init({ - tracesSampleRate: 0.5, - enableStallTracking: true, - enableAutoPerformanceTracking: false, + enableAutoPerformanceTracking: true, }); - expect(stallTrackingIsEnabled()).toBe(false); + expect(autoPerformanceIsEnabled()).toBe(true); }); - it("Stall Tracking is enabled when Auto performance tracking is enabled", () => { + it("Auto Performance is enabled when tracing is enabled (tracesSampleRate)", () => { init({ tracesSampleRate: 0.5, - enableStallTracking: true, enableAutoPerformanceTracking: true, }); - expect(stallTrackingIsEnabled()).toBe(true); + expect(autoPerformanceIsEnabled()).toBe(true); }); }); - }); - describe("enableAutoPerformanceTracking", () => { - const autoPerformanceIsEnabled = (): boolean => { - const mockCall = (initAndBind as jest.MockedFunction) - .mock.calls[0]; + describe("flush", () => { + it("Calls flush on the client", async () => { + const mockClient = getCurrentHub().getClient(); - if (mockCall) { - const options = mockCall[1]; + expect(mockClient).toBeTruthy(); - if (options.defaultIntegrations) { - return options.defaultIntegrations?.some( - (integration) => integration.name === ReactNativeTracing.id - ); - } - } - - return false; - }; + if (mockClient) { + const flushResult = await flush(); - it("Auto Performance is not enabled when tracing is disabled", () => { - init({ - enableStallTracking: true, + // eslint-disable-next-line @typescript-eslint/unbound-method + expect(mockClient.flush).toBeCalled(); + expect(flushResult).toBe(true); + } }); - expect(autoPerformanceIsEnabled()).toBe(false); - }); + it("Returns false if flush failed and logs error", async () => { + const mockClient = getCurrentHub().getClient(); - it("Auto Performance is enabled when tracing is enabled (tracesSampler)", () => { - init({ - tracesSampler: () => true, - enableAutoPerformanceTracking: true, - }); + expect(mockClient).toBeTruthy(); + if (mockClient) { + mockClient.flush = jest.fn(() => Promise.reject()); - expect(autoPerformanceIsEnabled()).toBe(true); - }); + const flushResult = await flush(); - it("Auto Performance is enabled when tracing is enabled (tracesSampleRate)", () => { - init({ - tracesSampleRate: 0.5, - enableAutoPerformanceTracking: true, + // eslint-disable-next-line @typescript-eslint/unbound-method + expect(mockClient.flush).toBeCalled(); + expect(flushResult).toBe(false); + // eslint-disable-next-line @typescript-eslint/unbound-method + expect(logger.error).toBeCalledWith( + "Failed to flush the event queue." + ); + } }); - - expect(autoPerformanceIsEnabled()).toBe(true); - }); - }); - - describe("flush", () => { - it("Calls flush on the client", async () => { - const mockClient = getCurrentHub().getClient(); - - expect(mockClient).toBeTruthy(); - - if (mockClient) { - const flushResult = await flush(); - - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(mockClient.flush).toBeCalled(); - expect(flushResult).toBe(true); - } - }); - - it("Returns false if flush failed and logs error", async () => { - const mockClient = getCurrentHub().getClient(); - - expect(mockClient).toBeTruthy(); - if (mockClient) { - mockClient.flush = jest.fn(() => Promise.reject()); - - const flushResult = await flush(); - - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(mockClient.flush).toBeCalled(); - expect(flushResult).toBe(false); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(logger.error).toBeCalledWith("Failed to flush the event queue."); - } }); }); }); diff --git a/test/tracing/reactnativetracing.test.ts b/test/tracing/reactnativetracing.test.ts index 1111e44682..0668f4ef7e 100644 --- a/test/tracing/reactnativetracing.test.ts +++ b/test/tracing/reactnativetracing.test.ts @@ -87,6 +87,7 @@ describe("ReactNativeTracing", () => { // use setImmediate as app start is handled inside a promise. setImmediate(() => { + integration.onAppStartFinish(Date.now() / 1000); const transaction = mockHub.getScope()?.getTransaction(); expect(transaction).toBeDefined(); diff --git a/test/integrations/stalltracking.test.ts b/test/tracing/stalltracking.test.ts similarity index 57% rename from test/integrations/stalltracking.test.ts rename to test/tracing/stalltracking.test.ts index 8316d61090..efd8bf9b07 100644 --- a/test/integrations/stalltracking.test.ts +++ b/test/tracing/stalltracking.test.ts @@ -1,6 +1,24 @@ import { IdleTransaction, Transaction } from "@sentry/tracing"; +import { Event } from "@sentry/types"; -import { StallTracking } from "../../src/js/integrations"; +import { StallTrackingInstrumentation } from "../../src/js/tracing/stalltracking"; + +const hub = { + captureEvent: jest.fn(), +}; + +jest.mock("@sentry/hub", () => { + const hubOriginal = jest.requireActual("@sentry/hub"); + + return { + ...hubOriginal, + getCurrentHub: () => hub, + }; +}); + +const getLastEvent = (): Event => { + return hub.captureEvent.mock.calls[hub.captureEvent.mock.calls.length - 1][0]; +}; const expensiveOperation = () => { const expensiveObject: { value: string[] } = { @@ -13,24 +31,32 @@ const expensiveOperation = () => { } }; +beforeEach(() => { + jest.clearAllMocks(); +}); + describe("StallTracking", () => { it("Stall tracking detects a JS stall", (done) => { - const stallTracking = new StallTracking(); + const stallTracking = new StallTrackingInstrumentation(); const transaction = new Transaction({ name: "Test Transaction", + sampled: true, }); transaction.initSpanRecorder(); - const finishTracking = stallTracking.registerTransactionStart(transaction); + stallTracking.onTransactionStart(transaction); expensiveOperation(); setTimeout(() => { - const measurements = finishTracking(); + stallTracking.onTransactionFinish(transaction); + transaction.finish(); + + const measurements = getLastEvent()?.measurements; - expect(measurements).not.toEqual(null); - if (measurements !== null) { + expect(measurements).toBeDefined(); + if (measurements) { expect(measurements.stall_count.value).toBeGreaterThan(0); expect(measurements.stall_longest_time.value).toBeGreaterThan(0); expect(measurements.stall_total_time.value).toBeGreaterThan(0); @@ -41,14 +67,15 @@ describe("StallTracking", () => { }); it("Stall tracking detects multiple JS stalls", (done) => { - const stallTracking = new StallTracking(); + const stallTracking = new StallTrackingInstrumentation(); const transaction = new Transaction({ name: "Test Transaction", + sampled: true, }); transaction.initSpanRecorder(); - const finishTracking = stallTracking.registerTransactionStart(transaction); + stallTracking.onTransactionStart(transaction); expensiveOperation(); @@ -57,10 +84,12 @@ describe("StallTracking", () => { }, 200); setTimeout(() => { - const measurements = finishTracking(); + stallTracking.onTransactionFinish(transaction); + transaction.finish(); + const measurements = getLastEvent()?.measurements; - expect(measurements).not.toEqual(null); - if (measurements !== null) { + expect(measurements).toBeDefined(); + if (measurements) { expect(measurements.stall_count.value).toBe(2); expect(measurements.stall_longest_time.value).toBeGreaterThan(0); expect(measurements.stall_total_time.value).toBeGreaterThan(0); @@ -71,15 +100,19 @@ describe("StallTracking", () => { }); it("Stall tracking timeout is stopped after finishing all transactions (single)", () => { - const stallTracking = new StallTracking(); + const stallTracking = new StallTrackingInstrumentation(); const transaction = new Transaction({ name: "Test Transaction", + sampled: true, }); - const finishTracking = stallTracking.registerTransactionStart(transaction); + stallTracking.onTransactionStart(transaction); + + stallTracking.onTransactionFinish(transaction); + transaction.finish(); - const measurements = finishTracking(); + const measurements = getLastEvent()?.measurements; expect(measurements).not.toBe(null); @@ -87,40 +120,43 @@ describe("StallTracking", () => { }); it("Stall tracking timeout is stopped after finishing all transactions (multiple)", (done) => { - const stallTracking = new StallTracking(); + const stallTracking = new StallTrackingInstrumentation(); const transaction0 = new Transaction({ name: "Test Transaction 0", + sampled: true, }); const transaction1 = new Transaction({ name: "Test Transaction 1", + sampled: true, }); const transaction2 = new Transaction({ name: "Test Transaction 2", + sampled: true, }); - const finishTracking0 = stallTracking.registerTransactionStart( - transaction0 - ); - const finishTracking1 = stallTracking.registerTransactionStart( - transaction1 - ); + stallTracking.onTransactionStart(transaction0); + stallTracking.onTransactionStart(transaction1); - const measurements0 = finishTracking0(); - expect(measurements0).not.toBe(null); + stallTracking.onTransactionFinish(transaction0); + transaction0.finish(); + const measurements0 = getLastEvent()?.measurements; + expect(measurements0).toBeDefined(); setTimeout(() => { - const measurements1 = finishTracking1(); - expect(measurements1).not.toBe(null); + stallTracking.onTransactionFinish(transaction1); + transaction1.finish(); + const measurements1 = getLastEvent()?.measurements; + expect(measurements1).toBeDefined(); }, 600); setTimeout(() => { - const finishTracking2 = stallTracking.registerTransactionStart( - transaction2 - ); + stallTracking.onTransactionStart(transaction2); setTimeout(() => { - const measurements2 = finishTracking2(); + stallTracking.onTransactionFinish(transaction2); + transaction2.finish(); + const measurements2 = getLastEvent()?.measurements; expect(measurements2).not.toBe(null); expect(stallTracking.isTracking).toBe(false); @@ -133,69 +169,56 @@ describe("StallTracking", () => { }); it("Stall tracking returns measurements format on finish", () => { - const stallTracking = new StallTracking(); + const stallTracking = new StallTrackingInstrumentation(); const transaction = new Transaction({ name: "Test Transaction", + sampled: true, }); - const finishTracking = stallTracking.registerTransactionStart(transaction); + stallTracking.onTransactionStart(transaction); - const measurements = finishTracking(); + stallTracking.onTransactionFinish(transaction); + transaction.finish(); + const measurements = getLastEvent()?.measurements; - expect(measurements).not.toBe(null); + expect(measurements).toBeDefined(); - if (measurements !== null) { + if (measurements) { expect(measurements.stall_count.value).toBe(0); expect(measurements.stall_longest_time.value).toBe(0); expect(measurements.stall_total_time.value).toBe(0); } }); - it("Stall tracking only tracks a transaction once", () => { - const stallTracking = new StallTracking(); - - const transaction = new Transaction({ - name: "Test Transaction", - }); - - const finishTracking = stallTracking.registerTransactionStart(transaction); - - const noopFinishTracking = stallTracking.registerTransactionStart( - transaction - ); - - const measurements = finishTracking(); - - expect(measurements).not.toBe(null); - - expect(noopFinishTracking()).toBe(null); - }); - it("Stall tracking returns null on a custom endTimestamp that is not a span's", () => { - const stallTracking = new StallTracking(); + const stallTracking = new StallTrackingInstrumentation(); const transaction = new Transaction({ name: "Test Transaction", + sampled: true, }); - const finishTracking = stallTracking.registerTransactionStart(transaction); + stallTracking.onTransactionStart(transaction); - const measurements = finishTracking(Date.now() / 1000); + stallTracking.onTransactionFinish(transaction, Date.now() / 1000); + transaction.finish(); + const measurements = getLastEvent()?.measurements; - expect(measurements).toBe(null); + expect(measurements).toBeUndefined(); }); it("Stall tracking supports endTimestamp that is from the last span (trimEnd case)", (done) => { - const stallTracking = new StallTracking(); + const stallTracking = new StallTrackingInstrumentation(); const transaction = new Transaction({ name: "Test Transaction", trimEnd: true, + sampled: true, }); transaction.initSpanRecorder(); - const finishTracking = stallTracking.registerTransactionStart(transaction); + stallTracking.onTransactionStart(transaction); const span = transaction.startChild({ description: "Test Span", @@ -212,11 +235,13 @@ describe("StallTracking", () => { setTimeout(() => { expect(spanFinishTime).toEqual(expect.any(Number)); - const measurements = finishTracking(spanFinishTime); + stallTracking.onTransactionFinish(transaction); + transaction.finish(); + const measurements = getLastEvent()?.measurements; - expect(measurements).not.toEqual(null); + expect(measurements).toBeDefined(); - if (measurements !== null) { + if (measurements) { expect(measurements.stall_count.value).toEqual(expect.any(Number)); expect(measurements.stall_longest_time.value).toEqual( expect.any(Number) @@ -229,15 +254,16 @@ describe("StallTracking", () => { }); it("Stall tracking rejects endTimestamp that is from the last span if trimEnd is false (trimEnd case)", (done) => { - const stallTracking = new StallTracking(); + const stallTracking = new StallTrackingInstrumentation(); const transaction = new Transaction({ name: "Test Transaction", trimEnd: false, + sampled: true, }); transaction.initSpanRecorder(); - const finishTracking = stallTracking.registerTransactionStart(transaction); + stallTracking.onTransactionStart(transaction); const span = transaction.startChild({ description: "Test Span", @@ -254,23 +280,26 @@ describe("StallTracking", () => { setTimeout(() => { expect(spanFinishTime).toEqual(expect.any(Number)); - const measurements = finishTracking(spanFinishTime); + stallTracking.onTransactionFinish(transaction, spanFinishTime); + transaction.finish(); + const measurements = getLastEvent()?.measurements; - expect(measurements).toBe(null); + expect(measurements).toBeUndefined(); done(); }, 400); }); it("Stall tracking rejects endTimestamp even if it is a span time (custom endTimestamp case)", (done) => { - const stallTracking = new StallTracking(); + const stallTracking = new StallTrackingInstrumentation(); const transaction = new Transaction({ name: "Test Transaction", + sampled: true, }); transaction.initSpanRecorder(); - const finishTracking = stallTracking.registerTransactionStart(transaction); + stallTracking.onTransactionStart(transaction); const span = transaction.startChild({ description: "Test Span", @@ -288,9 +317,11 @@ describe("StallTracking", () => { expect(spanFinishTime).toEqual(expect.any(Number)); if (typeof spanFinishTime === "number") { - const measurements = finishTracking(spanFinishTime + 0.015); + stallTracking.onTransactionFinish(transaction, spanFinishTime + 0.015); + transaction.finish(); + const measurements = getLastEvent()?.measurements; - expect(measurements).toBe(null); + expect(measurements).toBeUndefined(); } done(); @@ -298,24 +329,34 @@ describe("StallTracking", () => { }); it("Stall tracking supports idleTransaction with unfinished spans", (done) => { - const stallTracking = new StallTracking(); + const stallTracking = new StallTrackingInstrumentation(); const idleTransaction = new IdleTransaction({ name: "Test Transaction", trimEnd: true, + sampled: true, }); idleTransaction.initSpanRecorder(); - const finishTracking = stallTracking.registerTransactionStart( - idleTransaction - ); + stallTracking.onTransactionStart(idleTransaction); idleTransaction.registerBeforeFinishCallback((_, endTimestamp) => { - const measurements = finishTracking(endTimestamp); + stallTracking.onTransactionFinish(idleTransaction, endTimestamp); + }); + + // Span is never finished. + idleTransaction.startChild({ + description: "Test Span", + }); + + setTimeout(() => { + idleTransaction.finish(); + + const measurements = getLastEvent()?.measurements; - expect(measurements).not.toEqual(null); + expect(measurements).toBeDefined(); - if (measurements !== null) { + if (measurements) { expect(measurements.stall_count.value).toEqual(expect.any(Number)); expect(measurements.stall_longest_time.value).toEqual( expect.any(Number) @@ -324,28 +365,20 @@ describe("StallTracking", () => { } done(); - }); - - // Span is never finished. - idleTransaction.startChild({ - description: "Test Span", - }); - - setTimeout(() => { - idleTransaction.finish(); }, 100); }); it("Stall tracking ignores unfinished spans in normal transactions", (done) => { - const stallTracking = new StallTracking(); + const stallTracking = new StallTrackingInstrumentation(); const transaction = new Transaction({ name: "Test Transaction", trimEnd: true, + sampled: true, }); transaction.initSpanRecorder(); - const finishTracking = stallTracking.registerTransactionStart(transaction); + stallTracking.onTransactionStart(transaction); // Span is never finished. transaction.startChild({ @@ -362,11 +395,13 @@ describe("StallTracking", () => { }, 100); setTimeout(() => { - const measurements = finishTracking(); + stallTracking.onTransactionFinish(transaction); + transaction.finish(); + const measurements = getLastEvent()?.measurements; - expect(measurements).not.toEqual(null); + expect(measurements).toBeDefined(); - if (measurements !== null) { + if (measurements) { expect(measurements.stall_count.value).toEqual(expect.any(Number)); expect(measurements.stall_longest_time.value).toEqual( expect.any(Number) @@ -379,15 +414,16 @@ describe("StallTracking", () => { }); it("Stall tracking only measures stalls inside the final time when trimEnd is used", (done) => { - const stallTracking = new StallTracking(); + const stallTracking = new StallTrackingInstrumentation(); const transaction = new Transaction({ name: "Test Transaction", trimEnd: true, + sampled: true, }); transaction.initSpanRecorder(); - const finishTracking = stallTracking.registerTransactionStart(transaction); + stallTracking.onTransactionStart(transaction); // Span will be finished const span = transaction.startChild({ @@ -399,11 +435,13 @@ describe("StallTracking", () => { }, 200); setTimeout(() => { - const measurements = finishTracking(); + stallTracking.onTransactionFinish(transaction); + transaction.finish(); + const measurements = getLastEvent()?.measurements; - expect(measurements).not.toEqual(null); + expect(measurements).toBeDefined(); - if (measurements !== null) { + if (measurements) { expect(measurements.stall_count.value).toEqual(1); expect(measurements.stall_longest_time.value).toEqual( expect.any(Number) @@ -422,23 +460,33 @@ describe("StallTracking", () => { expensiveOperation(); }); - it("Stall tracking discards the first transaction if more than 10 are running", () => { - const stallTracking = new StallTracking(); + it("Stall tracking does not track the first transaction if more than 10 are running", () => { + const stallTracking = new StallTrackingInstrumentation(); - const transactionFinishes = new Array(11).fill(0).map((_, i) => { + const transactions = new Array(11).fill(0).map((_, i) => { const transaction = new Transaction({ name: `Test Transaction ${i}`, + sampled: true, }); - return stallTracking.registerTransactionStart(transaction); + stallTracking.onTransactionStart(transaction); + + return transaction; }); - const measurements0 = transactionFinishes[0](); - expect(measurements0).toBe(null); + stallTracking.onTransactionFinish(transactions[0]); + transactions[0].finish(); + const measurements0 = getLastEvent()?.measurements; + expect(measurements0).toBeUndefined(); - const measurements1 = transactionFinishes[1](); - expect(measurements1).not.toBe(null); + stallTracking.onTransactionFinish(transactions[1]); + transactions[1].finish(); + const measurements1 = getLastEvent()?.measurements; + expect(measurements1).toBeDefined(); - transactionFinishes.slice(2).forEach((finish) => finish()); + transactions.slice(2).forEach((transaction) => { + stallTracking.onTransactionFinish(transaction); + transaction.finish(); + }); }); }); diff --git a/yarn.lock b/yarn.lock index b122666c29..f26b66e9ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1123,13 +1123,13 @@ sudo-prompt "^9.0.0" wcwidth "^1.0.1" -"@sentry-internal/eslint-config-sdk@6.7.1": - version "6.7.1" - resolved "https://registry.yarnpkg.com/@sentry-internal/eslint-config-sdk/-/eslint-config-sdk-6.7.1.tgz#e258b236511e8889fe95cc49ca2c15abbc803250" - integrity sha512-K7t/WLm5q2klB8wqt+7MgtNDyZEu7Q5HO/ur77zStcNmzi60A8ZnTT8cBZrKHtxc8vOuwLzcoCgM+KIWJOZSpg== +"@sentry-internal/eslint-config-sdk@6.12.0-beta.2": + version "6.12.0-beta.2" + resolved "https://registry.yarnpkg.com/@sentry-internal/eslint-config-sdk/-/eslint-config-sdk-6.12.0-beta.2.tgz#44b417fb26f209992f7d08577c7485ce3535f2a1" + integrity sha512-KiBL/nrfVhTw9rPAlNGC6Rey7s93mhpHdqogB/BtMNx3oRAYzKFzOWFwfqaWY6QFUKvlP0WXaSyc72akm648Lw== dependencies: - "@sentry-internal/eslint-plugin-sdk" "6.7.1" - "@sentry-internal/typescript" "6.7.1" + "@sentry-internal/eslint-plugin-sdk" "6.12.0-beta.2" + "@sentry-internal/typescript" "6.12.0-beta.2" "@typescript-eslint/eslint-plugin" "^3.9.0" "@typescript-eslint/parser" "^3.9.0" eslint-config-prettier "^6.11.0" @@ -1138,29 +1138,29 @@ eslint-plugin-jsdoc "^30.0.3" eslint-plugin-simple-import-sort "^5.0.3" -"@sentry-internal/eslint-plugin-sdk@6.7.1": - version "6.7.1" - resolved "https://registry.yarnpkg.com/@sentry-internal/eslint-plugin-sdk/-/eslint-plugin-sdk-6.7.1.tgz#6068ba3fde062bd4cc7bef3189ddd797b5c7bf18" - integrity sha512-rCZMHo4twjFhNFq8uOF41ZaUdsHWl2fmEsqJk6ufw3QRZEAgfGRSilwnTW1EFEfCi+Fj92Em7rq5HwXNYqURzQ== +"@sentry-internal/eslint-plugin-sdk@6.12.0-beta.2": + version "6.12.0-beta.2" + resolved "https://registry.yarnpkg.com/@sentry-internal/eslint-plugin-sdk/-/eslint-plugin-sdk-6.12.0-beta.2.tgz#8dc8854b644f7c295abcfe34b5ff6d66985a451b" + integrity sha512-H3YZNCQH8fmzdFan+Brh4bFtxnlQJpU8Qjv+V335LG+XG7rt3/nsUYYjzT71Ajr5F0n//bdyXQTMXoOabHQTSQ== dependencies: requireindex "~1.1.0" -"@sentry-internal/typescript@6.7.1": - version "6.7.1" - resolved "https://registry.yarnpkg.com/@sentry-internal/typescript/-/typescript-6.7.1.tgz#9de1a49c244ff44ea01865c91d96275187281a60" - integrity sha512-73Rscw2P5DBnVOWn1KbaYK2VjAoMD3POwf/3QrIPe5H9iBlXcU4ZdXZvvsajE+r+GMhCse9U3tIYvC4oEptKhQ== +"@sentry-internal/typescript@6.12.0-beta.2": + version "6.12.0-beta.2" + resolved "https://registry.yarnpkg.com/@sentry-internal/typescript/-/typescript-6.12.0-beta.2.tgz#c96de33e91b5ae4cb655afb11e287518dcaa6a04" + integrity sha512-a1dPWbS/RDXU70Qv0iievZoFUPmjUJfG+ZSDSFxokNcxzayl+bvpVhePxkDlZQQZgQEOT4FLDBs/E9wAiS/N3Q== dependencies: tslint-config-prettier "^1.18.0" tslint-consistent-codestyle "^1.15.1" -"@sentry/browser@6.7.1": - version "6.7.1" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-6.7.1.tgz#e01144a08984a486ecc91d7922cc457e9c9bd6b7" - integrity sha512-R5PYx4TTvifcU790XkK6JVGwavKaXwycDU0MaAwfc4Vf7BLm5KCNJCsDySu1RPAap/017MVYf54p6dWvKiRviA== +"@sentry/browser@6.12.0-beta.2": + version "6.12.0-beta.2" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-6.12.0-beta.2.tgz#6cb5e49aa2eabeffee8e3b1440aa7dfc4cb57996" + integrity sha512-p+4hf0/HOCJ8Y8ge5LyyxsNBtktFLvFj2j9DblkHvmAQaMjSKrK87B2zuMAW+FQjofUsvfJDRpGI1qOD0d5aXQ== dependencies: - "@sentry/core" "6.7.1" - "@sentry/types" "6.7.1" - "@sentry/utils" "6.7.1" + "@sentry/core" "6.12.0-beta.2" + "@sentry/types" "6.12.0-beta.2" + "@sentry/utils" "6.12.0-beta.2" tslib "^1.9.3" "@sentry/cli@^1.52.4": @@ -1174,72 +1174,72 @@ progress "^2.0.3" proxy-from-env "^1.1.0" -"@sentry/core@6.7.1": - version "6.7.1" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-6.7.1.tgz#c3aaa6415d06bec65ac446b13b84f073805633e3" - integrity sha512-VAv8OR/7INn2JfiLcuop4hfDcyC7mfL9fdPndQEhlacjmw8gRrgXjR7qyhnCTgzFLkHI7V5bcdIzA83TRPYQpA== +"@sentry/core@6.12.0-beta.2": + version "6.12.0-beta.2" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-6.12.0-beta.2.tgz#93aefbe8711a494e069dc122577bc4a79bfe29ca" + integrity sha512-gewpA+GuxcCSPcAXNTJjJKZhCNgN21lb3XonjXX6Vf7tIc7HQl+MQI4HcPN8gx5LY78VcwC1lyOtqU8+tHwb8Q== dependencies: - "@sentry/hub" "6.7.1" - "@sentry/minimal" "6.7.1" - "@sentry/types" "6.7.1" - "@sentry/utils" "6.7.1" + "@sentry/hub" "6.12.0-beta.2" + "@sentry/minimal" "6.12.0-beta.2" + "@sentry/types" "6.12.0-beta.2" + "@sentry/utils" "6.12.0-beta.2" tslib "^1.9.3" -"@sentry/hub@6.7.1": - version "6.7.1" - resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-6.7.1.tgz#d46d24deec67f0731a808ca16796e6765b371bc1" - integrity sha512-eVCTWvvcp6xa0A5GGNHMQEWslmKPlisE5rGmsV/kjvSUv3zSrI0eIDfb51ikdnCiBjHpK2NBWP8Vy8cZOEJegg== +"@sentry/hub@6.12.0-beta.2": + version "6.12.0-beta.2" + resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-6.12.0-beta.2.tgz#87db87f124a9eb5ed1399b47511901086feb3a37" + integrity sha512-zZSFbWAFDsRCHsgFzIVlZUK6r96pIiwi9Ut1eIlremtuyhTCWqjaLB7uc78bO62X00mCHFTI/l8YurCpKnwsug== dependencies: - "@sentry/types" "6.7.1" - "@sentry/utils" "6.7.1" + "@sentry/types" "6.12.0-beta.2" + "@sentry/utils" "6.12.0-beta.2" tslib "^1.9.3" -"@sentry/integrations@6.7.1": - version "6.7.1" - resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-6.7.1.tgz#9a6723e35589dfdb13c2cd22259184946f0b275e" - integrity sha512-nWxAPTunZxE+E6bi4FyhKHXcUUVpbSpvtwvdHiw/K72p7FuX/K0qU002Ltdfs4U1nyMIjesE776IGMrBLU67uA== +"@sentry/integrations@6.12.0-beta.2": + version "6.12.0-beta.2" + resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-6.12.0-beta.2.tgz#05931deea00f5ebeedf425f1906c46ce23ba62c2" + integrity sha512-tY4tZKkloLwaOVy4NHQKewu3sGfbJoaWIxMjyGTJVHouNjC2GOgEVQLe3DvfHAkGqVTYJhEOvMhwAsoAzlIzvQ== dependencies: - "@sentry/types" "6.7.1" - "@sentry/utils" "6.7.1" + "@sentry/types" "6.12.0-beta.2" + "@sentry/utils" "6.12.0-beta.2" localforage "^1.8.1" tslib "^1.9.3" -"@sentry/minimal@6.7.1": - version "6.7.1" - resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-6.7.1.tgz#babf85ee2f167e9dcf150d750d7a0b250c98449c" - integrity sha512-HDDPEnQRD6hC0qaHdqqKDStcdE1KhkFh0RCtJNMCDn0zpav8Dj9AteF70x6kLSlliAJ/JFwi6AmQrLz+FxPexw== +"@sentry/minimal@6.12.0-beta.2": + version "6.12.0-beta.2" + resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-6.12.0-beta.2.tgz#d9c8c0ba731ca7fdaa645bb11fa39681137608a0" + integrity sha512-nM7NnCFBIvjwR80y5xdQ80NjOJia7ApctRPcB+bZo3h096JH1me2FxnQzZzu38WUbnrbh7GhnisDoggoO2ihIQ== dependencies: - "@sentry/hub" "6.7.1" - "@sentry/types" "6.7.1" + "@sentry/hub" "6.12.0-beta.2" + "@sentry/types" "6.12.0-beta.2" tslib "^1.9.3" -"@sentry/react@6.7.1": - version "6.7.1" - resolved "https://registry.yarnpkg.com/@sentry/react/-/react-6.7.1.tgz#7d69b9509ee1c08fd20f41b2bd3452f061524c83" - integrity sha512-kLswcfwkq+Pv4ZAQ0Tq1X3PUx0t/glD3kRRSQ0ZGn4zdQWhkTkIaVeSrxfU+K9nwZisVEAVXtMJadk4X2KNySA== +"@sentry/react@6.12.0-beta.2": + version "6.12.0-beta.2" + resolved "https://registry.yarnpkg.com/@sentry/react/-/react-6.12.0-beta.2.tgz#60e23f8655eb588f2989524cdcc50dbafe212f94" + integrity sha512-HgjRRb/xCEyww9DvN4dNPlNJxKR8q09HJLuCm6ik3T9bLHjVPaZzUBXdgde+itcORRH+o8EIFbGEwXOvMZjJCg== dependencies: - "@sentry/browser" "6.7.1" - "@sentry/minimal" "6.7.1" - "@sentry/types" "6.7.1" - "@sentry/utils" "6.7.1" + "@sentry/browser" "6.12.0-beta.2" + "@sentry/minimal" "6.12.0-beta.2" + "@sentry/types" "6.12.0-beta.2" + "@sentry/utils" "6.12.0-beta.2" hoist-non-react-statics "^3.3.2" tslib "^1.9.3" -"@sentry/tracing@6.7.1": - version "6.7.1" - resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-6.7.1.tgz#b11f0c17a6c5dc14ef44733e5436afb686683268" - integrity sha512-wyS3nWNl5mzaC1qZ2AIp1hjXnfO9EERjMIJjCihs2LWBz1r3efxrHxJHs8wXlNWvrT3KLhq/7vvF5CdU82uPeQ== +"@sentry/tracing@6.12.0-beta.2": + version "6.12.0-beta.2" + resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-6.12.0-beta.2.tgz#cb2bf46142e5a544aad63b89b5c167866343f304" + integrity sha512-v3P8WTOQsz+XDIQuH3TuKUKEIrc5vIZGOBRn90SZolMH9CwqTRyItRgnA54EmhMi5QhjQyW5Qw5wAUfIRGoSwA== dependencies: - "@sentry/hub" "6.7.1" - "@sentry/minimal" "6.7.1" - "@sentry/types" "6.7.1" - "@sentry/utils" "6.7.1" + "@sentry/hub" "6.12.0-beta.2" + "@sentry/minimal" "6.12.0-beta.2" + "@sentry/types" "6.12.0-beta.2" + "@sentry/utils" "6.12.0-beta.2" tslib "^1.9.3" -"@sentry/types@6.7.1": - version "6.7.1" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.7.1.tgz#c8263e1886df5e815570c4668eb40a1cfaa1c88b" - integrity sha512-9AO7HKoip2MBMNQJEd6+AKtjj2+q9Ze4ooWUdEvdOVSt5drg7BGpK221/p9JEOyJAZwEPEXdcMd3VAIMiOb4MA== +"@sentry/types@6.12.0-beta.2": + version "6.12.0-beta.2" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.12.0-beta.2.tgz#47cc7e51163128fa3423bc97314a50c9e8146645" + integrity sha512-Oz/aUeSGW7dNMcCadZU60tv04BI3YZpBWgYP0uvF5F7fu6dPclxaSeH4JlU4RIfJ1jcM44hefnqUs16mJW4ZHg== "@sentry/typescript@^5.20.0": version "5.20.1" @@ -1249,12 +1249,12 @@ tslint-config-prettier "^1.18.0" tslint-consistent-codestyle "^1.15.1" -"@sentry/utils@6.7.1": - version "6.7.1" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.7.1.tgz#909184ad580f0f6375e1e4d4a6ffd33dfe64a4d1" - integrity sha512-Tq2otdbWlHAkctD+EWTYKkEx6BL1Qn3Z/imkO06/PvzpWvVhJWQ5qHAzz5XnwwqNHyV03KVzYB6znq1Bea9HuA== +"@sentry/utils@6.12.0-beta.2": + version "6.12.0-beta.2" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.12.0-beta.2.tgz#88ee87f57f2723c4364a94010e4be8a82c634f10" + integrity sha512-rTxBcEoqvHNVoIHubXF6vDBmUghq+6UGUyv3y8FKTeVazeCJgsw/ssLWwcaop6GoyBOrH17ljetmHI3ZgpMSdw== dependencies: - "@sentry/types" "6.7.1" + "@sentry/types" "6.12.0-beta.2" tslib "^1.9.3" "@sentry/wizard@^1.2.2":