From ec59ee5b8ac1bd7d95cefab09c91e1383c990a0a Mon Sep 17 00:00:00 2001 From: mdelez <60604010+mdelez@users.noreply.github.com> Date: Thu, 14 Oct 2021 16:08:15 +0200 Subject: [PATCH] feat: datadog RUM implementation (DEV-50) (#546) * feat: datadog RUM implementation * test(login-form): fixes tests * refactor(login-form): adds and uses config token properties for datadog init --- package-lock.json | 105 +++++++++++++++--- package.json | 3 +- src/app/app-init.service.ts | 18 ++- src/app/app.module.ts | 7 +- .../login-form/login-form.component.spec.ts | 16 ++- .../action/login-form/login-form.component.ts | 32 +++++- src/app/main/declarations/dsp-api-tokens.ts | 4 + .../main/declarations/dsp-dataDog-config.ts | 7 ++ src/config/config.dev.json | 27 +++-- src/config/config.prod.json | 7 +- src/config/config.test-server.json | 27 +++-- 11 files changed, 210 insertions(+), 43 deletions(-) create mode 100644 src/app/main/declarations/dsp-dataDog-config.ts diff --git a/package-lock.json b/package-lock.json index 966c2ab236..d2b7d28ec5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@angular/router": "^11.2.9", "@ckeditor/ckeditor5-angular": "^1.2.3", "@dasch-swiss/dsp-js": "^4.1.0", + "@datadog/browser-rum": "^3.6.3", "@ngx-translate/core": "^12.1.2", "@ngx-translate/http-loader": "5.0.0", "3d-force-graph": "^1.60.12", @@ -77,20 +78,6 @@ "typescript": "4.0.7" } }, - ".yalc/@dasch-swiss/dsp-js": { - "version": "4.0.0+714fcf1f", - "extraneous": true, - "license": "AGPL-3.0", - "dependencies": { - "@babel/helper-compilation-targets": "^7.14.5", - "@types/jsonld": "^1.5.0", - "json2typescript": "1.4.1", - "jsonld": "^5.2.0" - }, - "peerDependencies": { - "rxjs": "6.x" - } - }, "node_modules/@angular-devkit/architect": { "version": "0.1102.14", "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1102.14.tgz", @@ -2242,6 +2229,48 @@ "rxjs": "6.x" } }, + "node_modules/@datadog/browser-core": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@datadog/browser-core/-/browser-core-3.6.3.tgz", + "integrity": "sha512-fMT6RAfYPEytj845Gyjh2cSuUgfuTZ+taDLbRde6xnN7PRjQkQxD3vcprFCVwyZhSU4Pbv3uGeTbtaReOfq/vQ==", + "dependencies": { + "tslib": "^1.10.0" + } + }, + "node_modules/@datadog/browser-core/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@datadog/browser-rum": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@datadog/browser-rum/-/browser-rum-3.6.3.tgz", + "integrity": "sha512-fknkux4mJdk5NahVDP3pN/pBY6A+W1s9dK6dFXUacpy9AKiRs08ai0Avk2rNwUV06bWjZLoRsd6NgU/56KADGw==", + "dependencies": { + "@datadog/browser-core": "3.6.3", + "@datadog/browser-rum-core": "3.6.3", + "tslib": "^1.10.0" + } + }, + "node_modules/@datadog/browser-rum-core": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@datadog/browser-rum-core/-/browser-rum-core-3.6.3.tgz", + "integrity": "sha512-hNGJ2inecSlaNxF7mBfy59U6VXceKv80dH7PlRrLT4PHHl9V1AiIAWF+Gfig5S8+CzwmmBFJ6BoxO+DiUoiSHA==", + "dependencies": { + "@datadog/browser-core": "3.6.3", + "tslib": "^1.10.0" + } + }, + "node_modules/@datadog/browser-rum-core/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@datadog/browser-rum/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/@digitalbazaar/http-client": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@digitalbazaar/http-client/-/http-client-1.2.0.tgz", @@ -21942,6 +21971,54 @@ "jsonld": "^5.2.0" } }, + "@datadog/browser-core": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@datadog/browser-core/-/browser-core-3.6.3.tgz", + "integrity": "sha512-fMT6RAfYPEytj845Gyjh2cSuUgfuTZ+taDLbRde6xnN7PRjQkQxD3vcprFCVwyZhSU4Pbv3uGeTbtaReOfq/vQ==", + "requires": { + "tslib": "^1.10.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@datadog/browser-rum": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@datadog/browser-rum/-/browser-rum-3.6.3.tgz", + "integrity": "sha512-fknkux4mJdk5NahVDP3pN/pBY6A+W1s9dK6dFXUacpy9AKiRs08ai0Avk2rNwUV06bWjZLoRsd6NgU/56KADGw==", + "requires": { + "@datadog/browser-core": "3.6.3", + "@datadog/browser-rum-core": "3.6.3", + "tslib": "^1.10.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@datadog/browser-rum-core": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@datadog/browser-rum-core/-/browser-rum-core-3.6.3.tgz", + "integrity": "sha512-hNGJ2inecSlaNxF7mBfy59U6VXceKv80dH7PlRrLT4PHHl9V1AiIAWF+Gfig5S8+CzwmmBFJ6BoxO+DiUoiSHA==", + "requires": { + "@datadog/browser-core": "3.6.3", + "tslib": "^1.10.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, "@digitalbazaar/http-client": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@digitalbazaar/http-client/-/http-client-1.2.0.tgz", diff --git a/package.json b/package.json index f3c4ddb4df..ccf5248a2a 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "test-local": "ng test", "test-e2e-protractor": "ng e2e --prod=true --protractor-config=./e2e/protractor-ci.conf.js --webdriver-update=false", "webdriver-update": "webdriver-manager update --standalone false --gecko false --versions.chrome 2.37", - "yalc-add-lib": "rm -rf .yalc/@dasch-swiss && yalc add @dasch-swiss/dsp-js && npm install" + "yalc-add-lib": "rm -rf .yalc/@dasch-swiss && yalc add @dasch-swiss/dsp-js && yalc add @dasch-swiss/dsp-ui && npm install" }, "private": true, "dependencies": { @@ -34,6 +34,7 @@ "@angular/router": "^11.2.9", "@ckeditor/ckeditor5-angular": "^1.2.3", "@dasch-swiss/dsp-js": "^4.1.0", + "@datadog/browser-rum": "^3.6.3", "@ngx-translate/core": "^12.1.2", "@ngx-translate/http-loader": "5.0.0", "3d-force-graph": "^1.60.12", diff --git a/src/app/app-init.service.ts b/src/app/app-init.service.ts index 221b370735..0280bb289d 100644 --- a/src/app/app-init.service.ts +++ b/src/app/app-init.service.ts @@ -1,5 +1,6 @@ import { Injectable } from '@angular/core'; import { KnoraApiConfig } from '@dasch-swiss/dsp-js'; +import { DspDataDogConfig } from './main/declarations/dsp-dataDog-config'; import { DspIiifConfig } from './main/declarations/dsp-iiif-config'; @Injectable({ @@ -9,6 +10,7 @@ export class AppInitService { dspApiConfig: KnoraApiConfig; dspIiifConfig: DspIiifConfig; + dspDatadogConfig: DspDataDogConfig; config: object; @@ -29,7 +31,7 @@ export class AppInitService { // check for presence of apiProtocol and apiHost - if (typeof dspAppConfig.apiProtocol !== 'string' || typeof dspAppConfig.apiHost !== 'string') { + if (typeof dspAppConfig.apiProtocol !== 'string' || typeof dspAppConfig.apiHost !== 'string') { throw new Error('config misses required members: apiProtocol and/or apiHost'); } @@ -60,6 +62,15 @@ export class AppInitService { iiifPath ); + // init datadog configuration + this.dspDatadogConfig = new DspDataDogConfig( + dspAppConfig.dataDogLogging, + dspAppConfig.dataDogApplicationId, + dspAppConfig.dataDogClientToken, + dspAppConfig.dataDogSite, + dspAppConfig.dataDogService, + ); + // get all options from config this.config = dspAppConfig; @@ -75,6 +86,11 @@ export class AppInitService { this.config['iiifPort'] = iiifPort; this.config['iiifPath'] = iiifPath; this.config['iiifUrl'] = this.dspIiifConfig.iiifUrl; + this.config['dataDogLogging'] = this.dspDatadogConfig.dataDogLogging; + this.config['dataDogApplicationId'] = this.dspDatadogConfig.dataDogApplicationId; + this.config['dataDogClientToken'] = this.dspDatadogConfig.dataDogClientToken; + this.config['dataDogSite'] = this.dspDatadogConfig.dataDogSite; + this.config['dataDogService'] = this.dspDatadogConfig.dataDogService; resolve(); } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 42a66fa3e1..2c78e1c777 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -26,7 +26,7 @@ import { SelectedResourcesComponent } from './main/action/selected-resources/sel import { SortButtonComponent } from './main/action/sort-button/sort-button.component'; import { StringLiteralInputComponent } from './main/action/string-literal-input/string-literal-input.component'; import { CookiePolicyComponent } from './main/cookie-policy/cookie-policy.component'; -import { DspApiConfigToken, DspApiConnectionToken } from './main/declarations/dsp-api-tokens'; +import { DspApiConfigToken, DspApiConnectionToken, DspDataDogConfigToken } from './main/declarations/dsp-api-tokens'; import { DialogHeaderComponent } from './main/dialog/dialog-header/dialog-header.component'; import { DialogComponent } from './main/dialog/dialog.component'; import { AdminImageDirective } from './main/directive/admin-image/admin-image.directive'; @@ -348,6 +348,11 @@ export function httpLoaderFactory(httpClient: HttpClient) { useFactory: (appInitService: AppInitService) => appInitService.dspApiConfig, deps: [AppInitService] }, + { + provide: DspDataDogConfigToken, + useFactory: (appInitService: AppInitService) => appInitService.dspDatadogConfig, + deps: [AppInitService] + }, { provide: DspApiConnectionToken, useFactory: (appInitService: AppInitService) => new KnoraApiConnection(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 b32d8c7b6e..35544fca69 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 @@ -7,6 +7,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { ApiResponseData, AuthenticationEndpointV2, + KnoraApiConfig, LoginResponse, LogoutResponse, MockUsers, @@ -14,7 +15,8 @@ import { } from '@dasch-swiss/dsp-js'; import { of } from 'rxjs'; import { AjaxResponse } from 'rxjs/ajax'; -import { DspApiConnectionToken } from '../../declarations/dsp-api-tokens'; +import { DspApiConfigToken, DspApiConnectionToken, DspDataDogConfigToken } from '../../declarations/dsp-api-tokens'; +import { DspDataDogConfig } from '../../declarations/dsp-dataDog-config'; import { Session, SessionService } from '../../services/session.service'; import { LoginFormComponent } from './login-form.component'; @@ -54,6 +56,10 @@ describe('LoginFormComponent', () => { let sessionService: SessionService; beforeEach(waitForAsync(() => { + const dspConfSpy = new KnoraApiConfig('http', 'localhost', 3333, undefined, undefined, true); + + const dspDatadogSpy = new DspDataDogConfig(false, '', '', '', ''); + const dspConnSpy = { admin: { usersEndpoint: jasmine.createSpyObj('usersEndpoint', ['getUser']) @@ -74,6 +80,14 @@ describe('LoginFormComponent', () => { provide: DspApiConnectionToken, useValue: dspConnSpy }, + { + provide: DspApiConfigToken, + useValue: dspConfSpy + }, + { + provide: DspDataDogConfigToken, + useValue: dspDatadogSpy + }, FormBuilder, SessionService ], 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 0976402b26..d2848cdbea 100644 --- a/src/app/main/action/login-form/login-form.component.ts +++ b/src/app/main/action/login-form/login-form.component.ts @@ -1,7 +1,10 @@ import { Component, EventEmitter, Inject, Input, OnInit, Output } from '@angular/core'; +import { inject } from '@angular/core/testing'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { ApiResponseData, ApiResponseError, KnoraApiConnection, LoginResponse, LogoutResponse } from '@dasch-swiss/dsp-js'; -import { DspApiConnectionToken } from '../../declarations/dsp-api-tokens'; +import { ApiResponseData, ApiResponseError, KnoraApiConfig, KnoraApiConnection, LoginResponse, LogoutResponse } from '@dasch-swiss/dsp-js'; +import { datadogRum, RumFetchResourceEventDomainContext } from '@datadog/browser-rum'; +import { DspApiConfigToken, DspApiConnectionToken, DspDataDogConfigToken } from '../../declarations/dsp-api-tokens'; +import { DspDataDogConfig } from '../../declarations/dsp-dataDog-config'; import { NotificationService } from '../../services/notification.service'; import { Session, SessionService } from '../../services/session.service'; @@ -101,6 +104,8 @@ export class LoginFormComponent implements OnInit { constructor( @Inject(DspApiConnectionToken) private _dspApiConnection: KnoraApiConnection, + @Inject(DspApiConfigToken) private _dspApiConfig: KnoraApiConfig, + @Inject(DspDataDogConfigToken) private _dspDataDogConfig: DspDataDogConfig, private _notification: NotificationService, private _sessionService: SessionService, private _fb: FormBuilder @@ -151,6 +156,29 @@ export class LoginFormComponent implements OnInit { this.session = this._sessionService.getSession(); this.loginSuccess.emit(true); this.loading = false; + if (this._dspDataDogConfig.dataDogLogging) { + datadogRum.init({ + applicationId: this._dspDataDogConfig.dataDogApplicationId, + clientToken: this._dspDataDogConfig.dataDogClientToken, + site: this._dspDataDogConfig.dataDogSite, + service: this._dspDataDogConfig.dataDogService, + // specify a version number to identify the deployed version of your application in Datadog + // version: '1.0.0', + 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 + }); + } } ); }, diff --git a/src/app/main/declarations/dsp-api-tokens.ts b/src/app/main/declarations/dsp-api-tokens.ts index dd898feb82..710b4eeacf 100644 --- a/src/app/main/declarations/dsp-api-tokens.ts +++ b/src/app/main/declarations/dsp-api-tokens.ts @@ -1,8 +1,12 @@ import { InjectionToken } from '@angular/core'; import { KnoraApiConfig, KnoraApiConnection } from '@dasch-swiss/dsp-js'; +import { DspDataDogConfig } from './dsp-dataDog-config'; // config for dsp-js-lib (@dasch-swiss/dsp-js) config object export const DspApiConfigToken = new InjectionToken('DSP api configuration'); // connection config for dsp-js-lib (@dasch-swiss/dsp-js) connection export const DspApiConnectionToken = new InjectionToken('DSP api connection instance'); + +// config for datadog +export const DspDataDogConfigToken = new InjectionToken('DSP DataDog configuration'); diff --git a/src/app/main/declarations/dsp-dataDog-config.ts b/src/app/main/declarations/dsp-dataDog-config.ts new file mode 100644 index 0000000000..d8aa9ab99e --- /dev/null +++ b/src/app/main/declarations/dsp-dataDog-config.ts @@ -0,0 +1,7 @@ +export class DspDataDogConfig { + constructor(public dataDogLogging: boolean, + public dataDogApplicationId: string, + public dataDogClientToken: string, + public dataDogSite: string, + public dataDogService: string) { } +} diff --git a/src/config/config.dev.json b/src/config/config.dev.json index 510606c81f..237096b0e9 100644 --- a/src/config/config.dev.json +++ b/src/config/config.dev.json @@ -1,13 +1,18 @@ { - "apiProtocol": "http", - "apiHost": "0.0.0.0", - "apiPort": 3333, - "apiPath": "", - "iiifProtocol": "http", - "iiifHost": "0.0.0.0", - "iiifPort": 1024, - "iiifPath": "", - "geonameToken": "knora", - "jsonWebToken": "", - "logErrors": true + "apiProtocol": "http", + "apiHost": "0.0.0.0", + "apiPort": 3333, + "apiPath": "", + "iiifProtocol": "http", + "iiifHost": "0.0.0.0", + "iiifPort": 1024, + "iiifPath": "", + "geonameToken": "knora", + "jsonWebToken": "", + "logErrors": true, + "dataDogLogging": false, + "dataDogApplicationId": "", + "dataDogClientToken": "", + "dataDogSite": "", + "dataDogService": "" } diff --git a/src/config/config.prod.json b/src/config/config.prod.json index f6b3f86d90..753d83724b 100644 --- a/src/config/config.prod.json +++ b/src/config/config.prod.json @@ -9,5 +9,10 @@ "iiifPath": "", "geonameToken": "knora", "jsonWebToken": "", - "logErrors": false + "logErrors": false, + "dataDogLogging": false, + "dataDogApplicationId": "", + "dataDogClientToken": "", + "dataDogSite": "", + "dataDogService": "" } diff --git a/src/config/config.test-server.json b/src/config/config.test-server.json index a211a98595..4b6d1319f0 100644 --- a/src/config/config.test-server.json +++ b/src/config/config.test-server.json @@ -1,13 +1,18 @@ { - "apiProtocol": "https", - "apiHost": "api.test.dasch.swiss", - "apiPort": 443, - "apiPath": "", - "iiifProtocol": "https", - "iiifHost": "iiif.test.dasch.swiss", - "iiifPort": 443, - "iiifPath": "", - "geonameToken": "knora", - "jsonWebToken": "", - "logErrors": true + "apiProtocol": "https", + "apiHost": "api.test.dasch.swiss", + "apiPort": 443, + "apiPath": "", + "iiifProtocol": "https", + "iiifHost": "iiif.test.dasch.swiss", + "iiifPort": 443, + "iiifPath": "", + "geonameToken": "knora", + "jsonWebToken": "", + "logErrors": true, + "dataDogLogging": false, + "dataDogApplicationId": "", + "dataDogClientToken": "", + "dataDogSite": "", + "dataDogService": "" }