diff --git a/static/app/gettingStartedDocs/javascript/react.tsx b/static/app/gettingStartedDocs/javascript/react.tsx deleted file mode 100644 index a77e1a7803471d..00000000000000 --- a/static/app/gettingStartedDocs/javascript/react.tsx +++ /dev/null @@ -1,669 +0,0 @@ -import {ExternalLink} from 'sentry/components/core/link'; -import {buildSdkConfig} from 'sentry/components/onboarding/gettingStartedDoc/buildSdkConfig'; -import crashReportCallout from 'sentry/components/onboarding/gettingStartedDoc/feedback/crashReportCallout'; -import {widgetCalloutBlock} from 'sentry/components/onboarding/gettingStartedDoc/feedback/widgetCallout'; -import {tracePropagationBlock} from 'sentry/components/onboarding/gettingStartedDoc/replay/tracePropagationMessage'; -import type { - ContentBlock, - Docs, - DocsParams, - OnboardingConfig, -} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import { - getAIRulesForCodeEditorStep, - getUploadSourceMapsStep, -} from 'sentry/components/onboarding/gettingStartedDoc/utils'; -import { - getCrashReportJavaScriptInstallSteps, - getCrashReportModalConfigDescription, - getCrashReportModalIntroduction, - getFeedbackConfigOptions, - getFeedbackConfigureDescription, -} from 'sentry/components/onboarding/gettingStartedDoc/utils/feedbackOnboarding'; -import { - getReplayConfigOptions, - getReplayConfigureDescription, - getReplayVerifyStep, -} from 'sentry/components/onboarding/gettingStartedDoc/utils/replayOnboarding'; -import {featureFlag} from 'sentry/gettingStartedDocs/javascript/javascript/featureFlag'; -import {t, tct} from 'sentry/locale'; -import { - getJavascriptLogsOnboarding, - getJavascriptProfilingOnboarding, -} from 'sentry/utils/gettingStartedDocs/javascript'; - -type Params = DocsParams; - -const getDynamicParts = (params: Params): string[] => { - const dynamicParts: string[] = []; - - if (params.isPerformanceSelected) { - dynamicParts.push(` - // Tracing - tracesSampleRate: 1.0, // Capture 100% of the transactions - // Set 'tracePropagationTargets' to control for which URLs distributed tracing should be enabled - tracePropagationTargets: ["localhost", /^https:\\/\\/yourserver\\.io\\/api/]`); - } - - if (params.isReplaySelected) { - dynamicParts.push(` - // Session Replay - replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production. - replaysOnErrorSampleRate: 1.0 // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur.`); - } - - if (params.isLogsSelected) { - dynamicParts.push(` - // Enable logs to be sent to Sentry - enableLogs: true`); - } - - if (params.isProfilingSelected) { - dynamicParts.push(` - // Set profilesSampleRate to 1.0 to profile every transaction. - // Since profilesSampleRate is relative to tracesSampleRate, - // the final profiling rate can be computed as tracesSampleRate * profilesSampleRate - // For example, a tracesSampleRate of 0.5 and profilesSampleRate of 0.5 would - // results in 25% of transactions being profiled (0.5*0.5=0.25) - profilesSampleRate: 1.0`); - } - - return dynamicParts; -}; - -const getIntegrations = (params: Params): string[] => { - const integrations = []; - if (params.isPerformanceSelected) { - integrations.push(`Sentry.browserTracingIntegration()`); - } - - if (params.isProfilingSelected) { - integrations.push(`Sentry.browserProfilingIntegration()`); - } - - if (params.isReplaySelected) { - integrations.push( - `Sentry.replayIntegration(${getReplayConfigOptions(params.replayOptions)})` - ); - } - - if (params.isFeedbackSelected) { - integrations.push(` - Sentry.feedbackIntegration({ - colorScheme: "system", - ${getFeedbackConfigOptions(params.feedbackOptions)} - }),`); - } - - return integrations; -}; - -const getSdkSetupSnippet = (params: Params) => { - const config = buildSdkConfig({ - params, - staticParts: [ - `dsn: "${params.dsn.public}"`, - `// Setting this option to true will send default PII data to Sentry. - // For example, automatic IP address collection on events - sendDefaultPii: true`, - ], - getIntegrations, - getDynamicParts, - }); - - return ` -import * as Sentry from "@sentry/react"; - -Sentry.init({ - ${config} -}); - -const container = document.getElementById(“app”); -const root = createRoot(container); -root.render(); -`; -}; - -const getVerifySnippet = (params: Params) => { - const logsCode = params.isLogsSelected - ? ` - // Send a log before throwing the error - Sentry.logger.info('User triggered test error', { - action: 'test_error_button_click', - });` - : ''; - - return `import * as Sentry from '@sentry/react'; -// Add this button component to your app to test Sentry's error tracking -function ErrorButton() { - return ( - - ); -}`; -}; - -const installSnippetBlock: ContentBlock = { - type: 'code', - tabs: [ - { - label: 'npm', - language: 'bash', - code: 'npm install --save @sentry/react', - }, - { - label: 'yarn', - language: 'bash', - code: 'yarn add @sentry/react', - }, - { - label: 'pnpm', - language: 'bash', - code: 'pnpm add @sentry/react', - }, - ], -}; - -const onboarding: OnboardingConfig = { - introduction: () => - tct( - "In this quick guide you'll use [strong:npm], [strong:yarn], or [strong:pnpm] to set up:", - { - strong: , - } - ), - install: () => [ - { - type: StepType.INSTALL, - content: [ - { - type: 'text', - text: tct( - 'Add the Sentry SDK as a dependency using [code:npm], [code:yarn], or [code:pnpm]:', - {code: } - ), - }, - installSnippetBlock, - ], - }, - ], - configure: (params: Params) => [ - { - type: StepType.CONFIGURE, - content: [ - { - type: 'text', - text: t( - "Initialize Sentry as early as possible in your application's lifecycle." - ), - }, - { - type: 'code', - tabs: [ - { - label: 'JavaScript', - language: 'javascript', - code: getSdkSetupSnippet(params), - }, - ], - }, - { - type: 'conditional', - condition: params.isReplaySelected, - content: [tracePropagationBlock], - }, - ], - }, - getUploadSourceMapsStep({ - guideLink: 'https://docs.sentry.io/platforms/javascript/guides/react/sourcemaps/', - ...params, - }), - getAIRulesForCodeEditorStep({ - // ATTENTION: The rules defined here must match those in the documentation (see: https://github.com/getsentry/sentry-docs/blob/master/platform-includes/llm-rules-logs/javascript.react.mdx). - // If you make any changes, please update the docs accordingly. - rules: ` -These examples should be used as guidance when configuring Sentry functionality within a project. - -# Error / Exception Tracking - -Use \`Sentry.captureException(error)\` to capture an exception and log the error in Sentry. -Use this in try catch blocks or areas where exceptions are expected - -# Tracing Examples - -Spans should be created for meaningful actions within an applications like button clicks, API calls, and function calls -Ensure you are creating custom spans with meaningful names and operations -Use the \`Sentry.startSpan\` function to create a span -Child spans can exist within a parent span - -## Custom Span instrumentation in component actions - -\`\`\`javascript -function TestComponent() { - const handleTestButtonClick = () => { - // Create a transaction/span to measure performance - Sentry.startSpan( - { - op: "ui.click", - name: "Test Button Click", - }, - (span) => { - const value = "some config"; - const metric = "some metric"; - - // Metrics can be added to the span - span.setAttribute("config", value); - span.setAttribute("metric", metric); - - doSomething(); - }, - ); - }; - - return ( - - ); -} -\`\`\` - -## Custom span instrumentation in API calls - -\`\`\`javascript -async function fetchUserData(userId) { - return Sentry.startSpan( - { - op: "http.client", - name: \`GET /api/users/\${userId}\`, - }, - async () => { - const response = await fetch(\`/api/users/\${userId}\`); - const data = await response.json(); - return data; - }, - ); -} -\`\`\` - -# Logs - -Where logs are used, ensure Sentry is imported using \`import * as Sentry from "@sentry/react"\` -Enable logging in Sentry using \`Sentry.init({ enableLogs: true })\` -Reference the logger using \`const { logger } = Sentry\` -Sentry offers a consoleLoggingIntegration that can be used to log specific console error types automatically without instrumenting the individual logger calls - -## Configuration - -### Baseline - -\`\`\`javascript -import * as Sentry from "@sentry/react"; - -Sentry.init({ - dsn: "${params.dsn.public}", - - enableLogs: true, -}); -\`\`\` - -### Logger Integration - -\`\`\`javascript -Sentry.init({ - dsn: "${params.dsn.public}", - integrations: [ - // send console.log, console.warn, and console.error calls as logs to Sentry - Sentry.consoleLoggingIntegration({ levels: ["log", "warn", "error"] }), - ], -}); -\`\`\` - -## Logger Examples - -\`logger.fmt\` is a template literal function that should be used to bring variables into the structured logs. - -\`\`\`javascript -logger.trace("Starting database connection", { database: "users" }); -logger.debug(logger.fmt\`Cache miss for user: \${userId}\`); -logger.info("Updated profile", { profileId: 345 }); -logger.warn("Rate limit reached for endpoint", { - endpoint: "/api/results/", - isEnterprise: false, -}); -logger.error("Failed to process payment", { - orderId: "order_123", - amount: 99.99, -}); -logger.fatal("Database connection pool exhausted", { - database: "users", - activeConnections: 100, -}); -\`\`\` -`, - }), - ], - verify: (params: Params) => [ - { - type: StepType.VERIFY, - content: [ - { - type: 'text', - text: t( - "This snippet contains an intentional error and can be used as a test to make sure that everything's working as expected." - ), - }, - { - type: 'code', - tabs: [ - { - label: 'React', - language: 'javascript', - code: getVerifySnippet(params), - }, - ], - }, - ], - }, - ], - nextSteps: (params: Params) => { - const steps = [ - { - id: 'react-features', - name: t('React Features'), - description: t( - 'Learn about our first class integration with the React framework.' - ), - link: 'https://docs.sentry.io/platforms/javascript/guides/react/features/', - }, - ]; - - if (params.isPerformanceSelected) { - steps.push({ - id: 'react-router', - name: t('React Router'), - description: t( - 'Configure routing, so Sentry can generate parameterized route names for better grouping of tracing data.' - ), - link: 'https://docs.sentry.io/platforms/javascript/guides/react/configuration/integrations/react-router/', - }); - } - - if (params.isLogsSelected) { - steps.push({ - id: 'logs', - name: t('Logging Integrations'), - description: t( - 'Add logging integrations to automatically capture logs from your application.' - ), - link: 'https://docs.sentry.io/platforms/javascript/guides/react/logs/#integrations', - }); - } - - return steps; - }, -}; - -const replayOnboarding: OnboardingConfig = { - install: () => [ - { - type: StepType.INSTALL, - content: [ - { - type: 'text', - text: tct( - 'Add the Sentry SDK as a dependency using [code:npm], [code:yarn], or [code:pnpm]. You need a minimum version 7.27.0 of [code:@sentry/react] in order to use Session Replay. You do not need to install any additional packages.', - {code: } - ), - }, - installSnippetBlock, - ], - }, - ], - configure: (params: Params) => [ - { - type: StepType.CONFIGURE, - content: [ - { - type: 'text', - text: getReplayConfigureDescription({ - link: 'https://docs.sentry.io/platforms/javascript/guides/react/session-replay/', - }), - }, - { - type: 'code', - tabs: [ - { - label: 'JavaScript', - language: 'javascript', - code: getSdkSetupSnippet(params), - }, - ], - }, - ], - }, - ], - verify: getReplayVerifyStep(), - nextSteps: () => [], -}; - -const feedbackOnboarding: OnboardingConfig = { - install: () => [ - { - type: StepType.INSTALL, - content: [ - { - type: 'text', - text: tct( - 'For the User Feedback integration to work, you must have the Sentry browser SDK package, or an equivalent framework SDK (e.g. [code:@sentry/react]) installed, minimum version 7.85.0.', - { - code: , - } - ), - }, - installSnippetBlock, - ], - }, - ], - configure: (params: Params) => [ - { - type: StepType.CONFIGURE, - content: [ - { - type: 'text', - text: getFeedbackConfigureDescription({ - linkConfig: - 'https://docs.sentry.io/platforms/javascript/guides/react/user-feedback/configuration/', - linkButton: - 'https://docs.sentry.io/platforms/javascript/guides/react/user-feedback/configuration/#bring-your-own-button', - }), - }, - { - type: 'code', - tabs: [ - { - label: 'JavaScript', - language: 'javascript', - code: getSdkSetupSnippet(params), - }, - ], - }, - { - type: 'custom', - content: crashReportCallout({ - link: 'https://docs.sentry.io/platforms/javascript/guides/react/user-feedback/#crash-report-modal', - }), - }, - ], - }, - ], - verify: () => [], - nextSteps: () => [], -}; - -const crashReportOnboarding: OnboardingConfig = { - introduction: () => getCrashReportModalIntroduction(), - install: (params: Params) => getCrashReportJavaScriptInstallSteps(params), - configure: () => [ - { - type: StepType.CONFIGURE, - content: [ - { - type: 'text', - text: getCrashReportModalConfigDescription({ - link: 'https://docs.sentry.io/platforms/javascript/guides/react/user-feedback/configuration/#crash-report-modal', - }), - }, - widgetCalloutBlock({ - link: 'https://docs.sentry.io/platforms/javascript/guides/react/user-feedback/#user-feedback-widget', - }), - ], - }, - ], - verify: () => [], - nextSteps: () => [], -}; - -const performanceOnboarding: OnboardingConfig = { - introduction: () => - t( - "Adding Performance to your React project is simple. Make sure you've got these basics down." - ), - install: onboarding.install, - configure: params => [ - { - type: StepType.CONFIGURE, - content: [ - { - type: 'text', - text: t( - "Configuration should happen as early as possible in your application's lifecycle." - ), - }, - { - type: 'code', - language: 'javascript', - code: ` -import React from "react"; -import ReactDOM from "react-dom"; -import * as Sentry from "@sentry/react"; -import App from "./App"; - -Sentry.init({ - dsn: "${params.dsn.public}", - integrations: [Sentry.browserTracingIntegration()], - - // Set tracesSampleRate to 1.0 to capture 100% - // of transactions for performance monitoring. - // We recommend adjusting this value in production - tracesSampleRate: 1.0, - // Set \`tracePropagationTargets\` to control for which URLs distributed tracing should be enabled - tracePropagationTargets: ["localhost", /^https:\\/\\/yourserver\\.io\\/api/], -}); - -ReactDOM.render(, document.getElementById("root")); - -// Can also use with React Concurrent Mode -// ReactDOM.createRoot(document.getElementById('root')).render(); -`, - }, - { - type: 'text', - text: tct( - 'We recommend adjusting the value of [code:tracesSampleRate] in production. Learn more about tracing [linkTracingOptions:options], how to use the [linkTracesSampler:traces_sampler] function, or how to do [linkSampleTransactions:sampling].', - { - code: , - linkTracingOptions: ( - - ), - linkTracesSampler: ( - - ), - linkSampleTransactions: ( - - ), - } - ), - }, - ], - }, - { - title: t('Add Distributed Tracing (Optional)'), - content: [ - { - type: 'text', - text: tct( - "If you're using the current version of our JavaScript SDK and have enabled the [code: BrowserTracing] integration, distributed tracing will work out of the box. To get around possible [link:Browser CORS] issues, define your [code:tracePropagationTargets].", - { - code: , - link: ( - - ), - } - ), - }, - { - type: 'code', - language: 'javascript', - code: ` -Sentry.init({ - dsn: "${params.dsn.public}", - integrations: [Sentry.browserTracingIntegration()], - tracePropagationTargets: ["localhost", /^https:\\/\\/yourserver\\.io\\/api/] -}); -`, - }, - ], - }, - ], - verify: () => [ - { - type: StepType.VERIFY, - content: [ - { - type: 'text', - text: tct( - 'Verify that performance monitoring is working correctly with our [link:automatic instrumentation] by simply using your React application.', - { - link: ( - - ), - } - ), - }, - ], - }, - ], - nextSteps: () => [], -}; - -const profilingOnboarding = getJavascriptProfilingOnboarding({ - installSnippetBlock, - docsLink: - 'https://docs.sentry.io/platforms/javascript/guides/react/profiling/browser-profiling/', -}); - -const logsOnboarding: OnboardingConfig = getJavascriptLogsOnboarding({ - installSnippetBlock, - docsPlatform: 'react', - packageName: '@sentry/react', -}); - -const docs: Docs = { - onboarding, - feedbackOnboardingNpm: feedbackOnboarding, - replayOnboarding, - performanceOnboarding, - crashReportOnboarding, - profilingOnboarding, - logsOnboarding, - featureFlagOnboarding: featureFlag, -}; - -export default docs; diff --git a/static/app/gettingStartedDocs/javascript/react/crashReport.tsx b/static/app/gettingStartedDocs/javascript/react/crashReport.tsx new file mode 100644 index 00000000000000..69b06d86c1875c --- /dev/null +++ b/static/app/gettingStartedDocs/javascript/react/crashReport.tsx @@ -0,0 +1,34 @@ +import {widgetCalloutBlock} from 'sentry/components/onboarding/gettingStartedDoc/feedback/widgetCallout'; +import type { + DocsParams, + OnboardingConfig, +} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import { + getCrashReportJavaScriptInstallSteps, + getCrashReportModalConfigDescription, + getCrashReportModalIntroduction, +} from 'sentry/components/onboarding/gettingStartedDoc/utils/feedbackOnboarding'; + +export const crashReport: OnboardingConfig = { + introduction: () => getCrashReportModalIntroduction(), + install: (params: DocsParams) => getCrashReportJavaScriptInstallSteps(params), + configure: () => [ + { + type: StepType.CONFIGURE, + content: [ + { + type: 'text', + text: getCrashReportModalConfigDescription({ + link: 'https://docs.sentry.io/platforms/javascript/guides/react/user-feedback/configuration/#crash-report-modal', + }), + }, + widgetCalloutBlock({ + link: 'https://docs.sentry.io/platforms/javascript/guides/react/user-feedback/#user-feedback-widget', + }), + ], + }, + ], + verify: () => [], + nextSteps: () => [], +}; diff --git a/static/app/gettingStartedDocs/javascript/react/feedback.tsx b/static/app/gettingStartedDocs/javascript/react/feedback.tsx new file mode 100644 index 00000000000000..e4e723ab75f43b --- /dev/null +++ b/static/app/gettingStartedDocs/javascript/react/feedback.tsx @@ -0,0 +1,64 @@ +import crashReportCallout from 'sentry/components/onboarding/gettingStartedDoc/feedback/crashReportCallout'; +import type { + DocsParams, + OnboardingConfig, +} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {getFeedbackConfigureDescription} from 'sentry/components/onboarding/gettingStartedDoc/utils/feedbackOnboarding'; +import {tct} from 'sentry/locale'; + +import {getSdkSetupSnippet, installSnippetBlock} from './utils'; + +export const feedback: OnboardingConfig = { + install: () => [ + { + type: StepType.INSTALL, + content: [ + { + type: 'text', + text: tct( + 'For the User Feedback integration to work, you must have the Sentry browser SDK package, or an equivalent framework SDK (e.g. [code:@sentry/react]) installed, minimum version 7.85.0.', + { + code: , + } + ), + }, + installSnippetBlock, + ], + }, + ], + configure: (params: DocsParams) => [ + { + type: StepType.CONFIGURE, + content: [ + { + type: 'text', + text: getFeedbackConfigureDescription({ + linkConfig: + 'https://docs.sentry.io/platforms/javascript/guides/react/user-feedback/configuration/', + linkButton: + 'https://docs.sentry.io/platforms/javascript/guides/react/user-feedback/configuration/#bring-your-own-button', + }), + }, + { + type: 'code', + tabs: [ + { + label: 'JavaScript', + language: 'javascript', + code: getSdkSetupSnippet(params), + }, + ], + }, + { + type: 'custom', + content: crashReportCallout({ + link: 'https://docs.sentry.io/platforms/javascript/guides/react/user-feedback/#crash-report-modal', + }), + }, + ], + }, + ], + verify: () => [], + nextSteps: () => [], +}; diff --git a/static/app/gettingStartedDocs/javascript/react/index.tsx b/static/app/gettingStartedDocs/javascript/react/index.tsx new file mode 100644 index 00000000000000..72d5254b184a90 --- /dev/null +++ b/static/app/gettingStartedDocs/javascript/react/index.tsx @@ -0,0 +1,23 @@ +import type {Docs} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {featureFlag} from 'sentry/gettingStartedDocs/javascript/javascript/featureFlag'; +import {logs} from 'sentry/gettingStartedDocs/javascript/react/logs'; + +import {crashReport} from './crashReport'; +import {feedback} from './feedback'; +import {onboarding} from './onboarding'; +import {performance} from './performance'; +import {profiling} from './profiling'; +import {replay} from './replay'; + +const docs: Docs = { + onboarding, + feedbackOnboardingNpm: feedback, + replayOnboarding: replay, + performanceOnboarding: performance, + crashReportOnboarding: crashReport, + profilingOnboarding: profiling, + logsOnboarding: logs, + featureFlagOnboarding: featureFlag, +}; + +export default docs; diff --git a/static/app/gettingStartedDocs/javascript/react/logs.tsx b/static/app/gettingStartedDocs/javascript/react/logs.tsx new file mode 100644 index 00000000000000..2868ca22548df8 --- /dev/null +++ b/static/app/gettingStartedDocs/javascript/react/logs.tsx @@ -0,0 +1,9 @@ +import {getJavascriptLogsOnboarding} from 'sentry/utils/gettingStartedDocs/javascript'; + +import {installSnippetBlock} from './utils'; + +export const logs = getJavascriptLogsOnboarding({ + installSnippetBlock, + docsPlatform: 'react', + packageName: '@sentry/react', +}); diff --git a/static/app/gettingStartedDocs/javascript/react.spec.tsx b/static/app/gettingStartedDocs/javascript/react/onboarding.spec.tsx similarity index 99% rename from static/app/gettingStartedDocs/javascript/react.spec.tsx rename to static/app/gettingStartedDocs/javascript/react/onboarding.spec.tsx index 3b1febf24bfad6..e895f82fe22b2b 100644 --- a/static/app/gettingStartedDocs/javascript/react.spec.tsx +++ b/static/app/gettingStartedDocs/javascript/react/onboarding.spec.tsx @@ -4,7 +4,7 @@ import {textWithMarkupMatcher} from 'sentry-test/utils'; import {ProductSolution} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import docs from './react'; +import docs from '.'; describe('javascript-react onboarding docs', () => { it('renders onboarding docs correctly', () => { diff --git a/static/app/gettingStartedDocs/javascript/react/onboarding.tsx b/static/app/gettingStartedDocs/javascript/react/onboarding.tsx new file mode 100644 index 00000000000000..0b11453ad0b163 --- /dev/null +++ b/static/app/gettingStartedDocs/javascript/react/onboarding.tsx @@ -0,0 +1,277 @@ +import {tracePropagationBlock} from 'sentry/components/onboarding/gettingStartedDoc/replay/tracePropagationMessage'; +import type { + DocsParams, + OnboardingConfig, +} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import { + getAIRulesForCodeEditorStep, + getUploadSourceMapsStep, +} from 'sentry/components/onboarding/gettingStartedDoc/utils'; +import {t, tct} from 'sentry/locale'; + +import {getSdkSetupSnippet, installSnippetBlock} from './utils'; + +const getVerifySnippet = (params: DocsParams) => { + const logsCode = params.isLogsSelected + ? ` + // Send a log before throwing the error + Sentry.logger.info('User triggered test error', { + action: 'test_error_button_click', + });` + : ''; + + return `import * as Sentry from '@sentry/react'; +// Add this button component to your app to test Sentry's error tracking +function ErrorButton() { + return ( + + ); +}`; +}; + +export const onboarding: OnboardingConfig = { + introduction: () => + tct( + "In this quick guide you'll use [strong:npm], [strong:yarn], or [strong:pnpm] to set up:", + { + strong: , + } + ), + install: () => [ + { + type: StepType.INSTALL, + content: [ + { + type: 'text', + text: tct( + 'Add the Sentry SDK as a dependency using [code:npm], [code:yarn], or [code:pnpm]:', + {code: } + ), + }, + installSnippetBlock, + ], + }, + ], + configure: (params: DocsParams) => [ + { + type: StepType.CONFIGURE, + content: [ + { + type: 'text', + text: t( + "Initialize Sentry as early as possible in your application's lifecycle." + ), + }, + { + type: 'code', + tabs: [ + { + label: 'JavaScript', + language: 'javascript', + code: getSdkSetupSnippet(params), + }, + ], + }, + { + type: 'conditional', + condition: params.isReplaySelected, + content: [tracePropagationBlock], + }, + ], + }, + getUploadSourceMapsStep({ + guideLink: 'https://docs.sentry.io/platforms/javascript/guides/react/sourcemaps/', + ...params, + }), + getAIRulesForCodeEditorStep({ + // ATTENTION: The rules defined here must match those in the documentation (see: https://github.com/getsentry/sentry-docs/blob/master/platform-includes/llm-rules-logs/javascript.react.mdx). + // If you make any changes, please update the docs accordingly. + rules: ` +These examples should be used as guidance when configuring Sentry functionality within a project. + +# Error / Exception Tracking + +Use \`Sentry.captureException(error)\` to capture an exception and log the error in Sentry. +Use this in try catch blocks or areas where exceptions are expected + +# Tracing Examples + +Spans should be created for meaningful actions within an applications like button clicks, API calls, and function calls +Ensure you are creating custom spans with meaningful names and operations +Use the \`Sentry.startSpan\` function to create a span +Child spans can exist within a parent span + +## Custom Span instrumentation in component actions + +\`\`\`javascript +function TestComponent() { + const handleTestButtonClick = () => { + // Create a transaction/span to measure performance + Sentry.startSpan( + { + op: "ui.click", + name: "Test Button Click", + }, + (span) => { + const value = "some config"; + const metric = "some metric"; + + // Metrics can be added to the span + span.setAttribute("config", value); + span.setAttribute("metric", metric); + + doSomething(); + }, + ); + }; + + return ( + + ); +} +\`\`\` + +## Custom span instrumentation in API calls + +\`\`\`javascript +async function fetchUserData(userId) { + return Sentry.startSpan( + { + op: "http.client", + name: \`GET /api/users/\${userId}\`, + }, + async () => { + const response = await fetch(\`/api/users/\${userId}\`); + const data = await response.json(); + return data; + }, + ); +} +\`\`\` + +# Logs + +Where logs are used, ensure Sentry is imported using \`import * as Sentry from "@sentry/react"\` +Enable logging in Sentry using \`Sentry.init({ enableLogs: true })\` +Reference the logger using \`const { logger } = Sentry\` +Sentry offers a consoleLoggingIntegration that can be used to log specific console error types automatically without instrumenting the individual logger calls + +## Configuration + +### Baseline + +\`\`\`javascript +import * as Sentry from "@sentry/react"; + +Sentry.init({ + dsn: "${params.dsn.public}", + + enableLogs: true, +}); +\`\`\` + +### Logger Integration + +\`\`\`javascript +Sentry.init({ + dsn: "${params.dsn.public}", + integrations: [ + // send console.log, console.warn, and console.error calls as logs to Sentry + Sentry.consoleLoggingIntegration({ levels: ["log", "warn", "error"] }), + ], +}); +\`\`\` + +## Logger Examples + +\`logger.fmt\` is a template literal function that should be used to bring variables into the structured logs. + +\`\`\`javascript +logger.trace("Starting database connection", { database: "users" }); +logger.debug(logger.fmt\`Cache miss for user: \${userId}\`); +logger.info("Updated profile", { profileId: 345 }); +logger.warn("Rate limit reached for endpoint", { + endpoint: "/api/results/", + isEnterprise: false, +}); +logger.error("Failed to process payment", { + orderId: "order_123", + amount: 99.99, +}); +logger.fatal("Database connection pool exhausted", { + database: "users", + activeConnections: 100, +}); +\`\`\` +`, + }), + ], + verify: (params: DocsParams) => [ + { + type: StepType.VERIFY, + content: [ + { + type: 'text', + text: t( + "This snippet contains an intentional error and can be used as a test to make sure that everything's working as expected." + ), + }, + { + type: 'code', + tabs: [ + { + label: 'React', + language: 'javascript', + code: getVerifySnippet(params), + }, + ], + }, + ], + }, + ], + nextSteps: (params: DocsParams) => { + const steps = [ + { + id: 'react-features', + name: t('React Features'), + description: t( + 'Learn about our first class integration with the React framework.' + ), + link: 'https://docs.sentry.io/platforms/javascript/guides/react/features/', + }, + ]; + + if (params.isPerformanceSelected) { + steps.push({ + id: 'react-router', + name: t('React Router'), + description: t( + 'Configure routing, so Sentry can generate parameterized route names for better grouping of tracing data.' + ), + link: 'https://docs.sentry.io/platforms/javascript/guides/react/configuration/integrations/react-router/', + }); + } + + if (params.isLogsSelected) { + steps.push({ + id: 'logs', + name: t('Logging Integrations'), + description: t( + 'Add logging integrations to automatically capture logs from your application.' + ), + link: 'https://docs.sentry.io/platforms/javascript/guides/react/logs/#integrations', + }); + } + + return steps; + }, +}; diff --git a/static/app/gettingStartedDocs/javascript/react/performance.tsx b/static/app/gettingStartedDocs/javascript/react/performance.tsx new file mode 100644 index 00000000000000..90a85f76b6bf79 --- /dev/null +++ b/static/app/gettingStartedDocs/javascript/react/performance.tsx @@ -0,0 +1,119 @@ +import {ExternalLink} from 'sentry/components/core/link'; +import type {OnboardingConfig} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {t, tct} from 'sentry/locale'; + +import {onboarding} from './onboarding'; + +export const performance: OnboardingConfig = { + introduction: () => + t( + "Adding Performance to your React project is simple. Make sure you've got these basics down." + ), + install: onboarding.install, + configure: params => [ + { + type: StepType.CONFIGURE, + content: [ + { + type: 'text', + text: t( + "Configuration should happen as early as possible in your application's lifecycle." + ), + }, + { + type: 'code', + language: 'javascript', + code: ` +import React from "react"; +import ReactDOM from "react-dom"; +import * as Sentry from "@sentry/react"; +import App from "./App"; + +Sentry.init({ + dsn: "${params.dsn.public}", + integrations: [Sentry.browserTracingIntegration()], + + // Set tracesSampleRate to 1.0 to capture 100% + // of transactions for performance monitoring. + // We recommend adjusting this value in production + tracesSampleRate: 1.0, + // Set \`tracePropagationTargets\` to control for which URLs distributed tracing should be enabled + tracePropagationTargets: ["localhost", /^https:\\/\\/yourserver\\.io\\/api/], +}); + +ReactDOM.render(, document.getElementById("root")); + +// Can also use with React Concurrent Mode +// ReactDOM.createRoot(document.getElementById('root')).render(); +`, + }, + { + type: 'text', + text: tct( + 'We recommend adjusting the value of [code:tracesSampleRate] in production. Learn more about tracing [linkTracingOptions:options], how to use the [linkTracesSampler:traces_sampler] function, or how to do [linkSampleTransactions:sampling].', + { + code: , + linkTracingOptions: ( + + ), + linkTracesSampler: ( + + ), + linkSampleTransactions: ( + + ), + } + ), + }, + ], + }, + { + title: t('Add Distributed Tracing (Optional)'), + content: [ + { + type: 'text', + text: tct( + "If you're using the current version of our JavaScript SDK and have enabled the [code: BrowserTracing] integration, distributed tracing will work out of the box. To get around possible [link:Browser CORS] issues, define your [code:tracePropagationTargets].", + { + code: , + link: ( + + ), + } + ), + }, + { + type: 'code', + language: 'javascript', + code: ` +Sentry.init({ + dsn: "${params.dsn.public}", + integrations: [Sentry.browserTracingIntegration()], + tracePropagationTargets: ["localhost", /^https:\\/\\/yourserver\\.io\\/api/] +}); +`, + }, + ], + }, + ], + verify: () => [ + { + type: StepType.VERIFY, + content: [ + { + type: 'text', + text: tct( + 'Verify that performance monitoring is working correctly with our [link:automatic instrumentation] by simply using your React application.', + { + link: ( + + ), + } + ), + }, + ], + }, + ], + nextSteps: () => [], +}; diff --git a/static/app/gettingStartedDocs/javascript/react/profiling.tsx b/static/app/gettingStartedDocs/javascript/react/profiling.tsx new file mode 100644 index 00000000000000..eecd58bdf6da9c --- /dev/null +++ b/static/app/gettingStartedDocs/javascript/react/profiling.tsx @@ -0,0 +1,9 @@ +import {getJavascriptProfilingOnboarding} from 'sentry/utils/gettingStartedDocs/javascript'; + +import {installSnippetBlock} from './utils'; + +export const profiling = getJavascriptProfilingOnboarding({ + installSnippetBlock, + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/react/profiling/browser-profiling/', +}); diff --git a/static/app/gettingStartedDocs/javascript/react/replay.tsx b/static/app/gettingStartedDocs/javascript/react/replay.tsx new file mode 100644 index 00000000000000..f731a048a7a924 --- /dev/null +++ b/static/app/gettingStartedDocs/javascript/react/replay.tsx @@ -0,0 +1,52 @@ +import type {OnboardingConfig} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import { + getReplayConfigureDescription, + getReplayVerifyStep, +} from 'sentry/components/onboarding/gettingStartedDoc/utils/replayOnboarding'; +import {tct} from 'sentry/locale'; + +import {getSdkSetupSnippet, installSnippetBlock} from './utils'; + +export const replay: OnboardingConfig = { + install: () => [ + { + type: StepType.INSTALL, + content: [ + { + type: 'text', + text: tct( + 'Add the Sentry SDK as a dependency using [code:npm], [code:yarn], or [code:pnpm]. You need a minimum version 7.27.0 of [code:@sentry/react] in order to use Session Replay. You do not need to install any additional packages.', + {code: } + ), + }, + installSnippetBlock, + ], + }, + ], + configure: params => [ + { + type: StepType.CONFIGURE, + content: [ + { + type: 'text', + text: getReplayConfigureDescription({ + link: 'https://docs.sentry.io/platforms/javascript/guides/react/session-replay/', + }), + }, + { + type: 'code', + tabs: [ + { + label: 'JavaScript', + language: 'javascript', + code: getSdkSetupSnippet(params), + }, + ], + }, + ], + }, + ], + verify: getReplayVerifyStep(), + nextSteps: () => [], +}; diff --git a/static/app/gettingStartedDocs/javascript/react/utils.tsx b/static/app/gettingStartedDocs/javascript/react/utils.tsx new file mode 100644 index 00000000000000..e2d82277581259 --- /dev/null +++ b/static/app/gettingStartedDocs/javascript/react/utils.tsx @@ -0,0 +1,118 @@ +import {buildSdkConfig} from 'sentry/components/onboarding/gettingStartedDoc/buildSdkConfig'; +import type { + ContentBlock, + DocsParams, +} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {getFeedbackConfigOptions} from 'sentry/components/onboarding/gettingStartedDoc/utils/feedbackOnboarding'; +import {getReplayConfigOptions} from 'sentry/components/onboarding/gettingStartedDoc/utils/replayOnboarding'; + +const getDynamicParts = (params: DocsParams): string[] => { + const dynamicParts: string[] = []; + + if (params.isPerformanceSelected) { + dynamicParts.push(` + // Tracing + tracesSampleRate: 1.0, // Capture 100% of the transactions + // Set 'tracePropagationTargets' to control for which URLs distributed tracing should be enabled + tracePropagationTargets: ["localhost", /^https:\\/\\/yourserver\\.io\\/api/]`); + } + + if (params.isReplaySelected) { + dynamicParts.push(` + // Session Replay + replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production. + replaysOnErrorSampleRate: 1.0 // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur.`); + } + + if (params.isLogsSelected) { + dynamicParts.push(` + // Enable logs to be sent to Sentry + enableLogs: true`); + } + + if (params.isProfilingSelected) { + dynamicParts.push(` + // Set profilesSampleRate to 1.0 to profile every transaction. + // Since profilesSampleRate is relative to tracesSampleRate, + // the final profiling rate can be computed as tracesSampleRate * profilesSampleRate + // For example, a tracesSampleRate of 0.5 and profilesSampleRate of 0.5 would + // results in 25% of transactions being profiled (0.5*0.5=0.25) + profilesSampleRate: 1.0`); + } + + return dynamicParts; +}; + +const getIntegrations = (params: DocsParams): string[] => { + const integrations = []; + if (params.isPerformanceSelected) { + integrations.push(`Sentry.browserTracingIntegration()`); + } + + if (params.isProfilingSelected) { + integrations.push(`Sentry.browserProfilingIntegration()`); + } + + if (params.isReplaySelected) { + integrations.push( + `Sentry.replayIntegration(${getReplayConfigOptions(params.replayOptions)})` + ); + } + + if (params.isFeedbackSelected) { + integrations.push(` + Sentry.feedbackIntegration({ + colorScheme: "system", + ${getFeedbackConfigOptions(params.feedbackOptions)} + }),`); + } + + return integrations; +}; + +export function getSdkSetupSnippet(params: DocsParams) { + const config = buildSdkConfig({ + params, + staticParts: [ + `dsn: "${params.dsn.public}"`, + `// Setting this option to true will send default PII data to Sentry. + // For example, automatic IP address collection on events + sendDefaultPii: true`, + ], + getIntegrations, + getDynamicParts, + }); + + return ` +import * as Sentry from "@sentry/react"; + +Sentry.init({ + ${config} +}); + +const container = document.getElementById("app"); +const root = createRoot(container); +root.render(); +`; +} + +export const installSnippetBlock: ContentBlock = { + type: 'code', + tabs: [ + { + label: 'npm', + language: 'bash', + code: 'npm install --save @sentry/react', + }, + { + label: 'yarn', + language: 'bash', + code: 'yarn add @sentry/react', + }, + { + label: 'pnpm', + language: 'bash', + code: 'pnpm add @sentry/react', + }, + ], +};