Skip to content

Commit d8b6242

Browse files
authored
Merge branch 'develop' into cl/screenshot-integration
2 parents ce0be69 + 11b1a75 commit d8b6242

File tree

121 files changed

+4031
-2564
lines changed

Some content is hidden

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

121 files changed

+4031
-2564
lines changed

.craft.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ targets:
9191
- name: npm
9292
id: '@sentry/serverless'
9393
includeNames: /^sentry-serverless-\d.*\.tgz$/
94+
- name: npm
95+
id: '@sentry/google-cloud'
96+
includeNames: /^sentry-google-cloud-\d.*\.tgz$/
9497
- name: npm
9598
id: '@sentry/bun'
9699
includeNames: /^sentry-bun-\d.*\.tgz$/

.github/ISSUE_TEMPLATE/bug.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ body:
4343
- '@sentry/react'
4444
- '@sentry/remix'
4545
- '@sentry/serverless'
46+
- '@sentry/google-cloud'
4647
- '@sentry/svelte'
4748
- '@sentry/sveltekit'
4849
- '@sentry/vue'

MIGRATION.md

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -546,10 +546,43 @@ The following previously deprecated API has been removed from the `@sentry/nextj
546546
- `IS_BUILD`
547547
- `isBuild`
548548

549+
#### Merging of the Sentry Webpack Plugin options and SDK Build options
550+
551+
With version 8 of the Sentry Next.js SDK, `withSentryConfig` will no longer accept 3 arguments. The second argument
552+
(holding options for the Sentry Webpack plugin) and the third argument (holding options for SDK build-time
553+
configuration) should now be passed as one:
554+
555+
```ts
556+
// OLD
557+
const nextConfig = {
558+
// Your Next.js options...
559+
};
560+
561+
module.exports = withSentryConfig(
562+
nextConfig,
563+
{
564+
// Your Sentry Webpack Plugin Options...
565+
},
566+
{
567+
// Your Sentry SDK options...
568+
},
569+
);
570+
571+
// NEW
572+
const nextConfig = {
573+
// Your Next.js options...
574+
};
575+
576+
module.exports = withSentryConfig(nextConfig, {
577+
// Your Sentry Webpack Plugin Options...
578+
// AND your Sentry SDK options...
579+
});
580+
```
581+
549582
#### Removal of the `sentry` property in your Next.js options (next.config.js)
550583

551584
With version 8 of the Sentry Next.js SDK, the SDK will no longer support passing Next.js options with a `sentry`
552-
property to `withSentryConfig`. Please use the third argument of `withSentryConfig` to configure the SDK instead:
585+
property to `withSentryConfig`. Please use the second argument of `withSentryConfig` to configure the SDK instead:
553586

554587
```ts
555588
// v7
@@ -572,21 +605,25 @@ const nextConfig = {
572605
// Your Next.js options...
573606
};
574607

575-
module.exports = withSentryConfig(
576-
nextConfig,
577-
{
578-
// Your Sentry Webpack Plugin Options...
579-
},
580-
{
581-
// Your Sentry SDK options...
582-
},
583-
);
608+
module.exports = withSentryConfig(nextConfig, {
609+
// Your Sentry Webpack Plugin Options...
610+
// AND your Sentry SDK options...
611+
});
584612
```
585613

586614
The reason for this change is to have one consistent way of defining the SDK options. We hope that this change will
587615
reduce confusion when setting up the SDK, with the upside that the explicit option is properly typed and will therefore
588616
have code completion.
589617

618+
#### Updated the `@sentry/webpack-plugin` dependency to version 2
619+
620+
We bumped the internal usage of `@sentry/webpack-plugin` to a new major version. This comes with multiple upsides like a
621+
simpler configuration interface and the use of new state of the art Debug ID technology. Debug IDs will simplify the
622+
setup for source maps in Sentry and will not require you to match stack frame paths to uploaded artifacts anymore.
623+
624+
To see the new options, check out the docs at https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/,
625+
or look at the TypeScript type definitions of `withSentryConfig`.
626+
590627
### Astro SDK
591628

592629
#### Removal of `trackHeaders` option for Astro middleware
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
setTimeout(() => {
2+
throw new Error('Error during pageload');
3+
}, 100);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { expect } from '@playwright/test';
2+
import type { Event } from '@sentry/types';
3+
import { sentryTest } from '../../../../utils/fixtures';
4+
import { getMultipleSentryEnvelopeRequests, shouldSkipTracingTest } from '../../../../utils/helpers';
5+
6+
sentryTest(
7+
'should put the pageload transaction name onto an error event caught during pageload',
8+
async ({ getLocalTestPath, page }) => {
9+
if (shouldSkipTracingTest()) {
10+
sentryTest.skip();
11+
}
12+
13+
const url = await getLocalTestPath({ testDir: __dirname });
14+
15+
await page.goto(url);
16+
17+
const [e1, e2] = await getMultipleSentryEnvelopeRequests<Event>(page, 2);
18+
19+
const pageloadTxnEvent = e1.type === 'transaction' ? e1 : e2;
20+
const errorEvent = e1.type === 'transaction' ? e2 : e1;
21+
22+
expect(pageloadTxnEvent.contexts?.trace?.op).toEqual('pageload');
23+
expect(pageloadTxnEvent.spans?.length).toBeGreaterThan(0);
24+
expect(errorEvent.exception?.values?.[0]).toBeDefined();
25+
26+
expect(pageloadTxnEvent.transaction?.endsWith('index.html')).toBe(true);
27+
28+
expect(errorEvent.transaction).toEqual(pageloadTxnEvent.transaction);
29+
},
30+
);

dev-packages/e2e-tests/test-applications/angular-17/src/app/app.routes.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import { Routes } from '@angular/router';
2+
import { cancelGuard } from './cancel-guard.guard';
3+
import { CancelComponent } from './cancel/cancel.components';
4+
import { ComponentTrackingComponent } from './component-tracking/component-tracking.components';
25
import { HomeComponent } from './home/home.component';
36
import { UserComponent } from './user/user.component';
47

@@ -11,6 +14,15 @@ export const routes: Routes = [
1114
path: 'home',
1215
component: HomeComponent,
1316
},
17+
{
18+
path: 'cancel',
19+
component: CancelComponent,
20+
canActivate: [cancelGuard],
21+
},
22+
{
23+
path: 'component-tracking',
24+
component: ComponentTrackingComponent,
25+
},
1426
{
1527
path: 'redirect1',
1628
redirectTo: '/redirect2',
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { ActivatedRouteSnapshot, CanActivateFn, RouterStateSnapshot } from '@angular/router';
2+
3+
export const cancelGuard: CanActivateFn = (_next: ActivatedRouteSnapshot, _state: RouterStateSnapshot) => {
4+
return false;
5+
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { Component } from '@angular/core';
2+
3+
@Component({
4+
selector: 'app-cancel',
5+
standalone: true,
6+
template: `<div></div>`,
7+
})
8+
export class CancelComponent {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { AfterViewInit, Component, OnInit } from '@angular/core';
2+
import { TraceClassDecorator, TraceMethodDecorator, TraceModule } from '@sentry/angular-ivy';
3+
import { SampleComponent } from '../sample-component/sample-component.components';
4+
5+
@Component({
6+
selector: 'app-cancel',
7+
standalone: true,
8+
imports: [TraceModule, SampleComponent],
9+
template: `<app-sample-component [trace]="'sample-component'"></app-sample-component>`,
10+
})
11+
@TraceClassDecorator()
12+
export class ComponentTrackingComponent implements OnInit, AfterViewInit {
13+
@TraceMethodDecorator()
14+
ngOnInit() {}
15+
16+
@TraceMethodDecorator()
17+
ngAfterViewInit() {}
18+
}

dev-packages/e2e-tests/test-applications/angular-17/src/app/home/home.component.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import { RouterLink } from '@angular/router';
1111
<ul>
1212
<li> <a id="navLink" [routerLink]="['/users', '123']">Visit User 123</a> </li>
1313
<li> <a id="redirectLink" [routerLink]="['/redirect1']">Redirect</a> </li>
14+
<li> <a id="cancelLink" [routerLink]="['/cancel']">Cancel</a> </li>
15+
<li> <a id="nonExistentLink" [routerLink]="['/non-existent']">Error</a> </li>
16+
<li> <a id="componentTracking" [routerLink]="['/component-tracking']">Error</a> </li>
1417
</ul>
1518
<button id="errorBtn" (click)="throwError()">Throw error</button>
1619
</main>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Component, OnInit } from '@angular/core';
2+
3+
@Component({
4+
selector: 'app-sample-component',
5+
standalone: true,
6+
template: `<div></div>`,
7+
})
8+
export class SampleComponent implements OnInit {
9+
ngOnInit() {
10+
console.log('SampleComponent');
11+
}
12+
}

dev-packages/e2e-tests/test-applications/angular-17/tests/performance.test.ts

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { expect, test } from '@playwright/test';
2+
import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core';
23
import { waitForTransaction } from '../event-proxy-server';
34

45
test('sends a pageload transaction with a parameterized URL', async ({ page }) => {
@@ -126,3 +127,178 @@ test('groups redirects within one navigation root span', async ({ page }) => {
126127
expect(routingSpan).toBeDefined();
127128
expect(routingSpan?.description).toBe('/redirect1');
128129
});
130+
131+
test.describe('finish routing span', () => {
132+
test('finishes routing span on navigation cancel', async ({ page }) => {
133+
const navigationTxnPromise = waitForTransaction('angular-17', async transactionEvent => {
134+
return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
135+
});
136+
137+
await page.goto(`/`);
138+
139+
// immediately navigate to a different route
140+
const [_, navigationTxn] = await Promise.all([page.locator('#cancelLink').click(), navigationTxnPromise]);
141+
142+
expect(navigationTxn).toMatchObject({
143+
contexts: {
144+
trace: {
145+
op: 'navigation',
146+
origin: 'auto.navigation.angular',
147+
},
148+
},
149+
transaction: '/cancel',
150+
transaction_info: {
151+
source: 'url',
152+
},
153+
});
154+
155+
const routingSpan = navigationTxn.spans?.find(span => span.op === 'ui.angular.routing');
156+
157+
expect(routingSpan).toBeDefined();
158+
expect(routingSpan?.description).toBe('/cancel');
159+
});
160+
161+
test('finishes routing span on navigation error', async ({ page }) => {
162+
const navigationTxnPromise = waitForTransaction('angular-17', async transactionEvent => {
163+
return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
164+
});
165+
166+
await page.goto(`/`);
167+
168+
// immediately navigate to a different route
169+
const [_, navigationTxn] = await Promise.all([page.locator('#nonExistentLink').click(), navigationTxnPromise]);
170+
171+
const nonExistentRoute = '/non-existent';
172+
173+
expect(navigationTxn).toMatchObject({
174+
contexts: {
175+
trace: {
176+
op: 'navigation',
177+
origin: 'auto.navigation.angular',
178+
},
179+
},
180+
transaction: nonExistentRoute,
181+
transaction_info: {
182+
source: 'url',
183+
},
184+
});
185+
186+
const routingSpan = navigationTxn.spans?.find(span => span.op === 'ui.angular.routing');
187+
188+
expect(routingSpan).toBeDefined();
189+
expect(routingSpan?.description).toBe(nonExistentRoute);
190+
});
191+
});
192+
193+
test.describe('TraceDirective', () => {
194+
test('creates a child tracingSpan with component name as span name on ngOnInit', async ({ page }) => {
195+
const navigationTxnPromise = waitForTransaction('angular-17', async transactionEvent => {
196+
return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
197+
});
198+
199+
await page.goto(`/`);
200+
201+
// immediately navigate to a different route
202+
const [_, navigationTxn] = await Promise.all([page.locator('#componentTracking').click(), navigationTxnPromise]);
203+
204+
const traceDirectiveSpan = navigationTxn.spans?.find(
205+
span => span?.data && span?.data[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] === 'auto.ui.angular.trace_directive',
206+
);
207+
208+
expect(traceDirectiveSpan).toBeDefined();
209+
expect(traceDirectiveSpan).toEqual(
210+
expect.objectContaining({
211+
data: {
212+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.angular.init',
213+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular.trace_directive',
214+
},
215+
description: '<sample-component>',
216+
op: 'ui.angular.init',
217+
origin: 'auto.ui.angular.trace_directive',
218+
start_timestamp: expect.any(Number),
219+
timestamp: expect.any(Number),
220+
}),
221+
);
222+
});
223+
});
224+
225+
test.describe('TraceClassDecorator', () => {
226+
test('adds init span for decorated class', async ({ page }) => {
227+
const navigationTxnPromise = waitForTransaction('angular-17', async transactionEvent => {
228+
return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
229+
});
230+
231+
await page.goto(`/`);
232+
233+
// immediately navigate to a different route
234+
const [_, navigationTxn] = await Promise.all([page.locator('#componentTracking').click(), navigationTxnPromise]);
235+
236+
const classDecoratorSpan = navigationTxn.spans?.find(
237+
span => span?.data && span?.data[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] === 'auto.ui.angular.trace_class_decorator',
238+
);
239+
240+
expect(classDecoratorSpan).toBeDefined();
241+
expect(classDecoratorSpan).toEqual(
242+
expect.objectContaining({
243+
data: {
244+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.angular.init',
245+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular.trace_class_decorator',
246+
},
247+
// todo: right now, it shows the minified version of the component name - we will add a name input to the Decorator
248+
description: expect.any(String),
249+
op: 'ui.angular.init',
250+
origin: 'auto.ui.angular.trace_class_decorator',
251+
start_timestamp: expect.any(Number),
252+
timestamp: expect.any(Number),
253+
}),
254+
);
255+
});
256+
});
257+
258+
test.describe('TraceMethodDecorator', () => {
259+
test('instruments decorated methods (`ngOnInit` and `ngAfterViewInit`)', async ({ page }) => {
260+
const navigationTxnPromise = waitForTransaction('angular-17', async transactionEvent => {
261+
return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
262+
});
263+
264+
await page.goto(`/`);
265+
266+
// immediately navigate to a different route
267+
const [_, navigationTxn] = await Promise.all([page.locator('#componentTracking').click(), navigationTxnPromise]);
268+
269+
const ngInitSpan = navigationTxn.spans?.find(span => span.op === 'ui.angular.ngOnInit');
270+
const ngAfterViewInitSpan = navigationTxn.spans?.find(span => span.op === 'ui.angular.ngAfterViewInit');
271+
272+
expect(ngInitSpan).toBeDefined();
273+
expect(ngInitSpan).toEqual(
274+
expect.objectContaining({
275+
data: {
276+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.angular.ngOnInit',
277+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular.trace_method_decorator',
278+
},
279+
// todo: right now, it shows the minified version of the component name - we will add a name input to the Decorator
280+
description: expect.any(String),
281+
op: 'ui.angular.ngOnInit',
282+
origin: 'auto.ui.angular.trace_method_decorator',
283+
start_timestamp: expect.any(Number),
284+
timestamp: expect.any(Number),
285+
}),
286+
);
287+
288+
expect(ngAfterViewInitSpan).toBeDefined();
289+
expect(ngAfterViewInitSpan).toEqual(
290+
expect.objectContaining({
291+
data: {
292+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.angular.ngAfterViewInit',
293+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular.trace_method_decorator',
294+
},
295+
// todo: right now, it shows the minified version of the component name - we will add a name input to the Decorator
296+
description: expect.any(String),
297+
op: 'ui.angular.ngAfterViewInit',
298+
origin: 'auto.ui.angular.trace_method_decorator',
299+
start_timestamp: expect.any(Number),
300+
timestamp: expect.any(Number),
301+
}),
302+
);
303+
});
304+
});

0 commit comments

Comments
 (0)