From 79ceb115e258a881037bfbbe79409c3b24f6d0d6 Mon Sep 17 00:00:00 2001 From: JPeer264 Date: Wed, 9 Sep 2026 14:43:16 +0200 Subject: [PATCH] fix(onboarding): Update Ember onboarding for SDK v11 Add an explicit performance instance initializer when tracing is selected, as the v2 addon no longer registers instrumentation automatically. Keep loadInitializers in the application setup so the initializer runs. Replace timer-based verification with a clickable Ember component that sends selected logs and metrics before throwing a test error. Add regression coverage for setup, tracing selection, and verification. Co-authored-by: Cursor --- .../javascript-ember/onboarding.spec.tsx | 77 ++++++++++++++-- .../javascript-ember/onboarding.tsx | 87 ++++++++++++++++--- .../javascript-ember/utils.tsx | 4 +- 3 files changed, 148 insertions(+), 20 deletions(-) diff --git a/static/app/gettingStartedDocs/javascript-ember/onboarding.spec.tsx b/static/app/gettingStartedDocs/javascript-ember/onboarding.spec.tsx index 74a7036de76e..e93c0129d055 100644 --- a/static/app/gettingStartedDocs/javascript-ember/onboarding.spec.tsx +++ b/static/app/gettingStartedDocs/javascript-ember/onboarding.spec.tsx @@ -18,12 +18,79 @@ describe('javascript-ember onboarding docs', () => { ).toBeInTheDocument(); expect(screen.getByRole('heading', {name: 'Verify'})).toBeInTheDocument(); - // Includes import statement in multiple places expect( - screen.getAllByText( - textWithMarkupMatcher(/import \* as Sentry from "@sentry\/ember"/) - ) - ).toHaveLength(2); // Appears in configure and verify steps + screen.getByText(textWithMarkupMatcher(/import \* as Sentry from "@sentry\/ember"/)) + ).toBeInTheDocument(); + }); + + it('initializes the SDK directly and loads application initializers', () => { + renderWithOnboardingLayout(docs); + + const setup = screen.getByText(textWithMarkupMatcher(/Sentry\.init\(/)); + expect(setup).toHaveTextContent('import config from "./config/environment"'); + expect(setup).toHaveTextContent('loadInitializers(App, config.modulePrefix)'); + expect(setup).toHaveTextContent('dataCollection:'); + expect(setup).not.toHaveTextContent(/sendDefaultPii|enableLogs|enableMetrics/); + }); + + it('registers a performance instance initializer when tracing is selected', () => { + renderWithOnboardingLayout(docs, { + selectedProducts: [ProductSolution.PERFORMANCE_MONITORING], + }); + + const initializer = screen.getByText( + textWithMarkupMatcher(/export function initialize\(appInstance\)/) + ); + expect(initializer).toHaveTextContent( + 'import { instrumentAppInstancePerformance } from "@sentry/ember"' + ); + expect(initializer).toHaveTextContent( + 'instrumentAppInstancePerformance(appInstance)' + ); + expect(initializer).toHaveTextContent('export default { initialize }'); + }); + + it('omits performance instrumentation when tracing is not selected', () => { + renderWithOnboardingLayout(docs, { + selectedProducts: [ProductSolution.ERROR_MONITORING], + }); + + expect( + screen.queryByText(textWithMarkupMatcher(/instrumentAppInstancePerformance/)) + ).not.toBeInTheDocument(); + expect( + screen.queryByText(textWithMarkupMatcher(/tracesSampleRate/)) + ).not.toBeInTheDocument(); + }); + + it('verifies errors with a component action and button', () => { + renderWithOnboardingLayout(docs, { + selectedProducts: [ProductSolution.ERROR_MONITORING], + }); + + const component = screen.getByText(textWithMarkupMatcher(/throw new Error/)); + expect(component).toHaveTextContent('extends Component'); + expect(component).toHaveTextContent(/@action\s*triggerError\(\)/); + expect(component).not.toHaveTextContent(/setTimeout|@sentry\/ember/); + expect( + screen.getByText(textWithMarkupMatcher(/\{\{on "click" this\.triggerError\}\}/)) + ).toHaveTextContent('Break the world'); + }); + + it('sends both selected signals before the verification error', () => { + renderWithOnboardingLayout(docs, { + selectedProducts: [ + ProductSolution.ERROR_MONITORING, + ProductSolution.LOGS, + ProductSolution.METRICS, + ], + }); + + const component = screen.getByText(textWithMarkupMatcher(/throw new Error/)); + expect(component).toHaveTextContent('import * as Sentry from "@sentry/ember"'); + expect(component).toHaveTextContent( + /Sentry\.logger\.info.*Sentry\.metrics\.count.*throw new Error/ + ); }); it('displays sample rates by default', () => { diff --git a/static/app/gettingStartedDocs/javascript-ember/onboarding.tsx b/static/app/gettingStartedDocs/javascript-ember/onboarding.tsx index 27a29df8fe94..41e3f73d949b 100644 --- a/static/app/gettingStartedDocs/javascript-ember/onboarding.tsx +++ b/static/app/gettingStartedDocs/javascript-ember/onboarding.tsx @@ -1,4 +1,5 @@ import type { + ContentBlock, DocsParams, OnboardingConfig, } from 'sentry/components/onboarding/gettingStartedDoc/types'; @@ -10,30 +11,37 @@ import {getSdkSetupSnippet, installSnippetBlock} from './utils'; const getVerifyEmberSnippet = (params: DocsParams) => { const logsCode = params.isLogsSelected - ? `// Send a log before throwing the error - Sentry.logger.info(Sentry.logger.fmt\`User \${"sentry-test"} triggered test error button\`, { - action: "test_error_button_click", + ? ` // Send a log before throwing the error + Sentry.logger.info('User triggered test error', { + action: 'test_error_button_click', }); ` : ''; const metricsCode = params.isMetricsSelected - ? `// Send a test metric before throwing the error + ? ` // Send a test metric before throwing the error Sentry.metrics.count('test_counter', 1); ` : ''; - return ` -import * as Sentry from "@sentry/ember"; - -setTimeout(() => { - ${logsCode}${metricsCode}throw new Error("Sentry Test Error"); -});`; + return `import Component from "@glimmer/component"; +import { action } from "@ember/object"; +${ + params.isLogsSelected || params.isMetricsSelected + ? 'import * as Sentry from "@sentry/ember";\n' + : '' +} +export default class SentryTestComponent extends Component { + @action + triggerError() { +${logsCode}${metricsCode} throw new Error("Sentry Test Error"); + } +}`; }; export const onboarding: OnboardingConfig = { introduction: () => - tct("In this quick guide you'll use [strong:npm] or [strong:yarn] to set up:", { + tct("In this quick guide you'll use the [strong:Ember CLI] to set up:", { strong: , }), install: () => [ @@ -57,7 +65,7 @@ export const onboarding: OnboardingConfig = { { type: 'text', text: tct( - 'You should [code:init] the Sentry SDK as soon as possible during your application load up in [code:app.js], before initializing Ember:', + 'Initialize Sentry in [code:app/app.js], before the Application class. Keep the [code:loadInitializers] call so Ember loads your initializers:', { code: , } @@ -69,10 +77,39 @@ export const onboarding: OnboardingConfig = { { label: 'JavaScript', language: 'javascript', + filename: 'app/app.js', code: getSdkSetupSnippet(params), }, ], }, + ...((params.isPerformanceSelected + ? [ + { + type: 'text', + text: tct( + 'To enable tracing, create [code:app/instance-initializers/sentry-performance.js]. The v2 addon does not register performance instrumentation automatically:', + {code: } + ), + }, + { + type: 'code', + tabs: [ + { + label: 'JavaScript', + language: 'javascript', + filename: 'app/instance-initializers/sentry-performance.js', + code: `import { instrumentAppInstancePerformance } from "@sentry/ember"; + +export function initialize(appInstance) { + instrumentAppInstancePerformance(appInstance); +} + +export default { initialize };`, + }, + ], + }, + ] + : []) satisfies ContentBlock[]), ], }, getUploadSourceMapsStep({ @@ -86,8 +123,9 @@ export const onboarding: OnboardingConfig = { 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." + text: tct( + 'Create a [code:SentryTest] component with the following class and template:', + {code: } ), }, { @@ -96,10 +134,31 @@ export const onboarding: OnboardingConfig = { { label: 'JavaScript', language: 'javascript', + filename: 'app/components/sentry-test.js', code: getVerifyEmberSnippet(params), }, ], }, + { + type: 'code', + tabs: [ + { + label: 'Handlebars', + language: 'html', + filename: 'app/components/sentry-test.hbs', + code: ``, + }, + ], + }, + { + type: 'text', + text: tct( + 'Render [code:] in an application template, then click "Break the world" to send a test error to Sentry. If you selected Logs or Metrics, clicking the button sends those too.', + {code: } + ), + }, ], }, ], diff --git a/static/app/gettingStartedDocs/javascript-ember/utils.tsx b/static/app/gettingStartedDocs/javascript-ember/utils.tsx index 1d7f6c3cc2dd..e5978eda4d8b 100644 --- a/static/app/gettingStartedDocs/javascript-ember/utils.tsx +++ b/static/app/gettingStartedDocs/javascript-ember/utils.tsx @@ -43,7 +43,7 @@ const getDynamicParts = (params: DocsParams): string[] => { if (params.isPerformanceSelected) { dynamicParts.push(` // Tracing - tracesSampleRate: 1.0, // Capture 100% of the transactions + tracesSampleRate: 1.0, // Capture 100% of the traces // Set 'tracePropagationTargets' to control for which URLs distributed tracing should be enabled tracePropagationTargets: ["localhost", /^https:\\/\\/yourserver\\.io\\/api/]`); } @@ -91,6 +91,8 @@ export default class App extends Application { podModulePrefix = config.podModulePrefix; Resolver = Resolver; } + +loadInitializers(App, config.modulePrefix); `; }