From d08dea057d4822c5dbd219f249eedfe77e3321a5 Mon Sep 17 00:00:00 2001 From: Itay Date: Thu, 29 May 2025 17:13:27 -0300 Subject: [PATCH 1/3] docs: Add Objective C examples to cocoa onboarding + non UI verification options --- static/app/gettingStartedDocs/apple/ios.tsx | 196 ++++++++++++++++---- 1 file changed, 163 insertions(+), 33 deletions(-) diff --git a/static/app/gettingStartedDocs/apple/ios.tsx b/static/app/gettingStartedDocs/apple/ios.tsx index d47682cdf17143..cf3c8bcc5e91dc 100644 --- a/static/app/gettingStartedDocs/apple/ios.tsx +++ b/static/app/gettingStartedDocs/apple/ios.tsx @@ -20,7 +20,8 @@ import {getWizardInstallSnippet} from 'sentry/utils/gettingStartedDocs/mobileWiz export enum InstallationMode { AUTO = 'auto', - MANUAL = 'manual', + MANUAL_SWIFT = 'manual-swift', + MANUAL_OBJECTIVE_C = 'manual-objective-c', } const platformOptions = { @@ -32,8 +33,12 @@ const platformOptions = { value: InstallationMode.AUTO, }, { - label: t('Manual'), - value: InstallationMode.MANUAL, + label: t('Manual (Swift)'), + value: InstallationMode.MANUAL_SWIFT, + }, + { + label: t('Manual (Objective-C)'), + value: InstallationMode.MANUAL_OBJECTIVE_C, }, ], defaultValue: InstallationMode.AUTO, @@ -46,6 +51,11 @@ type Params = DocsParams; const isAutoInstall = (params: Params) => params.platformOptions.installationMode === InstallationMode.AUTO; +const isManualSwift = (params: Params) => + params.platformOptions.installationMode === InstallationMode.MANUAL_SWIFT; + +const selectedLanguage = (params: Params) => (isManualSwift(params) ? 'swift' : 'objc'); + const getManualInstallSnippet = (params: Params) => ` .package(url: "https://github.com/getsentry/sentry-cocoa", from: "${getPackageVersion( params, @@ -53,7 +63,7 @@ const getManualInstallSnippet = (params: Params) => ` '8.49.0' )}"),`; -const getConfigurationSnippet = (params: Params) => ` +const getConfigurationSnippetSwift = (params: Params) => ` import Sentry // .... @@ -108,6 +118,58 @@ func application(_ application: UIApplication, return true }`; +const getConfigurationSnippetObjectiveC = (params: Params) => ` +@import Sentry; + +// .... + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [SentrySDK startWithConfigureOptions:^(SentryOptions *options) { + options.dsn = "${params.dsn.public}"; + options.debug = YES; // Enabling debug when first installing is always helpful + + // Adds IP for users. + // For more information, visit: https://docs.sentry.io/platforms/apple/data-management/data-collected/ + options.sendDefaultPii = YES;${ + params.isPerformanceSelected + ? ` + + // Set tracesSampleRate to 1.0 to capture 100% of transactions for tracing. + // We recommend adjusting this value in production. + options.tracesSampleRate = @1.0;` + : '' + }${ + params.isProfilingSelected && + params.profilingOptions?.defaultProfilingMode !== 'continuous' + ? ` + + // Set tracesSampleRate to 1.0 to capture 100% of transactions for tracing. + // We recommend adjusting this value in production. + options.tracesSampleRate = @1.0;` + : params.isProfilingSelected && + params.profilingOptions?.defaultProfilingMode === 'continuous' + ? ` + + // Configure the profiler to start profiling when there is an active root span + // For more information, visit: https://docs.sentry.io/platforms/apple/profiling/ + [options setConfigureProfiling:^(SentryProfileOptions * _Nonnull profiling) { + profiling.lifecycle = SentryProfileLifecycleTrace; + profiling.sessionSampleRate = 1.0; + }];` + : '' + }${ + params.isReplaySelected + ? ` + + // Record Session Replays for 100% of Errors and 10% of Sessions + options.sessionReplay.onErrorSampleRate = 1.0; + options.sessionReplay.sessionSampleRate = 0.1;` + : '' + } + }]; + return YES; +}`; + const getConfigurationSnippetSwiftUi = (params: Params) => ` import Sentry @@ -160,7 +222,9 @@ struct SwiftUIApp: App { } }`; -const getVerifySnippet = () => ` +const getVerifySnippet = (params: Params) => + isManualSwift(params) + ? ` let button = UIButton(type: .roundedRect) button.frame = CGRect(x: 20, y: 50, width: 100, height: 30) button.setTitle("Break the world", for: []) @@ -169,6 +233,18 @@ view.addSubview(button) @IBAction func breakTheWorld(_ sender: AnyObject) { fatalError("Break the world") +}` + : ` +@import Sentry; + +UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; +button.frame = CGRectMake(20, 50, 100, 30); +[button setTitle:@"Break the world" forState:UIControlStateNormal]; +[button addTarget:self action:@selector(breakTheWorld:) forControlEvents:UIControlEventTouchUpInside]; +[view addSubview:button]; + +- (IBAction)breakTheWorld:(id)sender { + [SentrySDK crash]; }`; const getReplaySetupSnippet = (params: Params) => ` @@ -184,6 +260,24 @@ const getReplayConfigurationSnippet = () => ` options.sessionReplay.maskAllText = true options.sessionReplay.maskAllImages = true`; +const noUIErrorSnippet = (params: Params) => + isManualSwift(params) + ? ` +import Sentry + +DispatchQueue.global().asyncAfter(deadline: .now() + 10) { + SentrySDK.crash() +}` + : ` +@import Sentry; + +[SentrySDK performSelector:@selector(crash) withObject:nil afterDelay:10];`; + +const buttonSnippetSwift = () => ` +Button("Break the world") { + fatalError("Break the world") +}`; + const onboarding: OnboardingConfig = { install: params => isAutoInstall(params) @@ -314,28 +408,35 @@ const onboarding: OnboardingConfig = { )}

), - configurations: [ - { - language: 'swift', - code: getConfigurationSnippet(params), - }, - { - description: ( -

- {tct( - "When using SwiftUI and your app doesn't implement an app delegate, initialize the SDK within the [initializer: App conformer's initializer]:", - { - initializer: ( - - ), - } - )} -

- ), - language: 'swift', - code: getConfigurationSnippetSwiftUi(params), - }, - ], + configurations: isManualSwift(params) + ? [ + { + language: 'swift', + code: getConfigurationSnippetSwift(params), + }, + { + description: ( +

+ {tct( + "When using SwiftUI and your app doesn't implement an app delegate, initialize the SDK within the [initializer: App conformer's initializer]:", + { + initializer: ( + + ), + } + )} +

+ ), + language: 'swift', + code: getConfigurationSnippetSwiftUi(params), + }, + ] + : [ + { + language: 'objc', + code: getConfigurationSnippetObjectiveC(params), + }, + ], }, ], verify: params => @@ -361,12 +462,41 @@ const onboarding: OnboardingConfig = { )}

), - configurations: [ - { - language: 'swift', - code: getVerifySnippet(), - }, - ], + configurations: (() => { + const configs = [ + { + language: selectedLanguage(params), + code: getVerifySnippet(params), + }, + { + description: ( +

+ { + 'If your application does not have UI, you can use the following line to trigger a crash 10 seconds after the method is called:' + } +

+ ), + language: selectedLanguage(params), + code: noUIErrorSnippet(params), + }, + ]; + + if (isManualSwift(params)) { + configs.splice(1, 0, { + description: ( +

+ { + 'If you are using SwiftUI, you can use the following button to trigger a crash:' + } +

+ ), + language: 'swift', + code: buttonSnippetSwift(), + }); + } + + return configs; + })(), }, ], nextSteps: () => [ From b2c990af2773ddffae6b897a3019fa48e25748b4 Mon Sep 17 00:00:00 2001 From: Itay Date: Thu, 29 May 2025 17:24:59 -0300 Subject: [PATCH 2/3] Update tests --- .../app/gettingStartedDocs/apple/ios.spec.tsx | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/static/app/gettingStartedDocs/apple/ios.spec.tsx b/static/app/gettingStartedDocs/apple/ios.spec.tsx index b9a04a05314071..99f909e7dd9ca8 100644 --- a/static/app/gettingStartedDocs/apple/ios.spec.tsx +++ b/static/app/gettingStartedDocs/apple/ios.spec.tsx @@ -18,11 +18,24 @@ describe('apple-ios onboarding docs', function () { expect(screen.getByRole('heading', {name: 'Verify'})).toBeInTheDocument(); }); + it('renders swift onboarding docs correctly', async function () { + renderWithOnboardingLayout(docs, { + selectedProducts: [ProductSolution.ERROR_MONITORING], + selectedOptions: { + installationMode: InstallationMode.MANUAL_SWIFT, + }, + }); + + expect( + await screen.findAllByText(textWithMarkupMatcher(/Button\(\"Break the world\"\)/)) + ).toHaveLength(1); + }); + it('renders performance onboarding docs correctly', async function () { renderWithOnboardingLayout(docs, { selectedProducts: [ProductSolution.PERFORMANCE_MONITORING], selectedOptions: { - installationMode: InstallationMode.MANUAL, + installationMode: InstallationMode.MANUAL_SWIFT, }, }); @@ -34,7 +47,7 @@ describe('apple-ios onboarding docs', function () { it('renders transaction profiling', async function () { renderWithOnboardingLayout(docs, { selectedOptions: { - installationMode: InstallationMode.MANUAL, + installationMode: InstallationMode.MANUAL_SWIFT, }, }); @@ -61,7 +74,7 @@ describe('apple-ios onboarding docs', function () { docs, { selectedOptions: { - installationMode: InstallationMode.MANUAL, + installationMode: InstallationMode.MANUAL_SWIFT, }, }, { @@ -87,4 +100,17 @@ describe('apple-ios onboarding docs', function () { expect(lifecycleElements).toHaveLength(2); lifecycleElements.forEach(element => expect(element).toBeInTheDocument()); }); + + it('renders manual objective-c docs correctly', async function () { + renderWithOnboardingLayout(docs, { + selectedProducts: [ProductSolution.ERROR_MONITORING], + selectedOptions: { + installationMode: InstallationMode.MANUAL_OBJECTIVE_C, + }, + }); + + expect( + await screen.findAllByText(textWithMarkupMatcher(/@import Sentry;/)) + ).toHaveLength(3); + }); }); From 4429c04029a61c6b0a00ba9f0aba2afdcb9243bc Mon Sep 17 00:00:00 2001 From: Itay Date: Mon, 2 Jun 2025 15:39:06 -0700 Subject: [PATCH 3/3] Update ios get started snippets --- .../app/gettingStartedDocs/apple/ios.spec.tsx | 6 +- static/app/gettingStartedDocs/apple/ios.tsx | 105 ++++++------------ 2 files changed, 37 insertions(+), 74 deletions(-) diff --git a/static/app/gettingStartedDocs/apple/ios.spec.tsx b/static/app/gettingStartedDocs/apple/ios.spec.tsx index 99f909e7dd9ca8..6bac2c5d621bb8 100644 --- a/static/app/gettingStartedDocs/apple/ios.spec.tsx +++ b/static/app/gettingStartedDocs/apple/ios.spec.tsx @@ -27,7 +27,9 @@ describe('apple-ios onboarding docs', function () { }); expect( - await screen.findAllByText(textWithMarkupMatcher(/Button\(\"Break the world\"\)/)) + await screen.findAllByText( + textWithMarkupMatcher(/throw MyCustomError\.myFirstIssue/) + ) ).toHaveLength(1); }); @@ -111,6 +113,6 @@ describe('apple-ios onboarding docs', function () { expect( await screen.findAllByText(textWithMarkupMatcher(/@import Sentry;/)) - ).toHaveLength(3); + ).toHaveLength(1); }); }); diff --git a/static/app/gettingStartedDocs/apple/ios.tsx b/static/app/gettingStartedDocs/apple/ios.tsx index cf3c8bcc5e91dc..455ef00ebdc070 100644 --- a/static/app/gettingStartedDocs/apple/ios.tsx +++ b/static/app/gettingStartedDocs/apple/ios.tsx @@ -225,26 +225,34 @@ struct SwiftUIApp: App { const getVerifySnippet = (params: Params) => isManualSwift(params) ? ` -let button = UIButton(type: .roundedRect) -button.frame = CGRect(x: 20, y: 50, width: 100, height: 30) -button.setTitle("Break the world", for: []) -button.addTarget(self, action: #selector(self.breakTheWorld(_:)), for: .touchUpInside) -view.addSubview(button) - -@IBAction func breakTheWorld(_ sender: AnyObject) { - fatalError("Break the world") +enum MyCustomError: Error { + case myFirstIssue +} + +func thisFunctionThrows() throws { + throw MyCustomError.myFirstIssue +} + +func verifySentrySDK() { + do { + try thisFunctionThrows() + } catch { + SentrySDK.capture(error: error) + } }` : ` -@import Sentry; - -UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; -button.frame = CGRectMake(20, 50, 100, 30); -[button setTitle:@"Break the world" forState:UIControlStateNormal]; -[button addTarget:self action:@selector(breakTheWorld:) forControlEvents:UIControlEventTouchUpInside]; -[view addSubview:button]; +- (void)thisFunctionReturnsAnError:(NSError **)error { + *error = [NSError errorWithDomain:@"com.example.myapp" + code:1001 + userInfo:@{ + NSLocalizedDescriptionKey: @"Something went wrong." + }]; +} -- (IBAction)breakTheWorld:(id)sender { - [SentrySDK crash]; +- (void)verifySentrySDK { + NSError *error = nil; + [self thisFunctionReturnsAnError:&error]; + [SentrySDK captureError:error]; }`; const getReplaySetupSnippet = (params: Params) => ` @@ -260,24 +268,6 @@ const getReplayConfigurationSnippet = () => ` options.sessionReplay.maskAllText = true options.sessionReplay.maskAllImages = true`; -const noUIErrorSnippet = (params: Params) => - isManualSwift(params) - ? ` -import Sentry - -DispatchQueue.global().asyncAfter(deadline: .now() + 10) { - SentrySDK.crash() -}` - : ` -@import Sentry; - -[SentrySDK performSelector:@selector(crash) withObject:nil afterDelay:10];`; - -const buttonSnippetSwift = () => ` -Button("Break the world") { - fatalError("Break the world") -}`; - const onboarding: OnboardingConfig = { install: params => isAutoInstall(params) @@ -455,48 +445,19 @@ const onboarding: OnboardingConfig = { description: (

{tct( - 'This snippet contains an intentional error you can use to test that errors are uploaded to Sentry correctly. You can add it to your main [viewController: ViewController].', + 'This snippet contains an intentional error you can use to test that errors are uploaded to Sentry correctly. You can call [verifySentrySDK: verifySentrySDK()] from where you want to test it.', { - viewController: , + verifySentrySDK: , } )}

), - configurations: (() => { - const configs = [ - { - language: selectedLanguage(params), - code: getVerifySnippet(params), - }, - { - description: ( -

- { - 'If your application does not have UI, you can use the following line to trigger a crash 10 seconds after the method is called:' - } -

- ), - language: selectedLanguage(params), - code: noUIErrorSnippet(params), - }, - ]; - - if (isManualSwift(params)) { - configs.splice(1, 0, { - description: ( -

- { - 'If you are using SwiftUI, you can use the following button to trigger a crash:' - } -

- ), - language: 'swift', - code: buttonSnippetSwift(), - }); - } - - return configs; - })(), + configurations: [ + { + language: selectedLanguage(params), + code: getVerifySnippet(params), + }, + ], }, ], nextSteps: () => [