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: () => [