diff --git a/static/app/components/onboarding/productSelection.tsx b/static/app/components/onboarding/productSelection.tsx index 837ba0cf9f86d7..aadf967392ef1f 100644 --- a/static/app/components/onboarding/productSelection.tsx +++ b/static/app/components/onboarding/productSelection.tsx @@ -112,6 +112,7 @@ export const platformProductAvailability = { ProductSolution.SESSION_REPLAY, ProductSolution.LOGS, ], + dart: [ProductSolution.PERFORMANCE_MONITORING, ProductSolution.LOGS], kotlin: [ProductSolution.PERFORMANCE_MONITORING], go: [ProductSolution.PERFORMANCE_MONITORING, ProductSolution.LOGS], 'go-echo': [ProductSolution.PERFORMANCE_MONITORING, ProductSolution.LOGS], diff --git a/static/app/components/platformPicker.spec.tsx b/static/app/components/platformPicker.spec.tsx index 3df30e9dc56963..d89a831956a5ca 100644 --- a/static/app/components/platformPicker.spec.tsx +++ b/static/app/components/platformPicker.spec.tsx @@ -78,6 +78,7 @@ describe('PlatformPicker', function () { 'Angular', 'Astro', 'Browser JavaScript', + 'Dart', 'Ember', 'Flutter', 'Gatsby', diff --git a/static/app/data/platformPickerCategories.tsx b/static/app/data/platformPickerCategories.tsx index e10a7ae6ca9d64..a9b1e353e46aec 100644 --- a/static/app/data/platformPickerCategories.tsx +++ b/static/app/data/platformPickerCategories.tsx @@ -34,6 +34,7 @@ const popularPlatformCategories: Set = new Set([ ]); const browser: Set = new Set([ + 'dart', 'flutter', 'javascript', 'javascript-angular', @@ -58,6 +59,7 @@ const browser: Set = new Set([ const server: Set = new Set([ 'bun', + 'dart', 'deno', 'dotnet', 'dotnet-aspnet', @@ -122,6 +124,7 @@ const mobile: Set = new Set([ 'cordova', 'dotnet-maui', 'dotnet-xamarin', + 'dart', 'flutter', 'ionic', 'react-native', @@ -136,6 +139,7 @@ const desktop: Set = new Set([ 'dotnet-winforms', 'dotnet-wpf', 'electron', + 'dart', 'flutter', 'godot', 'java', diff --git a/static/app/gettingStartedDocs/dart/dart.tsx b/static/app/gettingStartedDocs/dart/dart.tsx index 00dfa6f457950b..0dfbcaf92c5c2d 100644 --- a/static/app/gettingStartedDocs/dart/dart.tsx +++ b/static/app/gettingStartedDocs/dart/dart.tsx @@ -3,6 +3,7 @@ import type { Docs, DocsParams, OnboardingConfig, + OnboardingStep, } from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; import { @@ -16,7 +17,7 @@ type Params = DocsParams; const getInstallSnippet = (params: Params) => ` dependencies: - sentry: ^${getPackageVersion(params, 'sentry.dart', '7.8.0')}`; + sentry: ^${getPackageVersion(params, 'sentry.dart', '9.6.0')}`; const getConfigureSnippet = (params: Params) => ` import 'package:sentry/sentry.dart'; @@ -26,19 +27,37 @@ Future main() async { options.dsn = '${params.dsn.public}'; // Adds request headers and IP for users, // visit: https://docs.sentry.io/platforms/dart/data-management/data-collected/ for more info - options.sendDefaultPii = true; + options.sendDefaultPii = true;${ + params.isLogsSelected + ? ` + // Enable logs to be sent to Sentry + options.enableLogs = true;` + : '' + }${ + 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; + options.tracesSampleRate = 1.0;` + : '' + } }); // or define SENTRY_DSN via Dart environment variable (--dart-define) }`; -const getVerifySnippet = () => ` +const getVerifySnippet = (params: Params) => { + const logsCode = params.isLogsSelected + ? ` + // Send a log before throwing the error + Sentry.logger.info("User triggered test error", { + 'action': SentryLogAttribute.string('test_error'), + });` + : ''; + return ` import 'package:sentry/sentry.dart'; -try { +try {${logsCode} aMethodThatMightFail(); } catch (exception, stackTrace) { await Sentry.captureException( @@ -46,10 +65,10 @@ try { stackTrace: stackTrace, ); }`; +}; const getPerfomanceSnippet = () => ` import 'package:sentry/sentry.dart'; -import { getPackageVersion } from 'sentry/utils/gettingStartedDocs/getPackageVersion'; final transaction = Sentry.startTransaction('processOrderBatch()', 'task'); @@ -115,46 +134,83 @@ const onboarding: OnboardingConfig = { ], }, ], - verify: () => [ - { - type: StepType.VERIFY, - description: t( - 'Create an intentional error, so you can test that everything is working:' - ), - configurations: [ - { - language: 'dart', - code: getVerifySnippet(), - additionalInfo: tct( - "If you're new to Sentry, use the email alert to access your account and complete a product tour.[break] If you're an existing user and have disabled alerts, you won't receive this email.", - { - break:
, - } - ), - }, - ], - }, - { - title: t('Tracing'), - description: t( - "You'll be able to monitor the performance of your app using the SDK. For example:" - ), - configurations: [ - { - language: 'dart', - code: getPerfomanceSnippet(), - additionalInfo: tct( - 'To learn more about the API and automatic instrumentations, check out the [perfDocs: performance documentation].', - { - perfDocs: ( - - ), - } - ), - }, - ], - }, - ], + verify: params => { + const steps: OnboardingStep[] = [ + { + type: StepType.VERIFY, + description: t( + 'Create an intentional error, so you can test that everything is working:' + ), + configurations: [ + { + language: 'dart', + code: getVerifySnippet(params), + additionalInfo: tct( + "If you're new to Sentry, use the email alert to access your account and complete a product tour.[break] If you're an existing user and have disabled alerts, you won't receive this email.", + { + break:
, + } + ), + }, + ], + }, + ]; + + if (params.isPerformanceSelected) { + steps.push({ + title: t('Tracing'), + description: t( + "You'll be able to monitor the performance of your app using the SDK. For example:" + ), + configurations: [ + { + language: 'dart', + code: getPerfomanceSnippet(), + additionalInfo: tct( + 'To learn more about the API and automatic instrumentations, check out the [perfDocs: performance documentation].', + { + perfDocs: ( + + ), + } + ), + }, + ], + }); + } + + return steps; + }, + nextSteps: params => { + const steps = [ + { + name: t('Upload Debug Symbols'), + description: t( + 'We offer a range of methods to provide Sentry with debug symbols so that you can see symbolicated stack traces and find the cause of your errors faster.' + ), + link: 'https://docs.sentry.io/platforms/flutter/upload-debug/', + }, + { + name: t('Connect your Git Repo'), + description: t( + 'Adding our Git integrations will allow us determine suspect commits, comment on PRs, and create links directly to your source code from Sentry issues.' + ), + link: 'https://docs.sentry.io/organization/integrations/source-code-mgmt/', + }, + ]; + + if (params.isLogsSelected) { + steps.push({ + name: t('Structured Logs'), + description: t( + 'Learn how to send structured logs to Sentry and view them alongside your errors.' + ), + link: 'https://docs.sentry.io/platforms/dart/logs/', + }); + } + + return steps; + }, }; export const feedbackOnboardingCrashApiDart: OnboardingConfig = { diff --git a/static/app/gettingStartedDocs/flutter/flutter.spec.tsx b/static/app/gettingStartedDocs/flutter/flutter.spec.tsx index 5a88f837f51714..1bd7683f7b3e6d 100644 --- a/static/app/gettingStartedDocs/flutter/flutter.spec.tsx +++ b/static/app/gettingStartedDocs/flutter/flutter.spec.tsx @@ -75,6 +75,9 @@ describe('flutter onboarding docs', function () { expect( await screen.findByText(textWithMarkupMatcher(/options.tracesSampleRate/)) ).toBeInTheDocument(); + expect( + await screen.findByText(textWithMarkupMatcher(/options\.tracesSampleRate = 1\.0/)) + ).toBeInTheDocument(); expect( await screen.findByText( textWithMarkupMatcher( @@ -103,6 +106,9 @@ describe('flutter onboarding docs', function () { expect( await screen.findByText(textWithMarkupMatcher(/options.profilesSampleRate/)) ).toBeInTheDocument(); + expect( + await screen.findByText(textWithMarkupMatcher(/options\.profilesSampleRate = 1\.0/)) + ).toBeInTheDocument(); expect( await screen.findByText( textWithMarkupMatcher(/Flutter Profiling alpha is available/) @@ -128,10 +134,14 @@ describe('flutter onboarding docs', function () { }); expect( - await screen.findByText(textWithMarkupMatcher(/options.replay.sessionSampleRate/)) + await screen.findByText( + textWithMarkupMatcher(/options\.replay\.sessionSampleRate = 0\.1/) + ) ).toBeInTheDocument(); expect( - await screen.findByText(textWithMarkupMatcher(/options.replay.onErrorSampleRate/)) + await screen.findByText( + textWithMarkupMatcher(/options\.replay\.onErrorSampleRate = 1\.0/) + ) ).toBeInTheDocument(); }); @@ -153,6 +163,11 @@ describe('flutter onboarding docs', function () { await screen.findByText(textWithMarkupMatcher(/options\.enableLogs = true/)) ).toBeInTheDocument(); + // Should include a log call in the verify snippet + expect( + await screen.findByText(textWithMarkupMatcher(/Sentry\.logger\.info/)) + ).toBeInTheDocument(); + // Should include logging next step expect(await screen.findByText('Structured Logs')).toBeInTheDocument(); }); diff --git a/static/app/gettingStartedDocs/flutter/flutter.tsx b/static/app/gettingStartedDocs/flutter/flutter.tsx index 04d3ed39c7ec53..51d99155242a60 100644 --- a/static/app/gettingStartedDocs/flutter/flutter.tsx +++ b/static/app/gettingStartedDocs/flutter/flutter.tsx @@ -49,7 +49,7 @@ const isAutoInstall = (params: Params) => params.platformOptions?.installationMode === InstallationMode.AUTO; const getManualInstallSnippet = (params: Params) => { - const version = getPackageVersion(params, 'sentry.dart.flutter', '8.13.2'); + const version = getPackageVersion(params, 'sentry.dart.flutter', '9.6.0'); return `dependencies: sentry_flutter: ^${version}`; }; @@ -64,6 +64,19 @@ Future main() async { // Adds request headers and IP for users, // visit: https://docs.sentry.io/platforms/dart/data-management/data-collected/ for more info options.sendDefaultPii = true;${ + params.isLogsSelected + ? ` + // Enable logs to be sent to Sentry + options.enableLogs = true;` + : '' + }${ + params.isReplaySelected + ? ` + // Set sessionSampleRate to 0.1 to capture 10% of sessions and onErrorSampleRate to 1.0 to capture 100% of errors. + options.replay.sessionSampleRate = 0.1; + options.replay.onErrorSampleRate = 1.0;` + : '' + }${ params.isPerformanceSelected ? ` // Set tracesSampleRate to 1.0 to capture 100% of transactions for tracing. @@ -77,19 +90,6 @@ Future main() async { // Setting to 1.0 will profile 100% of sampled transactions: options.profilesSampleRate = 1.0;` : '' - }${ - params.isReplaySelected - ? ` - // Set sessionSampleRate to 0.1 to capture 10% of sessions and onErrorSampleRate to 1.0 to capture 100% of errors. - options.replay.sessionSampleRate = 0.1; - options.replay.onErrorSampleRate = 1.0;` - : '' - }${ - params.isLogsSelected - ? ` - // Enable logs to be sent to Sentry - options.enableLogs = true;` - : '' } }, appRunner: () => runApp( @@ -109,14 +109,25 @@ const configureAdditionalInfo = tct( } ); -const getVerifySnippet = () => ` +const getVerifySnippet = (params: Params) => { + const logsCode = params.isLogsSelected + ? ` + // Send a log before throwing the error + Sentry.logger.info("User triggered test error button", { + 'action': SentryLogAttribute.string('test_error_button_click'), + });` + : ''; + return ` +import 'package:sentry/sentry.dart'; + child: ElevatedButton( - onPressed: () { + onPressed: () {${logsCode} throw StateError('This is test exception'); }, child: const Text('Verify Sentry Setup'), ) `; +}; const getFeedbackConfigureSnippet = () => ` // The example uses the NavigatorState to present the widget. Adapt as needed to your navigation stack. @@ -342,7 +353,7 @@ const onboarding: OnboardingConfig = { label: 'Dart', value: 'dart', language: 'dart', - code: getVerifySnippet(), + code: getVerifySnippet(params), }, ], },