Skip to content

Commit 3693a5d

Browse files
Lms24arturovt
andauthored
fix(v8/angular): Fall back to element tagName when name is not provided to TraceDirective (#14828)
Backport of #14778 --------- Co-authored-by: Artur <[email protected]>
1 parent bc7e87f commit 3693a5d

File tree

7 files changed

+88
-37
lines changed

7 files changed

+88
-37
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

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

13+
Work in this release was contributed by @arturovt. Thank you for your contribution!
14+
1315
## 8.47.0
1416

1517
- feat(v8/core): Add `updateSpanName` helper function (#14736)

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import { SampleComponent } from '../sample-component/sample-component.components
66
selector: 'app-cancel',
77
standalone: true,
88
imports: [TraceModule, SampleComponent],
9-
template: `<app-sample-component [trace]="'sample-component'"></app-sample-component>`,
9+
template: `
10+
<app-sample-component [trace]="'sample-component'"></app-sample-component>
11+
<app-sample-component trace></app-sample-component>
12+
`,
1013
})
1114
@TraceClass({ name: 'ComponentTrackingComponent' })
1215
export class ComponentTrackingComponent implements OnInit, AfterViewInit {

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

+28-15
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ test.describe('finish routing span', () => {
191191
});
192192

193193
test.describe('TraceDirective', () => {
194-
test('creates a child tracingSpan with component name as span name on ngOnInit', async ({ page }) => {
194+
test('creates a child span with the component name as span name on ngOnInit', async ({ page }) => {
195195
const navigationTxnPromise = waitForTransaction('angular-17', async transactionEvent => {
196196
return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
197197
});
@@ -201,23 +201,36 @@ test.describe('TraceDirective', () => {
201201
// immediately navigate to a different route
202202
const [_, navigationTxn] = await Promise.all([page.locator('#componentTracking').click(), navigationTxnPromise]);
203203

204-
const traceDirectiveSpan = navigationTxn.spans?.find(
204+
const traceDirectiveSpans = navigationTxn.spans?.filter(
205205
span => span?.data && span?.data[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] === 'auto.ui.angular.trace_directive',
206206
);
207207

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-
}),
208+
expect(traceDirectiveSpans).toHaveLength(2);
209+
expect(traceDirectiveSpans).toEqual(
210+
expect.arrayContaining([
211+
expect.objectContaining({
212+
data: {
213+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.angular.init',
214+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular.trace_directive',
215+
},
216+
description: '<sample-component>', // custom component name passed to trace directive
217+
op: 'ui.angular.init',
218+
origin: 'auto.ui.angular.trace_directive',
219+
start_timestamp: expect.any(Number),
220+
timestamp: expect.any(Number),
221+
}),
222+
expect.objectContaining({
223+
data: {
224+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.angular.init',
225+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular.trace_directive',
226+
},
227+
description: '<app-sample-component>', // fallback selector name
228+
op: 'ui.angular.init',
229+
origin: 'auto.ui.angular.trace_directive',
230+
start_timestamp: expect.any(Number),
231+
timestamp: expect.any(Number),
232+
}),
233+
]),
221234
);
222235
});
223236
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@sentry:registry=http://127.0.0.1:4873
2+
@sentry-internal:registry=http://127.0.0.1:4873

dev-packages/e2e-tests/test-applications/angular-19/src/app/component-tracking/component-tracking.components.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@ import { TraceClass, TraceMethod, TraceModule } from '@sentry/angular';
33
import { SampleComponent } from '../sample-component/sample-component.components';
44

55
@Component({
6-
selector: 'app-cancel',
6+
selector: 'app-component-tracking',
77
standalone: true,
88
imports: [TraceModule, SampleComponent],
9-
template: `<app-sample-component [trace]="'sample-component'"></app-sample-component>`,
9+
template: `
10+
<app-sample-component trace="sample-component"></app-sample-component>
11+
<app-sample-component trace></app-sample-component>
12+
`,
1013
})
1114
@TraceClass({ name: 'ComponentTrackingComponent' })
1215
export class ComponentTrackingComponent implements OnInit, AfterViewInit {

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

+28-15
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ test.describe('finish routing span', () => {
191191
});
192192

193193
test.describe('TraceDirective', () => {
194-
test('creates a child tracingSpan with component name as span name on ngOnInit', async ({ page }) => {
194+
test('creates a child span with the component name as span name on ngOnInit', async ({ page }) => {
195195
const navigationTxnPromise = waitForTransaction('angular-18', async transactionEvent => {
196196
return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
197197
});
@@ -201,23 +201,36 @@ test.describe('TraceDirective', () => {
201201
// immediately navigate to a different route
202202
const [_, navigationTxn] = await Promise.all([page.locator('#componentTracking').click(), navigationTxnPromise]);
203203

204-
const traceDirectiveSpan = navigationTxn.spans?.find(
204+
const traceDirectiveSpans = navigationTxn.spans?.filter(
205205
span => span?.data && span?.data[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] === 'auto.ui.angular.trace_directive',
206206
);
207207

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-
}),
208+
expect(traceDirectiveSpans).toHaveLength(2);
209+
expect(traceDirectiveSpans).toEqual(
210+
expect.arrayContaining([
211+
expect.objectContaining({
212+
data: {
213+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.angular.init',
214+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular.trace_directive',
215+
},
216+
description: '<sample-component>', // custom component name passed to trace directive
217+
op: 'ui.angular.init',
218+
origin: 'auto.ui.angular.trace_directive',
219+
start_timestamp: expect.any(Number),
220+
timestamp: expect.any(Number),
221+
}),
222+
expect.objectContaining({
223+
data: {
224+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.angular.init',
225+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular.trace_directive',
226+
},
227+
description: '<app-sample-component>', // fallback selector name
228+
op: 'ui.angular.init',
229+
origin: 'auto.ui.angular.trace_directive',
230+
start_timestamp: expect.any(Number),
231+
timestamp: expect.any(Number),
232+
}),
233+
]),
221234
);
222235
});
223236
});

packages/angular/src/tracing.ts

+19-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
2+
import { ElementRef } from '@angular/core';
13
import type { AfterViewInit, OnDestroy, OnInit } from '@angular/core';
24
import { Directive, Injectable, Input, NgModule } from '@angular/core';
35
import type { ActivatedRouteSnapshot, Event, RouterState } from '@angular/router';
@@ -235,24 +237,37 @@ export class TraceService implements OnDestroy {
235237
}
236238
}
237239

238-
const UNKNOWN_COMPONENT = 'unknown';
239-
240240
/**
241-
* A directive that can be used to capture initialization lifecycle of the whole component.
241+
* Captures the initialization lifecycle of the component this directive is applied to.
242+
* Specifically, this directive measures the time between `ngOnInit` and `ngAfterViewInit`
243+
* of the component.
244+
*
245+
* Falls back to the component's selector if no name is provided.
246+
*
247+
* @example
248+
* ```html
249+
* <app-my-component trace="myComponent"></app-my-component>
250+
* ```
242251
*/
243252
@Directive({ selector: '[trace]' })
244253
export class TraceDirective implements OnInit, AfterViewInit {
245254
@Input('trace') public componentName?: string;
246255

247256
private _tracingSpan?: Span;
248257

258+
public constructor(private readonly _host: ElementRef<HTMLElement>) {}
259+
249260
/**
250261
* Implementation of OnInit lifecycle method
251262
* @inheritdoc
252263
*/
253264
public ngOnInit(): void {
254265
if (!this.componentName) {
255-
this.componentName = UNKNOWN_COMPONENT;
266+
// Technically, the `trace` binding should always be provided.
267+
// However, if it is incorrectly declared on the element without a
268+
// value (e.g., `<app-component trace />`), we fall back to using `tagName`
269+
// (which is e.g. `APP-COMPONENT`).
270+
this.componentName = this._host.nativeElement.tagName.toLowerCase();
256271
}
257272

258273
if (getActiveSpan()) {

0 commit comments

Comments
 (0)