Skip to content

Commit feeb70a

Browse files
author
Luca Forstner
authored
Merge pull request #9596 from getsentry/prepare-release/7.81.0
2 parents 3d8f692 + 4ff96ec commit feeb70a

File tree

76 files changed

+1518
-411
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+1518
-411
lines changed

.github/workflows/codeql-analysis.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ concurrency:
3232
jobs:
3333
analyze:
3434
name: Analyze
35-
runs-on: ubuntu-20.04
35+
runs-on: ubuntu-latest
3636

3737
strategy:
3838
fail-fast: false
@@ -51,6 +51,7 @@ jobs:
5151
uses: github/codeql-action/init@v2
5252
with:
5353
config-file: ./.github/codeql/codeql-config.yml
54+
queries: security-extended
5455
languages: ${{ matrix.language }}
5556
# If you wish to specify custom queries, you can do so here or in a config file.
5657
# By default, queries listed here will override any specified in a config file.

CHANGELOG.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,59 @@
44

55
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
66

7+
## 7.81.0
8+
9+
### Important Changes
10+
11+
**- feat(nextjs): Add instrumentation utility for server actions (#9553)**
12+
13+
This release adds a utility function `withServerActionInstrumentation` to the `@sentry/nextjs` SDK for instrumenting your Next.js server actions with error and performance monitoring.
14+
15+
You can optionally pass form data and headers to record them, and configure the wrapper to record the Server Action responses:
16+
17+
```tsx
18+
import * as Sentry from "@sentry/nextjs";
19+
import { headers } from "next/headers";
20+
21+
export default function ServerComponent() {
22+
async function myServerAction(formData: FormData) {
23+
"use server";
24+
return await Sentry.withServerActionInstrumentation(
25+
"myServerAction", // The name you want to associate this Server Action with in Sentry
26+
{
27+
formData, // Optionally pass in the form data
28+
headers: headers(), // Optionally pass in headers
29+
recordResponse: true, // Optionally record the server action response
30+
},
31+
async () => {
32+
// ... Your Server Action code
33+
34+
return { name: "John Doe" };
35+
}
36+
);
37+
}
38+
39+
return (
40+
<form action={myServerAction}>
41+
<input type="text" name="some-input-value" />
42+
<button type="submit">Run Action</button>
43+
</form>
44+
);
45+
}
46+
```
47+
48+
### Other Changes
49+
50+
- docs(feedback): Example docs on `sendFeedback` (#9560)
51+
- feat(feedback): Add `level` and remove breadcrumbs from feedback event (#9533)
52+
- feat(vercel-edge): Add fetch instrumentation (#9504)
53+
- feat(vue): Support Vue 3 lifecycle hooks in mixin options (#9578)
54+
- fix(nextjs): Download CLI binary if it can't be found (#9584)
55+
- ref: Deprecate `extractTraceParentData` from `@sentry/core` & downstream packages (#9158)
56+
- ref(replay): Add further logging to network body parsing (#9566)
57+
58+
Work in this release contributed by @snoozbuster. Thank you for your contribution!
59+
760
## 7.80.1
861

962
- fix(astro): Adjust Vite plugin config to upload server source maps (#9541)

MIGRATION.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ npx @sentry/migr8@latest
88

99
This will let you select which updates to run, and automatically update your code. Make sure to still review all code changes!
1010

11+
## Deprecate `extractTraceParentData` export from `@sentry/core` & downstream packages
12+
13+
Instead, import this directly from `@sentry/utils`.
14+
15+
Generally, in most cases you should probably use `continueTrace` instead, which abstracts this away from you and handles scope propagation for you.
16+
1117
## Deprecate `timestampWithMs` export - #7878
1218

1319
The `timestampWithMs` util is deprecated in favor of using `timestampInSeconds`.

packages/astro/src/index.server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export {
1616
withMonitor,
1717
configureScope,
1818
createTransport,
19+
// eslint-disable-next-line deprecation/deprecation
1920
extractTraceparentData,
2021
getActiveTransaction,
2122
getHubFromCarrier,

packages/browser-integration-tests/fixtures/loader.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
new Promise(function (resolve, reject) {
2+
reject('this is unhandled');
3+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { expect } from '@playwright/test';
2+
3+
import { sentryTest } from '../../../../utils/fixtures';
4+
import { envelopeRequestParser, waitForErrorRequestOnUrl } from '../../../../utils/helpers';
5+
6+
sentryTest('unhandled promise rejection handler works', async ({ getLocalTestUrl, page }) => {
7+
const url = await getLocalTestUrl({ testDir: __dirname });
8+
const req = await waitForErrorRequestOnUrl(page, url);
9+
10+
const eventData = envelopeRequestParser(req);
11+
expect(eventData.exception?.values?.length).toBe(1);
12+
expect(eventData.exception?.values?.[0]?.value).toBe(
13+
'Non-Error promise rejection captured with value: this is unhandled',
14+
);
15+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import * as Sentry from '@sentry/browser';
2+
import { Feedback } from '@sentry-internal/feedback';
3+
4+
window.Sentry = Sentry;
5+
6+
Sentry.init({
7+
dsn: 'https://[email protected]/1337',
8+
integrations: [new Feedback()],
9+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
</head>
6+
<body></body>
7+
</html>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { expect } from '@playwright/test';
2+
3+
import { sentryTest } from '../../../utils/fixtures';
4+
import { envelopeRequestParser, getEnvelopeType } from '../../../utils/helpers';
5+
6+
sentryTest('should capture feedback (@sentry-internal/feedback import)', async ({ getLocalTestPath, page }) => {
7+
if (process.env.PW_BUNDLE) {
8+
sentryTest.skip();
9+
}
10+
11+
const feedbackRequestPromise = page.waitForResponse(res => {
12+
const req = res.request();
13+
14+
const postData = req.postData();
15+
if (!postData) {
16+
return false;
17+
}
18+
19+
try {
20+
return getEnvelopeType(req) === 'feedback';
21+
} catch (err) {
22+
return false;
23+
}
24+
});
25+
26+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
27+
return route.fulfill({
28+
status: 200,
29+
contentType: 'application/json',
30+
body: JSON.stringify({ id: 'test-id' }),
31+
});
32+
});
33+
34+
const url = await getLocalTestPath({ testDir: __dirname });
35+
36+
await page.goto(url);
37+
await page.getByText('Report a Bug').click();
38+
expect(await page.locator(':visible:text-is("Report a Bug")').count()).toEqual(1);
39+
await page.locator('[name="name"]').fill('Jane Doe');
40+
await page.locator('[name="email"]').fill('[email protected]');
41+
await page.locator('[name="message"]').fill('my example feedback');
42+
await page.getByLabel('Send Bug Report').click();
43+
44+
const feedbackEvent = envelopeRequestParser((await feedbackRequestPromise).request());
45+
expect(feedbackEvent).toEqual({
46+
type: 'feedback',
47+
contexts: {
48+
feedback: {
49+
contact_email: '[email protected]',
50+
message: 'my example feedback',
51+
name: 'Jane Doe',
52+
source: 'widget',
53+
url: expect.stringContaining('/dist/index.html'),
54+
},
55+
},
56+
level: 'info',
57+
timestamp: expect.any(Number),
58+
event_id: expect.stringMatching(/\w{32}/),
59+
environment: 'production',
60+
sdk: {
61+
integrations: expect.arrayContaining(['Feedback']),
62+
version: expect.any(String),
63+
name: 'sentry.javascript.browser',
64+
packages: expect.anything(),
65+
},
66+
request: {
67+
url: expect.stringContaining('/dist/index.html'),
68+
headers: {
69+
'User-Agent': expect.stringContaining(''),
70+
},
71+
},
72+
platform: 'javascript',
73+
});
74+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import * as Sentry from '@sentry/browser';
2+
import { Feedback } from '@sentry-internal/feedback';
3+
4+
window.Sentry = Sentry;
5+
6+
Sentry.init({
7+
dsn: 'https://[email protected]/1337',
8+
replaysOnErrorSampleRate: 1.0,
9+
replaysSessionSampleRate: 0,
10+
integrations: [
11+
new Sentry.Replay({
12+
flushMinDelay: 200,
13+
flushMaxDelay: 200,
14+
minReplayDuration: 0,
15+
}),
16+
new Feedback(),
17+
],
18+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
</head>
6+
<body></body>
7+
</html>
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { expect } from '@playwright/test';
2+
3+
import { sentryTest } from '../../../utils/fixtures';
4+
import { envelopeRequestParser, getEnvelopeType } from '../../../utils/helpers';
5+
import { getCustomRecordingEvents, getReplayEvent, waitForReplayRequest } from '../../../utils/replayHelpers';
6+
7+
sentryTest('should capture feedback (@sentry-internal/feedback import)', async ({ getLocalTestPath, page }) => {
8+
if (process.env.PW_BUNDLE) {
9+
sentryTest.skip();
10+
}
11+
12+
const reqPromise0 = waitForReplayRequest(page, 0);
13+
const feedbackRequestPromise = page.waitForResponse(res => {
14+
const req = res.request();
15+
16+
const postData = req.postData();
17+
if (!postData) {
18+
return false;
19+
}
20+
21+
try {
22+
return getEnvelopeType(req) === 'feedback';
23+
} catch (err) {
24+
return false;
25+
}
26+
});
27+
28+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
29+
return route.fulfill({
30+
status: 200,
31+
contentType: 'application/json',
32+
body: JSON.stringify({ id: 'test-id' }),
33+
});
34+
});
35+
36+
const url = await getLocalTestPath({ testDir: __dirname });
37+
38+
await page.goto(url);
39+
await page.getByText('Report a Bug').click();
40+
await page.locator('[name="name"]').fill('Jane Doe');
41+
await page.locator('[name="email"]').fill('[email protected]');
42+
await page.locator('[name="message"]').fill('my example feedback');
43+
await page.getByLabel('Send Bug Report').click();
44+
45+
const [feedbackResp, replayReq] = await Promise.all([feedbackRequestPromise, reqPromise0]);
46+
47+
const feedbackEvent = envelopeRequestParser(feedbackResp.request());
48+
const replayEvent = getReplayEvent(replayReq);
49+
const { breadcrumbs } = getCustomRecordingEvents(replayReq);
50+
51+
expect(breadcrumbs).toEqual(
52+
expect.arrayContaining([
53+
{
54+
category: 'sentry.feedback',
55+
data: { feedbackId: expect.any(String) },
56+
},
57+
]),
58+
);
59+
60+
expect(feedbackEvent).toEqual({
61+
type: 'feedback',
62+
contexts: {
63+
feedback: {
64+
contact_email: '[email protected]',
65+
message: 'my example feedback',
66+
name: 'Jane Doe',
67+
replay_id: replayEvent.event_id,
68+
source: 'widget',
69+
url: expect.stringContaining('/dist/index.html'),
70+
},
71+
},
72+
level: 'info',
73+
timestamp: expect.any(Number),
74+
event_id: expect.stringMatching(/\w{32}/),
75+
environment: 'production',
76+
sdk: {
77+
integrations: expect.arrayContaining(['Feedback']),
78+
version: expect.any(String),
79+
name: 'sentry.javascript.browser',
80+
packages: expect.anything(),
81+
},
82+
request: {
83+
url: expect.stringContaining('/dist/index.html'),
84+
headers: {
85+
'User-Agent': expect.stringContaining(''),
86+
},
87+
},
88+
platform: 'javascript',
89+
});
90+
});
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import {
2+
BrowserClient,
3+
Breadcrumbs,
4+
Dedupe,
5+
FunctionToString,
6+
HttpContext,
7+
InboundFilters,
8+
LinkedErrors,
9+
defaultStackParser,
10+
makeFetchTransport,
11+
Hub,
12+
} from '@sentry/browser';
13+
14+
const integrations = [
15+
new Breadcrumbs(),
16+
new FunctionToString(),
17+
new Dedupe(),
18+
new HttpContext(),
19+
new InboundFilters(),
20+
new LinkedErrors(),
21+
];
22+
23+
const client = new BrowserClient({
24+
dsn: 'https://[email protected]/1337',
25+
release: '0.0.1',
26+
environment: 'local',
27+
sampleRate: 1.0,
28+
tracesSampleRate: 0.0,
29+
transport: makeFetchTransport,
30+
stackParser: defaultStackParser,
31+
integrations,
32+
debug: true,
33+
});
34+
35+
const hub = new Hub(client);
36+
37+
hub.captureException(new Error('test client'));

0 commit comments

Comments
 (0)