From e3c1cdeaca865274a3a1dad961ee5fb4d4aa2bd6 Mon Sep 17 00:00:00 2001 From: mdelez <60604010+mdelez@users.noreply.github.com> Date: Mon, 27 Sep 2021 11:56:59 +0200 Subject: [PATCH 01/18] feat(error logging): rollbar implementation --- package-lock.json | 213 +++++++++++++++++- package.json | 1 + src/app/app.module.ts | 7 +- .../action/login-form/login-form.component.ts | 3 + src/app/main/error/error-handler.service.ts | 5 + src/app/rollbar.ts | 29 +++ 6 files changed, 245 insertions(+), 13 deletions(-) create mode 100644 src/app/rollbar.ts diff --git a/package-lock.json b/package-lock.json index 4a19d3b96d..9477252746 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,6 +38,7 @@ "ngx-color-picker": "^11.0.0", "openseadragon": "^2.4.0", "pdfjs-dist": "^2.7.570", + "rollbar": "^2.24.0", "rxjs": "~6.5.5", "semver": "^6.1.1", "three": "^0.125.0", @@ -5285,6 +5286,7 @@ "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-9.0.0.tgz", "integrity": "sha512-ctjwuntPfZZT2mNj2NDIVu51t9cvbhl/16epc5xEwyzyDt76pX9UgwvY+MbXrf/C/FWwdtmNtfP698BKI+9leQ==", "dev": true, + "peer": true, "peerDependencies": { "tslib": "^1.10.0" } @@ -5294,6 +5296,7 @@ "resolved": "https://registry.npmjs.org/@angular/core/-/core-9.0.0.tgz", "integrity": "sha512-6Pxgsrf0qF9iFFqmIcWmjJGkkCaCm6V5QNnxMy2KloO3SDq6QuMVRbN9RtC8Urmo25LP+eZ6ZgYqFYpdD8Hd9w==", "dev": true, + "peer": true, "peerDependencies": { "rxjs": "^6.5.3", "tslib": "^1.10.0", @@ -5550,6 +5553,11 @@ "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "dev": true }, + "node_modules/console-polyfill": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/console-polyfill/-/console-polyfill-0.3.0.tgz", + "integrity": "sha512-w+JSDZS7XML43Xnwo2x5O5vxB0ID7T5BdqDtyqT6uiCAX2kZAgcWxNaGqT97tZfSHzfOcvrfsDAodKcJ3UvnXQ==" + }, "node_modules/constants-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", @@ -6681,6 +6689,15 @@ } } }, + "node_modules/decache": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/decache/-/decache-3.1.0.tgz", + "integrity": "sha1-T1A2+9ZYH8yXI3rDlUokS5U2wto=", + "optional": true, + "dependencies": { + "find": "^0.2.4" + } + }, "node_modules/decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", @@ -7269,6 +7286,14 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/error-stack-parser": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz", + "integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==", + "dependencies": { + "stackframe": "^1.1.1" + } + }, "node_modules/es-abstract": { "version": "1.18.6", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.6.tgz", @@ -8370,6 +8395,15 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, + "node_modules/find": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/find/-/find-0.2.9.tgz", + "integrity": "sha1-S3Px/55WrZG3bnFkB/5f/mVUu4w=", + "optional": true, + "dependencies": { + "traverse-chain": "~0.1.0" + } + }, "node_modules/find-cache-dir": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", @@ -9939,6 +9973,11 @@ "node": ">= 0.10" } }, + "node_modules/is_js": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/is_js/-/is_js-0.9.0.tgz", + "integrity": "sha1-CrlFQFArp6+iTIVqqYVWFmnpxS0=" + }, "node_modules/is-absolute-url": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", @@ -10720,8 +10759,7 @@ "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "node_modules/json2typescript": { "version": "1.4.1", @@ -15097,6 +15135,14 @@ "node": ">= 6" } }, + "node_modules/request-ip": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/request-ip/-/request-ip-2.0.2.tgz", + "integrity": "sha1-3urm1K8hdoSX24zQX6NxQ/jxJX4=", + "dependencies": { + "is_js": "^0.9.0" + } + }, "node_modules/request/node_modules/qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", @@ -15326,6 +15372,51 @@ "inherits": "^2.0.1" } }, + "node_modules/rollbar": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/rollbar/-/rollbar-2.24.0.tgz", + "integrity": "sha512-cjAGDeTOUH5Bc4qzXlrp2D3eTO51adMWMxzOrr079t/nZidrO8ISBFM8SnazJVv5fOjIX5VbB/4a+gwbn9l4rw==", + "dependencies": { + "async": "~1.2.1", + "console-polyfill": "0.3.0", + "error-stack-parser": "^2.0.4", + "json-stringify-safe": "~5.0.0", + "lru-cache": "~2.2.1", + "request-ip": "~2.0.1", + "source-map": "^0.5.7", + "uuid": "3.0.x" + }, + "optionalDependencies": { + "decache": "^3.0.5" + } + }, + "node_modules/rollbar/node_modules/async": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/async/-/async-1.2.1.tgz", + "integrity": "sha1-pIFqF81f9RbfosdpikUzabl5DeA=" + }, + "node_modules/rollbar/node_modules/lru-cache": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.2.4.tgz", + "integrity": "sha1-bGWGGb7PFAMdDQtZSxYELOTcBj0=" + }, + "node_modules/rollbar/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollbar/node_modules/uuid": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz", + "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } + }, "node_modules/rollup": { "version": "2.38.4", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.38.4.tgz", @@ -16635,6 +16726,11 @@ "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", "dev": true }, + "node_modules/stackframe": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz", + "integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==" + }, "node_modules/static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -17554,6 +17650,12 @@ "node": ">=0.8" } }, + "node_modules/traverse-chain": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/traverse-chain/-/traverse-chain-0.1.0.tgz", + "integrity": "sha1-YdvC1Ttp/2CRoSoWj9fUMxB+QPE=", + "optional": true + }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -24342,8 +24444,6 @@ "integrity": "sha512-v3+E0Ucu2xWJMOJ2fA/q9pDT/hlxHftHGPUay1/1cTgyPV5JTHFdO9hqo837Sx2s9vKBMTt5gO+lhF95PO6J+g==", "dev": true, "requires": { - "@angular/compiler": "9.0.0", - "@angular/core": "9.0.0", "app-root-path": "^3.0.0", "aria-query": "^3.0.0", "axobject-query": "2.0.2", @@ -24359,17 +24459,17 @@ }, "dependencies": { "@angular/compiler": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-9.0.0.tgz", + "version": "https://registry.npmjs.org/@angular/compiler/-/compiler-9.0.0.tgz", "integrity": "sha512-ctjwuntPfZZT2mNj2NDIVu51t9cvbhl/16epc5xEwyzyDt76pX9UgwvY+MbXrf/C/FWwdtmNtfP698BKI+9leQ==", "dev": true, + "peer": true, "requires": {} }, "@angular/core": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-9.0.0.tgz", + "version": "https://registry.npmjs.org/@angular/core/-/core-9.0.0.tgz", "integrity": "sha512-6Pxgsrf0qF9iFFqmIcWmjJGkkCaCm6V5QNnxMy2KloO3SDq6QuMVRbN9RtC8Urmo25LP+eZ6ZgYqFYpdD8Hd9w==", "dev": true, + "peer": true, "requires": {} }, "source-map": { @@ -24596,6 +24696,11 @@ "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "dev": true }, + "console-polyfill": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/console-polyfill/-/console-polyfill-0.3.0.tgz", + "integrity": "sha512-w+JSDZS7XML43Xnwo2x5O5vxB0ID7T5BdqDtyqT6uiCAX2kZAgcWxNaGqT97tZfSHzfOcvrfsDAodKcJ3UvnXQ==" + }, "constants-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", @@ -25552,6 +25657,15 @@ "ms": "2.1.2" } }, + "decache": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/decache/-/decache-3.1.0.tgz", + "integrity": "sha1-T1A2+9ZYH8yXI3rDlUokS5U2wto=", + "optional": true, + "requires": { + "find": "^0.2.4" + } + }, "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", @@ -26053,6 +26167,14 @@ "is-arrayish": "^0.2.1" } }, + "error-stack-parser": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz", + "integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==", + "requires": { + "stackframe": "^1.1.1" + } + }, "es-abstract": { "version": "1.18.6", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.6.tgz", @@ -26938,6 +27060,15 @@ } } }, + "find": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/find/-/find-0.2.9.tgz", + "integrity": "sha1-S3Px/55WrZG3bnFkB/5f/mVUu4w=", + "optional": true, + "requires": { + "traverse-chain": "~0.1.0" + } + }, "find-cache-dir": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", @@ -28176,6 +28307,11 @@ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "dev": true }, + "is_js": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/is_js/-/is_js-0.9.0.tgz", + "integrity": "sha1-CrlFQFArp6+iTIVqqYVWFmnpxS0=" + }, "is-absolute-url": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", @@ -28750,8 +28886,7 @@ "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "json2typescript": { "version": "1.4.1", @@ -29875,7 +30010,6 @@ "resolved": "https://registry.npmjs.org/ng2-pdf-viewer/-/ng2-pdf-viewer-7.0.1.tgz", "integrity": "sha512-kjjsvHd5t1Ff7ydb3Far3d6cSyw/XJH5KXgcp/0bFzSFBAV2c5aOghxoY/yQVjgG+R6F16nVUh2UrZdngLXLSg==", "requires": { - "pdfjs-dist": "~2.7.570", "tslib": "^2.0.0" } }, @@ -32135,6 +32269,14 @@ } } }, + "request-ip": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/request-ip/-/request-ip-2.0.2.tgz", + "integrity": "sha1-3urm1K8hdoSX24zQX6NxQ/jxJX4=", + "requires": { + "is_js": "^0.9.0" + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -32286,6 +32428,44 @@ "inherits": "^2.0.1" } }, + "rollbar": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/rollbar/-/rollbar-2.24.0.tgz", + "integrity": "sha512-cjAGDeTOUH5Bc4qzXlrp2D3eTO51adMWMxzOrr079t/nZidrO8ISBFM8SnazJVv5fOjIX5VbB/4a+gwbn9l4rw==", + "requires": { + "async": "~1.2.1", + "console-polyfill": "0.3.0", + "decache": "^3.0.5", + "error-stack-parser": "^2.0.4", + "json-stringify-safe": "~5.0.0", + "lru-cache": "~2.2.1", + "request-ip": "~2.0.1", + "source-map": "^0.5.7", + "uuid": "3.0.x" + }, + "dependencies": { + "async": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/async/-/async-1.2.1.tgz", + "integrity": "sha1-pIFqF81f9RbfosdpikUzabl5DeA=" + }, + "lru-cache": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.2.4.tgz", + "integrity": "sha1-bGWGGb7PFAMdDQtZSxYELOTcBj0=" + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, + "uuid": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz", + "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=" + } + } + }, "rollup": { "version": "2.38.4", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.38.4.tgz", @@ -33341,6 +33521,11 @@ "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", "dev": true }, + "stackframe": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz", + "integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==" + }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -34071,6 +34256,12 @@ "punycode": "^2.1.1" } }, + "traverse-chain": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/traverse-chain/-/traverse-chain-0.1.0.tgz", + "integrity": "sha1-YdvC1Ttp/2CRoSoWj9fUMxB+QPE=", + "optional": true + }, "tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", diff --git a/package.json b/package.json index 02fdd65fa7..62c40516e4 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "ngx-color-picker": "^11.0.0", "openseadragon": "^2.4.0", "pdfjs-dist": "^2.7.570", + "rollbar": "^2.24.0", "rxjs": "~6.5.5", "semver": "^6.1.1", "three": "^0.125.0", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 8a8f854d99..1d3d12076d 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -2,7 +2,7 @@ import { ClipboardModule } from '@angular/cdk/clipboard'; import { CommonModule } from '@angular/common'; import { HttpClient, HttpClientModule } from '@angular/common/http'; -import { APP_INITIALIZER, NgModule } from '@angular/core'; +import { APP_INITIALIZER, ErrorHandler, Inject, Injectable, InjectionToken, NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; @@ -79,6 +79,7 @@ import { AddGroupComponent } from './project/permission/add-group/add-group.comp import { PermissionComponent } from './project/permission/permission.component'; import { ProjectFormComponent } from './project/project-form/project-form.component'; import { ProjectComponent } from './project/project.component'; +import { RollbarErrorHandler, RollbarService, rollbarFactory } from './rollbar'; import { GroupsListComponent } from './system/groups/groups-list/groups-list.component'; import { GroupsComponent } from './system/groups/groups.component'; import { ProjectsListComponent } from './system/projects/projects-list/projects-list.component'; @@ -350,7 +351,9 @@ export function httpLoaderFactory(httpClient: HttpClient) { provide: DspApiConnectionToken, useFactory: (appInitService: AppInitService) => new KnoraApiConnection(appInitService.dspApiConfig), deps: [AppInitService] - } + }, + { provide: ErrorHandler, useClass: RollbarErrorHandler }, + { provide: RollbarService, useFactory: rollbarFactory } ], bootstrap: [AppComponent] }) 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..d75bb8e826 100644 --- a/src/app/main/action/login-form/login-form.component.ts +++ b/src/app/main/action/login-form/login-form.component.ts @@ -167,6 +167,9 @@ export class LoginFormComponent implements OnInit { this.isError = true; this.loading = false; + + // log error to Rollbar (done automatically by simply throwing a new Error) + throw new Error('login failed'); } ); } diff --git a/src/app/main/error/error-handler.service.ts b/src/app/main/error/error-handler.service.ts index 7b94467328..3b68c76af0 100644 --- a/src/app/main/error/error-handler.service.ts +++ b/src/app/main/error/error-handler.service.ts @@ -45,6 +45,8 @@ export class ErrorHandlerService { dialogConfig ); + throw new Error('dsp-api not responding'); + } else if (error.status === 401 && typeof(error.error) !== 'string') { // logout if error status is a 401 error and comes from a DSP-JS request this._dspApiConnection.v2.auth.logout().subscribe( @@ -58,6 +60,7 @@ export class ErrorHandlerService { }, (logoutError: ApiResponseError) => { this._notification.openSnackBar(logoutError); + throw new Error(logoutError.error['message']); } ); @@ -65,6 +68,8 @@ export class ErrorHandlerService { // in any other case // open snack bar from dsp-ui notification service this._notification.openSnackBar(error); + // log error to Rollbar (done automatically by simply throwing a new Error) + throw new Error(error.error['message']); } } } diff --git a/src/app/rollbar.ts b/src/app/rollbar.ts new file mode 100644 index 0000000000..edd616e184 --- /dev/null +++ b/src/app/rollbar.ts @@ -0,0 +1,29 @@ +import * as Rollbar from 'rollbar'; + +import { + Injectable, + Inject, + InjectionToken, + ErrorHandler +} from '@angular/core'; + +const rollbarConfig = { + accessToken: '86fd4ff29d3148999d80bdd724ce7704', + captureUncaught: true, + captureUnhandledRejections: true, +}; + +export const RollbarService = new InjectionToken('rollbar'); + +@Injectable() +export class RollbarErrorHandler implements ErrorHandler { + constructor(@Inject(RollbarService) private rollbar: Rollbar) {} + + handleError(err: any): void { + this.rollbar.error(err.originalError || err); + } +} + +export function rollbarFactory() { + return new Rollbar(rollbarConfig); +} From 65f19733977742b13b110e7e62811e730e8785f9 Mon Sep 17 00:00:00 2001 From: Ivan Subotic <400790+subotic@users.noreply.github.com> Date: Fri, 22 Oct 2021 14:24:53 +0200 Subject: [PATCH 02/18] feat(error logging): add rollbar --- src/app/app-init.service.ts | 143 +++++++++--------- src/app/app.module.ts | 6 +- .../login-form/login-form.component.spec.ts | 6 +- .../action/login-form/login-form.component.ts | 22 +-- src/app/main/declarations/dsp-api-tokens.ts | 4 +- .../main/declarations/dsp-dataDog-config.ts | 7 - .../dsp-instrumentation-config.ts | 23 +++ src/app/rollbar.ts | 8 +- src/config/config.dev.json | 18 ++- src/config/config.prod.json | 18 ++- src/config/config.test-server.json | 18 ++- 11 files changed, 161 insertions(+), 112 deletions(-) delete mode 100644 src/app/main/declarations/dsp-dataDog-config.ts create mode 100644 src/app/main/declarations/dsp-instrumentation-config.ts diff --git a/src/app/app-init.service.ts b/src/app/app-init.service.ts index 0280bb289d..5413a46616 100644 --- a/src/app/app-init.service.ts +++ b/src/app/app-init.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { KnoraApiConfig } from '@dasch-swiss/dsp-js'; -import { DspDataDogConfig } from './main/declarations/dsp-dataDog-config'; +import { DspInstrumentationConfig, DspRollbarConfig, DspDataDogConfig } from './main/declarations/dsp-instrumentation-config'; import { DspIiifConfig } from './main/declarations/dsp-iiif-config'; @Injectable({ @@ -10,7 +10,7 @@ export class AppInitService { dspApiConfig: KnoraApiConfig; dspIiifConfig: DspIiifConfig; - dspDatadogConfig: DspDataDogConfig; + dspInstrumentationConfig: DspInstrumentationConfig; config: object; @@ -27,76 +27,77 @@ export class AppInitService { return new Promise((resolve, reject) => { fetch(`${path}/config.${env.name}.json`).then( - (response: Response) => response.json()).then(dspAppConfig => { - - - // check for presence of apiProtocol and apiHost - if (typeof dspAppConfig.apiProtocol !== 'string' || typeof dspAppConfig.apiHost !== 'string') { - throw new Error('config misses required members: apiProtocol and/or apiHost'); + (response: Response) => response.json()).then(jsonConfig => { + + + // check for presence of apiProtocol and apiHost + if (typeof jsonConfig.apiProtocol !== 'string' || typeof jsonConfig.apiHost !== 'string') { + throw new Error('config misses required members: apiProtocol and/or apiHost'); + } + + // make input type safe + const apiPort = (typeof jsonConfig.apiPort === 'number' ? jsonConfig.apiPort : null); + const apiPath = (typeof jsonConfig.apiPath === 'string' ? jsonConfig.apiPath : ''); + const jsonWebToken = (typeof jsonConfig.jsonWebToken === 'string' ? jsonConfig.jsonWebToken : ''); + const logErrors = (typeof jsonConfig.logErrors === 'boolean' ? jsonConfig.logErrors : false); + + // init dsp-api configuration + this.dspApiConfig = new KnoraApiConfig( + jsonConfig.apiProtocol, + jsonConfig.apiHost, + apiPort, + apiPath, + jsonWebToken, + logErrors + ); + + const iiifPort = (typeof jsonConfig.iiifPort === 'number' ? jsonConfig.iiifPort : null); + const iiifPath = (typeof jsonConfig.iiifPath === 'string' ? jsonConfig.iiifPath : ''); + + // init iiif configuration + this.dspIiifConfig = new DspIiifConfig( + jsonConfig.iiifProtocol, + jsonConfig.iiifHost, + iiifPort, + iiifPath + ); + + // init datadog configuration + this.dspInstrumentationConfig = new DspInstrumentationConfig( + jsonConfig.environment, + new DspDataDogConfig( + jsonConfig.dataDog.active, + jsonConfig.dataDog.applicationId, + jsonConfig.dataDog.clientToken, + jsonConfig.dataDog.site, + jsonConfig.dataDog.service, + ), + new DspRollbarConfig( + jsonConfig.rollbar.active + ) + ); + + // get all options from config + this.config = jsonConfig; + + // set sanitized standard config options + this.config['apiProtocol'] = jsonConfig.apiProtocol; + this.config['apiHost'] = jsonConfig.apiHost; + this.config['apiPort'] = apiPort; + this.config['apiPath'] = apiPath; + this.config['jsonWebToken'] = jsonWebToken; + this.config['logErrors'] = logErrors; + this.config['iiifProtocol'] = jsonConfig.iiifProtocol; + this.config['iiifHost'] = jsonConfig.iiifHost; + this.config['iiifPort'] = iiifPort; + this.config['iiifPath'] = iiifPath; + this.config['iiifUrl'] = this.dspIiifConfig.iiifUrl; + + resolve(); } - - // make input type safe - const apiPort = (typeof dspAppConfig.apiPort === 'number' ? dspAppConfig.apiPort : null); - const apiPath = (typeof dspAppConfig.apiPath === 'string' ? dspAppConfig.apiPath : ''); - const jsonWebToken = (typeof dspAppConfig.jsonWebToken === 'string' ? dspAppConfig.jsonWebToken : ''); - const logErrors = (typeof dspAppConfig.logErrors === 'boolean' ? dspAppConfig.logErrors : false); - - // init dsp-api configuration - this.dspApiConfig = new KnoraApiConfig( - dspAppConfig.apiProtocol, - dspAppConfig.apiHost, - apiPort, - apiPath, - jsonWebToken, - logErrors - ); - - const iiifPort = (typeof dspAppConfig.iiifPort === 'number' ? dspAppConfig.iiifPort : null); - const iiifPath = (typeof dspAppConfig.iiifPath === 'string' ? dspAppConfig.iiifPath : ''); - - // init iiif configuration - this.dspIiifConfig = new DspIiifConfig( - dspAppConfig.iiifProtocol, - dspAppConfig.iiifHost, - iiifPort, - 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; - - // set sanitized standard config options - this.config['apiProtocol'] = dspAppConfig.apiProtocol; - this.config['apiHost'] = dspAppConfig.apiHost; - this.config['apiPort'] = apiPort; - this.config['apiPath'] = apiPath; - this.config['jsonWebToken'] = jsonWebToken; - this.config['logErrors'] = logErrors; - this.config['iiifProtocol'] = dspAppConfig.iiifProtocol; - this.config['iiifHost'] = dspAppConfig.iiifHost; - 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(); - } - ).catch((err) => { - reject(err); - }); + ).catch((err) => { + reject(err); + }); }); } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index dfcede22a7..91e5630285 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -27,7 +27,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, DspDataDogConfigToken } from './main/declarations/dsp-api-tokens'; +import { DspApiConfigToken, DspApiConnectionToken, DspInstrumentationToken } 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'; @@ -358,8 +358,8 @@ export function httpLoaderFactory(httpClient: HttpClient) { deps: [AppInitService] }, { - provide: DspDataDogConfigToken, - useFactory: (appInitService: AppInitService) => appInitService.dspDatadogConfig, + provide: DspInstrumentationToken, + useFactory: (appInitService: AppInitService) => appInitService.dspInstrumentationConfig, deps: [AppInitService] }, { 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 35544fca69..604106bfea 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 @@ -15,8 +15,8 @@ import { } from '@dasch-swiss/dsp-js'; import { of } from 'rxjs'; import { AjaxResponse } from 'rxjs/ajax'; -import { DspApiConfigToken, DspApiConnectionToken, DspDataDogConfigToken } from '../../declarations/dsp-api-tokens'; -import { DspDataDogConfig } from '../../declarations/dsp-dataDog-config'; +import { DspApiConfigToken, DspApiConnectionToken, DspInstrumentationToken } from '../../declarations/dsp-api-tokens'; +import { DspDataDogConfig } from '../../declarations/dsp-instrumentation-config'; import { Session, SessionService } from '../../services/session.service'; import { LoginFormComponent } from './login-form.component'; @@ -85,7 +85,7 @@ describe('LoginFormComponent', () => { useValue: dspConfSpy }, { - provide: DspDataDogConfigToken, + provide: DspInstrumentationToken, useValue: dspDatadogSpy }, FormBuilder, 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 67d7c9d824..ba94700975 100644 --- a/src/app/main/action/login-form/login-form.component.ts +++ b/src/app/main/action/login-form/login-form.component.ts @@ -3,11 +3,13 @@ import { inject } from '@angular/core/testing'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; 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 { DspApiConfigToken, DspApiConnectionToken, DspInstrumentationToken } from '../../declarations/dsp-api-tokens'; +import { DspInstrumentationConfig } from '../../declarations/dsp-instrumentation-config'; import { NotificationService } from '../../services/notification.service'; import { Session, SessionService } from '../../services/session.service'; +const { version: appVersion } = require('../../../../package.json'); + @Component({ selector: 'app-login-form', templateUrl: './login-form.component.html', @@ -105,7 +107,7 @@ export class LoginFormComponent implements OnInit { constructor( @Inject(DspApiConnectionToken) private _dspApiConnection: KnoraApiConnection, @Inject(DspApiConfigToken) private _dspApiConfig: KnoraApiConfig, - @Inject(DspDataDogConfigToken) private _dspDataDogConfig: DspDataDogConfig, + @Inject(DspInstrumentationToken) private _dspInstrumentationConfig: DspInstrumentationConfig, private _notification: NotificationService, private _sessionService: SessionService, private _fb: FormBuilder @@ -156,14 +158,14 @@ export class LoginFormComponent implements OnInit { this.session = this._sessionService.getSession(); this.loginSuccess.emit(true); this.loading = false; - if (this._dspDataDogConfig.dataDogLogging) { + if (this._dspInstrumentationConfig.dataDog.active) { 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', + 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) => { diff --git a/src/app/main/declarations/dsp-api-tokens.ts b/src/app/main/declarations/dsp-api-tokens.ts index 710b4eeacf..d99cd3de8e 100644 --- a/src/app/main/declarations/dsp-api-tokens.ts +++ b/src/app/main/declarations/dsp-api-tokens.ts @@ -1,6 +1,6 @@ import { InjectionToken } from '@angular/core'; import { KnoraApiConfig, KnoraApiConnection } from '@dasch-swiss/dsp-js'; -import { DspDataDogConfig } from './dsp-dataDog-config'; +import { DspInstrumentationConfig } from './dsp-instrumentation-config'; // config for dsp-js-lib (@dasch-swiss/dsp-js) config object export const DspApiConfigToken = new InjectionToken('DSP api configuration'); @@ -9,4 +9,4 @@ export const DspApiConfigToken = new InjectionToken('DSP api con export const DspApiConnectionToken = new InjectionToken('DSP api connection instance'); // config for datadog -export const DspDataDogConfigToken = new InjectionToken('DSP DataDog configuration'); +export const DspInstrumentationToken = new InjectionToken('DSP instrumentation configuration'); diff --git a/src/app/main/declarations/dsp-dataDog-config.ts b/src/app/main/declarations/dsp-dataDog-config.ts deleted file mode 100644 index d8aa9ab99e..0000000000 --- a/src/app/main/declarations/dsp-dataDog-config.ts +++ /dev/null @@ -1,7 +0,0 @@ -export class DspDataDogConfig { - constructor(public dataDogLogging: boolean, - public dataDogApplicationId: string, - public dataDogClientToken: string, - public dataDogSite: string, - public dataDogService: string) { } -} diff --git a/src/app/main/declarations/dsp-instrumentation-config.ts b/src/app/main/declarations/dsp-instrumentation-config.ts new file mode 100644 index 0000000000..44018caff1 --- /dev/null +++ b/src/app/main/declarations/dsp-instrumentation-config.ts @@ -0,0 +1,23 @@ +export class DspInstrumentationConfig { + constructor( + public environment: string, + public dataDog: DspDataDogConfig, + public rollbar: DspRollbarConfig + ) { } +} + +export class DspDataDogConfig { + constructor( + public active: boolean, + public applicationId: string, + public clientToken: string, + public site: string, + public service: string + ) { } +} + +export class DspRollbarConfig { + constructor( + public active: boolean + ) { } +} diff --git a/src/app/rollbar.ts b/src/app/rollbar.ts index edd616e184..0a3745ecc1 100644 --- a/src/app/rollbar.ts +++ b/src/app/rollbar.ts @@ -7,6 +7,9 @@ import { ErrorHandler } from '@angular/core'; +import { DspInstrumentationToken } from './main/declarations/dsp-api-tokens'; +import { DspInstrumentationConfig } from './main/declarations/dsp-instrumentation-config'; + const rollbarConfig = { accessToken: '86fd4ff29d3148999d80bdd724ce7704', captureUncaught: true, @@ -17,7 +20,10 @@ export const RollbarService = new InjectionToken('rollbar'); @Injectable() export class RollbarErrorHandler implements ErrorHandler { - constructor(@Inject(RollbarService) private rollbar: Rollbar) {} + constructor( + @Inject(DspInstrumentationToken) private _dspInstrumentationConfig: DspInstrumentationConfig, + @Inject(RollbarService) private rollbar: Rollbar + ) { } handleError(err: any): void { this.rollbar.error(err.originalError || err); diff --git a/src/config/config.dev.json b/src/config/config.dev.json index 237096b0e9..beb5588860 100644 --- a/src/config/config.dev.json +++ b/src/config/config.dev.json @@ -10,9 +10,17 @@ "geonameToken": "knora", "jsonWebToken": "", "logErrors": true, - "dataDogLogging": false, - "dataDogApplicationId": "", - "dataDogClientToken": "", - "dataDogSite": "", - "dataDogService": "" + "instrumentation": { + "environment": "dev", + "dataDog": { + "active": false, + "applicationId": "", + "clientToken": "", + "site": "", + "service": "" + }, + "rollbar": { + "active": false + } + } } diff --git a/src/config/config.prod.json b/src/config/config.prod.json index 753d83724b..6429be9a10 100644 --- a/src/config/config.prod.json +++ b/src/config/config.prod.json @@ -10,9 +10,17 @@ "geonameToken": "knora", "jsonWebToken": "", "logErrors": false, - "dataDogLogging": false, - "dataDogApplicationId": "", - "dataDogClientToken": "", - "dataDogSite": "", - "dataDogService": "" + "instrumentation": { + "environment": "prod", + "dataDog": { + "active": false, + "applicationId": "", + "clientToken": "", + "site": "", + "service": "" + }, + "rollbar": { + "active": false + } + } } diff --git a/src/config/config.test-server.json b/src/config/config.test-server.json index 4b6d1319f0..8b4d75442e 100644 --- a/src/config/config.test-server.json +++ b/src/config/config.test-server.json @@ -10,9 +10,17 @@ "geonameToken": "knora", "jsonWebToken": "", "logErrors": true, - "dataDogLogging": false, - "dataDogApplicationId": "", - "dataDogClientToken": "", - "dataDogSite": "", - "dataDogService": "" + "instrumentation": { + "environment": "test", + "dataDog": { + "active": false, + "applicationId": "", + "clientToken": "", + "site": "", + "service": "" + }, + "rollbar": { + "active": false + } + } } From 2102b479c1d178c25f9ed9282a992a0f0205bdfd Mon Sep 17 00:00:00 2001 From: Ivan Subotic <400790+subotic@users.noreply.github.com> Date: Sat, 23 Oct 2021 12:01:43 +0200 Subject: [PATCH 03/18] feat(error logging): add rollbar --- .../declarations/dsp-instrumentation-config.ts | 3 ++- src/app/rollbar.ts | 14 +++++++------- src/config/config.dev.json | 3 ++- src/config/config.prod.json | 3 ++- src/config/config.test-server.json | 3 ++- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/app/main/declarations/dsp-instrumentation-config.ts b/src/app/main/declarations/dsp-instrumentation-config.ts index 44018caff1..c831bc6b08 100644 --- a/src/app/main/declarations/dsp-instrumentation-config.ts +++ b/src/app/main/declarations/dsp-instrumentation-config.ts @@ -18,6 +18,7 @@ export class DspDataDogConfig { export class DspRollbarConfig { constructor( - public active: boolean + public active: boolean, + public accessToken: string ) { } } diff --git a/src/app/rollbar.ts b/src/app/rollbar.ts index 0a3745ecc1..94554bb9dd 100644 --- a/src/app/rollbar.ts +++ b/src/app/rollbar.ts @@ -10,12 +10,6 @@ import { import { DspInstrumentationToken } from './main/declarations/dsp-api-tokens'; import { DspInstrumentationConfig } from './main/declarations/dsp-instrumentation-config'; -const rollbarConfig = { - accessToken: '86fd4ff29d3148999d80bdd724ce7704', - captureUncaught: true, - captureUnhandledRejections: true, -}; - export const RollbarService = new InjectionToken('rollbar'); @Injectable() @@ -31,5 +25,11 @@ export class RollbarErrorHandler implements ErrorHandler { } export function rollbarFactory() { - return new Rollbar(rollbarConfig); + return new Rollbar( + { + accessToken: this._dspInstrumentationConfig.rollbar.accessToken, // '86fd4ff29d3148999d80bdd724ce7704', + captureUncaught: true, + captureUnhandledRejections: true + } + ); } diff --git a/src/config/config.dev.json b/src/config/config.dev.json index beb5588860..e0a02ba40b 100644 --- a/src/config/config.dev.json +++ b/src/config/config.dev.json @@ -20,7 +20,8 @@ "service": "" }, "rollbar": { - "active": false + "active": false, + "accessToken": "" } } } diff --git a/src/config/config.prod.json b/src/config/config.prod.json index 6429be9a10..03d8c4dc37 100644 --- a/src/config/config.prod.json +++ b/src/config/config.prod.json @@ -20,7 +20,8 @@ "service": "" }, "rollbar": { - "active": false + "active": false, + "accessToken": "" } } } diff --git a/src/config/config.test-server.json b/src/config/config.test-server.json index 8b4d75442e..8781845576 100644 --- a/src/config/config.test-server.json +++ b/src/config/config.test-server.json @@ -20,7 +20,8 @@ "service": "" }, "rollbar": { - "active": false + "active": false, + "accessToken": "" } } } From 9b45dbdec72dbabe7619d7c4df76147f1d96e52d Mon Sep 17 00:00:00 2001 From: Ivan Subotic <400790+subotic@users.noreply.github.com> Date: Sun, 24 Oct 2021 18:05:05 +0200 Subject: [PATCH 04/18] feat(error logging): add rollbar --- angular.json | 3 + src/app/app-init.service.spec.ts | 80 +++++++++++++------ src/app/app-init.service.ts | 43 ++++------ src/app/app.module.ts | 40 ++++++++-- .../action/login-form/login-form.component.ts | 5 +- src/app/main/declarations/dsp-api-tokens.ts | 6 +- src/app/main/declarations/dsp-app-config.ts | 5 ++ .../dsp-instrumentation-config.ts | 4 +- src/app/rollbar.ts | 42 ++++++++-- .../upload/upload-file.service.spec.ts | 4 +- .../upload/upload-file.service.ts | 3 +- .../resource/services/geoname.service.spec.ts | 3 +- .../resource/services/geoname.service.ts | 72 ++++++++--------- src/config/config.dev.json | 6 +- src/config/config.prod.json | 4 +- src/config/config.test-server.json | 4 +- 16 files changed, 205 insertions(+), 119 deletions(-) create mode 100644 src/app/main/declarations/dsp-app-config.ts diff --git a/angular.json b/angular.json index b98b5c599e..4dc0832846 100644 --- a/angular.json +++ b/angular.json @@ -1,5 +1,8 @@ { "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "cli": { + "analytics": false + }, "version": 1, "newProjectRoot": "projects", "projects": { diff --git a/src/app/app-init.service.spec.ts b/src/app/app-init.service.spec.ts index abbf7404e3..39b2f65487 100644 --- a/src/app/app-init.service.spec.ts +++ b/src/app/app-init.service.spec.ts @@ -22,7 +22,23 @@ describe('TestService', () => { apiPort: 3333, apiPath: 'mypath', jsonWebToken: 'mytoken', - logErrors: true + logErrors: true, + geonameToken: "geoname_token", + instrumentation: { + environment: 'dev', + dataDog: { + enabled: true, + applicationId: 'app_id', + clientToken: 'client_token', + site: 'site', + service: 'dsp-app' + }, + rollbar: { + enabled: true, + accessToken: 'rollbar_token' + } + } + }))) ); @@ -34,13 +50,15 @@ describe('TestService', () => { expect(service.dspApiConfig.apiPath).toEqual('mypath'); expect(service.dspApiConfig.jsonWebToken).toEqual('mytoken'); expect(service.dspApiConfig.logErrors).toEqual(true); - - expect(service.config['apiProtocol']).toEqual('http'); - expect(service.config['apiHost']).toEqual('0.0.0.0'); - expect(service.config['apiPort']).toEqual(3333); - expect(service.config['apiPath']).toEqual('mypath'); - expect(service.config['jsonWebToken']).toEqual('mytoken'); - expect(service.config['logErrors']).toEqual(true); + expect(service.dspAppConfig.geonameToken).toEqual('geoname_token'); + expect(service.dspInstrumentationConfig.environment).toEqual('dev'); + expect(service.dspInstrumentationConfig.dataDog.enabled).toEqual(true); + expect(service.dspInstrumentationConfig.dataDog.applicationId).toEqual('app_id'); + expect(service.dspInstrumentationConfig.dataDog.clientToken).toEqual('client_token'); + expect(service.dspInstrumentationConfig.dataDog.site).toEqual('site'); + expect(service.dspInstrumentationConfig.dataDog.service).toEqual('dsp-app'); + expect(service.dspInstrumentationConfig.rollbar.enabled).toEqual(true); + expect(service.dspInstrumentationConfig.rollbar.accessToken).toEqual('rollbar_token'); expect(fetchSpy).toHaveBeenCalledTimes(1); expect(fetchSpy).toHaveBeenCalledWith('config/config.prod.json'); @@ -52,7 +70,21 @@ describe('TestService', () => { const fetchSpy = spyOn(window, 'fetch').and.callFake( path => Promise.resolve(new Response(JSON.stringify({ apiProtocol: 'http', - apiHost: '0.0.0.0' + apiHost: '0.0.0.0', + instrumentation: { + environment: 'dev', + dataDog: { + enabled: true, + applicationId: 'app_id', + clientToken: 'client_token', + site: 'site', + service: 'dsp-app' + }, + rollbar: { + enabled: true, + accessToken: 'rollbar_token' + } + } }))) ); @@ -65,13 +97,6 @@ describe('TestService', () => { expect(service.dspApiConfig.jsonWebToken).toEqual(''); expect(service.dspApiConfig.logErrors).toEqual(false); - expect(service.config['apiProtocol']).toEqual('http'); - expect(service.config['apiHost']).toEqual('0.0.0.0'); - expect(service.config['apiPort']).toEqual(null); - expect(service.config['apiPath']).toEqual(''); - expect(service.config['jsonWebToken']).toEqual(''); - expect(service.config['logErrors']).toEqual(false); - expect(fetchSpy).toHaveBeenCalledTimes(1); expect(fetchSpy).toHaveBeenCalledWith('config/config.prod.json'); @@ -83,7 +108,20 @@ describe('TestService', () => { path => Promise.resolve(new Response(JSON.stringify({ apiProtocol: 'http', apiHost: '0.0.0.0', - myOption: true + instrumentation: { + environment: 'dev', + dataDog: { + enabled: true, + applicationId: 'app_id', + clientToken: 'client_token', + site: 'site', + service: 'dsp-app' + }, + rollbar: { + enabled: true, + accessToken: 'rollbar_token' + } + } }))) ); @@ -96,14 +134,6 @@ describe('TestService', () => { expect(service.dspApiConfig.jsonWebToken).toEqual(''); expect(service.dspApiConfig.logErrors).toEqual(false); - expect(service.config['apiProtocol']).toEqual('http'); - expect(service.config['apiHost']).toEqual('0.0.0.0'); - expect(service.config['apiPort']).toEqual(null); - expect(service.config['apiPath']).toEqual(''); - expect(service.config['jsonWebToken']).toEqual(''); - expect(service.config['logErrors']).toEqual(false); - expect(service.config['myOption']).toEqual(true); - expect(fetchSpy).toHaveBeenCalledTimes(1); expect(fetchSpy).toHaveBeenCalledWith('config/config.prod.json'); diff --git a/src/app/app-init.service.ts b/src/app/app-init.service.ts index 5413a46616..45516c3a7d 100644 --- a/src/app/app-init.service.ts +++ b/src/app/app-init.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@angular/core'; import { KnoraApiConfig } from '@dasch-swiss/dsp-js'; import { DspInstrumentationConfig, DspRollbarConfig, DspDataDogConfig } from './main/declarations/dsp-instrumentation-config'; import { DspIiifConfig } from './main/declarations/dsp-iiif-config'; +import { DspAppConfig } from './main/declarations/dsp-app-config'; @Injectable({ providedIn: 'root' @@ -10,10 +11,9 @@ export class AppInitService { dspApiConfig: KnoraApiConfig; dspIiifConfig: DspIiifConfig; + dspAppConfig: DspAppConfig; dspInstrumentationConfig: DspInstrumentationConfig; - config: object; - constructor() { } @@ -29,7 +29,6 @@ export class AppInitService { fetch(`${path}/config.${env.name}.json`).then( (response: Response) => response.json()).then(jsonConfig => { - // check for presence of apiProtocol and apiHost if (typeof jsonConfig.apiProtocol !== 'string' || typeof jsonConfig.apiHost !== 'string') { throw new Error('config misses required members: apiProtocol and/or apiHost'); @@ -62,37 +61,27 @@ export class AppInitService { iiifPath ); - // init datadog configuration + // init dsp app extended configuration + this.dspAppConfig = new DspAppConfig( + jsonConfig.geonameToken + ) + + // init instrumentation configuration this.dspInstrumentationConfig = new DspInstrumentationConfig( - jsonConfig.environment, + jsonConfig.instrumentation.environment, new DspDataDogConfig( - jsonConfig.dataDog.active, - jsonConfig.dataDog.applicationId, - jsonConfig.dataDog.clientToken, - jsonConfig.dataDog.site, - jsonConfig.dataDog.service, + jsonConfig.instrumentation.dataDog.enabled, + jsonConfig.instrumentation.dataDog.applicationId, + jsonConfig.instrumentation.dataDog.clientToken, + jsonConfig.instrumentation.dataDog.site, + jsonConfig.instrumentation.dataDog.service, ), new DspRollbarConfig( - jsonConfig.rollbar.active + jsonConfig.instrumentation.rollbar.enabled, + jsonConfig.instrumentation.rollbar.accessToken ) ); - // get all options from config - this.config = jsonConfig; - - // set sanitized standard config options - this.config['apiProtocol'] = jsonConfig.apiProtocol; - this.config['apiHost'] = jsonConfig.apiHost; - this.config['apiPort'] = apiPort; - this.config['apiPath'] = apiPath; - this.config['jsonWebToken'] = jsonWebToken; - this.config['logErrors'] = logErrors; - this.config['iiifProtocol'] = jsonConfig.iiifProtocol; - this.config['iiifHost'] = jsonConfig.iiifHost; - this.config['iiifPort'] = iiifPort; - this.config['iiifPath'] = iiifPath; - this.config['iiifUrl'] = this.dspIiifConfig.iiifUrl; - resolve(); } ).catch((err) => { diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 91e5630285..b0f10601c9 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -27,7 +27,8 @@ 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, DspInstrumentationToken } from './main/declarations/dsp-api-tokens'; +import { DspApiConfigToken, DspApiConnectionToken, DspAppConfigToken, DspInstrumentationToken } from './main/declarations/dsp-api-tokens'; +import { DspAppConfig } from './main/declarations/dsp-app-config'; 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'; @@ -166,7 +167,7 @@ import { SearchSelectOntologyComponent } from './workspace/search/advanced-searc import { ExpertSearchComponent } from './workspace/search/expert-search/expert-search.component'; import { FulltextSearchComponent } from './workspace/search/fulltext-search/fulltext-search.component'; import { SearchPanelComponent } from './workspace/search/search-panel/search-panel.component'; - +import * as Rollbar from 'rollbar'; // translate: AoT requires an exported function for factories export function httpLoaderFactory(httpClient: HttpClient) { return new TranslateHttpLoader(httpClient, 'assets/i18n/', '.json'); @@ -357,18 +358,45 @@ export function httpLoaderFactory(httpClient: HttpClient) { useFactory: (appInitService: AppInitService) => appInitService.dspApiConfig, deps: [AppInitService] }, + { + provide: DspApiConnectionToken, + useFactory: (appInitService: AppInitService) => new KnoraApiConnection(appInitService.dspApiConfig), + deps: [AppInitService] + }, + { + provide: DspAppConfigToken, + useFactory: (appInitService: AppInitService) => appInitService.dspAppConfig, + deps: [AppInitService] + }, { provide: DspInstrumentationToken, useFactory: (appInitService: AppInitService) => appInitService.dspInstrumentationConfig, deps: [AppInitService] }, { - provide: DspApiConnectionToken, - useFactory: (appInitService: AppInitService) => new KnoraApiConnection(appInitService.dspApiConfig), + provide: RollbarService, + useFactory: (appInitService: AppInitService): Rollbar => new Rollbar( + { + accessToken: appInitService.dspInstrumentationConfig.rollbar.accessToken, + enabled: appInitService.dspInstrumentationConfig.rollbar.enabled, + captureUncaught: true, + captureUnhandledRejections: true, + nodeSourceMaps: false, + inspectAnonymousErrors: true, + ignoreDuplicateErrors: true, + wrapGlobalEventHandlers: false, + scrubRequestBody: true, + exitOnUncaughtException: false, + stackTraceLimit: 20 + }), deps: [AppInitService] }, - { provide: ErrorHandler, useClass: RollbarErrorHandler }, - { provide: RollbarService, useFactory: rollbarFactory } + { + provide: ErrorHandler, + useClass: RollbarErrorHandler, + deps: [RollbarService, DspInstrumentationToken] + } + ], bootstrap: [AppComponent] }) 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 ba94700975..b2910a7cab 100644 --- a/src/app/main/action/login-form/login-form.component.ts +++ b/src/app/main/action/login-form/login-form.component.ts @@ -8,7 +8,7 @@ import { DspInstrumentationConfig } from '../../declarations/dsp-instrumentation import { NotificationService } from '../../services/notification.service'; import { Session, SessionService } from '../../services/session.service'; -const { version: appVersion } = require('../../../../package.json'); +const { version: appVersion } = require('../../../../../package.json'); @Component({ selector: 'app-login-form', @@ -106,7 +106,6 @@ export class LoginFormComponent implements OnInit { constructor( @Inject(DspApiConnectionToken) private _dspApiConnection: KnoraApiConnection, - @Inject(DspApiConfigToken) private _dspApiConfig: KnoraApiConfig, @Inject(DspInstrumentationToken) private _dspInstrumentationConfig: DspInstrumentationConfig, private _notification: NotificationService, private _sessionService: SessionService, @@ -158,7 +157,7 @@ export class LoginFormComponent implements OnInit { this.session = this._sessionService.getSession(); this.loginSuccess.emit(true); this.loading = false; - if (this._dspInstrumentationConfig.dataDog.active) { + if (this._dspInstrumentationConfig.dataDog.enabled) { datadogRum.init({ applicationId: this._dspInstrumentationConfig.dataDog.applicationId, clientToken: this._dspInstrumentationConfig.dataDog.clientToken, diff --git a/src/app/main/declarations/dsp-api-tokens.ts b/src/app/main/declarations/dsp-api-tokens.ts index d99cd3de8e..78bfeac462 100644 --- a/src/app/main/declarations/dsp-api-tokens.ts +++ b/src/app/main/declarations/dsp-api-tokens.ts @@ -1,5 +1,6 @@ import { InjectionToken } from '@angular/core'; import { KnoraApiConfig, KnoraApiConnection } from '@dasch-swiss/dsp-js'; +import { DspAppConfig } from './dsp-app-config'; import { DspInstrumentationConfig } from './dsp-instrumentation-config'; // config for dsp-js-lib (@dasch-swiss/dsp-js) config object @@ -8,5 +9,8 @@ export const DspApiConfigToken = new InjectionToken('DSP api con // connection config for dsp-js-lib (@dasch-swiss/dsp-js) connection export const DspApiConnectionToken = new InjectionToken('DSP api connection instance'); -// config for datadog +// config for dsp-js-lib (@dasch-swiss/dsp-js) config object +export const DspAppConfigToken = new InjectionToken('DSP app specific extended configuration'); + +// config for instrumentation (datadog and rollbar) export const DspInstrumentationToken = new InjectionToken('DSP instrumentation configuration'); diff --git a/src/app/main/declarations/dsp-app-config.ts b/src/app/main/declarations/dsp-app-config.ts new file mode 100644 index 0000000000..e75e27a775 --- /dev/null +++ b/src/app/main/declarations/dsp-app-config.ts @@ -0,0 +1,5 @@ +export class DspAppConfig { + constructor( + public geonameToken: string + ) { } +} diff --git a/src/app/main/declarations/dsp-instrumentation-config.ts b/src/app/main/declarations/dsp-instrumentation-config.ts index c831bc6b08..93c636b2c3 100644 --- a/src/app/main/declarations/dsp-instrumentation-config.ts +++ b/src/app/main/declarations/dsp-instrumentation-config.ts @@ -8,7 +8,7 @@ export class DspInstrumentationConfig { export class DspDataDogConfig { constructor( - public active: boolean, + public enabled: boolean, public applicationId: string, public clientToken: string, public site: string, @@ -18,7 +18,7 @@ export class DspDataDogConfig { export class DspRollbarConfig { constructor( - public active: boolean, + public enabled: boolean, public accessToken: string ) { } } diff --git a/src/app/rollbar.ts b/src/app/rollbar.ts index 94554bb9dd..34656619e8 100644 --- a/src/app/rollbar.ts +++ b/src/app/rollbar.ts @@ -7,29 +7,55 @@ import { ErrorHandler } from '@angular/core'; -import { DspInstrumentationToken } from './main/declarations/dsp-api-tokens'; import { DspInstrumentationConfig } from './main/declarations/dsp-instrumentation-config'; +import { DspInstrumentationToken } from './main/declarations/dsp-api-tokens'; +import { AppInitService } from './app-init.service'; export const RollbarService = new InjectionToken('rollbar'); +const rollbarConfig: Rollbar.Configuration = { + accessToken: 'POST_CLIENT_ITEM_TOKEN', + enabled: false, + captureUncaught: true, + captureUnhandledRejections: true, + nodeSourceMaps: false, + inspectAnonymousErrors: true, + ignoreDuplicateErrors: true, + wrapGlobalEventHandlers: false, + scrubRequestBody: true, + exitOnUncaughtException: false, + stackTraceLimit: 20 +}; + @Injectable() export class RollbarErrorHandler implements ErrorHandler { constructor( - @Inject(DspInstrumentationToken) private _dspInstrumentationConfig: DspInstrumentationConfig, - @Inject(RollbarService) private rollbar: Rollbar - ) { } + @Inject(RollbarService) private _rollbar: Rollbar, + @Inject(DspInstrumentationToken) private _instrConfig: DspInstrumentationConfig + ) { + console.log(this._instrConfig); + console.log(this._rollbar); + } handleError(err: any): void { - this.rollbar.error(err.originalError || err); + this._rollbar.error(err.originalError || err); } } export function rollbarFactory() { - return new Rollbar( + return (initService: AppInitService): Rollbar => new Rollbar( { - accessToken: this._dspInstrumentationConfig.rollbar.accessToken, // '86fd4ff29d3148999d80bdd724ce7704', + accessToken: initService.dspInstrumentationConfig.rollbar.accessToken, + enabled: initService.dspInstrumentationConfig.rollbar.enabled, captureUncaught: true, - captureUnhandledRejections: true + captureUnhandledRejections: true, + nodeSourceMaps: false, + inspectAnonymousErrors: true, + ignoreDuplicateErrors: true, + wrapGlobalEventHandlers: false, + scrubRequestBody: true, + exitOnUncaughtException: false, + stackTraceLimit: 20 } ); } diff --git a/src/app/workspace/resource/representation/upload/upload-file.service.spec.ts b/src/app/workspace/resource/representation/upload/upload-file.service.spec.ts index 562627d86e..f9d3b6f357 100644 --- a/src/app/workspace/resource/representation/upload/upload-file.service.spec.ts +++ b/src/app/workspace/resource/representation/upload/upload-file.service.spec.ts @@ -15,8 +15,8 @@ describe('UploadFileService', () => { beforeEach(() => { const appInitSpy = { - config: { - 'iiifUrl': 'https://iiif.dasch.swiss' + dspIiifConfig: { + iiifUrl: 'https://iiif.dasch.swiss' } }; diff --git a/src/app/workspace/resource/representation/upload/upload-file.service.ts b/src/app/workspace/resource/representation/upload/upload-file.service.ts index 4c3ecfefb9..a0e1e505c4 100644 --- a/src/app/workspace/resource/representation/upload/upload-file.service.ts +++ b/src/app/workspace/resource/representation/upload/upload-file.service.ts @@ -20,7 +20,8 @@ export interface UploadedFileResponse { }) export class UploadFileService { - iiifUrl: string = (this._init.config['iiifUrl'].substr(-1) === '/') ? this._init.config['iiifUrl'] : this._init.config['iiifUrl'] + '/'; + //FIXME: DEV-162 + iiifUrl: string = (this._init.dspIiifConfig.iiifUrl.substr(-1) === '/') ? this._init.dspIiifConfig.iiifUrl : this._init.dspIiifConfig.iiifUrl + '/'; constructor( private readonly _init: AppInitService, diff --git a/src/app/workspace/resource/services/geoname.service.spec.ts b/src/app/workspace/resource/services/geoname.service.spec.ts index 8e4fa00119..dda07123ad 100644 --- a/src/app/workspace/resource/services/geoname.service.spec.ts +++ b/src/app/workspace/resource/services/geoname.service.spec.ts @@ -210,9 +210,10 @@ describe('GeonameService', () => { let httpTestingController: HttpTestingController; const appInitSpy = { - config: { + dspAppConfig: { geonameToken: 'token' } + }; beforeEach(() => { diff --git a/src/app/workspace/resource/services/geoname.service.ts b/src/app/workspace/resource/services/geoname.service.ts index a106506155..9bd7e70c5a 100644 --- a/src/app/workspace/resource/services/geoname.service.ts +++ b/src/app/workspace/resource/services/geoname.service.ts @@ -45,7 +45,7 @@ export class GeonameService { */ resolveGeonameID(id: string): Observable { - return this._http.get('https://ws.geonames.net/getJSON?geonameId=' + id + '&username=' + this._appInitService.config['geonameToken'] + '&style=short').pipe( + return this._http.get('https://ws.geonames.net/getJSON?geonameId=' + id + '&username=' + this._appInitService.dspAppConfig.geonameToken + '&style=short').pipe( map( (geo: { name: string; countryName: string; adminName1?: string; wikipediaURL?: string; lat: number; lng: number }) => { // assertions for TS compiler @@ -83,44 +83,44 @@ export class GeonameService { searchPlace(searchString: string): Observable { return this._http.get('https://ws.geonames.net/searchJSON?userName=' + - this._appInitService.config['geonameToken'] + - '&lang=en&style=full&maxRows=12&name_startsWith=' + encodeURIComponent(searchString)).pipe( - map( - (places: { - geonames: { geonameId: string; name: string; countryName: string; adminName1?: string; fclName: string }[]; // assertions for TS compiler - }) => { - - if (!Array.isArray(places.geonames)) { - // there is no top level array - throw new Error('search did not return an array of results'); - } - - return places.geonames.map( - geo => { + this._appInitService.dspAppConfig.geonameToken + + '&lang=en&style=full&maxRows=12&name_startsWith=' + encodeURIComponent(searchString)).pipe( + map( + (places: { + geonames: { geonameId: string; name: string; countryName: string; adminName1?: string; fclName: string }[]; // assertions for TS compiler + }) => { + + if (!Array.isArray(places.geonames)) { + // there is no top level array + throw new Error('search did not return an array of results'); + } - if (!(('geonameId' in geo) && ('name' in geo) && ('countryName' in geo) && ('fclName' in geo))) { - // at least one of the expected properties is not present - throw new Error('required property missing in geonames response'); + return places.geonames.map( + geo => { + + if (!(('geonameId' in geo) && ('name' in geo) && ('countryName' in geo) && ('fclName' in geo))) { + // at least one of the expected properties is not present + throw new Error('required property missing in geonames response'); + } + + return { + id: geo.geonameId.toString(), + displayName: geo.name + (geo.adminName1 !== undefined ? ', ' + geo.adminName1 : '') + ', ' + geo.countryName, + name: geo.name, + administrativeName: geo.adminName1, + country: geo.countryName, + locationType: geo.fclName + }; } + ); - return { - id: geo.geonameId.toString(), - displayName: geo.name + (geo.adminName1 !== undefined ? ', ' + geo.adminName1 : '') + ', ' + geo.countryName, - name: geo.name, - administrativeName: geo.adminName1, - country: geo.countryName, - locationType: geo.fclName - }; - } - ); - - } - ), - catchError(error => - // an error occurred - throwError(error) - ) - ); + } + ), + catchError(error => + // an error occurred + throwError(error) + ) + ); } } diff --git a/src/config/config.dev.json b/src/config/config.dev.json index e0a02ba40b..01ad2bf9a4 100644 --- a/src/config/config.dev.json +++ b/src/config/config.dev.json @@ -13,15 +13,15 @@ "instrumentation": { "environment": "dev", "dataDog": { - "active": false, + "enabled": false, "applicationId": "", "clientToken": "", "site": "", "service": "" }, "rollbar": { - "active": false, - "accessToken": "" + "enabled": true, + "accessToken": "416ea28b4e0c4861afc3fc7bfdfaa455" } } } diff --git a/src/config/config.prod.json b/src/config/config.prod.json index 03d8c4dc37..c67af2f02f 100644 --- a/src/config/config.prod.json +++ b/src/config/config.prod.json @@ -13,14 +13,14 @@ "instrumentation": { "environment": "prod", "dataDog": { - "active": false, + "enabled": false, "applicationId": "", "clientToken": "", "site": "", "service": "" }, "rollbar": { - "active": false, + "enabled": false, "accessToken": "" } } diff --git a/src/config/config.test-server.json b/src/config/config.test-server.json index 8781845576..fb89476e3a 100644 --- a/src/config/config.test-server.json +++ b/src/config/config.test-server.json @@ -13,14 +13,14 @@ "instrumentation": { "environment": "test", "dataDog": { - "active": false, + "enabled": false, "applicationId": "", "clientToken": "", "site": "", "service": "" }, "rollbar": { - "active": false, + "enabled": false, "accessToken": "" } } From 1ebfb28c2fd103f06a8c773525a6da2f816be8b2 Mon Sep 17 00:00:00 2001 From: Ivan Subotic <400790+subotic@users.noreply.github.com> Date: Mon, 25 Oct 2021 08:43:10 +0200 Subject: [PATCH 05/18] feat(error logging): add rollbar --- src/app/app-init.service.ts | 19 +++++++++++++------ src/app/app.module.ts | 35 ++++++++++------------------------- src/app/rollbar.ts | 35 ++++++++++++++++++++++++++--------- 3 files changed, 49 insertions(+), 40 deletions(-) diff --git a/src/app/app-init.service.ts b/src/app/app-init.service.ts index 45516c3a7d..8b09038f40 100644 --- a/src/app/app-init.service.ts +++ b/src/app/app-init.service.ts @@ -3,19 +3,19 @@ import { KnoraApiConfig } from '@dasch-swiss/dsp-js'; import { DspInstrumentationConfig, DspRollbarConfig, DspDataDogConfig } from './main/declarations/dsp-instrumentation-config'; import { DspIiifConfig } from './main/declarations/dsp-iiif-config'; import { DspAppConfig } from './main/declarations/dsp-app-config'; +import { environment } from '../environments/environment'; @Injectable({ providedIn: 'root' }) export class AppInitService { - dspApiConfig: KnoraApiConfig; - dspIiifConfig: DspIiifConfig; - dspAppConfig: DspAppConfig; - dspInstrumentationConfig: DspInstrumentationConfig; + public dspApiConfig: KnoraApiConfig | null; + public dspIiifConfig: DspIiifConfig | null; + public dspAppConfig: DspAppConfig | null; + public dspInstrumentationConfig: DspInstrumentationConfig | null; - constructor() { - } + constructor() { } /** * fetches and initialises the configuration. @@ -82,6 +82,9 @@ export class AppInitService { ) ); + console.group("AppInitService finished initialization"); + console.log(this); + console.groupEnd(); resolve(); } ).catch((err) => { @@ -90,3 +93,7 @@ export class AppInitService { }); } } + +export function appInitFactory() { + return (appInitService: AppInitService): Promise => appInitService.Init('config', environment); +} diff --git a/src/app/app.module.ts b/src/app/app.module.ts index b0f10601c9..379ad9dd47 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -14,8 +14,7 @@ import { AngularSplitModule } from 'angular-split'; import { MatJDNConvertibleCalendarDateAdapterModule } from 'jdnconvertiblecalendardateadapter'; import { PdfViewerModule } from 'ng2-pdf-viewer'; import { ColorPickerModule } from 'ngx-color-picker'; -import { environment } from '../environments/environment'; -import { AppInitService } from './app-init.service'; +import { AppInitService, appInitFactory } from './app-init.service'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { ConfirmationDialogComponent } from './main/action/confirmation-dialog/confirmation-dialog.component'; @@ -28,7 +27,6 @@ import { SortButtonComponent } from './main/action/sort-button/sort-button.compo import { StringLiteralInputComponent } from './main/action/string-literal-input/string-literal-input.component'; import { CookiePolicyComponent } from './main/cookie-policy/cookie-policy.component'; import { DspApiConfigToken, DspApiConnectionToken, DspAppConfigToken, DspInstrumentationToken } from './main/declarations/dsp-api-tokens'; -import { DspAppConfig } from './main/declarations/dsp-app-config'; 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'; @@ -167,7 +165,7 @@ import { SearchSelectOntologyComponent } from './workspace/search/advanced-searc import { ExpertSearchComponent } from './workspace/search/expert-search/expert-search.component'; import { FulltextSearchComponent } from './workspace/search/fulltext-search/fulltext-search.component'; import { SearchPanelComponent } from './workspace/search/search-panel/search-panel.component'; -import * as Rollbar from 'rollbar'; + // translate: AoT requires an exported function for factories export function httpLoaderFactory(httpClient: HttpClient) { return new TranslateHttpLoader(httpClient, 'assets/i18n/', '.json'); @@ -346,10 +344,10 @@ export function httpLoaderFactory(httpClient: HttpClient) { }) ], providers: [ + AppInitService, { provide: APP_INITIALIZER, - useFactory: (appInitService: AppInitService) => - (): Promise => appInitService.Init('config', environment), + useFactory: appInitFactory, deps: [AppInitService], multi: true }, @@ -373,28 +371,15 @@ export function httpLoaderFactory(httpClient: HttpClient) { useFactory: (appInitService: AppInitService) => appInitService.dspInstrumentationConfig, deps: [AppInitService] }, - { - provide: RollbarService, - useFactory: (appInitService: AppInitService): Rollbar => new Rollbar( - { - accessToken: appInitService.dspInstrumentationConfig.rollbar.accessToken, - enabled: appInitService.dspInstrumentationConfig.rollbar.enabled, - captureUncaught: true, - captureUnhandledRejections: true, - nodeSourceMaps: false, - inspectAnonymousErrors: true, - ignoreDuplicateErrors: true, - wrapGlobalEventHandlers: false, - scrubRequestBody: true, - exitOnUncaughtException: false, - stackTraceLimit: 20 - }), - deps: [AppInitService] - }, + // { + // provide: RollbarService, + // useFactory: rollbarFactory, + // deps: [AppInitService] + // }, { provide: ErrorHandler, useClass: RollbarErrorHandler, - deps: [RollbarService, DspInstrumentationToken] + deps: [AppInitService] } ], diff --git a/src/app/rollbar.ts b/src/app/rollbar.ts index 34656619e8..9945c6e505 100644 --- a/src/app/rollbar.ts +++ b/src/app/rollbar.ts @@ -9,7 +9,7 @@ import { import { DspInstrumentationConfig } from './main/declarations/dsp-instrumentation-config'; import { DspInstrumentationToken } from './main/declarations/dsp-api-tokens'; -import { AppInitService } from './app-init.service'; +import { appInitFactory, AppInitService } from './app-init.service'; export const RollbarService = new InjectionToken('rollbar'); @@ -29,24 +29,41 @@ const rollbarConfig: Rollbar.Configuration = { @Injectable() export class RollbarErrorHandler implements ErrorHandler { + + public rollbar: Rollbar; constructor( - @Inject(RollbarService) private _rollbar: Rollbar, - @Inject(DspInstrumentationToken) private _instrConfig: DspInstrumentationConfig + private _appInitService: AppInitService ) { - console.log(this._instrConfig); - console.log(this._rollbar); + appInitFactory; + console.log(this._appInitService.dspInstrumentationConfig); + this.rollbar = new Rollbar( + { + accessToken: _appInitService.dspInstrumentationConfig.rollbar.accessToken, + enabled: _appInitService.dspInstrumentationConfig.rollbar.enabled, + captureUncaught: true, + captureUnhandledRejections: true, + nodeSourceMaps: false, + inspectAnonymousErrors: true, + ignoreDuplicateErrors: true, + wrapGlobalEventHandlers: false, + scrubRequestBody: true, + exitOnUncaughtException: false, + stackTraceLimit: 20 + }); + + console.log(this.rollbar); } handleError(err: any): void { - this._rollbar.error(err.originalError || err); + this.rollbar.error(err.originalError || err); } } export function rollbarFactory() { - return (initService: AppInitService): Rollbar => new Rollbar( + return (appInitService: AppInitService): Rollbar => new Rollbar( { - accessToken: initService.dspInstrumentationConfig.rollbar.accessToken, - enabled: initService.dspInstrumentationConfig.rollbar.enabled, + accessToken: appInitService.dspInstrumentationConfig.rollbar.accessToken, + enabled: appInitService.dspInstrumentationConfig.rollbar.enabled, captureUncaught: true, captureUnhandledRejections: true, nodeSourceMaps: false, From 8a18020a9864a36f4857ebec29ace3f6a3da2d2d Mon Sep 17 00:00:00 2001 From: Ivan Subotic <400790+subotic@users.noreply.github.com> Date: Mon, 25 Oct 2021 12:23:01 +0200 Subject: [PATCH 06/18] feat(error logging): add rollbar --- package-lock.json | 14 +++++++------- src/app/app-init.service.ts | 36 ++++++++++++++++++++++-------------- src/app/rollbar.ts | 36 +++++++++++++++++++----------------- 3 files changed, 48 insertions(+), 38 deletions(-) diff --git a/package-lock.json b/package-lock.json index c6b8abcb79..18e0a5d2eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,6 @@ "requires": true, "packages": { "": { - "name": "dsp-app", "version": "6.5.0", "dependencies": { "@angular/animations": "^11.2.9", @@ -5329,7 +5328,6 @@ "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-9.0.0.tgz", "integrity": "sha512-ctjwuntPfZZT2mNj2NDIVu51t9cvbhl/16epc5xEwyzyDt76pX9UgwvY+MbXrf/C/FWwdtmNtfP698BKI+9leQ==", "dev": true, - "peer": true, "peerDependencies": { "tslib": "^1.10.0" } @@ -5339,7 +5337,6 @@ "resolved": "https://registry.npmjs.org/@angular/core/-/core-9.0.0.tgz", "integrity": "sha512-6Pxgsrf0qF9iFFqmIcWmjJGkkCaCm6V5QNnxMy2KloO3SDq6QuMVRbN9RtC8Urmo25LP+eZ6ZgYqFYpdD8Hd9w==", "dev": true, - "peer": true, "peerDependencies": { "rxjs": "^6.5.3", "tslib": "^1.10.0", @@ -24535,6 +24532,8 @@ "integrity": "sha512-v3+E0Ucu2xWJMOJ2fA/q9pDT/hlxHftHGPUay1/1cTgyPV5JTHFdO9hqo837Sx2s9vKBMTt5gO+lhF95PO6J+g==", "dev": true, "requires": { + "@angular/compiler": "9.0.0", + "@angular/core": "9.0.0", "app-root-path": "^3.0.0", "aria-query": "^3.0.0", "axobject-query": "2.0.2", @@ -24550,17 +24549,17 @@ }, "dependencies": { "@angular/compiler": { - "version": "https://registry.npmjs.org/@angular/compiler/-/compiler-9.0.0.tgz", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-9.0.0.tgz", "integrity": "sha512-ctjwuntPfZZT2mNj2NDIVu51t9cvbhl/16epc5xEwyzyDt76pX9UgwvY+MbXrf/C/FWwdtmNtfP698BKI+9leQ==", "dev": true, - "peer": true, "requires": {} }, "@angular/core": { - "version": "https://registry.npmjs.org/@angular/core/-/core-9.0.0.tgz", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-9.0.0.tgz", "integrity": "sha512-6Pxgsrf0qF9iFFqmIcWmjJGkkCaCm6V5QNnxMy2KloO3SDq6QuMVRbN9RtC8Urmo25LP+eZ6ZgYqFYpdD8Hd9w==", "dev": true, - "peer": true, "requires": {} }, "source-map": { @@ -30101,6 +30100,7 @@ "resolved": "https://registry.npmjs.org/ng2-pdf-viewer/-/ng2-pdf-viewer-7.0.1.tgz", "integrity": "sha512-kjjsvHd5t1Ff7ydb3Far3d6cSyw/XJH5KXgcp/0bFzSFBAV2c5aOghxoY/yQVjgG+R6F16nVUh2UrZdngLXLSg==", "requires": { + "pdfjs-dist": "~2.7.570", "tslib": "^2.0.0" } }, diff --git a/src/app/app-init.service.ts b/src/app/app-init.service.ts index 8b09038f40..8430b6310a 100644 --- a/src/app/app-init.service.ts +++ b/src/app/app-init.service.ts @@ -4,6 +4,7 @@ import { DspInstrumentationConfig, DspRollbarConfig, DspDataDogConfig } from './ import { DspIiifConfig } from './main/declarations/dsp-iiif-config'; import { DspAppConfig } from './main/declarations/dsp-app-config'; import { environment } from '../environments/environment'; +import { Observable } from 'rxjs'; @Injectable({ providedIn: 'root' @@ -13,7 +14,7 @@ export class AppInitService { public dspApiConfig: KnoraApiConfig | null; public dspIiifConfig: DspIiifConfig | null; public dspAppConfig: DspAppConfig | null; - public dspInstrumentationConfig: DspInstrumentationConfig | null; + public dspInstrumentationConfig: Observable | null; constructor() { } @@ -67,20 +68,27 @@ export class AppInitService { ) // init instrumentation configuration - this.dspInstrumentationConfig = new DspInstrumentationConfig( - jsonConfig.instrumentation.environment, - new DspDataDogConfig( - jsonConfig.instrumentation.dataDog.enabled, - jsonConfig.instrumentation.dataDog.applicationId, - jsonConfig.instrumentation.dataDog.clientToken, - jsonConfig.instrumentation.dataDog.site, - jsonConfig.instrumentation.dataDog.service, - ), - new DspRollbarConfig( - jsonConfig.instrumentation.rollbar.enabled, - jsonConfig.instrumentation.rollbar.accessToken + this.dspInstrumentationConfig = new Observable((observer) => { + + // observable execution + observer.next( + new DspInstrumentationConfig( + jsonConfig.instrumentation.environment, + new DspDataDogConfig( + jsonConfig.instrumentation.dataDog.enabled, + jsonConfig.instrumentation.dataDog.applicationId, + jsonConfig.instrumentation.dataDog.clientToken, + jsonConfig.instrumentation.dataDog.site, + jsonConfig.instrumentation.dataDog.service, + ), + new DspRollbarConfig( + jsonConfig.instrumentation.rollbar.enabled, + jsonConfig.instrumentation.rollbar.accessToken + ) + ) ) - ); + observer.complete() + }) console.group("AppInitService finished initialization"); console.log(this); diff --git a/src/app/rollbar.ts b/src/app/rollbar.ts index 9945c6e505..e7c6f1e6d8 100644 --- a/src/app/rollbar.ts +++ b/src/app/rollbar.ts @@ -10,6 +10,7 @@ import { import { DspInstrumentationConfig } from './main/declarations/dsp-instrumentation-config'; import { DspInstrumentationToken } from './main/declarations/dsp-api-tokens'; import { appInitFactory, AppInitService } from './app-init.service'; +import { environment } from 'src/environments/environment'; export const RollbarService = new InjectionToken('rollbar'); @@ -34,24 +35,25 @@ export class RollbarErrorHandler implements ErrorHandler { constructor( private _appInitService: AppInitService ) { - appInitFactory; - console.log(this._appInitService.dspInstrumentationConfig); - this.rollbar = new Rollbar( - { - accessToken: _appInitService.dspInstrumentationConfig.rollbar.accessToken, - enabled: _appInitService.dspInstrumentationConfig.rollbar.enabled, - captureUncaught: true, - captureUnhandledRejections: true, - nodeSourceMaps: false, - inspectAnonymousErrors: true, - ignoreDuplicateErrors: true, - wrapGlobalEventHandlers: false, - scrubRequestBody: true, - exitOnUncaughtException: false, - stackTraceLimit: 20 - }); + _appInitService.Init('config', environment).then(_ => { + console.log(this._appInitService.dspInstrumentationConfig); + this.rollbar = new Rollbar( + { + accessToken: _appInitService.dspInstrumentationConfig.rollbar.accessToken, + enabled: _appInitService.dspInstrumentationConfig.rollbar.enabled, + captureUncaught: true, + captureUnhandledRejections: true, + nodeSourceMaps: false, + inspectAnonymousErrors: true, + ignoreDuplicateErrors: true, + wrapGlobalEventHandlers: false, + scrubRequestBody: true, + exitOnUncaughtException: false, + stackTraceLimit: 20 + }); + console.log(this.rollbar); + }).catch(err => { console.log(err) }); - console.log(this.rollbar); } handleError(err: any): void { From 574bd3b24d3bc250c9d82bc852c11d0f613eaed3 Mon Sep 17 00:00:00 2001 From: Ivan Subotic <400790+subotic@users.noreply.github.com> Date: Mon, 25 Oct 2021 15:50:50 +0200 Subject: [PATCH 07/18] feat(error logging): add rollbar --- src/app/app-init.service.spec.ts | 119 +++++++++-------- src/app/app-init.service.ts | 140 ++++++++------------ src/app/app.module.ts | 17 +-- src/app/main/declarations/app-config.ts | 27 ++++ src/app/main/declarations/dsp-api-tokens.ts | 3 + src/app/rollbar.ts | 66 ++++----- src/config/config.dev.json | 4 +- src/main.ts | 29 +++- 8 files changed, 203 insertions(+), 202 deletions(-) create mode 100644 src/app/main/declarations/app-config.ts diff --git a/src/app/app-init.service.spec.ts b/src/app/app-init.service.spec.ts index 39b2f65487..ca302d40a2 100644 --- a/src/app/app-init.service.spec.ts +++ b/src/app/app-init.service.spec.ts @@ -1,11 +1,45 @@ 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'; describe('TestService', () => { let service: AppInitService; + const config: IConfig = { + apiProtocol: 'http', + apiHost: '0.0.0.0', + apiPort: 3333, + apiPath: 'mypath', + iiifProtocol: 'http', + iiifHost: '0.0.0.0', + iiifPort: 1024, + iiifPath: 'mypath', + jsonWebToken: 'mytoken', + logErrors: true, + geonameToken: "geoname_token", + instrumentation: { + environment: 'dev', + dataDog: { + enabled: true, + applicationId: 'app_id', + clientToken: 'client_token', + site: 'site', + service: 'dsp-app' + }, + rollbar: { + enabled: true, + accessToken: 'rollbar_token' + } + } + } + beforeEach(() => { - TestBed.configureTestingModule({}); + TestBed.configureTestingModule({ + providers: [ + { provide: APP_CONFIG, useValue: config } + ] + }); service = TestBed.inject(AppInitService); }); @@ -13,41 +47,15 @@ describe('TestService', () => { expect(service).toBeTruthy(); }); - it('should fetch the fully specified config file when method Init is called', async () => { - - const fetchSpy = spyOn(window, 'fetch').and.callFake( - path => Promise.resolve(new Response(JSON.stringify({ - apiProtocol: 'http', - apiHost: '0.0.0.0', - apiPort: 3333, - apiPath: 'mypath', - jsonWebToken: 'mytoken', - logErrors: true, - geonameToken: "geoname_token", - instrumentation: { - environment: 'dev', - dataDog: { - enabled: true, - applicationId: 'app_id', - clientToken: 'client_token', - site: 'site', - service: 'dsp-app' - }, - rollbar: { - enabled: true, - accessToken: 'rollbar_token' - } - } - - }))) - ); - - await service.Init('config', { name: 'prod', production: true }); - + it('should process the fully specified config', async () => { expect(service.dspApiConfig.apiProtocol).toEqual('http'); expect(service.dspApiConfig.apiHost).toEqual('0.0.0.0'); expect(service.dspApiConfig.apiPort).toEqual(3333); expect(service.dspApiConfig.apiPath).toEqual('mypath'); + expect(service.dspIiifConfig.iiifProtocol).toEqual('http'); + expect(service.dspIiifConfig.iiifHost).toEqual('0.0.0.0'); + expect(service.dspIiifConfig.iiifPort).toEqual(1024); + expect(service.dspIiifConfig.iiifPath).toEqual('mypath'); expect(service.dspApiConfig.jsonWebToken).toEqual('mytoken'); expect(service.dspApiConfig.logErrors).toEqual(true); expect(service.dspAppConfig.geonameToken).toEqual('geoname_token'); @@ -59,13 +67,9 @@ describe('TestService', () => { expect(service.dspInstrumentationConfig.dataDog.service).toEqual('dsp-app'); expect(service.dspInstrumentationConfig.rollbar.enabled).toEqual(true); expect(service.dspInstrumentationConfig.rollbar.accessToken).toEqual('rollbar_token'); - - expect(fetchSpy).toHaveBeenCalledTimes(1); - expect(fetchSpy).toHaveBeenCalledWith('config/config.prod.json'); - }); - it('should fetch the minimally specified config file when method Init is called', async () => { + xit('should fetch the minimally specified config file when method Init is called', async () => { const fetchSpy = spyOn(window, 'fetch').and.callFake( path => Promise.resolve(new Response(JSON.stringify({ @@ -88,7 +92,7 @@ describe('TestService', () => { }))) ); - await service.Init('config', { name: 'prod', production: true }); + // await service.Init('config', { name: 'prod', production: true }); expect(service.dspApiConfig.apiProtocol).toEqual('http'); expect(service.dspApiConfig.apiHost).toEqual('0.0.0.0'); @@ -102,7 +106,7 @@ describe('TestService', () => { }); - it('should fetch the config file with additional options when method Init is called', async () => { + xit('should fetch the config file with additional options when method Init is called', async () => { const fetchSpy = spyOn(window, 'fetch').and.callFake( path => Promise.resolve(new Response(JSON.stringify({ @@ -125,7 +129,6 @@ describe('TestService', () => { }))) ); - await service.Init('config', { name: 'prod', production: true }); expect(service.dspApiConfig.apiProtocol).toEqual('http'); expect(service.dspApiConfig.apiHost).toEqual('0.0.0.0'); @@ -139,24 +142,24 @@ describe('TestService', () => { }); - it('should throw an error if required members are missing on the config object', async () => { + xit('should throw an error if required members are missing on the config object', async () => { const fetchSpy = spyOn(window, 'fetch').and.callFake( path => Promise.resolve(new Response(JSON.stringify({}))) ); - await expectAsync(service.Init('config', { - name: 'prod', - production: true - })) - .toBeRejectedWith(new Error('config misses required members: apiProtocol and/or apiHost')); + // await expectAsync(service.Init('config', { + // name: 'prod', + // production: true + // })) + // .toBeRejectedWith(new Error('config misses required members: apiProtocol and/or apiHost')); expect(fetchSpy).toHaveBeenCalledTimes(1); expect(fetchSpy).toHaveBeenCalledWith('config/config.prod.json'); }); - it('should throw an error if "apiProtocol" is missing on the config object', async () => { + xit('should throw an error if "apiProtocol" is missing on the config object', async () => { const fetchSpy = spyOn(window, 'fetch').and.callFake( path => Promise.resolve(new Response(JSON.stringify({ @@ -164,18 +167,18 @@ describe('TestService', () => { }))) ); - await expectAsync(service.Init('config', { - name: 'prod', - production: true - })) - .toBeRejectedWith(new Error('config misses required members: apiProtocol and/or apiHost')); + // await expectAsync(service.Init('config', { + // name: 'prod', + // production: true + // })) + // .toBeRejectedWith(new Error('config misses required members: apiProtocol and/or apiHost')); expect(fetchSpy).toHaveBeenCalledTimes(1); expect(fetchSpy).toHaveBeenCalledWith('config/config.prod.json'); }); - it('should throw an error if "apiHost" is missing on the config object', async () => { + xit('should throw an error if "apiHost" is missing on the config object', async () => { const fetchSpy = spyOn(window, 'fetch').and.callFake( path => Promise.resolve(new Response(JSON.stringify({ @@ -183,11 +186,11 @@ describe('TestService', () => { }))) ); - await expectAsync(service.Init('config', { - name: 'prod', - production: true - })) - .toBeRejectedWith(new Error('config misses required members: apiProtocol and/or apiHost')); + // await expectAsync(service.Init('config', { + // name: 'prod', + // production: true + // })) + // .toBeRejectedWith(new Error('config misses required members: apiProtocol and/or apiHost')); expect(fetchSpy).toHaveBeenCalledTimes(1); expect(fetchSpy).toHaveBeenCalledWith('config/config.prod.json'); diff --git a/src/app/app-init.service.ts b/src/app/app-init.service.ts index 8430b6310a..c268429956 100644 --- a/src/app/app-init.service.ts +++ b/src/app/app-init.service.ts @@ -1,10 +1,10 @@ -import { Injectable } from '@angular/core'; +import { Inject, Injectable } from '@angular/core'; import { KnoraApiConfig } from '@dasch-swiss/dsp-js'; import { DspInstrumentationConfig, DspRollbarConfig, DspDataDogConfig } from './main/declarations/dsp-instrumentation-config'; import { DspIiifConfig } from './main/declarations/dsp-iiif-config'; import { DspAppConfig } from './main/declarations/dsp-app-config'; -import { environment } from '../environments/environment'; -import { Observable } from 'rxjs'; +import { IConfig } from './main/declarations/app-config'; +import { APP_CONFIG } from './main/declarations/dsp-api-tokens'; @Injectable({ providedIn: 'root' @@ -14,94 +14,66 @@ export class AppInitService { public dspApiConfig: KnoraApiConfig | null; public dspIiifConfig: DspIiifConfig | null; public dspAppConfig: DspAppConfig | null; - public dspInstrumentationConfig: Observable | null; + public dspInstrumentationConfig: DspInstrumentationConfig | null; - constructor() { } + constructor( + @Inject(APP_CONFIG) private config: IConfig + ) { + // check for presence of apiProtocol and apiHost + if (typeof this.config.apiProtocol !== 'string' || typeof this.config.apiHost !== 'string') { + throw new Error('config misses required members: apiProtocol and/or apiHost'); + } - /** - * fetches and initialises the configuration. - * - * @param path path to the config file. - * @param env environment to be used (dev or prod). - */ - Init(path: string, env: { name: string; production: boolean }): Promise { + // make input type safe + const apiPort = (typeof this.config.apiPort === 'number' ? this.config.apiPort : null); + const apiPath = (typeof this.config.apiPath === 'string' ? this.config.apiPath : ''); + const jsonWebToken = (typeof this.config.jsonWebToken === 'string' ? this.config.jsonWebToken : ''); + const logErrors = (typeof this.config.logErrors === 'boolean' ? this.config.logErrors : false); - return new Promise((resolve, reject) => { - fetch(`${path}/config.${env.name}.json`).then( - (response: Response) => response.json()).then(jsonConfig => { + // init dsp-api configuration + this.dspApiConfig = new KnoraApiConfig( + this.config.apiProtocol, + this.config.apiHost, + apiPort, + apiPath, + jsonWebToken, + logErrors + ); - // check for presence of apiProtocol and apiHost - if (typeof jsonConfig.apiProtocol !== 'string' || typeof jsonConfig.apiHost !== 'string') { - throw new Error('config misses required members: apiProtocol and/or apiHost'); - } + const iiifPort = (typeof this.config.iiifPort === 'number' ? this.config.iiifPort : null); + const iiifPath = (typeof this.config.iiifPath === 'string' ? this.config.iiifPath : ''); - // make input type safe - const apiPort = (typeof jsonConfig.apiPort === 'number' ? jsonConfig.apiPort : null); - const apiPath = (typeof jsonConfig.apiPath === 'string' ? jsonConfig.apiPath : ''); - const jsonWebToken = (typeof jsonConfig.jsonWebToken === 'string' ? jsonConfig.jsonWebToken : ''); - const logErrors = (typeof jsonConfig.logErrors === 'boolean' ? jsonConfig.logErrors : false); + // init iiif configuration + this.dspIiifConfig = new DspIiifConfig( + this.config.iiifProtocol, + this.config.iiifHost, + iiifPort, + iiifPath + ); - // init dsp-api configuration - this.dspApiConfig = new KnoraApiConfig( - jsonConfig.apiProtocol, - jsonConfig.apiHost, - apiPort, - apiPath, - jsonWebToken, - logErrors - ); + // init dsp app extended configuration + this.dspAppConfig = new DspAppConfig( + this.config.geonameToken + ) - const iiifPort = (typeof jsonConfig.iiifPort === 'number' ? jsonConfig.iiifPort : null); - const iiifPath = (typeof jsonConfig.iiifPath === 'string' ? jsonConfig.iiifPath : ''); + // init instrumentation configuration + this.dspInstrumentationConfig = new DspInstrumentationConfig( + this.config.instrumentation.environment, + new DspDataDogConfig( + this.config.instrumentation.dataDog.enabled, + this.config.instrumentation.dataDog.applicationId, + this.config.instrumentation.dataDog.clientToken, + this.config.instrumentation.dataDog.site, + this.config.instrumentation.dataDog.service, + ), + new DspRollbarConfig( + this.config.instrumentation.rollbar.enabled, + this.config.instrumentation.rollbar.accessToken + ) + ) - // init iiif configuration - this.dspIiifConfig = new DspIiifConfig( - jsonConfig.iiifProtocol, - jsonConfig.iiifHost, - iiifPort, - iiifPath - ); - - // init dsp app extended configuration - this.dspAppConfig = new DspAppConfig( - jsonConfig.geonameToken - ) - - // init instrumentation configuration - this.dspInstrumentationConfig = new Observable((observer) => { - - // observable execution - observer.next( - new DspInstrumentationConfig( - jsonConfig.instrumentation.environment, - new DspDataDogConfig( - jsonConfig.instrumentation.dataDog.enabled, - jsonConfig.instrumentation.dataDog.applicationId, - jsonConfig.instrumentation.dataDog.clientToken, - jsonConfig.instrumentation.dataDog.site, - jsonConfig.instrumentation.dataDog.service, - ), - new DspRollbarConfig( - jsonConfig.instrumentation.rollbar.enabled, - jsonConfig.instrumentation.rollbar.accessToken - ) - ) - ) - observer.complete() - }) - - console.group("AppInitService finished initialization"); - console.log(this); - console.groupEnd(); - resolve(); - } - ).catch((err) => { - reject(err); - }); - }); + console.group("AppInitService finished initialization"); + console.log(this); + console.groupEnd(); } } - -export function appInitFactory() { - return (appInitService: AppInitService): Promise => appInitService.Init('config', environment); -} diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 379ad9dd47..8f8e178727 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -14,7 +14,7 @@ import { AngularSplitModule } from 'angular-split'; import { MatJDNConvertibleCalendarDateAdapterModule } from 'jdnconvertiblecalendardateadapter'; import { PdfViewerModule } from 'ng2-pdf-viewer'; import { ColorPickerModule } from 'ngx-color-picker'; -import { AppInitService, appInitFactory } from './app-init.service'; +import { AppInitService } from './app-init.service'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { ConfirmationDialogComponent } from './main/action/confirmation-dialog/confirmation-dialog.component'; @@ -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, DspAppConfigToken, DspInstrumentationToken } from './main/declarations/dsp-api-tokens'; +import { APP_CONFIG, DspApiConfigToken, DspApiConnectionToken, DspAppConfigToken, DspInstrumentationToken } 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'; @@ -79,7 +79,7 @@ import { AddGroupComponent } from './project/permission/add-group/add-group.comp import { PermissionComponent } from './project/permission/permission.component'; import { ProjectFormComponent } from './project/project-form/project-form.component'; import { ProjectComponent } from './project/project.component'; -import { RollbarErrorHandler, RollbarService, rollbarFactory } from './rollbar'; +import { RollbarErrorHandler, RollbarService } from './rollbar'; import { GroupsListComponent } from './system/groups/groups-list/groups-list.component'; import { GroupsComponent } from './system/groups/groups.component'; import { ProjectsListComponent } from './system/projects/projects-list/projects-list.component'; @@ -345,12 +345,6 @@ export function httpLoaderFactory(httpClient: HttpClient) { ], providers: [ AppInitService, - { - provide: APP_INITIALIZER, - useFactory: appInitFactory, - deps: [AppInitService], - multi: true - }, { provide: DspApiConfigToken, useFactory: (appInitService: AppInitService) => appInitService.dspApiConfig, @@ -371,11 +365,6 @@ export function httpLoaderFactory(httpClient: HttpClient) { useFactory: (appInitService: AppInitService) => appInitService.dspInstrumentationConfig, deps: [AppInitService] }, - // { - // provide: RollbarService, - // useFactory: rollbarFactory, - // deps: [AppInitService] - // }, { provide: ErrorHandler, useClass: RollbarErrorHandler, diff --git a/src/app/main/declarations/app-config.ts b/src/app/main/declarations/app-config.ts new file mode 100644 index 0000000000..c1c3b92766 --- /dev/null +++ b/src/app/main/declarations/app-config.ts @@ -0,0 +1,27 @@ +export interface IConfig { + apiProtocol: 'http' | 'https'; + apiHost: string; + apiPort: number; + apiPath: string; + iiifProtocol: 'http' | 'https'; + iiifHost: string; + iiifPort: number; + iiifPath: string; + jsonWebToken: string; + geonameToken: string; + logErrors: boolean; + instrumentation: { + environment: string; + dataDog: { + enabled: boolean; + applicationId: string; + clientToken: string; + site: string; + service: string; + } + rollbar: { + enabled: boolean; + accessToken: string; + } + } +} diff --git a/src/app/main/declarations/dsp-api-tokens.ts b/src/app/main/declarations/dsp-api-tokens.ts index 78bfeac462..62d838d7d2 100644 --- a/src/app/main/declarations/dsp-api-tokens.ts +++ b/src/app/main/declarations/dsp-api-tokens.ts @@ -1,8 +1,11 @@ import { InjectionToken } from '@angular/core'; import { KnoraApiConfig, KnoraApiConnection } from '@dasch-swiss/dsp-js'; +import { IConfig } from './app-config'; import { DspAppConfig } from './dsp-app-config'; import { DspInstrumentationConfig } from './dsp-instrumentation-config'; +export const APP_CONFIG = new InjectionToken('app-config'); + // config for dsp-js-lib (@dasch-swiss/dsp-js) config object export const DspApiConfigToken = new InjectionToken('DSP api configuration'); diff --git a/src/app/rollbar.ts b/src/app/rollbar.ts index e7c6f1e6d8..1db5ee68da 100644 --- a/src/app/rollbar.ts +++ b/src/app/rollbar.ts @@ -7,10 +7,8 @@ import { ErrorHandler } from '@angular/core'; -import { DspInstrumentationConfig } from './main/declarations/dsp-instrumentation-config'; -import { DspInstrumentationToken } from './main/declarations/dsp-api-tokens'; -import { appInitFactory, AppInitService } from './app-init.service'; -import { environment } from 'src/environments/environment'; + +import { AppInitService } from './app-init.service'; export const RollbarService = new InjectionToken('rollbar'); @@ -31,50 +29,34 @@ const rollbarConfig: Rollbar.Configuration = { @Injectable() export class RollbarErrorHandler implements ErrorHandler { + public rollbar: Rollbar; + constructor( - private _appInitService: AppInitService + @Inject(AppInitService) private _appInitService: AppInitService, ) { - _appInitService.Init('config', environment).then(_ => { - console.log(this._appInitService.dspInstrumentationConfig); - this.rollbar = new Rollbar( - { - accessToken: _appInitService.dspInstrumentationConfig.rollbar.accessToken, - enabled: _appInitService.dspInstrumentationConfig.rollbar.enabled, - captureUncaught: true, - captureUnhandledRejections: true, - nodeSourceMaps: false, - inspectAnonymousErrors: true, - ignoreDuplicateErrors: true, - wrapGlobalEventHandlers: false, - scrubRequestBody: true, - exitOnUncaughtException: false, - stackTraceLimit: 20 - }); - console.log(this.rollbar); - }).catch(err => { console.log(err) }); - + this.rollbar = new Rollbar( + { + accessToken: this._appInitService.dspInstrumentationConfig.rollbar.accessToken, + enabled: this._appInitService.dspInstrumentationConfig.rollbar.enabled, + captureUncaught: true, + captureUnhandledRejections: true, + nodeSourceMaps: false, + inspectAnonymousErrors: true, + ignoreDuplicateErrors: true, + wrapGlobalEventHandlers: false, + scrubRequestBody: true, + exitOnUncaughtException: false, + stackTraceLimit: 20 + } + ); + + + console.log(this.rollbar) + this.rollbar.info("rollbar initialized") } handleError(err: any): void { this.rollbar.error(err.originalError || err); } } - -export function rollbarFactory() { - return (appInitService: AppInitService): Rollbar => new Rollbar( - { - accessToken: appInitService.dspInstrumentationConfig.rollbar.accessToken, - enabled: appInitService.dspInstrumentationConfig.rollbar.enabled, - captureUncaught: true, - captureUnhandledRejections: true, - nodeSourceMaps: false, - inspectAnonymousErrors: true, - ignoreDuplicateErrors: true, - wrapGlobalEventHandlers: false, - scrubRequestBody: true, - exitOnUncaughtException: false, - stackTraceLimit: 20 - } - ); -} diff --git a/src/config/config.dev.json b/src/config/config.dev.json index 01ad2bf9a4..ea8626e284 100644 --- a/src/config/config.dev.json +++ b/src/config/config.dev.json @@ -20,8 +20,8 @@ "service": "" }, "rollbar": { - "enabled": true, - "accessToken": "416ea28b4e0c4861afc3fc7bfdfaa455" + "enabled": false, + "accessToken": "" } } } diff --git a/src/main.ts b/src/main.ts index 9f35c668ea..71d88429cd 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,11 +1,36 @@ import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; +import { APP_CONFIG } from './app/main/declarations/dsp-api-tokens'; + +function configListener() { + try { + const configuration = JSON.parse(this.responseText); + + // pass config to bootstrap process using an injection token + platformBrowserDynamic([ + { provide: APP_CONFIG, useValue: configuration } + ]) + .bootstrapModule(AppModule) + .catch(err => console.error(err)); + + } catch (error) { + console.error(error); + } +} + +function configFailed(evt) { + console.error('Error: retrieving config.json'); +} if (environment.production) { enableProdMode(); } -platformBrowserDynamic().bootstrapModule(AppModule) - .catch(err => console.error(err)); +const request = new XMLHttpRequest(); +request.addEventListener('load', configListener); +request.addEventListener('error', configFailed); +request.open('GET', `./config/config.${environment.name}.json`); +request.send(); From 6bd785a343d6c636574f2aae056dd48690522032 Mon Sep 17 00:00:00 2001 From: Ivan Subotic <400790+subotic@users.noreply.github.com> Date: Mon, 25 Oct 2021 15:54:43 +0200 Subject: [PATCH 08/18] feat(error logging): add rollbar --- src/app/app-init.service.ts | 4 ---- src/app/rollbar.ts | 5 ----- 2 files changed, 9 deletions(-) diff --git a/src/app/app-init.service.ts b/src/app/app-init.service.ts index c268429956..f1a21f3361 100644 --- a/src/app/app-init.service.ts +++ b/src/app/app-init.service.ts @@ -71,9 +71,5 @@ export class AppInitService { this.config.instrumentation.rollbar.accessToken ) ) - - console.group("AppInitService finished initialization"); - console.log(this); - console.groupEnd(); } } diff --git a/src/app/rollbar.ts b/src/app/rollbar.ts index 1db5ee68da..6cc19f8a9f 100644 --- a/src/app/rollbar.ts +++ b/src/app/rollbar.ts @@ -29,7 +29,6 @@ const rollbarConfig: Rollbar.Configuration = { @Injectable() export class RollbarErrorHandler implements ErrorHandler { - public rollbar: Rollbar; constructor( @@ -50,10 +49,6 @@ export class RollbarErrorHandler implements ErrorHandler { stackTraceLimit: 20 } ); - - - console.log(this.rollbar) - this.rollbar.info("rollbar initialized") } handleError(err: any): void { From 06667c211751f410e0cd6ecc4f8ca5b3dae96d01 Mon Sep 17 00:00:00 2001 From: Kilchenmann Date: Tue, 26 Oct 2021 14:41:17 +0200 Subject: [PATCH 09/18] refactor(app-init): clean up code --- src/app/app-init.service.spec.ts | 4 +-- src/app/app-init.service.ts | 46 ++++++++++++++++---------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/app/app-init.service.spec.ts b/src/app/app-init.service.spec.ts index ca302d40a2..c4d54d457f 100644 --- a/src/app/app-init.service.spec.ts +++ b/src/app/app-init.service.spec.ts @@ -17,7 +17,7 @@ describe('TestService', () => { iiifPath: 'mypath', jsonWebToken: 'mytoken', logErrors: true, - geonameToken: "geoname_token", + geonameToken: 'geoname_token', instrumentation: { environment: 'dev', dataDog: { @@ -32,7 +32,7 @@ describe('TestService', () => { accessToken: 'rollbar_token' } } - } + }; beforeEach(() => { TestBed.configureTestingModule({ diff --git a/src/app/app-init.service.ts b/src/app/app-init.service.ts index f1a21f3361..3f7c5bb920 100644 --- a/src/app/app-init.service.ts +++ b/src/app/app-init.service.ts @@ -17,59 +17,59 @@ export class AppInitService { public dspInstrumentationConfig: DspInstrumentationConfig | null; 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') { + if (typeof this._config.apiProtocol !== 'string' || typeof this._config.apiHost !== 'string') { throw new Error('config misses required members: apiProtocol and/or apiHost'); } // make input type safe - const apiPort = (typeof this.config.apiPort === 'number' ? this.config.apiPort : null); - const apiPath = (typeof this.config.apiPath === 'string' ? this.config.apiPath : ''); - const jsonWebToken = (typeof this.config.jsonWebToken === 'string' ? this.config.jsonWebToken : ''); - const logErrors = (typeof this.config.logErrors === 'boolean' ? this.config.logErrors : false); + const apiPort = (typeof this._config.apiPort === 'number' ? this._config.apiPort : null); + const apiPath = (typeof this._config.apiPath === 'string' ? this._config.apiPath : ''); + const jsonWebToken = (typeof this._config.jsonWebToken === 'string' ? this._config.jsonWebToken : ''); + const logErrors = (typeof this._config.logErrors === 'boolean' ? this._config.logErrors : false); // init dsp-api configuration this.dspApiConfig = new KnoraApiConfig( - this.config.apiProtocol, - this.config.apiHost, + this._config.apiProtocol, + this._config.apiHost, apiPort, apiPath, jsonWebToken, logErrors ); - const iiifPort = (typeof this.config.iiifPort === 'number' ? this.config.iiifPort : null); - const iiifPath = (typeof this.config.iiifPath === 'string' ? this.config.iiifPath : ''); + const iiifPort = (typeof this._config.iiifPort === 'number' ? this._config.iiifPort : null); + const iiifPath = (typeof this._config.iiifPath === 'string' ? this._config.iiifPath : ''); // init iiif configuration this.dspIiifConfig = new DspIiifConfig( - this.config.iiifProtocol, - this.config.iiifHost, + this._config.iiifProtocol, + this._config.iiifHost, iiifPort, iiifPath ); // init dsp app extended configuration this.dspAppConfig = new DspAppConfig( - this.config.geonameToken - ) + this._config.geonameToken + ); // init instrumentation configuration this.dspInstrumentationConfig = new DspInstrumentationConfig( - this.config.instrumentation.environment, + this._config.instrumentation.environment, new DspDataDogConfig( - this.config.instrumentation.dataDog.enabled, - this.config.instrumentation.dataDog.applicationId, - this.config.instrumentation.dataDog.clientToken, - this.config.instrumentation.dataDog.site, - this.config.instrumentation.dataDog.service, + this._config.instrumentation.dataDog.enabled, + this._config.instrumentation.dataDog.applicationId, + this._config.instrumentation.dataDog.clientToken, + this._config.instrumentation.dataDog.site, + this._config.instrumentation.dataDog.service, ), new DspRollbarConfig( - this.config.instrumentation.rollbar.enabled, - this.config.instrumentation.rollbar.accessToken + this._config.instrumentation.rollbar.enabled, + this._config.instrumentation.rollbar.accessToken ) - ) + ); } } From 137cfc5ab810c5503e6c57df71d269dbbce421ff Mon Sep 17 00:00:00 2001 From: Kilchenmann Date: Tue, 26 Oct 2021 14:42:02 +0200 Subject: [PATCH 10/18] fix(error-handler): resolve throw error issue --- src/app/main/error/error-handler.service.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/app/main/error/error-handler.service.ts b/src/app/main/error/error-handler.service.ts index 3b68c76af0..58b1e8a161 100644 --- a/src/app/main/error/error-handler.service.ts +++ b/src/app/main/error/error-handler.service.ts @@ -1,6 +1,7 @@ import { Inject, Injectable } from '@angular/core'; import { MatDialog, MatDialogConfig } from '@angular/material/dialog'; import { ApiResponseData, ApiResponseError, KnoraApiConnection, LogoutResponse } from '@dasch-swiss/dsp-js'; +import { StatusMsg } from 'src/assets/http/statusMsg'; import { DspApiConnectionToken } from '../declarations/dsp-api-tokens'; import { DialogComponent } from '../dialog/dialog.component'; import { NotificationService } from '../services/notification.service'; @@ -16,6 +17,7 @@ export class ErrorHandlerService { private _notification: NotificationService, private _dialog: MatDialog, private _session: SessionService, + private _statusMsg: StatusMsg ) { } showMessage(error: ApiResponseError) { @@ -69,7 +71,19 @@ export class ErrorHandlerService { // open snack bar from dsp-ui notification service this._notification.openSnackBar(error); // log error to Rollbar (done automatically by simply throwing a new Error) - throw new Error(error.error['message']); + if (error instanceof ApiResponseError) { + if (error.error && !error.error['message'].startsWith('ajax error')) { + // the Api response error contains a complex error message from dsp-js-lib + throw new Error(error.error['message']); + } else { + const defaultStatusMsg = this._statusMsg.default; + const message = `${defaultStatusMsg[error.status].message} (${error.status}): ${defaultStatusMsg[error.status].description}`; + throw new Error(message); + } + } else { + throw new Error(error); + } + } } } From 1ecd2496dbbafbfa0d76bb97c4137dc5a39c21f8 Mon Sep 17 00:00:00 2001 From: Kilchenmann Date: Tue, 26 Oct 2021 14:42:28 +0200 Subject: [PATCH 11/18] test(value): fix geoname value --- .../values/geoname-value/geoname-value.component.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/workspace/resource/values/geoname-value/geoname-value.component.spec.ts b/src/app/workspace/resource/values/geoname-value/geoname-value.component.spec.ts index 2acdfc267c..d1a5fcd976 100644 --- a/src/app/workspace/resource/values/geoname-value/geoname-value.component.spec.ts +++ b/src/app/workspace/resource/values/geoname-value/geoname-value.component.spec.ts @@ -13,6 +13,7 @@ import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { HarnessLoader } from '@angular/cdk/testing'; import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { MatAutocompleteHarness } from '@angular/material/autocomplete/testing'; +import { AppInitService } from 'src/app/app-init.service'; @@ -85,7 +86,8 @@ describe('GeonameValueComponent', () => { ], providers: [{ provide: GeonameService, - useValue: mockGeonameService + useValue: mockGeonameService, + AppInitService }] }) .compileComponents(); From d62ce02bd4611731819761a1164d28ac64926a1c Mon Sep 17 00:00:00 2001 From: Kilchenmann Date: Tue, 26 Oct 2021 14:44:42 +0200 Subject: [PATCH 12/18] test: add missing cache service --- .../edit-list-item.component.spec.ts | 44 ++++++++++++++++-- src/app/project/project.component.spec.ts | 31 ++++++++++++- .../users-list/users-list.component.spec.ts | 46 ++++++++++++++++++- 3 files changed, 113 insertions(+), 8 deletions(-) diff --git a/src/app/project/list/list-item-form/edit-list-item/edit-list-item.component.spec.ts b/src/app/project/list/list-item-form/edit-list-item/edit-list-item.component.spec.ts index d3124c32b8..7b049b6b20 100644 --- a/src/app/project/list/list-item-form/edit-list-item/edit-list-item.component.spec.ts +++ b/src/app/project/list/list-item-form/edit-list-item/edit-list-item.component.spec.ts @@ -1,14 +1,25 @@ import { Component, DebugElement, ViewChild } from '@angular/core'; -import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { MatDialogModule } from '@angular/material/dialog'; import { MatSnackBarModule } from '@angular/material/snack-bar'; import { By } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { ApiResponseData, CreateChildNodeRequest, ListNodeInfoResponse, ListsEndpointAdmin, UpdateChildNodeRequest } from '@dasch-swiss/dsp-js'; +import { + ApiResponseData, + CreateChildNodeRequest, + ListNodeInfoResponse, + ListsEndpointAdmin, + MockProjects, + ProjectResponse, + ReadProject, + UpdateChildNodeRequest +} from '@dasch-swiss/dsp-js'; import { TranslateModule } from '@ngx-translate/core'; import { of } from 'rxjs'; import { AjaxResponse } from 'rxjs/ajax'; +import { AppInitService } from 'src/app/app-init.service'; import { ProgressIndicatorComponent } from 'src/app/main/action/progress-indicator/progress-indicator.component'; +import { CacheService } from 'src/app/main/cache/cache.service'; import { DspApiConnectionToken } from 'src/app/main/declarations/dsp-api-tokens'; import { EditListItemComponent } from './edit-list-item.component'; @@ -28,7 +39,7 @@ class TestHostUpdateChildNodeComponent { projectIri = 'http://rdfh.ch/projects/0001'; - constructor() {} + constructor() { } } /** @@ -53,7 +64,7 @@ class TestHostInsertChildNodeComponent { projectIri = 'http://rdfh.ch/projects/0001'; - constructor() {} + constructor() { } } describe('EditListItemComponent', () => { @@ -70,6 +81,8 @@ describe('EditListItemComponent', () => { } }; + const cacheServiceSpy = jasmine.createSpyObj('CacheService', ['get', 'set']); + TestBed.configureTestingModule({ declarations: [ EditListItemComponent, @@ -84,15 +97,38 @@ describe('EditListItemComponent', () => { TranslateModule.forRoot() ], providers: [ + AppInitService, { provide: DspApiConnectionToken, useValue: listsEndpointSpyObj + }, + { + provide: CacheService, + useValue: cacheServiceSpy } ] }) .compileComponents(); })); + beforeEach(() => { + + // mock cache service + const cacheSpy = TestBed.inject(CacheService); + + (cacheSpy as jasmine.SpyObj).get.and.callFake( + () => { + const response: ProjectResponse = new ProjectResponse(); + + const mockProjects = MockProjects.mockProjects(); + + response.project = mockProjects.body.projects[0]; + + return of(response.project as ReadProject); + } + ); + }); + describe('update list child node', () => { beforeEach(() => { const dspConnSpy = TestBed.inject(DspApiConnectionToken); diff --git a/src/app/project/project.component.spec.ts b/src/app/project/project.component.spec.ts index a3e3e8444f..5747ea9a72 100644 --- a/src/app/project/project.component.spec.ts +++ b/src/app/project/project.component.spec.ts @@ -1,13 +1,15 @@ -import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { MatDialogModule } from '@angular/material/dialog'; import { MatIconModule } from '@angular/material/icon'; import { MatSnackBarModule } from '@angular/material/snack-bar'; import { MatTabsModule } from '@angular/material/tabs'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; -import { KnoraApiConnection } from '@dasch-swiss/dsp-js'; +import { KnoraApiConnection, MockProjects, ProjectResponse, ReadProject } from '@dasch-swiss/dsp-js'; +import { of } from 'rxjs'; import { TestConfig } from 'test.config'; import { AppInitService } from '../app-init.service'; +import { CacheService } from '../main/cache/cache.service'; import { DspApiConfigToken, DspApiConnectionToken } from '../main/declarations/dsp-api-tokens'; import { DialogComponent } from '../main/dialog/dialog.component'; import { ErrorComponent } from '../main/error/error.component'; @@ -18,6 +20,9 @@ describe('ProjectComponent', () => { let fixture: ComponentFixture; beforeEach(waitForAsync(() => { + + const cacheServiceSpy = jasmine.createSpyObj('CacheService', ['get', 'set']); + TestBed.configureTestingModule({ declarations: [ ProjectComponent, @@ -41,11 +46,33 @@ describe('ProjectComponent', () => { { provide: DspApiConnectionToken, useValue: new KnoraApiConnection(TestConfig.ApiConfig) + }, + { + provide: CacheService, + useValue: cacheServiceSpy } ] }).compileComponents(); })); + beforeEach(() => { + + // mock cache service + const cacheSpy = TestBed.inject(CacheService); + + (cacheSpy as jasmine.SpyObj).get.and.callFake( + () => { + const response: ProjectResponse = new ProjectResponse(); + + const mockProjects = MockProjects.mockProjects(); + + response.project = mockProjects.body.projects[0]; + + return of(response.project as ReadProject); + } + ); + }); + beforeEach(() => { fixture = TestBed.createComponent(ProjectComponent); component = fixture.componentInstance; diff --git a/src/app/system/users/users-list/users-list.component.spec.ts b/src/app/system/users/users-list/users-list.component.spec.ts index edd540bfb9..6b66f5d91c 100644 --- a/src/app/system/users/users-list/users-list.component.spec.ts +++ b/src/app/system/users/users-list/users-list.component.spec.ts @@ -1,4 +1,4 @@ -import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatChipsModule } from '@angular/material/chips'; @@ -10,9 +10,10 @@ import { MatSnackBarModule } from '@angular/material/snack-bar'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { ActivatedRoute } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; -import { KnoraApiConnection } from '@dasch-swiss/dsp-js'; +import { KnoraApiConnection, MockProjects, ProjectResponse, ReadProject } from '@dasch-swiss/dsp-js'; import { of } from 'rxjs'; import { AppInitService } from 'src/app/app-init.service'; +import { CacheService } from 'src/app/main/cache/cache.service'; import { DspApiConfigToken, DspApiConnectionToken } from 'src/app/main/declarations/dsp-api-tokens'; import { DialogComponent } from 'src/app/main/dialog/dialog.component'; import { ErrorComponent } from 'src/app/main/error/error.component'; @@ -25,6 +26,9 @@ describe('UsersListComponent', () => { let fixture: ComponentFixture; beforeEach(waitForAsync(() => { + + const cacheServiceSpy = jasmine.createSpyObj('CacheService', ['get', 'set']); + TestBed.configureTestingModule({ declarations: [ UsersListComponent, @@ -67,6 +71,10 @@ describe('UsersListComponent', () => { { provide: DspApiConnectionToken, useValue: new KnoraApiConnection(TestConfig.ApiConfig) + }, + { + provide: CacheService, + useValue: cacheServiceSpy } ] }).compileComponents(); @@ -77,6 +85,21 @@ describe('UsersListComponent', () => { beforeEach(() => { let store = {}; + spyOn(sessionStorage, 'getItem').and.callFake( + (key: string): string => store[key] || null + ); + spyOn(sessionStorage, 'removeItem').and.callFake( + (key: string): void => { + delete store[key]; + } + ); + spyOn(sessionStorage, 'setItem').and.callFake( + (key: string, value: string): string => (store[key] = value) + ); + spyOn(sessionStorage, 'clear').and.callFake(() => { + store = {}; + }); + spyOn(localStorage, 'getItem').and.callFake( (key: string): string => store[key] || null ); @@ -101,6 +124,25 @@ describe('UsersListComponent', () => { fixture.detectChanges(); }); + + beforeEach(() => { + + // mock cache service + const cacheSpy = TestBed.inject(CacheService); + + (cacheSpy as jasmine.SpyObj).get.and.callFake( + () => { + const response: ProjectResponse = new ProjectResponse(); + + const mockProjects = MockProjects.mockProjects(); + + response.project = mockProjects.body.projects[0]; + + return of(response.project as ReadProject); + } + ); + }); + it('should create', () => { expect(localStorage.getItem('session')).toBe( JSON.stringify(TestConfig.CurrentSession) From 702eeac985a0d861f1a81bc58a931ecf43bfaa4c Mon Sep 17 00:00:00 2001 From: Kilchenmann Date: Tue, 26 Oct 2021 15:01:52 +0200 Subject: [PATCH 13/18] refactor: make tslint happy --- src/app/main/declarations/app-config.ts | 6 +- .../upload/upload-file.service.ts | 2 +- .../resource/services/geoname.service.ts | 66 +++++++++---------- 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/app/main/declarations/app-config.ts b/src/app/main/declarations/app-config.ts index c1c3b92766..3aeb082d91 100644 --- a/src/app/main/declarations/app-config.ts +++ b/src/app/main/declarations/app-config.ts @@ -18,10 +18,10 @@ export interface IConfig { clientToken: string; site: string; service: string; - } + }; rollbar: { enabled: boolean; accessToken: string; - } - } + }; + }; } diff --git a/src/app/workspace/resource/representation/upload/upload-file.service.ts b/src/app/workspace/resource/representation/upload/upload-file.service.ts index a0e1e505c4..3731618cfe 100644 --- a/src/app/workspace/resource/representation/upload/upload-file.service.ts +++ b/src/app/workspace/resource/representation/upload/upload-file.service.ts @@ -20,7 +20,7 @@ export interface UploadedFileResponse { }) export class UploadFileService { - //FIXME: DEV-162 + // fIXME: DEV-162 iiifUrl: string = (this._init.dspIiifConfig.iiifUrl.substr(-1) === '/') ? this._init.dspIiifConfig.iiifUrl : this._init.dspIiifConfig.iiifUrl + '/'; constructor( diff --git a/src/app/workspace/resource/services/geoname.service.ts b/src/app/workspace/resource/services/geoname.service.ts index 9bd7e70c5a..683158a0e5 100644 --- a/src/app/workspace/resource/services/geoname.service.ts +++ b/src/app/workspace/resource/services/geoname.service.ts @@ -85,42 +85,42 @@ export class GeonameService { return this._http.get('https://ws.geonames.net/searchJSON?userName=' + this._appInitService.dspAppConfig.geonameToken + '&lang=en&style=full&maxRows=12&name_startsWith=' + encodeURIComponent(searchString)).pipe( - map( - (places: { - geonames: { geonameId: string; name: string; countryName: string; adminName1?: string; fclName: string }[]; // assertions for TS compiler - }) => { - - if (!Array.isArray(places.geonames)) { - // there is no top level array - throw new Error('search did not return an array of results'); - } + map( + (places: { + geonames: { geonameId: string; name: string; countryName: string; adminName1?: string; fclName: string }[]; // assertions for TS compiler + }) => { + + if (!Array.isArray(places.geonames)) { + // there is no top level array + throw new Error('search did not return an array of results'); + } - return places.geonames.map( - geo => { - - if (!(('geonameId' in geo) && ('name' in geo) && ('countryName' in geo) && ('fclName' in geo))) { - // at least one of the expected properties is not present - throw new Error('required property missing in geonames response'); - } - - return { - id: geo.geonameId.toString(), - displayName: geo.name + (geo.adminName1 !== undefined ? ', ' + geo.adminName1 : '') + ', ' + geo.countryName, - name: geo.name, - administrativeName: geo.adminName1, - country: geo.countryName, - locationType: geo.fclName - }; + return places.geonames.map( + geo => { + + if (!(('geonameId' in geo) && ('name' in geo) && ('countryName' in geo) && ('fclName' in geo))) { + // at least one of the expected properties is not present + throw new Error('required property missing in geonames response'); } - ); - } - ), - catchError(error => - // an error occurred - throwError(error) - ) - ); + return { + id: geo.geonameId.toString(), + displayName: geo.name + (geo.adminName1 !== undefined ? ', ' + geo.adminName1 : '') + ', ' + geo.countryName, + name: geo.name, + administrativeName: geo.adminName1, + country: geo.countryName, + locationType: geo.fclName + }; + } + ); + + } + ), + catchError(error => + // an error occurred + throwError(error) + ) + ); } } From 5454685efd454c3cb34904f0b6fe5db3413b4b8e Mon Sep 17 00:00:00 2001 From: Kilchenmann Date: Tue, 26 Oct 2021 15:22:48 +0200 Subject: [PATCH 14/18] test: fix tests by adding cache service the correct way --- src/app/project/board/board.component.spec.ts | 30 ++++++++++++++-- .../collaboration.component.spec.ts | 28 ++++++++++++++- .../permission/permission.component.spec.ts | 30 ++++++++++++++-- .../users-list/users-list.component.spec.ts | 36 +++++++++---------- 4 files changed, 100 insertions(+), 24 deletions(-) diff --git a/src/app/project/board/board.component.spec.ts b/src/app/project/board/board.component.spec.ts index f31604110c..51b2714e5f 100644 --- a/src/app/project/board/board.component.spec.ts +++ b/src/app/project/board/board.component.spec.ts @@ -1,4 +1,4 @@ -import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { MatChipsModule } from '@angular/material/chips'; import { MatDialogModule } from '@angular/material/dialog'; import { MatDividerModule } from '@angular/material/divider'; @@ -7,13 +7,14 @@ import { MatSnackBarModule } from '@angular/material/snack-bar'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { ActivatedRoute } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; -import { KnoraApiConnection } from '@dasch-swiss/dsp-js'; +import { KnoraApiConnection, MockProjects, ProjectResponse, ReadProject } from '@dasch-swiss/dsp-js'; import { of } from 'rxjs'; import { AppInitService } from 'src/app/app-init.service'; import { DspApiConfigToken, DspApiConnectionToken } from 'src/app/main/declarations/dsp-api-tokens'; import { DialogComponent } from 'src/app/main/dialog/dialog.component'; import { ErrorComponent } from 'src/app/main/error/error.component'; import { TestConfig } from 'test.config'; +import { CacheService } from '../../main/cache/cache.service'; import { AttributionTabViewComponent } from './attribution-tab-view/attribution-tab-view.component'; import { BoardComponent } from './board.component'; import { ContactsTabViewComponent } from './contacts-tab-view/contacts-tab-view.component'; @@ -26,6 +27,9 @@ describe('BoardComponent', () => { let fixture: ComponentFixture; beforeEach(waitForAsync(() => { + + const cacheServiceSpy = jasmine.createSpyObj('CacheService', ['get', 'set']); + TestBed.configureTestingModule({ declarations: [ BoardComponent, @@ -69,11 +73,33 @@ describe('BoardComponent', () => { { provide: DspApiConnectionToken, useValue: new KnoraApiConnection(TestConfig.ApiConfig) + }, + { + provide: CacheService, + useValue: cacheServiceSpy } ] }).compileComponents(); })); + beforeEach(() => { + + // mock cache service + const cacheSpy = TestBed.inject(CacheService); + + (cacheSpy as jasmine.SpyObj).get.and.callFake( + () => { + const response: ProjectResponse = new ProjectResponse(); + + const mockProjects = MockProjects.mockProjects(); + + response.project = mockProjects.body.projects[0]; + + return of(response.project as ReadProject); + } + ); + }); + // mock localStorage beforeEach(() => { let store = {}; diff --git a/src/app/project/collaboration/collaboration.component.spec.ts b/src/app/project/collaboration/collaboration.component.spec.ts index fb65f89f83..2a3f68d0b3 100644 --- a/src/app/project/collaboration/collaboration.component.spec.ts +++ b/src/app/project/collaboration/collaboration.component.spec.ts @@ -12,10 +12,11 @@ import { MatSnackBarModule } from '@angular/material/snack-bar'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { ActivatedRoute } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; -import { KnoraApiConnection } from '@dasch-swiss/dsp-js'; +import { KnoraApiConnection, MockProjects, ProjectResponse, ReadProject } from '@dasch-swiss/dsp-js'; import { TranslateModule } from '@ngx-translate/core'; import { of } from 'rxjs'; import { AppInitService } from 'src/app/app-init.service'; +import { CacheService } from 'src/app/main/cache/cache.service'; import { DspApiConfigToken, DspApiConnectionToken } from 'src/app/main/declarations/dsp-api-tokens'; import { DialogComponent } from 'src/app/main/dialog/dialog.component'; import { ErrorComponent } from 'src/app/main/error/error.component'; @@ -30,6 +31,9 @@ describe('CollaborationComponent', () => { let fixture: ComponentFixture; beforeEach(waitForAsync(() => { + + const cacheServiceSpy = jasmine.createSpyObj('CacheService', ['get', 'set']); + TestBed.configureTestingModule({ declarations: [ CollaborationComponent, @@ -77,11 +81,33 @@ describe('CollaborationComponent', () => { { provide: DspApiConnectionToken, useValue: new KnoraApiConnection(TestConfig.ApiConfig) + }, + { + provide: CacheService, + useValue: cacheServiceSpy } ] }).compileComponents(); })); + beforeEach(() => { + + // mock cache service + const cacheSpy = TestBed.inject(CacheService); + + (cacheSpy as jasmine.SpyObj).get.and.callFake( + () => { + const response: ProjectResponse = new ProjectResponse(); + + const mockProjects = MockProjects.mockProjects(); + + response.project = mockProjects.body.projects[0]; + + return of(response.project as ReadProject); + } + ); + }); + // mock localStorage beforeEach(() => { let store = {}; diff --git a/src/app/project/permission/permission.component.spec.ts b/src/app/project/permission/permission.component.spec.ts index d690aa5811..34e3401376 100644 --- a/src/app/project/permission/permission.component.spec.ts +++ b/src/app/project/permission/permission.component.spec.ts @@ -1,14 +1,15 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; -import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { MatDialogModule } from '@angular/material/dialog'; import { MatIconModule } from '@angular/material/icon'; import { MatSnackBarModule } from '@angular/material/snack-bar'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { ActivatedRoute } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; -import { KnoraApiConnection } from '@dasch-swiss/dsp-js'; +import { KnoraApiConnection, MockProjects, ProjectResponse, ReadProject } from '@dasch-swiss/dsp-js'; import { of } from 'rxjs'; import { AppInitService } from 'src/app/app-init.service'; +import { CacheService } from 'src/app/main/cache/cache.service'; import { DspApiConfigToken, DspApiConnectionToken } from 'src/app/main/declarations/dsp-api-tokens'; import { DialogComponent } from 'src/app/main/dialog/dialog.component'; import { ErrorComponent } from 'src/app/main/error/error.component'; @@ -22,6 +23,9 @@ describe('PermissionComponent', () => { let fixture: ComponentFixture; beforeEach(waitForAsync(() => { + + const cacheServiceSpy = jasmine.createSpyObj('CacheService', ['get', 'set']); + TestBed.configureTestingModule({ declarations: [ PermissionComponent, @@ -61,12 +65,34 @@ describe('PermissionComponent', () => { { provide: DspApiConnectionToken, useValue: new KnoraApiConnection(TestConfig.ApiConfig) + }, + { + provide: CacheService, + useValue: cacheServiceSpy } ] }) .compileComponents(); })); + beforeEach(() => { + + // mock cache service + const cacheSpy = TestBed.inject(CacheService); + + (cacheSpy as jasmine.SpyObj).get.and.callFake( + () => { + const response: ProjectResponse = new ProjectResponse(); + + const mockProjects = MockProjects.mockProjects(); + + response.project = mockProjects.body.projects[0]; + + return of(response.project as ReadProject); + } + ); + }); + // mock localStorage beforeEach(() => { let store = {}; diff --git a/src/app/system/users/users-list/users-list.component.spec.ts b/src/app/system/users/users-list/users-list.component.spec.ts index 6b66f5d91c..d1ff027074 100644 --- a/src/app/system/users/users-list/users-list.component.spec.ts +++ b/src/app/system/users/users-list/users-list.component.spec.ts @@ -80,6 +80,23 @@ describe('UsersListComponent', () => { }).compileComponents(); })); + beforeEach(() => { + + // mock cache service + const cacheSpy = TestBed.inject(CacheService); + + (cacheSpy as jasmine.SpyObj).get.and.callFake( + () => { + const response: ProjectResponse = new ProjectResponse(); + + const mockProjects = MockProjects.mockProjects(); + + response.project = mockProjects.body.projects[0]; + + return of(response.project as ReadProject); + } + ); + }); // mock localStorage beforeEach(() => { @@ -124,25 +141,6 @@ describe('UsersListComponent', () => { fixture.detectChanges(); }); - - beforeEach(() => { - - // mock cache service - const cacheSpy = TestBed.inject(CacheService); - - (cacheSpy as jasmine.SpyObj).get.and.callFake( - () => { - const response: ProjectResponse = new ProjectResponse(); - - const mockProjects = MockProjects.mockProjects(); - - response.project = mockProjects.body.projects[0]; - - return of(response.project as ReadProject); - } - ); - }); - it('should create', () => { expect(localStorage.getItem('session')).toBe( JSON.stringify(TestConfig.CurrentSession) From 65ae9854e5558816f42db27794810283350ae15f Mon Sep 17 00:00:00 2001 From: Kilchenmann Date: Tue, 26 Oct 2021 15:25:26 +0200 Subject: [PATCH 15/18] test(login): fix test by adding session the correct way --- .../login-form/login-form.component.spec.ts | 17 ++++++++++++++++- .../action/login-form/login-form.component.ts | 5 ++--- 2 files changed, 18 insertions(+), 4 deletions(-) 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 604106bfea..f2888654f5 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 @@ -1,5 +1,5 @@ import { Component, OnInit, ViewChild } from '@angular/core'; -import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; import { MatInputModule } from '@angular/material/input'; import { MatSnackBarModule } from '@angular/material/snack-bar'; @@ -107,6 +107,21 @@ describe('LoginFormComponent', () => { beforeEach(() => { let store = {}; + spyOn(sessionStorage, 'getItem').and.callFake( + (key: string): string => store[key] || null + ); + spyOn(sessionStorage, 'removeItem').and.callFake( + (key: string): void => { + delete store[key]; + } + ); + spyOn(sessionStorage, 'setItem').and.callFake( + (key: string, value: string): string => (store[key] = value) + ); + spyOn(sessionStorage, 'clear').and.callFake(() => { + store = {}; + }); + spyOn(localStorage, 'getItem').and.callFake( (key: string): string => store[key] || null ); 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 b2910a7cab..cbd73f55e8 100644 --- a/src/app/main/action/login-form/login-form.component.ts +++ b/src/app/main/action/login-form/login-form.component.ts @@ -1,9 +1,8 @@ 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, KnoraApiConfig, KnoraApiConnection, LoginResponse, LogoutResponse } from '@dasch-swiss/dsp-js'; +import { ApiResponseData, ApiResponseError, KnoraApiConnection, LoginResponse, LogoutResponse } from '@dasch-swiss/dsp-js'; import { datadogRum, RumFetchResourceEventDomainContext } from '@datadog/browser-rum'; -import { DspApiConfigToken, DspApiConnectionToken, DspInstrumentationToken } from '../../declarations/dsp-api-tokens'; +import { DspApiConnectionToken, DspInstrumentationToken } from '../../declarations/dsp-api-tokens'; import { DspInstrumentationConfig } from '../../declarations/dsp-instrumentation-config'; import { NotificationService } from '../../services/notification.service'; import { Session, SessionService } from '../../services/session.service'; From fcad70d4d25a656e047f6e0c909620ef316b3c47 Mon Sep 17 00:00:00 2001 From: Kilchenmann Date: Tue, 26 Oct 2021 15:50:43 +0200 Subject: [PATCH 16/18] test(login): add app init service --- src/app/main/action/login-form/login-form.component.spec.ts | 2 ++ 1 file changed, 2 insertions(+) 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 f2888654f5..e6104c19a4 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 @@ -15,6 +15,7 @@ import { } from '@dasch-swiss/dsp-js'; import { of } from 'rxjs'; import { AjaxResponse } from 'rxjs/ajax'; +import { AppInitService } from 'src/app/app-init.service'; import { DspApiConfigToken, DspApiConnectionToken, DspInstrumentationToken } from '../../declarations/dsp-api-tokens'; import { DspDataDogConfig } from '../../declarations/dsp-instrumentation-config'; import { Session, SessionService } from '../../services/session.service'; @@ -76,6 +77,7 @@ describe('LoginFormComponent', () => { TestHostComponent ], providers: [ + AppInitService, { provide: DspApiConnectionToken, useValue: dspConnSpy From 23807cbf33a8bdacd560ed90dec4e361c53b1383 Mon Sep 17 00:00:00 2001 From: Kilchenmann Date: Tue, 26 Oct 2021 16:33:58 +0200 Subject: [PATCH 17/18] test(login): fix tests --- .../login-form/login-form.component.spec.ts | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) 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 e6104c19a4..2aee3b977b 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,7 +7,9 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { ApiResponseData, AuthenticationEndpointV2, + CredentialsResponse, KnoraApiConfig, + KnoraApiConnection, LoginResponse, LogoutResponse, MockUsers, @@ -16,6 +18,7 @@ import { import { of } from 'rxjs'; import { AjaxResponse } from 'rxjs/ajax'; import { AppInitService } from 'src/app/app-init.service'; +import { TestConfig } from 'test.config'; import { DspApiConfigToken, DspApiConnectionToken, DspInstrumentationToken } from '../../declarations/dsp-api-tokens'; import { DspDataDogConfig } from '../../declarations/dsp-instrumentation-config'; import { Session, SessionService } from '../../services/session.service'; @@ -57,18 +60,16 @@ describe('LoginFormComponent', () => { let sessionService: SessionService; beforeEach(waitForAsync(() => { - const dspConfSpy = new KnoraApiConfig('http', 'localhost', 3333, undefined, undefined, true); const dspDatadogSpy = new DspDataDogConfig(false, '', '', '', ''); - const dspConnSpy = { + const authEndpointSpyObj = { admin: { usersEndpoint: jasmine.createSpyObj('usersEndpoint', ['getUser']) }, v2: { - auth: jasmine.createSpyObj('auth', ['login', 'logout']), - jsonWebToken: '' - }, + auth: jasmine.createSpyObj('auth', ['login', 'logout']) + } }; TestBed.configureTestingModule({ @@ -78,20 +79,19 @@ describe('LoginFormComponent', () => { ], providers: [ AppInitService, + SessionService, { - provide: DspApiConnectionToken, - useValue: dspConnSpy + provide: DspApiConfigToken, + useValue: TestConfig.ApiConfig }, { - provide: DspApiConfigToken, - useValue: dspConfSpy + provide: DspApiConnectionToken, + useValue: authEndpointSpyObj }, { provide: DspInstrumentationToken, useValue: dspDatadogSpy }, - FormBuilder, - SessionService ], imports: [ ReactiveFormsModule, @@ -143,6 +143,7 @@ describe('LoginFormComponent', () => { }); beforeEach(() => { + testHostFixture = TestBed.createComponent(TestHostComponent); testHostComponent = testHostFixture.componentInstance; testHostFixture.detectChanges(); From 2cb652f6e1f9fd3797447a2a278a0fbea7bd181d Mon Sep 17 00:00:00 2001 From: Kilchenmann Date: Wed, 27 Oct 2021 10:21:39 +0200 Subject: [PATCH 18/18] refactor: clean up imports --- package-lock.json | 1 + src/app/app.module.ts | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 18e0a5d2eb..ed37ad7510 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "requires": true, "packages": { "": { + "name": "dsp-app", "version": "6.5.0", "dependencies": { "@angular/animations": "^11.2.9", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 8f8e178727..66c51e91bc 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -2,7 +2,7 @@ import { ClipboardModule } from '@angular/cdk/clipboard'; import { CommonModule } from '@angular/common'; import { HttpClient, HttpClientModule } from '@angular/common/http'; -import { APP_INITIALIZER, ErrorHandler, Inject, Injectable, InjectionToken, NgModule } from '@angular/core'; +import { ErrorHandler, NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; @@ -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 { APP_CONFIG, DspApiConfigToken, DspApiConnectionToken, DspAppConfigToken, DspInstrumentationToken } from './main/declarations/dsp-api-tokens'; +import { DspApiConfigToken, DspApiConnectionToken, DspAppConfigToken, DspInstrumentationToken } 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'; @@ -79,7 +79,7 @@ import { AddGroupComponent } from './project/permission/add-group/add-group.comp import { PermissionComponent } from './project/permission/permission.component'; import { ProjectFormComponent } from './project/project-form/project-form.component'; import { ProjectComponent } from './project/project.component'; -import { RollbarErrorHandler, RollbarService } from './rollbar'; +import { RollbarErrorHandler } from './rollbar'; import { GroupsListComponent } from './system/groups/groups-list/groups-list.component'; import { GroupsComponent } from './system/groups/groups.component'; import { ProjectsListComponent } from './system/projects/projects-list/projects-list.component';