Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@

- Warn Expo users at Metro startup when prebuilt native projects are missing Sentry configuration ([#5984](https://github.com/getsentry/sentry-react-native/pull/5984))

### Fixes

- Fix iOS UI profiling options being silently ignored ([#6012](https://github.com/getsentry/sentry-react-native/pull/6012))

### Dependencies

- Bump JavaScript SDK from v10.48.0 to v10.49.0 ([#6011](https://github.com/getsentry/sentry-react-native/pull/6011))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#import <OCMock/OCMock.h>
#import <RNSentry/RNSentry.h>
#import <Sentry/PrivateSentrySDKOnly.h>
#import <Sentry/SentryProfilingConditionals.h>
#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
@import Sentry;
Expand Down Expand Up @@ -1372,6 +1373,103 @@ - (void)testStartBeforeBreadcrumbsCallbackDoesNotFiltersOutNonDevServerOrDsnRequ
XCTAssertEqual(breadcrumb, result);
}

#if SENTRY_TARGET_PROFILING_SUPPORTED
// Regression test for the v8.0.0 bug where the init path (RNSentryStart) did not
// handle `_experiments.profilingOptions`, silently dropping iOS UI profiling config.
// This pins the full entry point used by `initNativeSdk` in RNSentry.mm.
- (void)testStartWithDictionaryInstallsConfigureProfilingFromExperimentsProfilingOptions
{
NSError *error = nil;

NSDictionary *_Nonnull mockedReactNativeDictionary = @{
@"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456",
@"_experiments" : @ {
@"profilingOptions" : @ {
@"profileSessionSampleRate" : @1.0,
@"lifecycle" : @"trace",
@"startOnAppStart" : @YES,
},
},
};
[RNSentryStart startWithOptions:mockedReactNativeDictionary error:&error];
SentryOptions *actualOptions = PrivateSentrySDKOnly.options;

XCTAssertNotNil(actualOptions, @"Did not create sentry options");
XCTAssertNil(error, @"Should not pass no error");
XCTAssertNotNil(actualOptions.configureProfiling,
@"configureProfiling must be installed after startWithOptions when profilingOptions is "
@"present");

SentryProfileOptions *probe = [[SentryProfileOptions alloc] init];
actualOptions.configureProfiling(probe);
XCTAssertEqual(probe.sessionSampleRate, 1.0f);
XCTAssertEqual(probe.lifecycle, SentryProfileLifecycleTrace);
XCTAssertTrue(probe.profileAppStarts);
}

- (void)testStartCreateOptionsWithDictionaryProfilingOptionsInstallsConfigureProfiling
{
NSError *error = nil;

NSDictionary *_Nonnull mockedReactNativeDictionary = @{
@"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456",
@"_experiments" : @ {
@"profilingOptions" : @ {
@"profileSessionSampleRate" : @1.0,
@"lifecycle" : @"trace",
@"startOnAppStart" : @YES,
},
},
};
SentryOptions *actualOptions =
[RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error];

XCTAssertNotNil(actualOptions, @"Did not create sentry options");
XCTAssertNil(error, @"Should not pass no error");
XCTAssertNotNil(actualOptions.configureProfiling,
@"configureProfiling callback should be installed when profilingOptions is present");

SentryProfileOptions *probe = [[SentryProfileOptions alloc] init];
actualOptions.configureProfiling(probe);
XCTAssertEqual(probe.sessionSampleRate, 1.0f);
XCTAssertEqual(probe.lifecycle, SentryProfileLifecycleTrace);
XCTAssertTrue(probe.profileAppStarts);
}

- (void)testStartCreateOptionsWithDictionaryProfilingOptionsMissingDoesNotInstallConfigureProfiling
{
NSError *error = nil;

NSDictionary *_Nonnull mockedReactNativeDictionary = @{
@"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456",
};
SentryOptions *actualOptions =
[RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error];

XCTAssertNotNil(actualOptions, @"Did not create sentry options");
XCTAssertNil(error, @"Should not pass no error");
XCTAssertNil(actualOptions.configureProfiling,
@"configureProfiling callback should not be installed without profilingOptions");
}

- (void)testStartCreateOptionsWithDictionaryEmptyExperimentsDoesNotInstallConfigureProfiling
{
NSError *error = nil;

NSDictionary *_Nonnull mockedReactNativeDictionary = @{
@"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456",
@"_experiments" : @ { },
};
SentryOptions *actualOptions =
[RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error];

XCTAssertNotNil(actualOptions, @"Did not create sentry options");
XCTAssertNil(error, @"Should not pass no error");
XCTAssertNil(actualOptions.configureProfiling,
@"configureProfiling callback should not be installed when profilingOptions is absent");
}
#endif

- (void)testStartEventFromSentryCocoaReactNativeHasOriginAndEnvironmentTags
{
SentryEvent *testEvent = [[SentryEvent alloc] init];
Expand Down
10 changes: 10 additions & 0 deletions packages/core/ios/RNSentryStart.m
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,16 @@ + (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull)
}
}

// Configure iOS UI Profiling from _experiments.profilingOptions
NSDictionary *experiments = mutableOptions[@"_experiments"];
if (experiments != nil && [experiments isKindOfClass:[NSDictionary class]]) {
NSDictionary *profilingOptions = experiments[@"profilingOptions"];
if (profilingOptions != nil && [profilingOptions isKindOfClass:[NSDictionary class]]) {
[RNSentryExperimentalOptions configureProfilingWithOptions:profilingOptions
sentryOptions:sentryOptions];
}
}

// Set strict trace continuation options
if ([mutableOptions valueForKey:@"strictTraceContinuation"] != nil) {
sentryOptions.strictTraceContinuation =
Expand Down
Loading