Skip to content

Commit 96ec73d

Browse files
committed
feat(angular): Auto-detect component name in TraceDirective
1 parent f68fdaa commit 96ec73d

File tree

2 files changed

+80
-7
lines changed

2 files changed

+80
-7
lines changed

packages/angular/src/tracing.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
/* eslint-disable max-lines */
2-
import { AfterViewInit, Directive, Injectable, Input, NgModule, OnDestroy, OnInit } from '@angular/core';
2+
import {
3+
AfterViewInit,
4+
Directive,
5+
Injectable,
6+
Input,
7+
NgModule,
8+
OnDestroy,
9+
OnInit,
10+
Optional,
11+
ViewContainerRef,
12+
} from '@angular/core';
313
import { ActivatedRouteSnapshot, Event, NavigationEnd, NavigationStart, ResolveEnd, Router } from '@angular/router';
414
import { getCurrentHub, WINDOW } from '@sentry/browser';
515
import { Span, Transaction, TransactionContext } from '@sentry/types';
@@ -163,13 +173,15 @@ export class TraceDirective implements OnInit, AfterViewInit {
163173

164174
private _tracingSpan?: Span;
165175

176+
public constructor(@Optional() private readonly _vcRef: ViewContainerRef) {}
177+
166178
/**
167179
* Implementation of OnInit lifecycle method
168180
* @inheritdoc
169181
*/
170182
public ngOnInit(): void {
171183
if (!this.componentName) {
172-
this.componentName = UNKNOWN_COMPONENT;
184+
this.componentName = detectComponentName(this._vcRef as EnhancedVieContainerRef);
173185
}
174186

175187
const activeTransaction = getActiveTransaction();
@@ -192,6 +204,29 @@ export class TraceDirective implements OnInit, AfterViewInit {
192204
}
193205
}
194206

207+
type EnhancedVieContainerRef = ViewContainerRef & {
208+
_lContainer?: {
209+
localName?: string;
210+
}[][];
211+
};
212+
213+
/**
214+
* Detects the angular component name from the passed ViewContainerReference.
215+
* Specifically, it looks up the selector of the component (e.g. `app-my-component`) or
216+
* falls back to the default component name if the selector is not available.
217+
*/
218+
function detectComponentName(vcRef: EnhancedVieContainerRef | undefined): string {
219+
if (vcRef && vcRef._lContainer && vcRef._lContainer[0] && vcRef._lContainer[0][0]) {
220+
// TODO: is this preferrable?
221+
// Alternative: We could get the class name like so:
222+
// const className = Object.getPrototypeOf(vcRef._lContainer[0][8]).constructor.name;
223+
224+
const selectorName = vcRef._lContainer[0][0].localName;
225+
return selectorName || UNKNOWN_COMPONENT;
226+
}
227+
return UNKNOWN_COMPONENT;
228+
}
229+
195230
/**
196231
* A module serves as a single compilation unit for the `TraceDirective` and can be re-used by any other module.
197232
*/

packages/angular/test/tracing.test.ts

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -256,12 +256,50 @@ describe('Angular Tracing', () => {
256256

257257
describe('TraceDirective', () => {
258258
it('should create an instance', () => {
259-
const directive = new TraceDirective();
259+
const directive = new TraceDirective({} as unknown as any);
260260
expect(directive).toBeTruthy();
261261
});
262262

263+
it('should create a child tracingSpan on init (WIP)', async () => {
264+
const customStartTransaction = jest.fn(defaultStartTransaction);
265+
266+
@Component({
267+
selector: 'app-component',
268+
template: `<app-child trace></app-child>`,
269+
})
270+
class AppComponent {
271+
public constructor() {}
272+
}
273+
274+
@Component({
275+
selector: 'app-child',
276+
template: `<p>Hi</p>`,
277+
})
278+
class ChildComponent {
279+
public constructor() {}
280+
}
281+
282+
const env = await TestEnv.setup({
283+
components: [AppComponent, ChildComponent, TraceDirective],
284+
defaultComponent: AppComponent,
285+
customStartTransaction,
286+
useTraceService: false,
287+
});
288+
289+
transaction.startChild = jest.fn();
290+
291+
//directive.ngOnInit();
292+
293+
expect(transaction.startChild).toHaveBeenCalledWith({
294+
op: 'ui.angular.init',
295+
description: '<unknown>',
296+
});
297+
298+
env.destroy();
299+
});
300+
263301
it('should create a child tracingSpan on init', async () => {
264-
const directive = new TraceDirective();
302+
//const directive = new TraceDirective({} as unknown as any);
265303
const customStartTransaction = jest.fn(defaultStartTransaction);
266304

267305
const env = await TestEnv.setup({
@@ -272,7 +310,7 @@ describe('Angular Tracing', () => {
272310

273311
transaction.startChild = jest.fn();
274312

275-
directive.ngOnInit();
313+
//directive.ngOnInit();
276314

277315
expect(transaction.startChild).toHaveBeenCalledWith({
278316
op: 'ui.angular.init',
@@ -283,7 +321,7 @@ describe('Angular Tracing', () => {
283321
});
284322

285323
it('should use component name as span description', async () => {
286-
const directive = new TraceDirective();
324+
const directive = new TraceDirective({} as unknown as any);
287325
const finishMock = jest.fn();
288326
const customStartTransaction = jest.fn(defaultStartTransaction);
289327

@@ -309,7 +347,7 @@ describe('Angular Tracing', () => {
309347
});
310348

311349
it('should finish tracingSpan after view init', async () => {
312-
const directive = new TraceDirective();
350+
const directive = new TraceDirective({} as unknown as any);
313351
const finishMock = jest.fn();
314352
const customStartTransaction = jest.fn(defaultStartTransaction);
315353

0 commit comments

Comments
 (0)