Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Resolves: #10238 Clones the `request` in `action` to read `formData` and sets each entry as an attribute to the `action` span. Also adds a new e2e test application using latest Remix with Express, which both tests server and client-side. There seem to be a few limitations regarding the availability of complete data (multiple file uploads from a single input for example), but I think we can consider this as the best effort. This will only work when `sendDefaultPii` is set to `true`, but we can also add another option to control this. --------- Co-authored-by: Abhijeet Prasad <aprasad@sentry.io>
- Loading branch information
1 parent
cda367c
commit d381ace
Showing
28 changed files
with
1,056 additions
and
10 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
79 changes: 79 additions & 0 deletions
79
dev-packages/e2e-tests/test-applications/create-remix-app-express/.eslintrc.cjs
This file contains 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,79 @@ | ||
/** | ||
* This is intended to be a basic starting point for linting in your app. | ||
* It relies on recommended configs out of the box for simplicity, but you can | ||
* and should modify this configuration to best suit your team's needs. | ||
*/ | ||
|
||
/** @type {import('eslint').Linter.Config} */ | ||
module.exports = { | ||
root: true, | ||
parserOptions: { | ||
ecmaVersion: 'latest', | ||
sourceType: 'module', | ||
ecmaFeatures: { | ||
jsx: true, | ||
}, | ||
}, | ||
env: { | ||
browser: true, | ||
commonjs: true, | ||
es6: true, | ||
}, | ||
|
||
// Base config | ||
extends: ['eslint:recommended'], | ||
|
||
overrides: [ | ||
// React | ||
{ | ||
files: ['**/*.{js,jsx,ts,tsx}'], | ||
plugins: ['react', 'jsx-a11y'], | ||
extends: [ | ||
'plugin:react/recommended', | ||
'plugin:react/jsx-runtime', | ||
'plugin:react-hooks/recommended', | ||
'plugin:jsx-a11y/recommended', | ||
], | ||
settings: { | ||
react: { | ||
version: 'detect', | ||
}, | ||
formComponents: ['Form'], | ||
linkComponents: [ | ||
{ name: 'Link', linkAttribute: 'to' }, | ||
{ name: 'NavLink', linkAttribute: 'to' }, | ||
], | ||
'import/resolver': { | ||
typescript: {}, | ||
}, | ||
}, | ||
}, | ||
|
||
// Typescript | ||
{ | ||
files: ['**/*.{ts,tsx}'], | ||
plugins: ['@typescript-eslint', 'import'], | ||
parser: '@typescript-eslint/parser', | ||
settings: { | ||
'import/internal-regex': '^~/', | ||
'import/resolver': { | ||
node: { | ||
extensions: ['.ts', '.tsx'], | ||
}, | ||
typescript: { | ||
alwaysTryTypes: true, | ||
}, | ||
}, | ||
}, | ||
extends: ['plugin:@typescript-eslint/recommended', 'plugin:import/recommended', 'plugin:import/typescript'], | ||
}, | ||
|
||
// Node | ||
{ | ||
files: ['.eslintrc.cjs', 'server.js'], | ||
env: { | ||
node: true, | ||
}, | ||
}, | ||
], | ||
}; |
6 changes: 6 additions & 0 deletions
6
dev-packages/e2e-tests/test-applications/create-remix-app-express/.gitignore
This file contains 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,6 @@ | ||
node_modules | ||
|
||
/.cache | ||
/build | ||
/public/build | ||
.env |
2 changes: 2 additions & 0 deletions
2
dev-packages/e2e-tests/test-applications/create-remix-app-express/.npmrc
This file contains 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 |
46 changes: 46 additions & 0 deletions
46
dev-packages/e2e-tests/test-applications/create-remix-app-express/app/entry.client.tsx
This file contains 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,46 @@ | ||
import { RemixBrowser, useLocation, useMatches } from '@remix-run/react'; | ||
import * as Sentry from '@sentry/remix'; | ||
import { StrictMode, startTransition, useEffect } from 'react'; | ||
import { hydrateRoot } from 'react-dom/client'; | ||
|
||
Sentry.init({ | ||
environment: 'qa', // dynamic sampling bias to keep transactions | ||
dsn: window.ENV.SENTRY_DSN, | ||
integrations: [ | ||
Sentry.browserTracingIntegration({ | ||
useEffect, | ||
useLocation, | ||
useMatches, | ||
}), | ||
Sentry.replayIntegration(), | ||
], | ||
// Performance Monitoring | ||
tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production! | ||
replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production. | ||
replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur. | ||
tunnel: 'http://localhost:3031/', // proxy server | ||
}); | ||
|
||
Sentry.addEventProcessor(event => { | ||
if ( | ||
event.type === 'transaction' && | ||
(event.contexts?.trace?.op === 'pageload' || event.contexts?.trace?.op === 'navigation') | ||
) { | ||
const eventId = event.event_id; | ||
if (eventId) { | ||
window.recordedTransactions = window.recordedTransactions || []; | ||
window.recordedTransactions.push(eventId); | ||
} | ||
} | ||
|
||
return event; | ||
}); | ||
|
||
startTransition(() => { | ||
hydrateRoot( | ||
document, | ||
<StrictMode> | ||
<RemixBrowser /> | ||
</StrictMode>, | ||
); | ||
}); |
149 changes: 149 additions & 0 deletions
149
dev-packages/e2e-tests/test-applications/create-remix-app-express/app/entry.server.tsx
This file contains 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,149 @@ | ||
import * as Sentry from '@sentry/remix'; | ||
import * as isbotModule from 'isbot'; | ||
|
||
Sentry.init({ | ||
tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production! | ||
environment: 'qa', // dynamic sampling bias to keep transactions | ||
dsn: process.env.E2E_TEST_DSN, | ||
tunnel: 'http://localhost:3031/', // proxy server | ||
sendDefaultPii: true, // Testing the FormData | ||
}); | ||
|
||
import { PassThrough } from 'node:stream'; | ||
|
||
import type { AppLoadContext, EntryContext } from '@remix-run/node'; | ||
import { createReadableStreamFromReadable } from '@remix-run/node'; | ||
import { installGlobals } from '@remix-run/node'; | ||
import { RemixServer } from '@remix-run/react'; | ||
import { renderToPipeableStream } from 'react-dom/server'; | ||
|
||
installGlobals(); | ||
|
||
const ABORT_DELAY = 5_000; | ||
|
||
export const handleError = Sentry.wrapRemixHandleError; | ||
|
||
export default function handleRequest( | ||
request: Request, | ||
responseStatusCode: number, | ||
responseHeaders: Headers, | ||
remixContext: EntryContext, | ||
loadContext: AppLoadContext, | ||
) { | ||
return isBotRequest(request.headers.get('user-agent')) | ||
? handleBotRequest(request, responseStatusCode, responseHeaders, remixContext) | ||
: handleBrowserRequest(request, responseStatusCode, responseHeaders, remixContext); | ||
} | ||
|
||
// We have some Remix apps in the wild already running with isbot@3 so we need | ||
// to maintain backwards compatibility even though we want new apps to use | ||
// isbot@4. That way, we can ship this as a minor Semver update to @remix-run/dev. | ||
function isBotRequest(userAgent: string | null) { | ||
if (!userAgent) { | ||
return false; | ||
} | ||
|
||
// isbot >= 3.8.0, >4 | ||
if ('isbot' in isbotModule && typeof isbotModule.isbot === 'function') { | ||
return isbotModule.isbot(userAgent); | ||
} | ||
|
||
// isbot < 3.8.0 | ||
if ('default' in isbotModule && typeof isbotModule.default === 'function') { | ||
return isbotModule.default(userAgent); | ||
} | ||
|
||
return false; | ||
} | ||
|
||
function handleBotRequest( | ||
request: Request, | ||
responseStatusCode: number, | ||
responseHeaders: Headers, | ||
remixContext: EntryContext, | ||
) { | ||
return new Promise((resolve, reject) => { | ||
let shellRendered = false; | ||
const { pipe, abort } = renderToPipeableStream( | ||
<RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />, | ||
{ | ||
onAllReady() { | ||
shellRendered = true; | ||
const body = new PassThrough(); | ||
const stream = createReadableStreamFromReadable(body); | ||
|
||
responseHeaders.set('Content-Type', 'text/html'); | ||
|
||
resolve( | ||
new Response(stream, { | ||
headers: responseHeaders, | ||
status: responseStatusCode, | ||
}), | ||
); | ||
|
||
pipe(body); | ||
}, | ||
onShellError(error: unknown) { | ||
reject(error); | ||
}, | ||
onError(error: unknown) { | ||
responseStatusCode = 500; | ||
// Log streaming rendering errors from inside the shell. Don't log | ||
// errors encountered during initial shell rendering since they'll | ||
// reject and get logged in handleDocumentRequest. | ||
if (shellRendered) { | ||
console.error(error); | ||
} | ||
}, | ||
}, | ||
); | ||
|
||
setTimeout(abort, ABORT_DELAY); | ||
}); | ||
} | ||
|
||
function handleBrowserRequest( | ||
request: Request, | ||
responseStatusCode: number, | ||
responseHeaders: Headers, | ||
remixContext: EntryContext, | ||
) { | ||
return new Promise((resolve, reject) => { | ||
let shellRendered = false; | ||
const { pipe, abort } = renderToPipeableStream( | ||
<RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />, | ||
{ | ||
onShellReady() { | ||
shellRendered = true; | ||
const body = new PassThrough(); | ||
const stream = createReadableStreamFromReadable(body); | ||
|
||
responseHeaders.set('Content-Type', 'text/html'); | ||
|
||
resolve( | ||
new Response(stream, { | ||
headers: responseHeaders, | ||
status: responseStatusCode, | ||
}), | ||
); | ||
|
||
pipe(body); | ||
}, | ||
onShellError(error: unknown) { | ||
reject(error); | ||
}, | ||
onError(error: unknown) { | ||
responseStatusCode = 500; | ||
// Log streaming rendering errors from inside the shell. Don't log | ||
// errors encountered during initial shell rendering since they'll | ||
// reject and get logged in handleDocumentRequest. | ||
if (shellRendered) { | ||
console.error(error); | ||
} | ||
}, | ||
}, | ||
); | ||
|
||
setTimeout(abort, ABORT_DELAY); | ||
}); | ||
} |
80 changes: 80 additions & 0 deletions
80
dev-packages/e2e-tests/test-applications/create-remix-app-express/app/root.tsx
This file contains 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,80 @@ | ||
import { cssBundleHref } from '@remix-run/css-bundle'; | ||
import { LinksFunction, MetaFunction, json } from '@remix-run/node'; | ||
import { | ||
Links, | ||
LiveReload, | ||
Meta, | ||
Outlet, | ||
Scripts, | ||
ScrollRestoration, | ||
useLoaderData, | ||
useRouteError, | ||
} from '@remix-run/react'; | ||
import { captureRemixErrorBoundaryError, withSentry } from '@sentry/remix'; | ||
import type { SentryMetaArgs } from '@sentry/remix'; | ||
|
||
export const links: LinksFunction = () => [...(cssBundleHref ? [{ rel: 'stylesheet', href: cssBundleHref }] : [])]; | ||
|
||
export const loader = () => { | ||
return json({ | ||
ENV: { | ||
SENTRY_DSN: process.env.E2E_TEST_DSN, | ||
}, | ||
}); | ||
}; | ||
|
||
export const meta = ({ data }: SentryMetaArgs<MetaFunction<typeof loader>>) => { | ||
return [ | ||
{ | ||
env: data.ENV, | ||
}, | ||
{ | ||
name: 'sentry-trace', | ||
content: data.sentryTrace, | ||
}, | ||
{ | ||
name: 'baggage', | ||
content: data.sentryBaggage, | ||
}, | ||
]; | ||
}; | ||
|
||
export function ErrorBoundary() { | ||
const error = useRouteError(); | ||
const eventId = captureRemixErrorBoundaryError(error); | ||
|
||
return ( | ||
<div> | ||
<span>ErrorBoundary Error</span> | ||
<span id="event-id">{eventId}</span> | ||
</div> | ||
); | ||
} | ||
|
||
function App() { | ||
const { ENV } = useLoaderData(); | ||
|
||
return ( | ||
<html lang="en"> | ||
<head> | ||
<meta charSet="utf-8" /> | ||
<meta name="viewport" content="width=device-width,initial-scale=1" /> | ||
<script | ||
dangerouslySetInnerHTML={{ | ||
__html: `window.ENV = ${JSON.stringify(ENV)}`, | ||
}} | ||
/> | ||
<Meta /> | ||
<Links /> | ||
</head> | ||
<body> | ||
<Outlet /> | ||
<ScrollRestoration /> | ||
<Scripts /> | ||
<LiveReload /> | ||
</body> | ||
</html> | ||
); | ||
} | ||
|
||
export default withSentry(App); |
Oops, something went wrong.