diff --git a/package.json b/package.json index 842d22fbe79b82..70584d7d07470a 100644 --- a/package.json +++ b/package.json @@ -138,7 +138,7 @@ "moment-timezone": "0.5.44", "papaparse": "^5.3.2", "peggy": "^4.1.1", - "platformicons": "^7.0.1", + "platformicons": "^7.0.4", "po-catalog-loader": "2.1.0", "prettier": "3.3.2", "prismjs": "^1.29.0", diff --git a/src/sentry/models/project.py b/src/sentry/models/project.py index f37fb601d68b4f..25f3022b258801 100644 --- a/src/sentry/models/project.py +++ b/src/sentry/models/project.py @@ -64,8 +64,6 @@ "apple-macos", "bun", "capacitor", - "cloudflare-pages", - "cloudflare-workers", "cordova", "dart", "deno", @@ -119,6 +117,8 @@ "node", "node-awslambda", "node-azurefunctions", + "node-cloudflare-pages", + "node-cloudflare-workers", "node-connect", "node-express", "node-fastify", diff --git a/src/sentry/utils/platform_categories.py b/src/sentry/utils/platform_categories.py index 3baeb173bbb51f..a71e7c48e67da3 100644 --- a/src/sentry/utils/platform_categories.py +++ b/src/sentry/utils/platform_categories.py @@ -51,8 +51,6 @@ # When changing this file, make sure to keep sentry/static/app/data/platformCategories.tsx in sync. BACKEND = { "bun", - "cloudflare-pages", - "cloudflare-workers", "deno", "dotnet", "dotnet-aspnet", @@ -78,6 +76,8 @@ "kotlin", "native", "node", + "node-cloudflare-pages", + "node-cloudflare-workers", "node-connect", "node-express", "node-fastify", @@ -121,10 +121,11 @@ SERVERLESS = { "dotnet-awslambda", "dotnet-gcpfunctions", - "cloudflare-workers", "node-awslambda", "node-azurefunctions", "node-gcpfunctions", + "node-cloudflare-pages", + "node-cloudflare-workers", "python-awslambda", "python-azurefunctions", "python-gcpfunctions", diff --git a/static/app/data/platformCategories.tsx b/static/app/data/platformCategories.tsx index 200e4611e9fcf5..fadb85b4d4cab6 100644 --- a/static/app/data/platformCategories.tsx +++ b/static/app/data/platformCategories.tsx @@ -87,6 +87,8 @@ export const backend: PlatformKey[] = [ 'node-express', 'node-koa', 'node-connect', + 'node-cloudflare-pages', + 'node-cloudflare-workers', 'perl', 'php', 'php-laravel', @@ -127,6 +129,8 @@ export const serverless: PlatformKey[] = [ 'node-awslambda', 'node-azurefunctions', 'node-gcpfunctions', + 'node-cloudflare-pages', + 'node-cloudflare-workers', 'python-awslambda', 'python-azurefunctions', 'python-gcpfunctions', diff --git a/static/app/data/platformPickerCategories.tsx b/static/app/data/platformPickerCategories.tsx index 9d706d400d17ae..0292c4c285e8ee 100644 --- a/static/app/data/platformPickerCategories.tsx +++ b/static/app/data/platformPickerCategories.tsx @@ -73,6 +73,8 @@ const server: Set = new Set([ 'kotlin', 'native', 'node', + 'node-cloudflare-pages', + 'node-cloudflare-workers', 'node-connect', 'node-express', 'node-fastify', @@ -144,6 +146,8 @@ const serverless: Set = new Set([ 'node-awslambda', 'node-azurefunctions', 'node-gcpfunctions', + 'node-cloudflare-pages', + 'node-cloudflare-workers', 'python-awslambda', 'python-gcpfunctions', 'python-serverless', diff --git a/static/app/data/platforms.tsx b/static/app/data/platforms.tsx index 3fd8d5b6880ed8..248630191af85f 100644 --- a/static/app/data/platforms.tsx +++ b/static/app/data/platforms.tsx @@ -417,6 +417,20 @@ export const platforms: PlatformIntegration[] = [ language: 'node', link: 'https://docs.sentry.io/platforms/javascript/guides/azure-functions/', }, + { + id: 'node-cloudflare-pages', + name: 'Cloudflare Pages', + type: 'framework', + language: 'node', + link: 'https://docs.sentry.io/platforms/javascript/guides/cloudflare/', + }, + { + id: 'node-cloudflare-workers', + name: 'Cloudflare Workers', + type: 'framework', + language: 'node', + link: 'https://docs.sentry.io/platforms/javascript/guides/cloudflare/', + }, { id: 'node-connect', name: 'Connect', diff --git a/static/app/gettingStartedDocs/node/cloudflare-pages.spec.tsx b/static/app/gettingStartedDocs/node/cloudflare-pages.spec.tsx new file mode 100644 index 00000000000000..01560fa5c7a1c3 --- /dev/null +++ b/static/app/gettingStartedDocs/node/cloudflare-pages.spec.tsx @@ -0,0 +1,40 @@ +import {renderWithOnboardingLayout} from 'sentry-test/onboarding/renderWithOnboardingLayout'; +import {screen} from 'sentry-test/reactTestingLibrary'; +import {textWithMarkupMatcher} from 'sentry-test/utils'; + +import {ProductSolution} from 'sentry/components/onboarding/gettingStartedDoc/types'; + +import docs from './cloudflare-pages'; + +describe('express onboarding docs', function () { + it('renders onboarding docs correctly', () => { + renderWithOnboardingLayout(docs); + + // Renders main headings + expect(screen.getByRole('heading', {name: 'Install'})).toBeInTheDocument(); + expect(screen.getByRole('heading', {name: 'Configure SDK'})).toBeInTheDocument(); + expect(screen.getByRole('heading', {name: 'Upload Source Maps'})).toBeInTheDocument(); + + // Includes import statement + const allMatches = screen.getAllByText( + textWithMarkupMatcher(/import \* as Sentry from "@sentry\/cloudflare"/) + ); + allMatches.forEach(match => { + expect(match).toBeInTheDocument(); + }); + }); + + it('displays sample rates by default', () => { + renderWithOnboardingLayout(docs, { + selectedProducts: [ + ProductSolution.ERROR_MONITORING, + ProductSolution.PERFORMANCE_MONITORING, + ProductSolution.PROFILING, + ], + }); + + expect( + screen.getByText(textWithMarkupMatcher(/tracesSampleRate/)) + ).toBeInTheDocument(); + }); +}); diff --git a/static/app/gettingStartedDocs/node/cloudflare-pages.tsx b/static/app/gettingStartedDocs/node/cloudflare-pages.tsx new file mode 100644 index 00000000000000..38552b559f55a7 --- /dev/null +++ b/static/app/gettingStartedDocs/node/cloudflare-pages.tsx @@ -0,0 +1,161 @@ +import ExternalLink from 'sentry/components/links/externalLink'; +import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/step'; +import type { + Docs, + DocsParams, + OnboardingConfig, +} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {getUploadSourceMapsStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; +import { + getCrashReportJavaScriptInstallStep, + getCrashReportModalConfigDescription, + getCrashReportModalIntroduction, +} from 'sentry/components/onboarding/gettingStartedDoc/utils/feedbackOnboarding'; +import {t, tct} from 'sentry/locale'; +import {getInstallConfig} from 'sentry/utils/gettingStartedDocs/node'; + +type Params = DocsParams; + +const getSdkConfigureSnippetToml = () => ` +compatibility_flags = ["nodejs_compat"] +# compatibility_flags = ["nodejs_als"] +`; + +const getSdkConfigureSnippetJson = () => ` +{ + "compatibility_flags": [ + "nodejs_compat" + ], + "compatibility_date": "2024-09-23" +}`; + +const getSdkSetupSnippet = (params: Params) => ` +import * as Sentry from "@sentry/cloudflare"; + +export const onRequest = [ + // Make sure Sentry is the first middleware + Sentry.sentryPagesPlugin((context) => ({ + dsn: "${params.dsn.public}", + // Set tracesSampleRate to 1.0 to capture 100% of spans for tracing. + // Learn more at + // https://docs.sentry.io/platforms/javascript/configuration/options/#traces-sample-rate + tracesSampleRate: 1.0, + })), + // Add more middlewares here +];`; + +const getVerifySnippet = () => ` +setTimeout(() => { + throw new Error(); +});`; + +const onboarding: OnboardingConfig = { + introduction: () => + t( + 'In this quick guide you’ll set up and configure the Sentry Cloudflare SDK for the use in your Cloudflare Pages application.' + ), + install: params => [ + { + type: StepType.INSTALL, + description: t('Add the Sentry Cloudflare SDK as a dependency:'), + configurations: getInstallConfig(params, { + basePackage: '@sentry/cloudflare', + }), + }, + ], + configure: params => [ + { + type: StepType.CONFIGURE, + description: t( + "Configuration should happen as early as possible in your application's lifecycle." + ), + configurations: [ + { + description: tct( + "To use the SDK, you'll need to set either the [code:nodejs_compat] or [code:nodejs_als] compatibility flags in your [code:wrangler.toml]. This is because the SDK needs access to the [code:AsyncLocalStorage] API to work correctly.", + { + code: , + } + ), + code: [ + { + label: 'JSON', + value: 'json', + language: 'json', + filename: 'wrangler.json', + code: getSdkConfigureSnippetJson(), + }, + { + label: 'Toml', + value: 'toml', + language: 'toml', + filename: 'wrangler.toml', + code: getSdkConfigureSnippetToml(), + }, + ], + }, + { + description: tct( + 'Add the [code:sentryPagesPlugin] as [guideLink:middleware to your Cloudflare Pages application]. We recommend adding a [code:functions/_middleware.js] for the middleware setup so that Sentry is initialized for your entire app.', + { + code: , + guideLink: ( + + ), + } + ), + code: [ + { + label: 'JavaScript', + value: 'javascript', + language: 'javascript', + filename: 'functions/_middleware.js', + code: getSdkSetupSnippet(params), + }, + ], + }, + ], + }, + getUploadSourceMapsStep({ + guideLink: + 'https://docs.sentry.io/platforms/javascript/guides/cloudflare/sourcemaps/', + ...params, + }), + ], + verify: () => [ + { + type: StepType.VERIFY, + description: t( + "This snippet contains an intentional error and can be used as a test to make sure that everything's working as expected." + ), + configurations: [ + { + language: 'javascript', + code: getVerifySnippet(), + }, + ], + }, + ], +}; + +const crashReportOnboarding: OnboardingConfig = { + introduction: () => getCrashReportModalIntroduction(), + install: (params: Params) => getCrashReportJavaScriptInstallStep(params), + configure: () => [ + { + type: StepType.CONFIGURE, + description: getCrashReportModalConfigDescription({ + link: 'https://docs.sentry.io/platforms/javascript/guides/cloudflare/user-feedback/configuration/#crash-report-modal', + }), + }, + ], + verify: () => [], + nextSteps: () => [], +}; + +const docs: Docs = { + onboarding, + crashReportOnboarding, +}; + +export default docs; diff --git a/static/app/gettingStartedDocs/node/cloudflare-workers.spec.tsx b/static/app/gettingStartedDocs/node/cloudflare-workers.spec.tsx new file mode 100644 index 00000000000000..505e487aa2b372 --- /dev/null +++ b/static/app/gettingStartedDocs/node/cloudflare-workers.spec.tsx @@ -0,0 +1,53 @@ +import {renderWithOnboardingLayout} from 'sentry-test/onboarding/renderWithOnboardingLayout'; +import {screen} from 'sentry-test/reactTestingLibrary'; +import {textWithMarkupMatcher} from 'sentry-test/utils'; + +import {ProductSolution} from 'sentry/components/onboarding/gettingStartedDoc/types'; + +import docs from './cloudflare-workers'; + +describe('express onboarding docs', function () { + it('renders onboarding docs correctly', () => { + renderWithOnboardingLayout(docs); + + // Renders main headings + expect(screen.getByRole('heading', {name: 'Install'})).toBeInTheDocument(); + expect(screen.getByRole('heading', {name: 'Configure SDK'})).toBeInTheDocument(); + expect(screen.getByRole('heading', {name: 'Upload Source Maps'})).toBeInTheDocument(); + + // Includes import statement + const allMatches = screen.getAllByText( + textWithMarkupMatcher(/import \* as Sentry from "@sentry\/cloudflare"/) + ); + allMatches.forEach(match => { + expect(match).toBeInTheDocument(); + }); + }); + + it('displays sample rates by default', () => { + renderWithOnboardingLayout(docs, { + selectedProducts: [ + ProductSolution.ERROR_MONITORING, + ProductSolution.PERFORMANCE_MONITORING, + ProductSolution.PROFILING, + ], + }); + + expect( + screen.getByText(textWithMarkupMatcher(/tracesSampleRate/)) + ).toBeInTheDocument(); + }); + + it('enables performance setting the tracesSampleRate to 1', () => { + renderWithOnboardingLayout(docs, { + selectedProducts: [ + ProductSolution.ERROR_MONITORING, + ProductSolution.PERFORMANCE_MONITORING, + ], + }); + + expect( + screen.getByText(textWithMarkupMatcher(/tracesSampleRate: 1\.0/)) + ).toBeInTheDocument(); + }); +}); diff --git a/static/app/gettingStartedDocs/node/cloudflare-workers.tsx b/static/app/gettingStartedDocs/node/cloudflare-workers.tsx new file mode 100644 index 00000000000000..d627ee23973db9 --- /dev/null +++ b/static/app/gettingStartedDocs/node/cloudflare-workers.tsx @@ -0,0 +1,163 @@ +import ExternalLink from 'sentry/components/links/externalLink'; +import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/step'; +import type { + Docs, + DocsParams, + OnboardingConfig, +} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {getUploadSourceMapsStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; +import { + getCrashReportJavaScriptInstallStep, + getCrashReportModalConfigDescription, + getCrashReportModalIntroduction, +} from 'sentry/components/onboarding/gettingStartedDoc/utils/feedbackOnboarding'; +import {t, tct} from 'sentry/locale'; +import {getInstallConfig} from 'sentry/utils/gettingStartedDocs/node'; + +type Params = DocsParams; + +const getSdkConfigureSnippetToml = () => ` +compatibility_flags = ["nodejs_compat"] +# compatibility_flags = ["nodejs_als"] +`; + +const getSdkConfigureSnippetJson = () => ` +{ + "compatibility_flags": [ + "nodejs_compat" + ], + "compatibility_date": "2024-09-23" +}`; + +const getSdkSetupSnippet = (params: Params) => ` +import * as Sentry from "@sentry/cloudflare"; + +export default Sentry.withSentry( + env => ({ + dsn: "${params.dsn.public}", + // Set tracesSampleRate to 1.0 to capture 100% of spans for tracing. + // Learn more at + // https://docs.sentry.io/platforms/javascript/configuration/options/#traces-sample-rate + tracesSampleRate: 1.0, + }), + { + async fetch(request, env, ctx) { + return new Response('Hello World!'); + }, + } satisfies ExportedHandler, +);`; + +const getVerifySnippet = () => ` +setTimeout(() => { + throw new Error(); +});`; + +const onboarding: OnboardingConfig = { + introduction: () => + t( + 'In this quick guide you’ll set up and configure the Sentry Cloudflare SDK for the use in your Cloudflare Workers application.' + ), + install: params => [ + { + type: StepType.INSTALL, + description: t('Add the Sentry Cloudflare SDK as a dependency:'), + configurations: getInstallConfig(params, { + basePackage: '@sentry/cloudflare', + }), + }, + ], + configure: params => [ + { + type: StepType.CONFIGURE, + description: t( + "Configuration should happen as early as possible in your application's lifecycle." + ), + configurations: [ + { + description: tct( + "To use the SDK, you'll need to set either the [code:nodejs_compat] or [code:nodejs_als] compatibility flags in your [code:wrangler.json]/[code:wrangler.toml]. This is because the SDK needs access to the [code:AsyncLocalStorage] API to work correctly.", + { + code: , + } + ), + code: [ + { + label: 'JSON', + value: 'json', + language: 'json', + filename: 'wrangler.json', + code: getSdkConfigureSnippetJson(), + }, + { + label: 'Toml', + value: 'toml', + language: 'toml', + filename: 'wrangler.toml', + code: getSdkConfigureSnippetToml(), + }, + ], + }, + { + description: tct( + 'In order to initialize the SDK, wrap your handler with the [code:withSentry] function. Note that you can turn off almost all side effects using the respective options.', + { + code: , + guideLink: ( + + ), + } + ), + code: [ + { + label: 'TypeScript', + value: 'typescript', + language: 'typescript', + code: getSdkSetupSnippet(params), + }, + ], + }, + ], + }, + getUploadSourceMapsStep({ + guideLink: + 'https://docs.sentry.io/platforms/javascript/guides/cloudflare/sourcemaps/', + ...params, + }), + ], + verify: () => [ + { + type: StepType.VERIFY, + description: t( + "This snippet contains an intentional error and can be used as a test to make sure that everything's working as expected." + ), + configurations: [ + { + language: 'javascript', + code: getVerifySnippet(), + }, + ], + }, + ], +}; + +const crashReportOnboarding: OnboardingConfig = { + introduction: () => getCrashReportModalIntroduction(), + install: (params: Params) => getCrashReportJavaScriptInstallStep(params), + configure: () => [ + { + type: StepType.CONFIGURE, + description: getCrashReportModalConfigDescription({ + link: 'https://docs.sentry.io/platforms/javascript/guides/cloudflare/user-feedback/configuration/#crash-report-modal', + }), + }, + ], + verify: () => [], + nextSteps: () => [], +}; + +const docs: Docs = { + onboarding, + crashReportOnboarding, +}; + +export default docs; diff --git a/static/app/types/project.tsx b/static/app/types/project.tsx index 092ce739125cd1..82fd66caf52029 100644 --- a/static/app/types/project.tsx +++ b/static/app/types/project.tsx @@ -239,6 +239,8 @@ export type PlatformKey = | 'node' | 'node-awslambda' | 'node-azurefunctions' + | 'node-cloudflare-pages' + | 'node-cloudflare-workers' | 'node-connect' | 'node-express' | 'node-fastify' diff --git a/yarn.lock b/yarn.lock index ae8573970300c3..45bb2bd1cf4a06 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9866,10 +9866,10 @@ platform@^1.3.3: resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.6.tgz#48b4ce983164b209c2d45a107adb31f473a6e7a7" integrity sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg== -platformicons@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/platformicons/-/platformicons-7.0.1.tgz#eaf6a1e18fe8516b135df458ed603c4554ae050f" - integrity sha512-M/aNCZw4RJ8yIhkFBFhoQtOYgvwn+99upggQYy7BSaQxKaGS3Yb5/c1qC8+lKqzWWMZ+uafFOpEK6jLFPIkpww== +platformicons@^7.0.4: + version "7.0.4" + resolved "https://registry.yarnpkg.com/platformicons/-/platformicons-7.0.4.tgz#1e5df8b79478e11381dced9df45e129da172f89e" + integrity sha512-ld6YQ8CwUYScEp9qUW34tHq68nguzS7tTIYAW6TV64h/v8G1FqlKJ2eUZXMK/BSds8Vs+buV06NZX1r3Wm1kiw== dependencies: "@types/node" "*" "@types/react" "*"