diff --git a/.gitignore b/.gitignore index f4f46a5feeb99832da5739425488cf171397a413..8bd19c97593cdb7b55c66cf2997390a996e13077 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,6 @@ testem.log # System Files .DS_Store Thumbs.db + +### Sonar ### +.scannerwork diff --git a/README.md b/README.md index e97bd394205cba66e1656d534642347094729b3c..307680872f2e77dc7b3777afceef3c6c0a7753b2 100644 --- a/README.md +++ b/README.md @@ -3,26 +3,38 @@ ## Building ``` -sudo apt install nodejs -sudo apt install npm -sudo npm install -g npm@latest -sudo npm install -g @angular/cli - +sudo apt install nodejs npm +sudo -H npm install -g npm@latest +sudo -H npm install -g @angular/cli git clone -npm install +cd +npm ci +npm run lint +npm run test-headless ``` -## Testing locally +## Sonarqube +By default `http://localhost:9000` is used as a sonarqube server. +If you have a remote sonarqube server, update `sonar-project.properties` cunfiguration file and run the test with: ``` -ng serve --host 0.0.0.0 +npm run sonar +``` +Alternatively you can provide hostname and access token with command line: +``` +./node_modules/sonar-scanner/bin/sonar-scanner -Dsonar.host.url= -Dsonar.login= ``` -## Update angular +## Updating angular version ``` ng update @angular/cli @angular/core ``` +## Deploy for local manual testing +``` +ng serve --host 0.0.0.0 +``` + ## Build for production ``` -ng build --prod --base-href /methods/ +ng build --prod --base-href /catalogue/ ``` diff --git a/README_NG.md b/README_NG.md deleted file mode 100644 index 1555de6058c9ff89f6c352381b54390b8e2ffa34..0000000000000000000000000000000000000000 --- a/README_NG.md +++ /dev/null @@ -1,27 +0,0 @@ -# Methods - -This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.3.2. - -## Development server - -Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. - -## Code scaffolding - -Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. - -## Build - -Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. - -## Running unit tests - -Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). - -## Running end-to-end tests - -Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). - -## Further help - -To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). diff --git a/angular.json b/angular.json index e5398d40660876f6365b305efdcc4ccd8f391e08..2fca3ed0f6793f6931a63dcded789629558497e0 100644 --- a/angular.json +++ b/angular.json @@ -3,7 +3,7 @@ "version": 1, "newProjectRoot": "projects", "projects": { - "methods": { + "xtss-catalogue": { "root": "", "sourceRoot": "src", "projectType": "application", @@ -13,7 +13,7 @@ "build": { "builder": "@angular-devkit/build-angular:browser", "options": { - "outputPath": "dist/methods", + "outputPath": "dist/catalogue", "index": "src/index.html", "main": "src/main.ts", "polyfills": "src/polyfills.ts", @@ -58,23 +58,24 @@ "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { - "browserTarget": "methods:build" + "browserTarget": "xtss-catalogue:build" }, "configurations": { "production": { - "browserTarget": "methods:build:production" + "browserTarget": "xtss-catalogue:build:production" } } }, "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { - "browserTarget": "methods:build" + "browserTarget": "xtss-catalogue:build" } }, "test": { "builder": "@angular-devkit/build-angular:karma", "options": { + "codeCoverage": true, "main": "src/test.ts", "polyfills": "src/polyfills.ts", "tsConfig": "src/tsconfig.spec.json", @@ -103,7 +104,7 @@ } } }, - "methods-e2e": { + "xtss-catalogue-e2e": { "root": "e2e/", "projectType": "application", "prefix": "", @@ -112,11 +113,11 @@ "builder": "@angular-devkit/build-angular:protractor", "options": { "protractorConfig": "e2e/protractor.conf.js", - "devServerTarget": "methods:serve" + "devServerTarget": "xtss-catalogue:serve" }, "configurations": { "production": { - "devServerTarget": "methods:serve:production" + "devServerTarget": "xtss-catalogue:serve:production" } } }, @@ -132,5 +133,5 @@ } } }, - "defaultProject": "methods" + "defaultProject": "xtss-catalogue" } \ No newline at end of file diff --git a/e2e/src/app.e2e-spec.ts b/e2e/src/app.e2e-spec.ts index a685de4470bd7067d4ea85fb77bd5a8e823ebfe0..700bc8c12ba57c68319c4888661faea2eb4e7fc4 100644 --- a/e2e/src/app.e2e-spec.ts +++ b/e2e/src/app.e2e-spec.ts @@ -10,7 +10,7 @@ describe('workspace-project App', () => { it('should display welcome message', () => { page.navigateTo(); - expect(page.getTitleText()).toEqual('Welcome to methods!'); + expect(page.getTitleText()).toEqual('Welcome to xtss-catalogue!'); }); afterEach(async () => { diff --git a/package-lock.json b/package-lock.json index 3b719589ca40514bd2ac735a4976c375d386852c..525893fc553ea72f8964a06cf90d486f2cb41534 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,16 @@ { - "name": "methods", - "version": "0.0.0", + "name": "xtss-catalogue", + "version": "0.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { "@angular-devkit/architect": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.13.3.tgz", - "integrity": "sha512-89VL75bq3+h3m0jhzWNqXqW+HQcrihnM3i6eiUE6P81LcllP159JMlusAvB1LHLNc6Cc62wTq4BJr7KDILkPOA==", + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.13.8.tgz", + "integrity": "sha512-gxUs5rhnP576T8ZclKqxlspiChrqRtqaJo54wqNVFvYKEjRZKyMa+1AK6p0oD9zcIToEkcjknj3BbtQa27lLHg==", "dev": true, "requires": { - "@angular-devkit/core": "7.3.3", + "@angular-devkit/core": "7.3.8", "rxjs": "6.3.3" }, "dependencies": { @@ -26,16 +26,16 @@ } }, "@angular-devkit/build-angular": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.13.3.tgz", - "integrity": "sha512-UxD6UR/tXypMA4lqCiXLtcStI4wuIHLOJLwADmazndFjg1oLqH1onO6UQPHJ1drAUl+AzA5zTQZHzWYokxaLtg==", + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.13.8.tgz", + "integrity": "sha512-uRb8CKC0hUdcE+Fv2Ov9LJNelyjsiMuddBpo8pdTKCIHVVC6hvip9S/Z18Tvb207kKI3k7Dn+Ji1J63mCqmQzA==", "dev": true, "requires": { - "@angular-devkit/architect": "0.13.3", - "@angular-devkit/build-optimizer": "0.13.3", - "@angular-devkit/build-webpack": "0.13.3", - "@angular-devkit/core": "7.3.3", - "@ngtools/webpack": "7.3.3", + "@angular-devkit/architect": "0.13.8", + "@angular-devkit/build-optimizer": "0.13.8", + "@angular-devkit/build-webpack": "0.13.8", + "@angular-devkit/core": "7.3.8", + "@ngtools/webpack": "7.3.8", "ajv": "6.9.1", "autoprefixer": "9.4.6", "circular-dependency-plugin": "5.0.2", @@ -52,7 +52,7 @@ "mini-css-extract-plugin": "0.5.0", "minimatch": "3.0.4", "node-sass": "4.11.0", - "opn": "5.4.0", + "open": "6.0.0", "parse5": "4.0.0", "postcss": "7.0.14", "postcss-import": "12.0.1", @@ -63,7 +63,7 @@ "semver": "5.6.0", "source-map-loader": "0.2.4", "source-map-support": "0.5.10", - "speed-measure-webpack-plugin": "1.3.0", + "speed-measure-webpack-plugin": "1.3.1", "stats-webpack-plugin": "0.7.0", "style-loader": "0.23.1", "stylus": "0.54.5", @@ -90,9 +90,9 @@ } }, "@angular-devkit/build-optimizer": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.13.3.tgz", - "integrity": "sha512-lxM1icVFy3jyoQfWEGW8TG1M7LTl/Djc98MFBYp/lXoVo2JZoLxy7eo51sRuJFaB7/0mgMP2gs0FcU/Lr4gK+Q==", + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.13.8.tgz", + "integrity": "sha512-RvYxtsdYuvpFb1iivVixylSVN/Q8LsQ449uYuqEe3OsDjQBvUVG2fMLPOQjmKWhi0NC9WSsNiUluxLDNdvd0Vw==", "dev": true, "requires": { "loader-utils": "1.2.3", @@ -110,13 +110,13 @@ } }, "@angular-devkit/build-webpack": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.13.3.tgz", - "integrity": "sha512-o2ymctVCuz5GhKJH3LO1sl3AUbA4j7zlrqSGB5ToVRBn3GckJJnmfCZzr2SX5Ya4VofxVsIidsiZcawy4FpB2w==", + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.13.8.tgz", + "integrity": "sha512-WMyn1vUHyx+VfJKgYuEHrICwQzPMDTaUNB1zlvzZt9gX/9H+XnetrebeWBZCITPXHBw/377oA6wmiHWJ0yaZRw==", "dev": true, "requires": { - "@angular-devkit/architect": "0.13.3", - "@angular-devkit/core": "7.3.3", + "@angular-devkit/architect": "0.13.8", + "@angular-devkit/core": "7.3.8", "rxjs": "6.3.3" }, "dependencies": { @@ -132,9 +132,9 @@ } }, "@angular-devkit/core": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.3.tgz", - "integrity": "sha512-fosULDtMoDWrOyUzTmBkJccOy7zodo02kENyKai7vOv9EWfv9jytkVdNc+jl0ys9OE2QadvSYBo49jhnZxFXfQ==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.8.tgz", + "integrity": "sha512-3X9uzaZXFpm5o2TSzhD6wEOtVU32CgeytKjD1Scxj+uMMVo48SWLlKiFh312T+smI9ko7tOT8VqxglwYkWosgg==", "dev": true, "requires": { "ajv": "6.9.1", @@ -156,19 +156,19 @@ } }, "@angular-devkit/schematics": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-7.3.3.tgz", - "integrity": "sha512-SdDq9eKwceb6WLwci1fywtZ/kARR5CYyzi5dZIR1lOxrz00682uUBqH/X39mKdqc6eVqR7rtPceqNm6nQpOIMg==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-7.3.8.tgz", + "integrity": "sha512-mvaKoORZIaW/h0VNZ3IQWP0qThRCZRX6869FNlzV0jlW0mhn07XbiIGHCGGSCDRxS7qJ0VbuIVnKXntF+iDeWw==", "dev": true, "requires": { - "@angular-devkit/core": "7.3.3", + "@angular-devkit/core": "7.3.8", "rxjs": "6.3.3" }, "dependencies": { "@angular-devkit/core": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.3.tgz", - "integrity": "sha512-fosULDtMoDWrOyUzTmBkJccOy7zodo02kENyKai7vOv9EWfv9jytkVdNc+jl0ys9OE2QadvSYBo49jhnZxFXfQ==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.8.tgz", + "integrity": "sha512-3X9uzaZXFpm5o2TSzhD6wEOtVU32CgeytKjD1Scxj+uMMVo48SWLlKiFh312T+smI9ko7tOT8VqxglwYkWosgg==", "dev": true, "requires": { "ajv": "6.9.1", @@ -190,48 +190,48 @@ } }, "@angular/animations": { - "version": "7.2.7", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-7.2.7.tgz", - "integrity": "sha512-eU/wSkBmukZXCCe/epUl02xsKPauF+deMbncxBE+w/NmmWjJ77Q09iZAcgzM92RVXj2LsVYQXsNEBGT3X0hRZw==", + "version": "7.2.13", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-7.2.13.tgz", + "integrity": "sha512-Z0g0DthJnxTZ0dUc5BlojMq/0XIikhWzTqq0ym8w3G6jqBJD0OJ0jRCIfV0Leqlgzq6Jzvdrx0/JngBiKi5+uA==", "requires": { "tslib": "^1.9.0" } }, "@angular/cli": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-7.3.3.tgz", - "integrity": "sha512-dw1iBOYbQRN2l/BH21zDItDFC9KXgqeK0A/koDLDukjrUAnW/XVATjxGi+7EPlTpABTFhqu/rHZDy8aBglLDXQ==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-7.3.8.tgz", + "integrity": "sha512-5ldU1idvWstmRaavGZen9WRjfjIViERGt8NYuLLI7dgVLYOPF5TyFoTnpT5nxkiCopp4tPIcpbzPV394Bxmdtg==", "dev": true, "requires": { - "@angular-devkit/architect": "0.13.3", - "@angular-devkit/core": "7.3.3", - "@angular-devkit/schematics": "7.3.3", - "@schematics/angular": "7.3.3", - "@schematics/update": "0.13.3", + "@angular-devkit/architect": "0.13.8", + "@angular-devkit/core": "7.3.8", + "@angular-devkit/schematics": "7.3.8", + "@schematics/angular": "7.3.8", + "@schematics/update": "0.13.8", "@yarnpkg/lockfile": "1.1.0", "ini": "1.3.5", "inquirer": "6.2.1", "npm-package-arg": "6.1.0", - "opn": "5.4.0", + "open": "6.0.0", "pacote": "9.4.0", "semver": "5.6.0", "symbol-observable": "1.2.0" }, "dependencies": { "@angular-devkit/architect": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.13.3.tgz", - "integrity": "sha512-89VL75bq3+h3m0jhzWNqXqW+HQcrihnM3i6eiUE6P81LcllP159JMlusAvB1LHLNc6Cc62wTq4BJr7KDILkPOA==", + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.13.8.tgz", + "integrity": "sha512-gxUs5rhnP576T8ZclKqxlspiChrqRtqaJo54wqNVFvYKEjRZKyMa+1AK6p0oD9zcIToEkcjknj3BbtQa27lLHg==", "dev": true, "requires": { - "@angular-devkit/core": "7.3.3", + "@angular-devkit/core": "7.3.8", "rxjs": "6.3.3" } }, "@angular-devkit/core": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.3.tgz", - "integrity": "sha512-fosULDtMoDWrOyUzTmBkJccOy7zodo02kENyKai7vOv9EWfv9jytkVdNc+jl0ys9OE2QadvSYBo49jhnZxFXfQ==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.8.tgz", + "integrity": "sha512-3X9uzaZXFpm5o2TSzhD6wEOtVU32CgeytKjD1Scxj+uMMVo48SWLlKiFh312T+smI9ko7tOT8VqxglwYkWosgg==", "dev": true, "requires": { "ajv": "6.9.1", @@ -253,25 +253,25 @@ } }, "@angular/common": { - "version": "7.2.7", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-7.2.7.tgz", - "integrity": "sha512-U1l2CIcmpTAJMWcyTXI9qt1E8CxwKNW1vr6XWZo4X5ziCIzf7RvClzK7Ci5KZKkoPJrJqBJu661Q75Yt22dJsg==", + "version": "7.2.13", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-7.2.13.tgz", + "integrity": "sha512-NYlzUkFVgjLg9VB6/lkd8ZV0ZezSiv9vlg+26wOyw7x+gahRrm5WMAGF7eBLrXoZPEaoOO0uhKWKo7oiA0aufA==", "requires": { "tslib": "^1.9.0" } }, "@angular/compiler": { - "version": "7.2.7", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-7.2.7.tgz", - "integrity": "sha512-e61YVxW5x4w+X4yjGaptYoJIja7HwH0+8FFEaH6VuPl/DrK8wP4HDMhLo4NzdgeZKLR2jBIQSqLmoM8W7UXcqw==", + "version": "7.2.13", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-7.2.13.tgz", + "integrity": "sha512-k0IvaozNIlrPKUNF3M/NXMb/jfHBCDO9uRYA6h+84FFY4Y9po40c7YXfsfUxGKwouTWyemaxy9iXlLEnd3ELSQ==", "requires": { "tslib": "^1.9.0" } }, "@angular/compiler-cli": { - "version": "7.2.7", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-7.2.7.tgz", - "integrity": "sha512-UPWROJzBLejgNf+aqgEUXYts8UiFOl2IavDhS/olA9irszv2lNFj9Yqr8OKdy0jK/lKaipZog3VZEx8g5dNeBA==", + "version": "7.2.13", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-7.2.13.tgz", + "integrity": "sha512-UpA6V+GCY9qKj5j6tvzun2DJNjqRKjCrQgJqD5BIf4FTAKjVgqOvh++d23tbdltdjXlbHqUVRgfeXltbO91fWg==", "dev": true, "requires": { "canonical-path": "1.0.0", @@ -300,9 +300,9 @@ "dev": true }, "chokidar": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.2.tgz", - "integrity": "sha512-IwXUx0FXc5ibYmPC2XeEj5mpXoV66sR+t3jqu2NS2GYwCktt3KF1/Qqjws/NkegajBA4RbZ5+DDwlOiJsxDHEg==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.5.tgz", + "integrity": "sha512-i0TprVWp+Kj4WRPtInjexJ8Q+BqTE909VpH8xVhXrJkoc5QC8VO9TryGOqTr+2hljzc1sC62t22h5tZePodM/A==", "dev": true, "requires": { "anymatch": "^2.0.0", @@ -316,7 +316,7 @@ "normalize-path": "^3.0.0", "path-is-absolute": "^1.0.0", "readdirp": "^2.2.1", - "upath": "^1.1.0" + "upath": "^1.1.1" } }, "cross-spawn": { @@ -456,6 +456,12 @@ "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true }, + "upath": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", + "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==", + "dev": true + }, "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", @@ -501,47 +507,47 @@ } }, "@angular/core": { - "version": "7.2.7", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-7.2.7.tgz", - "integrity": "sha512-E7qjMQdS77SbRROKu13VsfL+MJN52eTlrU0SzEAFGUOgdvbmDYJOaEwjqrouKpYZ0pul8KOoalvlPB7oVflC7A==", + "version": "7.2.13", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-7.2.13.tgz", + "integrity": "sha512-vHD69xxDDSQaE8KfHeY2STJSd3xgfsz3/meBCAnT+Bpq9LqxL8DuPlrkC0kyBa2vyj/BwPR3CJNTaQrZcszJ/w==", "requires": { "tslib": "^1.9.0" } }, "@angular/forms": { - "version": "7.2.7", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-7.2.7.tgz", - "integrity": "sha512-2gBs+BG2cMPsHq9JVEzmu2Ev539zjfHmr6cna2W38KLXeGbNf42rbbMUXpYD8cndY0QTYcnwfMpRNIl9zKRZnw==", + "version": "7.2.13", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-7.2.13.tgz", + "integrity": "sha512-dBz7kYa8XoCKxZ+3EvYt6CxHZhM9Qbn3uYkLMsPA+NC6GtIt/tmYn1kNn+YWgVWZtWLvYRaOtYiCuMUJaRNQQw==", "requires": { "tslib": "^1.9.0" } }, "@angular/language-service": { - "version": "7.2.7", - "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-7.2.7.tgz", - "integrity": "sha512-d3iCBpOfgLNSGMrtqZvN6NHZIEnKD2MV8Hz4WsRLU4WY0RbshZj5dqx2nO3YRT2tACpAvhWBQoYvtLpTCPzsMA==", + "version": "7.2.13", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-7.2.13.tgz", + "integrity": "sha512-1bNWJpwH9wB0JybkbjdQp9J4bGmGxJX6BG7Mz3188Wc4J+aNy696Gc6IaJs7tFK8VXAdJrTJ5jGr9Oiu+ATe8w==", "dev": true }, "@angular/platform-browser": { - "version": "7.2.7", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-7.2.7.tgz", - "integrity": "sha512-9C3ffZs0ZUw+dYg1oJKiONf64UKTdAzIOaTQXTrVrCa3oN7Jb2tUfmpenmB+ATRxwhL2n7Yi725YWwxY2FwqvQ==", + "version": "7.2.13", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-7.2.13.tgz", + "integrity": "sha512-4n9De4sOwVoYHh6IGO2+UQIjABqGAXk4RdrEGpXqPBHCNO4sF43c2JsXbPTU4kjPVwTwposfLlKEOjTXfwxGow==", "requires": { "tslib": "^1.9.0" } }, "@angular/platform-browser-dynamic": { - "version": "7.2.7", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-7.2.7.tgz", - "integrity": "sha512-3nlcwCZOzlKw/4CMJ4zy1JEVy8Ky4KyLRRePLledOMdsGbuDIoq/kyAnBzg295Xe9ovBxv8cmuSkShci+s/x8g==", + "version": "7.2.13", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-7.2.13.tgz", + "integrity": "sha512-3+/BzrNLQ/Tn1hoPal3fvIeB3S/P3e00gHcH3oK+hfACYgWxLE1oIHL+w4NE2eTIJbHfphKhuascMaOH5WNlkg==", "requires": { "tslib": "^1.9.0" } }, "@angular/router": { - "version": "7.2.7", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-7.2.7.tgz", - "integrity": "sha512-59+M8+IH7V2NPPqWw2mwdg+kh/jfwQcXE0tB8iZ5V2ldACPucY/Td6qiT5H6t7EkELtvkKJwS6vKFV22qdRp3w==", + "version": "7.2.13", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-7.2.13.tgz", + "integrity": "sha512-pTdJT9TXk1A9YMa6C2zRRqLB4GPGMSik838P7n+yGrzhdybiudZU9T3egcxDRCWQMjsobVBRKLEUn405n3Hjgg==", "requires": { "tslib": "^1.9.0" } @@ -707,12 +713,12 @@ } }, "@ngtools/webpack": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-7.3.3.tgz", - "integrity": "sha512-G/1P00XHWVrKT3qoSyy7yAPT5/fuja84YifcGg/2SwmNNo4hTXxWhqec0/uHwgQr6nYhGDyzwwXYeKKyQkcfgw==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-7.3.8.tgz", + "integrity": "sha512-gfjSKz+F/2T4tZHpnQ1XqelKP/CIfI87XdoHsOI53ceTUrAkVKsOb3ULmEfkcdsdQZ/HhmCiLivcutHcW8xkhQ==", "dev": true, "requires": { - "@angular-devkit/core": "7.3.3", + "@angular-devkit/core": "7.3.8", "enhanced-resolve": "4.1.0", "rxjs": "6.3.3", "tree-kill": "1.2.1", @@ -747,20 +753,20 @@ } }, "@schematics/angular": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-7.3.3.tgz", - "integrity": "sha512-HbH8vajYPka0xGcFAN5IUBx8n8SFMQLFb9di2dJCOBaEakbKVkk8qtOpil54oFQbx7DFCvutq/p0u42JfEbuMQ==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-7.3.8.tgz", + "integrity": "sha512-7o90bnIxXNpJhWPDY/zCedcG6KMIihz7a4UQe6UdlhEX21MNZLYFiDiR5Vmsx39wjm2EfPh3JTuBIHGmMCXkQQ==", "dev": true, "requires": { - "@angular-devkit/core": "7.3.3", - "@angular-devkit/schematics": "7.3.3", + "@angular-devkit/core": "7.3.8", + "@angular-devkit/schematics": "7.3.8", "typescript": "3.2.4" }, "dependencies": { "@angular-devkit/core": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.3.tgz", - "integrity": "sha512-fosULDtMoDWrOyUzTmBkJccOy7zodo02kENyKai7vOv9EWfv9jytkVdNc+jl0ys9OE2QadvSYBo49jhnZxFXfQ==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.8.tgz", + "integrity": "sha512-3X9uzaZXFpm5o2TSzhD6wEOtVU32CgeytKjD1Scxj+uMMVo48SWLlKiFh312T+smI9ko7tOT8VqxglwYkWosgg==", "dev": true, "requires": { "ajv": "6.9.1", @@ -782,13 +788,13 @@ } }, "@schematics/update": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.13.3.tgz", - "integrity": "sha512-sCOFQ62dd7VdEGiSUJNZshNI31ODwpJjn2WIvFgZLt6sdHHun67s/JOvOUq4mxx6I74oD6RPJPF4AP5sigVxxg==", + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.13.8.tgz", + "integrity": "sha512-2jP9w7Nnn24jOdrJtWjoS9LsNPmO9/Eu/+gDxBAVERCqR71mtNW+DopgWDtxleE9jri/pZWrHwShGFCSS7w23g==", "dev": true, "requires": { - "@angular-devkit/core": "7.3.3", - "@angular-devkit/schematics": "7.3.3", + "@angular-devkit/core": "7.3.8", + "@angular-devkit/schematics": "7.3.8", "@yarnpkg/lockfile": "1.1.0", "ini": "1.3.5", "pacote": "9.4.0", @@ -798,9 +804,9 @@ }, "dependencies": { "@angular-devkit/core": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.3.tgz", - "integrity": "sha512-fosULDtMoDWrOyUzTmBkJccOy7zodo02kENyKai7vOv9EWfv9jytkVdNc+jl0ys9OE2QadvSYBo49jhnZxFXfQ==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.8.tgz", + "integrity": "sha512-3X9uzaZXFpm5o2TSzhD6wEOtVU32CgeytKjD1Scxj+uMMVo48SWLlKiFh312T+smI9ko7tOT8VqxglwYkWosgg==", "dev": true, "requires": { "ajv": "6.9.1", @@ -1169,9 +1175,9 @@ "dev": true }, "ansi-colors": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", - "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", + "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", "dev": true }, "ansi-escapes": { @@ -1283,12 +1289,6 @@ "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", "dev": true }, - "array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", - "dev": true - }, "array-union": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", @@ -1894,14 +1894,14 @@ } }, "browserslist": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.4.2.tgz", - "integrity": "sha512-ISS/AIAiHERJ3d45Fz0AVYKkgcy+F/eJHzKEvv1j0wwKGKD9T3BrwKr/5g45L+Y4XIK5PlTqefHciRFcfE1Jxg==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.5.4.tgz", + "integrity": "sha512-rAjx494LMjqKnMPhFkuLmLp8JWEX0o8ADTGeAbOqaF+XCvYLreZrG5uVjnPBlAQ8REZK4pzXGvp0bWgrFtKaag==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30000939", - "electron-to-chromium": "^1.3.113", - "node-releases": "^1.1.8" + "caniuse-lite": "^1.0.30000955", + "electron-to-chromium": "^1.3.122", + "node-releases": "^1.1.13" } }, "browserstack": { @@ -2051,9 +2051,9 @@ } }, "caniuse-lite": { - "version": "1.0.30000939", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000939.tgz", - "integrity": "sha512-oXB23ImDJOgQpGjRv1tCtzAvJr4/OvrHi5SO2vUgB0g0xpdZZoA/BxfImiWfdwoYdUTtQrPsXsvYU/dmCSM8gg==", + "version": "1.0.30000960", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000960.tgz", + "integrity": "sha512-7nK5qs17icQaX6V3/RYrJkOsZyRNnroA4+ZwxaKJzIKy+crIy0Mz5CBlLySd2SNV+4nbUZeqeNfiaEieUBu3aA==", "dev": true }, "canonical-path": { @@ -2148,12 +2148,6 @@ "integrity": "sha512-oC7/DVAyfcY3UWKm0sN/oVoDedQDQiw/vIiAnuTWTpE5s0zWf7l3WY417Xw/Fbi/QbAjctAkxgMiS9P0s3zkmA==", "dev": true }, - "circular-json": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.5.9.tgz", - "integrity": "sha512-4ivwqHpIFJZBuhN3g/pEcdbnGUywkBblloGbkglyloVjjR3uT6tieI89MVOfbP2tHX5sgb01FuLgAOzebNlJNQ==", - "dev": true - }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -2309,15 +2303,6 @@ "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", "dev": true }, - "combine-lists": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/combine-lists/-/combine-lists-1.0.1.tgz", - "integrity": "sha1-RYwH4J4NkA/Ci3Cj/sLazR0st/Y=", - "dev": true, - "requires": { - "lodash": "^4.5.0" - } - }, "combined-stream": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", @@ -2373,16 +2358,16 @@ } }, "compression": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.3.tgz", - "integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", "dev": true, "requires": { "accepts": "~1.3.5", "bytes": "3.0.0", - "compressible": "~2.0.14", + "compressible": "~2.0.16", "debug": "2.6.9", - "on-headers": "~1.0.1", + "on-headers": "~1.0.2", "safe-buffer": "5.1.2", "vary": "~1.1.2" } @@ -2703,9 +2688,9 @@ } }, "date-format": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-1.2.0.tgz", - "integrity": "sha1-YV6CjiM90aubua4JUODOzPpuytg=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.0.0.tgz", + "integrity": "sha512-M6UqVvZVgFYqZL1SfHsRGIQSz3ZL+qgbsV5Lp1Vj61LZVYuEwcMXYay7DRDtYs2HQQBK5hQtQ0fD9aEJ89V0LA==", "dev": true }, "date-now": { @@ -3006,9 +2991,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.113", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.113.tgz", - "integrity": "sha512-De+lPAxEcpxvqPTyZAXELNpRZXABRxf+uL/rSykstQhzj/B0l1150G/ExIIxKc16lI89Hgz81J0BHAcbTqK49g==", + "version": "1.3.124", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.124.tgz", + "integrity": "sha512-glecGr/kFdfeXUHOHAWvGcXrxNU+1wSO/t5B23tT1dtlvYB26GY8aHzZSWD7HqhqC800Lr+w/hQul6C5AF542w==", "dev": true }, "elliptic": { @@ -3193,9 +3178,9 @@ "dev": true }, "eslint-scope": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", - "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", "dev": true, "requires": { "esrecurse": "^4.1.0", @@ -3302,34 +3287,6 @@ "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", "dev": true }, - "expand-braces": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/expand-braces/-/expand-braces-0.1.2.tgz", - "integrity": "sha1-SIsdHSRRyz06axks/AMPRMWFX+o=", - "dev": true, - "requires": { - "array-slice": "^0.2.3", - "array-unique": "^0.2.1", - "braces": "^0.1.2" - }, - "dependencies": { - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true - }, - "braces": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-0.1.5.tgz", - "integrity": "sha1-wIVxEIUpHYt1/ddOqw+FlygHEeY=", - "dev": true, - "requires": { - "expand-range": "^0.1.0" - } - } - } - }, "expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -3365,30 +3322,6 @@ } } }, - "expand-range": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-0.1.1.tgz", - "integrity": "sha1-TLjtoJk8pW+k9B/ELzy7TMrf8EQ=", - "dev": true, - "requires": { - "is-number": "^0.1.1", - "repeat-string": "^0.2.2" - }, - "dependencies": { - "is-number": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-0.1.1.tgz", - "integrity": "sha1-aaevEWlj1HIG7JvZtIoUIW8eOAY=", - "dev": true - }, - "repeat-string": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-0.2.2.tgz", - "integrity": "sha1-x6jTI2BoNiBZp+RlH8aITosftK4=", - "dev": true - } - } - }, "express": { "version": "4.16.4", "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", @@ -3789,6 +3722,17 @@ "null-check": "^1.0.0" } }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, "fs-minipass": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", @@ -4524,12 +4468,12 @@ "dev": true }, "handlebars": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.0.tgz", - "integrity": "sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", + "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", "dev": true, "requires": { - "async": "^2.5.0", + "neo-async": "^2.6.0", "optimist": "^0.6.1", "source-map": "^0.6.1", "uglify-js": "^3.1.4" @@ -4828,9 +4772,9 @@ } }, "ieee754": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz", - "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", "dev": true }, "iferr": { @@ -4915,9 +4859,9 @@ } }, "p-limit": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", - "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -4933,9 +4877,9 @@ } }, "p-try": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", - "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, "pkg-dir": { @@ -5055,18 +4999,18 @@ } }, "strip-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.0.0.tgz", - "integrity": "sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "^4.0.0" + "ansi-regex": "^4.1.0" }, "dependencies": { "ansi-regex": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", - "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true } } @@ -5615,9 +5559,9 @@ "dev": true }, "js-yaml": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.1.tgz", - "integrity": "sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA==", + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", "dev": true, "requires": { "argparse": "^1.0.7", @@ -5675,6 +5619,15 @@ "minimist": "^1.2.0" } }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, "jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", @@ -5747,28 +5700,27 @@ } }, "karma": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/karma/-/karma-3.1.4.tgz", - "integrity": "sha512-31Vo8Qr5glN+dZEVIpnPCxEGleqE0EY6CtC2X9TagRV3rRQ3SNrvfhddICkJgUK3AgqpeKSZau03QumTGhGoSw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/karma/-/karma-4.0.1.tgz", + "integrity": "sha512-ind+4s03BqIXas7ZmraV3/kc5+mnqwCd+VDX1FndS6jxbt03kQKX2vXrWxNLuCjVYmhMwOZosAEKMM0a2q7w7A==", "dev": true, "requires": { "bluebird": "^3.3.0", "body-parser": "^1.16.1", + "braces": "^2.3.2", "chokidar": "^2.0.3", "colors": "^1.1.0", - "combine-lists": "^1.0.0", "connect": "^3.6.0", "core-js": "^2.2.0", "di": "^0.0.1", "dom-serialize": "^2.2.0", - "expand-braces": "^0.1.1", "flatted": "^2.0.0", "glob": "^7.1.1", "graceful-fs": "^4.1.2", "http-proxy": "^1.13.0", "isbinaryfile": "^3.0.0", - "lodash": "^4.17.5", - "log4js": "^3.0.0", + "lodash": "^4.17.11", + "log4js": "^4.0.0", "mime": "^2.3.1", "minimatch": "^3.0.2", "optimist": "^0.6.1", @@ -5783,9 +5735,9 @@ }, "dependencies": { "mime": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", - "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.2.tgz", + "integrity": "sha512-zJBfZDkwRu+j3Pdd2aHsR5GfH2jIWhmL1ZzBoc+X+3JEti2hbArWcyJ+1laC1D2/U/W1a/+Cegj0/OnEU2ybjg==", "dev": true }, "source-map": { @@ -6006,22 +5958,22 @@ "dev": true }, "log4js": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-3.0.6.tgz", - "integrity": "sha512-ezXZk6oPJCWL483zj64pNkMuY/NcRX5MPiB0zE6tjZM137aeusrOnW1ecxgF9cmwMWkBMhjteQxBPoZBh9FDxQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-4.1.0.tgz", + "integrity": "sha512-eDa+zZPeVEeK6QGJAePyXM6pg4P3n3TO5rX9iZMVY48JshsTyLJZLIL5HipI1kQ2qLsSyOpUqNND/C5H4WhhiA==", "dev": true, "requires": { - "circular-json": "^0.5.5", - "date-format": "^1.2.0", - "debug": "^3.1.0", + "date-format": "^2.0.0", + "debug": "^4.1.1", + "flatted": "^2.0.0", "rfdc": "^1.1.2", - "streamroller": "0.7.0" + "streamroller": "^1.0.4" }, "dependencies": { "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { "ms": "^2.1.1" @@ -6241,14 +6193,22 @@ "dev": true }, "mem": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.1.0.tgz", - "integrity": "sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", "dev": true, "requires": { "map-age-cleaner": "^0.1.1", - "mimic-fn": "^1.0.0", + "mimic-fn": "^2.0.0", "p-is-promise": "^2.0.0" + }, + "dependencies": { + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + } } }, "memory-fs": { @@ -6663,9 +6623,9 @@ } }, "node-releases": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.8.tgz", - "integrity": "sha512-gQm+K9mGCiT/NXHy+V/ZZS1N/LOaGGqRAAJJs3X9Ah1g+CIbRcBgNyoNYQ+SEtcyAtB9KqDruu+fF7nWjsqRaA==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.14.tgz", + "integrity": "sha512-d58EpVZRhQE60kWiWUaaPlK9dyC4zg3ZoMcHcky2d4hDksyQj0rUozwInOl0C66mBsqo01Tuns8AvxnL5S7PKg==", "dev": true, "requires": { "semver": "^5.3.0" @@ -6965,10 +6925,19 @@ "mimic-fn": "^1.0.0" } }, + "open": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/open/-/open-6.0.0.tgz", + "integrity": "sha512-/yb5mVZBz7mHLySMiSj2DcLtMBbFPJk5JBKEkHVZFxZAPzeg3L026O0T+lbdz1B2nyDnkClRSwRQJdeVUIF7zw==", + "dev": true, + "requires": { + "is-wsl": "^1.1.0" + } + }, "opn": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.4.0.tgz", - "integrity": "sha512-YF9MNdVy/0qvJvDtunAOzFw9iasOQHpVthTCvGzxt61Il64AYSGdK+rYwld7NAfk9qJ7dt+hymBNSc9LNYS+Sw==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", + "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", "dev": true, "requires": { "is-wsl": "^1.1.0" @@ -7052,9 +7021,9 @@ "dev": true }, "p-is-promise": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.0.0.tgz", - "integrity": "sha512-pzQPhYMCAgLAKPWD2jC3Se9fEfrD9npNos0y150EeqZll7akhEgGhTW/slB6lHku8AvYGiJ+YJ5hfHKePPgFWg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", "dev": true }, "p-limit": { @@ -7754,9 +7723,9 @@ "dev": true }, "querystringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.0.tgz", - "integrity": "sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", + "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", "dev": true }, "randombytes": { @@ -8771,9 +8740,9 @@ } }, "socks": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.2.3.tgz", - "integrity": "sha512-+2r83WaRT3PXYoO/1z+RDEBE7Z2f9YcdQnJ0K/ncXXbV5gJ6wYfNAebYFYiiUjM6E4JyXnPY8cimwyvFYHVUUA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.3.2.tgz", + "integrity": "sha512-pCpjxQgOByDHLlNqlnh/mNSAxIUkyBBuwwhTcV+enZGbDaClPvHdvm6uvOwZfFJkam7cGhBNbb4JxiP8UZkRvQ==", "dev": true, "requires": { "ip": "^1.1.5", @@ -8781,15 +8750,21 @@ } }, "socks-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.1.tgz", - "integrity": "sha512-Kezx6/VBguXOsEe5oU3lXYyKMi4+gva72TwJ7pQY5JfqUx2nMk7NXA6z/mpNqIlfQjWYVfeuNvQjexiTaTn6Nw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz", + "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==", "dev": true, "requires": { - "agent-base": "~4.2.0", - "socks": "~2.2.0" + "agent-base": "~4.2.1", + "socks": "~2.3.2" } }, + "sonar-scanner": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/sonar-scanner/-/sonar-scanner-3.1.0.tgz", + "integrity": "sha1-UcHBEB9UuYq8XYVlIJsdkjKXk0M=", + "dev": true + }, "source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -8947,9 +8922,9 @@ "dev": true }, "readable-stream": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.1.1.tgz", - "integrity": "sha512-DkN66hPyqDhnIQ6Jcsvx9bFjhw214O4poMBcIMgPVpQvNy9a0e0Uhg5SqySyDKAmUlwt8LonTBz1ezOnM8pUdA==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.3.0.tgz", + "integrity": "sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -8960,9 +8935,9 @@ } }, "speed-measure-webpack-plugin": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/speed-measure-webpack-plugin/-/speed-measure-webpack-plugin-1.3.0.tgz", - "integrity": "sha512-b9Yd0TrzceMVYSbuamM1sFsGM1oVfyFTM22gOoyLhymNvBVApuYpkdFOgYkKJpN/KhTpcCYcTGHg7X+FJ33Vvw==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/speed-measure-webpack-plugin/-/speed-measure-webpack-plugin-1.3.1.tgz", + "integrity": "sha512-qVIkJvbtS9j/UeZumbdfz0vg+QfG/zxonAjzefZrqzkr7xOncLVXkeGbTpzd1gjCBM4PmVNkWlkeTVhgskAGSQ==", "dev": true, "requires": { "chalk": "^2.0.1" @@ -9095,15 +9070,16 @@ "dev": true }, "streamroller": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-0.7.0.tgz", - "integrity": "sha512-WREzfy0r0zUqp3lGO096wRuUp7ho1X6uo/7DJfTlEi0Iv/4gT7YHqXDjKC2ioVGBZtE8QzsQD9nx1nIuoZ57jQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-1.0.4.tgz", + "integrity": "sha512-Wc2Gm5ygjSX8ZpW9J7Y9FwiSzTlKSvcl0FTTMd3rn7RoxDXpBW+xD9TY5sWL2n0UR61COB0LG1BQvN6nTUQbLQ==", "dev": true, "requires": { - "date-format": "^1.2.0", + "async": "^2.6.1", + "date-format": "^2.0.0", "debug": "^3.1.0", - "mkdirp": "^0.5.1", - "readable-stream": "^2.3.0" + "fs-extra": "^7.0.0", + "lodash": "^4.17.10" }, "dependencies": { "debug": { @@ -9254,9 +9230,9 @@ "dev": true }, "tapable": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.1.tgz", - "integrity": "sha512-9I2ydhj8Z9veORCw5PRm4u9uebCn0mcCa6scWoNcbZ6dAtoo2618u9UUzxgmsCOreJpqDDuv61LvwofW7hLcBA==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", "dev": true }, "tar": { @@ -9272,16 +9248,22 @@ } }, "terser": { - "version": "3.16.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-3.16.1.tgz", - "integrity": "sha512-JDJjgleBROeek2iBcSNzOHLKsB/MdDf+E/BOAJ0Tk9r7p9/fVobfv7LMJ/g/k3v9SXdmjZnIlFd5nfn/Rt0Xow==", + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-3.17.0.tgz", + "integrity": "sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ==", "dev": true, "requires": { - "commander": "~2.17.1", + "commander": "^2.19.0", "source-map": "~0.6.1", - "source-map-support": "~0.5.9" + "source-map-support": "~0.5.10" }, "dependencies": { + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "dev": true + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -9329,13 +9311,13 @@ } }, "find-cache-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.0.0.tgz", - "integrity": "sha512-LDUY6V1Xs5eFskUVYtIwatojt6+9xC9Chnlk/jYOOvn3FAFfSaWddxahDGyNHh0b2dMXa6YW2m0tk8TdVaXHlA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", "dev": true, "requires": { "commondir": "^1.0.1", - "make-dir": "^1.0.0", + "make-dir": "^2.0.0", "pkg-dir": "^3.0.0" } }, @@ -9367,6 +9349,16 @@ "yallist": "^3.0.2" } }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, "mississippi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", @@ -9386,9 +9378,9 @@ } }, "p-limit": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", - "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -9404,9 +9396,15 @@ } }, "p-try": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", - "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true }, "pkg-dir": { @@ -9770,6 +9768,12 @@ "imurmurhash": "^0.1.4" } }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -9856,9 +9860,9 @@ } }, "url-parse": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.4.tgz", - "integrity": "sha512-/92DTTorg4JjktLNLe6GPS2/RvAd/RGr6LuktmWSMLEOa6rjnlrFXNgSbSmkNvCoL2T028A0a1JaJLzRMlFoHg==", + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.6.tgz", + "integrity": "sha512-/B8AD9iQ01seoXmXf9z/MjLZQIdOoYl/+gvsQF6+mpnxaTfG9P7srYaiqaDMyKkR36XMXfhqSHss5MyFAO8lew==", "dev": true, "requires": { "querystringify": "^2.0.0", @@ -10073,9 +10077,9 @@ }, "dependencies": { "mime": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", - "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.2.tgz", + "integrity": "sha512-zJBfZDkwRu+j3Pdd2aHsR5GfH2jIWhmL1ZzBoc+X+3JEti2hbArWcyJ+1laC1D2/U/W1a/+Cegj0/OnEU2ybjg==", "dev": true } } @@ -10248,9 +10252,9 @@ } }, "mime": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", - "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.2.tgz", + "integrity": "sha512-zJBfZDkwRu+j3Pdd2aHsR5GfH2jIWhmL1ZzBoc+X+3JEti2hbArWcyJ+1laC1D2/U/W1a/+Cegj0/OnEU2ybjg==", "dev": true }, "ms": { @@ -10271,9 +10275,9 @@ } }, "p-limit": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", - "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -10289,9 +10293,9 @@ } }, "p-try": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", - "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, "pump": { diff --git a/package.json b/package.json index 470de6e947ed6f171f1b5c2a48b63eb35cb11812..2f26860726a6fe76b634102aae0be5f74119d650 100644 --- a/package.json +++ b/package.json @@ -1,24 +1,27 @@ { - "name": "methods", - "version": "0.0.0", + "name": "xtss-catalogue", + "version": "0.2.0", "scripts": { "ng": "ng", "start": "ng serve", "build": "ng build", "test": "ng test", + "test-headless": "ng test --watch=false --browsers=ChromiumHeadless", + "test-docker": "ng test --watch=false --browsers=ChromiumDocker", "lint": "ng lint", - "e2e": "ng e2e" + "e2e": "ng e2e", + "sonar": "sonar-scanner" }, "private": true, "dependencies": { - "@angular/animations": "^7.2.7", - "@angular/common": "^7.2.7", - "@angular/compiler": "^7.2.7", - "@angular/core": "^7.2.7", - "@angular/forms": "^7.2.7", - "@angular/platform-browser": "^7.2.7", - "@angular/platform-browser-dynamic": "^7.2.7", - "@angular/router": "^7.2.7", + "@angular/animations": "^7.2.13", + "@angular/common": "^7.2.13", + "@angular/compiler": "^7.2.13", + "@angular/core": "^7.2.13", + "@angular/forms": "^7.2.13", + "@angular/platform-browser": "^7.2.13", + "@angular/platform-browser-dynamic": "^7.2.13", + "@angular/router": "^7.2.13", "@ngx-translate/core": "^11.0.1", "@ngx-translate/http-loader": "^4.0.0", "bootstrap": "^4.3.1", @@ -28,22 +31,23 @@ "zone.js": "~0.8.26" }, "devDependencies": { - "@angular-devkit/build-angular": "^0.13.3", - "@angular/cli": "~7.3.3", - "@angular/compiler-cli": "^7.2.7", - "@angular/language-service": "^7.2.7", + "@angular-devkit/build-angular": "^0.13.8", + "@angular/cli": "~7.3.8", + "@angular/compiler-cli": "^7.2.13", + "@angular/language-service": "^7.2.13", "@types/jasmine": "~2.8.8", "@types/jasminewd2": "~2.0.3", "@types/node": "~8.9.4", "codelyzer": "~4.5.0", "jasmine-core": "~2.99.1", "jasmine-spec-reporter": "~4.2.1", - "karma": "~3.1.1", + "karma": "^4.0.1", "karma-chrome-launcher": "~2.2.0", "karma-coverage-istanbul-reporter": "~2.0.1", "karma-jasmine": "~1.1.2", "karma-jasmine-html-reporter": "^0.2.2", "protractor": "~5.4.0", + "sonar-scanner": "^3.1.0", "ts-node": "~7.0.0", "tslint": "~5.11.0", "typescript": "~3.2.2" diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000000000000000000000000000000000000..4661a59c4032c9ede3725e028d0575c5d164621b --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,10 @@ +sonar.projectKey=xtss-catalogue +sonar.sources=src +sonar.sourceEncoding=UTF-8 +sonar.exclusions=**/node_modules/**,**/*.spec.ts,src/environments/**,src/karma.conf.js,src/main.ts +sonar.tests=src +sonar.test.inclusions=**/*.spec.ts +sonar.typescript.lcov.reportPaths=coverage/xtss-catalogue/lcov.info +sonar.host.url=http://localhost:9000 +#sonar.host.url=https:// +#sonar.login= diff --git a/src/app/app-routing.module.spec.ts b/src/app/app-routing.module.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..3f287aa856b76e3d23f58497430b224315f8f2c6 --- /dev/null +++ b/src/app/app-routing.module.spec.ts @@ -0,0 +1,13 @@ +import { AppRoutingModule } from './app-routing.module'; + +describe('AppRoutingModule', () => { + let module: AppRoutingModule; + + beforeEach(() => { + module = new AppRoutingModule(); + }); + + it('should be created', () => { + expect(module).toBeTruthy(); + }); +}); diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 169a2dcc63e9532d1070bb158d00acaef63fe666..354d9273cde60fc900f1b9c4b2349c969f41d159 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -15,4 +15,4 @@ const routes: Routes = [ imports: [ RouterModule.forRoot(routes, {scrollPositionRestoration: 'enabled'}) ], exports: [ RouterModule ] }) -export class AppRoutingModule {} \ No newline at end of file +export class AppRoutingModule {} diff --git a/src/app/app.component.css b/src/app/app.component.css deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/src/app/app.component.html b/src/app/app.component.html index 742bd73a7a45484d273a74cf395b0a548752b02f..589078ee142d41d7c4385735ed76e8fc11d844c7 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index a4fd87655d25dc65ee96b128b7ce068653ce8379..c50815f65c22916a61a520d09e79ea0b413031e0 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -1,5 +1,5 @@ import { TestBed, async } from '@angular/core/testing'; -import {RouterTestingModule} from '@angular/router/testing' +import { RouterTestingModule } from '@angular/router/testing'; import { AppComponent } from './app.component'; describe('AppComponent', () => { diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 68478a86238a14c44445df8301d49bce462ed788..ead0de4491ac51c7a45cbf97c9f487cc61acc19e 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -2,8 +2,7 @@ import { Component } from '@angular/core'; @Component({ selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.css'] + templateUrl: './app.component.html' }) export class AppComponent { } diff --git a/src/app/app.config-mock.ts b/src/app/app.config-mock.ts new file mode 100644 index 0000000000000000000000000000000000000000..208efe5db5f5087337826d597d1fff1c12f01b9d --- /dev/null +++ b/src/app/app.config-mock.ts @@ -0,0 +1,34 @@ +import { Injectable } from '@angular/core'; +import { AppConfig } from './app.config'; + +@Injectable() +export class AppConfigMock extends AppConfig { + private configMock: any = { + MAX_LIMIT: 1000000, + DEFAULT_LIMIT: 10, + LIMITS: { + 10: 10, + 20: 20, + 50: 50 + }, + INSTANCES: { + EE: 'https://www.x-tee.ee/catalogue/EE/wsdls/', + 'ee-test': 'https://www.x-tee.ee/catalogue/ee-test/wsdls/', + 'ee-dev': 'https://www.x-tee.ee/catalogue/ee-dev/wsdls/' + }, + API_SERVICE: 'index.json', + API_HISTORY: 'history.json', + HISTORY_LIMIT: 30, + LANGUAGES: { + EST: 'est', + ENG: 'eng' + }, + PREVIEW_SIZE: 5, + // Smaller value for faster unit testing + FILTER_DEBOUNCE: 20 + }; + + public getConfig(key: any) { + return this.configMock[key]; + } +} diff --git a/src/app/app.config.spec.ts b/src/app/app.config.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..20c6ddaa8e8dde0396b648345b44dae0a9315d6c --- /dev/null +++ b/src/app/app.config.spec.ts @@ -0,0 +1,28 @@ +import { AppConfig } from './app.config'; +import { of } from 'rxjs'; + +describe('AppConfig', () => { + let config: AppConfig; + let httpClientSpy: { get: jasmine.Spy }; + + beforeEach(() => { + httpClientSpy = jasmine.createSpyObj('HttpClient', ['get']); + config = new AppConfig(httpClientSpy as any); + }); + + it('should be created', () => { + expect(config).toBeTruthy(); + }); + + it('should load configuration', async () => { + httpClientSpy.get.and.returnValue(of({TEST: 'OK'})); + await config.load(); + expect(httpClientSpy.get).toHaveBeenCalledWith('./assets/config.json'); + }); + + it('getConfig should work', async () => { + httpClientSpy.get.and.returnValue(of({TEST: 'OK'})); + await config.load(); + expect(config.getConfig('TEST')).toBe('OK'); + }); +}); diff --git a/src/app/app.config.ts b/src/app/app.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..239927358d607226f186e4105387b79f6609f0e5 --- /dev/null +++ b/src/app/app.config.ts @@ -0,0 +1,31 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +@Injectable() +export class AppConfig { + private config: any = null; + + constructor(private http: HttpClient) { } + + /** + * Use to get the data found in the config file + */ + public getConfig(key: any) { + return this.config[key]; + } + + /** + * This method loads "config.json" to get all configuration variables + */ + public load() { + return new Promise(resolve => { + // Not handling errors. App cannot work without valid configuration + this.http.get('./assets/config.json') + .subscribe(responseData => { + this.config = responseData; + resolve(true); + }); + + }); + } +} diff --git a/src/app/app.module.spec.ts b/src/app/app.module.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..9cc7eb094a6192f28059a896ded5b40bb1788761 --- /dev/null +++ b/src/app/app.module.spec.ts @@ -0,0 +1,35 @@ +import { AppModule, HttpLoaderFactory } from './app.module'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { AppConfig } from './app.config'; +import { TestBed } from '@angular/core/testing'; + +describe('AppModule', () => { + let appModule: AppModule; + let httpClientSpy: { get: jasmine.Spy }; + let appConfigSpy: { load: jasmine.Spy }; + + beforeEach(() => { + httpClientSpy = jasmine.createSpyObj('HttpClient', ['get']); + appConfigSpy = jasmine.createSpyObj('HttpClient', ['load']); + appModule = new AppModule(); + }); + + it('should be created', () => { + expect(appModule).toBeTruthy(); + }); + + it('HttpLoaderFactory should work', () => { + expect(HttpLoaderFactory(httpClientSpy as any) instanceof TranslateHttpLoader).toBeTruthy(); + }); + + it('AppConfig should be initialized', async () => { + TestBed.configureTestingModule({ + imports: [ AppModule ], + providers: [ + { provide: AppConfig, useValue: appConfigSpy } + ] + }); + expect(TestBed.get(AppConfig)).toBeTruthy(); + expect(appConfigSpy.load).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/app/app.module.ts b/src/app/app.module.ts index d6f95a708846ae5c479fc3350660af41ea89a9a2..6474b7c7456acd438af100344aed624e6a0af805 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -11,6 +11,9 @@ import { SubsystemItemComponent } from './subsystem-list/subsystem-item/subsyste import { AppRoutingModule } from './app-routing.module'; import { SubsystemComponent } from './subsystem/subsystem.component'; import { HeaderComponent } from './header/header.component'; +import { APP_INITIALIZER } from '@angular/core'; +import { AppConfig } from './app.config'; +import { MessagesComponent } from './messages/messages.component'; @NgModule({ declarations: [ @@ -19,7 +22,8 @@ import { HeaderComponent } from './header/header.component'; SearchComponent, SubsystemItemComponent, SubsystemComponent, - HeaderComponent + HeaderComponent, + MessagesComponent ], imports: [ BrowserModule, @@ -34,7 +38,10 @@ import { HeaderComponent } from './header/header.component'; } }) ], - providers: [], + providers: [ + AppConfig, + { provide: APP_INITIALIZER, useFactory: (config: AppConfig) => () => config.load(), deps: [AppConfig], multi: true } + ], bootstrap: [AppComponent] }) export class AppModule { } @@ -42,4 +49,4 @@ export class AppModule { } export function HttpLoaderFactory(http: HttpClient) { // Providing path as a workaround for ngx-translate bug with --base-href option return new TranslateHttpLoader(http, './assets/i18n/'); -} \ No newline at end of file +} diff --git a/src/app/header/header.component.css b/src/app/header/header.component.css deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/src/app/header/header.component.html b/src/app/header/header.component.html index 68c6d1db28f7b5cf63dbbe44ff3f7077168facd0..aee64ac5438939491ad6ab87fc3cdfef2e924fcd 100644 --- a/src/app/header/header.component.html +++ b/src/app/header/header.component.html @@ -7,4 +7,4 @@ - \ No newline at end of file + diff --git a/src/app/header/header.component.spec.ts b/src/app/header/header.component.spec.ts index e378666f14a64e4ce399869ab1cf0e90e63691d9..d09d6647a2b40c1ab0665e7ec63392168d80246e 100644 --- a/src/app/header/header.component.spec.ts +++ b/src/app/header/header.component.spec.ts @@ -2,6 +2,9 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TranslateModule } from '@ngx-translate/core'; import { HeaderComponent } from './header.component'; import { HttpClientModule } from '@angular/common/http'; +import { LanguagesService } from '../languages.service'; +import { AppConfigMock } from 'src/app/app.config-mock'; +import { AppConfig } from 'src/app/app.config'; describe('HeaderComponent', () => { let component: HeaderComponent; @@ -13,6 +16,9 @@ describe('HeaderComponent', () => { imports: [ TranslateModule.forRoot(), HttpClientModule + ], + providers: [ + { provide: AppConfig, useClass: AppConfigMock } ] }) .compileComponents(); @@ -27,4 +33,11 @@ describe('HeaderComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should set language', () => { + const languagesService: LanguagesService = TestBed.get(LanguagesService); + spyOn(languagesService, 'setLang').and.returnValue(null); + component.setLang('xxx'); + expect(languagesService.setLang).toHaveBeenCalledWith('xxx'); + }); }); diff --git a/src/app/header/header.component.ts b/src/app/header/header.component.ts index 20b3337f265623066468c1ca3f641a0faebeccd5..75354d018dea1a9eb17dcff181432d15382081c5 100644 --- a/src/app/header/header.component.ts +++ b/src/app/header/header.component.ts @@ -3,25 +3,23 @@ import { LanguagesService } from '../languages.service'; @Component({ selector: 'app-header', - templateUrl: './header.component.html', - styleUrls: ['./header.component.css'] + templateUrl: './header.component.html' }) export class HeaderComponent implements OnInit { constructor(private languagesService: LanguagesService) { } - ngOnInit() { + getLangs(): string[] { + return this.languagesService.getLangs(); } - getLangs():string[] { - return this.languagesService.getLangs() - } - - getLang():string { - return this.languagesService.getLang() + getLang(): string { + return this.languagesService.getLang(); } setLang(lang: string) { - return this.languagesService.setLang(lang) + return this.languagesService.setLang(lang); } + + ngOnInit() {} } diff --git a/src/app/instance-version.ts b/src/app/instance-version.ts new file mode 100644 index 0000000000000000000000000000000000000000..f69b6f1d6d9ddbf3224165fce89995e3f58daa28 --- /dev/null +++ b/src/app/instance-version.ts @@ -0,0 +1,5 @@ +export class InstanceVersion { + reportTime: string; + reportTimeCompact: string; + reportPath: string; +} diff --git a/src/app/languages.service.spec.ts b/src/app/languages.service.spec.ts index 98d5256a4f8b84c086edca52378d18ae43dd3185..d857a5e93411b3b64ade0f222fba38329693cae5 100644 --- a/src/app/languages.service.spec.ts +++ b/src/app/languages.service.spec.ts @@ -1,11 +1,18 @@ import { TestBed } from '@angular/core/testing'; -import { TranslateModule } from '@ngx-translate/core'; import { LanguagesService } from './languages.service'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { AppConfigMock } from 'src/app/app.config-mock'; +import { AppConfig } from 'src/app/app.config'; +import { HttpClientModule } from '@angular/common/http'; describe('LanguagesService', () => { beforeEach(() => TestBed.configureTestingModule({ imports: [ - TranslateModule.forRoot() + TranslateModule.forRoot(), + HttpClientModule + ], + providers: [ + { provide: AppConfig, useClass: AppConfigMock } ] })); @@ -13,4 +20,28 @@ describe('LanguagesService', () => { const service: LanguagesService = TestBed.get(LanguagesService); expect(service).toBeTruthy(); }); + + it('should set default lang with empty localStorage', () => { + const translateService: TranslateService = TestBed.get(TranslateService); + spyOn(translateService, 'setDefaultLang'); + spyOn(window.localStorage, 'getItem').and.returnValue(undefined); + TestBed.get(LanguagesService); + expect(translateService.setDefaultLang).toHaveBeenCalledWith('est'); + }); + + it('should set default lang from localStorage', () => { + const translateService: TranslateService = TestBed.get(TranslateService); + spyOn(translateService, 'setDefaultLang'); + spyOn(window.localStorage, 'getItem').and.returnValue('ENG'); + TestBed.get(LanguagesService); + expect(translateService.setDefaultLang).toHaveBeenCalledWith('eng'); + }); + + it('should set language', () => { + const translateService: TranslateService = TestBed.get(TranslateService); + spyOn(translateService, 'use'); + const service = TestBed.get(LanguagesService); + service.setLang('ENG'); + expect(translateService.use).toHaveBeenCalledWith('eng'); + }); }); diff --git a/src/app/languages.service.ts b/src/app/languages.service.ts index bcf9b0dd6049b7e6b3177d024750f34ea15ea8a3..8f13fd50353ba3333e6411a6a1baafa20a8c950b 100644 --- a/src/app/languages.service.ts +++ b/src/app/languages.service.ts @@ -1,60 +1,57 @@ import { Injectable } from '@angular/core'; -import {TranslateService} from "@ngx-translate/core"; +import {TranslateService} from '@ngx-translate/core'; import { Title } from '@angular/platform-browser'; import { Subscription } from 'rxjs'; import { take } from 'rxjs/operators'; - -const LANGUAGES = { - 'EST': 'est', - 'ENG': 'eng' -} +import { AppConfig } from './app.config'; @Injectable({ providedIn: 'root' }) export class LanguagesService { - private selectedLang = '' - private translateSubscription: Subscription - + private selectedLang = ''; + private translateSubscription: Subscription; + constructor( private translate: TranslateService, - private title: Title + private title: Title, + private config: AppConfig ) { - this.selectedLang = this.getDefaultLang() - translate.setDefaultLang(LANGUAGES[this.selectedLang]) - this.updateTitle() + this.selectedLang = this.getDefaultLang(); + translate.setDefaultLang(this.config.getConfig('LANGUAGES')[this.selectedLang]); + this.updateTitle(); } updateTitle(): void { // Not subscribing if subscription already in progress if (!this.translateSubscription || this.translateSubscription.closed) { this.translateSubscription = this.translate.get('index.title').pipe(take(1)).subscribe((res: string) => { - this.title.setTitle(res) + this.title.setTitle(res); }); } } getDefaultLang(): string { - if(window && window.localStorage && window.localStorage.getItem('lang')) { - return window.localStorage.getItem('lang') + if (window && window.localStorage && window.localStorage.getItem('lang')) { + return window.localStorage.getItem('lang'); } - return Object.keys(LANGUAGES)[0] + return Object.keys(this.config.getConfig('LANGUAGES'))[0]; } - getLang(): string { - return this.selectedLang + getLangs(): string[] { + return Object.keys(this.config.getConfig('LANGUAGES')); } - getLangs(): string[] { - return Object.keys(LANGUAGES) + getLang(): string { + return this.selectedLang; } setLang(lang: string): void { - this.selectedLang = lang - if(window && window.localStorage) { - window.localStorage.setItem('lang', this.selectedLang) + this.selectedLang = lang; + if (window && window.localStorage) { + window.localStorage.setItem('lang', this.selectedLang); } - this.translate.use(LANGUAGES[this.selectedLang]); - this.updateTitle() + this.translate.use(this.config.getConfig('LANGUAGES')[this.selectedLang]); + this.updateTitle(); } } diff --git a/src/app/messages/messages.component.html b/src/app/messages/messages.component.html new file mode 100644 index 0000000000000000000000000000000000000000..ef9536e8a3fc16126eb7944470c1720e74d6f208 --- /dev/null +++ b/src/app/messages/messages.component.html @@ -0,0 +1,3 @@ + diff --git a/src/app/messages/messages.component.spec.ts b/src/app/messages/messages.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..3f4fb72998f6e17ff8ae6f92e6f6e8a3675bafa0 --- /dev/null +++ b/src/app/messages/messages.component.spec.ts @@ -0,0 +1,32 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { TranslateModule } from '@ngx-translate/core'; +import { HttpClientModule } from '@angular/common/http'; +import { MessagesComponent } from './messages.component'; + +describe('MessagesComponent', () => { + let component: MessagesComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + MessagesComponent + ], + imports: [ + TranslateModule.forRoot(), + HttpClientModule + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MessagesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/messages/messages.component.ts b/src/app/messages/messages.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..b065dc539a09a9b1303747967947435c34a43ff7 --- /dev/null +++ b/src/app/messages/messages.component.ts @@ -0,0 +1,16 @@ +import { Component, OnInit, Input } from '@angular/core'; + +@Component({ + selector: 'app-messages', + templateUrl: './messages.component.html' +}) +export class MessagesComponent implements OnInit { + @Input() message: ''; + @Input() subsystemId: ''; + + constructor() { } + + ngOnInit() { + } + +} diff --git a/src/app/methods.service.spec.ts b/src/app/methods.service.spec.ts deleted file mode 100644 index eb25906193a178a10f11fae01a9396b4dedb09e2..0000000000000000000000000000000000000000 --- a/src/app/methods.service.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { TestBed } from '@angular/core/testing'; -import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; -import { MethodsService } from './methods.service'; -import { HttpClient, HttpClientModule } from '@angular/common/http'; -import { HttpLoaderFactory } from './app.module'; - -describe('MethodsService', () => { - beforeEach(() => TestBed.configureTestingModule({ - imports: [ - TranslateModule.forRoot({ - loader: { - provide: TranslateLoader, - useFactory: HttpLoaderFactory, - deps: [HttpClient] - } - }), - HttpClientModule - ] - })); - - it('should be created', () => { - const service: MethodsService = TestBed.get(MethodsService); - expect(service).toBeTruthy(); - }); -}); \ No newline at end of file diff --git a/src/app/methods.service.ts b/src/app/methods.service.ts deleted file mode 100644 index da62d947b37551c2b3bc762ff90c16fae355e914..0000000000000000000000000000000000000000 --- a/src/app/methods.service.ts +++ /dev/null @@ -1,222 +0,0 @@ -import { Injectable, Output, EventEmitter } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; -import { Observable, of } from 'rxjs'; -import { catchError } from 'rxjs/operators'; -import { Subsystem } from './subsystem'; -import { Method } from './method'; - -const MAX_LIMIT: number = 1000000; -const CONFIG = { - 'EE': 'https://www.x-tee.ee/catalogue/EE/wsdls/', - 'ee-test': 'https://www.x-tee.ee/catalogue/ee-test/wsdls/', - 'ee-dev': 'https://www.x-tee.ee/catalogue/ee-dev/wsdls/' -} -const API_SERVICE = 'index.json'; - -@Injectable({ - providedIn: 'root' -}) -export class MethodsService { - private apiUrlBase = ''; - private limit: number = 10; - private offset: number = 0; - private nonEmpty: boolean = false; - private filter: string = ""; - private subsystems: Subsystem[] = []; - private loadingDone: boolean = false; - private loadingError: boolean = false; - private instance: string = ''; - private instanceData = new Object(); - - @Output() subsystemsUpdated: EventEmitter = new EventEmitter(); - @Output() warnings: EventEmitter = new EventEmitter(); - - constructor(private http: HttpClient) {} - - private signalRefresh() { - this.subsystemsUpdated.emit(null); - } - - private filteredSubsystems(): Subsystem[] { - let filtered: Subsystem[] = [] - let limit: number = this.limit - for (let subsystem of this.subsystems) { - if (this.nonEmpty && !subsystem.methods.length) { - // Filtering out empty subsystems - continue - } - if ( - this.filter != '' - && !subsystem.methods.length - ) { - // Subsystem without methods - if (subsystem.fullSubsystemName.toLowerCase().indexOf(this.filter.toLowerCase()) < 0) { - // Subsystem name does not match the filter - continue - } - } else if (this.filter != '') { - // Subsystem with methods - let filteredMethods: Method[] = [] - for (let method of subsystem.methods) { - if(method.fullMethodName.toLowerCase().indexOf(this.filter.toLowerCase()) >= 0) { - filteredMethods.push(method) - } - } - if (!filteredMethods.length) { - // No matching method names found - continue - } - - // Copy object to avoid overwriting methods array in subsystem object - // TODO: Is there a better way??? - subsystem = Object.assign(Object.create(subsystem), subsystem); - // Leaving only matcing methods - subsystem.methods = filteredMethods - } - - filtered.push(subsystem) - limit -= 1 - if (limit == 0) { - break - } - } - - return filtered.slice(this.offset, this.limit); - } - - // TODO: move that to Class constructor? - private setFullNames() { - for (let i in this.subsystems) { - this.subsystems[i].fullSubsystemName = this.subsystems[i].xRoadInstance - + '/' + this.subsystems[i].memberClass - + '/' + this.subsystems[i].memberCode - + '/' + this.subsystems[i].subsystemCode - for (let j in this.subsystems[i].methods) { - this.subsystems[i].methods[j].fullMethodName = this.subsystems[i].fullSubsystemName - + '/' + this.subsystems[i].methods[j].serviceCode - + '/' + this.subsystems[i].methods[j].serviceVersion - } - } - } - - /** - * Handle Http operation that failed. - * Let the app continue. - * @param result - optional value to return as the observable result - */ - private handleError (result?: T) { - return (error: any): Observable => { - this.loadingError = true - this.emitWarning('Error while loading data from server!') - - // Let the app keep running by returning an empty result. - return of(result as T); - }; - } - - getDefaultInstance(): string { - return Object.keys(CONFIG)[0] - } - - getInstances(): string[] { - return Object.keys(CONFIG) - } - - getInstance(): string { - return this.instance - } - - emitWarning(msg: string) { - this.warnings.emit(msg); - } - - setInstance(instance: string) { - this.instance = instance - this.apiUrlBase = CONFIG[instance] - - // Data of this instance already loaded - if (this.instanceData[instance] && this.instanceData[instance].length) { - this.subsystems = this.instanceData[instance] - this.signalRefresh(); - } else { - this.loadingDone = false; - this.loadingError = false; - this.http.get(this.apiUrlBase + API_SERVICE) - .pipe( - catchError(this.handleError([])) - ).subscribe(subsystems => { - this.instanceData[instance] = subsystems - this.subsystems = this.instanceData[instance] - this.setFullNames(); - this.loadingDone = true; - this.signalRefresh(); - }) - } - } - - getApiUrlBase(): string { - return this.apiUrlBase - } - - getLimit(): string { - if (this.limit == MAX_LIMIT) { - return 'all' - } - return this.limit.toString() - } - - getNonEmpty(): boolean { - return this.nonEmpty - } - - getfilter(): string { - return this.filter - } - - isLoadingDone(): boolean { - return this.loadingDone - } - - isLoadingError(): boolean { - return this.loadingError - } - - getMethods(): Subsystem[] { - return this.filteredSubsystems(); - } - - setNonEmpty(nonEmpty: boolean) { - this.nonEmpty = nonEmpty; - this.signalRefresh(); - } - - setLimit (limit: string) { - switch(limit) { - case '20': - this.limit = 20; - break; - case '50': - this.limit = 50; - break; - case 'all': - this.limit = MAX_LIMIT; - break; - default: - this.limit = 10; - } - this.signalRefresh(); - } - - setFilter(filter: string) { - if (this.filter != filter.trim()) { - this.filter = filter.trim(); - this.signalRefresh(); - } - } - - getSubsystem(name: string): Subsystem { - return this.subsystems.find(function(element) { - return element.fullSubsystemName === name; - }) - } -} diff --git a/src/app/subsystem-list/search/search.component.css b/src/app/subsystem-list/search/search.component.css deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/src/app/subsystem-list/search/search.component.html b/src/app/subsystem-list/search/search.component.html index d702833ccd5b96f654294e08ad00c71e95d515aa..9f8615bd1311a5a2a90dc3864803cf326b35772d 100644 --- a/src/app/subsystem-list/search/search.component.html +++ b/src/app/subsystem-list/search/search.component.html @@ -16,9 +16,7 @@ @@ -32,4 +30,4 @@ - \ No newline at end of file + diff --git a/src/app/subsystem-list/search/search.component.spec.ts b/src/app/subsystem-list/search/search.component.spec.ts index 583b17d4bfd999de833f67926248d9d98941d6dc..c5e08d0fe7b6fd357820552471d57491f5d9b8a1 100644 --- a/src/app/subsystem-list/search/search.component.spec.ts +++ b/src/app/subsystem-list/search/search.component.spec.ts @@ -3,10 +3,14 @@ import { TranslateModule } from '@ngx-translate/core'; import { HttpClientModule } from '@angular/common/http'; import { FormsModule } from '@angular/forms'; import { SearchComponent } from './search.component'; +import { SubsystemsService } from 'src/app/subsystems.service'; +import { AppConfigMock } from 'src/app/app.config-mock'; +import { AppConfig } from 'src/app/app.config'; describe('SearchComponent', () => { let component: SearchComponent; let fixture: ComponentFixture; + let subsystemsService: SubsystemsService; beforeEach(async(() => { TestBed.configureTestingModule({ @@ -15,12 +19,20 @@ describe('SearchComponent', () => { FormsModule, TranslateModule.forRoot(), HttpClientModule + ], + providers: [ + { provide: AppConfig, useClass: AppConfigMock } ] }) .compileComponents(); })); beforeEach(() => { + subsystemsService = TestBed.get(SubsystemsService); + spyOn(subsystemsService, 'getLimits').and.returnValue({10: 10, 20: 20}); + spyOn(subsystemsService, 'setNonEmpty').and.returnValue(null); + spyOn(subsystemsService, 'setLimit').and.returnValue(null); + spyOn(subsystemsService, 'setFilter').and.returnValue(null); fixture = TestBed.createComponent(SearchComponent); component = fixture.componentInstance; fixture.detectChanges(); @@ -29,4 +41,24 @@ describe('SearchComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); -}); \ No newline at end of file + + it('getLimitKeys should work', () => { + expect(component.getLimitKeys()).toEqual(['10', '20']); + expect(subsystemsService.getLimits).toHaveBeenCalledWith(); + }); + + it('setNonEmpty should work', () => { + component.setNonEmpty(true); + expect(subsystemsService.setNonEmpty).toHaveBeenCalledWith(true); + }); + + it('setLimit should work', () => { + component.setLimit('50'); + expect(subsystemsService.setLimit).toHaveBeenCalledWith('50'); + }); + + it('setFilter should work', () => { + component.setFilter('test'); + expect(subsystemsService.setFilter).toHaveBeenCalledWith('test'); + }); +}); diff --git a/src/app/subsystem-list/search/search.component.ts b/src/app/subsystem-list/search/search.component.ts index 5c48c55db1ac525af403187c0ba8b015fbd0b252..f98d8a3c2ff1f17f5f1e9ba56def4b8a5451d022 100644 --- a/src/app/subsystem-list/search/search.component.ts +++ b/src/app/subsystem-list/search/search.component.ts @@ -1,35 +1,40 @@ import { Component, OnInit } from '@angular/core'; -import { MethodsService } from '../../methods.service'; +import { SubsystemsService } from '../../subsystems.service'; @Component({ selector: 'app-search', - templateUrl: './search.component.html', - styleUrls: ['./search.component.css'] + templateUrl: './search.component.html' }) export class SearchComponent implements OnInit { + limit: string; + limits: object; + nonEmpty: boolean; + filter: string; + + constructor(private subsystemsService: SubsystemsService) { + this.limit = this.subsystemsService.getLimit(); + this.limits = this.subsystemsService.getLimits(); + } - limit: string - nonEmpty: boolean - filter: string - - constructor(private methodsService: MethodsService) { } - - ngOnInit() { - this.limit = this.methodsService.getLimit() - this.nonEmpty = this.methodsService.getNonEmpty() - this.filter = this.methodsService.getfilter() + getLimitKeys(): string[] { + return Object.keys(this.limits); } setNonEmpty(nonEmpty: boolean) { - this.methodsService.setNonEmpty(nonEmpty) + this.subsystemsService.setNonEmpty(nonEmpty); } setLimit(limit: string) { - this.methodsService.setLimit(limit) + this.subsystemsService.setLimit(limit); } setFilter(filter: string) { - this.methodsService.setFilter(filter) + this.subsystemsService.setFilter(filter); } + ngOnInit() { + this.limit = this.subsystemsService.getLimit(); + this.nonEmpty = this.subsystemsService.getNonEmpty(); + this.filter = this.subsystemsService.getfilter(); + } } diff --git a/src/app/subsystem-list/subsystem-item/subsystem-item.component.css b/src/app/subsystem-list/subsystem-item/subsystem-item.component.css index f4be51e77a6c1fb61da9c424800e086845acd1f3..c2c466066310cb2fa8646fcd4e8e330abfaf05ff 100644 --- a/src/app/subsystem-list/subsystem-item/subsystem-item.component.css +++ b/src/app/subsystem-list/subsystem-item/subsystem-item.component.css @@ -1,3 +1,3 @@ .pointerCursor { cursor: pointer; -} \ No newline at end of file +} diff --git a/src/app/subsystem-list/subsystem-item/subsystem-item.component.html b/src/app/subsystem-list/subsystem-item/subsystem-item.component.html index 754aae29dcec702813c94ce09848829e3329c750..18f95dcc40c4ca866adc14654bfd032e1ed6c546 100644 --- a/src/app/subsystem-list/subsystem-item/subsystem-item.component.html +++ b/src/app/subsystem-list/subsystem-item/subsystem-item.component.html @@ -15,10 +15,12 @@ {{method.fullMethodName}} WSDL - {{'subsystem.statusWsdlError' | translate}} -

+ {{'subsystem.statusWsdlError' | translate}} + {{'subsystem.statusWsdlTimeout' | translate}} + {{'subsystem.statusWsdlSkipped' | translate}} +

{{'subsystem.moreMethods' | translate:{"count": getNotInPreview()} }}

- \ No newline at end of file + diff --git a/src/app/subsystem-list/subsystem-item/subsystem-item.component.spec.ts b/src/app/subsystem-list/subsystem-item/subsystem-item.component.spec.ts index b12284aae6bfe463c98a2fd75d5231436a487fa8..2a8304a46fb2ec6c279f56ed000ae5bb6b6c9ac7 100644 --- a/src/app/subsystem-list/subsystem-item/subsystem-item.component.spec.ts +++ b/src/app/subsystem-list/subsystem-item/subsystem-item.component.spec.ts @@ -3,43 +3,90 @@ import { TranslateModule } from '@ngx-translate/core'; import { HttpClientModule } from '@angular/common/http'; import { SubsystemItemComponent } from './subsystem-item.component'; import { RouterTestingModule } from '@angular/router/testing'; +import { SubsystemsService } from 'src/app/subsystems.service'; +import { Router } from '@angular/router'; +import { Method } from 'src/app/method'; +import { AppConfigMock } from 'src/app/app.config-mock'; +import { AppConfig } from 'src/app/app.config'; + +const PREVIEW_SIZE = 5; describe('SubsystemItemComponent', () => { let component: SubsystemItemComponent; let fixture: ComponentFixture; + let subsystemsService: SubsystemsService; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ - SubsystemItemComponent + SubsystemItemComponent ], imports: [ TranslateModule.forRoot(), HttpClientModule, RouterTestingModule + ], + providers: [ + { provide: Router, useValue: { + navigateByUrl: jasmine.createSpy('navigateByUrl') + }}, + { provide: AppConfig, useClass: AppConfigMock } ] }) .compileComponents(); })); beforeEach(() => { + subsystemsService = TestBed.get(SubsystemsService); + spyOn(subsystemsService, 'getApiUrlBase').and.returnValue(null); + fixture = TestBed.createComponent(SubsystemItemComponent); component = fixture.componentInstance; - component.subsystem = { - xRoadInstance: "XRD", - memberClass: "CLASS", - memberCode: "CODE", - subsystemCode: "SUB", - subsystemStatus: "OK", - fullSubsystemName: "XRD/CLASS/CODE/SUB", + xRoadInstance: 'INST', + memberClass: 'CLASS', + memberCode: 'CODE', + subsystemCode: 'SUB', + subsystemStatus: 'OK', + fullSubsystemName: 'INST/CLASS/CODE/SUB', methods: [] - } - + }; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); -}); \ No newline at end of file + + it('getApiUrlBase should work', () => { + component.getApiUrlBase(); + expect(subsystemsService.getApiUrlBase).toHaveBeenCalled(); + }); + + it('should preview methods', () => { + expect(component.getMethodsPreview().length).toBe(0); + for (let i = 0; i < PREVIEW_SIZE + 10; i++) { + component.subsystem.methods.push(new Method()); + } + expect(component.getMethodsPreview().length).toBe(PREVIEW_SIZE); + }); + + it('should calculate methods not in preview', () => { + expect(component.getNotInPreview()).toBe(0); + for (let i = 0; i < PREVIEW_SIZE + 10; i++) { + component.subsystem.methods.push(new Method()); + } + expect(component.getNotInPreview()).toBe(10); + }); + + it('should go to detail view', () => { + const spy = TestBed.get(Router).navigateByUrl; + component.showDetail(); + expect(spy).toHaveBeenCalledWith('/INST/CLASS/CODE/SUB'); + + spy.calls.reset(); + spyOn(subsystemsService, 'getInstanceVersion').and.returnValue('12345'); + component.showDetail(); + expect(spy).toHaveBeenCalledWith('/INST/CLASS/CODE/SUB?at=12345'); + }); +}); diff --git a/src/app/subsystem-list/subsystem-item/subsystem-item.component.ts b/src/app/subsystem-list/subsystem-item/subsystem-item.component.ts index 9b9aeea838aaf1f8e4c98552b942835c77bb5de2..4dace562a56880d4680d063b89b3d9730e25a525 100644 --- a/src/app/subsystem-list/subsystem-item/subsystem-item.component.ts +++ b/src/app/subsystem-list/subsystem-item/subsystem-item.component.ts @@ -1,8 +1,9 @@ import { Component, OnInit, Input } from '@angular/core'; import { Subsystem } from '../../subsystem'; import { Method } from '../../method'; -import { MethodsService } from '../../methods.service'; +import { SubsystemsService } from '../../subsystems.service'; import { Router } from '@angular/router'; +import { AppConfig } from '../../app.config'; @Component({ selector: 'app-subsystem-item', @@ -10,31 +11,27 @@ import { Router } from '@angular/router'; styleUrls: ['./subsystem-item.component.css'] }) export class SubsystemItemComponent implements OnInit { - - @Input() subsystem: Subsystem - private previewSize: number = 5 + @Input() subsystem: Subsystem; constructor( - private methodsService: MethodsService, - private router: Router + private subsystemsService: SubsystemsService, + private router: Router, + private config: AppConfig ) { } - ngOnInit() { - } - getApiUrlBase(): string { - return this.methodsService.getApiUrlBase() + return this.subsystemsService.getApiUrlBase(); } getMethodsPreview(): Method[] { - return this.subsystem.methods.length ? this.subsystem.methods.slice(0, this.previewSize) : [] + return this.subsystem.methods.length ? this.subsystem.methods.slice(0, this.config.getConfig('PREVIEW_SIZE')) : []; } getNotInPreview(): number { - if (this.subsystem.methods.length - this.previewSize < 0) { - return 0 + if (this.subsystem.methods.length - this.config.getConfig('PREVIEW_SIZE') < 0) { + return 0; } - return this.subsystem.methods.length - this.previewSize + return this.subsystem.methods.length - this.config.getConfig('PREVIEW_SIZE'); } showDetail() { @@ -43,6 +40,9 @@ export class SubsystemItemComponent implements OnInit { + '/' + this.subsystem.memberClass + '/' + this.subsystem.memberCode + '/' + this.subsystem.subsystemCode - ) + + (this.subsystemsService.getInstanceVersion() ? '?at=' + this.subsystemsService.getInstanceVersion() : '') + ); } + + ngOnInit() {} } diff --git a/src/app/subsystem-list/subsystem-list.component.css b/src/app/subsystem-list/subsystem-list.component.css deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/src/app/subsystem-list/subsystem-list.component.html b/src/app/subsystem-list/subsystem-list.component.html index c406243caf925aa0effc69cec58185a7c78a61cf..ff0f642e109f2f730ffc6ba08ccac41153cd989f 100644 --- a/src/app/subsystem-list/subsystem-list.component.html +++ b/src/app/subsystem-list/subsystem-list.component.html @@ -4,11 +4,10 @@

-

+

+

- +
+
+
+ {{'subsystemList.selectVersion' | translate}} +
+
+
+
+ +
+
+
+
+
-
- -
+ +
+ +

{{'subsystemList.moreSubsystems' | translate}}

+ +

+ +

diff --git a/src/app/subsystem-list/subsystem-list.component.spec.ts b/src/app/subsystem-list/subsystem-list.component.spec.ts index 309a200f435924bca31e9d254fe9c362e978dc4a..1e97f57c0475296656b985a762abdad8472649ce 100644 --- a/src/app/subsystem-list/subsystem-list.component.spec.ts +++ b/src/app/subsystem-list/subsystem-list.component.spec.ts @@ -1,55 +1,249 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TranslateModule } from '@ngx-translate/core'; import { HttpClientModule } from '@angular/common/http'; -import { RouterTestingModule } from '@angular/router/testing' import { SubsystemListComponent } from './subsystem-list.component'; -import { Component, Input, Output, EventEmitter } from '@angular/core'; -import { ViewportScroller } from '@angular/common'; +import { Component, Input } from '@angular/core'; import { Subsystem } from '../subsystem'; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, Router, Scroll } from '@angular/router'; import { of } from 'rxjs'; - +import { SubsystemsService } from '../subsystems.service'; +import { ViewportScroller } from '@angular/common'; +import { AppConfigMock } from 'src/app/app.config-mock'; +import { AppConfig } from 'src/app/app.config'; +import { FormsModule } from '@angular/forms'; + @Component({selector: 'app-header', template: ''}) class HeaderStubComponent {} +@Component({selector: 'app-messages', template: ''}) +class MessagesStubComponent { + @Input() message: string; +} @Component({selector: 'app-search', template: ''}) class SearchStubComponent {} @Component({selector: 'app-subsystem-item', template: ''}) class SubsystemItemStubComponent { - @Input() subsystem: Subsystem + @Input() subsystem: Subsystem; } describe('SubsystemListComponent', () => { let component: SubsystemListComponent; let fixture: ComponentFixture; + let getInstanceSpy; + let getInstancesSpy; + let subsystemsService: SubsystemsService; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ SubsystemListComponent, HeaderStubComponent, + MessagesStubComponent, SearchStubComponent, SubsystemItemStubComponent ], imports: [ + FormsModule, TranslateModule.forRoot(), - HttpClientModule, - RouterTestingModule + HttpClientModule + ], + providers: [ + { provide: ActivatedRoute, useValue: { + params: of({ + instance: 'INST' + }) + }}, + { provide: Router, useValue: { + events: of(new Scroll(null, [11, 12], null)), + navigateByUrl: jasmine.createSpy('navigateByUrl') + }}, + { provide: AppConfig, useClass: AppConfigMock } ] }) .compileComponents(); })); beforeEach(() => { - // Mocks and spies - TestBed.get(ActivatedRoute).params = of({ - "instance": "EE" - }) + subsystemsService = TestBed.get(SubsystemsService); + getInstanceSpy = spyOn(subsystemsService, 'getInstance').and.returnValue('INST'); + getInstancesSpy = spyOn(subsystemsService, 'getInstances').and.returnValue(['INST']); + spyOn(TestBed.get(ViewportScroller), 'scrollToPosition'); + spyOn(subsystemsService, 'setInstance').and.returnValue(null); + spyOn(subsystemsService, 'getDefaultInstance').and.returnValue('DEFINST'); + spyOn(TestBed.get(SubsystemsService), 'getApiUrlBase').and.returnValue('base'); + }); + + it('should create', () => { + fixture = TestBed.createComponent(SubsystemListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it('should redirect on incorrect instance', () => { + getInstancesSpy.and.returnValue(['XXX']); + fixture = TestBed.createComponent(SubsystemListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + expect(TestBed.get(Router).navigateByUrl).toHaveBeenCalledWith('/DEFINST'); + }); + + it('should detect when instance is not selected', () => { + getInstanceSpy.and.returnValue(''); + fixture = TestBed.createComponent(SubsystemListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + expect(subsystemsService.setInstance).toHaveBeenCalledWith('INST', ''); + }); + + it('should detect change instance', () => { + getInstanceSpy.and.returnValue('INST2'); + getInstancesSpy.and.returnValue(['INST', 'INST2']); + fixture = TestBed.createComponent(SubsystemListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + expect(subsystemsService.setInstance).toHaveBeenCalledWith('INST', ''); + }); + + it('should scroll to position', () => { + fixture = TestBed.createComponent(SubsystemListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + expect(TestBed.get(ViewportScroller).scrollToPosition).toHaveBeenCalledWith([11, 12]); + }); + + it('switchInstance should work', () => { + fixture = TestBed.createComponent(SubsystemListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + const spy = TestBed.get(Router).navigateByUrl; + + component.switchInstance('NEWINST'); + expect(spy).toHaveBeenCalledWith('/NEWINST'); + + spy.calls.reset(); + component.switchInstance('INST'); + expect(spy).toHaveBeenCalledWith('/INST'); + }); + + it('should receive service warnings', () => { + fixture = TestBed.createComponent(SubsystemListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + subsystemsService.warnings.emit('WARN'); + expect(component.message).toBe('WARN'); + }); + + it('scrollToTop should work', () => { + fixture = TestBed.createComponent(SubsystemListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + const spy = TestBed.get(ViewportScroller).scrollToPosition; + spy.calls.reset(); + component.scrollToTop(); + expect(spy).toHaveBeenCalledWith([0, 0]); + }); + + it('isPartialList should work', () => { + fixture = TestBed.createComponent(SubsystemListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + + subsystemsService.filteredSubsystemsSubject.next([new Subsystem(), new Subsystem()]); + + const getLimitSpy = spyOn(subsystemsService, 'getLimit').and.returnValue(['all']); + expect(component.isPartialList()).toBeFalsy(); + + getLimitSpy.and.returnValue(['2']); + expect(component.isPartialList()).toBeTruthy(); + + getLimitSpy.and.returnValue(['3']); + expect(component.isPartialList()).toBeFalsy(); + }); + + it('setInstanceVersion should work without instance version', () => { fixture = TestBed.createComponent(SubsystemListComponent); component = fixture.componentInstance; fixture.detectChanges(); + component.setInstanceVersion(); + expect(TestBed.get(Router).navigateByUrl).toHaveBeenCalledWith('/INST'); + }); +}); + +describe('SubsystemListComponent (with instance version)', () => { + let component: SubsystemListComponent; + let fixture: ComponentFixture; + let getInstanceSpy; + let getInstancesSpy; + let subsystemsService: SubsystemsService; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + SubsystemListComponent, + HeaderStubComponent, + MessagesStubComponent, + SearchStubComponent, + SubsystemItemStubComponent + ], + imports: [ + FormsModule, + TranslateModule.forRoot(), + HttpClientModule + ], + providers: [ + { provide: ActivatedRoute, useValue: { + params: of({ + instance: 'INST' + }), + snapshot: { + queryParams: { + at: '12345' + } + } + }}, + { provide: Router, useValue: { + events: of(new Scroll(null, [11, 12], null)), + navigateByUrl: jasmine.createSpy('navigateByUrl') + }}, + { provide: AppConfig, useClass: AppConfigMock } + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + subsystemsService = TestBed.get(SubsystemsService); + getInstanceSpy = spyOn(subsystemsService, 'getInstance').and.returnValue('INST'); + getInstancesSpy = spyOn(subsystemsService, 'getInstances').and.returnValue(['INST']); + spyOn(TestBed.get(ViewportScroller), 'scrollToPosition'); + spyOn(subsystemsService, 'setInstance').and.returnValue(null); + spyOn(subsystemsService, 'getDefaultInstance').and.returnValue('DEFINST'); + spyOn(TestBed.get(SubsystemsService), 'getApiUrlBase').and.returnValue('base'); }); it('should create', () => { + fixture = TestBed.createComponent(SubsystemListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); expect(component).toBeTruthy(); + expect(component.instanceVersion).toBe('12345'); + }); + + it('setInstanceVersion should work with instance version', () => { + fixture = TestBed.createComponent(SubsystemListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + component.setInstanceVersion(); + expect(TestBed.get(Router).navigateByUrl).toHaveBeenCalledWith('/INST?at=12345'); + }); + + it('switchInstance should reset instance version when instance does not change', () => { + fixture = TestBed.createComponent(SubsystemListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + component.instanceVersion = 'test'; + component.switchInstance('INST'); + expect(TestBed.get(Router).navigateByUrl).toHaveBeenCalledWith('/INST'); + expect(component.instanceVersion).toBe(''); }); -}); \ No newline at end of file +}); diff --git a/src/app/subsystem-list/subsystem-list.component.ts b/src/app/subsystem-list/subsystem-list.component.ts index d1edb7954c22a340484bb42f1ae70c07baabfc50..109badac79ca46e690aa2028527d04a1205ff510 100644 --- a/src/app/subsystem-list/subsystem-list.component.ts +++ b/src/app/subsystem-list/subsystem-list.component.ts @@ -1,27 +1,29 @@ -import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Component, OnInit, AfterViewInit, OnDestroy } from '@angular/core'; import { Subsystem } from '../subsystem'; -import { MethodsService } from '../methods.service'; +import { SubsystemsService } from '../subsystems.service'; import { ActivatedRoute, Router, Scroll } from '@angular/router'; -import { Subscription } from 'rxjs'; +import { Subscription, BehaviorSubject } from 'rxjs'; import { ViewportScroller } from '@angular/common'; import { filter } from 'rxjs/operators'; +import { InstanceVersion } from '../instance-version'; @Component({ selector: 'app-subsystem-list', - templateUrl: './subsystem-list.component.html', - styleUrls: ['./subsystem-list.component.css'] + templateUrl: './subsystem-list.component.html' }) -export class SubsystemListComponent implements OnInit, OnDestroy { - subsystems: Subsystem[] - message: string = '' - scrollPosition: [number, number] = [0, 0] - routerScrollSubscription: Subscription - routeSubscription: Subscription - updatedSubscription: Subscription - warningsSubscription: Subscription +export class SubsystemListComponent implements OnInit, AfterViewInit, OnDestroy { + message: string; + scrollSubject: BehaviorSubject = new BehaviorSubject(null); + routerScrollSubscription: Subscription; + routeSubscription: Subscription; + warningsSubscription: Subscription; + scrollSubjectSubscription: Subscription; + filteredSubsystems: BehaviorSubject; + instanceVersions: BehaviorSubject; + instanceVersion: string; constructor( - private methodsService: MethodsService, + private subsystemsService: SubsystemsService, private route: ActivatedRoute, private router: Router, private viewportScroller: ViewportScroller @@ -29,77 +31,108 @@ export class SubsystemListComponent implements OnInit, OnDestroy { // Geting previous scroll position this.routerScrollSubscription = this.router.events.pipe( filter(e => e instanceof Scroll) - ).subscribe(e => { + ).subscribe(async (e) => { if ((e as Scroll).position) { - this.scrollPosition = (e as Scroll).position; - } else { - this.scrollPosition = [0, 0]; + this.scrollSubject.next((e as Scroll).position); } }); } + getInstance(): string { + return this.subsystemsService.getInstance(); + } + + getInstances(): string[] { + return this.subsystemsService.getInstances(); + } + + switchInstance(instance: string): void { + this.router.navigateByUrl('/' + instance); + // Reloading data if clicked on the current instance + if (this.subsystemsService.getInstance() === instance) { + this.instanceVersion = ''; + this.subsystemsService.setInstance(instance, this.instanceVersion); + } + } + + getApiUrl(): string { + return this.subsystemsService.getApiUrl(); + } + + isPartialList(): boolean { + const limit = parseInt(this.subsystemsService.getLimit(), 10); + if (isNaN(limit)) { + // limit is "all" (or some faulty string) + return false; + } else if (this.filteredSubsystems.value.length < limit) { + return false; + } else { + // If subsystems length == limit then we still asume it is partial + return true; + } + } + + scrollToTop() { + this.viewportScroller.scrollToPosition([0, 0]); + } + + setInstanceVersion() { + // This will update URL without triggering route.params + this.router.navigateByUrl( + '/' + this.subsystemsService.getInstance() + + (this.instanceVersion ? '?at=' + this.instanceVersion : '')); + this.subsystemsService.setInstance(this.subsystemsService.getInstance(), this.instanceVersion); + } + ngOnInit() { // Reset message on page load - this.message = '' + this.message = ''; + + this.filteredSubsystems = this.subsystemsService.filteredSubsystemsSubject; + this.instanceVersions = this.subsystemsService.instanceVersionsSubject; // Service will tell when data loading failed! - this.warningsSubscription = this.methodsService.warnings.subscribe(signal => { - this.message = signal + this.warningsSubscription = this.subsystemsService.warnings.subscribe(signal => { + this.message = signal; }); - + this.routeSubscription = this.route.params.subscribe( params => { // Reset message on navigation - this.message = '' + this.message = ''; // Redirect to default instance if instance is empty or invalid - if (!this.methodsService.getInstances().includes(params['instance'])) { - this.router.navigateByUrl('/' + this.methodsService.getDefaultInstance()) - return + if (!this.subsystemsService.getInstances().includes(params.instance)) { + this.router.navigateByUrl('/' + this.subsystemsService.getDefaultInstance()); + return; } - // Only reload on switching of instance or when no instance is selected yet on service side - if (this.getInstance() == '' || this.getInstance() != params['instance']) { - this.methodsService.setInstance(params['instance'] ? params['instance'] : this.methodsService.getDefaultInstance()) + + // Set selected instance version + if (this.route.snapshot && this.route.snapshot.queryParams.at) { + this.instanceVersion = this.route.snapshot.queryParams.at; + } else { + this.instanceVersion = ''; } - }); - // Service will tell when updated data is available! - this.updatedSubscription = this.methodsService.subsystemsUpdated.subscribe(signal => { - this.getMethods(); + // Only reload on switching of instance or when no instance is selected yet on service side + if (this.getInstance() === '' || this.getInstance() !== params.instance) { + this.subsystemsService.setInstance(params.instance, this.instanceVersion); + } }); - // If json data is loaded update event will not be emited. - // This line must be after subscription (data may be changed while we start subscription) - this.getMethods(); } ngAfterViewInit() { // Restoring scroll position - this.viewportScroller.scrollToPosition(this.scrollPosition); - // TODO: what if this.scrollPosition is not ready yet? - /*this.routerScrollSubscription.add(() => { - this.viewportScroller.scrollToPosition(this.scrollPosition); - })*/ + this.scrollSubjectSubscription = this.scrollSubject.subscribe( position => { + if (position) { + this.viewportScroller.scrollToPosition(position); + } + }); } ngOnDestroy() { - this.routerScrollSubscription.unsubscribe() - this.routeSubscription.unsubscribe() - this.updatedSubscription.unsubscribe() - this.warningsSubscription.unsubscribe() - } - - getInstance(): string { - return this.methodsService.getInstance() - } - - getInstances(): string[] { - return this.methodsService.getInstances() - } - - getMethods(): void { - this.subsystems = this.methodsService.getMethods() - } - - switchInstance(instance: string): void { - this.router.navigateByUrl('/' + instance) + this.routerScrollSubscription.unsubscribe(); + this.routeSubscription.unsubscribe(); + this.warningsSubscription.unsubscribe(); + this.scrollSubjectSubscription.unsubscribe(); } } diff --git a/src/app/subsystem/subsystem.component.css b/src/app/subsystem/subsystem.component.css deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/src/app/subsystem/subsystem.component.html b/src/app/subsystem/subsystem.component.html index 0662f7a13240021ab211d85c1b5a7fb86e68b5d2..89829678aaa97f0413531af11e1879fe320ea9a0 100644 --- a/src/app/subsystem/subsystem.component.html +++ b/src/app/subsystem/subsystem.component.html @@ -1,15 +1,13 @@ -

{{'subsystem.heading' | translate:{"subsystem": subsystemId, "instance": getInstance()} }}

+

{{'subsystem.heading' | translate:{"subsystem": subsystemId, "instance": getInstance() === "" ? paramsInstance : getInstance()} }}

- + -
+
{{subsystem.fullSubsystemName}} @@ -27,8 +25,15 @@ {{method.fullMethodName}} WSDL - {{'subsystem.statusWsdlError' | translate}} -

+ {{'subsystem.statusWsdlError' | translate}} + {{'subsystem.statusWsdlTimeout' | translate}} + {{'subsystem.statusWsdlSkipped' | translate}} +

+
+ +

+ +

diff --git a/src/app/subsystem/subsystem.component.spec.ts b/src/app/subsystem/subsystem.component.spec.ts index a39dc9768e8f14977bbbff2713e62dd5db393497..32b6ed7c468168cdaa186767c73d11fea2bdbb03 100644 --- a/src/app/subsystem/subsystem.component.spec.ts +++ b/src/app/subsystem/subsystem.component.spec.ts @@ -1,63 +1,240 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TranslateModule } from '@ngx-translate/core'; -import { Component } from '@angular/core'; +import { Component, Input } from '@angular/core'; import { SubsystemComponent } from './subsystem.component'; -import { RouterTestingModule } from '@angular/router/testing'; import { HttpClientModule } from '@angular/common/http'; -//import { ViewportScroller } from '@angular/common'; -//import { MethodsService } from '../methods.service'; -import { ActivatedRoute } from '@angular/router'; -import { of } from 'rxjs'; +import { Router, ActivatedRoute, Scroll } from '@angular/router'; +import { of, BehaviorSubject } from 'rxjs'; +import { SubsystemsService } from '../subsystems.service'; +import { ViewportScroller } from '@angular/common'; +import { AppConfigMock } from 'src/app/app.config-mock'; +import { AppConfig } from 'src/app/app.config'; @Component({selector: 'app-header', template: ''}) class HeaderStubComponent {} +@Component({selector: 'app-messages', template: ''}) +class MessagesStubComponent { + @Input() message: string; + @Input() subsystemId: string; +} describe('SubsystemComponent', () => { let component: SubsystemComponent; let fixture: ComponentFixture; + let getInstanceSpy; + let getInstancesSpy; + let subsystemsService: SubsystemsService; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ SubsystemComponent, - HeaderStubComponent + HeaderStubComponent, + MessagesStubComponent ], imports: [ - TranslateModule.forRoot(), - HttpClientModule, - RouterTestingModule - ]/*, + TranslateModule.forRoot(), + HttpClientModule + ], providers: [ - MethodsService - ]*/ + { provide: ActivatedRoute, useValue: { + params: of({ + instance: 'INST', + class: 'CLASS', + member: 'MEMBER', + subsystem: 'SYSTEM' + }) + }}, + { provide: Router, useValue: { + events: of(new Scroll(null, [11, 12], null)), + navigateByUrl: jasmine.createSpy('navigateByUrl') + }}, + { provide: AppConfig, useClass: AppConfigMock } + ] }) .compileComponents(); })); beforeEach(() => { - // Mocks and spies - TestBed.get(ActivatedRoute).params = of({ - "instance": "EE", - "class": "CLASS", - "member": "MEMBER", - "subsystem": "SYSTEM" - }) - //spyOn(TestBed.get(ViewportScroller), "scrollToPosition").and.callFake(() => {}); + subsystemsService = TestBed.get(SubsystemsService); + getInstanceSpy = spyOn(subsystemsService, 'getInstance').and.returnValue('INST'); + getInstancesSpy = spyOn(subsystemsService, 'getInstances').and.returnValue(['INST']); + spyOn(TestBed.get(ViewportScroller), 'scrollToPosition'); + spyOn(subsystemsService, 'setInstance').and.returnValue(null); + spyOn(TestBed.get(SubsystemsService), 'getApiUrlBase').and.returnValue('base'); + subsystemsService.subsystemsSubject = new BehaviorSubject([ + { + memberClass: '', + subsystemCode: '', + xRoadInstance: '', + subsystemStatus: '', + memberCode: '', + fullSubsystemName: 'INST/CLASS/MEMBER/SYSTEM', + methods: [] + } + ]); + }); + it('should create', () => { fixture = TestBed.createComponent(SubsystemComponent); component = fixture.componentInstance; fixture.detectChanges(); + expect(component).toBeTruthy(); }); - /*afterEach(() => { - TestBed.resetTestEnvironment() - });*/ + it('should detect incorrect instance', () => { + getInstancesSpy.and.returnValue(['XXX']); + fixture = TestBed.createComponent(SubsystemComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + expect(component.message).toBe('subsystem.incorrectInstanceWarning'); + }); - it('should create', () => { - expect(component).toBeTruthy(); + it('should detect when instance is not selected', () => { + getInstanceSpy.and.returnValue(''); + fixture = TestBed.createComponent(SubsystemComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + expect(subsystemsService.setInstance).toHaveBeenCalledWith('INST', ''); + }); + + it('should detect change instance', () => { + getInstanceSpy.and.returnValue('INST2'); + getInstancesSpy.and.returnValue(['INST', 'INST2']); + fixture = TestBed.createComponent(SubsystemComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + expect(subsystemsService.setInstance).toHaveBeenCalledWith('INST', ''); }); - /*it('scrollToPosition was called', async(() => { - expect(TestBed.get(ViewportScroller).scrollToPosition).toHaveBeenCalledWith([0, 0]) - }));*/ + it('should detect incorrect subsystem', () => { + subsystemsService.subsystemsSubject = new BehaviorSubject([ + { + memberClass: '', + subsystemCode: '', + xRoadInstance: '', + subsystemStatus: '', + memberCode: '', + fullSubsystemName: 'INST/CLASS/MEMBER2/SYSTEM', + methods: [] + } + ]); + fixture = TestBed.createComponent(SubsystemComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + expect(component.message).toBe('subsystem.subsystemNotFoundWarning'); + }); + + it('should scroll to position', () => { + fixture = TestBed.createComponent(SubsystemComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + expect(TestBed.get(ViewportScroller).scrollToPosition).toHaveBeenCalledWith([11, 12]); + }); + + it('getApiUrlBase should work', () => { + fixture = TestBed.createComponent(SubsystemComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + component.getApiUrlBase(); + expect(subsystemsService.getApiUrlBase).toHaveBeenCalled(); + }); + + it('goToList should work', () => { + fixture = TestBed.createComponent(SubsystemComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + component.goToList(); + expect(TestBed.get(Router).navigateByUrl).toHaveBeenCalledWith('/INST'); + }); + + it('should receive service warnings', () => { + fixture = TestBed.createComponent(SubsystemComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + subsystemsService.warnings.emit('WARN'); + expect(component.message).toBe('WARN'); + }); + + it('scrollToTop should work', () => { + fixture = TestBed.createComponent(SubsystemComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + const spy = TestBed.get(ViewportScroller).scrollToPosition; + spy.calls.reset(); + component.scrollToTop(); + expect(spy).toHaveBeenCalledWith([0, 0]); + }); +}); + + +describe('SubsystemComponent (with instance version)', () => { + let component: SubsystemComponent; + let fixture: ComponentFixture; + let getInstanceSpy; + let getInstancesSpy; + let subsystemsService: SubsystemsService; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + SubsystemComponent, + HeaderStubComponent, + MessagesStubComponent + ], + imports: [ + TranslateModule.forRoot(), + HttpClientModule + ], + providers: [ + { provide: ActivatedRoute, useValue: { + params: of({ + instance: 'INST', + class: 'CLASS', + member: 'MEMBER', + subsystem: 'SYSTEM' + }), + snapshot: { + queryParams: { + at: '12345' + } + } + }}, + { provide: Router, useValue: { + events: of(new Scroll(null, [11, 12], null)), + navigateByUrl: jasmine.createSpy('navigateByUrl') + }}, + { provide: AppConfig, useClass: AppConfigMock } + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + subsystemsService = TestBed.get(SubsystemsService); + getInstanceSpy = spyOn(subsystemsService, 'getInstance').and.returnValue('INST'); + getInstancesSpy = spyOn(subsystemsService, 'getInstances').and.returnValue(['INST']); + spyOn(TestBed.get(ViewportScroller), 'scrollToPosition'); + spyOn(subsystemsService, 'setInstance').and.returnValue(null); + spyOn(TestBed.get(SubsystemsService), 'getApiUrlBase').and.returnValue('base'); + subsystemsService.subsystemsSubject = new BehaviorSubject([ + { + memberClass: '', + subsystemCode: '', + xRoadInstance: '', + subsystemStatus: '', + memberCode: '', + fullSubsystemName: 'INST/CLASS/MEMBER/SYSTEM', + methods: [] + } + ]); + }); + + it('goToList should work with instance version', () => { + fixture = TestBed.createComponent(SubsystemComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + component.goToList(); + expect(TestBed.get(Router).navigateByUrl).toHaveBeenCalledWith('/INST?at=12345'); + }); }); diff --git a/src/app/subsystem/subsystem.component.ts b/src/app/subsystem/subsystem.component.ts index fae97582377023207e49f3f1f0c432e1f2e7d9f6..ff03b970080584e4f38f387539daa806716583ce 100644 --- a/src/app/subsystem/subsystem.component.ts +++ b/src/app/subsystem/subsystem.component.ts @@ -1,114 +1,129 @@ -import { Component, OnInit, OnDestroy } from '@angular/core'; -import { MethodsService } from '../methods.service'; +import { Component, OnInit, AfterViewInit, OnDestroy } from '@angular/core'; +import { SubsystemsService } from '../subsystems.service'; import { Subsystem } from '../subsystem'; import { ActivatedRoute, Router, Scroll } from '@angular/router'; -import { Subscription } from 'rxjs'; +import { Subscription, BehaviorSubject } from 'rxjs'; import { ViewportScroller } from '@angular/common'; -import { filter, take } from 'rxjs/operators'; +import { filter } from 'rxjs/operators'; @Component({ selector: 'app-subsystem', - templateUrl: './subsystem.component.html', - styleUrls: ['./subsystem.component.css'] + templateUrl: './subsystem.component.html' }) -export class SubsystemComponent implements OnInit, OnDestroy { - subsystem: Subsystem - subsystemId: string - message: string = '' - scrollPosition: [number, number] = [0, 0] - routerScrollSubscription: Subscription - routeSubscription: Subscription - updatedSubscription: Subscription - warningsSubscription: Subscription +export class SubsystemComponent implements OnInit, AfterViewInit, OnDestroy { + subsystemId = ''; + message = ''; + // Contains instance from route.params (for displaying warning) + paramsInstance = ''; + private scrollSubject: BehaviorSubject = new BehaviorSubject(null); + private routerScrollSubscription: Subscription; + private routeSubscription: Subscription; + private warningsSubscription: Subscription; + private scrollSubjectSubscription: Subscription; + private subsystemsSubscription: Subscription; + private instanceVersion: string; + subsystemSubject: BehaviorSubject = new BehaviorSubject(null); constructor( - private methodsService: MethodsService, + private subsystemsService: SubsystemsService, private route: ActivatedRoute, private router: Router, private viewportScroller: ViewportScroller ) { // Geting previous scroll position this.routerScrollSubscription = this.router.events.pipe( - filter(e => e instanceof Scroll), - take(1) - ).subscribe(e => { + filter(e => e instanceof Scroll) + ).subscribe(async (e) => { if ((e as Scroll).position) { - this.scrollPosition = (e as Scroll).position; - } else { - this.scrollPosition = [0, 0]; + this.scrollSubject.next((e as Scroll).position); } }); } - private checkSubsystem() { - // Do not overwrite previous warnings - if (!this.subsystem && !this.message) { - this.message = 'Subsystem "' + this.subsystemId + '" cannot be found!' - } -} + private getSubsystem(subsystems: Subsystem[], name: string): Subsystem { + return subsystems.find((element) => { + return element.fullSubsystemName === name; + }); + } + + getInstance(): string { + return this.subsystemsService.getInstance(); + } + + getApiUrlBase(): string { + return this.subsystemsService.getApiUrlBase(); + } + + goToList(): void { + this.router.navigateByUrl( + '/' + this.subsystemsService.getInstance() + + (this.instanceVersion ? '?at=' + this.instanceVersion : '') + ); + } + + scrollToTop() { + this.viewportScroller.scrollToPosition([0, 0]); + } ngOnInit() { // Reset message on page load - this.message = '' + this.message = ''; // Service will tell when data loading failed! - this.warningsSubscription = this.methodsService.warnings.subscribe(signal => { - this.message = signal + this.warningsSubscription = this.subsystemsService.warnings.subscribe(signal => { + this.message = signal; }); this.routeSubscription = this.route.params.subscribe( params => { // Checking if instance is correct - if (!this.methodsService.getInstances().includes(params['instance'])) { - this.message = 'Incorrect instance!' - return + if (!this.subsystemsService.getInstances().includes(params.instance)) { + this.paramsInstance = params.instance; + this.message = 'subsystem.incorrectInstanceWarning'; + return; } - this.subsystemId = params['instance'] + '/' + params['class'] + '/' + params['member'] + '/' + params['subsystem'] - // Only reload on switching of instance or when no instance is selected yet on service side - if (this.getInstance() == '' || this.getInstance() != params['instance']) { - this.methodsService.setInstance(params['instance'] ? params['instance'] : this.methodsService.getDefaultInstance()) + + // Set selected instance version + if (this.route.snapshot && this.route.snapshot.queryParams.at) { + this.instanceVersion = this.route.snapshot.queryParams.at; + } else { + this.instanceVersion = ''; } - }); - // Service will tell when data has finished loading - this.updatedSubscription = this.methodsService.subsystemsUpdated.subscribe(signal => { - this.subsystem = this.methodsService.getSubsystem(this.subsystemId) - if (this.methodsService.isLoadingDone() && !this.methodsService.isLoadingError()) { - this.checkSubsystem() + this.subsystemId = params.instance + '/' + params.class + '/' + params.member + '/' + params.subsystem; + + // Only reload on switching of instance or when no instance is selected yet on service side + if (this.getInstance() === '' || this.getInstance() !== params.instance) { + // this.subsystemsService.setInstance(params.instance ? params.instance : this.subsystemsService.getDefaultInstance()); + this.subsystemsService.setInstance(params.instance, this.instanceVersion); } + + this.subsystemsSubscription = this.subsystemsService.subsystemsSubject.subscribe(subsystems => { + const subsystem = this.getSubsystem(subsystems, this.subsystemId); + if (!subsystem && !this.message && subsystems.length) { + this.message = 'subsystem.subsystemNotFoundWarning'; + } else { + this.subsystemSubject.next(subsystem); + } + }); }); - // If json data is loaded update event will not be emited. - // This line must be after subscription (data may be changed while we start subscription) - if (this.subsystemId && this.methodsService.isLoadingDone() && !this.methodsService.isLoadingError()) { - this.subsystem = this.methodsService.getSubsystem(this.subsystemId) - this.checkSubsystem() - } } ngAfterViewInit() { // Restoring scroll position - this.viewportScroller.scrollToPosition(this.scrollPosition); - // TODO: what if this.scrollPosition is not ready yet? - /*this.routerScrollSubscription.add(() => { - this.viewportScroller.scrollToPosition(this.scrollPosition); - })*/ + this.scrollSubjectSubscription = this.scrollSubject.subscribe( position => { + if (position) { + this.viewportScroller.scrollToPosition(position); + } + }); } ngOnDestroy() { - this.routerScrollSubscription.unsubscribe() - this.routeSubscription.unsubscribe() - this.updatedSubscription.unsubscribe() - this.warningsSubscription.unsubscribe() - } - - getInstance(): string { - return this.methodsService.getInstance() - } - - getApiUrlBase(): string { - return this.methodsService.getApiUrlBase() - } - - goToList(): void { - this.router.navigateByUrl('/' + this.methodsService.getInstance()) + this.routerScrollSubscription.unsubscribe(); + this.routeSubscription.unsubscribe(); + this.warningsSubscription.unsubscribe(); + this.scrollSubjectSubscription.unsubscribe(); + if (this.subsystemsSubscription) { + this.subsystemsSubscription.unsubscribe(); + } } } diff --git a/src/app/subsystems.service.spec.ts b/src/app/subsystems.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..0106e015ac2c04400b14fd1cb79914f17754d1a1 --- /dev/null +++ b/src/app/subsystems.service.spec.ts @@ -0,0 +1,381 @@ +import { SubsystemsService } from './subsystems.service'; +import { of, defer } from 'rxjs'; +import { Subsystem } from './subsystem'; +import { Method } from './method'; +import { HttpErrorResponse } from '@angular/common/http'; +import { tick, fakeAsync } from '@angular/core/testing'; +import { AppConfigMock } from './app.config-mock'; +import { InstanceVersion } from './instance-version'; + +describe('SubsystemsService', () => { + let httpClientSpy: { get: jasmine.Spy }; + let service: SubsystemsService; + let config: AppConfigMock; + + beforeEach(() => { + httpClientSpy = jasmine.createSpyObj('HttpClient', ['get']); + config = new AppConfigMock(httpClientSpy as any); + service = new SubsystemsService(httpClientSpy as any, config); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should set instance on HTTP OK', fakeAsync(() => { + const sourceSubsystems = [ + { + memberClass: 'CLASS', + subsystemCode: 'SYSTEM', + xRoadInstance: 'INST', + subsystemStatus: 'OK', + memberCode: 'MEMBER', + methods: [ + { + methodStatus: 'OK', + serviceCode: 'SERVICE', + wsdl: 'URL', + serviceVersion: 'VER' + } + ] + }, + { + memberClass: 'CLASS', + subsystemCode: 'SYSTEM', + xRoadInstance: 'INST', + subsystemStatus: 'OK', + memberCode: 'MEMBER2', + methods: [] + } + ]; + const expectedSubsystems = [ + { + memberClass: 'CLASS', + subsystemCode: 'SYSTEM', + xRoadInstance: 'INST', + subsystemStatus: 'OK', + memberCode: 'MEMBER', + fullSubsystemName: 'INST/CLASS/MEMBER/SYSTEM', + methods: [ + { + methodStatus: 'OK', + serviceCode: 'SERVICE', + wsdl: 'URL', + serviceVersion: 'VER', + fullMethodName: 'INST/CLASS/MEMBER/SYSTEM/SERVICE/VER' + } + ] + }, + { + memberClass: 'CLASS', + subsystemCode: 'SYSTEM', + xRoadInstance: 'INST', + subsystemStatus: 'OK', + memberCode: 'MEMBER2', + fullSubsystemName: 'INST/CLASS/MEMBER2/SYSTEM', + methods: [] + } + ]; + httpClientSpy.get.and.returnValue(of(sourceSubsystems)); + // Setting value to test resetting of values + service.subsystemsSubject.next([new Subsystem()]); + service.filteredSubsystemsSubject.next([new Subsystem()]); + // Disabling updateInstanceVersions() + service.updateInstanceVersions = () => {}; + + service.setInstance('EE', ''); + // Waiting for asynchronous work + tick(); + expect(httpClientSpy.get).toHaveBeenCalledWith('https://www.x-tee.ee/catalogue/EE/wsdls/index.json'); + expect(service.subsystemsSubject.value).toEqual(expectedSubsystems); + // No filters yet + expect(service.filteredSubsystemsSubject.value).toEqual(expectedSubsystems); + + service.setInstance('EE', '12345'); + // Waiting for asynchronous work + tick(); + expect(httpClientSpy.get).toHaveBeenCalledWith('https://www.x-tee.ee/catalogue/EE/wsdls/index_12345.json'); + })); + + it('should set instance on HTTP ERROR', fakeAsync(() => { + const errorResponse = new HttpErrorResponse({error: 'error', status: 404, statusText: 'Not Found'}); + httpClientSpy.get.and.returnValue(defer(() => Promise.reject(errorResponse))); + // Setting value to test resetting of values + service.subsystemsSubject.next([new Subsystem()]); + service.filteredSubsystemsSubject.next([new Subsystem()]); + // Disabling updateInstanceVersions() + service.updateInstanceVersions = () => {}; + + service.setInstance('EE', ''); + // Waiting for asynchronous work + tick(); + expect(httpClientSpy.get).toHaveBeenCalledWith('https://www.x-tee.ee/catalogue/EE/wsdls/index.json'); + expect(service.subsystemsSubject.value).toEqual([]); + expect(service.filteredSubsystemsSubject.value).toEqual([]); + })); + + it('should set instance versions on HTTP OK', fakeAsync(() => { + const sourceHistory = [ + { + reportTime: '2019-04-15 08:01:41', + reportPath: 'index_20190415080141.json' + }, + { + reportTime: '2019-04-14 08:01:37', + reportPath: 'index_FAIL.json' + } + ]; + const expectedInstanceVersions = [ + { + reportTime: '2019-04-15 08:01:41', + reportTimeCompact: '20190415080141', + reportPath: 'index_20190415080141.json' + } + ]; + httpClientSpy.get.and.returnValue(of(sourceHistory)); + // Setting value to test resetting of values + service.instanceVersionsSubject.next([new InstanceVersion()]); + // Disabling updateSubsystems() + service.updateSubsystems = () => {}; + + service.setInstance('EE', ''); + // Waiting for asynchronous work + tick(); + expect(httpClientSpy.get).toHaveBeenCalledWith('https://www.x-tee.ee/catalogue/EE/wsdls/history.json'); + expect(service.instanceVersionsSubject.value).toEqual(expectedInstanceVersions); + + const longHistory = []; + for (let i = 0; i < config.getConfig('HISTORY_LIMIT') + 1; i++) { + longHistory.push( + { + reportTime: '2019-04-15 08:01:41', + reportPath: 'index_20190415080141.json' + } + ); + } + httpClientSpy.get.and.returnValue(of(longHistory)); + service.setInstance('EE'); + // Waiting for asynchronous work + tick(); + expect(longHistory.length).toBe(config.getConfig('HISTORY_LIMIT') + 1); + expect(service.instanceVersionsSubject.value.length).toEqual(config.getConfig('HISTORY_LIMIT')); + + })); + + it('should set instance versions on HTTP ERROR', fakeAsync(() => { + const errorResponse = new HttpErrorResponse({error: 'error', status: 404, statusText: 'Not Found'}); + httpClientSpy.get.and.returnValue(defer(() => Promise.reject(errorResponse))); + // Setting value to test resetting of values + service.instanceVersionsSubject.next([new InstanceVersion()]); + // Disabling updateSubsystems() + service.updateSubsystems = () => {}; + + service.setInstance('EE'); + // Waiting for asynchronous work + tick(); + expect(httpClientSpy.get).toHaveBeenCalledWith('https://www.x-tee.ee/catalogue/EE/wsdls/history.json'); + expect(service.instanceVersionsSubject.value).toEqual([]); + })); + + it('should filter nonEmpty subsystems', () => { + const sourceSubsystems = [ + { + memberClass: 'CLASS', + subsystemCode: 'SYSTEM', + xRoadInstance: 'INST', + subsystemStatus: 'OK', + memberCode: 'MEMBER', + fullSubsystemName: 'INST/CLASS/MEMBER/SYSTEM', + methods: [{} as Method] + }, + { + memberClass: 'CLASS', + subsystemCode: 'SYSTEM', + xRoadInstance: 'INST', + subsystemStatus: 'OK', + memberCode: 'MEMBER2', + fullSubsystemName: 'INST/CLASS/MEMBER2/SYSTEM', + methods: [] + } + ]; + const expectedSubsystems = [ + { + memberClass: 'CLASS', + subsystemCode: 'SYSTEM', + xRoadInstance: 'INST', + subsystemStatus: 'OK', + memberCode: 'MEMBER', + fullSubsystemName: 'INST/CLASS/MEMBER/SYSTEM', + methods: [{} as Method] + } + ]; + service.subsystemsSubject.next(sourceSubsystems); + service.setNonEmpty(true); + expect(service.filteredSubsystemsSubject.value).toEqual(expectedSubsystems); + }); + + it('should set filter for subsystems', fakeAsync(() => { + const sourceSubsystems = [ + { + memberClass: 'CLASS', + subsystemCode: 'SYSTEM', + xRoadInstance: 'INST', + subsystemStatus: 'OK', + memberCode: 'MEMBER', + fullSubsystemName: 'INST/CLASS/MEMBER/SYSTEM', + methods: [ + { + methodStatus: 'OK', + serviceCode: 'SERVICE', + wsdl: 'URL', + serviceVersion: 'VER', + fullMethodName: 'INST/CLASS/MEMBER/SYSTEM/SERVICE/VER' + }, + { + methodStatus: 'OK', + serviceCode: 'SERVICE2', + wsdl: 'URL', + serviceVersion: 'VER', + fullMethodName: 'INST/CLASS/MEMBER/SYSTEM/SERVICE2/VER' + } + ] + }, + { + memberClass: 'CLASS', + subsystemCode: 'SYSTEM', + xRoadInstance: 'INST', + subsystemStatus: 'OK', + memberCode: 'MEMBER2', + fullSubsystemName: 'INST/CLASS/MEMBER2/SYSTEM', + methods: [] + } + ]; + const expectedSubsystems1 = [ + { + memberClass: 'CLASS', + subsystemCode: 'SYSTEM', + xRoadInstance: 'INST', + subsystemStatus: 'OK', + memberCode: 'MEMBER2', + fullSubsystemName: 'INST/CLASS/MEMBER2/SYSTEM', + methods: [] + } + ]; + const expectedSubsystems2 = [ + { + memberClass: 'CLASS', + subsystemCode: 'SYSTEM', + xRoadInstance: 'INST', + subsystemStatus: 'OK', + memberCode: 'MEMBER', + fullSubsystemName: 'INST/CLASS/MEMBER/SYSTEM', + methods: [ + { + methodStatus: 'OK', + serviceCode: 'SERVICE2', + wsdl: 'URL', + serviceVersion: 'VER', + fullMethodName: 'INST/CLASS/MEMBER/SYSTEM/SERVICE2/VER' + } + ] + } + ]; + service.subsystemsSubject.next(sourceSubsystems); + + // Search member without methods + service.setFilter('MEMBER2'); + // Waiting for a debounce time to apply filter + tick(config.getConfig('FILTER_DEBOUNCE')); + expect(service.filteredSubsystemsSubject.value).toEqual(expectedSubsystems1); + + // Search member with multiple methods + service.setFilter('SERVICE2'); + // Waiting for a debounce time to apply filter + tick(config.getConfig('FILTER_DEBOUNCE')); + expect(service.filteredSubsystemsSubject.value).toEqual(expectedSubsystems2); + + // Search with limit + const sourceSubsystems2 = []; + for (let i = 0; i < config.getConfig('DEFAULT_LIMIT') + 1; i++) { + sourceSubsystems2.push( + { + memberClass: 'CLASS', + subsystemCode: 'SYSTEM', + xRoadInstance: 'INST', + subsystemStatus: 'OK', + memberCode: 'MEMBER' + i, + fullSubsystemName: 'INST/CLASS/MEMBER' + i + '/SYSTEM', + methods: [] + } + ); + } + service.subsystemsSubject.next(sourceSubsystems2); + service.setFilter('MEMBER'); + // Waiting for a debounce time to apply filter + tick(config.getConfig('FILTER_DEBOUNCE')); + expect(service.filteredSubsystemsSubject.value.length).toEqual(config.getConfig('DEFAULT_LIMIT')); + })); + + it('should set limit', () => { + const sourceSubsystems = []; + for (let i = 0; i < 51; i++) { + sourceSubsystems.push( + { + memberClass: 'CLASS', + subsystemCode: 'SYSTEM', + xRoadInstance: 'INST', + subsystemStatus: 'OK', + memberCode: 'MEMBER' + i, + fullSubsystemName: 'INST/CLASS/MEMBER' + i + '/SYSTEM', + methods: [] + } + ); + } + service.subsystemsSubject.next(sourceSubsystems); + + service.setLimit('all'); + expect(service.filteredSubsystemsSubject.value.length).toEqual(51); + + service.setLimit('50'); + expect(service.filteredSubsystemsSubject.value.length).toEqual(50); + + service.setLimit('20'); + expect(service.filteredSubsystemsSubject.value.length).toEqual(20); + + service.setLimit('10'); + expect(service.filteredSubsystemsSubject.value.length).toEqual(10); + + // Should set default limit of 10 + service.setLimit('5'); + expect(service.filteredSubsystemsSubject.value.length).toEqual(10); + }); + + it('getLimit should work', () => { + expect(service.getLimit()).toEqual(config.getConfig('DEFAULT_LIMIT').toString()); + + service.setLimit('all'); + expect(service.getLimit()).toEqual('all'); + }); + + it('getLimits should work', () => { + expect(service.getLimits()).toEqual(config.getConfig('LIMITS')); + }); + + it('getInstances should work', () => { + expect(service.getInstances()).toEqual(Object.keys(config.getConfig('INSTANCES'))); + }); + + it('getDefaultInstance should work', () => { + expect(service.getDefaultInstance()).toEqual(Object.keys(config.getConfig('INSTANCES'))[0]); + }); + + it('getInstance should work', () => { + // Default value + expect(service.getInstance()).toEqual(''); + }); + + it('getApiUrlBase should work', () => { + // Default value + expect(service.getApiUrlBase()).toEqual(''); + }); +}); diff --git a/src/app/subsystems.service.ts b/src/app/subsystems.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..d557da7c418e67f6e3482746011f2b52b6675f2c --- /dev/null +++ b/src/app/subsystems.service.ts @@ -0,0 +1,235 @@ +import { Injectable, EventEmitter } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { of, BehaviorSubject, Subject } from 'rxjs'; +import { catchError, debounceTime, distinctUntilChanged } from 'rxjs/operators'; +import { Subsystem } from './subsystem'; +import { Method } from './method'; +import { AppConfig } from './app.config'; +import { InstanceVersion } from './instance-version'; + +@Injectable({ + providedIn: 'root' +}) +export class SubsystemsService { + private apiUrlBase = ''; + private limit: number = this.config.getConfig('DEFAULT_LIMIT'); + private nonEmpty = false; + private filter = ''; + private instance = ''; + private instanceVersion = ''; + subsystemsSubject: BehaviorSubject = new BehaviorSubject([]); + filteredSubsystemsSubject: BehaviorSubject = new BehaviorSubject([]); + instanceVersionsSubject: BehaviorSubject = new BehaviorSubject([]); + private updateFilter = new Subject(); + + warnings: EventEmitter = new EventEmitter(); + + constructor( + private http: HttpClient, + private config: AppConfig + ) { + // Debouncing update of filter + this.updateFilter.pipe( + debounceTime(this.config.getConfig('FILTER_DEBOUNCE')), + distinctUntilChanged() + ).subscribe(() => { + this.updateFiltered(); + }); + } + + private filteredSubsystems(): Subsystem[] { + const filtered: Subsystem[] = []; + let limit: number = this.limit; + for (let subsystem of this.subsystemsSubject.value) { + if (this.nonEmpty && !subsystem.methods.length) { + // Filtering out empty subsystems + continue; + } + if ( + this.filter !== '' + && !subsystem.methods.length + ) { + // Subsystem without methods + if (subsystem.fullSubsystemName.toLowerCase().indexOf(this.filter.toLowerCase()) < 0) { + // Subsystem name does not match the filter + continue; + } + } else if (this.filter !== '') { + // Subsystem with methods + const filteredMethods: Method[] = []; + for (const method of subsystem.methods) { + if (method.fullMethodName.toLowerCase().indexOf(this.filter.toLowerCase()) >= 0) { + filteredMethods.push(method); + } + } + if (!filteredMethods.length) { + // No matching method names found + continue; + } + // Copy object to avoid overwriting methods array in subsystem object + subsystem = Object.assign(Object.create(subsystem), subsystem); + // Leaving only matcing methods + subsystem.methods = filteredMethods; + } + filtered.push(subsystem); + limit -= 1; + if (limit === 0) { + break; + } + } + return filtered; + } + + private setFullNames(subsystems: Subsystem[]): Subsystem[] { + for (const subsystem of subsystems) { + subsystem.fullSubsystemName = subsystem.xRoadInstance + + '/' + subsystem.memberClass + + '/' + subsystem.memberCode + + '/' + subsystem.subsystemCode; + for (const method of subsystem.methods) { + method.fullMethodName = subsystem.fullSubsystemName + + '/' + method.serviceCode + + '/' + method.serviceVersion; + } + } + return subsystems; + } + + private emitWarning(msg: string) { + this.warnings.emit(msg); + } + + private updateFiltered() { + this.filteredSubsystemsSubject.next(this.filteredSubsystems()); + } + + getDefaultInstance(): string { + return Object.keys(this.config.getConfig('INSTANCES'))[0]; + } + + getInstances(): string[] { + return Object.keys(this.config.getConfig('INSTANCES')); + } + + getInstance(): string { + return this.instance; + } + + // Not "private" to be able to override in unit tests + updateSubsystems() { + // Reset only if has values (less refreshes) + if (this.subsystemsSubject.value.length) { + this.subsystemsSubject.next([]); + } + this.http.get( + this.apiUrlBase + (this.instanceVersion ? 'index_' + this.instanceVersion + '.json' : this.config.getConfig('API_SERVICE')) + ).pipe( + catchError( () => { + this.emitWarning('service.dataLoadingError'); + // Let the app keep running by returning an empty result. + return of([]); + }) + ).subscribe(subsystems => { + this.subsystemsSubject.next(this.setFullNames(subsystems)); + this.updateFiltered(); + }); + } + + // Not "private" to be able to override in unit tests + updateInstanceVersions() { + this.instanceVersionsSubject.next([]); + this.http.get(this.apiUrlBase + this.config.getConfig('API_HISTORY')) + .pipe( + catchError(() => { + // Let the app keep running by returning an empty result. + return of([]); + }) + ).subscribe(history => { + const versions: InstanceVersion[] = []; + for (const version of history) { + const matches = version.reportPath.match(/index_(\d+).json/); + if (matches && matches.length === 2) { + version.reportTimeCompact = matches[1]; + versions.push(version); + } + if (versions.length >= this.config.getConfig('HISTORY_LIMIT')) { + break; + } + } + this.instanceVersionsSubject.next(versions); + }); + } + + setInstance(instance: string, instanceVersion: string = '') { + this.instance = instance; + this.instanceVersion = instanceVersion; + this.apiUrlBase = this.config.getConfig('INSTANCES')[instance]; + + this.updateSubsystems(); + this.updateInstanceVersions(); + } + + getApiUrlBase(): string { + return this.apiUrlBase; + } + + getApiUrl(): string { + return this.apiUrlBase + this.config.getConfig('API_SERVICE'); + } + + getLimit(): string { + if (this.limit === this.config.getConfig('MAX_LIMIT')) { + return 'all'; + } + return this.limit.toString(); + } + + getLimits(): object { + return this.config.getConfig('LIMITS'); + } + + setLimit(limit: string) { + const limits = this.config.getConfig('LIMITS'); + let found = false; + for (const key of Object.keys(limits)) { + if (limit === key) { + this.limit = limits[key]; + found = true; + break; + } + } + if (!found && limit === 'all') { + this.limit = this.config.getConfig('MAX_LIMIT'); + found = true; + } + if (!found) { + this.limit = this.config.getConfig('DEFAULT_LIMIT'); + } + this.updateFiltered(); + } + + getNonEmpty(): boolean { + return this.nonEmpty; + } + + setNonEmpty(nonEmpty: boolean) { + this.nonEmpty = nonEmpty; + this.updateFiltered(); + } + + getfilter(): string { + return this.filter; + } + + setFilter(filter: string) { + if (this.filter !== filter.trim()) { + this.filter = filter.trim(); + // Debouncing update of filter + this.updateFilter.next(this.filter); + } + } + + getInstanceVersion(): string { + return this.instanceVersion; + } +} diff --git a/src/assets/config.json b/src/assets/config.json new file mode 100644 index 0000000000000000000000000000000000000000..795d56d686f3643020c4e41478cfb1b86be5b701 --- /dev/null +++ b/src/assets/config.json @@ -0,0 +1,23 @@ +{ + "MAX_LIMIT": 1000000, + "DEFAULT_LIMIT": 10, + "LIMITS": { + "10": 10, + "20": 20, + "50": 50 + }, + "INSTANCES": { + "EE": "https://www.x-tee.ee/catalogue/EE/wsdls/", + "ee-test": "https://www.x-tee.ee/catalogue/ee-test/wsdls/", + "ee-dev": "https://www.x-tee.ee/catalogue/ee-dev/wsdls/" + }, + "API_SERVICE": "index.json", + "API_HISTORY": "history.json", + "HISTORY_LIMIT": 30, + "LANGUAGES": { + "EST": "est", + "ENG": "eng" + }, + "PREVIEW_SIZE": 5, + "FILTER_DEBOUNCE": 200 +} diff --git a/src/assets/i18n/eng.json b/src/assets/i18n/eng.json index f84c5f3be2cda591b85c6d868e2d53eeb8cb6682..1b17f9c82a5a336f3723ef7097de8b26d8befe09 100644 --- a/src/assets/i18n/eng.json +++ b/src/assets/i18n/eng.json @@ -1,35 +1,47 @@ { - "index": { - "title": "X-tee Subsystems and Methods catalogue" + "index": { + "title": "X-tee Subsystems and Methods catalogue" + }, + "service": { + "dataLoadingError": "Error while loading data from server!" + }, + "subsystemList": { + "heading": "Catalogue of all X-tee subsystems with methods and WSDL descriptions", + "intro": { + "p1": "This catalogue is generated by making getWsdl requests from RIA's Monitoring Security Server to all X-tee subsystems.", + "p2": "Subsystems with the Empty icon have no X-Tee services available.", + "p3": "Subsystems with the Error icon either could not be reached by RIA's Monitoring Security Server or there was some other error during the request.", + "p4": "Latest catalogue version in JSON form: JSON", + "p5": "Support: help@ria.ee" }, - "subsystemList": { - "heading": "Catalogue of all X-tee subsystems with methods and WSDL descriptions", - "intro": { - "p1": "This catalogue is generated by making getWsdl requests from RIA's Monitoring Security Server to all X-tee subsystems.", - "p2": "Subsystems with the Empty icon have no X-Tee services available.", - "p3": "Subsystems with the Error icon either could not be reached by RIA's Monitoring Security Server or there was some other error during the request.", - "p4": "Support: help@ria.ee" - }, - "selectInstance": "Select X-tee instance" + "moreSubsystems": "To see more subsystems change amount of subsystems to display or filter by subsystem or service name.", + "selectInstance": "Select X-tee instance", + "selectVersion": "Select methods catalogue version", + "latestVersion": "Latest version" + }, + "search": { + "searchFilters": "Search filters", + "nameFilter": "Filter by name", + "limit": "Amount of subsystems to display", + "nonEmpty": "Show only producer subsystems", + "allOption": "All" + }, + "subsystem": { + "heading": "Subsystem {{subsystem}} with methods and WSDL descriptions for X-tee instance \"{{instance}}\"", + "intro": { + "p1": "Support: help@ria.ee" }, - "search": { - "searchFilters": "Search filters", - "nameFilter": "Filter by name", - "limit": "Amount of subsystems to display", - "nonEmpty": "Show only producer subsystems", - "allOption": "All" - }, - "subsystem": { - "heading": "Subsystem {{subsystem}} with methods and WSDL descriptions for X-tee instance \"{{instance}}\"", - "intro": { - "p1": "Support: help@ria.ee" - }, - "statusEmpty": "Empty", - "statusError": "Error", - "statusWsdlError": "Error while downloading or parsing of WSDL", - "allSystemsBtn": "Show all subsystems", - "statusErrorInfo": "Subsystem either could not be reached by RIA's Monitoring Security Server or there was some other error during the request.", - "statusEmptyInfo": "Subsystem does not have any methods.", - "moreMethods": "{{count}} more..." - } -} \ No newline at end of file + "statusEmpty": "Empty", + "statusError": "Error", + "statusWsdlError": "Error while downloading or parsing of WSDL", + "statusWsdlTimeout": "WSDL query timed out", + "statusWsdlSkipped": "WSDL skipped due to previous Timeout", + "allSystemsBtn": "Show all subsystems", + "statusErrorInfo": "Subsystem either could not be reached by RIA's Monitoring Security Server or there was some other error during the request.", + "statusEmptyInfo": "Subsystem does not have any methods.", + "moreMethods": "{{count}} more ...", + "incorrectInstanceWarning": "Incorrect instance!", + "subsystemNotFoundWarning": "Subsystem \"{{subsystem}}\" cannot be found!" + }, + "scrollToTop": "Scroll to top" +} diff --git a/src/assets/i18n/est.json b/src/assets/i18n/est.json index 2fc97947e452fc1f4b4fb60bdc3a122464ec8882..8dee45fb3d459c864a9152d23dec80d1e1eab692 100644 --- a/src/assets/i18n/est.json +++ b/src/assets/i18n/est.json @@ -1,35 +1,47 @@ { - "index": { - "title": "X-tee alamsüsteemide ja teenuste kataloog" + "index": { + "title": "X-tee alamsüsteemide ja teenuste kataloog" + }, + "service": { + "dataLoadingError": "Viga andmete laadimisel serverist!" + }, + "subsystemList": { + "heading": "X-tee alamsüsteemide kataloog teenuste ja WSDL kirjeldustega", + "intro": { + "p1": "Antud kataloog on genereeritud saates getWsdl päringud RIA monitooringu turvaserverist kõigi X-tee alamsüsteemide pihta.", + "p2": "Alamsüsteemid Tühi ikooniga ei oma X-tee teenuseid.", + "p3": "Alamsüsteemid Viga ikooniga kas ei ole ligipääsetavad RIA monitooringu turvaserveri poolt, või päringu tegemisel ilmnes muu viga.", + "p4": "Kõige värskem kataloogi versioon JSON kujul: JSON", + "p5": "Kasutajatugi: help@ria.ee" }, - "subsystemList": { - "heading": "X-tee alamsüsteemide kataloog teenuste ja WSDL kirjeldustega", - "intro": { - "p1": "Antud kataloog on genereeritud saates getWsdl päringud RIA monitooringu turvaserverist kõigi X-tee alamsüsteemide pihta.", - "p2": "Alamsüsteemid Tühi ikooniga ei oma X-tee teenuseid.", - "p3": "Alamsüsteemid Viga ikooniga kas ei ole ligipääsetavad RIA monitooringu turvaserveri poolt, või päringu tegemisel ilmnes muu viga.", - "p4": "Kasutajatugi: help@ria.ee" - }, - "selectInstance": "Vali X-tee instants" + "moreSubsystems": "Selleks, et näha rohkem alamsüsteeme, muutke alamsüsteemide näitamise piirangu või otsige alamsüsteemi või teenuse nime järgi.", + "selectInstance": "Vali X-tee instants", + "selectVersion": "Vali teenuste kataloogi versiooni", + "latestVersion": "Viimane versioon" + }, + "search": { + "searchFilters": "Otsingu filtrid", + "nameFilter": "Filtreeri nime järgi", + "limit": "Alamsüsteemide näitamise piirang", + "nonEmpty": "Näita vaid teenustepakkujad", + "allOption": "Kõik" + }, + "subsystem": { + "heading": "Alamsüsteem {{subsystem}} teenuste ja WSDL kirjeldustega X-tee instantsi \"{{instance}}\" jaoks", + "intro": { + "p1": "Kasutajatugi: help@ria.ee" }, - "search": { - "searchFilters": "Otsingu filtrid", - "nameFilter": "Filtreeri nime järgi", - "limit": "Alamsüsteemide näitamise piirang", - "nonEmpty": "Näita vaid teenustepakkujad", - "allOption": "Kõik" - }, - "subsystem": { - "heading": "Alamsüsteem {{subsystem}} teenuste ja WSDL kirjeldustega X-tee instantsi \"{{instance}}\" jaoks", - "intro": { - "p1": "Kasutajatugi: help@ria.ee" - }, - "statusEmpty": "Tühi", - "statusError": "Viga", - "statusWsdlError": "Viga WSDL'i laadimisel või töötlemisel", - "allSystemsBtn": "Näita kõiki alamsüsteeme", - "statusErrorInfo": "Alamsüsteem kas ei ole ligipääsetav RIA monitooringu turvaserveri poolt, või päringu tegemisel ilmnes muu viga.", - "statusEmptyInfo": "Alamsüsteem ei oma X-tee teenuseid.", - "moreMethods": "veel {{count}}..." - } + "statusEmpty": "Tühi", + "statusError": "Viga", + "statusWsdlError": "Viga WSDL'i laadimisel või töötlemisel", + "statusWsdlTimeout": "WSDL'i laadimine aegus", + "statusWsdlSkipped": "Eelneva aegumise tõttu WSDL'i laadimine pole teostatud", + "allSystemsBtn": "Näita kõiki alamsüsteeme", + "statusErrorInfo": "Alamsüsteem kas ei ole ligipääsetav RIA monitooringu turvaserveri poolt, või päringu tegemisel ilmnes muu viga.", + "statusEmptyInfo": "Alamsüsteem ei oma X-tee teenuseid.", + "moreMethods": "veel {{count}} ...", + "incorrectInstanceWarning": "Vale instants!", + "subsystemNotFoundWarning": "Alamsüsteem \"{{subsystem}}\" ei ole leitud!" + }, + "scrollToTop": "Lehe algusesse" } \ No newline at end of file diff --git a/src/favicon.ico b/src/favicon.ico index 59be6177eaadcf1c6d9b2d5fb6c4aae7b64eada2..04acd86c8ef6950acc34651f41e18dff304a533c 100644 Binary files a/src/favicon.ico and b/src/favicon.ico differ diff --git a/src/favicon_orig.ico b/src/favicon_orig.ico new file mode 100644 index 0000000000000000000000000000000000000000..59be6177eaadcf1c6d9b2d5fb6c4aae7b64eada2 Binary files /dev/null and b/src/favicon_orig.ico differ diff --git a/src/karma.conf.js b/src/karma.conf.js index 85c6c360aa5719877eab63a4ad9d632da52abaef..95bd189470e4b581448d532cf0a65c8295b11da5 100644 --- a/src/karma.conf.js +++ b/src/karma.conf.js @@ -3,6 +3,12 @@ module.exports = function (config) { config.set({ + customLaunchers: { + ChromiumDocker: { + base: 'ChromiumHeadless', + flags: ['--no-sandbox'] + } + }, basePath: '', frameworks: ['jasmine', '@angular-devkit/build-angular'], plugins: [ @@ -16,7 +22,7 @@ module.exports = function (config) { clearContext: false // leave Jasmine Spec Runner output visible in browser }, coverageIstanbulReporter: { - dir: require('path').join(__dirname, '../coverage/methods'), + dir: require('path').join(__dirname, '../coverage/xtss-catalogue'), reports: ['html', 'lcovonly', 'text-summary'], fixWebpackSourcePaths: true },