diff --git a/docs/contributing/pages/components.mdx b/docs/contributing/pages/components.mdx
index 08c432083954ce..03ec2d8b5b1019 100644
--- a/docs/contributing/pages/components.mdx
+++ b/docs/contributing/pages/components.mdx
@@ -242,3 +242,110 @@ Attributes:
- `supported` (string[])
- `notSupported` (string[])
- `noGuides` (boolean) - hide this on all guides (takes precedence over `supported`/`notSupported`)
+
+## Onboarding Options
+
+If you're writing product feature specific docs, you can specify code block `onboardingOptions` metadata:
+
+````markdown
+```go {"onboardingOptions": {"performance": "13-17"}}
+// your code here
+```
+````
+
+the general syntax is `{"onboardingOptions": {"feature": "range"}}` where `feature` is the feature id
+and `range` is the corresponding line range (similar to the line highlighting syntax).
+
+You can specify multiple features by separating them with a comma:
+
+`{"onboardingOptions": {"performance": "13-17", "profiling": "5-6"}}`
+
+The range visibility will be controlled by the `OnboardingOptionButtons` component:
+
+````jsx diff
+
+````
+
+- `options` can either be either an object of this shape:
+
+```typescript
+{
+ id: 'error-monitoring' | 'performance' | 'profiling' | 'session-replay',
+ disabled: boolean,
+ checked: boolean
+}
+```
+or a string (one of these `id`s 👆) for convenience when using defaults.
+
+
+ The underlying implementation relies on the `onboardingOptions` metadata in the code blocks to be valid JSON syntax.
+
+
+- default values: `checked: false` and `disabled: false` (`true` for `error-monitoring`).
+
+Example (output of the above):
+
+
+
+```go {"onboardingOptions": {"performance": "13-17"}}
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/getsentry/sentry-go"
+ sentrygin "github.com/getsentry/sentry-go/gin"
+ "github.com/gin-gonic/gin"
+)
+
+// To initialize Sentry's handler, you need to initialize Sentry itself beforehand
+if err := sentry.Init(sentry.ClientOptions{
+ Dsn: "___PUBLIC_DSN___",
+ EnableTracing: true,
+ // Set TracesSampleRate to 1.0 to capture 100%
+ // of transactions for performance monitoring.
+ // We recommend adjusting this value in production,
+ TracesSampleRate: 1.0,
+}); err != nil {
+ fmt.Printf("Sentry initialization failed: %v\n", err)
+}
+
+// Then create your app
+app := gin.Default()
+
+// Once it's done, you can attach the handler as one of your middleware
+app.Use(sentrygin.New(sentrygin.Options{}))
+
+// Set up routes
+app.GET("/", func(ctx *gin.Context) {
+ ctx.String(http.StatusOK, "Hello world!")
+})
+
+// And run it
+app.Run(":3000")
+```
+
+You can conditionally render content based on the selected onboarding options using the
+`OnboardingOption` component
+
+Example (toggle the `performance` option above to see the effect):
+
+
+
+```jsx
+
+ This code block is wrapped in a `OnboardingOption` component and will only
+ be rendered when the `performance` option is selected.
+
+```
+
diff --git a/docs/platforms/go/guides/gin/index.mdx b/docs/platforms/go/guides/gin/index.mdx
index 63a1ab73721105..e81fd93f5c6e51 100644
--- a/docs/platforms/go/guides/gin/index.mdx
+++ b/docs/platforms/go/guides/gin/index.mdx
@@ -9,6 +9,13 @@ For a quick reference, there is a [complete example](https://github.com/getsentr
## Install
+
+
```bash
go get github.com/getsentry/sentry-go/gin
```
@@ -17,7 +24,7 @@ go get github.com/getsentry/sentry-go/gin
-```go
+```go {"onboardingOptions": {"performance": "13-17"}}
import (
"fmt"
"net/http"
diff --git a/docs/platforms/php/index.mdx b/docs/platforms/php/index.mdx
index 4350150524f5b0..46bb3f07a44193 100644
--- a/docs/platforms/php/index.mdx
+++ b/docs/platforms/php/index.mdx
@@ -21,6 +21,14 @@ This Sentry PHP SDK provides support for PHP 7.2 or later. If you are using our
## Install
+
+
Sentry captures data by using an SDK within your application’s runtime. These are platform-specific and allow Sentry to have a deep understanding of how your application works.
Install the SDK using [Composer](https://getcomposer.org/).
@@ -29,24 +37,42 @@ Install the SDK using [Composer](https://getcomposer.org/).
composer require sentry/sentry
```
-## Configure
+
+
+Install the Excimer extension via PECL:
+
+```bash
+pecl install excimer
+```
-After you’ve completed setting up a project in Sentry, Sentry will give you a value which we call a DSN or Data Source Name. It looks a lot like a standard URL, but it’s just a representation of the configuration required by the Sentry SDKs. It consists of a few pieces, including the protocol, public key, the server address, and the project identifier.
+The Excimer PHP extension supports PHP 7.2 and up. Excimer requires Linux or macOS and doesn't support Windows. For additional ways to install Excimer, [see docs](/platforms/php/profiling/#installation).
+
+
+
+## Configure
To capture all errors, even the one during the startup of your application, you should initialize the Sentry PHP SDK as soon as possible.
-```php
-\Sentry\init(['dsn' => '___PUBLIC_DSN___' ]);
+```php {"onboardingOptions": {"performance": "3-4", "profiling": "5-6"}}
+\Sentry\init([
+ 'dsn' => '___PUBLIC_DSN___' ,
+ // Specify a fixed sample rate
+ 'traces_sample_rate' => 1.0,
+ // Set a sampling rate for profiling - this is relative to traces_sample_rate
+ 'profiles_sample_rate' => 1.0,
+]);
```
-## Usage
+## Verify
+
+In PHP you can either capture a caught exception or capture the last error with captureLastError.
```php
try {
- $this->functionFailsForSure();
+ $this->functionFailsForSure();
} catch (\Throwable $exception) {
- \Sentry\captureException($exception);
+ \Sentry\captureException($exception);
}
```
diff --git a/package.json b/package.json
index bce850b7f336b7..d3d0c749eefc07 100644
--- a/package.json
+++ b/package.json
@@ -67,6 +67,7 @@
"next-auth": "^4.24.5",
"next-mdx-remote": "^4.4.1",
"nextjs-toploader": "^1.6.6",
+ "parse-numeric-range": "^1.3.0",
"platformicons": "^5.10.9",
"prism-sentry": "^1.0.2",
"prismjs": "^1.27.0",
diff --git a/src/components/codeBlock/index.tsx b/src/components/codeBlock/index.tsx
index 8e9057af073274..cbc88eaf4c9db5 100644
--- a/src/components/codeBlock/index.tsx
+++ b/src/components/codeBlock/index.tsx
@@ -30,8 +30,7 @@ export function CodeBlock({filename, language, children}: CodeBlockProps) {
return;
}
- let code =
- codeRef.current.textContent || codeRef.current.innerText.replace(/\n\n/g, '\n');
+ let code = codeRef.current.innerText.replace(/\n\n/g, '\n');
// don't copy leading prompt for bash
if (language === 'bash' || language === 'shell') {
diff --git a/src/components/docPage/type.scss b/src/components/docPage/type.scss
index cf2a2220ef85da..6f10b8f1cf8bd7 100644
--- a/src/components/docPage/type.scss
+++ b/src/components/docPage/type.scss
@@ -1,5 +1,4 @@
.prose {
-
h1,
h2,
h3,
@@ -140,9 +139,54 @@
}
}
- dt+dd {
+ dt + dd {
margin-bottom: var(--paragraph-margin-bottom);
}
+
+ [data-onboarding-option].hidden {
+ display: none;
+ }
+ [data-onboarding-option].animate-line {
+ animation:
+ slideLeft 0.2s ease-out forwards,
+ highlight 1.2s forwards;
+ }
+ [data-onboarding-option].animate-content {
+ animation: slideDown 0.2s ease-out forwards;
+ }
+}
+
+@keyframes slideDown {
+ from {
+ transform: translateY(-12px);
+ }
+ to {
+ transform: translateY(0);
+ }
+}
+
+@keyframes slideLeft {
+ from {
+ transform: translateX(-1ch);
+ }
+ to {
+ transform: translateX(0);
+ }
+}
+
+@keyframes highlight {
+ 0% {
+ background-color: rgba(255, 255, 255, 0);
+ }
+ 10% {
+ background-color: rgba(255, 255, 255, 0.15);
+ }
+ 80% {
+ background-color: rgba(255, 255, 255, 0.15);
+ }
+ 100% {
+ background-color: rgba(255, 255, 255, 0);
+ }
}
.sidebar {
@@ -164,7 +208,7 @@ h3.config-key {
//
This paragraph has a normal bottom margin
//
This paragraph does not have a bottom margin
//
-.content-flush-bottom>*:last-child {
+.content-flush-bottom > *:last-child {
margin-bottom: 0 !important;
}
diff --git a/src/components/onboarding/index.tsx b/src/components/onboarding/index.tsx
new file mode 100644
index 00000000000000..c4d58d06def3ef
--- /dev/null
+++ b/src/components/onboarding/index.tsx
@@ -0,0 +1,267 @@
+'use client';
+
+import {ReactNode, useEffect, useReducer, useRef, useState} from 'react';
+import {QuestionMarkCircledIcon} from '@radix-ui/react-icons';
+import * as Tooltip from '@radix-ui/react-tooltip';
+import {Button, Checkbox, Theme} from '@radix-ui/themes';
+
+import styles from './styles.module.scss';
+
+const optionDetails: Record<
+ OptionId,
+ {
+ description: ReactNode;
+ name: string;
+ deps?: OptionId[];
+ }
+> = {
+ 'error-monitoring': {
+ name: 'Error Monitoring',
+ description: "Let's admit it, we all have errors.",
+ },
+ performance: {
+ name: 'Performance Monitoring',
+ description: (
+
+ Automatic performance issue detection across services and context on who is
+ impacted, outliers, regressions, and the root cause of your slowdown.
+
+ ),
+ },
+ profiling: {
+ name: 'Profiling',
+ description: (
+
+ Requires Performance Monitoring
+ See the exact lines of code causing your performance bottlenecks, for faster
+ troubleshooting and resource optimization.
+
+ ),
+ deps: ['performance'],
+ },
+ 'session-replay': {
+ name: 'Session Replay',
+ description: (
+
+ Video-like reproductions of user sessions with debugging context to help you
+ confirm issue impact and troubleshoot faster.
+
+ ),
+ },
+};
+
+const OPTION_IDS = [
+ 'error-monitoring',
+ 'performance',
+ 'profiling',
+ 'session-replay',
+] as const;
+
+type OptionId = (typeof OPTION_IDS)[number];
+
+type OnboardingOptionType = {
+ /**
+ * Unique identifier for the option, will control the visibility
+ * of ` somewhere on the page
+ * or lines of code specified in in a `{onboardingOptions: {this_id: 'line-range'}}` in a code block meta
+ */
+ id: OptionId;
+ /**
+ * defaults to `true`
+ */
+ checked?: boolean;
+ disabled?: boolean;
+};
+
+const validateOptionIds = (options: Pick[]) => {
+ options.forEach(option => {
+ if (!OPTION_IDS.includes(option.id)) {
+ throw new Error(
+ `Invalid option id: ${option.id}.\nValid options are: ${OPTION_IDS.map(opt => `"${opt}"`).join(', ')}`
+ );
+ }
+ });
+};
+
+export function OnboardingOption({
+ children,
+ optionId,
+}: {
+ children: React.ReactNode;
+ optionId: OptionId;
+}) {
+ validateOptionIds([{id: optionId}]);
+ return