diff --git a/src/app/app-init.service.spec.ts b/src/app/app-init.service.spec.ts index c4d54d457f..d2e81ae963 100644 --- a/src/app/app-init.service.spec.ts +++ b/src/app/app-init.service.spec.ts @@ -1,7 +1,8 @@ import { TestBed } from '@angular/core/testing'; import { AppInitService } from './app-init.service'; import { IConfig } from './main/declarations/app-config'; -import { APP_CONFIG } from './main/declarations/dsp-api-tokens'; +import { APP_CONFIG, DspInstrumentationToken } from './main/declarations/dsp-api-tokens'; +import { DspDataDogConfig, DspInstrumentationConfig } from './main/declarations/dsp-instrumentation-config'; describe('TestService', () => { let service: AppInitService; @@ -34,10 +35,34 @@ describe('TestService', () => { } }; + const dspDatadogSpy = new DspDataDogConfig(false, '', '', '', ''); + + const instrumentationConfig: DspInstrumentationConfig = { + environment: 'dev', + dataDog: { + enabled: false, + applicationId: 'app_id', + clientToken: 'client_token', + site: 'site', + service: 'dsp-app' + }, + rollbar: { + enabled: false, + accessToken: 'rollbar_token' + } + }; + beforeEach(() => { TestBed.configureTestingModule({ providers: [ - { provide: APP_CONFIG, useValue: config } + { + provide: APP_CONFIG, + useValue: config + }, + { + provide: DspInstrumentationToken, + useValue: instrumentationConfig + }, ] }); service = TestBed.inject(AppInitService); diff --git a/src/app/app-init.service.ts b/src/app/app-init.service.ts index 271dd52aa6..a1c48baf95 100644 --- a/src/app/app-init.service.ts +++ b/src/app/app-init.service.ts @@ -36,7 +36,7 @@ export class AppInitService { } constructor( - @Inject(APP_CONFIG) private _config: IConfig + @Inject(APP_CONFIG) private _config: IConfig, ) { // check for presence of apiProtocol and apiHost if (typeof this._config.apiProtocol !== 'string' || typeof this._config.apiHost !== 'string') { diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index 76011b4dbe..8737f7696f 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -12,7 +12,8 @@ import { TranslateModule } from '@ngx-translate/core'; import { TestConfig } from 'test.config'; import { AppInitService } from './app-init.service'; import { AppComponent } from './app.component'; -import { DspApiConfigToken, DspApiConnectionToken } from './main/declarations/dsp-api-tokens'; +import { DspApiConfigToken, DspApiConnectionToken, DspInstrumentationToken } from './main/declarations/dsp-api-tokens'; +import { DspDataDogConfig, DspInstrumentationConfig } from './main/declarations/dsp-instrumentation-config'; import { HeaderComponent } from './main/header/header.component'; import { SelectLanguageComponent } from './main/select-language/select-language.component'; import { UserMenuComponent } from './user/user-menu/user-menu.component'; @@ -20,6 +21,7 @@ import { UserMenuComponent } from './user/user-menu/user-menu.component'; describe('AppComponent', () => { beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ declarations: [ AppComponent, diff --git a/src/app/app.component.ts b/src/app/app.component.ts index ffccdf73fe..4dc6332069 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -19,7 +19,6 @@ export class AppComponent implements OnInit { // set the page title this._titleService.setTitle('DaSCH Service Platform'); - } ngOnInit() { diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 66c51e91bc..c39984c330 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -48,6 +48,7 @@ import { LinkifyPipe } from './main/pipes/string-transformation/linkify.pipe'; import { StringifyStringLiteralPipe } from './main/pipes/string-transformation/stringify-string-literal.pipe'; import { TruncatePipe } from './main/pipes/string-transformation/truncate.pipe'; import { SelectLanguageComponent } from './main/select-language/select-language.component'; +import { DatadogRumService } from './main/services/datadog-rum.service'; import { MaterialModule } from './material-module'; import { AddressTemplateComponent } from './project/board/address-template/address-template.component'; import { AttributionTabViewComponent } from './project/board/attribution-tab-view/attribution-tab-view.component'; @@ -345,6 +346,7 @@ export function httpLoaderFactory(httpClient: HttpClient) { ], providers: [ AppInitService, + DatadogRumService, { provide: DspApiConfigToken, useFactory: (appInitService: AppInitService) => appInitService.dspApiConfig, diff --git a/src/app/main/action/login-form/login-form.component.spec.ts b/src/app/main/action/login-form/login-form.component.spec.ts index 505ff2a921..41c9a5375d 100644 --- a/src/app/main/action/login-form/login-form.component.spec.ts +++ b/src/app/main/action/login-form/login-form.component.spec.ts @@ -17,6 +17,7 @@ import { AppInitService } from 'src/app/app-init.service'; import { TestConfig } from 'test.config'; import { DspApiConfigToken, DspApiConnectionToken, DspInstrumentationToken } from '../../declarations/dsp-api-tokens'; import { DspDataDogConfig, DspInstrumentationConfig } from '../../declarations/dsp-instrumentation-config'; +import { DatadogRumService } from '../../services/datadog-rum.service'; import { Session, SessionService } from '../../services/session.service'; import { LoginFormComponent } from './login-form.component'; @@ -57,23 +58,6 @@ describe('LoginFormComponent', () => { beforeEach(waitForAsync(() => { - const dspDatadogSpy = new DspDataDogConfig(false, '', '', '', ''); - - const instrumentationConfig: DspInstrumentationConfig = { - environment: 'dev', - dataDog: { - enabled: false, - applicationId: 'app_id', - clientToken: 'client_token', - site: 'site', - service: 'dsp-app' - }, - rollbar: { - enabled: false, - accessToken: 'rollbar_token' - } - }; - const authEndpointSpyObj = { admin: { usersEndpoint: jasmine.createSpyObj('usersEndpoint', ['getUser']) @@ -83,6 +67,8 @@ describe('LoginFormComponent', () => { } }; + const datadogRumServiceSpy = jasmine.createSpyObj('datadogRumService', ['initializeRum', 'setActiveUser', 'removeActiveUser']); + TestBed.configureTestingModule({ declarations: [ LoginFormComponent, @@ -106,9 +92,9 @@ describe('LoginFormComponent', () => { useValue: authEndpointSpyObj }, { - provide: DspInstrumentationToken, - useValue: instrumentationConfig - }, + provide: DatadogRumService, + useValue: datadogRumServiceSpy + } ] }) .compileComponents(); diff --git a/src/app/main/action/login-form/login-form.component.ts b/src/app/main/action/login-form/login-form.component.ts index cbd73f55e8..1ab8fc2367 100644 --- a/src/app/main/action/login-form/login-form.component.ts +++ b/src/app/main/action/login-form/login-form.component.ts @@ -4,6 +4,7 @@ import { ApiResponseData, ApiResponseError, KnoraApiConnection, LoginResponse, L import { datadogRum, RumFetchResourceEventDomainContext } from '@datadog/browser-rum'; import { DspApiConnectionToken, DspInstrumentationToken } from '../../declarations/dsp-api-tokens'; import { DspInstrumentationConfig } from '../../declarations/dsp-instrumentation-config'; +import { DatadogRumService } from '../../services/datadog-rum.service'; import { NotificationService } from '../../services/notification.service'; import { Session, SessionService } from '../../services/session.service'; @@ -105,7 +106,7 @@ export class LoginFormComponent implements OnInit { constructor( @Inject(DspApiConnectionToken) private _dspApiConnection: KnoraApiConnection, - @Inject(DspInstrumentationToken) private _dspInstrumentationConfig: DspInstrumentationConfig, + private _datadogRumService: DatadogRumService, private _notification: NotificationService, private _sessionService: SessionService, private _fb: FormBuilder @@ -156,29 +157,7 @@ export class LoginFormComponent implements OnInit { this.session = this._sessionService.getSession(); this.loginSuccess.emit(true); this.loading = false; - if (this._dspInstrumentationConfig.dataDog.enabled) { - datadogRum.init({ - applicationId: this._dspInstrumentationConfig.dataDog.applicationId, - clientToken: this._dspInstrumentationConfig.dataDog.clientToken, - site: this._dspInstrumentationConfig.dataDog.site, - service: this._dspInstrumentationConfig.dataDog.service, - env: this._dspInstrumentationConfig.environment, - version: appVersion, - sampleRate: 100, - trackInteractions: true, - beforeSend: (event, context) => { - // collect a RUM resource's response headers - if (event.type === 'resource' && event.resource.type === 'xhr') { - event.context = { ...event.context, responseHeaders: (context as RumFetchResourceEventDomainContext).response.body }; - } - }, - }); - - datadogRum.setUser({ - id: identifier, - identifierType: identifierType - }); - } + this._datadogRumService.setActiveUser(identifier, identifierType); } ); }, @@ -215,6 +194,7 @@ export class LoginFormComponent implements OnInit { (response: ApiResponseData) => { this.logoutSuccess.emit(true); this._sessionService.destroySession(); + this._datadogRumService.removeActiveUser(); this.loading = false; this.buildLoginForm(); this.session = undefined; diff --git a/src/app/main/services/datadog-rum.service.spec.ts b/src/app/main/services/datadog-rum.service.spec.ts new file mode 100644 index 0000000000..6bef06897b --- /dev/null +++ b/src/app/main/services/datadog-rum.service.spec.ts @@ -0,0 +1,39 @@ +import { TestBed } from '@angular/core/testing'; + +import { DatadogRumService } from './datadog-rum.service'; + +describe('DatadogRumService', () => { + let service: DatadogRumService; + const mockdatadogRumService = jasmine.createSpyObj('datadogRumService', ['initializeRum', 'setActiveUser', 'removeActiveUser']); + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + { provide: DatadogRumService, useValue: mockdatadogRumService } + ] + }); + service = TestBed.inject(DatadogRumService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + describe('setActiveUser', () => { + it('should set the active user', () => { + const identifier = 'test'; + const identifierType = 'email'; + mockdatadogRumService.setActiveUser.and.callThrough(); + service.setActiveUser(identifier, identifierType); + expect(service.setActiveUser).toHaveBeenCalledOnceWith(identifier, identifierType); + }); + }); + + describe('removeActiveUser', () => { + it('should remove the active user', () => { + mockdatadogRumService.removeActiveUser.and.callThrough(); + service.removeActiveUser(); + expect(service.removeActiveUser).toHaveBeenCalled(); + }); + }); +}); diff --git a/src/app/main/services/datadog-rum.service.ts b/src/app/main/services/datadog-rum.service.ts new file mode 100644 index 0000000000..a1919555b2 --- /dev/null +++ b/src/app/main/services/datadog-rum.service.ts @@ -0,0 +1,50 @@ +import { Inject, Injectable } from '@angular/core'; +import { datadogRum, RumFetchResourceEventDomainContext } from '@datadog/browser-rum'; +import { DspInstrumentationToken } from '../declarations/dsp-api-tokens'; +import { DspInstrumentationConfig } from '../declarations/dsp-instrumentation-config'; + +const { version: appVersion } = require('../../../../package.json'); + +@Injectable({ + providedIn: 'root' +}) +export class DatadogRumService { + + constructor( + @Inject(DspInstrumentationToken) private _dspInstrumentationConfig: DspInstrumentationConfig + ) { + if (this._dspInstrumentationConfig.dataDog.enabled) { + datadogRum.init({ + applicationId: this._dspInstrumentationConfig.dataDog.applicationId, + clientToken: this._dspInstrumentationConfig.dataDog.clientToken, + site: this._dspInstrumentationConfig.dataDog.site, + service: this._dspInstrumentationConfig.dataDog.service, + env: this._dspInstrumentationConfig.environment, + version: appVersion, + sampleRate: 100, + trackInteractions: true, + beforeSend: (event, context) => { + // collect a RUM resource's response headers + if (event.type === 'resource' && event.resource.type === 'xhr') { + event.context = { ...event.context, responseHeaders: (context as RumFetchResourceEventDomainContext).response.body }; + } + }, + }); + } + } + + setActiveUser(identifier: any, identifierType: 'iri' | 'email' | 'username'): void { + if(datadogRum.getInternalContext().application_id) { + datadogRum.setUser({ + id: identifier, + identifierType: identifierType + }); + } + } + + removeActiveUser(): void { + if(datadogRum.getInternalContext().application_id) { + datadogRum.removeUser(); + } + } +}