-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
feat(react-router): Add support for React Router instrumentation API #18580
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 10 commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
b188e0a
feat(react-router): Add support for React Router instrumentation API
onurtemizkan 95721fc
Move instrumentation API functions to serverGlobals not to break hydr…
onurtemizkan fa71e6b
Update hydrogen server transaction tests with better parameterization
onurtemizkan 75374f3
Address copilot review
onurtemizkan 8281d63
Improve E2E test coverage
onurtemizkan 7aa2d67
Use snake_case for span ops
onurtemizkan e3064e2
Move navigate hook flag inside client check
onurtemizkan 23514b7
Move data inside `mechanism` object
onurtemizkan 9b756ae
Merge remote-tracking branch 'origin/develop' into react-router-8-ins…
onurtemizkan 0c84709
Prevent Framework Mode navigation span regression
onurtemizkan 681eb3e
Enhance navigation with popstate listener and numeric navigation hand…
onurtemizkan 1571025
Lint
onurtemizkan 7c9148e
Set span status on request handler errors
onurtemizkan 3ad323c
Merge branch 'develop' into react-router-8-instrumentation-api
onurtemizkan f1aab54
Clean up
onurtemizkan 7ab179a
Move `captureInstrumentationError` calls inside error check blocks
onurtemizkan 4cc4af8
Move error capture inside check
onurtemizkan 46aee15
Merge branch 'develop' into react-router-8-instrumentation-api
onurtemizkan 9cf02d8
Merge branch 'develop' into react-router-8-instrumentation-api
chargome File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 32 additions & 0 deletions
32
dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/.gitignore
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||
|
|
||
| # dependencies | ||
| /node_modules | ||
| /.pnp | ||
| .pnp.js | ||
|
|
||
| # testing | ||
| /coverage | ||
|
|
||
| # production | ||
| /build | ||
|
|
||
| # misc | ||
| .DS_Store | ||
| .env.local | ||
| .env.development.local | ||
| .env.test.local | ||
| .env.production.local | ||
|
|
||
| npm-debug.log* | ||
| yarn-debug.log* | ||
| yarn-error.log* | ||
|
|
||
| /test-results/ | ||
| /playwright-report/ | ||
| /playwright/.cache/ | ||
|
|
||
| !*.d.ts | ||
|
|
||
| # react router | ||
| .react-router |
2 changes: 2 additions & 0 deletions
2
dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/.npmrc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| @sentry:registry=http://127.0.0.1:4873 | ||
| @sentry-internal:registry=http://127.0.0.1:4873 |
5 changes: 5 additions & 0 deletions
5
...packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/app/app.css
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| body { | ||
| font-family: system-ui, sans-serif; | ||
| margin: 0; | ||
| padding: 20px; | ||
| } |
33 changes: 33 additions & 0 deletions
33
...e2e-tests/test-applications/react-router-7-framework-instrumentation/app/entry.client.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| import * as Sentry from '@sentry/react-router'; | ||
| import { StrictMode, startTransition } from 'react'; | ||
| import { hydrateRoot } from 'react-dom/client'; | ||
| import { HydratedRouter } from 'react-router/dom'; | ||
|
|
||
| // Create the tracing integration with useInstrumentationAPI enabled | ||
| // This must be set BEFORE Sentry.init() to prepare the instrumentation | ||
| const tracing = Sentry.reactRouterTracingIntegration({ useInstrumentationAPI: true }); | ||
|
|
||
| Sentry.init({ | ||
| environment: 'qa', // dynamic sampling bias to keep transactions | ||
| dsn: 'https://username@domain/123', | ||
| tunnel: `http://localhost:3031/`, // proxy server | ||
| integrations: [tracing], | ||
| tracesSampleRate: 1.0, | ||
| tracePropagationTargets: [/^\//], | ||
| }); | ||
|
|
||
| // Get the client instrumentation from the Sentry integration | ||
| // NOTE: As of React Router 7.x, HydratedRouter does NOT invoke these hooks in Framework Mode. | ||
| // The client-side instrumentation is prepared for when React Router adds support. | ||
| // Client-side navigation is currently handled by the legacy instrumentHydratedRouter() approach. | ||
| const sentryClientInstrumentation = [tracing.clientInstrumentation]; | ||
|
|
||
| startTransition(() => { | ||
| hydrateRoot( | ||
| document, | ||
| <StrictMode> | ||
| {/* unstable_instrumentations is React Router 7.x's prop name (will become `instrumentations` in v8) */} | ||
| <HydratedRouter unstable_instrumentations={sentryClientInstrumentation} /> | ||
| </StrictMode>, | ||
| ); | ||
| }); |
22 changes: 22 additions & 0 deletions
22
...e2e-tests/test-applications/react-router-7-framework-instrumentation/app/entry.server.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { createReadableStreamFromReadable } from '@react-router/node'; | ||
| import * as Sentry from '@sentry/react-router'; | ||
| import { renderToPipeableStream } from 'react-dom/server'; | ||
| import { ServerRouter } from 'react-router'; | ||
| import { type HandleErrorFunction } from 'react-router'; | ||
|
|
||
| const ABORT_DELAY = 5_000; | ||
|
|
||
| const handleRequest = Sentry.createSentryHandleRequest({ | ||
| streamTimeout: ABORT_DELAY, | ||
| ServerRouter, | ||
| renderToPipeableStream, | ||
| createReadableStreamFromReadable, | ||
| }); | ||
|
|
||
| export default handleRequest; | ||
|
|
||
| export const handleError: HandleErrorFunction = Sentry.createSentryHandleError({ logErrors: true }); | ||
|
|
||
| // Use Sentry's instrumentation API for server-side tracing | ||
| // `unstable_instrumentations` is React Router 7.x's export name (will become `instrumentations` in v8) | ||
| export const unstable_instrumentations = [Sentry.createSentryServerInstrumentation()]; |
69 changes: 69 additions & 0 deletions
69
...ackages/e2e-tests/test-applications/react-router-7-framework-instrumentation/app/root.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| import * as Sentry from '@sentry/react-router'; | ||
| import { Links, Meta, Outlet, Scripts, ScrollRestoration, isRouteErrorResponse } from 'react-router'; | ||
| import type { Route } from './+types/root'; | ||
| import stylesheet from './app.css?url'; | ||
|
|
||
| export const links: Route.LinksFunction = () => [ | ||
| { rel: 'preconnect', href: 'https://fonts.googleapis.com' }, | ||
| { | ||
| rel: 'preconnect', | ||
| href: 'https://fonts.gstatic.com', | ||
| crossOrigin: 'anonymous', | ||
| }, | ||
| { | ||
| rel: 'stylesheet', | ||
| href: 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap', | ||
| }, | ||
| { rel: 'stylesheet', href: stylesheet }, | ||
| ]; | ||
|
|
||
| export function Layout({ children }: { children: React.ReactNode }) { | ||
| return ( | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charSet="utf-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||
| <Meta /> | ||
| <Links /> | ||
| </head> | ||
| <body> | ||
| {children} | ||
| <ScrollRestoration /> | ||
| <Scripts /> | ||
| </body> | ||
| </html> | ||
| ); | ||
| } | ||
|
|
||
| export default function App() { | ||
| return <Outlet />; | ||
| } | ||
|
|
||
| export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { | ||
| let message = 'Oops!'; | ||
| let details = 'An unexpected error occurred.'; | ||
| let stack: string | undefined; | ||
|
|
||
| if (isRouteErrorResponse(error)) { | ||
| message = error.status === 404 ? '404' : 'Error'; | ||
| details = error.status === 404 ? 'The requested page could not be found.' : error.statusText || details; | ||
| } else if (error && error instanceof Error) { | ||
| Sentry.captureException(error); | ||
| if (import.meta.env.DEV) { | ||
| details = error.message; | ||
| stack = error.stack; | ||
| } | ||
| } | ||
|
|
||
| return ( | ||
| <main className="pt-16 p-4 container mx-auto"> | ||
| <h1>{message}</h1> | ||
| <p>{details}</p> | ||
| {stack && ( | ||
| <pre className="w-full p-4 overflow-x-auto"> | ||
| <code>{stack}</code> | ||
| </pre> | ||
| )} | ||
| </main> | ||
| ); | ||
| } |
19 changes: 19 additions & 0 deletions
19
...ckages/e2e-tests/test-applications/react-router-7-framework-instrumentation/app/routes.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import { type RouteConfig, index, prefix, route } from '@react-router/dev/routes'; | ||
|
|
||
| export default [ | ||
| index('routes/home.tsx'), | ||
| ...prefix('performance', [ | ||
| index('routes/performance/index.tsx'), | ||
| route('ssr', 'routes/performance/ssr.tsx'), | ||
| route('with/:param', 'routes/performance/dynamic-param.tsx'), | ||
| route('static', 'routes/performance/static.tsx'), | ||
| route('server-loader', 'routes/performance/server-loader.tsx'), | ||
| route('server-action', 'routes/performance/server-action.tsx'), | ||
| route('with-middleware', 'routes/performance/with-middleware.tsx'), | ||
| route('error-loader', 'routes/performance/error-loader.tsx'), | ||
| route('error-action', 'routes/performance/error-action.tsx'), | ||
| route('error-middleware', 'routes/performance/error-middleware.tsx'), | ||
| route('lazy-route', 'routes/performance/lazy-route.tsx'), | ||
| route('fetcher-test', 'routes/performance/fetcher-test.tsx'), | ||
| ]), | ||
| ] satisfies RouteConfig; |
10 changes: 10 additions & 0 deletions
10
.../e2e-tests/test-applications/react-router-7-framework-instrumentation/app/routes/home.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| export function meta() { | ||
| return [ | ||
| { title: 'React Router Instrumentation API Test' }, | ||
| { name: 'description', content: 'Testing React Router instrumentation API' }, | ||
| ]; | ||
| } | ||
|
|
||
| export default function Home() { | ||
| return <div>home</div>; | ||
| } |
15 changes: 15 additions & 0 deletions
15
...cations/react-router-7-framework-instrumentation/app/routes/performance/dynamic-param.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import type { Route } from './+types/dynamic-param'; | ||
|
|
||
| // Minimal loader to trigger Sentry's route instrumentation | ||
| export function loader() { | ||
| return null; | ||
| } | ||
|
|
||
| export default function DynamicParamPage({ params }: Route.ComponentProps) { | ||
| return ( | ||
| <div> | ||
| <h1>Dynamic Param Page</h1> | ||
| <div>Param: {params.param}</div> | ||
| </div> | ||
| ); | ||
| } |
16 changes: 16 additions & 0 deletions
16
...ications/react-router-7-framework-instrumentation/app/routes/performance/error-action.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import { Form } from 'react-router'; | ||
|
|
||
| export async function action(): Promise<never> { | ||
| throw new Error('Action error for testing'); | ||
| } | ||
|
|
||
| export default function ErrorActionPage() { | ||
| return ( | ||
| <div> | ||
| <h1>Error Action Page</h1> | ||
| <Form method="post"> | ||
| <button type="submit">Trigger Error</button> | ||
| </Form> | ||
| </div> | ||
| ); | ||
| } |
12 changes: 12 additions & 0 deletions
12
...ications/react-router-7-framework-instrumentation/app/routes/performance/error-loader.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| export function loader(): never { | ||
| throw new Error('Loader error for testing'); | ||
| } | ||
|
|
||
| export default function ErrorLoaderPage() { | ||
| return ( | ||
| <div> | ||
| <h1>Error Loader Page</h1> | ||
| <p>This should not render</p> | ||
| </div> | ||
| ); | ||
| } |
16 changes: 16 additions & 0 deletions
16
...ions/react-router-7-framework-instrumentation/app/routes/performance/error-middleware.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import type { Route } from './+types/error-middleware'; | ||
|
|
||
| export const middleware: Route.MiddlewareFunction[] = [ | ||
| async function errorMiddleware() { | ||
| throw new Error('Middleware error for testing'); | ||
| }, | ||
| ]; | ||
|
|
||
| export default function ErrorMiddlewarePage() { | ||
| return ( | ||
| <div> | ||
| <h1>Error Middleware Page</h1> | ||
| <p>This should not render</p> | ||
| </div> | ||
| ); | ||
| } |
30 changes: 30 additions & 0 deletions
30
...ications/react-router-7-framework-instrumentation/app/routes/performance/fetcher-test.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import { useFetcher } from 'react-router'; | ||
| import type { Route } from './+types/fetcher-test'; | ||
|
|
||
| export async function loader() { | ||
| return { message: 'Fetcher test page loaded' }; | ||
| } | ||
|
|
||
| export async function action({ request }: Route.ActionArgs) { | ||
| const formData = await request.formData(); | ||
| const value = formData.get('value')?.toString() || ''; | ||
| await new Promise(resolve => setTimeout(resolve, 50)); | ||
| return { success: true, value }; | ||
| } | ||
|
|
||
| export default function FetcherTestPage() { | ||
| const fetcher = useFetcher(); | ||
|
|
||
| return ( | ||
| <div> | ||
| <h1 id="fetcher-test-title">Fetcher Test Page</h1> | ||
| <fetcher.Form method="post"> | ||
| <input type="hidden" name="value" value="test-value" /> | ||
| <button type="submit" id="fetcher-submit"> | ||
| Submit via Fetcher | ||
| </button> | ||
| </fetcher.Form> | ||
| {fetcher.data?.success && <div id="fetcher-result">Fetcher result: {fetcher.data.value}</div>} | ||
| </div> | ||
| ); | ||
| } |
19 changes: 19 additions & 0 deletions
19
...st-applications/react-router-7-framework-instrumentation/app/routes/performance/index.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import { Link } from 'react-router'; | ||
|
|
||
| // Minimal loader to trigger Sentry's route instrumentation | ||
| export function loader() { | ||
| return null; | ||
| } | ||
|
|
||
| export default function PerformancePage() { | ||
| return ( | ||
| <div> | ||
| <h1>Performance Page</h1> | ||
| <nav> | ||
| <Link to="/performance/ssr">SSR Page</Link> | ||
| <Link to="/performance/with/sentry">With Param Page</Link> | ||
| <Link to="/performance/server-loader">Server Loader</Link> | ||
| </nav> | ||
| </div> | ||
| ); | ||
| } | ||
14 changes: 14 additions & 0 deletions
14
...plications/react-router-7-framework-instrumentation/app/routes/performance/lazy-route.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| export async function loader() { | ||
| // Simulate a slow lazy load | ||
| await new Promise(resolve => setTimeout(resolve, 100)); | ||
| return { message: 'Lazy loader data' }; | ||
| } | ||
|
|
||
| export default function LazyRoute() { | ||
| return ( | ||
| <div> | ||
| <h1 id="lazy-route-title">Lazy Route</h1> | ||
| <p id="lazy-route-content">This route was lazily loaded</p> | ||
| </div> | ||
| ); | ||
| } |
22 changes: 22 additions & 0 deletions
22
...cations/react-router-7-framework-instrumentation/app/routes/performance/server-action.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { Form } from 'react-router'; | ||
| import type { Route } from './+types/server-action'; | ||
|
|
||
| export async function action({ request }: Route.ActionArgs) { | ||
| const formData = await request.formData(); | ||
| const name = formData.get('name')?.toString() || ''; | ||
| await new Promise(resolve => setTimeout(resolve, 100)); | ||
| return { success: true, name }; | ||
| } | ||
|
|
||
| export default function ServerActionPage({ actionData }: Route.ComponentProps) { | ||
| return ( | ||
| <div> | ||
| <h1>Server Action Page</h1> | ||
| <Form method="post"> | ||
| <input type="text" name="name" defaultValue="sentry" /> | ||
| <button type="submit">Submit</button> | ||
| </Form> | ||
| {actionData?.success && <div>Action completed for: {actionData.name}</div>} | ||
| </div> | ||
| ); | ||
| } |
16 changes: 16 additions & 0 deletions
16
...cations/react-router-7-framework-instrumentation/app/routes/performance/server-loader.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import type { Route } from './+types/server-loader'; | ||
|
|
||
| export async function loader() { | ||
| await new Promise(resolve => setTimeout(resolve, 100)); | ||
| return { data: 'burritos' }; | ||
| } | ||
|
|
||
| export default function ServerLoaderPage({ loaderData }: Route.ComponentProps) { | ||
| const { data } = loaderData; | ||
| return ( | ||
| <div> | ||
| <h1>Server Loader Page</h1> | ||
| <div>{data}</div> | ||
| </div> | ||
| ); | ||
| } |
12 changes: 12 additions & 0 deletions
12
...test-applications/react-router-7-framework-instrumentation/app/routes/performance/ssr.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import { Link } from 'react-router'; | ||
|
|
||
| export default function SsrPage() { | ||
| return ( | ||
| <div> | ||
| <h1>SSR Page</h1> | ||
| <nav> | ||
| <Link to="/performance">Back to Performance</Link> | ||
| </nav> | ||
| </div> | ||
| ); | ||
| } |
7 changes: 7 additions & 0 deletions
7
...t-applications/react-router-7-framework-instrumentation/app/routes/performance/static.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| export default function StaticPage() { | ||
| return ( | ||
| <div> | ||
| <h1>Static Page</h1> | ||
| </div> | ||
| ); | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.