diff --git a/static/app/components/onboarding/productSelection.tsx b/static/app/components/onboarding/productSelection.tsx
index 2dc684504c26..7ed1150922dd 100644
--- a/static/app/components/onboarding/productSelection.tsx
+++ b/static/app/components/onboarding/productSelection.tsx
@@ -65,6 +65,8 @@ export const platformProductAvailability = {
],
node: [ProductSolution.PERFORMANCE_MONITORING, ProductSolution.PROFILING],
'node-connect': [ProductSolution.PERFORMANCE_MONITORING, ProductSolution.PROFILING],
+ 'node-express': [ProductSolution.PERFORMANCE_MONITORING, ProductSolution.PROFILING],
+ 'node-koa': [ProductSolution.PERFORMANCE_MONITORING, ProductSolution.PROFILING],
python: [ProductSolution.PERFORMANCE_MONITORING, ProductSolution.PROFILING],
'python-aiohttp': [ProductSolution.PERFORMANCE_MONITORING, ProductSolution.PROFILING],
'python-awslambda': [ProductSolution.PERFORMANCE_MONITORING, ProductSolution.PROFILING],
diff --git a/static/app/gettingStartedDocs/node/express.spec.tsx b/static/app/gettingStartedDocs/node/express.spec.tsx
index ee5754ede820..700cae7666a8 100644
--- a/static/app/gettingStartedDocs/node/express.spec.tsx
+++ b/static/app/gettingStartedDocs/node/express.spec.tsx
@@ -9,7 +9,12 @@ describe('GettingStartedWithExpress', function () {
const {container} = render();
// Steps
- for (const step of steps()) {
+ for (const step of steps({
+ installSnippet: 'test-install-snippet',
+ importContent: 'test-import-content',
+ initContent: 'test-init-content',
+ hasPerformanceMonitoring: true,
+ })) {
expect(
screen.getByRole('heading', {name: step.title ?? StepTitle[step.type]})
).toBeInTheDocument();
diff --git a/static/app/gettingStartedDocs/node/express.tsx b/static/app/gettingStartedDocs/node/express.tsx
index c6ed276ccfd8..922e95d918af 100644
--- a/static/app/gettingStartedDocs/node/express.tsx
+++ b/static/app/gettingStartedDocs/node/express.tsx
@@ -1,44 +1,44 @@
import {Layout, LayoutProps} from 'sentry/components/onboarding/gettingStartedDoc/layout';
import {ModuleProps} from 'sentry/components/onboarding/gettingStartedDoc/sdkDocumentation';
import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/step';
-import {PlatformKey} from 'sentry/data/platformCategories';
import {t, tct} from 'sentry/locale';
-import type {Organization} from 'sentry/types';
-
-type StepProps = {
- newOrg: boolean;
- organization: Organization;
- platformKey: PlatformKey;
- projectId: string;
- sentryInitContent: string;
-};
+import {
+ getDefaultInitParams,
+ getDefaultNodeImports,
+ getInstallSnippet,
+ getProductInitParams,
+ getProductIntegrations,
+ getProductSelectionMap,
+ joinWithIndentation,
+} from 'sentry/utils/gettingStartedDocs/node';
+
+interface StepProps {
+ hasPerformanceMonitoring: boolean;
+ importContent: string;
+ initContent: string;
+ installSnippet: string;
+}
const performanceIntegrations: string[] = [
- `// enable HTTP calls tracing
-new Sentry.Integrations.Http({ tracing: true }),`,
- `// enable Express.js middleware tracing
-new Sentry.Integrations.Express({ app }),`,
+ '// enable HTTP calls tracing',
+ 'new Sentry.Integrations.Http({ tracing: true }),',
+ '// enable Express.js middleware tracing',
+ 'new Sentry.Integrations.Express({ app }),',
];
-const performanceOtherConfig = `// Performance Monitoring
-tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production!`;
-
export const steps = ({
- sentryInitContent,
-}: Partial = {}): LayoutProps['steps'] => [
+ installSnippet,
+ importContent,
+ initContent,
+ hasPerformanceMonitoring,
+}: StepProps): LayoutProps['steps'] => [
{
type: StepType.INSTALL,
description: t('Add the Sentry Node SDK as a dependency:'),
configurations: [
{
language: 'bash',
- code: `
-# Using yarn
-yarn add @sentry/node
-
-# Using npm
-npm install --save @sentry/node
- `,
+ code: installSnippet,
},
],
},
@@ -56,40 +56,41 @@ npm install --save @sentry/node
{
language: 'javascript',
code: `
- import * as Sentry from "@sentry/node";
- import express from "express";
+${importContent}
- // or using CommonJS
- // const Sentry = require('@sentry/node');
- // const express = require('express');
+const app = express();
- const app = express();
+Sentry.init({
+${initContent}
+});
- Sentry.init({
- ${sentryInitContent},
- });
+// The request handler must be the first middleware on the app
+app.use(Sentry.Handlers.requestHandler());${
+ hasPerformanceMonitoring
+ ? `
- // Trace incoming requests
- app.use(Sentry.Handlers.requestHandler());
- app.use(Sentry.Handlers.tracingHandler());
+// TracingHandler creates a trace for every incoming request
+app.use(Sentry.Handlers.tracingHandler());`
+ : ''
+ }
- // All your controllers should live here
- app.get("/", function rootHandler(req, res) {
- res.end("Hello world!");
- });
+// All your controllers should live here
+app.get("/", function rootHandler(req, res) {
+ res.end("Hello world!");
+});
- // The error handler must be registered before any other error middleware and after all controllers
- app.use(Sentry.Handlers.errorHandler());
+// The error handler must be registered before any other error middleware and after all controllers
+app.use(Sentry.Handlers.errorHandler());
- // Optional fallthrough error handler
- app.use(function onError(err, req, res, next) {
- // The error id is attached to \`res.sentry\` to be returned
- // and optionally displayed to the user for support.
- res.statusCode = 500;
- res.end(res.sentry + "\\n");
- });
+// Optional fallthrough error handler
+app.use(function onError(err, req, res, next) {
+ // The error id is attached to \`res.sentry\` to be returned
+ // and optionally displayed to the user for support.
+ res.statusCode = 500;
+ res.end(res.sentry + "\\n");
+});
- app.listen(3000);
+app.listen(3000);
`,
},
],
@@ -112,24 +113,41 @@ npm install --save @sentry/node
},
];
-export function GettingStartedWithExpress({dsn, newOrg, platformKey}: ModuleProps) {
- let sentryInitContent: string[] = [`dsn: "${dsn}",`];
-
- const integrations = [...performanceIntegrations];
- const otherConfigs = [performanceOtherConfig];
-
- if (integrations.length > 0) {
- sentryInitContent = sentryInitContent.concat('integrations: [', integrations, '],');
- }
-
- if (otherConfigs.length > 0) {
- sentryInitContent = sentryInitContent.concat(otherConfigs);
- }
+export function GettingStartedWithExpress({
+ dsn,
+ newOrg,
+ platformKey,
+ activeProductSelection = [],
+}: ModuleProps) {
+ const productSelection = getProductSelectionMap(activeProductSelection);
+
+ const installSnippet = getInstallSnippet({productSelection});
+ const imports = getDefaultNodeImports({productSelection});
+ imports.push('import express from "express";');
+
+ const integrations = [
+ ...(productSelection['performance-monitoring'] ? performanceIntegrations : []),
+ ...getProductIntegrations({productSelection}),
+ ];
+
+ const integrationParam =
+ integrations.length > 0
+ ? `integrations: [\n${joinWithIndentation(integrations)}\n],`
+ : null;
+
+ const initContent = joinWithIndentation([
+ ...getDefaultInitParams({dsn}),
+ ...(integrationParam ? [integrationParam] : []),
+ ...getProductInitParams({productSelection}),
+ ]);
return (
);
// Steps
- for (const step of steps()) {
+ for (const step of steps({
+ installSnippet: 'test-install-snippet',
+ importContent: 'test-import-content',
+ initContent: 'test-init-content',
+ hasPerformanceMonitoring: true,
+ })) {
expect(
screen.getByRole('heading', {name: step.title ?? StepTitle[step.type]})
).toBeInTheDocument();
diff --git a/static/app/gettingStartedDocs/node/koa.tsx b/static/app/gettingStartedDocs/node/koa.tsx
index 5a767437f416..115105690a3d 100644
--- a/static/app/gettingStartedDocs/node/koa.tsx
+++ b/static/app/gettingStartedDocs/node/koa.tsx
@@ -1,42 +1,42 @@
import {Layout, LayoutProps} from 'sentry/components/onboarding/gettingStartedDoc/layout';
import {ModuleProps} from 'sentry/components/onboarding/gettingStartedDoc/sdkDocumentation';
import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/step';
-import {PlatformKey} from 'sentry/data/platformCategories';
import {t, tct} from 'sentry/locale';
-import {Organization} from 'sentry/types';
-
-type StepProps = {
- newOrg: boolean;
- organization: Organization;
- platformKey: PlatformKey;
- projectId: string;
- sentryInitContent: string;
-};
+import {
+ getDefaultInitParams,
+ getDefaultNodeImports,
+ getInstallSnippet,
+ getProductInitParams,
+ getProductIntegrations,
+ getProductSelectionMap,
+ joinWithIndentation,
+} from 'sentry/utils/gettingStartedDocs/node';
+
+interface StepProps {
+ hasPerformanceMonitoring: boolean;
+ importContent: string;
+ initContent: string;
+ installSnippet: string;
+}
const performanceIntegrations: string[] = [
- `// Automatically instrument Node.js libraries and frameworks
- ...Sentry.autoDiscoverNodePerformanceMonitoringIntegrations(),`,
+ '// Automatically instrument Node.js libraries and frameworks',
+ '...Sentry.autoDiscoverNodePerformanceMonitoringIntegrations(),',
];
-const performanceOtherConfig = `// Performance Monitoring
-tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production!`;
-
export const steps = ({
- sentryInitContent,
-}: Partial = {}): LayoutProps['steps'] => [
+ installSnippet,
+ importContent,
+ initContent,
+ hasPerformanceMonitoring,
+}: StepProps): LayoutProps['steps'] => [
{
type: StepType.INSTALL,
description: t('Add the Sentry Node SDK as a dependency:'),
configurations: [
{
language: 'bash',
- code: `
-# Using yarn
-yarn add @sentry/node @sentry/utils
-
-# Using npm
-npm install --save @sentry/node @sentry/utils
- `,
+ code: installSnippet,
},
],
},
@@ -54,95 +54,98 @@ npm install --save @sentry/node @sentry/utils
{
language: 'javascript',
code: `
- const Sentry = require("@sentry/node");
- const { stripUrlQueryAndFragment } = require("@sentry/utils");
- const Koa = require("koa");
- const app = new Koa();
+${importContent}
+
+const app = new Koa();
+
+Sentry.init({
+${initContent}
+});${
+ hasPerformanceMonitoring
+ ? `
+
+const requestHandler = (ctx, next) => {
+ return new Promise((resolve, reject) => {
+ Sentry.runWithAsyncContext(async () => {
+ const hub = Sentry.getCurrentHub();
+ hub.configureScope((scope) =>
+ scope.addEventProcessor((event) =>
+ Sentry.addRequestDataToEvent(event, ctx.request, {
+ include: {
+ user: false,
+ },
+ })
+ )
+ );
+
+ try {
+ await next();
+ } catch (err) {
+ reject(err);
+ }
+ resolve();
+ });
+ });
+};
- Sentry.init({
- ${sentryInitContent},
- });
+// This tracing middleware creates a transaction per request
+const tracingMiddleWare = async (ctx, next) => {
+ const reqMethod = (ctx.method || "").toUpperCase();
+ const reqUrl = ctx.url && stripUrlQueryAndFragment(ctx.url);
+
+ // Connect to trace of upstream app
+ let traceparentData;
+ if (ctx.request.get("sentry-trace")) {
+ traceparentData = Sentry.extractTraceparentData(
+ ctx.request.get("sentry-trace")
+ );
+ }
- const requestHandler = (ctx, next) => {
- return new Promise((resolve, reject) => {
- Sentry.runWithAsyncContext(async () => {
- const hub = Sentry.getCurrentHub();
- hub.configureScope((scope) =>
- scope.addEventProcessor((event) =>
- Sentry.addRequestDataToEvent(event, ctx.request, {
- include: {
- user: false,
- },
- })
- )
- );
-
- try {
- await next();
- } catch (err) {
- reject(err);
- }
- resolve();
- });
- });
- };
-
- // this tracing middleware creates a transaction per request
- const tracingMiddleWare = async (ctx, next) => {
- const reqMethod = (ctx.method || "").toUpperCase();
- const reqUrl = ctx.url && stripUrlQueryAndFragment(ctx.url);
-
- // connect to trace of upstream app
- let traceparentData;
- if (ctx.request.get("sentry-trace")) {
- traceparentData = Sentry.extractTraceparentData(
- ctx.request.get("sentry-trace")
- );
- }
-
- const transaction = Sentry.startTransaction({
- name: \`\${reqMethod} \${reqUrl}\`,
- op: "http.server",
- ...traceparentData,
- });
-
- ctx.__sentry_transaction = transaction;
-
- // We put the transaction on the scope so users can attach children to it
- Sentry.getCurrentHub().configureScope((scope) => {
- scope.setSpan(transaction);
- });
-
- ctx.res.on("finish", () => {
- // Push \`transaction.finish\` to the next event loop so open spans have a chance to finish before the transaction closes
- setImmediate(() => {
- // if you're using koa router, set the matched route as transaction name
- if (ctx._matchedRoute) {
- const mountPath = ctx.mountPath || "";
- transaction.setName(\`\${reqMethod} \${mountPath}\${ctx._matchedRoute}\`);
- }
- transaction.setHttpStatus(ctx.status);
- transaction.finish();
- });
- });
-
- await next();
- };
-
- app.use(requestHandler);
- app.use(tracingMiddleWare);
-
- // usual error handler
- app.on("error", (err, ctx) => {
- Sentry.withScope((scope) => {
- scope.addEventProcessor((event) => {
- return Sentry.addRequestDataToEvent(event, ctx.request);
- });
- Sentry.captureException(err);
- });
- });
+ const transaction = Sentry.startTransaction({
+ name: \`\${reqMethod} \${reqUrl}\`,
+ op: "http.server",
+ ...traceparentData,
+ });
+
+ ctx.__sentry_transaction = transaction;
+
+ // We put the transaction on the scope so users can attach children to it
+ Sentry.getCurrentHub().configureScope((scope) => {
+ scope.setSpan(transaction);
+ });
+
+ ctx.res.on("finish", () => {
+ // Push \`transaction.finish\` to the next event loop so open spans have a chance to finish before the transaction closes
+ setImmediate(() => {
+ // If you're using koa router, set the matched route as transaction name
+ if (ctx._matchedRoute) {
+ const mountPath = ctx.mountPath || "";
+ transaction.setName(\`\${reqMethod} \${mountPath}\${ctx._matchedRoute}\`);
+ }
+ transaction.setHttpStatus(ctx.status);
+ transaction.finish();
+ });
+ });
+
+ await next();
+};
- app.listen(3000);
+app.use(requestHandler);
+app.use(tracingMiddleWare);`
+ : ''
+ }
+
+// Send errors to Sentry
+app.on("error", (err, ctx) => {
+ Sentry.withScope((scope) => {
+ scope.addEventProcessor((event) => {
+ return Sentry.addRequestDataToEvent(event, ctx.request);
+ });
+ Sentry.captureException(err);
+ });
+});
+
+app.listen(3000);
`,
},
],
@@ -165,24 +168,47 @@ npm install --save @sentry/node @sentry/utils
},
];
-export function GettingStartedWithKoa({dsn, newOrg, platformKey}: ModuleProps) {
- let sentryInitContent: string[] = [`dsn: "${dsn}",`];
-
- const integrations = [...performanceIntegrations];
- const otherConfigs = [performanceOtherConfig];
-
- if (integrations.length > 0) {
- sentryInitContent = sentryInitContent.concat('integrations: [', integrations, '],');
- }
-
- if (otherConfigs.length > 0) {
- sentryInitContent = sentryInitContent.concat(otherConfigs);
- }
+export function GettingStartedWithKoa({
+ dsn,
+ newOrg,
+ platformKey,
+ activeProductSelection = [],
+}: ModuleProps) {
+ const productSelection = getProductSelectionMap(activeProductSelection);
+
+ const additionalPackages = productSelection['performance-monitoring']
+ ? ['@sentry/utils']
+ : [];
+ const installSnippet = getInstallSnippet({productSelection, additionalPackages});
+ let imports = getDefaultNodeImports({productSelection});
+ imports = imports.concat([
+ 'import { stripUrlQueryAndFragment } from "@sentry/utils";',
+ 'import Koa from "koa";',
+ ]);
+
+ const integrations = [
+ ...(productSelection['performance-monitoring'] ? performanceIntegrations : []),
+ ...getProductIntegrations({productSelection}),
+ ];
+
+ const integrationParam =
+ integrations.length > 0
+ ? `integrations: [\n${joinWithIndentation(integrations)}\n],`
+ : null;
+
+ const initContent = joinWithIndentation([
+ ...getDefaultInitParams({dsn}),
+ ...(integrationParam ? [integrationParam] : []),
+ ...getProductInitParams({productSelection}),
+ ]);
return (