diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..e0972a5241ed071536f133a467a0661e0f902c9a --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,35 @@ +stages: + - trigger-child-pipelines + +anis-client: + stage: trigger-child-pipelines + trigger: + include: client/.gitlab-ci.yml + strategy: depend + only: + changes: + - client/**/* + refs: + - develop + +anis-server: + stage: trigger-child-pipelines + trigger: + include: server/.gitlab-ci.yml + strategy: depend + only: + changes: + - server/**/* + refs: + - develop + +anis-services: + stage: trigger-child-pipelines + trigger: + include: services/.gitlab-ci.yml + strategy: depend + only: + changes: + - services/**/* + refs: + - develop \ No newline at end of file diff --git a/LICENSE b/LICENSE index bb3cf1c5764313c8e553bacb424b62a30af73ff0..5a52df68a6df724c157b0743657d6c83b7e49a92 100644 --- a/LICENSE +++ b/LICENSE @@ -1,14 +1,15 @@ -AstroNomical Information System +AstroNomical Information System - ANIS + website: https://anis.lam.fr Copyright: CNRS - 2021 - Address: Centre de donneeS Astrophysique de Marseille (CeSAM) + Address: Centre de donnéeS Astrophysique de Marseille (CeSAM) Laboratoire d'Astrophysique de Marseille - Ple de l'Etoile, site de Chteau-Gombert - 38, rue Frdric Joliot-Curie + Pôle de l'Etoile, site de Château-Gombert + 38, rue Frédéric Joliot-Curie 13388 Marseille cedex 13 France CNRS U.M.R 7326 -Anis Server is governed by the CeCILL license under French law and +ANIS is governed by the CeCILL license under French law and abiding by the rules of distribution of free software. You can use, modify and/ or redistribute the software under the terms of the CeCILL license as circulated by CEA, CNRS and INRIA at the following URL diff --git a/Makefile b/Makefile index 0368837d826004435fc49f42ac9940da72657ad0..be40c4535f47f42898b06fcd34a33ef361223216 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,7 @@ list: @echo " shell_client > shell into angular client container" @echo " build_client > generate the angular client dist application (html, css, js)" @echo " test_client > Starts the angular client unit tests" + @echo " client_coverage > Run nginx web server test client coverage" @echo " install_server > install server dependencies" @echo " shell_server > shell into php server container" @echo " test_server > Starts the server php unit tests" @@ -54,7 +55,10 @@ build_client: @docker-compose exec client ng build test_client: - @docker-compose exec client ng test --no-watch --code-coverage + @docker-compose exec client npx jest + +client_coverage: + @docker run --name anis-client-code-coverage -d -p 8888:80 -v $(CURDIR)/client/coverage/anis-client:/usr/share/nginx/html:ro nginx install_server: @docker run --init -it --rm --user $(UID):$(GID) \ diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..c68157c1a6a96fd9bee8def6b8dc7b32d86adaac --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# AstroNomical Information System - ANIS + +## Introduction + +AstroNomical Information System is a generic web tool that aims to facilitate +the provision of data (Astrophysics), accessible from a database, for the scientific +community. + +This software allows you to control one or more databases related to astronomical +projects and allows access to datasets via a web interface or URLs. + +Anis is protected by the CeCILL licence (see LICENCE file at the software root). + +## Authors + +Here is the list of people involved in the development: + +* `François Agneray` : Laboratoire d'Astrophysique de Marseille (CNRS) +* `Chrystel Moreau` : Laboratoire d'Astrophysique de Marseille (CNRS) +* `Tifenn Guillas` : Laboratoire d'Astrophysique de Marseille (CNRS) + +## More resources: + +* [Website](https://anis.lam.fr) +* [Documentation](https://anis.lam.fr/doc/) + +## Installing and starting the application + +Anis Server contains a Makefile that helps the developer to install and start the application. + +To list all operations availables just type `make` in your terminal at the root of this application. + +- To install client dependancies: `make install_client` +- To install server dependancies: `make install_server` +- To build or rebuild all docker images and start containers: `make rebuild` +- To start/stop/restart/status all services: `make start|stop|restart|status` +- To display logs for all services: `make logs` +- To open a shell command into client container: `make shell_client` +- To open a shell command into server container: `make shell_server` +- To execute server tests suite: `make test_server` +- To execute php code sniffer: `make phpcs` +- To create the metamodel database: `make create-db` +- TO remove the metadata database: `make remove-pgdata` + +## Web interface + +In development mode the web interface can be accessed at the following url : http://localhost:4200 diff --git a/client/.gitlab-ci.yml b/client/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..f803db047618684ee122a8bb17322c98f4d9dd8f --- /dev/null +++ b/client/.gitlab-ci.yml @@ -0,0 +1,83 @@ +stages: + - install_dependencies + - test + - sonar + - build + - dockerize + +variables: + VERSION: "3.7.0" + SONARQUBE_URL: https://sonarqube.lam.fr + +install_dependencies: + image: node:16-slim + stage: install_dependencies + cache: + key: ${CI_COMMIT_REF_SLUG}_client + paths: + - client/node_modules + policy: pull-push + script: + - cd client + - yarn install + +test: + image: node:16-slim + stage: test + cache: + key: ${CI_COMMIT_REF_SLUG}_client + paths: + - node_modules + policy: pull + script: + - cd client + - npx jest --coverage --coverageReporters lcov + artifacts: + paths: + - client/coverage + +sonar_scanner: + image: sonarsource/sonar-scanner-cli:latest + stage: sonar + cache: + key: ${CI_COMMIT_REF_SLUG}_client + paths: + - client/node_modules + policy: pull + script: + - cd client + - sonar-scanner + -Dsonar.projectKey=anis-client + -Dsonar.sources=src + -Dsonar.projectVersion=$VERSION + -Dsonar.host.url=$SONARQUBE_URL + -Dsonar.login=$SONAR_TOKEN_CLIENT + -Dsonar.exclusions=**.spec.ts + -Dsonar.typescript.lcov.reportPaths=./coverage/anis-client/lcov.info + +build: + image: node:16-slim + stage: build + cache: + key: ${CI_COMMIT_REF_SLUG}_client + paths: + - client/node_modules + policy: pull + script: + - cd client + - yarn global add @angular/cli@latest + - ng build + artifacts: + paths: + - client/dist + +dockerize: + image: docker:stable + stage: dockerize + cache: {} + script: + - cd client + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + - docker pull $CI_REGISTRY/anis/anis-next/client:latest || true + - docker build --cache-from $CI_REGISTRY/anis/anis-next/client:latest -t $CI_REGISTRY/anis/anis-next/client:latest . + - docker push $CI_REGISTRY/anis/anis-next/client:latest diff --git a/client/angular.json b/client/angular.json index 01838b184b3b0e34d8a5060e94bc566d373890d8..17efce44ec943e8de2e16019742c49a058247df6 100644 --- a/client/angular.json +++ b/client/angular.json @@ -89,24 +89,6 @@ "options": { "browserTarget": "client:build" } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "src/styles.scss" - ], - "scripts": [] - } } } } diff --git a/client/jest.config.js b/client/jest.config.js new file mode 100644 index 0000000000000000000000000000000000000000..d23d9551d318b95afc421fc394c4f3acb970d8f6 --- /dev/null +++ b/client/jest.config.js @@ -0,0 +1,16 @@ +const { pathsToModuleNameMapper } = require('ts-jest/utils'); +const { compilerOptions } = require('./tsconfig'); + +module.exports = { + preset: 'jest-preset-angular', + testMatch: ['**/+(*.)+(spec).+(ts|js)'], + setupFilesAfterEnv: ['<rootDir>/src/test.ts'], + collectCoverage: false, + collectCoverageFrom: ['src/**/*.ts'], + coverageReporters: ['html'], + coverageDirectory: 'coverage/anis-client', + moduleDirectories: ["node_modules", "./"], + moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths || {}, { + prefix: '<rootDir>/', + }), +}; diff --git a/client/karma.conf.js b/client/karma.conf.js deleted file mode 100644 index c6a90c10a8ee5c516f491e1bb1b2acc24763b9d0..0000000000000000000000000000000000000000 --- a/client/karma.conf.js +++ /dev/null @@ -1,44 +0,0 @@ -// Karma configuration file, see link for more information -// https://karma-runner.github.io/1.0/config/configuration-file.html - -module.exports = function (config) { - config.set({ - basePath: '', - frameworks: ['jasmine', '@angular-devkit/build-angular'], - plugins: [ - require('karma-jasmine'), - require('karma-chrome-launcher'), - require('karma-jasmine-html-reporter'), - require('karma-coverage'), - require('@angular-devkit/build-angular/plugins/karma') - ], - client: { - jasmine: { - // you can add configuration options for Jasmine here - // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html - // for example, you can disable the random execution with `random: false` - // or set a specific seed with `seed: 4321` - }, - clearContext: false // leave Jasmine Spec Runner output visible in browser - }, - jasmineHtmlReporter: { - suppressAll: true // removes the duplicated traces - }, - coverageReporter: { - dir: require('path').join(__dirname, './coverage/client'), - subdir: '.', - reporters: [ - { type: 'html' }, - { type: 'text-summary' } - ] - }, - reporters: ['progress', 'kjhtml'], - port: 9876, - colors: true, - logLevel: config.LOG_INFO, - autoWatch: true, - browsers: ['Chrome'], - singleRun: false, - restartOnFileChange: true - }); -}; diff --git a/client/package.json b/client/package.json index 7e7cad96734364427fdcc7fe3022f1e64f022190..1069d167dc4188e3a4e06442bc7e19522d57a83f 100644 --- a/client/package.json +++ b/client/package.json @@ -5,8 +5,7 @@ "ng": "ng", "start": "ng serve", "build": "ng build", - "watch": "ng build --watch --configuration development", - "test": "ng test" + "watch": "ng build --watch --configuration development" }, "private": true, "dependencies": { @@ -26,6 +25,7 @@ "@ngrx/store": "12.1.0", "@ngrx/store-devtools": "12.1.0", "bootstrap": "4.6", + "d3": "^5.15.1", "keycloak-angular": "^8.2.0", "keycloak-js": "^14.0.0", "ngx-bootstrap": "^7.0.0-rc.1", @@ -39,14 +39,13 @@ "@angular-devkit/build-angular": "~12.0.4", "@angular/cli": "~12.0.4", "@angular/compiler-cli": "~12.0.4", + "@types/d3": "^5.7.2", "@types/jasmine": "~3.6.0", + "@types/jest": "^26.0.24", "@types/node": "^12.11.1", "jasmine-core": "~3.7.0", - "karma": "~6.3.0", - "karma-chrome-launcher": "~3.1.0", - "karma-coverage": "~2.0.3", - "karma-jasmine": "~4.0.0", - "karma-jasmine-html-reporter": "^1.5.0", + "jest": "^27.0.6", + "jest-preset-angular": "^9.0.5", "typescript": "~4.2.3" } } diff --git a/client/src/app/admin/admin-auth.guard.ts b/client/src/app/admin/admin-auth.guard.ts new file mode 100644 index 0000000000000000000000000000000000000000..02ac7a5e6dea1d5f36f8abb33b1a912d2c75d9e5 --- /dev/null +++ b/client/src/app/admin/admin-auth.guard.ts @@ -0,0 +1,59 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { Injectable } from '@angular/core'; +import { CanActivate, Router } from '@angular/router'; +import { Store, select } from '@ngrx/store'; + +import { combineLatest, Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import * as authActions from 'src/app/auth/auth.actions'; +import * as authSelector from 'src/app/auth/auth.selector'; +import { AppConfigService } from 'src/app/app-config.service'; + +@Injectable({ + providedIn: 'root', +}) +export class AdminAuthGuard implements CanActivate { + constructor( + protected readonly router: Router, + private store: Store<{ }>, + private config: AppConfigService + ) { } + + canActivate(): Observable<boolean> { + return combineLatest([ + this.store.pipe(select(authSelector.selectUserRoles)), + this.store.pipe(select(authSelector.selectIsAuthenticated)) + ]).pipe( + map(([userRoles, isAuthenticated]) => { + // Auth disabled + if (!this.config.authenticationEnabled) { + return true; + } + + // Force the user to log in if currently unauthenticated. + if (!isAuthenticated) { + this.store.dispatch(authActions.login()); + return false; + } + + // If authenticated but not admin go to unauthorized page. + if (!userRoles.includes(this.config.adminRole)) { + this.router.navigateByUrl('/unauthorized'); + return false; + } + + // Let "Router" allow user entering the page + return true; + }) + ); + } +} diff --git a/client/src/app/admin/admin-routing.module.ts b/client/src/app/admin/admin-routing.module.ts index 6fa41e51897b04d580ea504f2da972238d9eed53..d3aef6a6b6acd59c9bd6da9d15e7d601e3b34eeb 100644 --- a/client/src/app/admin/admin-routing.module.ts +++ b/client/src/app/admin/admin-routing.module.ts @@ -11,6 +11,7 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { AdminComponent } from './admin.component'; +import { AdminAuthGuard } from './admin-auth.guard'; import { InstanceListComponent } from './containers/instance/instance-list.component'; import { NewInstanceComponent } from './containers/instance/new-instance.component'; import { EditInstanceComponent } from './containers/instance/edit-instance.component'; @@ -31,7 +32,7 @@ import { SettingsComponent } from './containers/settings/settings.component'; const routes: Routes = [ { - path: 'admin', component: AdminComponent, children: [ + path: '', component: AdminComponent, canActivate: [AdminAuthGuard], children: [ { path: '', redirectTo: 'instance-list', pathMatch: 'full' }, { path: 'instance-list', component: InstanceListComponent }, { path: 'new-instance', component: NewInstanceComponent }, @@ -56,7 +57,7 @@ const routes: Routes = [ ]; @NgModule({ - imports: [RouterModule.forRoot(routes)], + imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) export class AdminRoutingModule { } diff --git a/client/src/app/admin/admin.component.html b/client/src/app/admin/admin.component.html index 979df50e47db62138793be21dff9adf7df5e4cf1..61101934f1e33d3d27823eb8998cedcf423d891b 100644 --- a/client/src/app/admin/admin.component.html +++ b/client/src/app/admin/admin.component.html @@ -3,6 +3,8 @@ [links]="links" [isAuthenticated]="isAuthenticated | async" [userProfile]="userProfile | async" + [baseHref]="getBaseHref()" + [authenticationEnabled]="getAuthenticationEnabled()" (login)="login()" (logout)="logout()" (openEditProfile)="openEditProfile()"> diff --git a/client/src/app/admin/admin.component.ts b/client/src/app/admin/admin.component.ts index a66e4f324e6afb0e123c825b0e06dd1ec73d195d..93e97de10f1e36607d482147e840a4567f1e5e4c 100644 --- a/client/src/app/admin/admin.component.ts +++ b/client/src/app/admin/admin.component.ts @@ -14,6 +14,7 @@ import { Store } from '@ngrx/store'; import { UserProfile } from 'src/app/auth/user-profile.model'; import * as authActions from 'src/app/auth/auth.actions'; import * as authSelector from 'src/app/auth/auth.selector'; +import { AppConfigService } from 'src/app/app-config.service'; @Component({ selector: 'app-admin', @@ -27,7 +28,7 @@ import * as authSelector from 'src/app/auth/auth.selector'; */ export class AdminComponent { public links = [ - { label: 'Back to portal', icon: 'fas fa-level-up-alt', routerLink: '/portal-home' }, + { label: 'Back to portal', icon: 'fas fa-level-up-alt', routerLink: '/portal' }, { label: 'Instances', icon: 'fas fa-object-group', routerLink: 'instance-list' }, { label: 'Surveys', icon: 'fas fa-table', routerLink: 'survey-list'}, { label: 'Databases', icon: 'fas fa-database', routerLink: 'database-list'}, @@ -37,12 +38,20 @@ export class AdminComponent { public userProfile: Observable<UserProfile>; public userRoles: Observable<string[]>; - constructor(private store: Store<{ }>) { + constructor(private store: Store<{ }>, private config: AppConfigService) { this.isAuthenticated = store.select(authSelector.selectIsAuthenticated); this.userProfile = store.select(authSelector.selectUserProfile); this.userRoles = store.select(authSelector.selectUserRoles); } + getBaseHref() { + return this.config.baseHref; + } + + getAuthenticationEnabled() { + return this.config.authenticationEnabled; + } + login(): void { this.store.dispatch(authActions.login()); } diff --git a/client/src/app/admin/components/attribute/add-attribute.component.ts b/client/src/app/admin/components/attribute/add-attribute.component.ts index f7934d61f05212f998fcdcf66c10556de52e0046..fc050468a347ea1eb1ded1d298b01fdcbb1fd24b 100644 --- a/client/src/app/admin/components/attribute/add-attribute.component.ts +++ b/client/src/app/admin/components/attribute/add-attribute.component.ts @@ -1,3 +1,12 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter, TemplateRef } from '@angular/core'; import { BsModalService } from 'ngx-bootstrap/modal'; diff --git a/client/src/app/admin/components/attribute/criteria/generate-option-list.component.ts b/client/src/app/admin/components/attribute/criteria/generate-option-list.component.ts index 1b6905a2bbcd1f55707332bf561aa085ea78005d..cc0899ce9fea358fc0b06e4a8fc576a06518e951 100644 --- a/client/src/app/admin/components/attribute/criteria/generate-option-list.component.ts +++ b/client/src/app/admin/components/attribute/criteria/generate-option-list.component.ts @@ -1,3 +1,12 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter, TemplateRef } from '@angular/core'; import { BsModalService } from 'ngx-bootstrap/modal'; diff --git a/client/src/app/admin/components/attribute/criteria/option-form.component.ts b/client/src/app/admin/components/attribute/criteria/option-form.component.ts index 5e2a620d0a6985db4690cf84108b4e59b85d2ee2..cff2bdcdf35a3401fad6adf163e80240c75cdd84 100644 --- a/client/src/app/admin/components/attribute/criteria/option-form.component.ts +++ b/client/src/app/admin/components/attribute/criteria/option-form.component.ts @@ -1,3 +1,12 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { Component, Input, ChangeDetectionStrategy } from '@angular/core'; import { FormGroup } from '@angular/forms'; diff --git a/client/src/app/admin/components/attribute/criteria/option-list.component.ts b/client/src/app/admin/components/attribute/criteria/option-list.component.ts index 492bac069a3bfccdd9e8a9f3846a168651c1281b..c3e643a5a7c5cf9b145970aeb5853227a86f1139 100644 --- a/client/src/app/admin/components/attribute/criteria/option-list.component.ts +++ b/client/src/app/admin/components/attribute/criteria/option-list.component.ts @@ -1,3 +1,12 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { Component, Input, OnInit, Output, EventEmitter } from '@angular/core'; import { FormArray, FormControl, FormGroup, Validators } from '@angular/forms'; diff --git a/client/src/app/admin/components/attribute/criteria/table-criteria.component.ts b/client/src/app/admin/components/attribute/criteria/table-criteria.component.ts index fe5c7db6f47a4db658929ac124354620ee5286f4..9cf5e6dea58fc2c086408e5bbba425aea966a31b 100644 --- a/client/src/app/admin/components/attribute/criteria/table-criteria.component.ts +++ b/client/src/app/admin/components/attribute/criteria/table-criteria.component.ts @@ -1,3 +1,12 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { Component } from '@angular/core'; @Component({ diff --git a/client/src/app/admin/components/attribute/criteria/tr-criteria.component.ts b/client/src/app/admin/components/attribute/criteria/tr-criteria.component.ts index 87109e23f306817016211fcf8c23f33ee25ba656..9c1f559f4c038fd74a28174071260afdd97c489d 100644 --- a/client/src/app/admin/components/attribute/criteria/tr-criteria.component.ts +++ b/client/src/app/admin/components/attribute/criteria/tr-criteria.component.ts @@ -1,3 +1,12 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, OnInit } from '@angular/core'; import { FormArray, FormControl, FormGroup } from '@angular/forms'; @@ -6,7 +15,7 @@ import { Attribute, Option, CriteriaFamily, SelectOption } from 'src/app/metamod @Component({ selector: '[criteria]', templateUrl: 'tr-criteria.component.html', - styleUrls: [ '../tr.component.css' ], + styleUrls: [ '../tr.component.scss' ], changeDetection: ChangeDetectionStrategy.OnPush }) export class TrCriteriaComponent implements OnInit { diff --git a/client/src/app/admin/components/attribute/design/index.ts b/client/src/app/admin/components/attribute/design/index.ts index 97b51ae6380e1ab54108323c607878012b8500c0..e2a18cde45ee5821f4cbbdff63ac2ebf00d12480 100644 --- a/client/src/app/admin/components/attribute/design/index.ts +++ b/client/src/app/admin/components/attribute/design/index.ts @@ -1,3 +1,12 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { TableDesignComponent } from './table-design.component'; import { TrDesignComponent } from './tr-design.component'; diff --git a/client/src/app/admin/components/attribute/design/table-design.component.ts b/client/src/app/admin/components/attribute/design/table-design.component.ts index ece9403ec5149fcac9cddc7211434f8cbbb49ee1..4ca4666de803c2c57e84ff169e20140aad1b5861 100644 --- a/client/src/app/admin/components/attribute/design/table-design.component.ts +++ b/client/src/app/admin/components/attribute/design/table-design.component.ts @@ -1,3 +1,12 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { Component } from '@angular/core'; @Component({ diff --git a/client/src/app/admin/components/attribute/design/tr-design.component.ts b/client/src/app/admin/components/attribute/design/tr-design.component.ts index 72d1dcedc79ed6c4bde11309d5be0cb3db93df8e..f9a42f6fac4e54b93513e28e5a1365d3747cea2c 100644 --- a/client/src/app/admin/components/attribute/design/tr-design.component.ts +++ b/client/src/app/admin/components/attribute/design/tr-design.component.ts @@ -1,3 +1,12 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, OnInit } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; @@ -6,7 +15,7 @@ import { Attribute, SelectOption } from 'src/app/metamodel/models'; @Component({ selector: '[design]', templateUrl: 'tr-design.component.html', - styleUrls: [ '../tr.component.css' ], + styleUrls: [ '../tr.component.scss' ], changeDetection: ChangeDetectionStrategy.OnPush }) export class TrDesignComponent implements OnInit { diff --git a/client/src/app/admin/components/attribute/detail/index.ts b/client/src/app/admin/components/attribute/detail/index.ts index 41a27ae41042126954ad127f19255e1f7f4428ea..d1581ea5b138546e26d7eba21a7cd6d624ce1612 100644 --- a/client/src/app/admin/components/attribute/detail/index.ts +++ b/client/src/app/admin/components/attribute/detail/index.ts @@ -1,3 +1,12 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { TableDetailComponent } from './table-detail.component'; import { TrDetailComponent } from './tr-detail.component'; diff --git a/client/src/app/admin/components/attribute/detail/table-detail.component.ts b/client/src/app/admin/components/attribute/detail/table-detail.component.ts index 5ff070ee6bd57d8971192ca8ec16a1ac16f62e7e..f13fc516276e4de6ed864b1473db48c86d11ced8 100644 --- a/client/src/app/admin/components/attribute/detail/table-detail.component.ts +++ b/client/src/app/admin/components/attribute/detail/table-detail.component.ts @@ -1,3 +1,12 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { Component } from '@angular/core'; @Component({ diff --git a/client/src/app/admin/components/attribute/detail/tr-detail.component.ts b/client/src/app/admin/components/attribute/detail/tr-detail.component.ts index 2f268277c8ef10cca4a9d1e89afc7871c9fe552b..d485de9dbcd7c5aa3a5b15fa4b3a46824ee3a114 100644 --- a/client/src/app/admin/components/attribute/detail/tr-detail.component.ts +++ b/client/src/app/admin/components/attribute/detail/tr-detail.component.ts @@ -1,3 +1,12 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, OnInit } from '@angular/core'; import { FormControl, FormGroup } from '@angular/forms'; @@ -6,7 +15,7 @@ import { Attribute, SelectOption } from 'src/app/metamodel/models'; @Component({ selector: '[detail]', templateUrl: 'tr-detail.component.html', - styleUrls: [ '../tr.component.css' ], + styleUrls: [ '../tr.component.scss' ], changeDetection: ChangeDetectionStrategy.OnPush }) export class TrDetailComponent implements OnInit { diff --git a/client/src/app/admin/components/attribute/index.ts b/client/src/app/admin/components/attribute/index.ts index 4d35bcb434eb4bca8dce8b0d06c48966cb889285..fc2f19be83072e0b31e0947c6cacdeeca9ceb864 100644 --- a/client/src/app/admin/components/attribute/index.ts +++ b/client/src/app/admin/components/attribute/index.ts @@ -1,3 +1,12 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { AddAttributeComponent } from './add-attribute.component'; import { designComponents } from './design'; import { criteriaComponents } from './criteria'; diff --git a/client/src/app/admin/components/attribute/output/index.ts b/client/src/app/admin/components/attribute/output/index.ts index b5f11cda5869b5136f7e7f02e13f851565f60ef0..1c915eff1cc2d578dd535dd7e6ee51451e80c72b 100644 --- a/client/src/app/admin/components/attribute/output/index.ts +++ b/client/src/app/admin/components/attribute/output/index.ts @@ -1,3 +1,12 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { TableOutputComponent } from "./table-output.component"; import { TrOutputComponent } from "./tr-output.component"; diff --git a/client/src/app/admin/components/attribute/output/table-output.component.ts b/client/src/app/admin/components/attribute/output/table-output.component.ts index 30c36b2babb7851963e557fd76ec19233995529b..a2aefb31b4810d8402e43fdbc87c8f3e4ad21037 100644 --- a/client/src/app/admin/components/attribute/output/table-output.component.ts +++ b/client/src/app/admin/components/attribute/output/table-output.component.ts @@ -1,3 +1,12 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { Component } from '@angular/core'; @Component({ diff --git a/client/src/app/admin/components/attribute/output/tr-output.component.ts b/client/src/app/admin/components/attribute/output/tr-output.component.ts index a2a7afc994a2711a2acfe186f08b95cb49a85545..f75c845210810abaeaac0395598cb18f9aa2d784 100644 --- a/client/src/app/admin/components/attribute/output/tr-output.component.ts +++ b/client/src/app/admin/components/attribute/output/tr-output.component.ts @@ -1,3 +1,12 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, OnInit } from '@angular/core'; import { FormControl, FormGroup } from '@angular/forms'; @@ -6,7 +15,7 @@ import { Attribute, OutputCategory } from 'src/app/metamodel/models'; @Component({ selector: '[output]', templateUrl: 'tr-output.component.html', - styleUrls: [ '../tr.component.css' ], + styleUrls: [ '../tr.component.scss' ], changeDetection: ChangeDetectionStrategy.OnPush }) export class TrOutputComponent implements OnInit { diff --git a/client/src/app/admin/components/attribute/result/index.ts b/client/src/app/admin/components/attribute/result/index.ts index 4b25f427fd813bfca6ca81a5bf44c4188f42b4e7..3517973ab6dd103644a98387970d4f37872b6c94 100644 --- a/client/src/app/admin/components/attribute/result/index.ts +++ b/client/src/app/admin/components/attribute/result/index.ts @@ -1,3 +1,12 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { TableResultComponent } from './table-result.component'; import { TrResultComponent } from './tr-result.component'; import { renderers } from './renderers'; diff --git a/client/src/app/admin/components/attribute/result/renderers/detail-renderer.component.ts b/client/src/app/admin/components/attribute/result/renderers/detail-renderer.component.ts index 2939aecf082237ddfa821aaa4f28b3b85a09d858..4dfc1917776660fa01cef50275fcd0a25e960abd 100644 --- a/client/src/app/admin/components/attribute/result/renderers/detail-renderer.component.ts +++ b/client/src/app/admin/components/attribute/result/renderers/detail-renderer.component.ts @@ -1,3 +1,12 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { Component, Input, ChangeDetectionStrategy } from '@angular/core'; import { FormGroup } from '@angular/forms'; diff --git a/client/src/app/admin/components/attribute/result/renderers/download-renderer.component.ts b/client/src/app/admin/components/attribute/result/renderers/download-renderer.component.ts index ef1fc0d8dca863f65ac15a0e18d0a87784ea26a6..f3ccfee56acdfe319c922b94a050dc2016f4dbf9 100644 --- a/client/src/app/admin/components/attribute/result/renderers/download-renderer.component.ts +++ b/client/src/app/admin/components/attribute/result/renderers/download-renderer.component.ts @@ -1,3 +1,12 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { Component, Input, ChangeDetectionStrategy } from '@angular/core'; import { FormGroup } from '@angular/forms'; diff --git a/client/src/app/admin/components/attribute/result/renderers/image-renderer.component.ts b/client/src/app/admin/components/attribute/result/renderers/image-renderer.component.ts index 8706711543b54f65eeb80158f5869d8bc2df6fcb..1441a6a8d02d150de426e338c213f575f906b44e 100644 --- a/client/src/app/admin/components/attribute/result/renderers/image-renderer.component.ts +++ b/client/src/app/admin/components/attribute/result/renderers/image-renderer.component.ts @@ -1,3 +1,12 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { Component, Input, ChangeDetectionStrategy } from '@angular/core'; import { FormGroup } from '@angular/forms'; diff --git a/client/src/app/admin/components/attribute/result/renderers/index.ts b/client/src/app/admin/components/attribute/result/renderers/index.ts index bf18afa1c74e0390e22d1d91b826378f7b7fcda2..89c8ed9df366bfeacc2b2d3d5c79a16bb6602c32 100644 --- a/client/src/app/admin/components/attribute/result/renderers/index.ts +++ b/client/src/app/admin/components/attribute/result/renderers/index.ts @@ -1,3 +1,12 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { DetailRendererComponent } from './detail-renderer.component'; import { DownloadRendererComponent } from './download-renderer.component'; import { ImageRendererComponent } from './image-renderer.component'; diff --git a/client/src/app/admin/components/attribute/result/renderers/link-renderer.component.ts b/client/src/app/admin/components/attribute/result/renderers/link-renderer.component.ts index a67b880373e3a77de5b3dc03415a095110cd315d..d8d136372b8eaec7e50143324d0f0102ffdeb749 100644 --- a/client/src/app/admin/components/attribute/result/renderers/link-renderer.component.ts +++ b/client/src/app/admin/components/attribute/result/renderers/link-renderer.component.ts @@ -1,3 +1,12 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { Component, Input, ChangeDetectionStrategy } from '@angular/core'; import { FormGroup } from '@angular/forms'; diff --git a/client/src/app/admin/components/attribute/result/renderers/renderer-form-factory.ts b/client/src/app/admin/components/attribute/result/renderers/renderer-form-factory.ts index 8a6e7ef3472d123076d87e9fbe2dde4610e75dd0..22c2f5e30eb422a5fbfc7857615f03988ec254cb 100644 --- a/client/src/app/admin/components/attribute/result/renderers/renderer-form-factory.ts +++ b/client/src/app/admin/components/attribute/result/renderers/renderer-form-factory.ts @@ -1,3 +1,12 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { FormControl } from "@angular/forms"; export abstract class RendererFormFactory { diff --git a/client/src/app/admin/components/attribute/result/table-result.component.ts b/client/src/app/admin/components/attribute/result/table-result.component.ts index d4aa11035eae37eba4bfe62451039fb2175a79b0..86ce85cb110aea655b48802702f6fa3e1b6cff98 100644 --- a/client/src/app/admin/components/attribute/result/table-result.component.ts +++ b/client/src/app/admin/components/attribute/result/table-result.component.ts @@ -1,3 +1,12 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { Component } from '@angular/core'; @Component({ diff --git a/client/src/app/admin/components/attribute/result/tr-result.component.ts b/client/src/app/admin/components/attribute/result/tr-result.component.ts index 38ae002881cd5b1acc23d3e380b1febeca96df19..fe0aa76ee37d653c1ac16cfb3137b0a3cd59d67b 100644 --- a/client/src/app/admin/components/attribute/result/tr-result.component.ts +++ b/client/src/app/admin/components/attribute/result/tr-result.component.ts @@ -1,3 +1,12 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, OnInit } from '@angular/core'; import { FormControl, FormGroup } from '@angular/forms'; @@ -7,7 +16,7 @@ import { RendererFormFactory } from './renderers/renderer-form-factory'; @Component({ selector: '[result]', templateUrl: 'tr-result.component.html', - styleUrls: [ '../tr.component.css' ], + styleUrls: [ '../tr.component.scss' ], changeDetection: ChangeDetectionStrategy.OnPush }) export class TrResultComponent implements OnInit { diff --git a/client/src/app/admin/components/attribute/tr.component.css b/client/src/app/admin/components/attribute/tr.component.scss similarity index 100% rename from client/src/app/admin/components/attribute/tr.component.css rename to client/src/app/admin/components/attribute/tr.component.scss diff --git a/client/src/app/admin/components/attribute/vo/index.ts b/client/src/app/admin/components/attribute/vo/index.ts index 8b6aa3b64ad95dba7b0ad4323b6f7af7583ad06e..80eb8323179ac219829e543e6319e9c06d040240 100644 --- a/client/src/app/admin/components/attribute/vo/index.ts +++ b/client/src/app/admin/components/attribute/vo/index.ts @@ -1,3 +1,12 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { TableVoComponent } from './table-vo.component'; import { TrVoComponent } from './tr-vo.component'; diff --git a/client/src/app/admin/components/attribute/vo/table-vo.component.ts b/client/src/app/admin/components/attribute/vo/table-vo.component.ts index ba4352e033e9f7ef300d331b041dd005dafd6b51..57df81c8f7f727ff283bd08e1732d19948e55a4b 100644 --- a/client/src/app/admin/components/attribute/vo/table-vo.component.ts +++ b/client/src/app/admin/components/attribute/vo/table-vo.component.ts @@ -1,3 +1,12 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { Component } from '@angular/core'; @Component({ diff --git a/client/src/app/admin/components/attribute/vo/tr-vo.component.ts b/client/src/app/admin/components/attribute/vo/tr-vo.component.ts index 521725061e70a4774a6f9658eac64c8787f127f1..e0dd9bb2fa25492df131aed1335612316a594ec8 100644 --- a/client/src/app/admin/components/attribute/vo/tr-vo.component.ts +++ b/client/src/app/admin/components/attribute/vo/tr-vo.component.ts @@ -1,3 +1,12 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, OnInit } from '@angular/core'; import { FormControl, FormGroup } from '@angular/forms'; @@ -6,7 +15,7 @@ import { Attribute } from 'src/app/metamodel/models'; @Component({ selector: '[vo]', templateUrl: 'tr-vo.component.html', - styleUrls: [ '../tr.component.css' ], + styleUrls: [ '../tr.component.scss' ], changeDetection: ChangeDetectionStrategy.OnPush }) export class TrVoComponent implements OnInit { diff --git a/client/src/app/app-config.service.ts b/client/src/app/app-config.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..5944e78a5bc3b8821afd91d1a3b8bc431d643157 --- /dev/null +++ b/client/src/app/app-config.service.ts @@ -0,0 +1,13 @@ +import { Injectable } from '@angular/core'; + +@Injectable() +export class AppConfigService { + public apiUrl: string; + public servicesUrl: string; + public baseHref: string; + public authenticationEnabled: boolean; + public ssoAuthUrl: string; + public ssoRealm: string; + public ssoClientId: string; + public adminRole: string; +} diff --git a/client/src/app/app-init.ts b/client/src/app/app-init.ts new file mode 100644 index 0000000000000000000000000000000000000000..b3dc042681bc40d7d903a4e8585e2349fdff7613 --- /dev/null +++ b/client/src/app/app-init.ts @@ -0,0 +1,29 @@ +import { APP_INITIALIZER } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +import { KeycloakService } from 'keycloak-angular'; +import { Store } from '@ngrx/store'; + +import { AppConfigService } from './app-config.service'; +import { initializeKeycloak } from 'src/app/auth/init.keycloak'; + +function appInit(http: HttpClient, appConfigService: AppConfigService, keycloak: KeycloakService, store: Store<{ }>) { + return () => { + return http.get('/assets/app.config.json') + .toPromise() + .then(data => { + Object.assign(appConfigService, data); + return appConfigService; + }) + .then(appConfigService => { + return initializeKeycloak(keycloak, store, appConfigService) + }); + } +} + +export const appInitializer = { + provide: APP_INITIALIZER, + useFactory: appInit, + multi: true, + deps: [ HttpClient, AppConfigService, KeycloakService, Store ] +}; diff --git a/client/src/app/app-routing.module.ts b/client/src/app/app-routing.module.ts index 8c1885fa65c4ae6e4b4f783cb4081092fb62f45d..a003f5b07cd14c37cb0b2e4f3af9be40d224461f 100644 --- a/client/src/app/app-routing.module.ts +++ b/client/src/app/app-routing.module.ts @@ -11,9 +11,14 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { NotFoundPageComponent } from './core/containers/not-found-page.component'; +import { UnauthorizedComponent } from './core/containers/unauthorized.component'; const routes: Routes = [ - { path: '', redirectTo: 'portal-home', pathMatch: 'full' }, + { path: '', redirectTo: 'portal', pathMatch: 'full' }, + { path: 'portal', loadChildren: () => import('./portal/portal.module').then(m => m.PortalModule) }, + { path: 'admin', loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule) }, + { path: 'instance', loadChildren: () => import('./instance/instance.module').then(m => m.InstanceModule) }, + { path: 'unauthorized', component: UnauthorizedComponent }, { path: '**', component: NotFoundPageComponent } ]; diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts index 8914bdcb34ef2a4201d8378d55841bd2d75e3966..cca4aece56fc59af597c3db12456c7952748f33d 100644 --- a/client/src/app/app.module.ts +++ b/client/src/app/app.module.ts @@ -24,10 +24,9 @@ import { CustomSerializer } from './custom-route-serializer'; import { CoreModule } from './core/core.module'; import { AuthModule } from './auth/auth.module'; import { MetamodelModule } from './metamodel/metamodel.module'; -import { PortalModule } from './portal/portal.module'; -import { AdminModule } from './admin/admin.module'; -import { InstanceModule } from './instance/instance.module'; import { AppComponent } from './core/containers/app.component'; +import { AppConfigService } from './app-config.service'; +import { appInitializer } from './app-init'; @NgModule({ imports: [ @@ -37,9 +36,6 @@ import { AppComponent } from './core/containers/app.component'; CoreModule, AuthModule, MetamodelModule, - PortalModule, - AdminModule, - InstanceModule, AppRoutingModule, StoreModule.forRoot(reducers, { metaReducers, @@ -62,6 +58,10 @@ import { AppComponent } from './core/containers/app.component'; logOnly: environment.production }) ], + providers: [ + AppConfigService, + appInitializer + ], bootstrap: [AppComponent] }) export class AppModule { } diff --git a/client/src/app/auth/auth.effects.ts b/client/src/app/auth/auth.effects.ts index a98a959494731d20dfc60d4e4d09495359d6a64b..69cefb0e2b4f468564a02cdcbafb631a22113e7f 100644 --- a/client/src/app/auth/auth.effects.ts +++ b/client/src/app/auth/auth.effects.ts @@ -15,7 +15,7 @@ import { tap, switchMap } from 'rxjs/operators'; import { KeycloakService } from 'keycloak-angular'; import * as authActions from './auth.actions'; -import { environment } from 'src/environments/environment'; +import { AppConfigService } from 'src/app/app-config.service'; @Injectable() export class AuthEffects { @@ -23,11 +23,7 @@ export class AuthEffects { this.actions$.pipe( ofType(authActions.login), tap(_ => { - let redirectUri = window.location.origin; - if (environment.baseHref !== '/') { - redirectUri += environment.baseHref; - } - redirectUri += environment.ssoLoginRedirectUri; + let redirectUri = window.location.toString() this.keycloak.login({ redirectUri }); }) ), @@ -39,10 +35,9 @@ export class AuthEffects { ofType(authActions.logout), tap(_ => { let redirectUri = window.location.origin; - if (environment.baseHref !== '/') { - redirectUri += environment.baseHref; + if (this.config.baseHref !== '/') { + redirectUri += this.config.baseHref; } - redirectUri += environment.ssoLogoutRedirectUri; this.keycloak.logout(redirectUri); }) ), @@ -66,13 +61,14 @@ export class AuthEffects { openEditProfile$ = createEffect(() => this.actions$.pipe( ofType(authActions.openEditProfile), - tap(_ => window.open(environment.ssoAuthUrl + '/realms/' + environment.ssoRealm + '/account', '_blank')) + tap(_ => window.open(this.config.ssoAuthUrl + '/realms/' + this.config.ssoRealm + '/account', '_blank')) ), { dispatch: false } ); constructor( private actions$: Actions, - private keycloak: KeycloakService + private keycloak: KeycloakService, + private config: AppConfigService ) {} } diff --git a/client/src/app/auth/auth.module.ts b/client/src/app/auth/auth.module.ts index 1c04b526d5fa4866c40d04c2ecc6ccaae2284cfb..5f92830201a13e8f8b84300b2cab0cd4cd0989cb 100644 --- a/client/src/app/auth/auth.module.ts +++ b/client/src/app/auth/auth.module.ts @@ -13,19 +13,14 @@ import { KeycloakAngularModule } from 'keycloak-angular'; import { StoreModule } from '@ngrx/store'; import { EffectsModule } from '@ngrx/effects'; -import { initializeKeycloakAnis } from './init.keycloak'; import { authReducer } from './auth.reducer'; import { AuthEffects } from './auth.effects'; -import { environment } from 'src/environments/environment'; @NgModule({ imports: [ - environment.authenticationEnabled ? KeycloakAngularModule : [], + KeycloakAngularModule, StoreModule.forFeature('auth', authReducer), - environment.authenticationEnabled ? EffectsModule.forFeature([ AuthEffects ]): [] - ], - providers: [ - environment.authenticationEnabled ? initializeKeycloakAnis: [] + EffectsModule.forFeature([ AuthEffects ]) ] }) export class AuthModule { } diff --git a/client/src/app/auth/init.keycloak.ts b/client/src/app/auth/init.keycloak.ts index f0509a317a70c330bc5a31107aa51cc3c2f404cd..a5186253c05dd947ef3c8518fce6652ea60ced12 100644 --- a/client/src/app/auth/init.keycloak.ts +++ b/client/src/app/auth/init.keycloak.ts @@ -7,48 +7,41 @@ * file that was distributed with this source code. */ -import { APP_INITIALIZER } from '@angular/core'; import { from } from 'rxjs'; import { KeycloakService, KeycloakEventType } from 'keycloak-angular'; import { Store } from '@ngrx/store'; import * as keycloakActions from './auth.actions'; -import * as fromKeycloak from './auth.reducer'; -import { environment } from 'src/environments/environment'; +import { AppConfigService } from '../app-config.service'; -function initializeKeycloak(keycloak: KeycloakService, store: Store<{ keycloak: fromKeycloak.State }>) { - return async () => { - from(keycloak.keycloakEvents$).subscribe(event => { - if (event.type === KeycloakEventType.OnAuthSuccess) { - store.dispatch(keycloakActions.authSuccess()); - } - }) +export function initializeKeycloak(keycloak: KeycloakService, store: Store<{ }>, appConfigService: AppConfigService) { + if (!appConfigService.authenticationEnabled) { + return Promise.resolve(true); + } - let silentCheckSsoRedirectUri = window.location.origin; - if (environment.baseHref != '/') { - silentCheckSsoRedirectUri += environment.baseHref; + from(keycloak.keycloakEvents$).subscribe(event => { + if (event.type === KeycloakEventType.OnAuthSuccess) { + store.dispatch(keycloakActions.authSuccess()); } - silentCheckSsoRedirectUri += '/assets/silent-check-sso.html'; + }) - return keycloak.init({ - config: { - url: environment.ssoAuthUrl, - realm: environment.ssoRealm, - clientId: environment.ssoClientId, - }, - initOptions: { - onLoad: 'check-sso', - silentCheckSsoRedirectUri - }, - loadUserProfileAtStartUp: true - }); + let silentCheckSsoRedirectUri = window.location.origin; + if (appConfigService.baseHref != '/') { + silentCheckSsoRedirectUri += appConfigService.baseHref; } -} + silentCheckSsoRedirectUri += '/assets/silent-check-sso.html'; -export const initializeKeycloakAnis = { - provide: APP_INITIALIZER, - useFactory: initializeKeycloak, - multi: true, - deps: [ KeycloakService, Store ], -}; + return keycloak.init({ + config: { + url: appConfigService.ssoAuthUrl, + realm: appConfigService.ssoRealm, + clientId: appConfigService.ssoClientId, + }, + initOptions: { + onLoad: 'check-sso', + silentCheckSsoRedirectUri + }, + loadUserProfileAtStartUp: true + }); +} diff --git a/client/src/app/core/containers/app.component.spec.ts b/client/src/app/core/containers/app.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..49336f78e39111fb5200f7f60fc55ed08c608e96 --- /dev/null +++ b/client/src/app/core/containers/app.component.spec.ts @@ -0,0 +1,70 @@ +import { TestBed, waitForAsync, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { of } from 'rxjs'; +import { provideMockStore, MockStore } from '@ngrx/store/testing'; + +import { AppComponent } from './app.component'; +import { AppConfigService } from 'src/app/app-config.service'; +import * as authActions from 'src/app/auth/auth.actions'; + +describe('AppComponent', () => { + let component: AppComponent; + let fixture: ComponentFixture<AppComponent>; + let store: MockStore; + let config: AppConfigService + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [RouterTestingModule], + declarations: [ + AppComponent + ], + providers: [ + provideMockStore({ }), + AppConfigService + ] + }).compileComponents(); + fixture = TestBed.createComponent(AppComponent); + component = fixture.componentInstance; + store = TestBed.inject(MockStore); + config = TestBed.inject(AppConfigService); + })); + + it('should create the app', () => { + expect(component).toBeDefined(); + }); + + it('authenticationEnabled() should give authentication enabled config key value', () => { + config.authenticationEnabled = true; + expect(component.authenticationEnabled()).toBeTruthy(); + }); + + it('login() should dispatch login action', () => { + const spy = jest.spyOn(store, 'dispatch'); + component.login(); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith(authActions.login()); + }); + + it('logout() should dispatch logout action', () => { + const spy = jest.spyOn(store, 'dispatch'); + component.logout(); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith(authActions.logout()); + }); + + it('openEditProfile() should dispatch open edit profile action', () => { + const spy = jest.spyOn(store, 'dispatch'); + component.openEditProfile(); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith(authActions.openEditProfile()); + }); + + it('isAnisAdmin() should return observable true if user is authenticated', () => { + component.userRoles = of(['user']); + component.isAnisAdmin().subscribe(isAuthenticated => expect(isAuthenticated).toBeFalsy()); + component.userRoles = of(['user', 'anis_admin']); + component.isAnisAdmin().subscribe(isAuthenticated => expect(isAuthenticated).toBeTruthy()); + }); +}); diff --git a/client/src/app/core/containers/app.component.ts b/client/src/app/core/containers/app.component.ts index c265196bb2531b78a0262cc364ced47b63bd68f7..7ede1446430a597d5ef318ba39940265e28aa3e0 100644 --- a/client/src/app/core/containers/app.component.ts +++ b/client/src/app/core/containers/app.component.ts @@ -17,7 +17,7 @@ import * as fromAuth from '../../auth/auth.reducer'; import * as authActions from '../../auth/auth.actions'; import * as authSelector from '../../auth/auth.selector'; import { UserProfile } from '../../auth/user-profile.model'; -import { environment } from 'src/environments/environment'; +import { AppConfigService } from '../../app-config.service'; @Component({ selector: 'app-root', @@ -30,14 +30,14 @@ export class AppComponent { public userProfile: Observable<UserProfile>; public userRoles: Observable<string[]>; - constructor(private store: Store<{ auth: fromAuth.State }>) { + constructor(private store: Store<{ auth: fromAuth.State }>, private config: AppConfigService) { this.isAuthenticated = store.select(authSelector.selectIsAuthenticated); this.userProfile = store.select(authSelector.selectUserProfile); this.userRoles = store.select(authSelector.selectUserRoles); } authenticationEnabled(): boolean { - return environment.authenticationEnabled; + return this.config.authenticationEnabled; } login(): void { diff --git a/client/src/app/core/containers/not-found-page.component.spec.ts b/client/src/app/core/containers/not-found-page.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..96dabccdf7c2923c7fd8b31e450f5b092d6addaa --- /dev/null +++ b/client/src/app/core/containers/not-found-page.component.spec.ts @@ -0,0 +1,24 @@ +import { TestBed, waitForAsync, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { NotFoundPageComponent } from './not-found-page.component'; + +describe('NotFoundPageComponent', () => { + let component: NotFoundPageComponent; + let fixture: ComponentFixture<NotFoundPageComponent>; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [RouterTestingModule], + declarations: [ + NotFoundPageComponent + ] + }).compileComponents(); + fixture = TestBed.createComponent(NotFoundPageComponent); + component = fixture.componentInstance; + })); + + it('should create the not found page component', () => { + expect(component).toBeDefined(); + }); +}); diff --git a/client/src/app/core/containers/unauthorized.component.html b/client/src/app/core/containers/unauthorized.component.html new file mode 100644 index 0000000000000000000000000000000000000000..becf4b8eb909051595b0a3a2e989989a8c6558f0 --- /dev/null +++ b/client/src/app/core/containers/unauthorized.component.html @@ -0,0 +1,12 @@ +<main role="main" class="container-fluid pb-4"> + <div class="container"> + <div class="text-center"> + <img class="mb-4" src="assets/cesam_anis80.png" alt=""> + + <p> + You are not authorized to navigate to this interface (403).<br /> + Please contact the administrator to increase your access rights. + </p> + </div> + </div> +</main> diff --git a/client/src/app/core/containers/unauthorized.component.spec.ts b/client/src/app/core/containers/unauthorized.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..51a3acc62289172bc5dbbf79227cb3b40da9af16 --- /dev/null +++ b/client/src/app/core/containers/unauthorized.component.spec.ts @@ -0,0 +1,24 @@ +import { TestBed, waitForAsync, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { UnauthorizedComponent } from './unauthorized.component'; + +describe('UnauthorizedComponent', () => { + let component: UnauthorizedComponent; + let fixture: ComponentFixture<UnauthorizedComponent>; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [RouterTestingModule], + declarations: [ + UnauthorizedComponent + ] + }).compileComponents(); + fixture = TestBed.createComponent(UnauthorizedComponent); + component = fixture.componentInstance; + })); + + it('should create the unauthorized component', () => { + expect(component).toBeDefined(); + }); +}); diff --git a/client/src/app/core/containers/unauthorized.component.ts b/client/src/app/core/containers/unauthorized.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..6567df192b3a61a8eb72d622f9efa38193459d22 --- /dev/null +++ b/client/src/app/core/containers/unauthorized.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-unauthorized', + templateUrl: 'unauthorized.component.html' +}) +export class UnauthorizedComponent { } diff --git a/client/src/app/core/core.module.ts b/client/src/app/core/core.module.ts index 76f5e2493172efaca784d12955e136c857ca7b82..45581a8c53bd35ba20747f1e8fe72d2bfdacb1df 100644 --- a/client/src/app/core/core.module.ts +++ b/client/src/app/core/core.module.ts @@ -13,6 +13,7 @@ import { RouterModule } from '@angular/router'; import { AppComponent } from './containers/app.component'; import { NotFoundPageComponent } from './containers/not-found-page.component'; +import { ToastrModule } from 'ngx-toastr'; export const COMPONENTS = [ AppComponent, @@ -22,7 +23,8 @@ export const COMPONENTS = [ @NgModule({ imports: [ CommonModule, - RouterModule + RouterModule, + ToastrModule.forRoot() ], declarations: COMPONENTS, exports: COMPONENTS diff --git a/client/src/app/instance/detail/components/default/default-object.component.html b/client/src/app/instance/detail/components/default/default-object.component.html new file mode 100644 index 0000000000000000000000000000000000000000..bc6843ae7d94023d1cd8b39b9c9c57631d17822f --- /dev/null +++ b/client/src/app/instance/detail/components/default/default-object.component.html @@ -0,0 +1,11 @@ +<div class="row justify-content-center"> + <div class="col col-lg-10 col-xl-8 mt-4"> + <app-object-data + [datasetSelected]="datasetSelected" + [outputFamilyList]="outputFamilyList" + [outputCategoryList]="outputCategoryList" + [attributeList]="attributeList" + [object]="object"> + </app-object-data> + </div> +</div> diff --git a/client/src/app/instance/detail/components/default/default-object.component.ts b/client/src/app/instance/detail/components/default/default-object.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..058a6eda84682117c638f3b96313f04f6ae192bb --- /dev/null +++ b/client/src/app/instance/detail/components/default/default-object.component.ts @@ -0,0 +1,29 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { Component, Input, ChangeDetectionStrategy } from '@angular/core'; + +import { Attribute, OutputFamily, OutputCategory } from 'src/app/metamodel/models'; + +@Component({ + selector: 'app-default-object', + templateUrl: 'default-object.component.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +/** + * @class + * @classdesc Detail default object component. + */ +export class DefaultObjectComponent { + @Input() datasetSelected: string; + @Input() outputFamilyList: OutputFamily[]; + @Input() outputCategoryList: OutputCategory[]; + @Input() attributeList: Attribute[]; + @Input() object: any; +} diff --git a/client/src/app/instance/detail/components/default/index.ts b/client/src/app/instance/detail/components/default/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..f36eed1181aaad1346a0f96e9b5375b1f68979b2 --- /dev/null +++ b/client/src/app/instance/detail/components/default/index.ts @@ -0,0 +1,5 @@ +import { DefaultObjectComponent } from './default-object.component'; + +export const defaultComponents = [ + DefaultObjectComponent +]; diff --git a/client/src/app/instance/detail/components/index.ts b/client/src/app/instance/detail/components/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..e0d32c36208a417c4ec089814d619e25f5a69c6b --- /dev/null +++ b/client/src/app/instance/detail/components/index.ts @@ -0,0 +1,9 @@ +import { defaultComponents } from './default'; +import { spectraComponents } from './spectra'; +import { ObjectDataComponent } from './object-data.component'; + +export const dummiesComponents = [ + defaultComponents, + spectraComponents, + ObjectDataComponent +]; diff --git a/client/src/app/instance/detail/components/object-data.component.html b/client/src/app/instance/detail/components/object-data.component.html new file mode 100644 index 0000000000000000000000000000000000000000..8c469250b9ef97abbc24a6c81a407076bef0fa86 --- /dev/null +++ b/client/src/app/instance/detail/components/object-data.component.html @@ -0,0 +1,66 @@ +<div *ngIf="getAttributeBySearchFlag('RA') && getAttributeBySearchFlag('DEC')" class="row"> + <div class="col-12"> + <table class="table mb-1"> + <tr> + <th>Alpha</th> + <th>Delta</th> + <th class="text-center" rowspan="2"><img src="assets/cesam_anis80.png" alt="CeSAM logo" /></th> + </tr> + <tr> + <td>{{ object[getAttributeBySearchFlag('RA').label] }}</td> + <td>{{ object[getAttributeBySearchFlag('DEC').label] }}</td> + </tr> + </table> + <hr class="mt-0 mb-4"> + </div> +</div> + +<!-- Accordion families --> +<accordion [isAnimated]="true"> + <accordion-group *ngFor="let family of outputFamilyList" #ag [isOpen]="true" class="pl-2"> + <button class="btn btn-link btn-block clearfix pb-2" accordion-heading> + <span class="pull-left float-left text-primary"> + {{ family.label }} + + <span *ngIf="ag.isOpen"> + <span class="fas fa-chevron-up"></span> + </span> + <span *ngIf="!ag.isOpen"> + <span class="fas fa-chevron-down"></span> + </span> + </span> + </button> + + <!-- Accordion categories --> + <accordion [isAnimated]="true"> + <accordion-group *ngFor="let category of getCategoryByFamilySortedByDisplay(family.id)" #ag [isOpen]="true" class="pl-4"> + <button class="btn btn-link btn-block clearfix pb-2" accordion-heading> + <span class="pull-left float-left text-primary"> + {{ category.label }} + + <span *ngIf="ag.isOpen"> + <span class="fas fa-chevron-up"></span> + </span> + <span *ngIf="!ag.isOpen"> + <span class="fas fa-chevron-down"></span> + </span> + </span> + </button> + + <!-- Output list --> + <div *ngFor="let attribute of getAttributesVisibleByCategory(category.id)" class="row pb-2"> + <div class="col-5 font-weight-bold">{{ attribute.form_label }}</div> + <ng-container [ngSwitch]="attribute.renderer_detail"> + <div *ngSwitchCase="'download'" class="col"> + <a [href]="getDownloadHref(object[attribute.label])" role="button" class="btn btn-primary btn-sm"> + <span class="fas fa-download"></span> + {{ object[attribute.label] }} + </a> + </div> + <div *ngSwitchDefault class="col">{{ object[attribute.label] }}</div> + </ng-container > + </div> + </accordion-group> + </accordion> + </accordion-group> +</accordion> diff --git a/client/src/app/instance/detail/components/object-data.component.ts b/client/src/app/instance/detail/components/object-data.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..16de054ffd8348a0701382356e74f9412523540e --- /dev/null +++ b/client/src/app/instance/detail/components/object-data.component.ts @@ -0,0 +1,86 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { Component, Input, ChangeDetectionStrategy } from '@angular/core'; + +import { Attribute, OutputCategory, OutputFamily } from 'src/app/metamodel/models'; +import { getHost } from 'src/app/shared/utils'; +import { AppConfigService } from 'src/app/app-config.service'; + +@Component({ + selector: 'app-object-data', + templateUrl: 'object-data.component.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +/** + * @class + * @classdesc Detail object data component. + */ +export class ObjectDataComponent { + @Input() datasetSelected: string; + @Input() outputFamilyList: OutputFamily[]; + @Input() outputCategoryList: OutputCategory[]; + @Input() attributeList: Attribute[]; + @Input() object: any; + + constructor(private appConfig: AppConfigService) { } + + /** + * Returns category list sorted by display, for the given output family ID. + * + * @param {number} idFamily - The output family ID. + * + * @return Category[] + */ + getCategoryByFamilySortedByDisplay(idFamily: number): OutputCategory[] { + return this.outputCategoryList + .filter(category => category.id_output_family === idFamily) + //.sort(sortByDisplay); + } + + /** + * Returns attribute list sorted by detail display, for the given output category ID. + * + * @param {number} idCategory - The output category ID. + * + * @return Attribute[] + */ + getAttributesVisibleByCategory(idCategory: number): Attribute[] { + return this.attributeList + .filter(a => a.detail) + .filter(a => a.id_output_category === idCategory) + .sort((a, b) => a.display_detail - b.display_detail); + } + + /** + * Returns attribute list sorted by detail display. + * + * @return Attribute[] + */ + getAttributesVisible(): Attribute[] { + return this.attributeList + .filter(a => a.detail) + .sort((a, b) => a.display_detail - b.display_detail); + } + + /** + * Returns attribute for the given search flag. + * + * @param {string} searchFlag - The search flag. + * + * @return Attribute + */ + getAttributeBySearchFlag(searchFlag: string): Attribute { + return this.getAttributesVisible().find(attribute => attribute.search_flag === searchFlag); + } + + getDownloadHref(value: string) { + return getHost(this.appConfig.apiUrl) + '/download-file/' + this.datasetSelected + '/' + value; + } +} diff --git a/client/src/app/instance/detail/components/spectra/graph/point.ts b/client/src/app/instance/detail/components/spectra/graph/point.ts new file mode 100644 index 0000000000000000000000000000000000000000..b45ba9d36139645b8cbb14a7bf558423d614a976 --- /dev/null +++ b/client/src/app/instance/detail/components/spectra/graph/point.ts @@ -0,0 +1,13 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +export interface Point { + x: number, + y: number +} diff --git a/client/src/app/instance/detail/components/spectra/graph/rays.ts b/client/src/app/instance/detail/components/spectra/graph/rays.ts new file mode 100644 index 0000000000000000000000000000000000000000..8dbba2bc26073ec94082ec9804d58755aa6beb17 --- /dev/null +++ b/client/src/app/instance/detail/components/spectra/graph/rays.ts @@ -0,0 +1,108 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + + export const emissionLines = [ + {name: 'SII', wavelength: 10320}, + {name: 'HeI', wavelength: 7065.2}, + {name: 'SII', wavelength: 6732.68}, + {name: 'SII', wavelength: 6718.39}, + {name: 'NII', wavelength: 6585.27}, + {name: 'Hα', wavelength: 6564.61}, + {name: 'NII', wavelength: 6549.86}, + {name: 'HeI', wavelength: 5876.0}, + {name: 'OIII', wavelength: 5008.24}, + {name: 'OIII', wavelength: 4960.29}, + {name: 'Hβ', wavelength: 4862.70}, + {name: 'OIII', wavelength: 4364.44}, + {name: 'Hγ', wavelength: 4341.68}, + {name: 'Hδ', wavelength: 4102.89}, + {name: 'SII', wavelength: 4072.0}, + {name: 'Hksi', wavelength: 3890.15}, + {name: 'NeIII', wavelength: 3869.81}, + {name: 'H9', wavelength: 3836.47}, + {name: 'Hθ', wavelength: 3798.98}, + {name: 'H11', wavelength: 3771.70}, + {name: 'OII', wavelength: 3728.49}, + {name: 'NeV', wavelength: 3426.5}, + {name: 'NeV', wavelength: 3346.4}, + {name: 'HeII', wavelength: 3204.03}, + {name: 'FeII', wavelength: 2964.0}, + {name: 'MgII', wavelength: 2799.0}, + {name: 'NII', wavelength: 2142.0}, + {name: 'CIII', wavelength: 1908.73}, + {name: 'HeII', wavelength: 1640.42}, + {name: 'CIV', wavelength: 1549.0}, + {name: 'SiIV', wavelength: 1397.0}, + {name: 'CII', wavelength: 1334.53}, + {name: 'OI', wavelength: 1303.0}, + {name: 'NV', wavelength: 1240.0}, + {name: 'LyA', wavelength: 1215.67}, + {name: 'OVI', wavelength: 1033.0}, + {name: 'LyB', wavelength: 1025.72}, + {name: 'LyG', wavelength: 972.53} +]; + +export const absorptionLines = [ + {name: 'TiO', wavelength: 8863.0}, + {name: 'TiO', wavelength: 8430.0}, + {name: 'NaI', wavelength: 8197.05}, + {name: 'NaI', wavelength: 8185.50}, + {name: 'TiO', wavelength: 7590.0}, + {name: 'HeI', wavelength: 7065.2}, + {name: 'Hα', wavelength: 6564.61}, + {name: 'TiO', wavelength: 6159.0}, + {name: 'NaD', wavelength: 5892.5}, + {name: 'TiO', wavelength: 5603.0}, + {name: 'Ca,Fe', wavelength: 5269.0}, + {name: 'MgI', wavelength: 5174.12}, + {name: 'Hβ', wavelength: 4862.70}, + {name: 'Hγ', wavelength: 4341.68}, + {name: 'Gband', wavelength: 4304.4}, + {name: 'CN', wavelength: 4216.0}, + {name: 'Hδ', wavelength: 4102.89}, + {name: 'CaII_H', wavelength: 3969.59}, + {name: 'CaII_K', wavelength: 3934.78}, + {name: 'Hksi', wavelength: 3890.15}, + {name: 'H9', wavelength: 3836.47}, + {name: 'Hθ', wavelength: 3798.98}, + {name: 'H11', wavelength: 3771.70}, + {name: 'FeI', wavelength: 3581.0}, + {name: 'HeII', wavelength: 3204.03}, + {name: 'FeII', wavelength: 2964.0}, + {name: 'MgII', wavelength: 2796.35}, + {name: 'MgII', wavelength: 2803.53}, + {name: 'FeII', wavelength: 2626.45}, + {name: 'FeII', wavelength: 2600.17}, + {name: 'FeII', wavelength: 2586.65}, + {name: 'FeII', wavelength: 2382.76}, + {name: 'FeII', wavelength: 2374.46}, + {name: 'FeII', wavelength: 2344.21}, + {name: 'FeII', wavelength: 2260.78}, + {name: 'AlIII', wavelength: 1854.72}, + {name: 'AlII', wavelength: 1670.78}, + {name: 'HeII', wavelength: 1640.42}, + {name: 'FeII', wavelength: 1608.45}, + {name: 'CIV', wavelength: 1548.20}, + {name: 'CIV', wavelength: 1550.77}, + {name: 'SiII', wavelength: 1533.43}, + {name: 'SiII', wavelength: 1526.71}, + {name: 'SiIV', wavelength: 1402.77}, + {name: 'SiIV', wavelength: 1393.75}, + {name: 'CII', wavelength: 1334.53}, + {name: 'OI', wavelength: 1302.17}, + {name: 'OI', wavelength: 1304.86}, + {name: 'SiII', wavelength: 1260.42}, + {name: 'NV', wavelength: 1238.82}, + {name: 'NV', wavelength: 1242.80}, + {name: 'LyA', wavelength: 1215.67}, + {name: 'OVI', wavelength: 1037.62}, + {name: 'OVI', wavelength: 1031.93}, + {name: 'LyB', wavelength: 1025.72}, + {name: 'LyG', wavelength: 972.53} +]; diff --git a/client/src/app/instance/detail/components/spectra/graph/spectra-graph.component.html b/client/src/app/instance/detail/components/spectra/graph/spectra-graph.component.html new file mode 100644 index 0000000000000000000000000000000000000000..9a1c8a0aeab69b32d8cd98a5a5b96e4f54aa65d6 --- /dev/null +++ b/client/src/app/instance/detail/components/spectra/graph/spectra-graph.component.html @@ -0,0 +1,3 @@ +<div id="svg-container"> + <svg id="mygraph"></svg> +</div> diff --git a/client/src/app/instance/detail/components/spectra/graph/spectra-graph.component.scss b/client/src/app/instance/detail/components/spectra/graph/spectra-graph.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..5a58e12529fd174067354db0f9434c1a177a302a --- /dev/null +++ b/client/src/app/instance/detail/components/spectra/graph/spectra-graph.component.scss @@ -0,0 +1,119 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +.titles text { + fill: #5a5a5a; + font-size: 24px; +} + +.axis line { + fill: none; + stroke: black; + shape-rendering: crispEdges; +} + +.grid line { + fill: none; + stroke: lightgray; + shape-rendering: crispEdges; +} + +.ray line { + stroke-dasharray: 3, 3; + stroke-width: 1.5px; +} + +.emission line { + stroke: steelblue; +} + +.emission text { + fill: steelblue; +} + +.absorption line { + stroke: tomato; +} + +.absorption text { + fill: tomato; +} + +.spectra-line, .line { + fill: none; + stroke: black; + stroke-width: 1px; +} + +.spectra-area, .area { + fill: #D0D0D0; + opacity: 0.6; +} + +.overlay { + fill: none; + pointer-events: all; +} + +.big-circle-tootlip { + fill: #7ac29a; +} + +.little-circle-tooltip { + fill: rgb(16, 75, 42); + stroke: #fff; + stroke-width: 1.5px; +} + +.rect-tootlip { + fill: #fafafa; + stroke: #7ac29a; + opacity: 0.9; + stroke-width: 1; +} + +.text-tooltip { + font-size: 15px; + color: #333333; + fill: #333333; +} + +.text-y-value { + font-weight: bold; +} + +.emission-button circle { + stroke: steelblue; + stroke-width: 3px; + cursor: pointer; +} + +.button-off circle { + fill: transparent; +} + +.emission-button-on circle { + fill: steelblue; +} + +.absorption-button circle { + stroke: tomato; + stroke-width: 3px; + cursor: pointer; +} + +.absorption-button-on circle { + fill: tomato; +} + +.emission-button text, .absorption-button text { + font-size: 15px; + color: #333333; + fill: #333333; +} diff --git a/client/src/app/instance/detail/components/spectra/graph/spectra-graph.component.ts b/client/src/app/instance/detail/components/spectra/graph/spectra-graph.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..c4be29c7a18d65b933ad6285702f7d809a642adb --- /dev/null +++ b/client/src/app/instance/detail/components/spectra/graph/spectra-graph.component.ts @@ -0,0 +1,444 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { Component, ChangeDetectionStrategy, ViewEncapsulation, OnInit, Input } from '@angular/core'; + +import * as d3 from 'd3'; + +import { emissionLines, absorptionLines } from './rays'; +import { SpectraType } from './spectra-type'; +import { Point } from './point'; + +@Component({ + selector: 'app-spectra-graph', + encapsulation: ViewEncapsulation.None, + templateUrl: 'spectra-graph.component.html', + styleUrls: ['spectra-graph.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class SpectraGraphComponent implements OnInit { + @Input() z: number; + @Input() spectraCSV: string; + + svg: d3.Selection<d3.BaseType, unknown, HTMLElement, any>; + focus: d3.Selection<SVGGElement, unknown, HTMLElement, any>; + width: number; + height: number; + brushHeight: number; + margin = { top: 50, right: 10, bottom: 150, left: 100 }; + x: d3.ScaleLinear<number, number>; + xAxis: d3.Axis<number | { valueOf(): number }>; + y: d3.ScaleLinear<number, number>; + spectraLine: d3.Line<Point>; + spectraArea: d3.Area<Point>; + displayEmissionLines = true; + dispayAbsorptionLines = true; + + ngOnInit() { + this.width = document.getElementById("svg-container").offsetWidth * 1 - this.margin.left - this.margin.right; + this.height = 600 - this.margin.top - this.margin.bottom; + this.brushHeight = 50; + this.x = d3.scaleLinear().range([0, this.width]); + this.y = d3.scaleLinear().range([this.height, 0]); + + this.initSvg(); + this.addTitles(); + + const dataset = d3.csvParse<Point, SpectraType>(this.spectraCSV, (row) => { + return { + x: parseFloat(row.x), + y: parseFloat(row.Flux) + }; + }); + + this.setupDomain(dataset); + this.addGrid(); + this.addSpectraLine(dataset); + this.addSpectraArea(dataset); + this.addRays(); + this.addAxis(); + this.addRaysButtons(); + this.addBrush(dataset); + this.addTooltip(this.width, this.height, dataset, this.x, this.y); + } + + private initSvg(): void { + this.svg = d3.select("svg#mygraph") + .attr("width", this.width + this.margin.left + this.margin.right) + .attr("height", this.height + this.margin.top + this.margin.bottom); + + this.svg.append("defs").append("clipPath") + .attr("id", "clip") + .append("rect") + .attr("width", this.width) + .attr("height", this.height); + + this.focus = this.svg.append("g") + .attr("class", "focus") + .attr("transform", "translate(" + this.margin.left + "," + this.margin.top + ")"); + } + + private addTitles(): void { + const titles = this.focus.append("g") + .attr("class", "titles"); + + titles.append("text") + .attr("x", (this.width / 2)) + .attr("y", this.height + this.margin.bottom - 20) + .attr("text-anchor", "middle") + .text("Wavelength [Ångströms]"); + + titles.append("text") + .attr("x", (this.margin.left * 0.75) * -1) + .attr("y", this.height / 2) + .attr("text-anchor", "middle") + .attr("transform", "rotate(-90," + ((this.margin.left * 0.75) * -1) + "," + this.height / 2 + ")") + .text("Flux Fλ (erg/sec/cm2/Å)"); + } + + private setupDomain(dataset: d3.DSVParsedArray<Point>): void { + const xMin = d3.min(dataset, (d) => d.x); + const xMax = d3.max(dataset, (d) => d.x); + this.x.domain([xMin, xMax]); + + const yMin = d3.min(dataset, (d) => d.y); + const yMax = d3.max(dataset, (d) => d.y) * 1.1; + this.y.domain([yMin, yMax]); + } + + private addGrid(): void { + const grid = this.focus.append("g") + .attr("class", "grid"); + + grid.append("g") + .attr("class", "grid-x") + .selectAll() + .data(this.x.ticks(10)) + .enter() + .append("line") + .attr("class", "grid-line-x") + .attr("x1", (d: number) => this.x(d)) + .attr("x2", (d: number) => this.x(d)) + .attr("y1", 0) + .attr("y2", this.height); + + grid.append("g") + .attr("class", "grid-y") + .selectAll() + .data(this.y.ticks(10)) + .enter() + .append("line") + .attr("class", "grid-line-y") + .attr("x1", 0) + .attr("x2", this.width) + .attr("y1", (d: number) => this.y(d)) + .attr("y2", (d: number) => this.y(d)); + } + + private addSpectraLine(dataset: d3.DSVParsedArray<Point>): void { + this.spectraLine = d3.line<Point>() + .x((d) => this.x(d.x)) + .y((d) => this.y(d.y)); + + this.focus.append("g") + .attr("clip-path", "url(#clip)") + .append("path") + .datum(dataset) + .attr("class", "spectra-line") + .attr("d", this.spectraLine); + } + + private addSpectraArea(dataset: d3.DSVParsedArray<Point>): void { + this.spectraArea = d3.area<Point>() + .x((d) => this.x(d.x)) + .y0(this.height) + .y1((d) => this.y(d.y)); + + this.focus.append("g") + .attr("clip-path", "url(#clip)") + .append("path") + .datum(dataset) + .attr("class", "spectra-area") + .attr("d", this.spectraArea); + } + + private addRays(): void { + const coef = 1 + this.z; + + const er = this.focus.append("g") + .style("display", "block") + .attr("class", "ray emission") + .attr("clip-path", "url(#clip)"); + + er.selectAll() + .data(emissionLines) + .enter() + .append("line") + .attr("x1", r => this.x(r.wavelength * coef)) + .attr("x2", r => this.x(r.wavelength * coef)) + .attr("y1", this.height) + .attr("y2", 0); + + er.selectAll() + .data(emissionLines) + .enter() + .append("text") + .attr("x", r => this.x(r.wavelength * coef) - 5) + .attr("y", this.height * 0.2) + .attr("transform", r => "rotate(-90, " + (this.x(r.wavelength * coef) - 5) + "," + this.height * 0.2 + ")") + .text(r => r.name); + + const ar = this.focus.append("g") + .style("display", "block") + .attr("class", "ray absorption") + .attr("clip-path", "url(#clip)"); + + ar.selectAll() + .data(absorptionLines) + .enter() + .append("line") + .attr("x1", r => this.x(r.wavelength * coef)) + .attr("x2", r => this.x(r.wavelength * coef)) + .attr("y1", this.height) + .attr("y2", 0); + + ar.selectAll() + .data(absorptionLines) + .enter() + .append("text") + .attr("x", r => this.x(r.wavelength * coef) - 5) + .attr("y", this.height * 0.8) + .attr("transform", r => "rotate(-90, " + (this.x(r.wavelength * coef) - 5) + "," + this.height * 0.8 + ")") + .text(r => r.name); + } + + private addAxis(): void { + this.xAxis = d3.axisBottom(this.x); + this.focus.append("g") + .attr("class", "axis axis-x") + .attr("transform", "translate(0," + this.height + ")") + .call(this.xAxis); + + this.focus.append("g") + .attr("class", "axis axis-y") + .call(d3.axisLeft(this.y).tickFormat(d3.format(".1e"))) + } + + private addRaysButtons(): void { + const gButtons = this.svg.append("g") + .attr("class", "rays-buttons") + .attr("transform", "translate(" + this.margin.left + "," + 25 + ")"); + + const emission = gButtons.append("g") + .attr("class", "emission-button emission-button-on"); + + const circleEmission = emission.append("circle") + .attr("cx", 20) + .attr("cy", 0) + .attr("r", 10); + + circleEmission.on("click", () => { + const e = this.focus.select(".emission"); + if (this.displayEmissionLines) { + e.style("display", "none"); + emission.attr("class", "emission-button button-off"); + } else { + e.style("display", "block"); + emission.attr("class", "emission-button emission-button-on"); + } + this.displayEmissionLines = !this.displayEmissionLines; + }); + + emission.append("text") + .attr("x", 35) + .attr("y", 5) + .text("Display emission lines") + + const absorption = gButtons.append("g") + .attr("class", "absorption-button absorption-button-on"); + + const circleAbsorption = absorption.append("circle") + .attr("cx", 215) + .attr("cy", 0) + .attr("r", 10); + + circleAbsorption.on("click", () => { + const a = this.focus.select(".absorption"); + if (this.dispayAbsorptionLines) { + a.style("display", "none"); + absorption.attr("class", "absorption-button button-off"); + } else { + a.style("display", "block"); + absorption.attr("class", "absorption-button absorption-button-on"); + } + this.dispayAbsorptionLines = !this.dispayAbsorptionLines; + }); + + absorption.append("text") + .attr("x", 230) + .attr("y", 5) + .text("Display absorption lines") + + } + + private addTooltip( + width: number, + height: number, + dataset: d3.DSVParsedArray<Point>, + x: d3.ScaleLinear<number, number>, + y: d3.ScaleLinear<number, number> + ): void { + const tooltip = this.focus.append("g") + .style("display", "none"); + + tooltip.append("circle") + .attr("class", "big-circle-tootlip") + .attr("r", 10); + + tooltip.append("circle") + .attr("class", "little-circle-tooltip") + .attr("r", 4); + + tooltip.append("polyline") + .attr("points","0,0 0,40 55,40 60,45 65,40 135,40 135,0 0,0") + .attr("class", "rect-tootlip") + .attr("transform", "translate(-60, -55)"); + + const xValue = tooltip.append("text") + .attr("class", "text-tooltip") + .attr("transform", "translate(-55, -40)") + .append("tspan") + .text("X : ") + .append("tspan"); + + const yValue = tooltip.append("text") + .attr("class", "text-tooltip") + .attr("transform", "translate(-55, -24)") + .append("tspan") + .text("Flux : ") + .append("tspan") + .attr("class", "text-y-value") + + const bisectX = d3.bisector((p: Point) => p.x).left; + this.focus.append("rect") + .attr("class", "overlay") + .attr("width", width) + .attr("height", height) + .on("mouseover", () => tooltip.style("display", null)) + .on("mouseout", () => tooltip.style("display", "none")) + .on("mousemove", (d, i, n) => { + const node = n[i]; + const mouse = d3.mouse(node); + const x0 = x.invert(mouse[0]); + const index = bisectX(dataset, x0); + const datum = dataset[index]; + tooltip.attr("transform", "translate(" + x(datum.x) + "," + y(datum.y) + ")"); + xValue.text(datum.x); + yValue.text(datum.y); + }); + } + + private addBrush(dataset: d3.DSVParsedArray<Point>): void { + const context = this.svg.append("g") + .attr("class", "context") + .attr("transform", "translate(" + this.margin.left + "," + 480 + ")"); + + const xBrush = d3.scaleLinear().range([0, this.width]); + const yBrush = d3.scaleLinear().range([this.brushHeight, 0]); + xBrush.domain(this.x.domain()); + yBrush.domain(this.y.domain()); + + const xBrushAxis = d3.axisBottom(xBrush); + + const lineBrush = d3.line<Point>() + .x((d) => xBrush(d.x)) + .y((d) => yBrush(d.y)); + + context.append("g") + .attr("class", "line") + .append("path") + .datum(dataset) + .attr("d", lineBrush); + + const areaBrush = d3.area<Point>() + .x((d) => xBrush(d.x)) + .y0(this.brushHeight) + .y1((d) => yBrush(d.y)); + + context.append("g") + .attr("class", "area") + .append("path") + .datum(dataset) + .attr("d", areaBrush); + + context.append("g") + .attr("class", "axis") + .attr("transform", "translate(0," + this.brushHeight + ")") + .call(xBrushAxis); + + const brush = d3.brushX() + .extent([[0, 0], [this.width, this.brushHeight]]) + .on("end", () => { + const selection = d3.event.selection || xBrush.range(); + this.x.domain(selection.map(xBrush.invert, xBrush)); + + // Update spectra graph + this.focus.select(".spectra-line") + .attr("d", this.spectraLine); + this.focus.select(".spectra-area") + .attr("d", this.spectraArea); + + // Update axis + this.focus.select(".axis-x").call(this.xAxis); + + // Update grid + this.focus.selectAll(".grid-line-x") + .remove(); + this.focus.select(".grid-x") + .selectAll() + .data(this.x.ticks()) + .enter() + .append("line") + .attr("class", "grid-line-x") + .attr("x1", (d: number) => this.x(d)) + .attr("x2", (d: number) => this.x(d)) + .attr("y1", 0) + .attr("y2", this.height); + + // Update rays + const coef = 1 + this.z; + this.focus.select(".emission") + .selectAll("line") + .data(emissionLines) + .attr("x1", r => this.x(r.wavelength * coef)) + .attr("x2", r => this.x(r.wavelength * coef)); + this.focus.select(".emission") + .selectAll("text") + .data(emissionLines) + .attr("x", r => this.x(r.wavelength * coef) - 5) + .attr("transform", r => "rotate(-90, " + (this.x(r.wavelength * coef) - 5) + "," + this.height * 0.2 + ")") + + this.focus.select(".absorption") + .selectAll("line") + .data(absorptionLines) + .attr("x1", r => this.x(r.wavelength * coef)) + .attr("x2", r => this.x(r.wavelength * coef)); + this.focus.select(".absorption") + .selectAll("text") + .data(absorptionLines) + .attr("x", r => this.x(r.wavelength * coef) - 5) + .attr("transform", r => "rotate(-90, " + (this.x(r.wavelength * coef) - 5) + "," + this.height * 0.8 + ")") + }); + + context.append("g") + .attr("class", "brush") + .call(brush) + .call(brush.move, this.x.range()); + } +} diff --git a/client/src/app/instance/detail/components/spectra/graph/spectra-type.ts b/client/src/app/instance/detail/components/spectra/graph/spectra-type.ts new file mode 100644 index 0000000000000000000000000000000000000000..180320a2cb8bfb3061fc46289f3d2ee574464c53 --- /dev/null +++ b/client/src/app/instance/detail/components/spectra/graph/spectra-type.ts @@ -0,0 +1,10 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +export type SpectraType = "x" | "Flux"; diff --git a/client/src/app/instance/detail/components/spectra/index.ts b/client/src/app/instance/detail/components/spectra/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..77b5e03935e6f4321da07852a1d56a460bc70c3a --- /dev/null +++ b/client/src/app/instance/detail/components/spectra/index.ts @@ -0,0 +1,7 @@ +import { SpectraObjectComponent } from "./spectra-object.component"; +import { SpectraGraphComponent } from "./graph/spectra-graph.component"; + +export const spectraComponents = [ + SpectraObjectComponent, + SpectraGraphComponent +]; diff --git a/client/src/app/instance/detail/components/spectra/spectra-object.component.html b/client/src/app/instance/detail/components/spectra/spectra-object.component.html new file mode 100644 index 0000000000000000000000000000000000000000..a5c472ccf3df9c49290d85d91f3e7890bf6df7ea --- /dev/null +++ b/client/src/app/instance/detail/components/spectra/spectra-object.component.html @@ -0,0 +1,36 @@ +<div class="row"> + <div *ngIf="getAttributeSpectraGraph()" class="col col-md-8 col-sm-12"> + <div *ngIf="spectraIsLoading" id="div-spinner" class="text-center"> + <span class="fas fa-circle-notch fa-spin fa-3x"></span> + <span class="sr-only">Loading...</span> + </div> + <app-spectra-graph *ngIf="spectraIsLoaded" [z]="getZ()" [spectraCSV]="spectraCSV"></app-spectra-graph> + </div> + + <div [ngClass]="{'col-md-4 col-sm-12': getAttributeSpectraGraph()}" class="col mt-4"> + <div *ngIf="getSpectra()" class="jumbotron row mb-3 p-4"> + <div class="col-auto align-self-center"> + <p>Download:</p> + </div> + <div class="w-100 d-block d-xl-none"></div> + <div class="col"> + <div class="row justify-content-center"> + <div class="col-auto"> + <a [href]="getSpectra()" target="_blank" class="btn btn-lg btn-block dl-btn"> + <!-- <span [inlineSVG]="'assets/logo_spectra.svg'" title="Download SPECTRA archive"></span> --> + Download SPECTRA archive + </a> + </div> + </div> + </div> + </div> + + <app-object-data + [datasetSelected]="datasetSelected" + [outputFamilyList]="outputFamilyList" + [outputCategoryList]="outputCategoryList" + [attributeList]="attributeList" + [object]="object"> + </app-object-data> + </div> +</div> diff --git a/client/src/app/instance/detail/components/spectra/spectra-object.component.scss b/client/src/app/instance/detail/components/spectra/spectra-object.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..260a2036246abdb58c05276b1a262e50360ac3f3 --- /dev/null +++ b/client/src/app/instance/detail/components/spectra/spectra-object.component.scss @@ -0,0 +1,13 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +.dl-btn { + height: 80px; + display: inline-block; +} diff --git a/client/src/app/instance/detail/components/spectra/spectra-object.component.ts b/client/src/app/instance/detail/components/spectra/spectra-object.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..9c7095f2860c9712915d6dc5c61e0676fe7fc73d --- /dev/null +++ b/client/src/app/instance/detail/components/spectra/spectra-object.component.ts @@ -0,0 +1,86 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { Component, Input, ChangeDetectionStrategy, Output, EventEmitter, OnInit } from '@angular/core'; + +import { Attribute, OutputCategory, OutputFamily } from 'src/app/metamodel/models'; +import { getHost } from 'src/app/shared/utils'; +import { AppConfigService } from 'src/app/app-config.service'; + +@Component({ + selector: 'app-spectra-object', + templateUrl: 'spectra-object.component.html', + styleUrls: ['spectra-object.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +/** + * @class + * @classdesc Detail spectra object component. + * + * @implements OnInit + */ +export class SpectraObjectComponent implements OnInit { + @Input() datasetSelected: string; + @Input() outputFamilyList: OutputFamily[]; + @Input() outputCategoryList: OutputCategory[]; + @Input() attributeList: Attribute[]; + @Input() object: any; + @Input() spectraIsLoading: boolean; + @Input() spectraIsLoaded: boolean; + @Input() spectraCSV: string; + @Output() getSpectraCSV: EventEmitter<string> = new EventEmitter(); + + constructor(private appConfig: AppConfigService) { } + + ngOnInit() { + const attributeSpectraGraph = this.getAttributeSpectraGraph(); + if (attributeSpectraGraph) { + Promise.resolve(null).then(() => this.getSpectraCSV.emit(this.object[attributeSpectraGraph.label])); + } + } + + /** + * Returns spectra file URL. + * + * @return string + */ + getSpectra(): string { + const spectraAttribute: Attribute = this.attributeList + .filter(a => a.detail) + .find(attribute => attribute.search_flag === 'SPECTRUM_1D'); + return getHost(this.appConfig.apiUrl) + '/download-file/' + this.datasetSelected + '/' + this.object[spectraAttribute.label]; + } + + /** + * Returns detail rendered spectra graph attribute. + * + * @return Attribute + */ + getAttributeSpectraGraph(): Attribute { + return this.attributeList + .filter(a => a.detail) + .find(attribute => attribute.renderer_detail === 'spectra_graph'); + } + + /** + * Returns Z. + * + * @return number + */ + getZ(): number { + const attributeZ = this.attributeList + .filter(a => a.detail) + .find(attribute => attribute.search_flag === 'Z'); + if (attributeZ) { + return +this.object[attributeZ.label]; + } else { + return 0; + } + } +} diff --git a/client/src/app/instance/detail/containers/detail.component.html b/client/src/app/instance/detail/containers/detail.component.html new file mode 100644 index 0000000000000000000000000000000000000000..2899b8cf78fc9f670a81366d3efd8b049e074bc6 --- /dev/null +++ b/client/src/app/instance/detail/containers/detail.component.html @@ -0,0 +1,34 @@ +<div class="container-fluid"> + <div *ngIf="!(pristine | async)" class="row mt-2 mb-2 justify-content-between"> + <div class="col"> + <button (click)="goBackToResult()" class="btn btn-outline-secondary"> + <span class="fa fa-backward"></span> Back to search results + </button> + </div> + </div> + <app-spinner *ngIf="(attributeListIsLoading | async) + || (outputFamilyListIsLoading | async) + || (outputCategoryListIsLoading | async) + || (objectIsLoading | async)"> + </app-spinner> + <div *ngIf="(attributeListIsLoaded | async) && (outputFamilyListIsLoaded | async) && (outputCategoryListIsLoaded | async) && (objectIsLoaded | async)" [ngSwitch]="getObjectType(attributeList | async)"> + <app-spectra-object *ngSwitchCase="'spectra'" + [datasetSelected]="datasetSelected | async" + [outputFamilyList]="outputFamilyList | async" + [outputCategoryList]="outputCategoryList | async" + [attributeList]="attributeList | async" + [object]="object | async" + [spectraCSV]="spectraCSV | async" + [spectraIsLoading]="spectraIsLoading | async" + [spectraIsLoaded]="spectraIsLoaded | async" + (getSpectraCSV)="getSpectraCSV($event)"> + </app-spectra-object> + <app-default-object *ngSwitchDefault + [datasetSelected]="datasetSelected | async" + [outputFamilyList]="outputFamilyList | async" + [outputCategoryList]="outputCategoryList | async" + [attributeList]="attributeList | async" + [object]="object | async"> + </app-default-object> + </div> +</div> diff --git a/client/src/app/instance/detail/containers/detail.component.ts b/client/src/app/instance/detail/containers/detail.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..331290c257df23a6736d98e8f7f702e3ab2da687 --- /dev/null +++ b/client/src/app/instance/detail/containers/detail.component.ts @@ -0,0 +1,143 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Location } from '@angular/common'; + +import { Store } from '@ngrx/store'; +import { Observable, Subscription } from 'rxjs'; + +import { Attribute, OutputCategory, OutputFamily } from 'src/app/metamodel/models'; +import * as fromDetail from 'src/app/instance/store/reducers/detail.reducer'; +import * as detailActions from 'src/app/instance/store/actions/detail.actions'; +import * as detailSelector from 'src/app/instance/store/selectors/detail.selector'; +import * as datasetSelector from 'src/app/metamodel/selectors/dataset.selector'; +import * as searchSelector from 'src/app/instance/store/selectors/search.selector'; +import * as outputFamilyActions from 'src/app/metamodel/actions/output-family.actions'; +import * as outputFamilySelector from 'src/app/metamodel/selectors/output-family.selector'; +import * as outputCategoryActions from 'src/app/metamodel/actions/output-category.actions'; +import * as outputCategorySelector from 'src/app/metamodel/selectors/output-category.selector'; +import * as attributeActions from 'src/app/metamodel/actions/attribute.actions'; +import * as attributeSelector from 'src/app/metamodel/selectors/attribute.selector'; + +/** + * Interface for store state. + * + * @interface StoreState + */ +interface StoreState { + detail: fromDetail.State; +} + +@Component({ + selector: 'app-detail-page', + templateUrl: 'detail.component.html' +}) +/** + * @class + * @classdesc Detail container. + * + * @implements OnInit + * @implements OnDestroy + */ +export class DetailComponent implements OnInit, OnDestroy { + public datasetSelected: Observable<string>; + public pristine: Observable<boolean>; + public attributeList: Observable<Attribute[]>; + public attributeListIsLoading: Observable<boolean>; + public attributeListIsLoaded: Observable<boolean>; + public outputFamilyList: Observable<OutputFamily[]>; + public outputFamilyListIsLoading: Observable<boolean>; + public outputFamilyListIsLoaded: Observable<boolean>; + public outputCategoryList: Observable<OutputCategory[]>; + public outputCategoryListIsLoading: Observable<boolean>; + public outputCategoryListIsLoaded: Observable<boolean>; + public object: Observable<any>; + public objectIsLoading: Observable<boolean>; + public objectIsLoaded: Observable<boolean>; + public spectraCSV: Observable<string>; + public spectraIsLoading: Observable<boolean>; + public spectraIsLoaded: Observable<boolean>; + + private attributeListIsLoadedSubscription: Subscription; + + constructor(private location: Location, private store: Store<StoreState>) { + this.datasetSelected = store.select(datasetSelector.selectDatasetNameByRoute); + this.pristine = store.select(searchSelector.selectPristine); + this.attributeList = store.select(attributeSelector.selectAllAttributes); + this.attributeListIsLoading = store.select(attributeSelector.selectAttributeListIsLoading); + this.attributeListIsLoaded = store.select(attributeSelector.selectAttributeListIsLoaded); + this.outputFamilyList = store.select(outputFamilySelector.selectAllOutputFamilies); + this.outputFamilyListIsLoading = store.select(outputFamilySelector.selectOutputFamilyListIsLoading); + this.outputFamilyListIsLoaded = store.select(outputFamilySelector.selectOutputFamilyListIsLoaded); + this.outputCategoryList = store.select(outputCategorySelector.selectAllOutputCategories); + this.outputCategoryListIsLoading = store.select(outputCategorySelector.selectOutputCategoryListIsLoading); + this.outputCategoryListIsLoaded = store.select(outputCategorySelector.selectOutputCategoryListIsLoaded); + this.object = this.store.select(detailSelector.selectObject); + this.objectIsLoading = this.store.select(detailSelector.selectObjectIsLoading); + this.objectIsLoaded = this.store.select(detailSelector.selectObjectIsLoaded); + this.spectraCSV = this.store.select(detailSelector.selectSpectraCSV); + this.spectraIsLoading = this.store.select(detailSelector.selectObjectIsLoading); + this.spectraIsLoaded = this.store.select(detailSelector.selectSpectraIsLoaded); + } + + ngOnInit() { + // Create a micro task that is processed after the current synchronous code + // This micro task prevent the expression has changed after view init error + Promise.resolve(null).then(() => this.store.dispatch(attributeActions.loadAttributeList())); + Promise.resolve(null).then(() => this.store.dispatch(outputFamilyActions.loadOutputFamilyList())); + Promise.resolve(null).then(() => this.store.dispatch(outputCategoryActions.loadOutputCategoryList())); + this.attributeListIsLoadedSubscription = this.attributeListIsLoaded.subscribe(attributeListIsLoaded => { + if (attributeListIsLoaded) { + Promise.resolve(null).then(() => this.store.dispatch(detailActions.retrieveObject())); + } + }); + } + + /** + * Returns the object type. + * + * @param {Attribute[]} attributeList - The attribute list. + * + * @return string + */ + getObjectType(attributeList: Attribute[]): string { + const spectrumAttribute: Attribute = attributeList + .filter(a => a.detail) + .find(attribute => attribute.search_flag === 'SPECTRUM_1D'); + if (spectrumAttribute) { + return 'spectra'; + } + return 'default'; + } + + /** + * Gets back to result page. + */ + goBackToResult(): void { + this.location.back(); + } + + /** + * Dispatches action to retrieve spectra file. + * + * @param {string} spectraFile - The spectra file name. + */ + getSpectraCSV(spectraFile: string): void { + this.store.dispatch(detailActions.retrieveSpectra({ filename: spectraFile })); + } + + /** + * Resets detail information. + */ + ngOnDestroy() { + this.attributeListIsLoadedSubscription.unsubscribe(); + // this.store.dispatch(new detailActions.DestroyDetailAction()); + } +} diff --git a/client/src/app/instance/detail/detail-routing.module.ts b/client/src/app/instance/detail/detail-routing.module.ts index ee1308922ba3f0b402a1a1bee41c26a5c08dce7e..8f88b99bb56b2ad21b228b818173bdb623ed7f6d 100644 --- a/client/src/app/instance/detail/detail-routing.module.ts +++ b/client/src/app/instance/detail/detail-routing.module.ts @@ -10,10 +10,11 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; -//import { DetailComponent } from './detail.component'; +import { DetailComponent } from './containers/detail.component'; const routes: Routes = [ - //{ path: '', component: DetailComponent } + { path: '', redirectTo: ':/dname/:id', pathMatch: 'full' }, + { path: ':dname/:id', component: DetailComponent } ]; @NgModule({ @@ -23,5 +24,5 @@ const routes: Routes = [ export class DetailRoutingModule { } export const routedComponents = [ - //DetailComponent + DetailComponent ]; diff --git a/client/src/app/instance/detail/detail.module.ts b/client/src/app/instance/detail/detail.module.ts index 7e1605ceeda56e6d572a37d69b6fb11fb8e8bc7d..fbaaabfa3e090c12c51338d99b4ec7ebd6f32bdc 100644 --- a/client/src/app/instance/detail/detail.module.ts +++ b/client/src/app/instance/detail/detail.module.ts @@ -11,12 +11,16 @@ import { NgModule } from '@angular/core'; import { SharedModule } from 'src/app/shared/shared.module'; import { DetailRoutingModule, routedComponents } from './detail-routing.module'; +import { dummiesComponents } from './components'; @NgModule({ imports: [ SharedModule, DetailRoutingModule ], - declarations: [routedComponents] + declarations: [ + routedComponents, + dummiesComponents + ] }) export class DetailModule { } diff --git a/client/src/app/instance/documentation/containers/documentation.component.ts b/client/src/app/instance/documentation/containers/documentation.component.ts index cd48d259a7848a8754ea007a8ade6ba1c658da9d..4146df4261f2d8bbc91ccaf3ca2309ea0be5dc27 100644 --- a/client/src/app/instance/documentation/containers/documentation.component.ts +++ b/client/src/app/instance/documentation/containers/documentation.component.ts @@ -14,8 +14,8 @@ import { Observable } from 'rxjs'; import * as documentationActions from 'src/app/instance/store/actions/documentation.actions'; import * as datasetSelector from 'src/app/metamodel/selectors/dataset.selector'; +import { AppConfigService } from 'src/app/app-config.service'; import { Attribute } from 'src/app/metamodel/models'; -import { environment } from 'src/environments/environment'; import * as instanceSelector from "../../../metamodel/selectors/instance.selector"; import * as attributeSelector from "../../../metamodel/selectors/attribute.selector"; @@ -37,7 +37,7 @@ export class DocumentationComponent implements OnInit { public attributeListIsLoaded: Observable<boolean>; public attributeList: Observable<Attribute[]>; - constructor(private store: Store<{ }>) { + constructor(private store: Store<{ }>, private config: AppConfigService) { this.instanceSelected = store.select(instanceSelector.selectInstanceNameByRoute); this.datasetSelected = store.select(datasetSelector.selectDatasetNameByRoute); this.attributeListIsLoading = store.select(attributeSelector.selectAttributeListIsLoading); @@ -55,10 +55,10 @@ export class DocumentationComponent implements OnInit { * @return string */ getUrlServer(): string { - if (!environment.apiUrl.startsWith('http')) { + if (!this.config.apiUrl.startsWith('http')) { const url = window.location; - return url.protocol + '//' + url.host + environment.apiUrl; + return url.protocol + '//' + url.host + this.config.apiUrl; } - return environment.apiUrl; + return this.config.apiUrl; } } diff --git a/client/src/app/instance/instance-routing.module.ts b/client/src/app/instance/instance-routing.module.ts index 37baa1075d4b111e51c541b0cad73beb09937cb2..053f59c53dbca4657b86d991f96f63645148ad67 100644 --- a/client/src/app/instance/instance-routing.module.ts +++ b/client/src/app/instance/instance-routing.module.ts @@ -14,17 +14,18 @@ import { InstanceComponent } from './instance.component'; const routes: Routes = [ { - path: 'instance/:iname', component: InstanceComponent, children: [ + path: ':iname', component: InstanceComponent, children: [ { path: '', redirectTo: 'home', pathMatch: 'full' }, { path: 'home', loadChildren: () => import('./home/home.module').then(m => m.HomeModule) }, { path: 'documentation', loadChildren: () => import('./documentation/documentation.module').then(m => m.DocumentationModule) }, - { path: 'search', loadChildren: () => import('./search/search.module').then(m => m.SearchModule) } + { path: 'search', loadChildren: () => import('./search/search.module').then(m => m.SearchModule) }, + { path: 'detail', loadChildren: () => import('./detail/detail.module').then(m => m.DetailModule) } ] } ]; @NgModule({ - imports: [RouterModule.forRoot(routes)], + imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) export class InstanceRoutingModule { } diff --git a/client/src/app/instance/instance.component.html b/client/src/app/instance/instance.component.html index 979df50e47db62138793be21dff9adf7df5e4cf1..61101934f1e33d3d27823eb8998cedcf423d891b 100644 --- a/client/src/app/instance/instance.component.html +++ b/client/src/app/instance/instance.component.html @@ -3,6 +3,8 @@ [links]="links" [isAuthenticated]="isAuthenticated | async" [userProfile]="userProfile | async" + [baseHref]="getBaseHref()" + [authenticationEnabled]="getAuthenticationEnabled()" (login)="login()" (logout)="logout()" (openEditProfile)="openEditProfile()"> diff --git a/client/src/app/instance/instance.component.ts b/client/src/app/instance/instance.component.ts index 15365842642b82d50d179a22cf95881fc4b13659..214fb769ab385501d5a877f016369ac40eb209f5 100644 --- a/client/src/app/instance/instance.component.ts +++ b/client/src/app/instance/instance.component.ts @@ -15,6 +15,7 @@ import { UserProfile } from 'src/app/auth/user-profile.model'; import * as authActions from 'src/app/auth/auth.actions'; import * as authSelector from 'src/app/auth/auth.selector'; import * as metamodelActions from './store/actions/metamodel.actions'; +import { AppConfigService } from 'src/app/app-config.service'; @Component({ selector: 'app-instance', @@ -35,7 +36,7 @@ export class InstanceComponent implements OnInit { public userProfile: Observable<UserProfile>; public userRoles: Observable<string[]>; - constructor(private store: Store<{ }>) { + constructor(private store: Store<{ }>, private config: AppConfigService) { this.isAuthenticated = store.select(authSelector.selectIsAuthenticated); this.userProfile = store.select(authSelector.selectUserProfile); this.userRoles = store.select(authSelector.selectUserRoles); @@ -46,6 +47,14 @@ export class InstanceComponent implements OnInit { Promise.resolve(null).then(() => this.store.dispatch(metamodelActions.loadInstanceMetamodel())); } + getBaseHref() { + return this.config.baseHref; + } + + getAuthenticationEnabled() { + return this.config.authenticationEnabled; + } + login(): void { this.store.dispatch(authActions.login()); } diff --git a/client/src/app/instance/instance.reducer.ts b/client/src/app/instance/instance.reducer.ts index 0c1981cf76c6306499f8c3167c22227f8dedc97f..f660ab9ab6c3dd59f99ddf24e047eac5cd2b82a7 100644 --- a/client/src/app/instance/instance.reducer.ts +++ b/client/src/app/instance/instance.reducer.ts @@ -13,17 +13,23 @@ import { RouterReducerState } from 'src/app/custom-route-serializer'; import * as metamodel from './store/reducers/metamodel.reducer'; import * as search from './store/reducers/search.reducer'; import * as samp from './store/reducers/samp.reducer'; +import * as coneSearch from './store/reducers/cone-search.reducer'; +import * as detail from './store/reducers/detail.reducer'; export interface State { metamodel: metamodel.State, search: search.State, - samp: samp.State + samp: samp.State, + coneSearch: coneSearch.State + detail: detail.State } const reducers = { metamodel: metamodel.metamodelReducer, search: search.searchReducer, - samp: samp.sampReducer + samp: samp.sampReducer, + coneSearch: coneSearch.coneSearchReducer, + detail: detail.detailReducer }; export const instanceReducer = combineReducers(reducers); diff --git a/client/src/app/instance/search/components/criteria/cone-search-tab.component.html b/client/src/app/instance/search/components/criteria/cone-search-tab.component.html new file mode 100644 index 0000000000000000000000000000000000000000..ea951c32bd3106f1c745ad5e18876c73d592bad5 --- /dev/null +++ b/client/src/app/instance/search/components/criteria/cone-search-tab.component.html @@ -0,0 +1,37 @@ +<accordion *ngIf="(datasetList | datasetByName:datasetSelected).config.cone_search.cone_search_enabled" [isAnimated]="true"> + <accordion-group #ag [panelClass]="'custom-accordion'" [isOpen]="(datasetList | datasetByName:datasetSelected).config.cone_search.cone_search_opened" class="my-2"> + <button class="btn btn-link btn-block clearfix" accordion-heading> + <span class="pull-left float-left"> + Cone search + + <span *ngIf="ag.isOpen"> + <span class="fas fa-chevron-up"></span> + </span> + <span *ngIf="!ag.isOpen"> + <span class="fas fa-chevron-down"></span> + </span> + </span> + </button> + <div class="row"> + <div class="col"> + <app-cone-search + [coneSearch]="coneSearch" + [resolver]="resolver" + [resolverIsLoading]="resolverIsLoading" + [resolverIsLoaded]="resolverIsLoaded" + (addConeSearch)="addConeSearch.emit($event)" + (deleteConeSearch)="deleteConeSearch.emit()" + (retrieveCoordinates)="retrieveCoordinates.emit($event)" #cs> + </app-cone-search> + </div> + <div class="col-2 text-center align-self-end"> + <button class="btn btn-outline-success" *ngIf="!coneSearch" [hidden]="cs.form.invalid" (click)="addConeSearch.emit(cs.getConeSearch())"> + <span class="fas fa-plus fa-fw"></span> + </button> + <button class="btn btn-outline-danger" *ngIf="coneSearch" (click)="deleteConeSearch.emit()"> + <span class="fa fa-times fa-fw"></span> + </button> + </div> + </div> + </accordion-group> +</accordion> diff --git a/client/src/app/instance/search/components/criteria/cone-search-tab.component.ts b/client/src/app/instance/search/components/criteria/cone-search-tab.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..85a3f259e3df262a93c190d37c7733d31b1743dc --- /dev/null +++ b/client/src/app/instance/search/components/criteria/cone-search-tab.component.ts @@ -0,0 +1,34 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; + +import { Dataset } from 'src/app/metamodel/models'; +import { ConeSearch, Resolver } from 'src/app/instance/store/models'; + +@Component({ + selector: 'app-cone-search-tab', + templateUrl: 'cone-search-tab.component.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +/** + * @class + * @classdesc Search cone search tab component. + */ +export class ConeSearchTabComponent { + @Input() datasetSelected: string; + @Input() datasetList: Dataset[]; + @Input() coneSearch: ConeSearch; + @Input() resolver: Resolver; + @Input() resolverIsLoading: boolean; + @Input() resolverIsLoaded: boolean; + @Output() addConeSearch: EventEmitter<ConeSearch> = new EventEmitter(); + @Output() deleteConeSearch: EventEmitter<{ }> = new EventEmitter(); + @Output() retrieveCoordinates: EventEmitter<string> = new EventEmitter(); +} diff --git a/client/src/app/instance/search/components/criteria/index.ts b/client/src/app/instance/search/components/criteria/index.ts index bdee82025471c6892a6af287afb3c151e019d2fd..88656ad3c2668f4eb770c6db2672fb8302115a68 100644 --- a/client/src/app/instance/search/components/criteria/index.ts +++ b/client/src/app/instance/search/components/criteria/index.ts @@ -1,8 +1,10 @@ +import { ConeSearchTabComponent } from './cone-search-tab.component'; import { CriteriaTabsComponent } from './criteria-tabs.component'; import { CriteriaByFamilyComponent } from './criteria-by-family.component'; import { searchTypeComponents } from './search-type'; export const criteriaComponents = [ + ConeSearchTabComponent, CriteriaTabsComponent, CriteriaByFamilyComponent, searchTypeComponents diff --git a/client/src/app/instance/search/components/criteria/search-type/between-date.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/between-date.component.spec.ts deleted file mode 100644 index 72ebd410aa82ef81bf7c10fd618ab211ad3c581d..0000000000000000000000000000000000000000 --- a/client/src/app/instance/search/components/criteria/search-type/between-date.component.spec.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Component, ViewChild } from '@angular/core'; -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { FormsModule, ReactiveFormsModule, FormControl } from '@angular/forms'; - -import { BsDatepickerModule } from 'ngx-bootstrap/datepicker'; - -import { BetweenDateComponent } from './between-date.component'; -import { BetweenCriterion } from '../../../store/model'; - -describe('[Search][Criteria][SearchType] Component: BetweenDateComponent', () => { - @Component({ - selector: `app-host`, - template: `<app-between-date [criterion]='criterion'></app-between-date>` - }) - class TestHostComponent { - @ViewChild(BetweenDateComponent, { static: false }) - public testedComponent: BetweenDateComponent; - public criterion: BetweenCriterion = undefined; - } - - let testHostComponent: TestHostComponent; - let testHostFixture: ComponentFixture<TestHostComponent>; - let testedComponent: BetweenDateComponent; - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [BetweenDateComponent, TestHostComponent], - imports: [BsDatepickerModule.forRoot(), FormsModule, ReactiveFormsModule] - }); - testHostFixture = TestBed.createComponent(TestHostComponent); - testHostComponent = testHostFixture.componentInstance; - testHostFixture.detectChanges(); - testedComponent = testHostComponent.testedComponent; - })); - - it('should create the component', () => { - expect(testedComponent).toBeTruthy(); - }); - - it('#getDateString() should format a datetime object into a string', () => { - const dateObject = new Date('February 08, 2019 15:47:00'); - const expectedDateString = '2019-02-08'; - const formatedDate = testedComponent.getDateString(dateObject); - expect(formatedDate).toEqual(expectedDateString); - }); - - it('#getDefault() should enable and not fill form as criterion not defined in host component', () => { - expect(testedComponent.field.value).toBeNull(); - expect(testedComponent.field.enabled).toBeTruthy(); - }); - - it('#getDefault() should fill and disable form if criterion is defined', () => { - const criterion = { id: 1, type: 'between', min: '2019-02-08', max: '2019-02-17' } as BetweenCriterion; - const expectedFieldValue = [new Date(criterion.min), new Date(criterion.max)]; - testedComponent.getDefault(criterion); - expect(testedComponent.field.value).toEqual(expectedFieldValue); - expect(testedComponent.field.disabled).toBeTruthy(); - }); - - it('raises the add criterion event when clicked', () => { - testedComponent.id = 1; - const dateMin = '2019-02-08'; - const dateMax = '2019-02-17'; - testedComponent.field = new FormControl([new Date(dateMin), new Date(dateMax)]); - const expectedCriterion = { id: testedComponent.id, type: 'between', min: dateMin, max: dateMax } as BetweenCriterion; - testedComponent.addCriterion.subscribe((event: BetweenCriterion) => expect(event).toEqual(expectedCriterion)); - testedComponent.emitAdd(); - }); - - it('raises the delete criterion event when clicked', () => { - testedComponent.id = 1; - testedComponent.deleteCriterion.subscribe((event: number) => expect(event).toEqual(1)); - testedComponent.emitDelete(); - }); -}); diff --git a/client/src/app/instance/search/components/criteria/search-type/between.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/between.component.spec.ts deleted file mode 100644 index 80df0b7b01ffcf564995ab3e582e1cadc754b4b0..0000000000000000000000000000000000000000 --- a/client/src/app/instance/search/components/criteria/search-type/between.component.spec.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { Component, ViewChild } from '@angular/core'; -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { FormsModule, ReactiveFormsModule, FormControl } from '@angular/forms'; - -import { BetweenComponent } from './between.component'; -import { BetweenCriterion } from '../../../store/model'; - -describe('[Search][Criteria][SearchType] Component: BetweenComponent', () => { - @Component({ - selector: `app-host`, - template: `<app-between [criterion]='criterion'></app-between>` - }) - class TestHostComponent { - @ViewChild(BetweenComponent, { static: false }) - public testedComponent: BetweenComponent; - public criterion: BetweenCriterion = undefined; - } - - let testHostComponent: TestHostComponent; - let testHostFixture: ComponentFixture<TestHostComponent>; - let testedComponent: BetweenComponent; - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [BetweenComponent, TestHostComponent], - imports: [FormsModule, ReactiveFormsModule] - }); - testHostFixture = TestBed.createComponent(TestHostComponent); - testHostComponent = testHostFixture.componentInstance; - testHostFixture.detectChanges(); - testedComponent = testHostComponent.testedComponent; - })); - - it('should create the component', () => { - expect(testedComponent).toBeTruthy(); - }); - - it('#getDefault() should enable and not fill form as criterion not defined in host component', () => { - expect(testedComponent.fieldMin.value).toBeNull(); - expect(testedComponent.fieldMin.enabled).toBeTruthy(); - expect(testedComponent.fieldMax.value).toBeNull(); - expect(testedComponent.fieldMax.enabled).toBeTruthy(); - }); - - it('#getDefault() should fill and disable form if criterion is defined', () => { - const min = '10'; - const max = '20'; - const criterion = { id: 1, type: 'between', min, max } as BetweenCriterion; - const expectedFieldMinValue = min; - const expectedFieldMaxValue = max; - testedComponent.getDefault(criterion); - expect(testedComponent.fieldMin.value).toEqual(expectedFieldMinValue); - expect(testedComponent.fieldMin.disabled).toBeTruthy(); - expect(testedComponent.fieldMax.value).toEqual(expectedFieldMaxValue); - expect(testedComponent.fieldMax.disabled).toBeTruthy(); - }); - - it('#getPlaceholderMin() should fill the placeholder if defined', () => { - const placeholder = '10'; - testedComponent.placeholderMin = placeholder; - expect(testedComponent.getPlaceholderMin()).toEqual(placeholder); - }); - - it('#getPlaceholderMin() should not fill the placeholder if not defined', () => { - expect(testedComponent.getPlaceholderMin()).toEqual(''); - }); - - it('#getPlaceholderMax() should fill the placeholder if defined', () => { - const placeholder = '10'; - testedComponent.placeholderMax = placeholder; - expect(testedComponent.getPlaceholderMax()).toEqual(placeholder); - }); - - it('#getPlaceholderMax() should not fill the placeholder if not defined', () => { - expect(testedComponent.getPlaceholderMax()).toEqual(''); - }); - - it('raises the add criterion event when clicked', () => { - testedComponent.id = 1; - const min = '10'; - const max = '20'; - testedComponent.fieldMin = new FormControl(min); - testedComponent.fieldMax = new FormControl(max); - const expectedCriterion = { id: testedComponent.id, type: 'between', min, max } as BetweenCriterion; - testedComponent.addCriterion.subscribe((event: BetweenCriterion) => expect(event).toEqual(expectedCriterion)); - testedComponent.emitAdd(); - }); - - it('raises the delete criterion event when clicked', () => { - testedComponent.id = 1; - testedComponent.deleteCriterion.subscribe((event: number) => expect(event).toEqual(1)); - testedComponent.emitDelete(); - }); -}); diff --git a/client/src/app/instance/search/components/criteria/search-type/checkbox.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/checkbox.component.spec.ts deleted file mode 100644 index 0c10c5ec9edd0c2d1e345cc00cb52b6b65a4efe8..0000000000000000000000000000000000000000 --- a/client/src/app/instance/search/components/criteria/search-type/checkbox.component.spec.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { Component, ViewChild } from '@angular/core'; -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { FormsModule, ReactiveFormsModule, FormControl, FormArray } from '@angular/forms'; - -import { CheckboxComponent } from './checkbox.component'; -import { SelectMultipleCriterion } from '../../../store/model'; -import { Option } from '../../../../metamodel/model'; - -describe('[Search][Criteria][SearchType] Component: CheckboxComponent', () => { - @Component({ - selector: `app-host`, - template: `<app-checkbox [options]='options' [criterion]='criterion'></app-checkbox>` - }) - class TestHostComponent { - @ViewChild(CheckboxComponent, { static: false }) - public testedComponent: CheckboxComponent; - public criterion: SelectMultipleCriterion = undefined; - public options: Option[] = [ - { label: 'One', value: 'one', display: 1 }, - { label: 'Two', value: 'two', display: 2 }, - { label: 'Three', value: 'three', display: 3 } - ]; - } - - let testHostComponent: TestHostComponent; - let testHostFixture: ComponentFixture<TestHostComponent>; - let testedComponent: CheckboxComponent; - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [CheckboxComponent, TestHostComponent], - imports: [FormsModule, ReactiveFormsModule] - }); - testHostFixture = TestBed.createComponent(TestHostComponent); - testHostComponent = testHostFixture.componentInstance; - testHostFixture.detectChanges(); - testedComponent = testHostComponent.testedComponent; - })); - - it('should create the component', () => { - expect(testedComponent).toBeTruthy(); - }); - - it('#getDefault() should not fill and enable form if criterion not defined', () => { - expect(testedComponent.checkboxes.length).toEqual(3); - expect(testedComponent.isChecked()).toBeFalsy(); - expect(testedComponent.checkboxes.enabled).toBeTruthy(); - }); - - it('#getDefault() should fill and disable form if criterion is defined', () => { - const options: Option[] = [ - { label: 'One', value: 'one', display: 1 }, - { label: 'Two', value: 'two', display: 2 } - ]; - const criterion = { id: 1, type: 'multiple', options} as SelectMultipleCriterion; - testedComponent.getDefault(criterion); - expect(testedComponent.isChecked()).toBeTruthy(); - const values = testedComponent.checkboxes.value as boolean[]; - expect(values.filter(v => v).length).toEqual(2); - }); - - it('raises the add criterion event when clicked', () => { - testedComponent.id = 1; - testedComponent.checkboxes = new FormArray([ - new FormControl(false), - new FormControl(true), - new FormControl(true) - ]) - const expectedCriterion = { - id: 1, - type: 'multiple', - options: [ - { label: 'Two', value: 'two', display: 2 }, - { label: 'Three', value: 'three', display: 3 } - ] - }; - testedComponent.addCriterion.subscribe((event: SelectMultipleCriterion) => expect(event).toEqual(expectedCriterion)); - testedComponent.emitAdd(); - }); - - it('raises the delete criterion event when clicked', () => { - testedComponent.id = 1; - testedComponent.deleteCriterion.subscribe((event: number) => expect(event).toEqual(1)); - testedComponent.emitDelete(); - }); -}); diff --git a/client/src/app/instance/search/components/criteria/search-type/datalist.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/datalist.component.spec.ts deleted file mode 100644 index 588ac8c5304030b4386af241390533238f912512..0000000000000000000000000000000000000000 --- a/client/src/app/instance/search/components/criteria/search-type/datalist.component.spec.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core'; -import { FormsModule, ReactiveFormsModule, FormControl } from '@angular/forms'; - -import { DatalistComponent } from './datalist.component'; -import { FieldCriterion } from '../../../store/model'; - -describe('[Search][Criteria][SearchType] Component: DatalistComponent', () => { - @Component({ - selector: `app-host`, - template: `<app-datalist [criterion]='criterion'></app-datalist>` - }) - class TestHostComponent { - @ViewChild(DatalistComponent, { static: false }) - public testedComponent: DatalistComponent; - public criterion: FieldCriterion = undefined; - } - - @Component({ selector: 'app-operator', template: '' }) - class OperatorStubComponent { - @Input() operator: string; - @Input() searchType: string; - @Input() advancedForm: boolean; - @Input() disabled: boolean; - @Output() changeOperator: EventEmitter<string> = new EventEmitter(); - } - - @Component({ selector: 'app-help-like', template: '' }) - class HelpLikeStubComponent { } - - let testHostComponent: TestHostComponent; - let testHostFixture: ComponentFixture<TestHostComponent>; - let testedComponent: DatalistComponent; - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [ - DatalistComponent, - TestHostComponent, - OperatorStubComponent, - HelpLikeStubComponent - ], - imports: [FormsModule, ReactiveFormsModule] - }); - testHostFixture = TestBed.createComponent(TestHostComponent); - testHostComponent = testHostFixture.componentInstance; - testHostFixture.detectChanges(); - testedComponent = testHostComponent.testedComponent; - })); - - it('should create the component', () => { - expect(testedComponent).toBeTruthy(); - }); - - it('#getDefault() should enable and not fill form as criterion not defined in host component', () => { - expect(testedComponent.field.value).toBeNull(); - expect(testedComponent.field.enabled).toBeTruthy(); - }); - - it('#getDefault() should fill and disable form if criterion is defined', () => { - const value = '10'; - const criterion = { id: 1, type: 'field', operator: 'eq', value } as FieldCriterion; - const expectedFieldValue = value; - testedComponent.getDefault(criterion); - expect(testedComponent.field.value).toEqual(expectedFieldValue); - expect(testedComponent.field.disabled).toBeTruthy(); - }); - - it('#getPlaceholder() should fill the placeholder if defined', () => { - const placeholder = '10'; - testedComponent.placeholder = placeholder; - expect(testedComponent.getPlaceholder()).toEqual(placeholder); - }); - - it('#getPlaceholder() should not fill the placeholder if not defined', () => { - expect(testedComponent.getPlaceholder()).toEqual(''); - }); - - it('#getDatalistId() should return an id', () => { - testedComponent.id = 1; - expect(testedComponent.getDatalistId()).toEqual('datalist_' + 1); - }); - - it('#changeOperator() should change the operator', () => { - expect(testedComponent.operator).toBeUndefined(); - testedComponent.changeOperator('toto'); - expect(testedComponent.operator).toBe('toto'); - }); - - it('raises the add criterion event when clicked', () => { - testedComponent.id = 1; - const operator = '='; - const value = '10'; - testedComponent.field = new FormControl(value); - testedComponent.operator = operator; - const expectedCriterion = { id: testedComponent.id, type: 'field', operator, value } as FieldCriterion; - testedComponent.addCriterion.subscribe((event: FieldCriterion) => expect(event).toEqual(expectedCriterion)); - testedComponent.emitAdd(); - }); - - it('raises the delete criterion event when clicked', () => { - testedComponent.id = 1; - testedComponent.deleteCriterion.subscribe((event: number) => expect(event).toEqual(1)); - testedComponent.emitDelete(); - }); -}); diff --git a/client/src/app/instance/search/components/criteria/search-type/date.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/date.component.spec.ts deleted file mode 100644 index a67b65f674076ee75448da86877a1bdc093317f2..0000000000000000000000000000000000000000 --- a/client/src/app/instance/search/components/criteria/search-type/date.component.spec.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core'; -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { FormsModule, ReactiveFormsModule, FormControl } from '@angular/forms'; - -import { BsDatepickerModule } from 'ngx-bootstrap/datepicker'; - -import { DateComponent } from './date.component'; -import { FieldCriterion } from '../../../store/model'; - -describe('[Search][Criteria][SearchType] Component: DateComponent', () => { - @Component({ - selector: `app-host`, - template: `<app-date [criterion]='criterion'></app-date>` - }) - class TestHostComponent { - @ViewChild(DateComponent, { static: false }) - public testedComponent: DateComponent; - public criterion: FieldCriterion = undefined; - } - - @Component({ selector: 'app-operator', template: '' }) - class OperatorStubComponent { - @Input() operator: string; - @Input() searchType: string; - @Input() advancedForm: boolean; - @Input() disabled: boolean; - @Output() changeOperator: EventEmitter<string> = new EventEmitter(); - } - - let testHostComponent: TestHostComponent; - let testHostFixture: ComponentFixture<TestHostComponent>; - let testedComponent: DateComponent; - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [ - DateComponent, - TestHostComponent, - OperatorStubComponent - ], - imports: [BsDatepickerModule.forRoot(), FormsModule, ReactiveFormsModule] - }); - testHostFixture = TestBed.createComponent(TestHostComponent); - testHostComponent = testHostFixture.componentInstance; - testHostFixture.detectChanges(); - testedComponent = testHostComponent.testedComponent; - })); - - it('should create the component', () => { - expect(testedComponent).toBeTruthy(); - }); - - it('#getDefault() should enable and not fill form as criterion not defined in host component', () => { - expect(testedComponent.field.value).toBeNull(); - expect(testedComponent.field.enabled).toBeTruthy(); - }); - - it('#getDefault() should fill and disable form if criterion is defined', () => { - const value = '2019-02-17'; - const criterion = { id: 1, type: 'field', operator: 'eq', value } as FieldCriterion; - const expectedFieldValue = new Date(value); - testedComponent.getDefault(criterion); - expect(testedComponent.field.value).toEqual(expectedFieldValue); - expect(testedComponent.field.disabled).toBeTruthy(); - }); - - it('#getPlaceholder() should fill the placeholder if defined', () => { - const placeholder = '2019-02-17'; - testedComponent.placeholder = placeholder; - expect(testedComponent.getPlaceholder()).toEqual(placeholder); - }); - - it('#getPlaceholder() should not fill the placeholder if not defined', () => { - expect(testedComponent.getPlaceholder()).toEqual(''); - }); - - it('#getDateString() should return a date as string', () => { - const dateString = '2019-02-17'; - const date = new Date(dateString); - expect(testedComponent.getDateString(date)).toEqual(dateString); - }); - - it('#changeOperator() should change the operator', () => { - expect(testedComponent.operator).toBeUndefined(); - testedComponent.changeOperator('toto'); - expect(testedComponent.operator).toBe('toto'); - }); - - it('raises the add criterion event when clicked', () => { - testedComponent.id = 1; - const operator = '='; - testedComponent.operator = operator; - const date = '2019-02-17'; - testedComponent.field = new FormControl(new Date(date)); - const expectedCriterion = { id: testedComponent.id, type: 'field', operator, value: date } as FieldCriterion; - testedComponent.addCriterion.subscribe((event: FieldCriterion) => expect(event).toEqual(expectedCriterion)); - testedComponent.emitAdd(); - }); - - it('raises the delete criterion event when clicked', () => { - testedComponent.id = 1; - testedComponent.deleteCriterion.subscribe((event: number) => expect(event).toEqual(1)); - testedComponent.emitDelete(); - }); -}); diff --git a/client/src/app/instance/search/components/criteria/search-type/datetime.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/datetime.component.spec.ts deleted file mode 100644 index 045aeeeeee9bdf4ea743d8308504869ba35e73b3..0000000000000000000000000000000000000000 --- a/client/src/app/instance/search/components/criteria/search-type/datetime.component.spec.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core'; -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { FormsModule, ReactiveFormsModule, FormControl } from '@angular/forms'; - -import { BsDatepickerModule } from 'ngx-bootstrap/datepicker'; -import { NgSelectModule } from '@ng-select/ng-select'; - -import { DatetimeComponent } from './datetime.component'; -import { FieldCriterion } from '../../../store/model'; - -describe('[Search][Criteria][SearchType] Component: DatetimeComponent', () => { - @Component({ - selector: `app-host`, - template: `<app-datetime [criterion]='criterion'></app-datetime>` - }) - class TestHostComponent { - @ViewChild(DatetimeComponent, { static: false }) - public testedComponent: DatetimeComponent; - public criterion: FieldCriterion = undefined; - } - - @Component({ selector: 'app-operator', template: '' }) - class OperatorStubComponent { - @Input() operator: string; - @Input() searchType: string; - @Input() advancedForm: boolean; - @Input() disabled: boolean; - @Output() changeOperator: EventEmitter<string> = new EventEmitter(); - } - - let testHostComponent: TestHostComponent; - let testHostFixture: ComponentFixture<TestHostComponent>; - let testedComponent: DatetimeComponent; - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [ - DatetimeComponent, - TestHostComponent, - OperatorStubComponent - ], - imports: [BsDatepickerModule.forRoot(), NgSelectModule, FormsModule, ReactiveFormsModule] - }); - testHostFixture = TestBed.createComponent(TestHostComponent); - testHostComponent = testHostFixture.componentInstance; - testHostFixture.detectChanges(); - testedComponent = testHostComponent.testedComponent; - })); - - it('should create the component', () => { - expect(testedComponent).toBeTruthy(); - }); - - it('#getDefault() should enable and not fill form as criterion not defined in host component', () => { - expect(testedComponent.date.value).toBeNull(); - expect(testedComponent.date.enabled).toBeTruthy(); - expect(testedComponent.hh.value).toBeNull(); - expect(testedComponent.hh.enabled).toBeTruthy(); - expect(testedComponent.mm.value).toBeNull(); - expect(testedComponent.mm.enabled).toBeTruthy(); - expect(testedComponent.isValidFields).toBeFalsy(); - }); - - it('#getDefault() should fill and disable form if criterion is defined', () => { - const value = '2019-02-17 15:47'; - const criterion = { id: 1, type: 'field', operator: 'eq', value } as FieldCriterion; - const expectedDate = new Date('2019-02-17'); - const expectedHour = '15'; - const expectedMinute = '47'; - testedComponent.getDefault(criterion); - expect(testedComponent.date.value).toEqual(expectedDate); - expect(testedComponent.date.disabled).toBeTruthy(); - expect(testedComponent.hh.value).toEqual(expectedHour); - expect(testedComponent.hh.disabled).toBeTruthy(); - expect(testedComponent.mm.value).toEqual(expectedMinute); - expect(testedComponent.mm.disabled).toBeTruthy(); - expect(testedComponent.isValidFields).toBeTruthy(); - }); - - it('#initTime(t) should return an array of string with 2 digits from 0 to t', () => { - const n = 10; - expect(testedComponent.initTime(n).length).toEqual(n); - expect(testedComponent.initTime(n)[5]).toEqual('05'); - }); - - it('#change() should set #isValidFields to false if one or more fields are not defined', () => { - testedComponent.change(); - expect(testedComponent.isValidFields).toBeFalsy(); - testedComponent.hh = new FormControl('15'); - testedComponent.change(); - expect(testedComponent.isValidFields).toBeFalsy(); - testedComponent.mm = new FormControl('47'); - testedComponent.change(); - expect(testedComponent.isValidFields).toBeFalsy(); - testedComponent.date = new FormControl(new Date('2019-02-17')); - testedComponent.change(); - expect(testedComponent.isValidFields).toBeTruthy(); - }); - - it('#change() should set #datetime with fields value when defined', () => { - testedComponent.date = new FormControl(new Date('2019-02-17')); - testedComponent.hh = new FormControl('15'); - testedComponent.mm = new FormControl('47'); - testedComponent.change(); - expect(testedComponent.datetime).toEqual(new Date('2019-02-17 15:47')); - }); - - it('#changeOperator() should change the operator', () => { - expect(testedComponent.operator).toBeUndefined(); - testedComponent.changeOperator('toto'); - expect(testedComponent.operator).toBe('toto'); - }); - - it('raises the add criterion event when clicked', () => { - testedComponent.id = 1; - const operator = '='; - testedComponent.operator = operator; - testedComponent.datetime = new Date('2019-02-17 15:47'); - testedComponent.hh = new FormControl('15'); - testedComponent.mm = new FormControl('47'); - const expectedCriterion = { id: testedComponent.id, type: 'field', operator, value: '2019-02-17 15:47' } as FieldCriterion; - testedComponent.addCriterion.subscribe((event: FieldCriterion) => expect(event).toEqual(expectedCriterion)); - testedComponent.emitAdd(); - }); - - it('raises the delete criterion event when clicked', () => { - testedComponent.id = 1; - testedComponent.deleteCriterion.subscribe((event: number) => expect(event).toEqual(1)); - testedComponent.emitDelete(); - }); -}); diff --git a/client/src/app/instance/search/components/criteria/search-type/field.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/field.component.spec.ts deleted file mode 100644 index 4c7d55122bb30cd3b326b0de96acaf49da61cc6a..0000000000000000000000000000000000000000 --- a/client/src/app/instance/search/components/criteria/search-type/field.component.spec.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core'; -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { FormsModule, ReactiveFormsModule, FormControl } from '@angular/forms'; - -import { FieldComponent } from './field.component'; -import { FieldCriterion } from '../../../store/model'; - -describe('[Search][Criteria][SearchType] Component: FieldComponent', () => { - @Component({ - selector: `app-host`, - template: `<app-field [criterion]='criterion'></app-field>` - }) - class TestHostComponent { - @ViewChild(FieldComponent, { static: false }) - public testedComponent: FieldComponent; - public criterion: FieldCriterion = undefined; - } - - @Component({ selector: 'app-operator', template: '' }) - class OperatorStubComponent { - @Input() operator: string; - @Input() searchType: string; - @Input() advancedForm: boolean; - @Input() disabled: boolean; - @Output() changeOperator: EventEmitter<string> = new EventEmitter(); - } - - @Component({ selector: 'app-help-like', template: '' }) - class HelpLikeStubComponent { } - - let testHostComponent: TestHostComponent; - let testHostFixture: ComponentFixture<TestHostComponent>; - let testedComponent: FieldComponent; - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [ - FieldComponent, - TestHostComponent, - OperatorStubComponent, - HelpLikeStubComponent - ], - imports: [FormsModule, ReactiveFormsModule] - }); - testHostFixture = TestBed.createComponent(TestHostComponent); - testHostComponent = testHostFixture.componentInstance; - testHostFixture.detectChanges(); - testedComponent = testHostComponent.testedComponent; - })); - - it('should create the component', () => { - expect(testedComponent).toBeTruthy(); - }); - - it('#getDefault() should enable and not fill form as criterion not defined in host component', () => { - expect(testedComponent.field.value).toBeNull(); - expect(testedComponent.field.enabled).toBeTruthy(); - }); - - it('#getDefault() should fill and disable form if criterion is defined', () => { - const value = 'test'; - const criterion = { id: 1, type: 'field', operator: 'eq', value } as FieldCriterion; - const expectedFieldValue = value; - testedComponent.getDefault(criterion); - expect(testedComponent.field.value).toEqual(expectedFieldValue); - expect(testedComponent.field.disabled).toBeTruthy(); - }); - - it('#getType() should return `number` if criterion is a number type', () => { - testedComponent.attributeType = 'smallint'; - expect(testedComponent.getType()).toEqual('number'); - testedComponent.attributeType = 'integer'; - expect(testedComponent.getType()).toEqual('number'); - testedComponent.attributeType = 'decimal'; - expect(testedComponent.getType()).toEqual('number'); - testedComponent.attributeType = 'float'; - expect(testedComponent.getType()).toEqual('number'); - }); - - it('#getType() should return `text` if criterion is not a number type', () => { - testedComponent.attributeType = 'char'; - expect(testedComponent.getType()).toEqual('text'); - }); - - it('#getPlaceholder() should fill the placeholder if defined', () => { - const placeholder = 'placeholder'; - testedComponent.placeholder = placeholder; - expect(testedComponent.getPlaceholder()).toEqual(placeholder); - }); - - it('#getPlaceholder() should not fill the placeholder if not defined', () => { - expect(testedComponent.getPlaceholder()).toEqual(''); - }); - - it('#changeOperator() should change the operator', () => { - expect(testedComponent.operator).toBeUndefined(); - testedComponent.changeOperator('toto'); - expect(testedComponent.operator).toBe('toto'); - }); - - it('raises the add criterion event when clicked', () => { - testedComponent.id = 1; - const operator = '='; - testedComponent.operator = operator; - const value = 'test'; - testedComponent.field = new FormControl(value); - const expectedCriterion = { id: testedComponent.id, type: 'field', operator, value } as FieldCriterion; - testedComponent.addCriterion.subscribe((event: FieldCriterion) => expect(event).toEqual(expectedCriterion)); - testedComponent.emitAdd(); - }); - - it('raises the delete criterion event when clicked', () => { - testedComponent.id = 1; - testedComponent.deleteCriterion.subscribe((event: number) => expect(event).toEqual(1)); - testedComponent.emitDelete(); - }); -}); diff --git a/client/src/app/instance/search/components/criteria/search-type/help-like.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/help-like.component.spec.ts deleted file mode 100644 index 62a5a61175432e293174d02ce10b91c589b5a62f..0000000000000000000000000000000000000000 --- a/client/src/app/instance/search/components/criteria/search-type/help-like.component.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { HelpLikeComponent } from './help-like.component'; - -describe('[Search][Criteria][SearchType] Component: HelpLikeComponent', () => { - let component: HelpLikeComponent; - let fixture: ComponentFixture<HelpLikeComponent>; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [HelpLikeComponent] - }); - fixture = TestBed.createComponent(HelpLikeComponent); - component = fixture.componentInstance; - }); - - it('should create the component', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/client/src/app/instance/search/components/criteria/search-type/json.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/json.component.spec.ts deleted file mode 100644 index 29767ff55c53ba7454cfa1e90a38f401fc4ba24b..0000000000000000000000000000000000000000 --- a/client/src/app/instance/search/components/criteria/search-type/json.component.spec.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Component, ViewChild } from '@angular/core'; -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; - -import { JsonComponent } from './json.component'; -import { JsonCriterion } from '../../../store/model'; - -describe('[Search][Criteria][SearchType] Component: JsonComponent', () => { - @Component({ - selector: `app-host`, - template: `<app-json-criteria [criterion]='criterion'></app-json-criteria>` - }) - class TestHostComponent { - @ViewChild(JsonComponent, { static: false }) - public testedComponent: JsonComponent; - public criterion: JsonCriterion = undefined; - } - - let testHostComponent: TestHostComponent; - let testHostFixture: ComponentFixture<TestHostComponent>; - let testedComponent: JsonComponent; - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [JsonComponent, TestHostComponent], - imports: [FormsModule, ReactiveFormsModule] - }); - testHostFixture = TestBed.createComponent(TestHostComponent); - testHostComponent = testHostFixture.componentInstance; - testHostFixture.detectChanges(); - testedComponent = testHostComponent.testedComponent; - })); - - it('should create the component', () => { - expect(testedComponent).toBeTruthy(); - }); - - it('#getDefault() should enable and not fill form as criterion not defined in host component', () => { - expect(testedComponent.jsonForm.controls.path.value).toBeNull(); - expect(testedComponent.jsonForm.controls.operator.value).toBeNull(); - expect(testedComponent.jsonForm.controls.value.value).toBeNull(); - expect(testedComponent.jsonForm.enabled).toBeTruthy(); - }); - - it('#getDefault() should fill and disable form if criterion is defined', () => { - const path = 'path'; - const operator = '='; - const value = 'test'; - const criterion = { id: 1, type: 'json', path, operator, value } as JsonCriterion; - testedComponent.getDefault(criterion); - expect(testedComponent.jsonForm.controls.path.value).toEqual(path); - expect(testedComponent.jsonForm.controls.operator.value).toEqual(operator); - expect(testedComponent.jsonForm.controls.value.value).toEqual(value); - expect(testedComponent.jsonForm.disabled).toBeTruthy(); - }); - - it('raises the add criterion event when clicked', () => { - testedComponent.id = 1; - const path = 'path'; - const operator = '='; - const value = 'test'; - testedComponent.jsonForm.controls.path.setValue(path); - testedComponent.jsonForm.controls.operator.setValue(operator); - testedComponent.jsonForm.controls.value.setValue(value); - const expectedCriterion = { id: testedComponent.id, type: 'json', path, operator, value } as JsonCriterion; - testedComponent.addCriterion.subscribe((event: JsonCriterion) => expect(event).toEqual(expectedCriterion)); - testedComponent.emitAdd(); - }); - - it('raises the delete criterion event when clicked', () => { - testedComponent.id = 1; - testedComponent.deleteCriterion.subscribe((event: number) => expect(event).toEqual(1)); - testedComponent.emitDelete(); - }); -}); diff --git a/client/src/app/instance/search/components/criteria/search-type/list.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/list.component.spec.ts deleted file mode 100644 index d08f64b79485959e4474efe49f989faad0b23ca3..0000000000000000000000000000000000000000 --- a/client/src/app/instance/search/components/criteria/search-type/list.component.spec.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { Component, ViewChild } from '@angular/core'; -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { FormsModule, ReactiveFormsModule, FormControl } from '@angular/forms'; - -import { ListComponent } from './list.component'; -import { ListCriterion } from '../../../store/model'; - -describe('[Search][Criteria][SearchType] Component: ListComponent', () => { - @Component({ - selector: `app-host`, - template: `<app-list [criterion]='criterion'></app-list>` - }) - class TestHostComponent { - @ViewChild(ListComponent, { static: false }) - public testedComponent: ListComponent; - public criterion: ListComponent = undefined; - } - - let testHostComponent: TestHostComponent; - let testHostFixture: ComponentFixture<TestHostComponent>; - let testedComponent: ListComponent; - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [ - ListComponent, - TestHostComponent - ], - imports: [FormsModule, ReactiveFormsModule] - }); - testHostFixture = TestBed.createComponent(TestHostComponent); - testHostComponent = testHostFixture.componentInstance; - testHostFixture.detectChanges(); - testedComponent = testHostComponent.testedComponent; - })); - - it('should create the component', () => { - expect(testedComponent).toBeTruthy(); - }); - - it('#getDefault() should enable and not fill form as criterion not defined in host component', () => { - expect(testedComponent.list.value).toBeNull(); - expect(testedComponent.list.enabled).toBeTruthy(); - }); - - it('#getDefault() should fill and disable form if criterion is defined', () => { - const values = ['1', '2']; - const criterion = { id: 1, type: 'list', values } as ListCriterion; - const expectedListValues = values.join('\n'); - testedComponent.getDefault(criterion); - expect(testedComponent.list.value).toBe(expectedListValues); - expect(testedComponent.list.disabled).toBeTruthy(); - }); - - it('#getPlaceholder() should fill the placeholder if defined', () => { - const placeholder = 'placeholder'; - testedComponent.placeholder = placeholder; - expect(testedComponent.getPlaceholder()).toBe(placeholder); - }); - - it('#getPlaceholder() should not fill the placeholder if not defined', () => { - expect(testedComponent.getPlaceholder()).toBe(''); - }); - - it('raises the add criterion event when clicked', () => { - testedComponent.id = 1; - const values = '1\n2'; - testedComponent.list = new FormControl(values); - const expectedCriterion = { id: testedComponent.id, type: 'list', values: ['1', '2'] } as ListCriterion; - testedComponent.addCriterion.subscribe((event: ListCriterion) => expect(event).toEqual(expectedCriterion)); - testedComponent.emitAdd(); - }); -}); diff --git a/client/src/app/instance/search/components/criteria/search-type/operator.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/operator.component.spec.ts deleted file mode 100644 index cec4db0981a0806e7dd6112bea0e1c8e75cc3b7b..0000000000000000000000000000000000000000 --- a/client/src/app/instance/search/components/criteria/search-type/operator.component.spec.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { Component, ViewChild } from '@angular/core'; -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; - -import { OperatorComponent } from './operator.component'; - -describe('[Search][Criteria][SearchType] Component: OperatorComponent', () => { - @Component({ - selector: `app-host`, - template: ` - <app-operator - [operator]='operator' - [searchType]='searchType' - [advancedForm]='advancedForm' - [disabled]='disabled'> - </app-operator>` - }) - class TestHostComponent { - @ViewChild(OperatorComponent, { static: false }) - testedComponent: OperatorComponent; - operator = 'eq'; - searchType: string = undefined; - advancedForm: boolean = undefined; - disabled: boolean = undefined; - } - - let testHostComponent: TestHostComponent; - let testHostFixture: ComponentFixture<TestHostComponent>; - let testedComponent: OperatorComponent; - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [OperatorComponent, TestHostComponent], - imports: [FormsModule, ReactiveFormsModule] - }); - testHostFixture = TestBed.createComponent(TestHostComponent); - testHostComponent = testHostFixture.componentInstance; - testHostFixture.detectChanges(); - testedComponent = testHostComponent.testedComponent; - })); - - it('should create the component', () => { - expect(testedComponent).toBeTruthy(); - }); - - it('#getLabel() should return the correct operator form label', () => { - expect(testedComponent.getLabel('eq')).toBe('='); - expect(testedComponent.getLabel('neq')).toBe('≠'); - expect(testedComponent.getLabel('gt')).toBe('>'); - expect(testedComponent.getLabel('gte')).toBe('>='); - expect(testedComponent.getLabel('lt')).toBe('<'); - expect(testedComponent.getLabel('lte')).toBe('<='); - expect(testedComponent.getLabel('lk')).toBe('like'); - expect(testedComponent.getLabel('nlk')).toBe('not like'); - }); - - it('raises the changeOperator event when the value change', () => { - testedComponent.changeOperator.subscribe((event: string) => expect(event).toEqual('eq')); - testedComponent.emitChange('eq'); - }); - - it('should display the select box when it\'s an enabled advanced form', () => { - testHostComponent.advancedForm = true; - testHostComponent.disabled = false; - testHostFixture.detectChanges(); - const template = testHostFixture.nativeElement; - expect(template.querySelector('select')).toBeTruthy(); - expect(template.querySelector('.readonly')).toBeFalsy(); - }); - - it('should display the readonly field when it\'s not an advanced form or not enabled advanced form', () => { - testHostComponent.advancedForm = true; - testHostComponent.disabled = true; - testHostFixture.detectChanges(); - const template = testHostFixture.nativeElement; - expect(template.querySelector('select')).toBeFalsy(); - expect(template.querySelector('.readonly')).toBeTruthy(); - testHostComponent.advancedForm = true; - testHostFixture.detectChanges(); - expect(template.querySelector('select')).toBeFalsy(); - expect(template.querySelector('.readonly')).toBeTruthy(); - }); -}); diff --git a/client/src/app/instance/search/components/criteria/search-type/radio.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/radio.component.spec.ts deleted file mode 100644 index e6b5658f4821e904e6942dd10bbc921450524d73..0000000000000000000000000000000000000000 --- a/client/src/app/instance/search/components/criteria/search-type/radio.component.spec.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { Component, ViewChild } from '@angular/core'; -import { FormsModule, ReactiveFormsModule, FormControl } from '@angular/forms'; - -import { RadioComponent } from './radio.component'; -import { FieldCriterion } from '../../../store/model'; - -describe('[Search][Criteria][SearchType] Component: RadioComponent', () => { - @Component({ - selector: `app-host`, - template: `<app-radio [criterion]='criterion'></app-radio>` - }) - class TestHostComponent { - @ViewChild(RadioComponent, { static: false }) - public testedComponent: RadioComponent; - public criterion: FieldCriterion = undefined; - } - - let testHostComponent: TestHostComponent; - let testHostFixture: ComponentFixture<TestHostComponent>; - let testedComponent: RadioComponent; - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [RadioComponent, TestHostComponent], - imports: [FormsModule, ReactiveFormsModule] - }); - testHostFixture = TestBed.createComponent(TestHostComponent); - testHostComponent = testHostFixture.componentInstance; - testHostFixture.detectChanges(); - testedComponent = testHostComponent.testedComponent; - })); - - it('should create the component', () => { - expect(testedComponent).toBeTruthy(); - }); - - it('#getDefault() should enable and not fill form as criterion not defined in host component', () => { - expect(testedComponent.radio.value).toBeNull(); - expect(testedComponent.radio.enabled).toBeTruthy(); - }); - - it('#getDefault() should fill and disable form if criterion is defined', () => { - const operator = '='; - const value = 'test'; - const criterion = { id: 1, type: 'field', operator, value } as FieldCriterion; - testedComponent.getDefault(criterion); - expect(testedComponent.radio.value).toEqual(value); - expect(testedComponent.radio.disabled).toBeTruthy(); - }); - - it('raises the add criterion event when clicked', () => { - testedComponent.id = 1; - const operator = '='; - testedComponent.operator = operator; - const value = 'three'; - testedComponent.radio = new FormControl(value); - testedComponent.options = [ - { label: 'One', value: 'one', display: 1 }, - { label: 'Two', value: 'two', display: 2 }, - { label: 'Three', value: 'three', display: 3 } - ]; - const expectedCriterion = { id: testedComponent.id, type: 'field', operator, value } as FieldCriterion; - testedComponent.addCriterion.subscribe((event: FieldCriterion) => expect(event).toEqual(expectedCriterion)); - testedComponent.emitAdd(); - }); - - it('raises the delete criterion event when clicked', () => { - testedComponent.id = 1; - testedComponent.deleteCriterion.subscribe((event: number) => expect(event).toEqual(1)); - testedComponent.emitDelete(); - }); -}); diff --git a/client/src/app/instance/search/components/criteria/search-type/select-multiple.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/select-multiple.component.spec.ts deleted file mode 100644 index e6f8ccfd504802a0a1b8031e59e4dc4a86326dc1..0000000000000000000000000000000000000000 --- a/client/src/app/instance/search/components/criteria/search-type/select-multiple.component.spec.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { Component, ViewChild } from '@angular/core'; -import { FormsModule, ReactiveFormsModule, FormControl } from '@angular/forms'; - -import { NgSelectModule } from '@ng-select/ng-select'; - -import { SelectMultipleComponent } from './select-multiple.component'; -import { SelectMultipleCriterion } from '../../../store/model'; - -describe('[Search][Criteria][SearchType] Component: SelectMultipleComponent', () => { - @Component({ - selector: `app-host`, - template: `<app-select-multiple [criterion]='criterion'></app-select-multiple>` - }) - class TestHostComponent { - @ViewChild(SelectMultipleComponent, { static: false }) - public testedComponent: SelectMultipleComponent; - public criterion: SelectMultipleCriterion = undefined; - } - - let testHostComponent: TestHostComponent; - let testHostFixture: ComponentFixture<TestHostComponent>; - let testedComponent: SelectMultipleComponent; - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [SelectMultipleComponent, TestHostComponent], - imports: [NgSelectModule, FormsModule, ReactiveFormsModule] - }); - testHostFixture = TestBed.createComponent(TestHostComponent); - testHostComponent = testHostFixture.componentInstance; - testHostFixture.detectChanges(); - testedComponent = testHostComponent.testedComponent; - })); - - it('should create the component', () => { - expect(testedComponent).toBeTruthy(); - }); - - it('#getDefault() should enable and not fill form as criterion not defined in host component', () => { - expect(testedComponent.ms.value).toBeNull(); - expect(testedComponent.ms.enabled).toBeTruthy(); - }); - - it('#getDefault() should fill and disable form if criterion is defined', () => { - const options = [ - { label: 'One', value: 'one', display: 1 }, - { label: 'Two', value: 'two', display: 2 }, - { label: 'Three', value: 'three', display: 3 } - ]; - const criterion = { id: 1, type: 'multiple', options} as SelectMultipleCriterion; - testedComponent.getDefault(criterion); - expect(testedComponent.ms.value).toEqual(['one', 'two', 'three']); - expect(testedComponent.ms.disabled).toBeTruthy(); - }); - - it('raises the add criterion event when clicked', () => { - testedComponent.id = 1; - testedComponent.options = [ - { label: 'One', value: 'one', display: 1 }, - { label: 'Two', value: 'two', display: 2 }, - { label: 'Three', value: 'three', display: 3 } - ]; - const value = ['three']; - testedComponent.ms = new FormControl(value); - const expectedValue = [ - { label: 'Three', value: 'three', display: 3 } - ]; - const expectedCriterion = { id: testedComponent.id, type: 'multiple', options: expectedValue } as SelectMultipleCriterion; - testedComponent.addCriterion.subscribe((event: SelectMultipleCriterion) => expect(event).toEqual(expectedCriterion)); - testedComponent.emitAdd(); - }); - - it('raises the delete criterion event when clicked', () => { - testedComponent.id = 1; - testedComponent.deleteCriterion.subscribe((event: number) => expect(event).toEqual(1)); - testedComponent.emitDelete(); - }); -}); diff --git a/client/src/app/instance/search/components/criteria/search-type/select.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/select.component.spec.ts deleted file mode 100644 index 05f8edff0f509d817f66706bf62fe2fa7ae28bc7..0000000000000000000000000000000000000000 --- a/client/src/app/instance/search/components/criteria/search-type/select.component.spec.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core'; -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { FormsModule, ReactiveFormsModule, FormControl } from '@angular/forms'; - -import { NgSelectModule } from '@ng-select/ng-select'; - -import { SelectComponent } from './select.component'; -import { FieldCriterion } from '../../../store/model'; - -describe('[Search][Criteria][SearchType] Component: SelectComponent', () => { - @Component({ - selector: `app-host`, - template: `<app-select [criterion]='criterion'></app-select>` - }) - class TestHostComponent { - @ViewChild(SelectComponent, { static: false }) - public testedComponent: SelectComponent; - public criterion: FieldCriterion = undefined; - } - - @Component({ selector: 'app-operator', template: '' }) - class OperatorStubComponent { - @Input() operator: string; - @Input() searchType: string; - @Input() advancedForm: boolean; - @Input() disabled: boolean; - @Output() changeOperator: EventEmitter<string> = new EventEmitter(); - } - - @Component({ selector: 'app-help-like', template: '' }) - class HelpLikeStubComponent { } - - let testHostComponent: TestHostComponent; - let testHostFixture: ComponentFixture<TestHostComponent>; - let testedComponent: SelectComponent; - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [ - SelectComponent, - TestHostComponent, - OperatorStubComponent, - HelpLikeStubComponent - ], - imports: [NgSelectModule, FormsModule, ReactiveFormsModule] - }); - testHostFixture = TestBed.createComponent(TestHostComponent); - testHostComponent = testHostFixture.componentInstance; - testHostFixture.detectChanges(); - testedComponent = testHostComponent.testedComponent; - })); - - it('should create the component', () => { - expect(testedComponent).toBeTruthy(); - }); - - it('#getDefault() should enable and not fill form as criterion not defined in host component', () => { - expect(testedComponent.se.value).toBeNull(); - expect(testedComponent.se.enabled).toBeTruthy(); - }); - - it('#getDefault() should fill and disable form if criterion is defined', () => { - const operator = '='; - const value = 'test'; - const criterion = { id: 1, type: 'field', operator, value } as FieldCriterion; - testedComponent.getDefault(criterion); - expect(testedComponent.se.value).toEqual(value); - expect(testedComponent.se.disabled).toBeTruthy(); - }); - - it('#changeOperator() should change the operator', () => { - expect(testedComponent.operator).toBeUndefined(); - testedComponent.changeOperator('toto'); - expect(testedComponent.operator).toBe('toto'); - }); - - it('raises the add criterion event when clicked', () => { - testedComponent.id = 1; - const operator = '='; - testedComponent.operator = operator; - const value = 'three'; - testedComponent.se = new FormControl(value); - testedComponent.options = [ - { label: 'One', value: 'one', display: 1 }, - { label: 'Two', value: 'two', display: 2 }, - { label: 'Three', value: 'three', display: 3 } - ]; - const expectedCriterion = { id: testedComponent.id, type: 'field', operator, value } as FieldCriterion; - testedComponent.addCriterion.subscribe((event: FieldCriterion) => expect(event).toEqual(expectedCriterion)); - testedComponent.emitAdd(); - }); - - it('raises the delete criterion event when clicked', () => { - testedComponent.id = 1; - testedComponent.deleteCriterion.subscribe((event: number) => expect(event).toEqual(1)); - testedComponent.emitDelete(); - }); -}); diff --git a/client/src/app/instance/search/components/criteria/search-type/time.component.spec.ts b/client/src/app/instance/search/components/criteria/search-type/time.component.spec.ts deleted file mode 100644 index f93f526c832f091e3df4eed6eb2300a585751186..0000000000000000000000000000000000000000 --- a/client/src/app/instance/search/components/criteria/search-type/time.component.spec.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core'; -import { FormsModule, ReactiveFormsModule, FormControl } from '@angular/forms'; - -import { NgSelectModule } from '@ng-select/ng-select'; - -import { TimeComponent } from './time.component'; -import { FieldCriterion } from '../../../store/model'; - -describe('[Search][Criteria][SearchType] Component: TimeComponent', () => { - @Component({ - selector: `app-host`, - template: `<app-time [criterion]='criterion'></app-time>` - }) - class TestHostComponent { - @ViewChild(TimeComponent, { static: false }) - public testedComponent: TimeComponent; - public criterion: FieldCriterion = undefined; - } - - @Component({ selector: 'app-operator', template: '' }) - class OperatorStubComponent { - @Input() operator: string; - @Input() searchType: string; - @Input() advancedForm: boolean; - @Input() disabled: boolean; - @Output() changeOperator: EventEmitter<string> = new EventEmitter(); - } - - let testHostComponent: TestHostComponent; - let testHostFixture: ComponentFixture<TestHostComponent>; - let testedComponent: TimeComponent; - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [ - TimeComponent, - TestHostComponent, - OperatorStubComponent - ], - imports: [NgSelectModule, FormsModule, ReactiveFormsModule] - }); - testHostFixture = TestBed.createComponent(TestHostComponent); - testHostComponent = testHostFixture.componentInstance; - testHostFixture.detectChanges(); - testedComponent = testHostComponent.testedComponent; - })); - - it('should create the component', () => { - expect(testedComponent).toBeTruthy(); - }); - - it('#getDefault() should enable and not fill form as criterion not defined in host component', () => { - expect(testedComponent.hh.value).toBeNull(); - expect(testedComponent.hh.enabled).toBeTruthy(); - expect(testedComponent.mm.value).toBeNull(); - expect(testedComponent.mm.enabled).toBeTruthy(); - }); - - it('#getDefault() should fill and disable form if criterion is defined', () => { - const criterion = { id: 1, type: 'field', operator: 'eq', value: '15:47' } as FieldCriterion; - const expectedHour = '15'; - const expectedMinute = '47'; - testedComponent.getDefault(criterion); - expect(testedComponent.hh.value).toEqual(expectedHour); - expect(testedComponent.hh.disabled).toBeTruthy(); - expect(testedComponent.mm.value).toEqual(expectedMinute); - expect(testedComponent.mm.disabled).toBeTruthy(); - }); - - it('#initTime(t) should return an array of string with 2 digits from 0 to t', () => { - const n = 10; - expect(testedComponent.initTime(n).length).toEqual(n); - expect(testedComponent.initTime(n)[5]).toEqual('05'); - }); - - it('#changeOperator() should change the operator', () => { - expect(testedComponent.operator).toBeUndefined(); - testedComponent.changeOperator('toto'); - expect(testedComponent.operator).toBe('toto'); - }); - - it('raises the add criterion event when clicked', () => { - testedComponent.id = 1; - const operator = 'eq'; - testedComponent.operator = operator; - testedComponent.hh = new FormControl('15'); - testedComponent.mm = new FormControl('47'); - const expectedCriterion = { id: testedComponent.id, type: 'field', operator, value: '15:47' } as FieldCriterion; - testedComponent.addCriterion.subscribe((event: FieldCriterion) => expect(event).toEqual(expectedCriterion)); - testedComponent.emitAdd(); - }); - - it('raises the delete criterion event when clicked', () => { - testedComponent.id = 1; - testedComponent.deleteCriterion.subscribe((event: number) => expect(event).toEqual(1)); - testedComponent.emitDelete(); - }); -}); diff --git a/client/src/app/instance/search/components/result/datatable-tab.component.html b/client/src/app/instance/search/components/result/datatable-tab.component.html index 670acae1dd47d1c6dc836cbd4f78e0cec4592d37..26dd36d1be6a174d083b150ebb580194511e8e9c 100644 --- a/client/src/app/instance/search/components/result/datatable-tab.component.html +++ b/client/src/app/instance/search/components/result/datatable-tab.component.html @@ -1,5 +1,5 @@ -<accordion *ngIf="getDataset().config.datatable.datatable_enabled" [isAnimated]="true"> - <accordion-group #ag [isOpen]="getDataset().config.datatable.datatable_opened" [panelClass]="'custom-accordion'" class="my-2"> +<accordion *ngIf="(datasetList | datasetByName:datasetSelected).config.datatable.datatable_enabled" [isAnimated]="true"> + <accordion-group #ag [isOpen]="(datasetList | datasetByName:datasetSelected).config.datatable.datatable_opened" [panelClass]="'custom-accordion'" class="my-2"> <button class="btn btn-link btn-block clearfix" accordion-heading> <span class="pull-left float-left"> Display result details @@ -9,7 +9,8 @@ </span> </button> <app-datatable - [dataset]="getDataset()" + [dataset]="datasetList | datasetByName:datasetSelected" + [instanceSelected]="instanceSelected" [attributeList]="attributeList" [outputList]="outputList" [dataLength]="dataLength" diff --git a/client/src/app/instance/search/components/result/datatable-tab.component.ts b/client/src/app/instance/search/components/result/datatable-tab.component.ts index 0b7b2258095c4ff700fc940781e0ed6a4dd5d6af..c24e1979d36d8710d5efe016bd9ba69a5e4c35d2 100644 --- a/client/src/app/instance/search/components/result/datatable-tab.component.ts +++ b/client/src/app/instance/search/components/result/datatable-tab.component.ts @@ -23,6 +23,7 @@ import { Pagination } from 'src/app/instance/store/models'; */ export class DatatableTabComponent { @Input() datasetSelected: string; + @Input() instanceSelected: string; @Input() datasetList: Dataset[]; @Input() attributeList: Attribute[]; @Input() outputList: number[]; @@ -34,13 +35,4 @@ export class DatatableTabComponent { @Output() retrieveData: EventEmitter<Pagination> = new EventEmitter(); @Output() addSelectedData: EventEmitter<number | string> = new EventEmitter(); @Output() deleteSelectedData: EventEmitter<number | string> = new EventEmitter(); - - /** - * Returns selected dataset for the search. - * - * @return Dataset - */ - getDataset(): Dataset { - return this.datasetList.find(dataset => dataset.name === this.datasetSelected); - } } diff --git a/client/src/app/instance/search/components/result/download.component.ts b/client/src/app/instance/search/components/result/download.component.ts index aedba8081c7c8c2412e7a397ddf8a12c0a0cc8c0..729dfe595cc1f1e8d5484bf2d91b84311d35ca44 100644 --- a/client/src/app/instance/search/components/result/download.component.ts +++ b/client/src/app/instance/search/components/result/download.component.ts @@ -13,6 +13,7 @@ import { Criterion, criterionToString } from '../../../store/models'; import { Dataset } from 'src/app/metamodel/models'; import { getHost as host } from 'src/app/shared/utils'; // import { ConeSearch } from '../../../shared/cone-search/store/model'; +import { AppConfigService } from 'src/app/app-config.service'; @Component({ selector: 'app-download', @@ -36,6 +37,8 @@ export class DownloadComponent { @Input() sampRegistered: boolean; @Output() broadcast: EventEmitter<string> = new EventEmitter(); + constructor(private appConfig: AppConfigService) { } + /** * Returns dataset label. * @@ -76,7 +79,7 @@ export class DownloadComponent { * @return string */ getUrl(format: string): string { - let query: string = host() + '/search/' + this.datasetSelected + '?a=' + this.outputList.join(';'); + let query: string = host(this.appConfig.apiUrl) + '/search/' + this.datasetSelected + '?a=' + this.outputList.join(';'); if (this.criteriaList.length > 0) { query += '&c=' + this.criteriaList.map(criterion => criterionToString(criterion)).join(';'); } @@ -88,7 +91,7 @@ export class DownloadComponent { } getUrlArchive(): string { - let query: string = host() + '/archive/' + this.datasetSelected + '?a=' + this.outputList.join(';'); + let query: string = host(this.appConfig.apiUrl) + '/archive/' + this.datasetSelected + '?a=' + this.outputList.join(';'); if (this.criteriaList.length > 0) { query += '&c=' + this.criteriaList.map(criterion => criterionToString(criterion)).join(';'); } diff --git a/client/src/app/instance/search/components/result/reminder.component.html b/client/src/app/instance/search/components/result/reminder.component.html index d76e57d79282e0ffe5efefd8ab799a41ee6036e9..38372acef640c24c36328c786925527dd5ecfd8f 100644 --- a/client/src/app/instance/search/components/result/reminder.component.html +++ b/client/src/app/instance/search/components/result/reminder.component.html @@ -24,14 +24,14 @@ </div> <div *ngIf="nbCriteria() > 0" class="row"> - <!-- <div *ngIf="isConeSearchAdded" class="col-12 col-md-6 col-xl-4 pb-3"> + <div *ngIf="coneSearch" class="col-12 col-md-6 col-xl-4 pb-3"> <span class="title">Cone search</span> <ul class="list-unstyled pl-3"> <li>RA = {{ coneSearch.ra }}°</li> <li>DEC = {{ coneSearch.dec }}°</li> <li>radius = {{ coneSearch.radius }} arcsecond</li> </ul> - </div> --> + </div> <ng-container *ngFor="let family of criteriaFamilyList"> <ng-container *ngIf="criteriaByFamily(family.id).length > 0"> diff --git a/client/src/app/instance/search/components/result/reminder.component.ts b/client/src/app/instance/search/components/result/reminder.component.ts index 9961ece07a9ed7101ff1de5aa915187a94884628..8e3d88ea4ffc5584ba30b77ba2168c0a1e993cd0 100644 --- a/client/src/app/instance/search/components/result/reminder.component.ts +++ b/client/src/app/instance/search/components/result/reminder.component.ts @@ -28,9 +28,8 @@ export class ReminderComponent { @Input() criteriaFamilyList: CriteriaFamily[]; @Input() outputFamilyList: OutputFamily[]; @Input() outputCategoryList: OutputCategory[]; - // @Input() isConeSearchAdded: boolean; - // @Input() coneSearch: ConeSearch; @Input() criteriaList: Criterion[]; + @Input() coneSearch: ConeSearch; @Input() outputList: number[]; isSummaryActivated(): boolean { @@ -49,9 +48,9 @@ export class ReminderComponent { * @return number */ nbCriteria(): number { - // if (this.isConeSearchAdded) { - // return this.criteriaList.length + 1; - // } + if (this.coneSearch) { + return this.criteriaList.length + 1; + } return this.criteriaList.length; } diff --git a/client/src/app/instance/search/components/result/url-display.component.ts b/client/src/app/instance/search/components/result/url-display.component.ts index 469bc9b420a9710c4a959a99c088dbf01d572822..1ed461ba64d2d44b402b2cf8eaedf98d59e5024d 100644 --- a/client/src/app/instance/search/components/result/url-display.component.ts +++ b/client/src/app/instance/search/components/result/url-display.component.ts @@ -14,6 +14,7 @@ import { ToastrService } from 'ngx-toastr'; import { Criterion, ConeSearch, criterionToString } from 'src/app/instance/store/models'; import { Dataset } from 'src/app/metamodel/models'; import { getHost as host } from 'src/app/shared/utils'; +import { AppConfigService } from 'src/app/app-config.service'; @Component({ selector: 'app-url-display', @@ -32,7 +33,7 @@ export class UrlDisplayComponent { @Input() criteriaList: Criterion[]; @Input() outputList: number[]; - constructor(private toastr: ToastrService) { } + constructor(private toastr: ToastrService, private appConfig: AppConfigService) { } /** * Checks if URL display is enabled. @@ -55,7 +56,7 @@ export class UrlDisplayComponent { * @return string */ getUrl(): string { - let query: string = host() + '/search/' + this.datasetSelected + '?a=' + this.outputList.join(';'); + let query: string = host(this.appConfig.apiUrl) + '/search/' + this.datasetSelected + '?a=' + this.outputList.join(';'); if (this.criteriaList.length > 0) { query += '&c=' + this.criteriaList.map(criterion => criterionToString(criterion)).join(';'); } diff --git a/client/src/app/instance/search/components/summary.component.html b/client/src/app/instance/search/components/summary.component.html index 0fc5455ed4c10d7ee751b4823bb686211b37d056..86c376fb03367425c7f6a4776396d78ee31c6422 100644 --- a/client/src/app/instance/search/components/summary.component.html +++ b/client/src/app/instance/search/components/summary.component.html @@ -15,14 +15,14 @@ <p *ngIf="noCriteria()" class="pl-5 font-weight-bold"> No selected criteria </p> - <!-- <span *ngIf="isConeSearchAdded" class="pl-5"> + <span *ngIf="coneSearch" class="pl-5"> Cone search: <ul class="ml-3 pl-5 list-unstyled"> <li>RA = {{ coneSearch.ra }}°</li> <li>DEC = {{ coneSearch.dec }}°</li> <li>radius = {{ coneSearch.radius }} arcsecond</li> </ul> - </span> --> + </span> <ul *ngIf="criteriaList.length > 0" class="pl-5 list-unstyled"> <li *ngFor="let criterion of criteriaList"> {{ getAttribute(criterion.id).form_label }} {{ printCriterion(criterion) }} diff --git a/client/src/app/instance/search/components/summary.component.ts b/client/src/app/instance/search/components/summary.component.ts index dfa03f44f4c86f23bb6fb8ab10ad3e5a8600b462..3e76c6275872536dca5f00257e2969ea611ad3b3 100644 --- a/client/src/app/instance/search/components/summary.component.ts +++ b/client/src/app/instance/search/components/summary.component.ts @@ -9,9 +9,8 @@ import { ChangeDetectionStrategy, Component, Input, ViewEncapsulation } from '@angular/core'; -import { Criterion, SearchQueryParams, getPrettyCriterion } from '../../store/models'; +import { Criterion, ConeSearch, SearchQueryParams, getPrettyCriterion } from '../../store/models'; import { Attribute, Dataset, CriteriaFamily, OutputFamily, OutputCategory } from 'src/app/metamodel/models'; -// import { ConeSearch } from '../../shared/cone-search/store/model'; @Component({ selector: 'app-summary', @@ -27,8 +26,6 @@ export class SummaryComponent { @Input() currentStep: string; @Input() datasetSelected: string; @Input() datasetList: Dataset[]; - // @Input() isConeSearchAdded: boolean; - // @Input() coneSearch: ConeSearch; @Input() attributeList: Attribute[]; @Input() criteriaFamilyList: CriteriaFamily[]; @Input() outputFamilyList: OutputFamily[]; @@ -36,6 +33,7 @@ export class SummaryComponent { @Input() criteriaList: Criterion[]; @Input() outputList: number[]; @Input() queryParams: SearchQueryParams; + @Input() coneSearch: ConeSearch; accordionFamilyIsOpen = true; @@ -54,8 +52,7 @@ export class SummaryComponent { * @return boolean */ noCriteria(): boolean { - //if (this.isConeSearchAdded || this.criteriaList.length > 0) { - if (this.criteriaList.length > 0) { + if (this.coneSearch || this.criteriaList.length > 0) { return false } return true; diff --git a/client/src/app/instance/search/containers/abstract-search.component.ts b/client/src/app/instance/search/containers/abstract-search.component.ts index a6ebdbbebc3bb3175648b19ddadfb4adc037c880..6774ae781e2266fbb6a6eb2503c477954393eef0 100644 --- a/client/src/app/instance/search/containers/abstract-search.component.ts +++ b/client/src/app/instance/search/containers/abstract-search.component.ts @@ -3,7 +3,7 @@ import { Directive, OnInit, OnDestroy } from '@angular/core'; import { Store } from '@ngrx/store'; import { Observable, Subscription } from 'rxjs'; -import { Criterion, SearchQueryParams } from '../../store/models'; +import { ConeSearch, Criterion, SearchQueryParams } from '../../store/models'; import { Dataset, Attribute, CriteriaFamily, OutputFamily, OutputCategory } from 'src/app/metamodel/models'; import * as instanceSelector from 'src/app/metamodel/selectors/instance.selector'; @@ -14,6 +14,7 @@ import * as outputFamilySelector from 'src/app/metamodel/selectors/output-family import * as outputCategorySelector from 'src/app/metamodel/selectors/output-category.selector'; import * as searchActions from '../../store/actions/search.actions'; import * as searchSelector from '../../store/selectors/search.selector'; +import * as coneSearchSelector from '../../store/selectors/cone-search.selector'; @Directive() export abstract class AbstractSearchComponent implements OnInit, OnDestroy { @@ -39,6 +40,7 @@ export abstract class AbstractSearchComponent implements OnInit, OnDestroy { public criteriaList: Observable<Criterion[]>; public outputList: Observable<number[]>; public queryParams: Observable<SearchQueryParams>; + public coneSearch: Observable<ConeSearch>; private attributeListIsLoadedSubscription: Subscription; @@ -65,6 +67,7 @@ export abstract class AbstractSearchComponent implements OnInit, OnDestroy { this.criteriaList = this.store.select(searchSelector.selectCriteriaList); this.outputList = this.store.select(searchSelector.selectOutputList); this.queryParams = this.store.select(searchSelector.selectQueryParams); + this.coneSearch = this.store.select(coneSearchSelector.selectConeSearch); } ngOnInit() { diff --git a/client/src/app/instance/search/containers/criteria.component.html b/client/src/app/instance/search/containers/criteria.component.html index 5fb87883773d70c4baf88ec337f5320fec6bd919..8ff2d1d903ea462d493393f6efa68700811f4265 100644 --- a/client/src/app/instance/search/containers/criteria.component.html +++ b/client/src/app/instance/search/containers/criteria.component.html @@ -7,13 +7,17 @@ && (criteriaFamilyListIsLoaded | async) && (attributeListIsLoaded | async)" class="row mt-4"> <div class="col-12 col-md-8 col-lg-9"> - <!-- <app-cone-search-tab + <app-cone-search-tab [datasetSelected]="datasetSelected | async" [datasetList]="datasetList | async" - [isConeSearchAdded]="isConeSearchAdded | async" - [isValidConeSearch]="isValidConeSearch | async" - (coneSearchAdded)="coneSearchAdded($event)"> - </app-cone-search-tab> --> + [coneSearch]="coneSearch | async" + [resolver]="resolver | async" + [resolverIsLoading]="resolverIsLoading | async" + [resolverIsLoaded]="resolverIsLoaded | async" + (addConeSearch)="addConeSearch($event)" + (deleteConeSearch)="deleteConeSearch()" + (retrieveCoordinates)="retrieveCoordinates($event)"> + </app-cone-search-tab> <app-criteria-tabs [attributeList]="attributeList | async" [criteriaFamilyList]="criteriaFamilyList | async" @@ -34,7 +38,8 @@ [outputCategoryList]="outputCategoryList | async" [criteriaList]="criteriaList | async" [outputList]="outputList | async" - [queryParams]="queryParams | async"> + [queryParams]="queryParams | async" + [coneSearch]="coneSearch | async"> </app-summary> </div> </div> @@ -47,7 +52,7 @@ </a> </div> <div class="col col-auto"> - <a routerLink="/search/output/{{ datasetSelected | async }}" [queryParams]="queryParams | async" + <a routerLink="/instance/{{ instanceSelected | async }}/search/output/{{ datasetSelected | async }}" [queryParams]="queryParams | async" class="btn btn-outline-primary"> Output <span class="fas fa-arrow-right"></span> </a> diff --git a/client/src/app/instance/search/containers/criteria.component.ts b/client/src/app/instance/search/containers/criteria.component.ts index a226c24ed0131bc8d5bfba3dc12a8f590121fd29..b03d239dc5f4ac0bc2e9a84b79b23d81a2ce6cd8 100644 --- a/client/src/app/instance/search/containers/criteria.component.ts +++ b/client/src/app/instance/search/containers/criteria.component.ts @@ -9,9 +9,14 @@ import { Component } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { Observable } from 'rxjs'; + import { AbstractSearchComponent } from './abstract-search.component'; -import { Criterion } from '../../store/models'; +import { ConeSearch, Criterion, Resolver } from '../../store/models'; import * as searchActions from '../../store/actions/search.actions'; +import * as coneSearchActions from '../../store/actions/cone-search.actions'; +import * as coneSearchSelector from '../../store/selectors/cone-search.selector'; @Component({ selector: 'app-criteria', @@ -22,6 +27,17 @@ import * as searchActions from '../../store/actions/search.actions'; * @classdesc Search criteria container. */ export class CriteriaComponent extends AbstractSearchComponent { + public resolver: Observable<Resolver>; + public resolverIsLoading: Observable<boolean>; + public resolverIsLoaded: Observable<boolean>; + + constructor(protected store: Store<{ }>) { + super(store); + this.resolver = this.store.select(coneSearchSelector.selectResolver); + this.resolverIsLoading = this.store.select(coneSearchSelector.selectResolverIsLoading); + this.resolverIsLoaded = this.store.select(coneSearchSelector.selectResolverIsLoaded); + } + ngOnInit() { Promise.resolve(null).then(() => this.store.dispatch(searchActions.changeStep({ step: 'criteria' }))); Promise.resolve(null).then(() => this.store.dispatch(searchActions.checkCriteria())); @@ -45,4 +61,16 @@ export class CriteriaComponent extends AbstractSearchComponent { deleteCriterion(idCriterion: number): void { this.store.dispatch(searchActions.deleteCriterion({ idCriterion })); } + + addConeSearch(coneSearch: ConeSearch): void { + this.store.dispatch(coneSearchActions.addConeSearch({ coneSearch })); + } + + deleteConeSearch(): void { + this.store.dispatch(coneSearchActions.deleteConeSearch()); + } + + retrieveCoordinates(name: string): void { + this.store.dispatch(coneSearchActions.retrieveCoordinates({ name })); + } } diff --git a/client/src/app/instance/search/containers/dataset.component.html b/client/src/app/instance/search/containers/dataset.component.html index b86d581556b64900a383598f8beae670d3dff20f..77107988152d7cafa0de1aee2c1d0172768d3afd 100644 --- a/client/src/app/instance/search/containers/dataset.component.html +++ b/client/src/app/instance/search/containers/dataset.component.html @@ -36,7 +36,8 @@ [outputCategoryList]="outputCategoryList | async" [criteriaList]="criteriaList | async" [outputList]="outputList | async" - [queryParams]="queryParams | async"> + [queryParams]="queryParams | async" + [coneSearch]="coneSearch | async"> </app-summary> </div> </ng-container> diff --git a/client/src/app/instance/search/containers/output.component.html b/client/src/app/instance/search/containers/output.component.html index 9b0cdec4da374ed048fea9eea3630a1628a1e605..12d7f60558376fa5c8b4a679cc8fa1a2b4eafce0 100644 --- a/client/src/app/instance/search/containers/output.component.html +++ b/client/src/app/instance/search/containers/output.component.html @@ -27,7 +27,8 @@ [outputCategoryList]="outputCategoryList | async" [criteriaList]="criteriaList | async" [outputList]="outputList | async" - [queryParams]="queryParams | async"> + [queryParams]="queryParams | async" + [coneSearch]="coneSearch | async"> </app-summary> </div> </div> diff --git a/client/src/app/instance/search/containers/result.component.html b/client/src/app/instance/search/containers/result.component.html index 9eacc2ffbff8b44a625d444390f47af89e481e08..9e4fec28e05e52c08e8baab91789b911b6bbf5f6 100644 --- a/client/src/app/instance/search/containers/result.component.html +++ b/client/src/app/instance/search/containers/result.component.html @@ -36,6 +36,7 @@ [outputFamilyList]="outputFamilyList | async" [outputCategoryList]="outputCategoryList | async" [criteriaList]="criteriaList | async" + [coneSearch]="coneSearch | async" [outputList]="outputList | async"> </app-reminder> <app-samp @@ -63,6 +64,7 @@ </app-cone-search-plot-tab> --> <app-datatable-tab [datasetSelected]="datasetSelected | async" + [instanceSelected]="instanceSelected | async" [datasetList]="datasetList | async" [attributeList]="attributeList | async" [outputList]="outputList | async" diff --git a/client/src/app/instance/shared-search/components/cone-search/cone-search.component.html b/client/src/app/instance/shared-search/components/cone-search/cone-search.component.html index c0cf4330cf393e0886bfdffecd414e6a7720b607..6e676e971e10e1ebf73318e490770eb147abdae1 100644 --- a/client/src/app/instance/shared-search/components/cone-search/cone-search.component.html +++ b/client/src/app/instance/shared-search/components/cone-search/cone-search.component.html @@ -1,47 +1,29 @@ <div class="row pb-4"> <div class="col"> <app-resolver - [resolverWip]="resolverWip | async" - [resolver]="resolver | async" - [disabled]="disabled" - (resolveName)="retrieveCoordinates($event)"> + [coneSearch]="coneSearch" + [resolver]="resolver" + [resolverIsLoading]="resolverIsLoading" + [resolverIsLoaded]="resolverIsLoaded" + (retrieveCoordinates)="retrieveCoordinates.emit($event)"> </app-resolver> </div> </div> <div class="row"> <div class="col pb-4"> - <app-ra - [coneSearch]="coneSearch | async" - [resolver]="resolver | async" - [unit]="unit" - [disabled]="disabled" - (updateConeSearch)="updateConeSearch($event)" - (deleteResolver)="deleteResolver()"> + <app-ra [form]="form" [unit]="unit" [resolver]="resolver"> </app-ra> </div> <div class="col-auto p-0 align-self-center"> - <button class="btn btn-outline-secondary" - [disabled]="disabled" - (click)="unit === 'degree' ? unit = 'hms' : unit = 'degree'" - title="Change unit"> - <span class="fas fa-sync-alt"></span> + <button class="btn btn-outline-secondary" [disabled]="coneSearch" (click)="unit === 'degree' ? unit = 'hms' : unit = 'degree'" title="Change unit"> + <span class="fas fa-sync-alt"></span> </button> </div> <div class="col"> - <app-dec - [coneSearch]="coneSearch | async" - [resolver]="resolver | async" - [unit]="unit" - [disabled]="disabled" - (updateConeSearch)="updateConeSearch($event)" - (deleteResolver)="deleteResolver()"> + <app-dec [form]="form" [unit]="unit" [resolver]="resolver"> </app-dec> </div> <div class="col-12"> - <app-radius - [coneSearch]="coneSearch | async" - [disabled]="disabled" - (updateConeSearch)="updateConeSearch($event)"> - </app-radius> + <app-radius [form]="form"></app-radius> </div> </div> diff --git a/client/src/app/instance/shared-search/components/cone-search/cone-search.component.ts b/client/src/app/instance/shared-search/components/cone-search/cone-search.component.ts index d506c3180a782ca2f64141bb863c8e39b23ca25e..ca589c83866668ad1f6fd8953bb7567d637889df 100644 --- a/client/src/app/instance/shared-search/components/cone-search/cone-search.component.ts +++ b/client/src/app/instance/shared-search/components/cone-search/cone-search.component.ts @@ -7,9 +7,11 @@ * file that was distributed with this source code. */ -import { Component, Input, Output, EventEmitter } from '@angular/core'; +import { Component, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges } from '@angular/core'; +import { FormGroup, FormControl, Validators } from '@angular/forms'; import { ConeSearch, Resolver } from 'src/app/instance/store/models'; +import { nanValidator, rangeValidator } from '../../validators'; @Component({ selector: 'app-cone-search', @@ -19,14 +21,60 @@ import { ConeSearch, Resolver } from 'src/app/instance/store/models'; * @class * @classdesc Cone search container. */ -export class ConeSearchComponent { - @Input() disabled: boolean = false; - @Input() resolverWip: boolean; - @Input() resolver: Resolver; +export class ConeSearchComponent implements OnChanges { @Input() coneSearch: ConeSearch; + @Input() resolver: Resolver; + @Input() resolverIsLoading: boolean; + @Input() resolverIsLoaded: boolean; + @Output() addConeSearch: EventEmitter<ConeSearch> = new EventEmitter(); + @Output() deleteConeSearch: EventEmitter<{ }> = new EventEmitter(); @Output() retrieveCoordinates: EventEmitter<string> = new EventEmitter(); - @Output() updateConeSearch: EventEmitter<ConeSearch> = new EventEmitter(); - @Output() deleteResolver: EventEmitter<{}> = new EventEmitter(); - unit = 'degree'; + public form = new FormGroup({ + ra: new FormControl('', [Validators.required, nanValidator, rangeValidator(0, 360, 'RA')]), + ra_hms: new FormGroup({ + h: new FormControl('', [Validators.required, nanValidator, rangeValidator(0, 24, 'Hours')]), + m: new FormControl('', [Validators.required, nanValidator, rangeValidator(0, 60, 'Minutes')]), + s: new FormControl('', [Validators.required, nanValidator, rangeValidator(0, 60, 'Seconds')]) + }), + dec: new FormControl('', [Validators.required, nanValidator, rangeValidator(-90, 90, 'DEC')]), + dec_dms: new FormGroup({ + d: new FormControl('', [Validators.required, nanValidator, rangeValidator(-90, 90, 'Degree')]), + m: new FormControl('', [Validators.required, nanValidator, rangeValidator(0, 60, 'Minutes')]), + s: new FormControl('', [Validators.required, nanValidator, rangeValidator(0, 60, 'Seconds')]) + }), + radius: new FormControl(2, [Validators.required, rangeValidator(0, 150, 'Radius')]) + }); + + public unit = 'degree'; + + ngOnChanges(changes: SimpleChanges) { + if (changes.resolver && changes.resolver.currentValue) { + this.unit = 'degree'; + } + + if (changes.coneSearch && !changes.coneSearch.currentValue) { + if (this.unit = 'degree') { + this.form.controls.ra.enable(); + this.form.controls.dec.enable(); + this.form.controls.radius.enable(); + } else { + this.form.controls.ra_hms.enable(); + this.form.controls.dec_dms.enable(); + } + } + + if (changes.coneSearch && changes.coneSearch.currentValue) { + this.form.patchValue(this.coneSearch); + this.form.disable(); + } + } + + getConeSearch(): ConeSearch { + return { + ra: this.form.controls.ra.value, + dec: this.form.controls.dec.value, + radius: this.form.controls.radius.value + }; + } } diff --git a/client/src/app/instance/shared-search/components/cone-search/dec.component.html b/client/src/app/instance/shared-search/components/cone-search/dec.component.html index 93539d69f28e7d93341ffa4e254b8440f9acc3aa..c8fe7677d7fa8cafd270f39ecdfae51c2630b970 100644 --- a/client/src/app/instance/shared-search/components/cone-search/dec.component.html +++ b/client/src/app/instance/shared-search/components/cone-search/dec.component.html @@ -1,92 +1,75 @@ -<div class="row px-3"> - <label>DEC</label> - <div class="input-group"> - <input type="text" class="form-control" [formControl]="decDegree" (input)="decChange()" autocomplete="off"> - <div class="input-group-append"> - <span class="input-group-text">°</span> - </div> - </div> -</div> - -<div class="row mt-2 px-3"> - <div class="col px-0 pr-xl-1"> +<form [formGroup]="form" novalidate> + <div class="row px-3"> + <label>DEC</label> <div class="input-group"> - <input type="text" - class="form-control" - [formControl]="decH" - (input)="decChange()" - (focusin)="changeFocus('dech', true)" - (focusout)="changeFocus('dech', false)" - (change)="setToDefaultValue()" - autocomplete="off"> + <input type="number" class="form-control" formControlName="dec" autocomplete="off"> <div class="input-group-append"> <span class="input-group-text">°</span> </div> </div> </div> - <div class="w-100 d-block d-xl-none"></div> - <div class="col mt-1 mt-xl-auto px-0 pr-xl-1"> - <div class="input-group"> - <input type="text" - class="form-control" - [formControl]="decM" - (input)="decChange()" - (focusin)="changeFocus('decm', true)" - (focusout)="changeFocus('decm', false)" - (change)="setToDefaultValue()" - autocomplete="off"> - <div class="input-group-append"> - <span class="input-group-text">'</span> + + <div formGroupName="dec_dms"> + <div class="row mt-2 px-3"> + <div class="col px-0 pr-xl-1"> + <div class="input-group"> + <input type="number" class="form-control" formControlName="d" autocomplete="off"> + <div class="input-group-append"> + <span class="input-group-text">°</span> + </div> + </div> </div> - </div> - </div> - <div class="w-100 d-block d-xl-none"></div> - <div class="col mt-1 mt-xl-auto px-0"> - <div class="input-group"> - <input type="text" - class="form-control" - [formControl]="decS" - (input)="decChange()" - (focusin)="changeFocus('decs', true)" - (focusout)="changeFocus('decs', false)" - (change)="setToDefaultValue()" - autocomplete="off"> - <div class="input-group-append"> - <span class="input-group-text">''</span> + <div class="w-100 d-block d-xl-none"></div> + <div class="col mt-1 mt-xl-auto px-0 pr-xl-1"> + <div class="input-group"> + <input type="number" class="form-control" formControlName="m" autocomplete="off"> + <div class="input-group-append"> + <span class="input-group-text">'</span> + </div> + </div> + </div> + <div class="w-100 d-block d-xl-none"></div> + <div class="col mt-1 mt-xl-auto px-0"> + <div class="input-group"> + <input type="number" class="form-control" formControlName="s" autocomplete="off"> + <div class="input-group-append"> + <span class="input-group-text">''</span> + </div> + </div> </div> </div> </div> -</div> +</form> -<div *ngIf="decDegree.invalid" class="row px-3 text-danger"> - <div *ngIf="decDegree.errors.nan"> - {{ decDegree.errors.nan.value }} +<div *ngIf="form.controls.dec.invalid" class="row px-3 text-danger"> + <div *ngIf="form.controls.dec.errors.nan"> + {{ form.controls.dec.errors.nan.value }} </div> - <div *ngIf="decDegree.errors.range" [hidden]="decDegree.errors.nan"> - {{ decDegree.errors.range.value }} + <div *ngIf="form.controls.dec.errors.range" [hidden]="form.controls.dec.errors.nan"> + {{ form.controls.dec.errors.range.value }} </div> </div> -<div *ngIf="decH.invalid" class="row px-3 text-danger"> - <div *ngIf="decH.errors.nan"> - {{ decH.errors.nan.value }} +<div *ngIf="getDecDmsForm().controls.d.invalid" class="row px-3 text-danger"> + <div *ngIf="getDecDmsForm().controls.d.errors.nan"> + {{ getDecDmsForm().controls.d.errors.nan.value }} </div> - <div *ngIf="decH.errors.range" [hidden]="decH.errors.nan"> - {{ decH.errors.range.value }} + <div *ngIf="getDecDmsForm().controls.d.errors.range" [hidden]="getDecDmsForm().controls.d.errors.nan"> + {{ getDecDmsForm().controls.d.errors.range.value }} </div> </div> -<div *ngIf="decM.invalid" class="row px-3 text-danger"> - <div *ngIf="decM.errors.nan"> - {{ decM.errors.nan.value }} +<div *ngIf="getDecDmsForm().controls.m.invalid" class="row px-3 text-danger"> + <div *ngIf="getDecDmsForm().controls.m.errors.nan"> + {{ getDecDmsForm().controls.m.errors.nan.value }} </div> - <div *ngIf="decM.errors.range" [hidden]="decM.errors.nan"> - {{ decM.errors.range.value }} + <div *ngIf="getDecDmsForm().controls.m.errors.range" [hidden]="getDecDmsForm().controls.m.errors.nan"> + {{ getDecDmsForm().controls.m.errors.range.value }} </div> </div> -<div *ngIf="decS.invalid" class="row px-3 text-danger"> - <div *ngIf="decS.errors.nan"> - {{ decS.errors.nan.value }} +<div *ngIf="getDecDmsForm().controls.s.invalid" class="row px-3 text-danger"> + <div *ngIf="getDecDmsForm().controls.s.errors.nan"> + {{ getDecDmsForm().controls.s.errors.nan.value }} </div> - <div *ngIf="decS.errors.range" [hidden]="decS.errors.nan"> - {{ decS.errors.range.value }} + <div *ngIf="getDecDmsForm().controls.s.errors.range" [hidden]="getDecDmsForm().controls.s.errors.nan"> + {{ getDecDmsForm().controls.s.errors.range.value }} </div> </div> diff --git a/client/src/app/instance/shared-search/components/cone-search/dec.component.ts b/client/src/app/instance/shared-search/components/cone-search/dec.component.ts index 3566031548cf7ad568b692487382f20033bccb0d..193bc66543fb5e9e5110c7d582591d5305160e29 100644 --- a/client/src/app/instance/shared-search/components/cone-search/dec.component.ts +++ b/client/src/app/instance/shared-search/components/cone-search/dec.component.ts @@ -7,11 +7,13 @@ * file that was distributed with this source code. */ -import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; -import { FormControl, Validators } from '@angular/forms'; +import { Component, Input, ChangeDetectionStrategy, OnInit, OnDestroy, OnChanges, SimpleChanges } from '@angular/core'; +import { FormGroup } from '@angular/forms'; -import { nanValidator, rangeValidator } from '../../validators'; -import { ConeSearch, Resolver } from 'src/app/instance/store/models'; +import { Subscription } from 'rxjs'; +import { debounceTime } from 'rxjs/operators'; + +import { Resolver } from 'src/app/instance/store/models'; @Component({ selector: 'app-dec', @@ -23,199 +25,71 @@ import { ConeSearch, Resolver } from 'src/app/instance/store/models'; * @class * @classdesc DEC component. */ -export class DecComponent { - /** - * Disables DEC fields. - * - * @param {boolean} disabled - If the field has to be disabled. - */ - @Input() - set disabled(disabled: boolean) { - this.isDisabled = disabled; - this.initFields(); +export class DecComponent implements OnInit, OnDestroy, OnChanges { + @Input() form: FormGroup; + @Input() unit: string; + @Input() resolver: Resolver; + + public decControlSubscription: Subscription + public decDmsSubscription: Subscription; + + ngOnInit() { + this.form.controls.dec_dms.disable(); + this.decControlSubscription = this.form.controls.dec.valueChanges.pipe(debounceTime(250)) + .subscribe(deg => this.deg2DMS(deg)); } - /** - * Sets RA, DEC and radius from cone search. - * - * @param {ConeSearch} coneSearch - The cone search. - */ - @Input() - set coneSearch(coneSearch: ConeSearch) { - this.ra = coneSearch.ra; - this.radius = coneSearch.radius; - if (coneSearch.dec) { - this.decDegree.setValue(coneSearch.dec); - if(this.decDegree.valid && !this.decHFocused && !this.decMFocused && !this.decSFocused) { - this.decDegree2HMS(coneSearch.dec); + + ngOnChanges(changes: SimpleChanges): void { + if (changes.unit && !changes.unit.firstChange) { + if (changes.unit.currentValue === 'degree') { + this.form.controls.dec_dms.disable(); + this.form.controls.dec.enable(); + this.decControlSubscription = this.form.controls.dec.valueChanges.pipe(debounceTime(250)) + .subscribe(deg => this.deg2DMS(deg)); + if (this.decDmsSubscription) this.decDmsSubscription.unsubscribe(); + } + if (changes.unit.currentValue === 'hms') { + this.form.controls.dec_dms.enable(); + this.form.controls.dec.disable(); + this.decDmsSubscription = this.form.controls.dec_dms.valueChanges.pipe(debounceTime(250)) + .subscribe(value => this.DMS2Deg(value)); + if (this.decControlSubscription) this.decControlSubscription.unsubscribe(); } - } else { - this.decDegree.reset(); - this.decH.reset(); - this.decM.reset(); - this.decS.reset(); } - this.initFields(); - } - /** - * Sets RA from resolver. - * - * @param {Resolver} resolver - The resolver. - */ - @Input() - set resolver(resolver: Resolver) { - this.resolvedDec = null; - if (resolver) { - this.resolvedDec = resolver.dec; - this.decDegree.setValue(resolver.dec); - this.decDegree2HMS(resolver.dec); + + if (changes.resolver && changes.resolver.currentValue) { + this.form.controls.dec.setValue(changes.resolver.currentValue.dec); } } - /** - * Sets isDegree. - * - * @param {string} unit - The unit. - */ - @Input() - set unit(unit: string) { - unit === 'degree' ? this.isDegree = true : this.isDegree = false; - this.initFields(); - } - @Output() updateConeSearch: EventEmitter<ConeSearch> = new EventEmitter(); - @Output() deleteResolver: EventEmitter<null> = new EventEmitter(); - - ra: number; - radius: number; - isDisabled = false; - isDegree = true; - resolvedDec: number; - decHFocused: boolean = false; - decMFocused: boolean = false; - decSFocused: boolean = false; - - decDegree = new FormControl('', [Validators.required, nanValidator, rangeValidator(-90, 90, 'DEC')]); - decH = new FormControl('', [nanValidator, rangeValidator(-90, 90, 'Degree')]); - decM = new FormControl('', [nanValidator, rangeValidator(0, 60, 'Minutes')]); - decS = new FormControl('', [nanValidator, rangeValidator(0, 60, 'Seconds')]); - /** - * Sets DEC fields. - */ - initFields(): void { - if (this.isDisabled) { - this.decDegree.disable(); - this.decH.disable(); - this.decM.disable(); - this.decS.disable(); - } else if (this.isDegree) { - this.decDegree.enable(); - this.decH.disable(); - this.decM.disable(); - this.decS.disable(); - } else { - this.decDegree.disable(); - this.decH.enable(); - this.decM.enable(); - this.decS.enable(); - } + getDecDmsForm() { + const decDmsForm = this.form.controls.dec_dms as FormGroup; + return decDmsForm; } - /** - * Converts DEC hour minute second from degree and sets DEC HMS fields. - * - * @param {number} value - The degree value. - */ - decDegree2HMS(value: number): void { - const hh = Math.trunc(value); - let tmp = (Math.abs(value - hh)) * 60; + deg2DMS(deg: number): void { + const hh = Math.trunc(deg); + let tmp = (Math.abs(deg - hh)) * 60; const mm = Math.trunc(tmp); tmp = (tmp - mm) * 60; const ss = tmp.toFixed(2); - this.decH.setValue(hh); - this.decM.setValue(mm); - this.decS.setValue(ss); + const decDmsForm = this.getDecDmsForm(); + decDmsForm.controls.d.setValue(hh); + decDmsForm.controls.m.setValue(mm); + decDmsForm.controls.s.setValue(ss); } - /** - * Sets DEC degree from hour minute second and sets DEC degree field. - */ - decHMS2Degree(): void { - const hh = +this.decH.value; - const mm = +this.decM.value; - const ss = +this.decS.value; - const tmp = ((ss / 60) + mm) / 60; - let deg = tmp + Math.abs(hh); - if (hh < 0) { + DMS2Deg(dms: {d: number, m: number, s: number }): void { + const tmp = ((dms.s / 60) + dms.m) / 60; + let deg = tmp + Math.abs(dms.d); + if (dms.d < 0) { deg = -deg; } - this.decDegree.setValue(+deg.toFixed(8)); + this.form.controls.dec.setValue(deg); } - /** - * Changes fields focus. - * - * @param {string} field - The field. - * @param {boolean} isFocused - Is the field is focused. - */ - changeFocus(field: string, isFocused: boolean) { - switch (field) { - case 'dech': - this.decHFocused = isFocused; - break; - case 'decm': - this.decMFocused = isFocused; - break - case 'decs': - this.decSFocused = isFocused; - break; - } - } - - /** - * Manages DEC value change. - */ - decChange(): void { - if (this.isDegree) { - if (this.decDegree.valid) { - this.decDegree2HMS(this.decDegree.value); - } else { - this.decH.reset(); - this.decM.reset(); - this.decS.reset(); - } - this.updateConeSearch.emit({ ra: this.ra, dec: this.decDegree.value, radius: this.radius } as ConeSearch); - } else { - if (this.decH.valid && this.decM.valid && this.decS.valid) { - this.setToDefaultValue(); - this.decHMS2Degree(); - this.updateConeSearch.emit({ ra: this.ra, dec: this.decDegree.value, radius: this.radius } as ConeSearch); - } else { - this.decDegree.reset(); - } - } - this.resetResolver(); - } - - /** - * Sets DEC hour minute second fields to default value if not valid. - */ - setToDefaultValue(): void { - if (this.decH.value === '' || this.decH.value === null) { - this.decH.setValue(0); - } - if (this.decM.value === '' || this.decM.value === null) { - this.decM.setValue(0); - } - if (this.decS.value === '' || this.decS.value === null) { - this.decS.setValue(0); - } - } - - /** - * Emits reset resolver event. - */ - resetResolver(): void { - if (this.resolvedDec && this.resolvedDec !== this.decDegree.value) { - this.deleteResolver.emit(); - } + ngOnDestroy() { + if (this.decControlSubscription) this.decControlSubscription.unsubscribe(); + if (this.decDmsSubscription) this.decDmsSubscription.unsubscribe(); } } diff --git a/client/src/app/instance/shared-search/components/cone-search/ra.component.html b/client/src/app/instance/shared-search/components/cone-search/ra.component.html index e1ff92f6fa13d6a68cb3cbf742663ba56fe49b94..bb180bf81ba5dc29c24c2197b74691d51fbd7002 100644 --- a/client/src/app/instance/shared-search/components/cone-search/ra.component.html +++ b/client/src/app/instance/shared-search/components/cone-search/ra.component.html @@ -1,92 +1,75 @@ -<div class="row px-3"> - <label>RA</label> - <div class="input-group"> - <input type="text" class="form-control" [formControl]="raDegree" (input)="raChange()" autocomplete="off"> - <div class="input-group-append"> - <span class="input-group-text">°</span> - </div> - </div> -</div> - -<div class="row mt-2 px-3"> - <div class="col px-0 pr-xl-1"> +<form [formGroup]="form" novalidate> + <div class="row px-3"> + <label>RA</label> <div class="input-group"> - <input type="text" - class="form-control" - [formControl]="raH" - (input)="raChange()" - (focusin)="changeFocus('rah', true)" - (focusout)="changeFocus('rah', false)" - (change)="setToDefaultValue()" - autocomplete="off"> + <input type="number" class="form-control" formControlName="ra" autocomplete="off"> <div class="input-group-append"> - <span class="input-group-text">H</span> + <span class="input-group-text">°</span> </div> </div> </div> - <div class="w-100 d-block d-xl-none"></div> - <div class="col mt-1 mt-xl-auto px-0 pr-xl-1"> - <div class="input-group"> - <input type="text" - class="form-control" - [formControl]="raM" - (input)="raChange()" - (focusin)="changeFocus('ram', true)" - (focusout)="changeFocus('ram', false)" - (change)="setToDefaultValue()" - autocomplete="off"> - <div class="input-group-append"> - <span class="input-group-text">'</span> + + <div formGroupName="ra_hms"> + <div class="row mt-2 px-3"> + <div class="col px-0 pr-xl-1"> + <div class="input-group"> + <input type="number" class="form-control" formControlName="h" autocomplete="off"> + <div class="input-group-append"> + <span class="input-group-text">H</span> + </div> + </div> </div> - </div> - </div> - <div class="w-100 d-block d-xl-none"></div> - <div class="col mt-1 mt-xl-auto px-0"> - <div class="input-group"> - <input type="text" - class="form-control" - [formControl]="raS" - (input)="raChange()" - (focusin)="changeFocus('ras', true)" - (focusout)="changeFocus('ras', false)" - (change)="setToDefaultValue()" - autocomplete="off"> - <div class="input-group-append"> - <span class="input-group-text">''</span> + <div class="w-100 d-block d-xl-none"></div> + <div class="col mt-1 mt-xl-auto px-0 pr-xl-1"> + <div class="input-group"> + <input type="number" class="form-control" formControlName="m" autocomplete="off"> + <div class="input-group-append"> + <span class="input-group-text">'</span> + </div> + </div> + </div> + <div class="w-100 d-block d-xl-none"></div> + <div class="col mt-1 mt-xl-auto px-0"> + <div class="input-group"> + <input type="number" class="form-control" formControlName="s" autocomplete="off"> + <div class="input-group-append"> + <span class="input-group-text">''</span> + </div> + </div> </div> </div> </div> -</div> +</form> -<div *ngIf="raDegree.invalid" class="row px-3 text-danger"> - <div *ngIf="raDegree.errors.nan"> - {{ raDegree.errors.nan.value }} +<div *ngIf="form.controls.ra.invalid" class="row px-3 text-danger"> + <div *ngIf="form.controls.ra.errors.nan"> + {{ form.controls.ra.errors.nan.value }} </div> - <div *ngIf="raDegree.errors.range" [hidden]="raDegree.errors.nan"> - {{ raDegree.errors.range.value }} + <div *ngIf="form.controls.ra.errors.range" [hidden]="form.controls.ra.errors.nan"> + {{ form.controls.ra.errors.range.value }} </div> </div> -<div *ngIf="raH.invalid" class="row px-3 text-danger"> - <div *ngIf="raH.errors.nan"> - {{ raH.errors.nan.value }} +<div *ngIf="getRaHmsForm().controls.h.invalid" class="row px-3 text-danger"> + <div *ngIf="getRaHmsForm().controls.h.errors.nan"> + {{ getRaHmsForm().controls.h.errors.nan.value }} </div> - <div *ngIf="raH.errors.range" [hidden]="raH.errors.nan"> - {{ raH.errors.range.value }} + <div *ngIf="getRaHmsForm().controls.h.errors.range" [hidden]="getRaHmsForm().controls.h.errors.nan"> + {{ getRaHmsForm().controls.h.errors.range.value }} </div> </div> -<div *ngIf="raM.invalid" class="row px-3 text-danger"> - <div *ngIf="raM.errors.nan"> - {{ raM.errors.nan.value }} +<div *ngIf="getRaHmsForm().controls.m.invalid" class="row px-3 text-danger"> + <div *ngIf="getRaHmsForm().controls.m.errors.nan"> + {{ getRaHmsForm().controls.m.errors.nan.value }} </div> - <div *ngIf="raM.errors.range" [hidden]="raM.errors.nan"> - {{ raM.errors.range.value }} + <div *ngIf="getRaHmsForm().controls.m.errors.range" [hidden]="getRaHmsForm().controls.m.errors.nan"> + {{ getRaHmsForm().controls.m.errors.range.value }} </div> </div> -<div *ngIf="raS.invalid" class="row px-3 text-danger"> - <div *ngIf="raS.errors.nan"> - {{ raS.errors.nan.value }} +<div *ngIf="getRaHmsForm().controls.s.invalid" class="row px-3 text-danger"> + <div *ngIf="getRaHmsForm().controls.s.errors.nan"> + {{ getRaHmsForm().controls.s.errors.nan.value }} </div> - <div *ngIf="raS.errors.range" [hidden]="raS.errors.nan"> - {{ raS.errors.range.value }} + <div *ngIf="getRaHmsForm().controls.s.errors.range" [hidden]="getRaHmsForm().controls.s.errors.nan"> + {{ getRaHmsForm().controls.s.errors.range.value }} </div> </div> diff --git a/client/src/app/instance/shared-search/components/cone-search/ra.component.ts b/client/src/app/instance/shared-search/components/cone-search/ra.component.ts index 30050f202540cf03c3d6659588e2c2c1595b5e90..aab36bf7ec811d5984d1d977b0b78847db203ab9 100644 --- a/client/src/app/instance/shared-search/components/cone-search/ra.component.ts +++ b/client/src/app/instance/shared-search/components/cone-search/ra.component.ts @@ -7,11 +7,13 @@ * file that was distributed with this source code. */ -import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; -import { FormControl, Validators } from '@angular/forms'; +import { Component, Input, ChangeDetectionStrategy, OnDestroy, OnInit, OnChanges, SimpleChanges } from '@angular/core'; +import { FormGroup } from '@angular/forms'; -import { nanValidator, rangeValidator } from '../../validators'; -import { ConeSearch, Resolver } from 'src/app/instance/store/models'; +import { Subscription } from 'rxjs'; +import { debounceTime } from 'rxjs/operators'; + +import { Resolver } from 'src/app/instance/store/models'; @Component({ selector: 'app-ra', @@ -23,196 +25,68 @@ import { ConeSearch, Resolver } from 'src/app/instance/store/models'; * @class * @classdesc RA component. */ -export class RaComponent { - /** - * Disables RA fields. - * - * @param {boolean} disabled - If the field has to be disabled. - */ - @Input() - set disabled(disabled: boolean) { - this.isDisabled = disabled; - this.initFields(); +export class RaComponent implements OnInit, OnDestroy, OnChanges { + @Input() form: FormGroup; + @Input() unit: string; + @Input() resolver: Resolver; + + public raControlSubscription: Subscription; + public raHmsFormSubscription: Subscription; + + ngOnInit() { + this.form.controls.ra_hms.disable(); + this.raControlSubscription = this.form.controls.ra.valueChanges.pipe(debounceTime(250)) + .subscribe(deg => this.deg2HMS(deg)); } - /** - * Sets RA, DEC and radius from cone search. - * - * @param {ConeSearch} coneSearch - The cone search. - */ - @Input() - set coneSearch(coneSearch: ConeSearch) { - this.dec = coneSearch.dec; - this.radius = coneSearch.radius; - if (coneSearch.ra) { - this.raDegree.setValue(coneSearch.ra); - if (this.raDegree.valid && !this.raHFocused && !this.raMFocused && !this.raSFocused) { - this.raDegree2HMS(coneSearch.ra); + + ngOnChanges(changes: SimpleChanges): void { + if (changes.unit && !changes.unit.firstChange) { + if (changes.unit.currentValue === 'degree') { + this.form.controls.ra_hms.disable(); + this.form.controls.ra.enable(); + this.raControlSubscription = this.form.controls.ra.valueChanges.pipe(debounceTime(250)) + .subscribe(deg => this.deg2HMS(deg)); + if (this.raHmsFormSubscription) this.raHmsFormSubscription.unsubscribe(); + } + if (changes.unit.currentValue === 'hms') { + this.form.controls.ra_hms.enable(); + this.form.controls.ra.disable(); + this.raHmsFormSubscription = this.form.controls.ra_hms.valueChanges.pipe(debounceTime(250)) + .subscribe(value => this.HMS2Deg(value)); + if (this.raControlSubscription) this.raControlSubscription.unsubscribe(); } - } else { - this.raDegree.reset(); - this.raH.reset(); - this.raM.reset(); - this.raS.reset(); } - this.initFields(); - } - /** - * Sets RA from resolver. - * - * @param {Resolver} resolver - The resolver. - */ - @Input() - set resolver(resolver: Resolver) { - this.resolvedRa = null; - if (resolver) { - this.resolvedRa = resolver.ra; - this.raDegree.setValue(resolver.ra); - this.raDegree2HMS(resolver.ra); + + if (changes.resolver && changes.resolver.currentValue) { + this.form.controls.ra.setValue(changes.resolver.currentValue.ra); } } - /** - * Sets isDegree. - * - * @param {string} unit - The unit. - */ - @Input() - set unit(unit: string) { - unit === 'degree' ? this.isDegree = true : this.isDegree = false; - this.initFields(); - } - @Output() updateConeSearch: EventEmitter<ConeSearch> = new EventEmitter(); - @Output() deleteResolver: EventEmitter<null> = new EventEmitter(); - - dec: number = null; - radius: number = null; - isDisabled = false; - isDegree = true; - resolvedRa: number; - raHFocused: boolean = false; - raMFocused: boolean = false; - raSFocused: boolean = false; - - raDegree = new FormControl('', [Validators.required, nanValidator, rangeValidator(0, 360, 'RA')]); - raH = new FormControl('', [nanValidator, rangeValidator(0, 24, 'Hours')]); - raM = new FormControl('', [nanValidator, rangeValidator(0, 60, 'Minutes')]); - raS = new FormControl('', [nanValidator, rangeValidator(0, 60, 'Seconds')]); - /** - * Sets RA fields. - */ - initFields(): void { - if (this.isDisabled) { - this.raDegree.disable(); - this.raH.disable(); - this.raM.disable(); - this.raS.disable(); - } else if (this.isDegree) { - this.raDegree.enable(); - this.raH.disable(); - this.raM.disable(); - this.raS.disable(); - } else { - this.raDegree.disable(); - this.raH.enable(); - this.raM.enable(); - this.raS.enable(); - } + getRaHmsForm() { + const raHmsForm = this.form.controls.ra_hms as FormGroup; + return raHmsForm; } - /** - * Converts RA hour minute second from degree and sets RA HMS fields. - * - * @param {number} value - The degree value. - */ - raDegree2HMS(value: number): void { - let tmp = value / 15; + deg2HMS(deg: number): void { + let tmp = deg / 15; const hh = Math.trunc(tmp); tmp = (tmp - hh) * 60; const mm = Math.trunc(tmp); tmp = (tmp - mm) * 60; const ss = +tmp.toFixed(2); - this.raH.setValue(hh); - this.raM.setValue(mm); - this.raS.setValue(ss); + const raHmsForm = this.getRaHmsForm(); + raHmsForm.controls.h.setValue(hh); + raHmsForm.controls.m.setValue(mm); + raHmsForm.controls.s.setValue(ss); } - /** - * Sets RA degree from hour minute second and sets RA degree field. - */ - raHMS2Degree(): void { - const hh = +this.raH.value; - const mm = +this.raM.value; - const ss = +this.raS.value; - const deg = +(((((ss / 60) + mm) / 60) + hh) * 15).toFixed(8); - this.raDegree.setValue(deg); + HMS2Deg(hms: {h: number, m: number, s: number }): void { + const deg = +(((((hms.s / 60) + hms.m) / 60) + hms.h) * 15).toFixed(8); + this.form.controls.ra.setValue(deg); } - /** - * Changes fields focus. - * - * @param {string} field - The field. - * @param {boolean} isFocused - Is the field is focused. - */ - changeFocus(field: string, isFocused: boolean): void { - switch (field) { - case 'rah': - this.raHFocused = isFocused; - break; - case 'ram': - this.raMFocused = isFocused; - break - case 'ras': - this.raSFocused = isFocused; - break; - } - } - - /** - * Manages RA value change. - */ - raChange(): void { - if (this.isDegree) { - if (this.raDegree.valid) { - this.raDegree2HMS(this.raDegree.value); - } else { - this.raH.reset(); - this.raM.reset(); - this.raS.reset(); - } - this.updateConeSearch.emit({ ra: this.raDegree.value, dec: this.dec, radius: this.radius } as ConeSearch); - } else { - if (this.raH.valid && this.raM.valid && this.raS.valid) { - this.setToDefaultValue(); - this.raHMS2Degree(); - this.updateConeSearch.emit({ ra: this.raDegree.value, dec: this.dec, radius: this.radius } as ConeSearch); - } else { - this.raDegree.reset(); - } - } - this.resetResolver(); - } - - /** - * Sets RA hour minute second fields to default value if not valid. - */ - setToDefaultValue(): void { - if (this.raH.value === '' || this.raH.value === null) { - this.raH.setValue(0); - } - if (this.raM.value === '' || this.raM.value === null) { - this.raM.setValue(0); - } - if (this.raS.value === '' || this.raS.value === null) { - this.raS.setValue(0); - } - } - - /** - * Emits reset resolver event. - */ - resetResolver(): void { - if (this.resolvedRa && this.resolvedRa !== this.raDegree.value) { - this.deleteResolver.emit(); - } + ngOnDestroy() { + if (this.raControlSubscription) this.raControlSubscription.unsubscribe(); + if (this.raHmsFormSubscription) this.raHmsFormSubscription.unsubscribe(); } } diff --git a/client/src/app/instance/shared-search/components/cone-search/radius.component.html b/client/src/app/instance/shared-search/components/cone-search/radius.component.html index 406cdfc30e2dcaeb8151aad6ac776a9d23cc3c60..87cdf7a216c9304bef8e199db31a26860a70adda 100644 --- a/client/src/app/instance/shared-search/components/cone-search/radius.component.html +++ b/client/src/app/instance/shared-search/components/cone-search/radius.component.html @@ -1,27 +1,22 @@ -<div class="row"> - <div class="col form-group mb-0"> - <label>Radius</label> - <input #rr - type="range" - min="0" - max="150" - [formControl]="radiusRange" - (input)="radiusChange(rr.value)" - class="form-control-range mt-2" - autocomplete="off"> - </div> - <div class="w-100 d-block d-lg-none"></div> - <div class="col col-lg-auto form-group mb-0"> - <div class="input-group mt-4"> - <input #rf id="radius-field" type="number" class="form-control" [formControl]="radiusField" (input)="radiusChange(rf.value)" autocomplete="off"> - <div class="input-group-append"> - <span class="input-group-text">arcsecond</span> +<form [formGroup]="form" novalidate> + <div class="row"> + <div class="col form-group mb-0"> + <label>Radius</label> + <input type="range" min="0" max="150" class="form-control-range mt-2" [value]="form.value.radius" formControlName="radius" autocomplete="off"> + </div> + <div class="w-100 d-block d-lg-none"></div> + <div class="col col-lg-auto form-group mb-0"> + <div class="input-group mt-4"> + <input type="number" class="form-control" [value]="form.value.radius" formControlName="radius" autocomplete="off"> + <div class="input-group-append"> + <span class="input-group-text">arcsecond</span> + </div> </div> </div> - </div> - <div *ngIf="radiusField.invalid" class="col-12 text-danger"> - <div *ngIf="radiusField.errors.range"> - {{ radiusField.errors.range.value }} + <div *ngIf="form.controls.radius.invalid" class="col-12 text-danger"> + <div *ngIf="form.controls.radius.errors.range"> + {{ form.controls.radius.errors.range.value }} + </div> </div> </div> -</div> +</form> \ No newline at end of file diff --git a/client/src/app/instance/shared-search/components/cone-search/radius.component.ts b/client/src/app/instance/shared-search/components/cone-search/radius.component.ts index 5add3abf531de6ea605e5547a42bc19832ec16f1..fde6ceb216655a19b21d8966877a01fd29a0d0b1 100644 --- a/client/src/app/instance/shared-search/components/cone-search/radius.component.ts +++ b/client/src/app/instance/shared-search/components/cone-search/radius.component.ts @@ -7,11 +7,8 @@ * file that was distributed with this source code. */ -import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; -import { FormControl } from '@angular/forms'; - -import { rangeValidator } from '../../validators'; -import { ConeSearch } from 'src/app/instance/store/models'; +import { Component, Input, ChangeDetectionStrategy } from '@angular/core'; +import { FormGroup } from '@angular/forms'; @Component({ selector: 'app-radius', @@ -24,55 +21,5 @@ import { ConeSearch } from 'src/app/instance/store/models'; * @classdesc Radius component. */ export class RadiusComponent { - /** - * Sets RA, DEC and radius from cone search. - * - * @param {ConeSearch} coneSearch - The cone search. - */ - @Input() - set coneSearch(coneSearch: ConeSearch) { - this.ra = coneSearch.ra; - this.dec = coneSearch.dec; - if (coneSearch.radius) { - this.radiusField.setValue(coneSearch.radius); - this.radiusRange.setValue(coneSearch.radius); - } else { - this.radiusRange.setValue(0); - this.radiusField.setValue(0); - } - } - /** - * Disables radius fields. - * - * @param {boolean} disabled - If the field has to be disabled. - */ - @Input() - set disabled(disabled: boolean) { - if (disabled) { - this.radiusField.disable(); - this.radiusRange.disable(); - } else { - this.radiusField.enable(); - this.radiusRange.enable(); - } - } - @Output() updateConeSearch: EventEmitter<ConeSearch> = new EventEmitter(); - - ra: number; - dec: number; - radiusRange = new FormControl(''); - radiusField = new FormControl('', [rangeValidator(0, 150, 'Radius')]); - - /** - * Sets radius value form inputs and emits cone search event. - * - * @param {string} value - The value of radius. - * - * @fires EventEmitter<ConeSearch> - */ - radiusChange(value: string): void { - this.radiusField.setValue(+value); - this.radiusRange.setValue(+value); - this.updateConeSearch.emit({ ra: this.ra, dec: this.dec, radius: +value } as ConeSearch); - } + @Input() form: FormGroup; } diff --git a/client/src/app/instance/shared-search/components/cone-search/resolver.component.html b/client/src/app/instance/shared-search/components/cone-search/resolver.component.html index 86714689d9db49082707206b9002ed94cdf661e8..abf2a55cc7b6b98bc988778340658be4d0588d4b 100644 --- a/client/src/app/instance/shared-search/components/cone-search/resolver.component.html +++ b/client/src/app/instance/shared-search/components/cone-search/resolver.component.html @@ -1,15 +1,17 @@ -<div class="row"> - <div class="col pr-0"> - <label for="resolver">Resolve RA and DEC with Sesame Name Resolver</label> - <input #n id="resolver" type="text" class="form-control" [formControl]="field" autocomplete="off"> +<form [formGroup]="form" (ngSubmit)="submit()" novalidate> + <div class="row"> + <div class="col pr-0"> + <label for="resolver">Resolve RA and DEC with Sesame Name Resolver</label> + <input type="text" class="form-control" name="name" formControlName="name" autocomplete="off"> + </div> + <div class="col-auto pt-5 pt-lg-4"> + <button *ngIf="!resolverIsLoading" [disabled]="!form.valid || form.pristine" type="submit" class="btn btn-outline-secondary mt-2"> + <span class="fas fa-search"></span> + </button> + <button *ngIf="resolverIsLoading" class="btn btn-outline-secondary mt-2" disabled> + <span class="fas fa-circle-notch fa-spin"></span> + <span class="sr-only">Loading...</span> + </button> + </div> </div> - <div class="col-auto pt-5 pt-lg-4"> - <button *ngIf="!resolverWip" id="btn-search" class="btn btn-outline-secondary mt-2" [disabled]="field.disabled" (click)="resolveName.emit(n.value)"> - <span class="fas fa-search"></span> - </button> - <button *ngIf="resolverWip" id="btn-wip" class="btn btn-outline-secondary mt-2" disabled> - <span class="fas fa-circle-notch fa-spin"></span> - <span class="sr-only">Loading...</span> - </button> - </div> -</div> +</form> diff --git a/client/src/app/instance/shared-search/components/cone-search/resolver.component.ts b/client/src/app/instance/shared-search/components/cone-search/resolver.component.ts index 1c01755596db0bfad2c8509903f7f76d68e1f957..18c094444753d9795a7088eb0aca5e3a1a8fae1d 100644 --- a/client/src/app/instance/shared-search/components/cone-search/resolver.component.ts +++ b/client/src/app/instance/shared-search/components/cone-search/resolver.component.ts @@ -7,10 +7,10 @@ * file that was distributed with this source code. */ -import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; -import { FormControl } from '@angular/forms'; +import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, OnInit, OnChanges, SimpleChanges } from '@angular/core'; +import { FormGroup, FormControl, Validators } from '@angular/forms'; -import { Resolver } from 'src/app/instance/store/models'; +import { ConeSearch, Resolver } from 'src/app/instance/store/models'; @Component({ selector: 'app-resolver', @@ -21,25 +21,34 @@ import { Resolver } from 'src/app/instance/store/models'; * @class * @classdesc Resolver component. */ -export class ResolverComponent { - @Input() - set disabled(disabled: boolean) { - if (disabled) { - this.field.disable(); - } else { - this.field.enable(); +export class ResolverComponent implements OnInit, OnChanges { + @Input() coneSearch: ConeSearch; + @Input() resolver: Resolver; + @Input() resolverIsLoading: boolean; + @Input() resolverIsLoaded: boolean; + @Output() retrieveCoordinates: EventEmitter<string> = new EventEmitter(); + + public form = new FormGroup({ + name: new FormControl('', [Validators.required]) + }); + + ngOnInit() { + if (this.coneSearch) { + this.form.disable(); } } - @Input() resolverWip: boolean; - @Input() - set resolver(resolver: Resolver) { - if (resolver) { - this.field.setValue(resolver.name); - } else { - this.field.reset(); + + ngOnChanges(changes: SimpleChanges): void { + if (changes.coneSearch && !changes.coneSearch.currentValue) { + this.form.enable(); + } + + if (changes.coneSearch && changes.coneSearch.currentValue) { + this.form.disable(); } } - @Output() resolveName: EventEmitter<string> = new EventEmitter(); - field = new FormControl(''); + submit() { + this.retrieveCoordinates.emit(this.form.controls.name.value); + } } diff --git a/client/src/app/instance/shared-search/components/datatable/datatable.component.html b/client/src/app/instance/shared-search/components/datatable/datatable.component.html index 8a373adc94235177078fdbe92c629b1bb642024b..881f79065975906242993d75e2239f945bd4ff4c 100644 --- a/client/src/app/instance/shared-search/components/datatable/datatable.component.html +++ b/client/src/app/instance/shared-search/components/datatable/datatable.component.html @@ -45,6 +45,7 @@ <app-detail-renderer [value]="datum[attribute.label]" [datasetName]="dataset.name" + [instanceSelected]="instanceSelected" [config]="getRendererConfig(attribute)"> </app-detail-renderer> </div> diff --git a/client/src/app/instance/shared-search/components/datatable/datatable.component.ts b/client/src/app/instance/shared-search/components/datatable/datatable.component.ts index 56d115ee220a55a674fc8b3a6343871b3c6c8178..698cf0403bf1ea003acb77f3b9da144bb38132db 100644 --- a/client/src/app/instance/shared-search/components/datatable/datatable.component.ts +++ b/client/src/app/instance/shared-search/components/datatable/datatable.component.ts @@ -26,6 +26,7 @@ import { Pagination, PaginationOrder } from 'src/app/instance/store/models'; */ export class DatatableComponent implements OnInit { @Input() dataset: Dataset; + @Input() instanceSelected: string; @Input() attributeList: Attribute[]; @Input() outputList: number[]; @Input() dataLength: number; diff --git a/client/src/app/instance/shared-search/components/datatable/renderer/detail-renderer.component.html b/client/src/app/instance/shared-search/components/datatable/renderer/detail-renderer.component.html index 0b6336b1e1acb9cf348dc857b43bd2fedc62ddc2..37c9f3133e39d52496faf4585956836050038ccc 100644 --- a/client/src/app/instance/shared-search/components/datatable/renderer/detail-renderer.component.html +++ b/client/src/app/instance/shared-search/components/datatable/renderer/detail-renderer.component.html @@ -1,4 +1,4 @@ -<a routerLink="/detail/{{ datasetName }}/{{ value }}" [target]="config.blank == true ? '_blank' : '_self'" +<a routerLink="/instance/{{ instanceSelected }}/detail/{{ datasetName }}/{{ value }}" [target]="config.blank == true ? '_blank' : '_self'" [ngClass]="{'btn btn-outline-primary btn-sm' : config.display == 'text-button'}"> {{ value }} </a> diff --git a/client/src/app/instance/shared-search/components/datatable/renderer/detail-renderer.component.ts b/client/src/app/instance/shared-search/components/datatable/renderer/detail-renderer.component.ts index 06411ef9ec5a95251f5cc49da809f0490eb95839..d6df4dfd8384e2931696c6807a5a0c97bfdeaefb 100644 --- a/client/src/app/instance/shared-search/components/datatable/renderer/detail-renderer.component.ts +++ b/client/src/app/instance/shared-search/components/datatable/renderer/detail-renderer.component.ts @@ -23,5 +23,6 @@ import { DetailRendererConfig } from 'src/app/metamodel/models/renderers/detail- export class DetailRendererComponent { @Input() value: string | number; @Input() datasetName: string; + @Input() instanceSelected: string; @Input() config: DetailRendererConfig; } diff --git a/client/src/app/instance/shared-search/components/datatable/renderer/download-renderer.component.ts b/client/src/app/instance/shared-search/components/datatable/renderer/download-renderer.component.ts index 74fc0a7f9a9f67367faeae9ea88d2bd404a25a12..19bcc38572ede27f043ee814f2f330d76eabeb25 100644 --- a/client/src/app/instance/shared-search/components/datatable/renderer/download-renderer.component.ts +++ b/client/src/app/instance/shared-search/components/datatable/renderer/download-renderer.component.ts @@ -11,6 +11,7 @@ import { Component, Input, ChangeDetectionStrategy } from '@angular/core'; import { DownloadRendererConfig } from 'src/app/metamodel/models/renderers/download-renderer-config.model'; import { getHost } from 'src/app/shared/utils'; +import { AppConfigService } from 'src/app/app-config.service'; @Component({ selector: 'app-download-renderer', @@ -26,13 +27,15 @@ export class DownloadRendererComponent { @Input() datasetName: string; @Input() config: DownloadRendererConfig; + constructor(private appConfig: AppConfigService) { } + /** * Returns link href. * * @return string */ getHref(): string { - return getHost() + '/download-file/' + this.datasetName + '/' + this.value; + return getHost(this.appConfig.apiUrl) + '/download-file/' + this.datasetName + '/' + this.value; } /** diff --git a/client/src/app/instance/shared-search/components/datatable/renderer/image-renderer.component.ts b/client/src/app/instance/shared-search/components/datatable/renderer/image-renderer.component.ts index 75521e7d2816346299a40bebc41cee587f5a0e40..677dfd73c7e0f8baf9cd8bd11e1a74015f49c42a 100644 --- a/client/src/app/instance/shared-search/components/datatable/renderer/image-renderer.component.ts +++ b/client/src/app/instance/shared-search/components/datatable/renderer/image-renderer.component.ts @@ -13,7 +13,7 @@ import { BsModalService } from 'ngx-bootstrap/modal'; import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service'; import { ImageRendererConfig } from 'src/app/metamodel/models/renderers'; -import { environment } from 'src/environments/environment'; +import { AppConfigService } from 'src/app/app-config.service'; @Component({ selector: 'app-image-renderer', @@ -28,11 +28,10 @@ export class ImageRendererComponent { @Input() value: string | number; @Input() datasetName: string; @Input() config: ImageRendererConfig; - private SERVICES_PATH: string = environment.servicesUrl; modalRef: BsModalRef; - constructor(private modalService: BsModalService) { } + constructor(private modalService: BsModalService, private appConfig: AppConfigService) { } openModal(template: TemplateRef<any>) { this.modalRef = this.modalService.show(template); @@ -45,7 +44,7 @@ export class ImageRendererComponent { */ getValue(): string { if (this.config.type === 'fits') { - return `${this.SERVICES_PATH}/fits-to-png/${this.datasetName}?filename=${this.value}` + return `${this.appConfig.servicesUrl}/fits-to-png/${this.datasetName}?filename=${this.value}` + `&stretch=linear&pmin=0.25&pmax=99.75&axes=true`; } else { return this.value as string; diff --git a/client/src/app/instance/shared-search/components/index.ts b/client/src/app/instance/shared-search/components/index.ts index f3ee95d0784280eae6250d149041229fd9e5127f..1bd2ebbdd5b479facf234dd6ba38ab4ef88f589e 100644 --- a/client/src/app/instance/shared-search/components/index.ts +++ b/client/src/app/instance/shared-search/components/index.ts @@ -1,7 +1,7 @@ -// import { coneSearchComponents } from './cone-search'; +import { coneSearchComponents } from './cone-search'; import { datatableComponents } from './datatable'; export const sharedComponents = [ - // coneSearchComponents, + coneSearchComponents, datatableComponents ]; diff --git a/client/src/app/instance/store/actions/cone-search.actions.ts b/client/src/app/instance/store/actions/cone-search.actions.ts index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..779b424153952b2ca4f4e0dba7157b5a97c55c0e 100644 --- a/client/src/app/instance/store/actions/cone-search.actions.ts +++ b/client/src/app/instance/store/actions/cone-search.actions.ts @@ -0,0 +1,18 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { createAction, props } from '@ngrx/store'; + +import { ConeSearch, Resolver } from '../models'; + +export const addConeSearch = createAction('[ConeSearch] Add Cone Search', props<{ coneSearch: ConeSearch }>()); +export const deleteConeSearch = createAction('[ConeSearch] Delete Cone Search'); +export const retrieveCoordinates = createAction('[ConeSearch] Retrieve Coordinates', props<{ name: string }>()); +export const retrieveCoordinatesSuccess = createAction('[ConeSearch] Retrieve Coordinates Success', props<{ resolver: Resolver }>()); +export const retrieveCoordinatesFail = createAction('[ConeSearch] Retrieve Coordinates Fail'); diff --git a/client/src/app/instance/store/actions/detail.actions.ts b/client/src/app/instance/store/actions/detail.actions.ts new file mode 100644 index 0000000000000000000000000000000000000000..c88210bc8e4e24fb0ee165d34c38c07a90792322 --- /dev/null +++ b/client/src/app/instance/store/actions/detail.actions.ts @@ -0,0 +1,17 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { createAction, props } from '@ngrx/store'; + +export const retrieveObject = createAction('[Detail] Retrieve Object'); +export const retrieveObjectSuccess = createAction('[Detail] Retrieve Object Success', props<{ object: any }>()); +export const retrieveObjectFail = createAction('[Detail] Retrieve Object Fail'); +export const retrieveSpectra = createAction('[Detail] Retrieve Spectra', props<{ filename: string }>()); +export const retrieveSpectraSuccess = createAction('[Detail] Retrieve Spectra Success', props<{ spectraCSV: string }>()); +export const retrieveSpectraFail = createAction('[Detail] Retrieve Spectra Fail'); diff --git a/client/src/app/instance/store/effects/cone-search.effects.ts b/client/src/app/instance/store/effects/cone-search.effects.ts new file mode 100644 index 0000000000000000000000000000000000000000..84d2ab8fe05cc36bdba788ba155cd47ae0c7270d --- /dev/null +++ b/client/src/app/instance/store/effects/cone-search.effects.ts @@ -0,0 +1,58 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { Injectable } from '@angular/core'; + +import { Actions, createEffect, ofType } from '@ngrx/effects'; +import { of } from 'rxjs'; +import { map, tap, mergeMap, catchError } from 'rxjs/operators'; +import { ToastrService } from 'ngx-toastr'; + +import * as coneSearchActions from '../actions/cone-search.actions'; +import { ConeSearchService } from '../services/cone-search.service'; + +@Injectable() +export class ConeSearchEffects { + retrieveCoordinates$ = createEffect(() => + this.actions$.pipe( + ofType(coneSearchActions.retrieveCoordinates), + mergeMap((action) => this.coneSearchService.retrieveCoordinates(action.name) + .pipe( + map(response => { + const parser = new DOMParser(); + const xml = parser.parseFromString(response,'text/xml'); + if (xml.getElementsByTagName('Resolver').length === 0) { + const name = xml.getElementsByTagName('name')[0].childNodes[0].nodeValue; + return coneSearchActions.retrieveCoordinatesFail(); + } + const name = xml.getElementsByTagName('name')[0].childNodes[0].nodeValue; + const ra = +xml.getElementsByTagName('jradeg')[0].childNodes[0].nodeValue; + const dec = +xml.getElementsByTagName('jdedeg')[0].childNodes[0].nodeValue; + const resolver = { name, ra, dec }; + return coneSearchActions.retrieveCoordinatesSuccess({ resolver }); + }), + catchError(() => of(coneSearchActions.retrieveCoordinatesFail())) + ) + ) + ) + ); + + retrieveCoordinatesFail$ = createEffect(() => + this.actions$.pipe( + ofType(coneSearchActions.retrieveCoordinatesFail), + tap(() => this.toastr.error('Failure to retrieve coordinates', 'The coordinates could not be retrieved')) + ), { dispatch: false} + ); + + constructor( + private actions$: Actions, + private coneSearchService: ConeSearchService, + private toastr: ToastrService + ) {} +} diff --git a/client/src/app/instance/store/effects/detail.effects.ts b/client/src/app/instance/store/effects/detail.effects.ts new file mode 100644 index 0000000000000000000000000000000000000000..256412aea966b727115af6394a73795b181e22e7 --- /dev/null +++ b/client/src/app/instance/store/effects/detail.effects.ts @@ -0,0 +1,80 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { Injectable } from '@angular/core'; + +import { Actions, createEffect, ofType, concatLatestFrom } from '@ngrx/effects'; +import { Store } from '@ngrx/store'; +import { of } from 'rxjs'; +import { map, tap, mergeMap, catchError } from 'rxjs/operators'; +import { ToastrService } from 'ngx-toastr'; + +import { DetailService } from '../services/detail.service'; +import * as detailActions from '../actions/detail.actions'; +import * as detailSelector from '../selectors/detail.selector'; +import * as attributeSelector from 'src/app/metamodel/selectors/attribute.selector'; +import * as datasetSelector from 'src/app/metamodel/selectors/dataset.selector'; + +@Injectable() +export class DetailEffects { + retrieveObject$ = createEffect(() => + this.actions$.pipe( + ofType(detailActions.retrieveObject), + concatLatestFrom(() => [ + this.store.select(datasetSelector.selectDatasetNameByRoute), + this.store.select(attributeSelector.selectAllAttributes), + this.store.select(detailSelector.selectIdByRoute) + ]), + mergeMap(([action, datasetName, attributeList, id]) => this.detailService.retrieveObject( + datasetName, + attributeList.find(attribute => attribute.order_by).id, + id, + attributeList.filter(attribute => attribute.detail).map(attribute => attribute.id) + ).pipe( + map(object => detailActions.retrieveObjectSuccess({ object: object[0] })), + catchError(() => of(detailActions.retrieveObjectFail())) + )) + ) + ); + + retrieveObjectFail$ = createEffect(() => + this.actions$.pipe( + ofType(detailActions.retrieveObjectFail), + tap(() => this.toastr.error('Loading Failed!', 'Unable to load the object')) + ), { dispatch: false} + ); + + retrieveSpectra$ = createEffect(() => + this.actions$.pipe( + ofType(detailActions.retrieveSpectra), + concatLatestFrom(() => this.store.select(datasetSelector.selectDatasetNameByRoute)), + mergeMap(([action, datasetName]) => this.detailService.retrieveSpectra( + datasetName, + action.filename + ).pipe( + map(spectraCSV => detailActions.retrieveSpectraSuccess({ spectraCSV })), + catchError(() => of(detailActions.retrieveSpectraFail())) + )) + ) + ); + + retrieveSpectraFail$ = createEffect(() => + this.actions$.pipe( + ofType(detailActions.retrieveSpectraFail), + tap(() => this.toastr.error('Loading Failed!', 'Unable to load spectra')) + ), { dispatch: false} + ); + + constructor( + private actions$: Actions, + private detailService: DetailService, + private store: Store<{ }>, + private toastr: ToastrService + ) {} +} diff --git a/client/src/app/instance/store/effects/index.ts b/client/src/app/instance/store/effects/index.ts index 6c5a834023827b54a609d049daa42baa9b829be3..609cc1388dcbf3a2b47c1f739c0a8e22989ec94b 100644 --- a/client/src/app/instance/store/effects/index.ts +++ b/client/src/app/instance/store/effects/index.ts @@ -1,11 +1,15 @@ import { MetamodelEffects } from './metamodel.effects'; -import { SampEffects } from "./samp.effects"; -import { SearchEffects } from "./search.effects"; +import { SampEffects } from './samp.effects'; +import { SearchEffects } from './search.effects'; +import { ConeSearchEffects } from './cone-search.effects'; +import { DetailEffects } from './detail.effects'; import { DocumentationEffects } from "./documentation.effects"; export const instanceEffects = [ MetamodelEffects, SampEffects, SearchEffects, + ConeSearchEffects, + DetailEffects, DocumentationEffects ]; diff --git a/client/src/app/instance/store/effects/search.effects.ts b/client/src/app/instance/store/effects/search.effects.ts index f3ffcd4d55f29538f39c453a2b763b179796226e..b92513c667482841cf169ebeef11fa595520adc9 100644 --- a/client/src/app/instance/store/effects/search.effects.ts +++ b/client/src/app/instance/store/effects/search.effects.ts @@ -15,7 +15,7 @@ import { of } from 'rxjs'; import { map, tap, mergeMap, catchError } from 'rxjs/operators'; import { ToastrService } from 'ngx-toastr'; -import { criterionToString, stringToCriterion } from '../models'; +import { ConeSearch, criterionToString, stringToCriterion } from '../models'; import { SearchService } from '../services/search.service'; import * as searchActions from '../actions/search.actions'; import * as attributeActions from 'src/app/metamodel/actions/attribute.actions'; @@ -25,6 +25,8 @@ import * as outputFamilyActions from 'src/app/metamodel/actions/output-family.ac import * as outputCategoryActions from 'src/app/metamodel/actions/output-category.actions'; import * as datasetSelector from 'src/app/metamodel/selectors/dataset.selector'; import * as searchSelector from '../selectors/search.selector'; +import * as coneSearchActions from '../actions/cone-search.actions'; +import * as coneSearchSelector from '../selectors/cone-search.selector'; @Injectable() export class SearchEffects { @@ -88,11 +90,19 @@ export class SearchEffects { this.actions$.pipe( ofType(searchActions.loadDefaultFormParameters), concatLatestFrom(() => [ + this.store.select(searchSelector.selectPristine), + this.store.select(searchSelector.selectCurrentDataset), this.store.select(attributeSelector.selectAllAttributes), this.store.select(searchSelector.selectCriteriaListByRoute), + this.store.select(coneSearchSelector.selectConeSearchByRoute), this.store.select(searchSelector.selectOutputListByRoute) ]), - mergeMap(([action, attributeList, criteriaList, outputList]) => { + mergeMap(([action, pristine, currentDataset, attributeList, criteriaList, coneSearch, outputList]) => { + if (!pristine || !currentDataset) { + // Default form parameters already loaded or no dataset selected + return of({ type: '[No Action] Load Default Form Parameters' }); + } + // Update criteria list let defaultCriteriaList = []; if (criteriaList) { @@ -109,6 +119,17 @@ export class SearchEffects { .map(attribute => stringToCriterion(attribute)); } + // Update cone search + let defaultConeSearch: ConeSearch = null; + if (coneSearch) { + const params = coneSearch.split(':'); + defaultConeSearch = { + ra: +params[0], + dec: +params[1], + radius: +params[2] + }; + } + // Update output list let defaultOutputList = []; if (outputList) { @@ -124,6 +145,7 @@ export class SearchEffects { // Returns actions and mark the form as dirty return [ searchActions.updateCriteriaList({ criteriaList: defaultCriteriaList }), + coneSearchActions.addConeSearch({ coneSearch: defaultConeSearch }), searchActions.updateOutputList({ outputList: defaultOutputList }), searchActions.markAsDirty() ]; @@ -136,13 +158,17 @@ export class SearchEffects { ofType(searchActions.retrieveDataLength), concatLatestFrom(() => [ this.store.select(datasetSelector.selectDatasetNameByRoute), - this.store.select(searchSelector.selectCriteriaList) + this.store.select(searchSelector.selectCriteriaList), + this.store.select(coneSearchSelector.selectConeSearch) ]), - mergeMap(([action, datasetName, criteriaList]) => { + mergeMap(([action, datasetName, criteriaList, coneSearch]) => { let query = datasetName + '?a=count'; if (criteriaList.length > 0) { query += '&c=' + criteriaList.map(criterion => criterionToString(criterion)).join(';'); } + if (coneSearch) { + query += '&cs=' + coneSearch.ra + ':' + coneSearch.dec + ':' + coneSearch.radius; + } return this.searchService.retrieveDataLength(query) .pipe( @@ -166,13 +192,18 @@ export class SearchEffects { concatLatestFrom(() => [ this.store.select(datasetSelector.selectDatasetNameByRoute), this.store.select(searchSelector.selectCriteriaList), + this.store.select(coneSearchSelector.selectConeSearch), this.store.select(searchSelector.selectOutputList) ]), - mergeMap(([action, datasetName, criteriaList, outputList]) => { + mergeMap(([action, datasetName, criteriaList, coneSearch, outputList]) => { let query = datasetName + '?a=' + outputList.join(';'); if (criteriaList.length > 0) { query += '&c=' + criteriaList.map(criterion => criterionToString(criterion)).join(';'); } + if (coneSearch) { + query += '&cs=' + coneSearch.ra + ':' + coneSearch.dec + ':' + coneSearch.radius; + } + query += '&p=' + action.pagination.nbItems + ':' + action.pagination.page; query += '&o=' + action.pagination.sortedCol + ':' + action.pagination.order; diff --git a/client/src/app/instance/store/reducers/cone-search.reducer.ts b/client/src/app/instance/store/reducers/cone-search.reducer.ts new file mode 100644 index 0000000000000000000000000000000000000000..6a4ee35ce53cc7f5271515cf2811850994e04353 --- /dev/null +++ b/client/src/app/instance/store/reducers/cone-search.reducer.ts @@ -0,0 +1,59 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { createReducer, on } from '@ngrx/store'; + +import * as coneSearchActions from '../actions/cone-search.actions'; +import { ConeSearch, Resolver } from '../models'; + +export interface State { + coneSearch: ConeSearch; + resolver: Resolver; + resolverIsLoading: boolean; + resolverIsLoaded: boolean; +} + +export const initialState: State = { + coneSearch: null, + resolver: null, + resolverIsLoading: false, + resolverIsLoaded: false +} + +export const coneSearchReducer = createReducer( + initialState, + on(coneSearchActions.addConeSearch, (state, { coneSearch }) => ({ + ...state, + coneSearch + })), + on(coneSearchActions.deleteConeSearch, state => ({ + ...state, + coneSearch: null + })), + on(coneSearchActions.retrieveCoordinates, state => ({ + ...state, + resolverIsLoading: true, + resolverIsLoaded: false + })), + on(coneSearchActions.retrieveCoordinatesSuccess, (state, { resolver }) => ({ + ...state, + resolver, + resolverIsLoading: false, + resolverIsLoaded: true + })), + on(coneSearchActions.retrieveCoordinatesFail, state => ({ + ...state, + resolverIsLoading: false + })) +); + +export const selectConeSearch = (state: State) => state.coneSearch; +export const selectResolver = (state: State) => state.resolver; +export const selectResolverIsLoading = (state: State) => state.resolverIsLoading; +export const selectResolverIsLoaded = (state: State) => state.resolverIsLoaded; diff --git a/client/src/app/instance/store/reducers/detail.reducer.ts b/client/src/app/instance/store/reducers/detail.reducer.ts new file mode 100644 index 0000000000000000000000000000000000000000..466fcfc1838c6e79d790cf87fafb944b96081189 --- /dev/null +++ b/client/src/app/instance/store/reducers/detail.reducer.ts @@ -0,0 +1,71 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { createReducer, on } from '@ngrx/store'; + +import * as detailActions from '../actions/detail.actions'; + +export interface State { + object: any; + objectIsLoading: boolean; + objectIsLoaded: boolean; + spectraCSV: string; + spectraIsLoading: boolean; + spectraIsLoaded: boolean; +} + +export const initialState: State = { + object: null, + objectIsLoading: false, + objectIsLoaded: false, + spectraCSV: null, + spectraIsLoading: false, + spectraIsLoaded: false, +}; + +export const detailReducer = createReducer( + initialState, + on(detailActions.retrieveObject, state => ({ + ...state, + objectIsLoading: true, + objectIsLoaded: false + })), + on(detailActions.retrieveObjectSuccess, (state, { object }) => ({ + ...state, + objectIsLoading: false, + objectIsLoaded: true, + object + })), + on(detailActions.retrieveObjectFail, state => ({ + ...state, + objectIsLoading: false + })), + on(detailActions.retrieveSpectra, state => ({ + ...state, + spectraIsLoading: true, + spectraIsLoaded: false + })), + on(detailActions.retrieveSpectraSuccess, (state, { spectraCSV }) => ({ + ...state, + spectraIsLoading: false, + spectraIsLoaded: true, + spectraCSV + })), + on(detailActions.retrieveSpectraFail, state => ({ + ...state, + spectraIsLoading: false + })) +); + +export const selectObject = (state: State) => state.object; +export const selectObjectIsLoading = (state: State) => state.objectIsLoading; +export const selectObjectIsLoaded = (state: State) => state.objectIsLoaded; +export const selectSpectraCSV = (state: State) => state.spectraCSV; +export const selectSpectraIsLoading = (state: State) => state.spectraIsLoading; +export const selectSpectraIsLoaded = (state: State) => state.spectraIsLoaded; diff --git a/client/src/app/instance/store/selectors/cone-search.selector.ts b/client/src/app/instance/store/selectors/cone-search.selector.ts new file mode 100644 index 0000000000000000000000000000000000000000..695bba185eacc01db90b4dfface55f691ee3d3c7 --- /dev/null +++ b/client/src/app/instance/store/selectors/cone-search.selector.ts @@ -0,0 +1,43 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { createSelector } from '@ngrx/store'; + +import * as reducer from '../../instance.reducer'; +import * as fromConeSearch from '../reducers/cone-search.reducer'; + +export const selectConeSearchState = createSelector( + reducer.getInstanceState, + (state: reducer.State) => state.coneSearch +); + +export const selectConeSearch = createSelector( + selectConeSearchState, + fromConeSearch.selectConeSearch +); + +export const selectResolver = createSelector( + selectConeSearchState, + fromConeSearch.selectResolver +); + +export const selectResolverIsLoading = createSelector( + selectConeSearchState, + fromConeSearch.selectResolverIsLoading +); + +export const selectResolverIsLoaded = createSelector( + selectConeSearchState, + fromConeSearch.selectResolverIsLoaded +); + +export const selectConeSearchByRoute = createSelector( + reducer.selectRouterState, + router => router.state.queryParams.cs as string +); diff --git a/client/src/app/instance/store/selectors/detail.selector.ts b/client/src/app/instance/store/selectors/detail.selector.ts new file mode 100644 index 0000000000000000000000000000000000000000..2fe62d5722257342c255c5b4680e6883ecad19f5 --- /dev/null +++ b/client/src/app/instance/store/selectors/detail.selector.ts @@ -0,0 +1,53 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { createSelector } from '@ngrx/store'; + +import * as reducer from '../../instance.reducer'; +import * as fromDetail from '../reducers/detail.reducer'; + +export const selectDetailState = createSelector( + reducer.getInstanceState, + (state: reducer.State) => state.detail +); + +export const selectObject = createSelector( + selectDetailState, + fromDetail.selectObject +); + +export const selectObjectIsLoading = createSelector( + selectDetailState, + fromDetail.selectObjectIsLoading +); + +export const selectObjectIsLoaded = createSelector( + selectDetailState, + fromDetail.selectObjectIsLoaded +); + +export const selectSpectraCSV = createSelector( + selectDetailState, + fromDetail.selectSpectraCSV +); + +export const selectSpectraIsLoading = createSelector( + selectDetailState, + fromDetail.selectSpectraIsLoading +); + +export const selectSpectraIsLoaded = createSelector( + selectDetailState, + fromDetail.selectSpectraIsLoaded +); + +export const selectIdByRoute = createSelector( + reducer.selectRouterState, + router => router.state.params.id +); diff --git a/client/src/app/instance/store/selectors/search.selector.ts b/client/src/app/instance/store/selectors/search.selector.ts index ee70032ca896242b28fe17f59bd61982c7159f74..44ebe388b9cde19e8bbfe3eff35a5bb9e7b68ecd 100644 --- a/client/src/app/instance/store/selectors/search.selector.ts +++ b/client/src/app/instance/store/selectors/search.selector.ts @@ -9,103 +9,106 @@ import { createSelector } from '@ngrx/store'; -import { Criterion, SearchQueryParams, criterionToString } from '../models'; +import { Criterion, SearchQueryParams, criterionToString, ConeSearch } from '../models'; import * as reducer from '../../instance.reducer'; import * as fromSearch from '../reducers/search.reducer'; +import * as coneSearchSelector from './cone-search.selector'; -export const selectInstanceState = createSelector( +export const selectSearchState = createSelector( reducer.getInstanceState, (state: reducer.State) => state.search ); export const selectPristine = createSelector( - selectInstanceState, + selectSearchState, fromSearch.selectPristine ); export const selectCurrentDataset = createSelector( - selectInstanceState, + selectSearchState, fromSearch.selectCurrentDataset ); export const selectCurrentStep = createSelector( - selectInstanceState, + selectSearchState, fromSearch.selectCurrentStep ); export const selectCriteriaStepChecked = createSelector( - selectInstanceState, + selectSearchState, fromSearch.selectCriteriaStepChecked ); export const selectOutputStepChecked = createSelector( - selectInstanceState, + selectSearchState, fromSearch.selectOutputStepChecked ); export const selectResultStepChecked = createSelector( - selectInstanceState, + selectSearchState, fromSearch.selectResultStepChecked ); export const selectIsConeSearchAdded = createSelector( - selectInstanceState, + selectSearchState, fromSearch.selectIsConeSearchAdded ); export const selectCriteriaList = createSelector( - selectInstanceState, + selectSearchState, fromSearch.selectCriteriaList ); export const selectOutputList = createSelector( - selectInstanceState, + selectSearchState, fromSearch.selectOutputList ); export const selectDataLengthIsLoading = createSelector( - selectInstanceState, + selectSearchState, fromSearch.selectDataLengthIsLoading ); export const selectDataLengthIsLoaded = createSelector( - selectInstanceState, + selectSearchState, fromSearch.selectDataLengthIsLoaded ); export const selectDataLength = createSelector( - selectInstanceState, + selectSearchState, fromSearch.selectDataLength ); export const selectDataIsLoading = createSelector( - selectInstanceState, + selectSearchState, fromSearch.selectDataIsLoading ); export const selectDataIsLoaded = createSelector( - selectInstanceState, + selectSearchState, fromSearch.selectDataIsLoaded ); export const selectData = createSelector( - selectInstanceState, + selectSearchState, fromSearch.selectData ); export const selectSelectedData = createSelector( - selectInstanceState, + selectSearchState, fromSearch.selectSelectedData ); export const selectQueryParams = createSelector( selectCriteriaList, + coneSearchSelector.selectConeSearch, selectOutputList, selectCriteriaStepChecked, selectOutputStepChecked, selectResultStepChecked, ( criteriaList: Criterion[], + coneSearch: ConeSearch, outputList: number[], criteriaStepChecked: boolean, outputStepChecked: boolean, @@ -118,6 +121,12 @@ export const selectQueryParams = createSelector( s: step, a: outputList.join(';') }; + if (coneSearch) { + queryParams = { + ...queryParams, + cs: coneSearch.ra + ':' + coneSearch.dec + ':' + coneSearch.radius + }; + } if (criteriaList.length > 0) { queryParams = { ...queryParams, diff --git a/client/src/app/instance/store/services/cone-search.service.ts b/client/src/app/instance/store/services/cone-search.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..37c3420e5f1af5b426de2cf6bc6c8885e152f8bb --- /dev/null +++ b/client/src/app/instance/store/services/cone-search.service.ts @@ -0,0 +1,33 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +@Injectable() +/** + * @class + * @classdesc Cone search service. + */ +export class ConeSearchService { + constructor(private http: HttpClient) { } + + /** + * Retrieves coordinates of an object name. + * + * @param {string} name - The name of the object. + * + * @return Observable<any> + */ + retrieveCoordinates(name: string): Observable<any> { + const url = 'https://cdsweb.u-strasbg.fr/cgi-bin/nph-sesame/-ox/NSV?' + name; + return this.http.get(url, { responseType: 'text' }); + } +} diff --git a/client/src/app/instance/store/services/detail.service.ts b/client/src/app/instance/store/services/detail.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..3b946c5b17a2e9c649be86c4fa267ebe32f4907d --- /dev/null +++ b/client/src/app/instance/store/services/detail.service.ts @@ -0,0 +1,50 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +import { Observable } from 'rxjs'; + +import { AppConfigService } from 'src/app/app-config.service'; + +@Injectable() +/** + * @class + * @classdesc Detail service. + */ +export class DetailService { + constructor(private http: HttpClient, private config: AppConfigService) { } + + /** + * Retrieves object details for the given parameters. + * + * @param {string} dname - The dataset name. + * @param {number} criterionId - The criterion ID. + * @param {string} objectSelected - The selected object ID. + * @param {number[]} outputList - The output list. + * + * @return Observable<any[]> + */ + retrieveObject(dname: string, criterionId: number, objectSelected: string, outputList: number[]): Observable<any[]> { + const query = dname + '?c=' + criterionId + '::eq::' + objectSelected + '&a=' + outputList.join(';'); + return this.http.get<any[]>(this.config.apiUrl + '/search/' + query); + } + + /** + * Retrieves object details for the given parameters. + * + * @param {string} spectraFile - The spectra file name. + * + * @return Observable<string> + */ + retrieveSpectra(dname: string, spectraFile: string): Observable<string> { + return this.http.get(this.config.servicesUrl + '/spectra-to-csv/' + dname + '?filename=' + spectraFile, { responseType: 'text' }); + } +} diff --git a/client/src/app/instance/store/services/index.ts b/client/src/app/instance/store/services/index.ts index 136f9cb320ff0395ea365076e1f7753d31f9f680..cabc079690c1bfea8a44fddb9baa7123796937b2 100644 --- a/client/src/app/instance/store/services/index.ts +++ b/client/src/app/instance/store/services/index.ts @@ -1,7 +1,11 @@ import { SearchService } from './search.service'; import { SampService } from './samp.service'; +import { ConeSearchService } from './cone-search.service'; +import { DetailService } from './detail.service'; export const instanceServices = [ SearchService, - SampService + SampService, + ConeSearchService, + DetailService ]; diff --git a/client/src/app/instance/store/services/samp.service.ts b/client/src/app/instance/store/services/samp.service.ts index c4a8a9dbb612ac46a8690fe0d70826875aade414..1429c9bf7394825137519be7bf9830021822d274 100644 --- a/client/src/app/instance/store/services/samp.service.ts +++ b/client/src/app/instance/store/services/samp.service.ts @@ -11,7 +11,7 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; -import { environment } from 'src/environments/environment'; +import { AppConfigService } from 'src/app/app-config.service'; declare var samp: any; @@ -23,8 +23,8 @@ declare var samp: any; export class SampService { private connector = null; - constructor() { - const baseUrl = window.location.protocol + "//" + window.location.host + environment.baseHref; + constructor(private config: AppConfigService) { + const baseUrl = window.location.protocol + "//" + window.location.host + this.config.baseHref; const meta = { "samp.name": "ANIS", "samp.description.text": "AstroNomical Information System", diff --git a/client/src/app/instance/store/services/search.service.ts b/client/src/app/instance/store/services/search.service.ts index 7f5321317525144fe8d1e7da4670f892d5ac6d7c..171814321dc1e71d546be92e53c614ccd4d36410 100644 --- a/client/src/app/instance/store/services/search.service.ts +++ b/client/src/app/instance/store/services/search.service.ts @@ -12,7 +12,7 @@ import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; -import { environment } from 'src/environments/environment'; +import { AppConfigService } from 'src/app/app-config.service'; @Injectable() /** @@ -20,9 +20,7 @@ import { environment } from 'src/environments/environment'; * @classdesc Search service. */ export class SearchService { - API_PATH: string = environment.apiUrl; - - constructor(private http: HttpClient) { } + constructor(private http: HttpClient, private config: AppConfigService) { } /** * Retrieves results for the given parameters. @@ -32,7 +30,7 @@ export class SearchService { * @return Observable<any[]> */ retrieveData(query: string): Observable<any[]> { - return this.http.get<any[]>(this.API_PATH + '/search/' + query); + return this.http.get<any[]>(this.config.apiUrl + '/search/' + query); } /** @@ -43,6 +41,6 @@ export class SearchService { * @return Observable<{ nb: number }[]> */ retrieveDataLength(query: string): Observable<{ nb: number }[]> { - return this.http.get<{ nb: number }[]>(this.API_PATH + '/search/' + query); + return this.http.get<{ nb: number }[]>(this.config.apiUrl + '/search/' + query); } } diff --git a/client/src/app/metamodel/services/attribute-distinct.service.ts b/client/src/app/metamodel/services/attribute-distinct.service.ts index bc89ba7c5f2127f4eda6d7fb5b24e91b66eb42f7..e5466159309a507362dfe260e8f33073516a4fd5 100644 --- a/client/src/app/metamodel/services/attribute-distinct.service.ts +++ b/client/src/app/metamodel/services/attribute-distinct.service.ts @@ -13,15 +13,13 @@ import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { Attribute } from '../models'; -import { environment } from 'src/environments/environment'; +import { AppConfigService } from 'src/app/app-config.service'; @Injectable() export class AttributeDistinctService { - private API_PATH: string = environment.apiUrl + '/'; - - constructor(private http: HttpClient) { } + constructor(private http: HttpClient, private config: AppConfigService) { } retrieveAttributeDistinctList(datasetName: string, attribute: Attribute): Observable<string[]> { - return this.http.get<string[]>(this.API_PATH + 'dataset/' + datasetName + '/attribute/' + attribute.id + '/distinct'); + return this.http.get<string[]>(this.config.apiUrl + '/dataset/' + datasetName + '/attribute/' + attribute.id + '/distinct'); } } diff --git a/client/src/app/metamodel/services/attribute.service.ts b/client/src/app/metamodel/services/attribute.service.ts index 08c84d6cfbc6ac70cfa3c755d00c82d4bce46860..9c2583c71f1c1af80bda0ad53daa8c9402ff11fc 100644 --- a/client/src/app/metamodel/services/attribute.service.ts +++ b/client/src/app/metamodel/services/attribute.service.ts @@ -13,27 +13,25 @@ import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { Attribute } from '../models'; -import { environment } from 'src/environments/environment'; +import { AppConfigService } from 'src/app/app-config.service'; @Injectable() export class AttributeService { - private API_PATH: string = environment.apiUrl + '/'; - - constructor(private http: HttpClient) { } + constructor(private http: HttpClient, private config: AppConfigService) { } retrieveAttributeList(datasetName: string): Observable<Attribute[]> { - return this.http.get<Attribute[]>(this.API_PATH + 'dataset/' + datasetName + '/attribute'); + return this.http.get<Attribute[]>(this.config.apiUrl + '/dataset/' + datasetName + '/attribute'); } addAttribute(datasetName: string, attribute: Attribute): Observable<Attribute> { - return this.http.post<Attribute>(this.API_PATH + 'dataset/' + datasetName + '/attribute', attribute); + return this.http.post<Attribute>(this.config.apiUrl + '/dataset/' + datasetName + '/attribute', attribute); } editAttribute(datasetName: string, attribute: Attribute): Observable<Attribute> { - return this.http.put<Attribute>(this.API_PATH + 'dataset/' + datasetName + '/attribute/' + attribute.id, attribute); + return this.http.put<Attribute>(this.config.apiUrl + '/dataset/' + datasetName + '/attribute/' + attribute.id, attribute); } deleteAttribute(datasetName: string, attribute: Attribute) { - return this.http.delete(this.API_PATH + 'dataset/' + datasetName + '/attribute/' + attribute.id); + return this.http.delete(this.config.apiUrl + '/dataset/' + datasetName + '/attribute/' + attribute.id); } } diff --git a/client/src/app/metamodel/services/column.service.ts b/client/src/app/metamodel/services/column.service.ts index 881006be1a815b3960e5be1d012e1dce0a25d97d..0da2af5345283d3374916e1fc40b4bc4cd9ef2cd 100644 --- a/client/src/app/metamodel/services/column.service.ts +++ b/client/src/app/metamodel/services/column.service.ts @@ -13,15 +13,13 @@ import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { Column } from '../models'; -import { environment } from 'src/environments/environment'; +import { AppConfigService } from 'src/app/app-config.service'; @Injectable() export class ColumnService { - private API_PATH: string = environment.apiUrl + '/'; - - constructor(private http: HttpClient) { } + constructor(private http: HttpClient, private config: AppConfigService) { } retrieveColumns(datasetName: string): Observable<Column[]> { - return this.http.get<Column[]>(this.API_PATH + 'dataset/' + datasetName + '/column'); + return this.http.get<Column[]>(this.config.apiUrl + '/dataset/' + datasetName + '/column'); } } diff --git a/client/src/app/metamodel/services/criteria-family.service.ts b/client/src/app/metamodel/services/criteria-family.service.ts index 32e2ecfabedacdf8282e5e97626e35d25ec6d5a3..09d6dd21a477f150f8de5f5fe0a6796e0860966f 100644 --- a/client/src/app/metamodel/services/criteria-family.service.ts +++ b/client/src/app/metamodel/services/criteria-family.service.ts @@ -13,27 +13,25 @@ import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { CriteriaFamily } from '../models'; -import { environment } from 'src/environments/environment'; +import { AppConfigService } from 'src/app/app-config.service'; @Injectable() export class CriteriaFamilyService { - private API_PATH: string = environment.apiUrl + '/'; - - constructor(private http: HttpClient) { } + constructor(private http: HttpClient, private config: AppConfigService) { } retrieveCriteriaFamilyList(datasetName: string): Observable<CriteriaFamily[]> { - return this.http.get<CriteriaFamily[]>(this.API_PATH + 'dataset/' + datasetName + '/criteria-family'); + return this.http.get<CriteriaFamily[]>(this.config.apiUrl + '/dataset/' + datasetName + '/criteria-family'); } addCriteriaFamily(datasetName: string, newCriteriaFamily: CriteriaFamily): Observable<CriteriaFamily> { - return this.http.post<CriteriaFamily>(this.API_PATH + 'dataset/' + datasetName + '/criteria-family', newCriteriaFamily); + return this.http.post<CriteriaFamily>(this.config.apiUrl + '/dataset/' + datasetName + '/criteria-family', newCriteriaFamily); } editCriteriaFamily(criteriaFamily: CriteriaFamily): Observable<CriteriaFamily> { - return this.http.put<CriteriaFamily>(this.API_PATH + 'criteria-family/' + criteriaFamily.id, criteriaFamily); + return this.http.put<CriteriaFamily>(this.config.apiUrl + '/criteria-family/' + criteriaFamily.id, criteriaFamily); } deleteCriteriaFamily(criteriaFamilyId: number) { - return this.http.delete(this.API_PATH + 'criteria-family/' + criteriaFamilyId); + return this.http.delete(this.config.apiUrl + '/criteria-family/' + criteriaFamilyId); } } diff --git a/client/src/app/metamodel/services/database.service.ts b/client/src/app/metamodel/services/database.service.ts index 567254fd3a852f25994ffc31b4264168bd092164..c569d351670179e73ccf3067dafa12bfdd6628e3 100644 --- a/client/src/app/metamodel/services/database.service.ts +++ b/client/src/app/metamodel/services/database.service.ts @@ -13,27 +13,25 @@ import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { Database } from '../models'; -import { environment } from 'src/environments/environment'; +import { AppConfigService } from 'src/app/app-config.service'; @Injectable() export class DatabaseService { - private API_PATH: string = environment.apiUrl + '/'; - - constructor(private http: HttpClient) { } + constructor(private http: HttpClient, private config: AppConfigService) { } retrieveDatabaseList(): Observable<Database[]> { - return this.http.get<Database[]>(this.API_PATH + 'database'); + return this.http.get<Database[]>(this.config.apiUrl + '/database'); } addDatabase(newDatabase: Database): Observable<Database> { - return this.http.post<Database>(this.API_PATH + 'database', newDatabase); + return this.http.post<Database>(this.config.apiUrl + '/database', newDatabase); } editDatabase(database: Database): Observable<Database> { - return this.http.put<Database>(this.API_PATH + 'database/' + database.id, database); + return this.http.put<Database>(this.config.apiUrl + '/database/' + database.id, database); } deleteDatabase(databaseId: number) { - return this.http.delete(this.API_PATH + 'database/' + databaseId); + return this.http.delete(this.config.apiUrl + '/database/' + databaseId); } } diff --git a/client/src/app/metamodel/services/dataset-family.service.ts b/client/src/app/metamodel/services/dataset-family.service.ts index 490a14af0b92afe8c121d4d54938b489826e51c6..96fc34460b1c5520a754f5f1afb9edafcde8d821 100644 --- a/client/src/app/metamodel/services/dataset-family.service.ts +++ b/client/src/app/metamodel/services/dataset-family.service.ts @@ -13,27 +13,25 @@ import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { DatasetFamily } from '../models'; -import { environment } from 'src/environments/environment'; +import { AppConfigService } from 'src/app/app-config.service'; @Injectable() export class DatasetFamilyService { - private API_PATH: string = environment.apiUrl + '/'; - - constructor(private http: HttpClient) { } + constructor(private http: HttpClient, private config: AppConfigService) { } retrieveDatasetFamilyList(instanceName: string): Observable<DatasetFamily[]> { - return this.http.get<DatasetFamily[]>(this.API_PATH + 'instance/' + instanceName + '/dataset-family'); + return this.http.get<DatasetFamily[]>(this.config.apiUrl + '/instance/' + instanceName + '/dataset-family'); } addDatasetFamily(instanceName: string, newDatasetFamily: DatasetFamily): Observable<DatasetFamily> { - return this.http.post<DatasetFamily>(this.API_PATH + 'instance/' + instanceName + '/dataset-family', newDatasetFamily); + return this.http.post<DatasetFamily>(this.config.apiUrl + '/instance/' + instanceName + '/dataset-family', newDatasetFamily); } editDatasetFamily(datasetFamily: DatasetFamily): Observable<DatasetFamily> { - return this.http.put<DatasetFamily>(this.API_PATH + 'dataset-family/' + datasetFamily.id, datasetFamily); + return this.http.put<DatasetFamily>(this.config.apiUrl + '/dataset-family/' + datasetFamily.id, datasetFamily); } deleteDatasetFamily(datasetFamilyId: number) { - return this.http.delete(this.API_PATH + 'dataset-family/' + datasetFamilyId); + return this.http.delete(this.config.apiUrl + '/dataset-family/' + datasetFamilyId); } } diff --git a/client/src/app/metamodel/services/dataset.service.ts b/client/src/app/metamodel/services/dataset.service.ts index 4b7c9b8369c11e0f9d2682ea36bd2f16f6a11d05..e8109448278f7e10f9e0f486eb25513c988c3265 100644 --- a/client/src/app/metamodel/services/dataset.service.ts +++ b/client/src/app/metamodel/services/dataset.service.ts @@ -13,27 +13,25 @@ import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { Dataset } from '../models'; -import { environment } from 'src/environments/environment'; +import { AppConfigService } from 'src/app/app-config.service'; @Injectable() export class DatasetService { - private API_PATH: string = environment.apiUrl + '/'; - - constructor(private http: HttpClient) { } + constructor(private http: HttpClient, private config: AppConfigService) { } retrieveDatasetList(instanceName: string): Observable<Dataset[]> { - return this.http.get<Dataset[]>(this.API_PATH + 'instance/' + instanceName + '/dataset'); + return this.http.get<Dataset[]>(this.config.apiUrl + '/instance/' + instanceName + '/dataset'); } addDataset(newDataset: Dataset): Observable<Dataset> { - return this.http.post<Dataset>(this.API_PATH + 'dataset-family/' + newDataset.id_dataset_family + '/dataset', newDataset); + return this.http.post<Dataset>(this.config.apiUrl + '/dataset-family/' + newDataset.id_dataset_family + '/dataset', newDataset); } editDataset(dataset: Dataset): Observable<Dataset> { - return this.http.put<Dataset>(this.API_PATH + 'dataset/' + dataset.name, dataset); + return this.http.put<Dataset>(this.config.apiUrl + '/dataset/' + dataset.name, dataset); } deleteDataset(datasetName: string) { - return this.http.delete(this.API_PATH + 'dataset/' + datasetName); + return this.http.delete(this.config.apiUrl + '/dataset/' + datasetName); } } diff --git a/client/src/app/metamodel/services/group.service.ts b/client/src/app/metamodel/services/group.service.ts index c4cbdde647f2b446576a48834e6de42d2913995b..b83489bebbbc04d63fcbfb237ef359266bd27348 100644 --- a/client/src/app/metamodel/services/group.service.ts +++ b/client/src/app/metamodel/services/group.service.ts @@ -13,27 +13,25 @@ import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { Group } from '../models'; -import { environment } from 'src/environments/environment'; +import { AppConfigService } from 'src/app/app-config.service'; @Injectable() export class GroupService { - private API_PATH: string = environment.apiUrl + '/'; - - constructor(private http: HttpClient) { } + constructor(private http: HttpClient, private config: AppConfigService) { } retrieveGroupList(instanceName: string): Observable<Group[]> { - return this.http.get<Group[]>(this.API_PATH + 'instance/' + instanceName + '/group'); + return this.http.get<Group[]>(this.config.apiUrl + '/instance/' + instanceName + '/group'); } addGroup(instanceName: string, newGroup: Group): Observable<Group> { - return this.http.post<Group>(this.API_PATH + 'instance/' + instanceName + '/group', newGroup); + return this.http.post<Group>(this.config.apiUrl + '/instance/' + instanceName + '/group', newGroup); } editGroup(group: Group): Observable<Group> { - return this.http.put<Group>(this.API_PATH + 'group/' + group.id, group); + return this.http.put<Group>(this.config.apiUrl + '/group/' + group.id, group); } deleteGroup(groupId: number) { - return this.http.delete(this.API_PATH + 'group/' + groupId); + return this.http.delete(this.config.apiUrl + '/group/' + groupId); } } diff --git a/client/src/app/metamodel/services/instance.service.ts b/client/src/app/metamodel/services/instance.service.ts index a5f4ab0e57f7dbdd4db7cebf56ff7ae8179e472a..d90f7f157acc9051687bd93469a49c46f9aa6e7f 100644 --- a/client/src/app/metamodel/services/instance.service.ts +++ b/client/src/app/metamodel/services/instance.service.ts @@ -13,27 +13,25 @@ import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { Instance } from '../models'; -import { environment } from 'src/environments/environment'; +import { AppConfigService } from 'src/app/app-config.service'; @Injectable() export class InstanceService { - private API_PATH: string = environment.apiUrl + '/'; - - constructor(private http: HttpClient) { } + constructor(private http: HttpClient, private config: AppConfigService) { } retrieveInstanceList(): Observable<Instance[]> { - return this.http.get<Instance[]>(this.API_PATH + 'instance'); + return this.http.get<Instance[]>(this.config.apiUrl + '/instance'); } addInstance(newInstance: Instance): Observable<Instance> { - return this.http.post<Instance>(this.API_PATH + 'instance', newInstance); + return this.http.post<Instance>(this.config.apiUrl + '/instance', newInstance); } editInstance(instance: Instance): Observable<Instance> { - return this.http.put<Instance>(this.API_PATH + 'instance/' + instance.name, instance); + return this.http.put<Instance>(this.config.apiUrl + '/instance/' + instance.name, instance); } deleteInstance(instanceName: string) { - return this.http.delete(this.API_PATH + 'instance/' + instanceName); + return this.http.delete(this.config.apiUrl + '/instance/' + instanceName); } } diff --git a/client/src/app/metamodel/services/output-category.service.ts b/client/src/app/metamodel/services/output-category.service.ts index 2d35525d6071b2c049dba7025a8b42f943e0005f..5cc3e49dbd165c348df8dc60b7389b4592ff4e45 100644 --- a/client/src/app/metamodel/services/output-category.service.ts +++ b/client/src/app/metamodel/services/output-category.service.ts @@ -13,27 +13,25 @@ import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { OutputCategory } from '../models'; -import { environment } from 'src/environments/environment'; +import { AppConfigService } from 'src/app/app-config.service'; @Injectable() export class OutputCategoryService { - private API_PATH: string = environment.apiUrl + '/'; - - constructor(private http: HttpClient) { } + constructor(private http: HttpClient, private config: AppConfigService) { } retrieveOutputCategoryList(datasetName: string): Observable<OutputCategory[]> { - return this.http.get<OutputCategory[]>(this.API_PATH + 'dataset/' + datasetName + '/output-category'); + return this.http.get<OutputCategory[]>(this.config.apiUrl + '/dataset/' + datasetName + '/output-category'); } addOutputCategory(newOutputCategory: OutputCategory): Observable<OutputCategory> { - return this.http.post<OutputCategory>(this.API_PATH + 'output-family/' + newOutputCategory.id_output_family + '/output-category', newOutputCategory); + return this.http.post<OutputCategory>(this.config.apiUrl + '/output-family/' + newOutputCategory.id_output_family + '/output-category', newOutputCategory); } editOutputCategory(outputCategory: OutputCategory): Observable<OutputCategory> { - return this.http.put<OutputCategory>(this.API_PATH + 'output-category/' + outputCategory.id, outputCategory); + return this.http.put<OutputCategory>(this.config.apiUrl + '/output-category/' + outputCategory.id, outputCategory); } deleteOutputCategory(outputCategoryId: number) { - return this.http.delete(this.API_PATH + 'output-category/' + outputCategoryId); + return this.http.delete(this.config.apiUrl + '/output-category/' + outputCategoryId); } } diff --git a/client/src/app/metamodel/services/output-family.service.ts b/client/src/app/metamodel/services/output-family.service.ts index 61494ed2cbcf8b2da2959c40290073c97cd55763..825e191902bac95960727424fdbe483c9f9d1a5a 100644 --- a/client/src/app/metamodel/services/output-family.service.ts +++ b/client/src/app/metamodel/services/output-family.service.ts @@ -13,27 +13,25 @@ import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { OutputFamily } from '../models'; -import { environment } from 'src/environments/environment'; +import { AppConfigService } from 'src/app/app-config.service'; @Injectable() export class OutputFamilyService { - private API_PATH: string = environment.apiUrl + '/'; - - constructor(private http: HttpClient) { } + constructor(private http: HttpClient, private config: AppConfigService) { } retrieveOutputFamilyList(datasetName: string): Observable<OutputFamily[]> { - return this.http.get<OutputFamily[]>(this.API_PATH + 'dataset/' + datasetName + '/output-family'); + return this.http.get<OutputFamily[]>(this.config.apiUrl + '/dataset/' + datasetName + '/output-family'); } addOutputFamily(datasetName: string, newOutputFamily: OutputFamily): Observable<OutputFamily> { - return this.http.post<OutputFamily>(this.API_PATH + 'dataset/' + datasetName + '/output-family', newOutputFamily); + return this.http.post<OutputFamily>(this.config.apiUrl + '/dataset/' + datasetName + '/output-family', newOutputFamily); } editOutputFamily(criteriaFamily: OutputFamily): Observable<OutputFamily> { - return this.http.put<OutputFamily>(this.API_PATH + 'output-family/' + criteriaFamily.id, criteriaFamily); + return this.http.put<OutputFamily>(this.config.apiUrl + '/output-family/' + criteriaFamily.id, criteriaFamily); } deleteOutputFamily(outputFamilyId: number) { - return this.http.delete(this.API_PATH + 'output-family/' + outputFamilyId); + return this.http.delete(this.config.apiUrl + '/output-family/' + outputFamilyId); } } diff --git a/client/src/app/metamodel/services/root-directory.service.ts b/client/src/app/metamodel/services/root-directory.service.ts index c34f2c41a9f92d24917e928aca14e97b4f135610..d173f00d10b99e3872109fac4823c225a2a5a674 100644 --- a/client/src/app/metamodel/services/root-directory.service.ts +++ b/client/src/app/metamodel/services/root-directory.service.ts @@ -13,15 +13,13 @@ import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { FileInfo } from '../models'; -import { environment } from 'src/environments/environment'; +import { AppConfigService } from 'src/app/app-config.service'; @Injectable() export class RootDirectoryService { - private API_PATH: string = environment.apiUrl + '/'; - - constructor(private http: HttpClient) { } + constructor(private http: HttpClient, private config: AppConfigService) { } retrieveRootDirectory(path: string): Observable<FileInfo[]> { - return this.http.get<FileInfo[]>(this.API_PATH + 'file-explorer/' + path); + return this.http.get<FileInfo[]>(this.config.apiUrl + '/file-explorer/' + path); } } diff --git a/client/src/app/metamodel/services/select-option.service.ts b/client/src/app/metamodel/services/select-option.service.ts index 07012cfab5b1126b321c6bc5970229b24af65e7c..e18a0dd35e8b55fce4006525e6705909ec208881 100644 --- a/client/src/app/metamodel/services/select-option.service.ts +++ b/client/src/app/metamodel/services/select-option.service.ts @@ -13,27 +13,25 @@ import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { SelectOption } from '../models'; -import { environment } from 'src/environments/environment'; +import { AppConfigService } from 'src/app/app-config.service'; @Injectable() export class SelectOptionService { - private API_PATH: string = environment.apiUrl + '/'; - - constructor(private http: HttpClient) { } + constructor(private http: HttpClient, private config: AppConfigService) { } retrieveSelectOptionList(): Observable<SelectOption[]> { - return this.http.get<SelectOption[]>(this.API_PATH + 'option'); + return this.http.get<SelectOption[]>(this.config.apiUrl + '/option'); } addSelectOption(settingsSelectOption: SelectOption): Observable<SelectOption> { - return this.http.post<SelectOption>(this.API_PATH + 'option', settingsSelectOption); + return this.http.post<SelectOption>(this.config.apiUrl + '/option', settingsSelectOption); } editSelectOption(settingsSelectOption: SelectOption): Observable<SelectOption> { - return this.http.put<SelectOption>(this.API_PATH + 'option/' + settingsSelectOption.id, settingsSelectOption); + return this.http.put<SelectOption>(this.config.apiUrl + '/option/' + settingsSelectOption.id, settingsSelectOption); } deleteSelectOption(id: number) { - return this.http.delete(this.API_PATH + 'option/' + id); + return this.http.delete(this.config.apiUrl + '/option/' + id); } } diff --git a/client/src/app/metamodel/services/select.service.ts b/client/src/app/metamodel/services/select.service.ts index e88b7289c73060638d29462bc0fa79c5ea3d18fe..b146a874930054099765f9939310be7be1ab7c6b 100644 --- a/client/src/app/metamodel/services/select.service.ts +++ b/client/src/app/metamodel/services/select.service.ts @@ -13,27 +13,25 @@ import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { Select } from '../models'; -import { environment } from 'src/environments/environment'; +import { AppConfigService } from 'src/app/app-config.service'; @Injectable() export class SelectService { - private API_PATH: string = environment.apiUrl + '/'; - - constructor(private http: HttpClient) { } + constructor(private http: HttpClient, private config: AppConfigService) { } retrieveSelectList(): Observable<Select[]> { - return this.http.get<Select[]>(this.API_PATH + 'select'); + return this.http.get<Select[]>(this.config.apiUrl + '/select'); } addSelect(select: Select): Observable<Select> { - return this.http.post<Select>(this.API_PATH + 'select', select); + return this.http.post<Select>(this.config.apiUrl + '/select', select); } editSelect(select: Select): Observable<Select> { - return this.http.put<Select>(this.API_PATH + 'select/' + select.name, select); + return this.http.put<Select>(this.config.apiUrl + '/select/' + select.name, select); } deleteSelect(name: string) { - return this.http.delete(this.API_PATH + 'select/' + name); + return this.http.delete(this.config.apiUrl + '/select/' + name); } } diff --git a/client/src/app/metamodel/services/survey.service.ts b/client/src/app/metamodel/services/survey.service.ts index 6f482874f18e4d0eb4a8bdd64d89f9d93359604a..faf0c1ea4d6eff2c1d72c60f2fcf551b81f843bd 100644 --- a/client/src/app/metamodel/services/survey.service.ts +++ b/client/src/app/metamodel/services/survey.service.ts @@ -13,27 +13,25 @@ import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { Survey } from '../models'; -import { environment } from 'src/environments/environment'; +import { AppConfigService } from 'src/app/app-config.service'; @Injectable() export class SurveyService { - private API_PATH: string = environment.apiUrl + '/'; - - constructor(private http: HttpClient) { } + constructor(private http: HttpClient, private config: AppConfigService) { } retrieveSurveyList(): Observable<Survey[]> { - return this.http.get<Survey[]>(this.API_PATH + 'survey'); + return this.http.get<Survey[]>(this.config.apiUrl + '/survey'); } addSurvey(newSurvey: Survey): Observable<Survey> { - return this.http.post<Survey>(this.API_PATH + 'survey', newSurvey); + return this.http.post<Survey>(this.config.apiUrl + '/survey', newSurvey); } editSurvey(survey: Survey): Observable<Survey> { - return this.http.put<Survey>(this.API_PATH + 'survey/' + survey.name, survey); + return this.http.put<Survey>(this.config.apiUrl + '/survey/' + survey.name, survey); } deleteSurvey(surveyName: string) { - return this.http.delete(this.API_PATH + 'survey/' + surveyName); + return this.http.delete(this.config.apiUrl + '/survey/' + surveyName); } } diff --git a/client/src/app/metamodel/services/table.service.ts b/client/src/app/metamodel/services/table.service.ts index 96625dfd1e4221867c42c946a1ee262397c7aacf..3a3722b7f515825255cc08de75447101e4f65e5a 100644 --- a/client/src/app/metamodel/services/table.service.ts +++ b/client/src/app/metamodel/services/table.service.ts @@ -12,15 +12,13 @@ import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; -import { environment } from 'src/environments/environment'; +import { AppConfigService } from 'src/app/app-config.service'; @Injectable() export class TableService { - private API_PATH: string = environment.apiUrl + '/'; - - constructor(private http: HttpClient) { } + constructor(private http: HttpClient, private config: AppConfigService) { } retrieveTableList(idDatabase: number): Observable<string[]> { - return this.http.get<string[]>(this.API_PATH + 'database/' + idDatabase + '/table'); + return this.http.get<string[]>(this.config.apiUrl + '/database/' + idDatabase + '/table'); } } diff --git a/client/src/app/portal/containers/portal-home.component.html b/client/src/app/portal/containers/portal-home.component.html index 16f93da003ef45253778b21864f45a82588fee1a..7fab8209cb2ce3ec74a2982dbfec41e47c310268 100644 --- a/client/src/app/portal/containers/portal-home.component.html +++ b/client/src/app/portal/containers/portal-home.component.html @@ -3,6 +3,8 @@ [links]="links" [isAuthenticated]="isAuthenticated | async" [userProfile]="userProfile | async" + [baseHref]="getBaseHref()" + [authenticationEnabled]="getAuthenticationEnabled()" (login)="login()" (logout)="logout()" (openEditProfile)="openEditProfile()"> diff --git a/client/src/app/portal/containers/portal-home.component.spec.ts b/client/src/app/portal/containers/portal-home.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..6d707e36f09a66596196c426ec4fc632bcbf1ddd --- /dev/null +++ b/client/src/app/portal/containers/portal-home.component.spec.ts @@ -0,0 +1,88 @@ +import { Component, Input } from '@angular/core'; +import { TestBed, waitForAsync, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { provideMockStore, MockStore } from '@ngrx/store/testing'; + +import { AppConfigService } from 'src/app/app-config.service'; +import { UserProfile } from 'src/app/auth/user-profile.model'; +import { Instance } from 'src/app/metamodel/models'; +import * as authActions from 'src/app/auth/auth.actions'; +import { PortalHomeComponent } from './portal-home.component'; + +describe('PortalHomeComponent', () => { + @Component({ selector: 'app-navbar', template: '' }) + class NavbarStubComponent { + @Input() links: {label: string, icon: string, routerLink: string}[]; + @Input() isAuthenticated: boolean; + @Input() userProfile: UserProfile = null; + @Input() baseHref: string; + @Input() authenticationEnabled: boolean; + } + + @Component({ selector: 'app-instance-card', template: '' }) + class InstanceCardStubComponent { + @Input() instance: Instance; + } + + let component: PortalHomeComponent; + let fixture: ComponentFixture<PortalHomeComponent>; + let store: MockStore; + let config: AppConfigService + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule + ], + declarations: [ + PortalHomeComponent, + NavbarStubComponent, + InstanceCardStubComponent + ], + providers: [ + provideMockStore({ }), + AppConfigService + ] + }).compileComponents(); + fixture = TestBed.createComponent(PortalHomeComponent); + component = fixture.componentInstance; + store = TestBed.inject(MockStore); + config = TestBed.inject(AppConfigService); + })); + + it('should create the portal home component', () => { + expect(component).toBeDefined(); + }); + + it('getBaseHref() should give base href config key value', () => { + config.baseHref = '/my-project'; + expect(component.getBaseHref()).toBe('/my-project'); + }); + + it('authenticationEnabled() should give authentication enabled config key value', () => { + config.authenticationEnabled = true; + expect(component.getAuthenticationEnabled()).toBeTruthy(); + }); + + it('login() should dispatch login action', () => { + const spy = jest.spyOn(store, 'dispatch'); + component.login(); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith(authActions.login()); + }); + + it('logout() should dispatch logout action', () => { + const spy = jest.spyOn(store, 'dispatch'); + component.logout(); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith(authActions.logout()); + }); + + it('openEditProfile() should dispatch open edit profile action', () => { + const spy = jest.spyOn(store, 'dispatch'); + component.openEditProfile(); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith(authActions.openEditProfile()); + }); +}); diff --git a/client/src/app/portal/containers/portal-home.component.ts b/client/src/app/portal/containers/portal-home.component.ts index 78b1b7908034b394c7921731a610dbea31e8ac31..4405a97255d7be0a78375e525c845e425bf657c0 100644 --- a/client/src/app/portal/containers/portal-home.component.ts +++ b/client/src/app/portal/containers/portal-home.component.ts @@ -7,8 +7,8 @@ * file that was distributed with this source code. */ -import { Component, OnInit } from '@angular/core'; -import { Observable } from 'rxjs'; +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Observable, Subscription } from 'rxjs'; import { Store } from '@ngrx/store'; import { UserProfile } from 'src/app/auth/user-profile.model'; @@ -17,6 +17,7 @@ import * as authActions from 'src/app/auth/auth.actions'; import * as authSelector from 'src/app/auth/auth.selector'; import * as instanceActions from 'src/app/metamodel/actions/instance.actions'; import * as instanceSelector from 'src/app/metamodel/selectors/instance.selector'; +import { AppConfigService } from 'src/app/app-config.service'; @Component({ selector: 'app-portal-home', @@ -28,10 +29,9 @@ import * as instanceSelector from 'src/app/metamodel/selectors/instance.selector * * @implements OnInit */ -export class PortalHomeComponent implements OnInit { +export class PortalHomeComponent implements OnInit, OnDestroy { public links = [ - { label: 'Home', icon: 'fas fa-home', routerLink: '/portal-home' }, - { label: 'Admin', icon: 'fas fa-tools', routerLink: '/admin' } + { label: 'Home', icon: 'fas fa-home', routerLink: '/portal' } ]; public isAuthenticated: Observable<boolean>; public userProfile: Observable<UserProfile>; @@ -40,7 +40,9 @@ export class PortalHomeComponent implements OnInit { public instanceListIsLoaded: Observable<boolean>; public instanceList: Observable<Instance[]>; - constructor(private store: Store<{ }>) { + public userRolesSubscription: Subscription; + + constructor(private store: Store<{ }>, private config: AppConfigService) { this.isAuthenticated = store.select(authSelector.selectIsAuthenticated); this.userProfile = store.select(authSelector.selectUserProfile); this.userRoles = store.select(authSelector.selectUserRoles); @@ -51,8 +53,26 @@ export class PortalHomeComponent implements OnInit { ngOnInit() { this.store.dispatch(instanceActions.loadInstanceList()); + const adminLink = { label: 'Admin', icon: 'fas fa-tools', routerLink: '/admin' }; + if (!this.config.authenticationEnabled) { + this.links.push(adminLink); + } else { + this.userRolesSubscription = this.userRoles.subscribe(userRoles => { + if (userRoles.includes(this.config.adminRole)) { + this.links.push(adminLink); + } + }); + } + } + + getBaseHref() { + return this.config.baseHref; } + getAuthenticationEnabled() { + return this.config.authenticationEnabled; + } + login(): void { this.store.dispatch(authActions.login()); } @@ -64,4 +84,8 @@ export class PortalHomeComponent implements OnInit { openEditProfile(): void { this.store.dispatch(authActions.openEditProfile()); } + + ngOnDestroy() { + if (this.userRolesSubscription) this.userRolesSubscription.unsubscribe(); + } } diff --git a/client/src/app/portal/portal-routing.module.ts b/client/src/app/portal/portal-routing.module.ts index ae68f942c0b7732c57bbb5e7b68096cd16c1032b..3874af69e7b07bd30358857ec40196b68681f2f5 100644 --- a/client/src/app/portal/portal-routing.module.ts +++ b/client/src/app/portal/portal-routing.module.ts @@ -13,11 +13,12 @@ import { RouterModule, Routes } from '@angular/router'; import { PortalHomeComponent } from './containers/portal-home.component'; const routes: Routes = [ - { path: 'portal-home', component: PortalHomeComponent } + { path: '', redirectTo: 'home', pathMatch: 'full' }, + { path: 'home', component: PortalHomeComponent } ]; @NgModule({ - imports: [RouterModule.forRoot(routes)], + imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) export class PortalRoutingModule { } diff --git a/client/src/app/shared/components/navbar.component.ts b/client/src/app/shared/components/navbar.component.ts index c88f742045052debb35000bb6da163ff9f150e2f..733e55789e7d5b65bea006491b717cfdd758574f 100644 --- a/client/src/app/shared/components/navbar.component.ts +++ b/client/src/app/shared/components/navbar.component.ts @@ -10,7 +10,6 @@ import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; import { UserProfile } from 'src/app/auth/user-profile.model'; -import { environment } from 'src/environments/environment' @Component({ selector: 'app-navbar', @@ -22,10 +21,9 @@ export class NavbarComponent { @Input() links: {label: string, icon: string, routerLink: string}[]; @Input() isAuthenticated: boolean; @Input() userProfile: UserProfile = null; + @Input() baseHref: string; + @Input() authenticationEnabled: boolean; @Output() login: EventEmitter<any> = new EventEmitter(); @Output() logout: EventEmitter<any> = new EventEmitter(); @Output() openEditProfile: EventEmitter<any> = new EventEmitter(); - - baseHref: string = environment.baseHref; - authenticationEnabled: boolean = environment.authenticationEnabled; } diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts index b402511e14ee743e933a76b564ed0d8110bf17d4..4a68bd0d8d84a6118dfd4b7f0b13a6059ba385b9 100644 --- a/client/src/app/shared/shared.module.ts +++ b/client/src/app/shared/shared.module.ts @@ -12,7 +12,6 @@ import { CommonModule } from '@angular/common'; import { RouterModule } from '@angular/router'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { ToastrModule } from 'ngx-toastr'; import { CollapseModule } from 'ngx-bootstrap/collapse'; import { BsDropdownModule } from 'ngx-bootstrap/dropdown'; import { ModalModule } from 'ngx-bootstrap/modal'; @@ -38,7 +37,6 @@ import { sharedPipes } from './pipes'; RouterModule, FormsModule, ReactiveFormsModule, - ToastrModule.forRoot(), CollapseModule.forRoot(), BsDropdownModule.forRoot(), ModalModule.forRoot(), @@ -55,7 +53,6 @@ import { sharedPipes } from './pipes'; CommonModule, FormsModule, ReactiveFormsModule, - ToastrModule, CollapseModule, BsDropdownModule, ModalModule, diff --git a/client/src/app/shared/utils.ts b/client/src/app/shared/utils.ts index d5250e7ad656835ccfe3b3e758efbe9b0614cca0..b43ae07dacaf2fa56684636c8b8478909c2ff2e2 100644 --- a/client/src/app/shared/utils.ts +++ b/client/src/app/shared/utils.ts @@ -1,5 +1,3 @@ -import { environment } from 'src/environments/environment'; - /** * Returns strict url address. * @@ -8,12 +6,12 @@ import { environment } from 'src/environments/environment'; * @example * const url: string = getHost() + '/following-url/'; */ -export const getHost = (): string => { - if (!environment.apiUrl.startsWith('http')) { +export const getHost = (apiUrl: string): string => { + if (!apiUrl.startsWith('http')) { const url = window.location; - return url.protocol + '//' + url.host + environment.apiUrl; + return url.protocol + '//' + url.host + apiUrl; } - return environment.apiUrl; + return apiUrl; } /** diff --git a/client/src/assets/app.config.json b/client/src/assets/app.config.json new file mode 100644 index 0000000000000000000000000000000000000000..0270c50c94d4db9f30d2c118f369f75b0c01884f --- /dev/null +++ b/client/src/assets/app.config.json @@ -0,0 +1,10 @@ +{ + "apiUrl": "http://localhost:8080", + "servicesUrl": "http://localhost:5000", + "baseHref": "/", + "authenticationEnabled": true, + "ssoAuthUrl": "http://localhost:8180/auth", + "ssoRealm": "anis", + "ssoClientId": "anis-client", + "adminRole": "anis_admin" +} \ No newline at end of file diff --git a/client/src/environments/environment.prod.ts b/client/src/environments/environment.prod.ts index 5b72becdf6cfb8b2cfbb20698a4971ee4a6c0a0a..5d0833162027e2147e99e72a1a94bb7d3cb62843 100644 --- a/client/src/environments/environment.prod.ts +++ b/client/src/environments/environment.prod.ts @@ -1,12 +1,3 @@ export const environment = { - production: true, - apiUrl: '/server', - servicesUrl: '/services', - baseHref: '/', - authenticationEnabled: true, - ssoAuthUrl: 'https://keycloak.lam.fr/auth/', - ssoRealm: 'anis', - ssoClientId: 'anis-dev', - ssoLoginRedirectUri: '/', - ssoLogoutRedirectUri: '/' + production: true }; diff --git a/client/src/environments/environment.ts b/client/src/environments/environment.ts index f2825d0d24a5bc4607c135ef2e9b56eac4128dc9..458476a4df52c64d1a2abee43c6cb91d87abad77 100644 --- a/client/src/environments/environment.ts +++ b/client/src/environments/environment.ts @@ -3,16 +3,7 @@ // The list of file replacements can be found in `angular.json`. export const environment = { - production: false, - apiUrl: 'http://localhost:8080', - servicesUrl: 'http://localhost:5000', - baseHref: '/', - authenticationEnabled: true, - ssoAuthUrl: 'http://localhost:8180/auth', - ssoRealm: 'anis', - ssoClientId: 'anis-client', - ssoLoginRedirectUri: '/', - ssoLogoutRedirectUri: '/' + production: false }; /* diff --git a/client/src/test.ts b/client/src/test.ts index 2042356408ff9a673a6ca98b4eb2b9cb7e96f1a5..67041854268d4672d4ca9c764a16321e05c57a58 100644 --- a/client/src/test.ts +++ b/client/src/test.ts @@ -1,25 +1,23 @@ -// This file is required by karma.conf.js and loads recursively all the .spec and framework files +import 'jest-preset-angular/setup-jest'; -import 'zone.js/testing'; -import { getTestBed } from '@angular/core/testing'; -import { - BrowserDynamicTestingModule, - platformBrowserDynamicTesting -} from '@angular/platform-browser-dynamic/testing'; +Object.defineProperty(window, 'CSS', { value: null }); +Object.defineProperty(window, 'getComputedStyle', { + value: () => { + return { + display: 'none', + appearance: ['-webkit-appearance'], + }; + }, +}); -declare const require: { - context(path: string, deep?: boolean, filter?: RegExp): { - keys(): string[]; - <T>(id: string): T; - }; -}; - -// First, initialize the Angular testing environment. -getTestBed().initTestEnvironment( - BrowserDynamicTestingModule, - platformBrowserDynamicTesting() -); -// Then we find all the tests. -const context = require.context('./', true, /\.spec\.ts$/); -// And load the modules. -context.keys().map(context); +Object.defineProperty(document, 'doctype', { + value: '<!DOCTYPE html>', +}); +Object.defineProperty(document.body.style, 'transform', { + value: () => { + return { + enumerable: true, + configurable: true, + }; + }, +}); diff --git a/client/tsconfig.json b/client/tsconfig.json index 824b803cca255aed75c72380255ffcc15fe1c85b..84c435c4bc3d43f702dc44877cea4216a255f5c0 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -1,4 +1,3 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ { "compileOnSave": false, "compilerOptions": { diff --git a/client/tsconfig.spec.json b/client/tsconfig.spec.json index 092345b02e807773164dba19f2b4d1f4ab869f67..8f2c1326542880fa39c0d4cf9e6600f06b0481a1 100644 --- a/client/tsconfig.spec.json +++ b/client/tsconfig.spec.json @@ -1,18 +1,14 @@ /* To learn more about this file see: https://angular.io/config/tsconfig. */ { - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./out-tsc/spec", - "types": [ - "jasmine" - ] - }, - "files": [ - "src/test.ts", - "src/polyfills.ts" - ], - "include": [ - "src/**/*.spec.ts", - "src/**/*.d.ts" - ] + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/spec", + "types": [ + "jest" + ], + "esModuleInterop": true, + "emitDecoratorMetadata": true + }, + "files": ["src/test.ts", "src/polyfills.ts"], + "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] } diff --git a/client/yarn.lock b/client/yarn.lock index 2a005f9358ac87f05bedd31cb41ab6f7bf727db2..dfc67f9ae6f0b25094479ddb02882daaaa9a9099 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -256,6 +256,27 @@ semver "^6.3.0" source-map "^0.5.0" +"@babel/core@^7.1.0", "@babel/core@^7.7.2": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.14.8.tgz#20cdf7c84b5d86d83fac8710a8bc605a7ba3f010" + integrity sha512-/AtaeEhT6ErpDhInbXmjHcUQXH0L0TEgscfcxk1qbOvLuKCa5aZT0SOOtDKFY96/CLROwbLSKyFor6idgNaU4Q== + dependencies: + "@babel/code-frame" "^7.14.5" + "@babel/generator" "^7.14.8" + "@babel/helper-compilation-targets" "^7.14.5" + "@babel/helper-module-transforms" "^7.14.8" + "@babel/helpers" "^7.14.8" + "@babel/parser" "^7.14.8" + "@babel/template" "^7.14.5" + "@babel/traverse" "^7.14.8" + "@babel/types" "^7.14.8" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.1.2" + semver "^6.3.0" + source-map "^0.5.0" + "@babel/core@^7.7.5", "@babel/core@^7.8.6": version "7.14.6" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.14.6.tgz#e0814ec1a950032ff16c13a2721de39a8416fcab" @@ -295,6 +316,15 @@ jsesc "^2.5.1" source-map "^0.5.0" +"@babel/generator@^7.14.8", "@babel/generator@^7.7.2": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.14.8.tgz#bf86fd6af96cf3b74395a8ca409515f89423e070" + integrity sha512-cYDUpvIzhBVnMzRoY1fkSEhK/HmwEVwlyULYgn/tMQYd6Obag3ylCjONle3gdErfXBW61SVTlR9QR7uWlgeIkg== + dependencies: + "@babel/types" "^7.14.8" + jsesc "^2.5.1" + source-map "^0.5.0" + "@babel/helper-annotate-as-pure@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.14.5.tgz#7bf478ec3b71726d56a8ca5775b046fc29879e61" @@ -412,6 +442,20 @@ "@babel/traverse" "^7.14.5" "@babel/types" "^7.14.5" +"@babel/helper-module-transforms@^7.14.8": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.14.8.tgz#d4279f7e3fd5f4d5d342d833af36d4dd87d7dc49" + integrity sha512-RyE+NFOjXn5A9YU1dkpeBaduagTlZ0+fccnIcAGbv1KGUlReBj7utF7oEth8IdIBQPcux0DDgW5MFBH2xu9KcA== + dependencies: + "@babel/helper-module-imports" "^7.14.5" + "@babel/helper-replace-supers" "^7.14.5" + "@babel/helper-simple-access" "^7.14.8" + "@babel/helper-split-export-declaration" "^7.14.5" + "@babel/helper-validator-identifier" "^7.14.8" + "@babel/template" "^7.14.5" + "@babel/traverse" "^7.14.8" + "@babel/types" "^7.14.8" + "@babel/helper-optimise-call-expression@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz#f27395a8619e0665b3f0364cddb41c25d71b499c" @@ -450,6 +494,13 @@ dependencies: "@babel/types" "^7.14.5" +"@babel/helper-simple-access@^7.14.8": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.14.8.tgz#82e1fec0644a7e775c74d305f212c39f8fe73924" + integrity sha512-TrFN4RHh9gnWEU+s7JloIho2T76GPwRHhdzOWLqTrMnlas8T9O7ec+oEDNsRXndOmru9ymH9DFrEOxpzPoSbdg== + dependencies: + "@babel/types" "^7.14.8" + "@babel/helper-skip-transparent-expression-wrappers@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.14.5.tgz#96f486ac050ca9f44b009fbe5b7d394cab3a0ee4" @@ -469,6 +520,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz#d0f0e277c512e0c938277faa85a3968c9a44c0e8" integrity sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg== +"@babel/helper-validator-identifier@^7.14.8": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.8.tgz#32be33a756f29e278a0d644fa08a2c9e0f88a34c" + integrity sha512-ZGy6/XQjllhYQrNw/3zfWRwZCTVSiBLZ9DHVZxn9n2gip/7ab8mv2TWlKPIBk26RwedCBoWdjLmn+t9na2Gcow== + "@babel/helper-validator-option@^7.12.17", "@babel/helper-validator-option@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3" @@ -493,6 +549,15 @@ "@babel/traverse" "^7.14.5" "@babel/types" "^7.14.5" +"@babel/helpers@^7.14.8": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.14.8.tgz#839f88f463025886cff7f85a35297007e2da1b77" + integrity sha512-ZRDmI56pnV+p1dH6d+UN6GINGz7Krps3+270qqI9UJ4wxYThfAIcI5i7j5vXC4FJ3Wap+S9qcebxeYiqn87DZw== + dependencies: + "@babel/template" "^7.14.5" + "@babel/traverse" "^7.14.8" + "@babel/types" "^7.14.8" + "@babel/highlight@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9" @@ -502,6 +567,11 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/parser@^7.1.0", "@babel/parser@^7.14.8", "@babel/parser@^7.7.2": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.14.8.tgz#66fd41666b2d7b840bd5ace7f7416d5ac60208d4" + integrity sha512-syoCQFOoo/fzkWDeM0dLEZi5xqurb5vuyzwIMNZRNun+N/9A4cUZeQaE7dTrB8jGaKuJRBtEOajtnmw0I5hvvA== + "@babel/parser@^7.12.13", "@babel/parser@^7.14.3", "@babel/parser@^7.14.5", "@babel/parser@^7.14.6": version "7.14.6" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.14.6.tgz#d85cc68ca3cac84eae384c06f032921f5227f4b2" @@ -651,7 +721,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-class-properties@^7.12.13": +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13", "@babel/plugin-syntax-class-properties@^7.8.3": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== @@ -679,6 +756,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-syntax-import-meta@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-json-strings@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" @@ -686,7 +770,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== @@ -700,7 +784,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-numeric-separator@^7.10.4": +"@babel/plugin-syntax-numeric-separator@^7.10.4", "@babel/plugin-syntax-numeric-separator@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== @@ -735,13 +819,20 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-top-level-await@^7.12.13": +"@babel/plugin-syntax-top-level-await@^7.12.13", "@babel/plugin-syntax-top-level-await@^7.8.3": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== dependencies: "@babel/helper-plugin-utils" "^7.14.5" +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.14.5.tgz#b82c6ce471b165b5ce420cf92914d6fb46225716" + integrity sha512-u6OXzDaIXjEstBRRoBCQ/uKQKlbuaeE5in0RvWdA4pN6AhqxTIwUsnHPU1CFZA/amYObMsuWhYfRl3Ch90HD0Q== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/plugin-transform-arrow-functions@^7.13.0": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.14.5.tgz#f7187d9588a768dd080bf4c9ffe117ea62f7862a" @@ -1124,7 +1215,7 @@ "@babel/parser" "^7.12.13" "@babel/types" "^7.12.13" -"@babel/template@^7.12.13", "@babel/template@^7.14.5": +"@babel/template@^7.12.13", "@babel/template@^7.14.5", "@babel/template@^7.3.3": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.14.5.tgz#a9bc9d8b33354ff6e55a9c60d1109200a68974f4" integrity sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g== @@ -1133,6 +1224,21 @@ "@babel/parser" "^7.14.5" "@babel/types" "^7.14.5" +"@babel/traverse@^7.1.0", "@babel/traverse@^7.14.8", "@babel/traverse@^7.7.2": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.14.8.tgz#c0253f02677c5de1a8ff9df6b0aacbec7da1a8ce" + integrity sha512-kexHhzCljJcFNn1KYAQ6A5wxMRzq9ebYpEDV4+WdNyr3i7O44tanbDOR/xjiG2F3sllan+LgwK+7OMk0EmydHg== + dependencies: + "@babel/code-frame" "^7.14.5" + "@babel/generator" "^7.14.8" + "@babel/helper-function-name" "^7.14.5" + "@babel/helper-hoist-variables" "^7.14.5" + "@babel/helper-split-export-declaration" "^7.14.5" + "@babel/parser" "^7.14.8" + "@babel/types" "^7.14.8" + debug "^4.1.0" + globals "^11.1.0" + "@babel/traverse@^7.13.0", "@babel/traverse@^7.14.2", "@babel/traverse@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.14.5.tgz#c111b0f58afab4fea3d3385a406f692748c59870" @@ -1148,6 +1254,14 @@ debug "^4.1.0" globals "^11.1.0" +"@babel/types@^7.0.0", "@babel/types@^7.14.8", "@babel/types@^7.3.0", "@babel/types@^7.3.3": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.14.8.tgz#38109de8fcadc06415fbd9b74df0065d4d41c728" + integrity sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q== + dependencies: + "@babel/helper-validator-identifier" "^7.14.8" + to-fast-properties "^2.0.0" + "@babel/types@^7.12.13", "@babel/types@^7.14.2", "@babel/types@^7.14.5", "@babel/types@^7.4.4", "@babel/types@^7.8.6": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.14.5.tgz#3bb997ba829a2104cedb20689c4a5b8121d383ff" @@ -1156,6 +1270,11 @@ "@babel/helper-validator-identifier" "^7.14.5" to-fast-properties "^2.0.0" +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + "@csstools/convert-colors@^1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7" @@ -1171,11 +1290,202 @@ resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.3.tgz#c36ffa64a2a239bf948541a97b6ae8d729e09a9a" integrity sha512-rFnSUN/QOtnOAgqFRooTA3H57JLDm0QEG/jPdk+tLQNL/eWd+Aok8g3qCI+Q1xuDPWpGW/i9JySpJVsq8Q0s9w== +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + "@istanbuljs/schema@^0.1.2": version "0.1.3" resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== +"@jest/console@^27.0.6": + version "27.0.6" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-27.0.6.tgz#3eb72ea80897495c3d73dd97aab7f26770e2260f" + integrity sha512-fMlIBocSHPZ3JxgWiDNW/KPj6s+YRd0hicb33IrmelCcjXo/pXPwvuiKFmZz+XuqI/1u7nbUK10zSsWL/1aegg== + dependencies: + "@jest/types" "^27.0.6" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^27.0.6" + jest-util "^27.0.6" + slash "^3.0.0" + +"@jest/core@^27.0.6": + version "27.0.6" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-27.0.6.tgz#c5f642727a0b3bf0f37c4b46c675372d0978d4a1" + integrity sha512-SsYBm3yhqOn5ZLJCtccaBcvD/ccTLCeuDv8U41WJH/V1MW5eKUkeMHT9U+Pw/v1m1AIWlnIW/eM2XzQr0rEmow== + dependencies: + "@jest/console" "^27.0.6" + "@jest/reporters" "^27.0.6" + "@jest/test-result" "^27.0.6" + "@jest/transform" "^27.0.6" + "@jest/types" "^27.0.6" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.8.1" + exit "^0.1.2" + graceful-fs "^4.2.4" + jest-changed-files "^27.0.6" + jest-config "^27.0.6" + jest-haste-map "^27.0.6" + jest-message-util "^27.0.6" + jest-regex-util "^27.0.6" + jest-resolve "^27.0.6" + jest-resolve-dependencies "^27.0.6" + jest-runner "^27.0.6" + jest-runtime "^27.0.6" + jest-snapshot "^27.0.6" + jest-util "^27.0.6" + jest-validate "^27.0.6" + jest-watcher "^27.0.6" + micromatch "^4.0.4" + p-each-series "^2.1.0" + rimraf "^3.0.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^27.0.6": + version "27.0.6" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-27.0.6.tgz#ee293fe996db01d7d663b8108fa0e1ff436219d2" + integrity sha512-4XywtdhwZwCpPJ/qfAkqExRsERW+UaoSRStSHCCiQTUpoYdLukj+YJbQSFrZjhlUDRZeNiU9SFH0u7iNimdiIg== + dependencies: + "@jest/fake-timers" "^27.0.6" + "@jest/types" "^27.0.6" + "@types/node" "*" + jest-mock "^27.0.6" + +"@jest/fake-timers@^27.0.6": + version "27.0.6" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-27.0.6.tgz#cbad52f3fe6abe30e7acb8cd5fa3466b9588e3df" + integrity sha512-sqd+xTWtZ94l3yWDKnRTdvTeZ+A/V7SSKrxsrOKSqdyddb9CeNRF8fbhAU0D7ZJBpTTW2nbp6MftmKJDZfW2LQ== + dependencies: + "@jest/types" "^27.0.6" + "@sinonjs/fake-timers" "^7.0.2" + "@types/node" "*" + jest-message-util "^27.0.6" + jest-mock "^27.0.6" + jest-util "^27.0.6" + +"@jest/globals@^27.0.6": + version "27.0.6" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-27.0.6.tgz#48e3903f99a4650673d8657334d13c9caf0e8f82" + integrity sha512-DdTGCP606rh9bjkdQ7VvChV18iS7q0IMJVP1piwTWyWskol4iqcVwthZmoJEf7obE1nc34OpIyoVGPeqLC+ryw== + dependencies: + "@jest/environment" "^27.0.6" + "@jest/types" "^27.0.6" + expect "^27.0.6" + +"@jest/reporters@^27.0.6": + version "27.0.6" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-27.0.6.tgz#91e7f2d98c002ad5df94d5b5167c1eb0b9fd5b00" + integrity sha512-TIkBt09Cb2gptji3yJXb3EE+eVltW6BjO7frO7NEfjI9vSIYoISi5R3aI3KpEDXlB1xwB+97NXIqz84qYeYsfA== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^27.0.6" + "@jest/test-result" "^27.0.6" + "@jest/transform" "^27.0.6" + "@jest/types" "^27.0.6" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.2" + graceful-fs "^4.2.4" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^4.0.3" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.0.2" + jest-haste-map "^27.0.6" + jest-resolve "^27.0.6" + jest-util "^27.0.6" + jest-worker "^27.0.6" + slash "^3.0.0" + source-map "^0.6.0" + string-length "^4.0.1" + terminal-link "^2.0.0" + v8-to-istanbul "^8.0.0" + +"@jest/source-map@^27.0.6": + version "27.0.6" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-27.0.6.tgz#be9e9b93565d49b0548b86e232092491fb60551f" + integrity sha512-Fek4mi5KQrqmlY07T23JRi0e7Z9bXTOOD86V/uS0EIW4PClvPDqZOyFlLpNJheS6QI0FNX1CgmPjtJ4EA/2M+g== + dependencies: + callsites "^3.0.0" + graceful-fs "^4.2.4" + source-map "^0.6.0" + +"@jest/test-result@^27.0.6": + version "27.0.6" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-27.0.6.tgz#3fa42015a14e4fdede6acd042ce98c7f36627051" + integrity sha512-ja/pBOMTufjX4JLEauLxE3LQBPaI2YjGFtXexRAjt1I/MbfNlMx0sytSX3tn5hSLzQsR3Qy2rd0hc1BWojtj9w== + dependencies: + "@jest/console" "^27.0.6" + "@jest/types" "^27.0.6" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^27.0.6": + version "27.0.6" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-27.0.6.tgz#80a913ed7a1130545b1cd777ff2735dd3af5d34b" + integrity sha512-bISzNIApazYOlTHDum9PwW22NOyDa6VI31n6JucpjTVM0jD6JDgqEZ9+yn575nDdPF0+4csYDxNNW13NvFQGZA== + dependencies: + "@jest/test-result" "^27.0.6" + graceful-fs "^4.2.4" + jest-haste-map "^27.0.6" + jest-runtime "^27.0.6" + +"@jest/transform@^27.0.6": + version "27.0.6" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-27.0.6.tgz#189ad7107413208f7600f4719f81dd2f7278cc95" + integrity sha512-rj5Dw+mtIcntAUnMlW/Vju5mr73u8yg+irnHwzgtgoeI6cCPOvUwQ0D1uQtc/APmWgvRweEb1g05pkUpxH3iCA== + dependencies: + "@babel/core" "^7.1.0" + "@jest/types" "^27.0.6" + babel-plugin-istanbul "^6.0.0" + chalk "^4.0.0" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.2.4" + jest-haste-map "^27.0.6" + jest-regex-util "^27.0.6" + jest-util "^27.0.6" + micromatch "^4.0.4" + pirates "^4.0.1" + slash "^3.0.0" + source-map "^0.6.1" + write-file-atomic "^3.0.0" + +"@jest/types@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" + integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^15.0.0" + chalk "^4.0.0" + +"@jest/types@^27.0.6": + version "27.0.6" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.0.6.tgz#9a992bc517e0c49f035938b8549719c2de40706b" + integrity sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^16.0.0" + chalk "^4.0.0" + "@jsdevtools/coverage-istanbul-loader@3.0.5": version "3.0.5" resolved "https://registry.yarnpkg.com/@jsdevtools/coverage-istanbul-loader/-/coverage-istanbul-loader-3.0.5.tgz#2a4bc65d0271df8d4435982db4af35d81754ee26" @@ -1319,6 +1629,20 @@ "@angular-devkit/schematics" "12.0.4" jsonc-parser "3.0.0" +"@sinonjs/commons@^1.7.0": + version "1.8.3" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" + integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^7.0.2": + version "7.1.2" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz#2524eae70c4910edccf99b2f4e6efc5894aff7b5" + integrity sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg== + dependencies: + "@sinonjs/commons" "^1.7.0" + "@tootallnate/once@1": version "1.1.2" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" @@ -1329,20 +1653,254 @@ resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.1.1.tgz#3348564048e7a2d7398c935d466c0414ebb6a669" integrity sha512-Z6DoceYb/1xSg5+e+ZlPZ9v0N16ZvZ+wYMraFue4HYrE4ttONKtsvruIRf6t9TBR0YvSOfi1hUU0fJfBLCDYow== -"@types/component-emitter@^1.2.10": - version "1.2.10" - resolved "https://registry.yarnpkg.com/@types/component-emitter/-/component-emitter-1.2.10.tgz#ef5b1589b9f16544642e473db5ea5639107ef3ea" - integrity sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg== +"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14": + version "7.1.15" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.15.tgz#2ccfb1ad55a02c83f8e0ad327cbc332f55eb1024" + integrity sha512-bxlMKPDbY8x5h6HBwVzEOk2C8fb6SLfYQ5Jw3uBYuYF1lfWk/kbLd81la82vrIkBb0l+JdmrZaDikPrNxpS/Ew== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" -"@types/cookie@^0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.0.tgz#14f854c0f93d326e39da6e3b6f34f7d37513d108" - integrity sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg== +"@types/babel__generator@*": + version "7.6.3" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.3.tgz#f456b4b2ce79137f768aa130d2423d2f0ccfaba5" + integrity sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.1" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" + integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" -"@types/cors@^2.8.8": - version "2.8.10" - resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.10.tgz#61cc8469849e5bcdd0c7044122265c39cec10cf4" - integrity sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ== +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": + version "7.14.2" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.14.2.tgz#ffcd470bbb3f8bf30481678fb5502278ca833a43" + integrity sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA== + dependencies: + "@babel/types" "^7.3.0" + +"@types/d3-array@^1": + version "1.2.9" + resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-1.2.9.tgz#c7dc78992cd8ca5c850243a265fd257ea56df1fa" + integrity sha512-E/7RgPr2ylT5dWG0CswMi9NpFcjIEDqLcUSBgNHe/EMahfqYaTx4zhcggG3khqoEB/leY4Vl6nTSbwLUPjXceA== + +"@types/d3-axis@^1": + version "1.0.16" + resolved "https://registry.yarnpkg.com/@types/d3-axis/-/d3-axis-1.0.16.tgz#93d7a28795c2f8b0e2fd550fcc4d29b7f174e693" + integrity sha512-p7085weOmo4W+DzlRRVC/7OI/jugaKbVa6WMQGCQscaMylcbuaVEGk7abJLNyGVFLeCBNrHTdDiqRGnzvL0nXQ== + dependencies: + "@types/d3-selection" "^1" + +"@types/d3-brush@^1": + version "1.1.5" + resolved "https://registry.yarnpkg.com/@types/d3-brush/-/d3-brush-1.1.5.tgz#c7cfb58decbfd53ad3e47f0376345e3640a68186" + integrity sha512-4zGkBafJf5zCsBtLtvDj/pNMo5X9+Ii/1hUz0GvQ+wEwelUBm2AbIDAzJnp2hLDFF307o0fhxmmocHclhXC+tw== + dependencies: + "@types/d3-selection" "^1" + +"@types/d3-chord@^1": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@types/d3-chord/-/d3-chord-1.0.11.tgz#5760765db1b1a4b936c0d9355a821dde9dd25da2" + integrity sha512-0DdfJ//bxyW3G9Nefwq/LDgazSKNN8NU0lBT3Cza6uVuInC2awMNsAcv1oKyRFLn9z7kXClH5XjwpveZjuz2eg== + +"@types/d3-collection@*": + version "1.0.10" + resolved "https://registry.yarnpkg.com/@types/d3-collection/-/d3-collection-1.0.10.tgz#bca161e336156968f267c077f7f2bfa8ff224e58" + integrity sha512-54Fdv8u5JbuXymtmXm2SYzi1x/Svt+jfWBU5junkhrCewL92VjqtCBDn97coBRVwVFmYNnVTNDyV8gQyPYfm+A== + +"@types/d3-color@^1": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-1.4.2.tgz#944f281d04a0f06e134ea96adbb68303515b2784" + integrity sha512-fYtiVLBYy7VQX+Kx7wU/uOIkGQn8aAEY8oWMoyja3N4dLd8Yf6XgSIR/4yWvMuveNOH5VShnqCgRqqh/UNanBA== + +"@types/d3-contour@^1": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@types/d3-contour/-/d3-contour-1.3.3.tgz#44529d498bbc1db78b195d75e1c9bb889edd647a" + integrity sha512-LxwmGIfVJIc1cKs7ZFRQ1FbtXpfH7QTXYRdMIJsFP71uCMdF6jJ0XZakYDX6Hn4yZkLf+7V8FgD34yCcok+5Ww== + dependencies: + "@types/d3-array" "^1" + "@types/geojson" "*" + +"@types/d3-dispatch@^1": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@types/d3-dispatch/-/d3-dispatch-1.0.9.tgz#c5a180f1e251de853b399cfbfbb6dd7f8bf842ae" + integrity sha512-zJ44YgjqALmyps+II7b1mZLhrtfV/FOxw9owT87mrweGWcg+WK5oiJX2M3SYJ0XUAExBduarysfgbR11YxzojQ== + +"@types/d3-drag@^1": + version "1.2.5" + resolved "https://registry.yarnpkg.com/@types/d3-drag/-/d3-drag-1.2.5.tgz#0b1b852cb41577075aa625ae6149379ea6c34dfd" + integrity sha512-7NeTnfolst1Js3Vs7myctBkmJWu6DMI3k597AaHUX98saHjHWJ6vouT83UrpE+xfbSceHV+8A0JgxuwgqgmqWw== + dependencies: + "@types/d3-selection" "^1" + +"@types/d3-dsv@^1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@types/d3-dsv/-/d3-dsv-1.2.1.tgz#1524fee9f19d689c2f76aa0e24e230762bf96994" + integrity sha512-LLmJmjiqp/fTNEdij5bIwUJ6P6TVNk5hKM9/uk5RPO2YNgEu9XvKO0dJ7Iqd3psEdmZN1m7gB1bOsjr4HmO2BA== + +"@types/d3-ease@^1": + version "1.0.10" + resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-1.0.10.tgz#09910e8558439b6038a7ed620650e510394ffa6d" + integrity sha512-fMFTCzd8DOwruE9zlu2O8ci5ct+U5jkGcDS+cH+HCidnJlDs0MZ+TuSVCFtEzh4E5MasItwy+HvgoFtxPHa5Cw== + +"@types/d3-fetch@^1": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@types/d3-fetch/-/d3-fetch-1.2.2.tgz#b93bfe248b8b761af82f4dac57959c989f67da3e" + integrity sha512-rtFs92GugtV/NpiJQd0WsmGLcg52tIL0uF0bKbbJg231pR9JEb6HT4AUwrtuLq3lOeKdLBhsjV14qb0pMmd0Aw== + dependencies: + "@types/d3-dsv" "^1" + +"@types/d3-force@^1": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/d3-force/-/d3-force-1.2.4.tgz#6e274c72288c2db08fbdb8f5b87b9aa83e55a9e8" + integrity sha512-fkorLTKvt6AQbFBQwn4aq7h9rJ4c7ZVcPMGB8X6eFFveAyMZcv7t7m6wgF4Eg93rkPgPORU7sAho1QSHNcZu6w== + +"@types/d3-format@^1": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@types/d3-format/-/d3-format-1.4.2.tgz#ea17bf559b71d9afd569ae9bfe4c544dab863baa" + integrity sha512-WeGCHAs7PHdZYq6lwl/+jsl+Nfc1J2W1kNcMeIMYzQsT6mtBDBgtJ/rcdjZ0k0rVIvqEZqhhuD5TK/v3P2gFHQ== + +"@types/d3-geo@^1": + version "1.12.3" + resolved "https://registry.yarnpkg.com/@types/d3-geo/-/d3-geo-1.12.3.tgz#512ebe735cb1cdf5f87ad59608416e2e9e868c5a" + integrity sha512-yZbPb7/5DyL/pXkeOmZ7L5ySpuGr4H48t1cuALjnJy5sXQqmSSAYBiwa6Ya/XpWKX2rJqGDDubmh3nOaopOpeA== + dependencies: + "@types/geojson" "*" + +"@types/d3-hierarchy@^1": + version "1.1.8" + resolved "https://registry.yarnpkg.com/@types/d3-hierarchy/-/d3-hierarchy-1.1.8.tgz#50657f420d565a06c0b950a4b82eee0a369f2dea" + integrity sha512-AbStKxNyWiMDQPGDguG2Kuhlq1Sv539pZSxYbx4UZeYkutpPwXCcgyiRrlV4YH64nIOsKx7XVnOMy9O7rJsXkg== + +"@types/d3-interpolate@^1": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-1.4.2.tgz#88902a205f682773a517612299a44699285eed7b" + integrity sha512-ylycts6llFf8yAEs1tXzx2loxxzDZHseuhPokrqKprTQSTcD3JbJI1omZP1rphsELZO3Q+of3ff0ZS7+O6yVzg== + dependencies: + "@types/d3-color" "^1" + +"@types/d3-path@^1": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-1.0.9.tgz#73526b150d14cd96e701597cbf346cfd1fd4a58c" + integrity sha512-NaIeSIBiFgSC6IGUBjZWcscUJEq7vpVu7KthHN8eieTV9d9MqkSOZLH4chq1PmcKy06PNe3axLeKmRIyxJ+PZQ== + +"@types/d3-polygon@^1": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@types/d3-polygon/-/d3-polygon-1.0.8.tgz#127ee83fccda5bf57384011da90f31367fea1530" + integrity sha512-1TOJPXCBJC9V3+K3tGbTqD/CsqLyv/YkTXAcwdsZzxqw5cvpdnCuDl42M4Dvi8XzMxZNCT9pL4ibrK2n4VmAcw== + +"@types/d3-quadtree@^1": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@types/d3-quadtree/-/d3-quadtree-1.0.9.tgz#c7c3b795b5af06e5b043d1d34e754a434b3bae59" + integrity sha512-5E0OJJn2QVavITFEc1AQlI8gLcIoDZcTKOD3feKFckQVmFV4CXhqRFt83tYNVNIN4ZzRkjlAMavJa1ldMhf5rA== + +"@types/d3-random@^1": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@types/d3-random/-/d3-random-1.1.3.tgz#8f7fdc23f92d1561e0694eb49567e8ab50537a19" + integrity sha512-XXR+ZbFCoOd4peXSMYJzwk0/elP37WWAzS/DG+90eilzVbUSsgKhBcWqylGWe+lA2ubgr7afWAOBaBxRgMUrBQ== + +"@types/d3-scale-chromatic@^1": + version "1.5.1" + resolved "https://registry.yarnpkg.com/@types/d3-scale-chromatic/-/d3-scale-chromatic-1.5.1.tgz#e2b7c3401e5c13809f831911eb820e444f4fc67a" + integrity sha512-7FtJYrmXTEWLykShjYhoGuDNR/Bda0+tstZMkFj4RRxUEryv16AGh3be21tqg84B6KfEwiZyEpBcTyPyU+GWjg== + +"@types/d3-scale@^2": + version "2.2.6" + resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-2.2.6.tgz#28540b4dfc99d978970e873e4138a6bea2ea6ab8" + integrity sha512-CHu34T5bGrJOeuhGxyiz9Xvaa9PlsIaQoOqjDg7zqeGj2x0rwPhGquiy03unigvcMxmvY0hEaAouT0LOFTLpIw== + dependencies: + "@types/d3-time" "^1" + +"@types/d3-selection@^1": + version "1.4.3" + resolved "https://registry.yarnpkg.com/@types/d3-selection/-/d3-selection-1.4.3.tgz#36928bbe64eb8e0bbcbaa01fb05c21ff6c71fa93" + integrity sha512-GjKQWVZO6Sa96HiKO6R93VBE8DUW+DDkFpIMf9vpY5S78qZTlRRSNUsHr/afDpF7TvLDV7VxrUFOWW7vdIlYkA== + +"@types/d3-shape@^1": + version "1.3.8" + resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-1.3.8.tgz#c3c15ec7436b4ce24e38de517586850f1fea8e89" + integrity sha512-gqfnMz6Fd5H6GOLYixOZP/xlrMtJms9BaS+6oWxTKHNqPGZ93BkWWupQSCYm6YHqx6h9wjRupuJb90bun6ZaYg== + dependencies: + "@types/d3-path" "^1" + +"@types/d3-time-format@^2": + version "2.3.1" + resolved "https://registry.yarnpkg.com/@types/d3-time-format/-/d3-time-format-2.3.1.tgz#87a30e4513b9d1d53b920327a361f87255bf3372" + integrity sha512-fck0Z9RGfIQn3GJIEKVrp15h9m6Vlg0d5XXeiE/6+CQiBmMDZxfR21XtjEPuDeg7gC3bBM0SdieA5XF3GW1wKA== + +"@types/d3-time@^1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-1.1.1.tgz#6cf3a4242c3bbac00440dfb8ba7884f16bedfcbf" + integrity sha512-ULX7LoqXTCYtM+tLYOaeAJK7IwCT+4Gxlm2MaH0ErKLi07R5lh8NHCAyWcDkCCmx1AfRcBEV6H9QE9R25uP7jw== + +"@types/d3-timer@^1": + version "1.0.10" + resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-1.0.10.tgz#329c51c2c931f44ed0acff78b8c84571acf0ed21" + integrity sha512-ZnAbquVqy+4ZjdW0cY6URp+qF/AzTVNda2jYyOzpR2cPT35FTXl78s15Bomph9+ckOiI1TtkljnWkwbIGAb6rg== + +"@types/d3-transition@^1": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@types/d3-transition/-/d3-transition-1.3.2.tgz#ed59beca7b4d679cfa52f88a6a50e5bbeb7e0a3c" + integrity sha512-J+a3SuF/E7wXbOSN19p8ZieQSFIm5hU2Egqtndbc54LXaAEOpLfDx4sBu/PKAKzHOdgKK1wkMhINKqNh4aoZAg== + dependencies: + "@types/d3-selection" "^1" + +"@types/d3-voronoi@*": + version "1.1.9" + resolved "https://registry.yarnpkg.com/@types/d3-voronoi/-/d3-voronoi-1.1.9.tgz#7bbc210818a3a5c5e0bafb051420df206617c9e5" + integrity sha512-DExNQkaHd1F3dFPvGA/Aw2NGyjMln6E9QzsiqOcBgnE+VInYnFBHBBySbZQts6z6xD+5jTfKCP7M4OqMyVjdwQ== + +"@types/d3-zoom@^1": + version "1.8.3" + resolved "https://registry.yarnpkg.com/@types/d3-zoom/-/d3-zoom-1.8.3.tgz#00237900c6fdc2bb4fe82679ee4d74eb8fbe7b3c" + integrity sha512-3kHkL6sPiDdbfGhzlp5gIHyu3kULhtnHTTAl3UBZVtWB1PzcLL8vdmz5mTx7plLiUqOA2Y+yT2GKjt/TdA2p7Q== + dependencies: + "@types/d3-interpolate" "^1" + "@types/d3-selection" "^1" + +"@types/d3@^5.7.2": + version "5.16.4" + resolved "https://registry.yarnpkg.com/@types/d3/-/d3-5.16.4.tgz#a7dc24a3dc1c19922eee72ba16144fd5bcea987a" + integrity sha512-2u0O9iP1MubFiQ+AhR1id4Egs+07BLtvRATG6IL2Gs9+KzdrfaxCKNq5hxEyw1kxwsqB/lCgr108XuHcKtb/5w== + dependencies: + "@types/d3-array" "^1" + "@types/d3-axis" "^1" + "@types/d3-brush" "^1" + "@types/d3-chord" "^1" + "@types/d3-collection" "*" + "@types/d3-color" "^1" + "@types/d3-contour" "^1" + "@types/d3-dispatch" "^1" + "@types/d3-drag" "^1" + "@types/d3-dsv" "^1" + "@types/d3-ease" "^1" + "@types/d3-fetch" "^1" + "@types/d3-force" "^1" + "@types/d3-format" "^1" + "@types/d3-geo" "^1" + "@types/d3-hierarchy" "^1" + "@types/d3-interpolate" "^1" + "@types/d3-path" "^1" + "@types/d3-polygon" "^1" + "@types/d3-quadtree" "^1" + "@types/d3-random" "^1" + "@types/d3-scale" "^2" + "@types/d3-scale-chromatic" "^1" + "@types/d3-selection" "^1" + "@types/d3-shape" "^1" + "@types/d3-time" "^1" + "@types/d3-time-format" "^2" + "@types/d3-timer" "^1" + "@types/d3-transition" "^1" + "@types/d3-voronoi" "*" + "@types/d3-zoom" "^1" "@types/eslint-scope@^3.7.0": version "3.7.0" @@ -1370,6 +1928,11 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.47.tgz#d7a51db20f0650efec24cd04994f523d93172ed4" integrity sha512-c5ciR06jK8u9BstrmJyO97m+klJrrhCf9u3rLu3DEAJBirxRqSCvDQoYKmxuYwQI5SZChAWu+tq9oVlGRuzPAg== +"@types/geojson@*": + version "7946.0.8" + resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.8.tgz#30744afdb385e2945e22f3b033f897f76b1f12ca" + integrity sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA== + "@types/glob@^7.1.1": version "7.1.3" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" @@ -1378,11 +1941,45 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/graceful-fs@^4.1.2": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" + integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" + integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw== + +"@types/istanbul-lib-report@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" + integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" + integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== + dependencies: + "@types/istanbul-lib-report" "*" + "@types/jasmine@~3.6.0": version "3.6.11" resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-3.6.11.tgz#4b1d77aa9dfc757407cb9e277216d8e83553f09d" integrity sha512-S6pvzQDvMZHrkBz2Mcn/8Du7cpr76PlRJBAoHnSDNbulULsH5dp0Gns+WRyNX5LHejz/ljxK4/vIHK/caHt6SQ== +"@types/jest@^26.0.24": + version "26.0.24" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.24.tgz#943d11976b16739185913a1936e0de0c4a7d595a" + integrity sha512-E/X5Vib8BWqZNRlDxj9vYXhsDwPYbPINqKF9BsnSoon4RQ0D9moEuLD8txgyypFLH7J4+Lho9Nr/c8H0Fi+17w== + dependencies: + jest-diff "^26.0.0" + pretty-format "^26.0.0" + "@types/json-schema@*", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6": version "7.0.7" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" @@ -1393,7 +1990,7 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.4.tgz#f0ec25dbf2f0e4b18647313ac031134ca5b24b21" integrity sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA== -"@types/node@*", "@types/node@>=10.0.0": +"@types/node@*": version "15.12.2" resolved "https://registry.yarnpkg.com/@types/node/-/node-15.12.2.tgz#1f2b42c4be7156ff4a6f914b2fb03d05fa84e38d" integrity sha512-zjQ69G564OCIWIOHSXyQEEDpdpGl+G348RAKY0XXy9Z5kU9Vzv1GMNnkar/ZJ8dzXB3COzD9Mo9NtRZ4xfgUww== @@ -1408,11 +2005,21 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== +"@types/prettier@^2.1.5": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.3.2.tgz#fc8c2825e4ed2142473b4a81064e6e081463d1b3" + integrity sha512-eI5Yrz3Qv4KPUa/nSIAi0h+qX0XyewOliug5F2QAtuRg6Kjg6jfmxe1GIwoIRhZspD1A0RP8ANrPwvEXXtRFog== + "@types/source-list-map@*": version "0.1.2" resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA== +"@types/stack-utils@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" + integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== + "@types/webpack-sources@^0.1.5": version "0.1.8" resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-0.1.8.tgz#078d75410435993ec8a0a2855e88706f3f751f81" @@ -1422,6 +2029,25 @@ "@types/source-list-map" "*" source-map "^0.6.1" +"@types/yargs-parser@*": + version "20.2.1" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129" + integrity sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw== + +"@types/yargs@^15.0.0": + version "15.0.14" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.14.tgz#26d821ddb89e70492160b66d10a0eb6df8f6fb06" + integrity sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ== + dependencies: + "@types/yargs-parser" "*" + +"@types/yargs@^16.0.0": + version "16.0.4" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.4.tgz#26aad98dd2c2a38e421086ea9ad42b9e51642977" + integrity sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw== + dependencies: + "@types/yargs-parser" "*" + "@webassemblyjs/ast@1.11.0": version "1.11.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.0.tgz#a5aa679efdc9e51707a4207139da57920555961f" @@ -1558,7 +2184,7 @@ resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== -abab@^2.0.5: +abab@^2.0.3, abab@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== @@ -1576,11 +2202,34 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: mime-types "~2.1.24" negotiator "0.6.2" +acorn-globals@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" + integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== + dependencies: + acorn "^7.1.1" + acorn-walk "^7.1.1" + +acorn-walk@^7.1.1: + version "7.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" + integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== + +acorn@^7.1.1: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + acorn@^8.2.1: version "8.4.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.4.0.tgz#af53266e698d7cffa416714b503066a82221be60" integrity sha512-ULr0LDaEqQrMFGyQ3bhJkLsbtrQ8QibAseGZeaSUiT/6zb9IvIkomWHJIvgvwad+hinRAgsI51JcWk2yvwyL+w== +acorn@^8.2.4: + version "8.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.4.1.tgz#56c36251fc7cabc7096adc18f05afe814321a28c" + integrity sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA== + adjust-sourcemap-loader@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz#fc4a0fd080f7d10471f30a7320f25560ade28c99" @@ -1721,6 +2370,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + anymatch@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" @@ -1729,7 +2383,7 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" -anymatch@~3.1.2: +anymatch@^3.0.3, anymatch@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== @@ -1750,6 +2404,13 @@ are-we-there-yet@~1.1.2: delegates "^1.0.0" readable-stream "^2.0.6" +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" @@ -1864,6 +2525,20 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== +babel-jest@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.0.6.tgz#e99c6e0577da2655118e3608b68761a5a69bd0d8" + integrity sha512-iTJyYLNc4wRofASmofpOc5NK9QunwMk+TLFgGXsTFS8uEqmd8wdI7sga0FPe2oVH3b5Agt/EAK1QjPEuKL8VfA== + dependencies: + "@jest/transform" "^27.0.6" + "@jest/types" "^27.0.6" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.0.0" + babel-preset-jest "^27.0.6" + chalk "^4.0.0" + graceful-fs "^4.2.4" + slash "^3.0.0" + babel-loader@8.2.2: version "8.2.2" resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.2.tgz#9363ce84c10c9a40e6c753748e1441b60c8a0b81" @@ -1881,6 +2556,27 @@ babel-plugin-dynamic-import-node@^2.3.3: dependencies: object.assign "^4.1.0" +babel-plugin-istanbul@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz#e159ccdc9af95e0b570c75b4573b7c34d671d765" + integrity sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^4.0.0" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.0.6.tgz#f7c6b3d764af21cb4a2a1ab6870117dbde15b456" + integrity sha512-CewFeM9Vv2gM7Yr9n5eyyLVPRSiBnk6lKZRjgwYnGKSl9M14TMn2vkN02wTF04OGuSDLEzlWiMzvjXuW9mB6Gw== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.0.0" + "@types/babel__traverse" "^7.0.6" + babel-plugin-polyfill-corejs2@^0.2.0: version "0.2.2" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.2.tgz#e9124785e6fd94f94b618a7954e5693053bf5327" @@ -1905,16 +2601,37 @@ babel-plugin-polyfill-regenerator@^0.2.0: dependencies: "@babel/helper-define-polyfill-provider" "^0.2.2" +babel-preset-current-node-syntax@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" + integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.8.3" + "@babel/plugin-syntax-import-meta" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-top-level-await" "^7.8.3" + +babel-preset-jest@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-27.0.6.tgz#909ef08e9f24a4679768be2f60a3df0856843f9d" + integrity sha512-WObA0/Biw2LrVVwZkF/2GqbOdzhKD6Fkdwhoy9ASIrOWr/zodcSpQh72JOkEn6NWyjmnPDjNSqaGN4KnpKzhXw== + dependencies: + babel-plugin-jest-hoist "^27.0.6" + babel-preset-current-node-syntax "^1.0.0" + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base64-arraybuffer@0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz#9818c79e059b1355f97e0428a017c838e90ba812" - integrity sha1-mBjHngWbE1X5fgQooBfIOOkLqBI= - base64-js@1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" @@ -1925,11 +2642,6 @@ base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== -base64id@2.0.0, base64id@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6" - integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== - base@^0.11.1: version "0.11.2" resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" @@ -1986,7 +2698,7 @@ bl@^4.1.0: inherits "^2.0.4" readable-stream "^3.4.0" -body-parser@1.19.0, body-parser@^1.19.0: +body-parser@1.19.0: version "1.19.0" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== @@ -2048,13 +2760,18 @@ braces@^2.3.1, braces@^2.3.2: split-string "^3.0.2" to-regex "^3.0.1" -braces@^3.0.1, braces@^3.0.2, braces@~3.0.2: +braces@^3.0.1, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== dependencies: fill-range "^7.0.1" +browser-process-hrtime@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" + integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== + browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4.16.0, browserslist@^4.16.6, browserslist@^4.6.4, browserslist@^4.9.1: version "4.16.6" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2" @@ -2066,7 +2783,21 @@ browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4 escalade "^3.1.1" node-releases "^1.1.71" -buffer-from@^1.0.0: +bs-logger@0.x: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@1.x, buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== @@ -2173,7 +2904,7 @@ callsites@^3.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -camelcase@^5.0.0: +camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== @@ -2217,7 +2948,7 @@ chalk@^2.0.0, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.1.0: +chalk@^4.0.0, chalk@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== @@ -2225,12 +2956,17 @@ chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== -"chokidar@>=3.0.0 <4.0.0", chokidar@^3.0.0, chokidar@^3.5.1: +"chokidar@>=3.0.0 <4.0.0", chokidar@^3.0.0: version "3.5.2" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== @@ -2274,11 +3010,21 @@ chrome-trace-event@^1.0.2: resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== +ci-info@^3.1.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6" + integrity sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A== + circular-dependency-plugin@5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/circular-dependency-plugin/-/circular-dependency-plugin-5.2.2.tgz#39e836079db1d3cf2f988dc48c5188a44058b600" integrity sha512-g38K9Cm5WRwlaH6g03B9OEz/0qRizI+2I7n+Gz+L5DxXJAPAiWQvwlYNm1V1jkdpUv95bOe/ASm2vfi/G560jQ== +cjs-module-lexer@^1.0.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" + integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== + class-utils@^0.3.5: version "0.3.6" resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" @@ -2343,11 +3089,21 @@ clone@^1.0.2: resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= + code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= +collect-v8-coverage@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" + integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== + collection-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" @@ -2390,19 +3146,14 @@ colorette@^1.2.1, colorette@^1.2.2: resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== -colors@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" - integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== - -combined-stream@^1.0.6, combined-stream@~1.0.6: +combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== dependencies: delayed-stream "~1.0.0" -commander@^2.20.0: +commander@2, commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -2417,7 +3168,7 @@ commondir@^1.0.1: resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= -component-emitter@^1.2.1, component-emitter@~1.3.0: +component-emitter@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== @@ -2452,16 +3203,6 @@ connect-history-api-fallback@^1.6.0: resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg== -connect@^3.7.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/connect/-/connect-3.7.0.tgz#5d49348910caa5e07a01800b030d0c35f20484f8" - integrity sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ== - dependencies: - debug "2.6.9" - finalhandler "1.1.2" - parseurl "~1.3.3" - utils-merge "1.0.1" - console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" @@ -2479,6 +3220,13 @@ content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== +convert-source-map@^1.4.0, convert-source-map@^1.6.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" + integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== + dependencies: + safe-buffer "~5.1.1" + convert-source-map@^1.5.1, convert-source-map@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" @@ -2496,11 +3244,6 @@ cookie@0.4.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== -cookie@~0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" - integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== - copy-anything@^2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-2.0.3.tgz#842407ba02466b0df844819bbe3baebbe5d45d87" @@ -2544,14 +3287,6 @@ core-util-is@1.0.2, core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= -cors@~2.8.5: - version "2.8.5" - resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" - integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== - dependencies: - object-assign "^4" - vary "^1" - cosmiconfig@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3" @@ -2585,6 +3320,15 @@ cross-spawn@^6.0.0: shebang-command "^1.2.0" which "^1.2.9" +cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + css-blank-pseudo@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz#dfdefd3254bf8a82027993674ccf35483bfcb3c5" @@ -2775,10 +3519,270 @@ csso@^4.2.0: dependencies: css-tree "^1.1.2" -custom-event@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425" - integrity sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU= +cssom@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" + integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== + +cssom@~0.3.6: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssstyle@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" + integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== + dependencies: + cssom "~0.3.6" + +d3-array@1, d3-array@^1.1.1, d3-array@^1.2.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.4.tgz#635ce4d5eea759f6f605863dbcfc30edc737f71f" + integrity sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw== + +d3-axis@1: + version "1.0.12" + resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-1.0.12.tgz#cdf20ba210cfbb43795af33756886fb3638daac9" + integrity sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ== + +d3-brush@1: + version "1.1.6" + resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-1.1.6.tgz#b0a22c7372cabec128bdddf9bddc058592f89e9b" + integrity sha512-7RW+w7HfMCPyZLifTz/UnJmI5kdkXtpCbombUSs8xniAyo0vIbrDzDwUJB6eJOgl9u5DQOt2TQlYumxzD1SvYA== + dependencies: + d3-dispatch "1" + d3-drag "1" + d3-interpolate "1" + d3-selection "1" + d3-transition "1" + +d3-chord@1: + version "1.0.6" + resolved "https://registry.yarnpkg.com/d3-chord/-/d3-chord-1.0.6.tgz#309157e3f2db2c752f0280fedd35f2067ccbb15f" + integrity sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA== + dependencies: + d3-array "1" + d3-path "1" + +d3-collection@1: + version "1.0.7" + resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.7.tgz#349bd2aa9977db071091c13144d5e4f16b5b310e" + integrity sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A== + +d3-color@1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.4.1.tgz#c52002bf8846ada4424d55d97982fef26eb3bc8a" + integrity sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q== + +d3-contour@1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-1.3.2.tgz#652aacd500d2264cb3423cee10db69f6f59bead3" + integrity sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg== + dependencies: + d3-array "^1.1.1" + +d3-dispatch@1: + version "1.0.6" + resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-1.0.6.tgz#00d37bcee4dd8cd97729dd893a0ac29caaba5d58" + integrity sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA== + +d3-drag@1: + version "1.2.5" + resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-1.2.5.tgz#2537f451acd39d31406677b7dc77c82f7d988f70" + integrity sha512-rD1ohlkKQwMZYkQlYVCrSFxsWPzI97+W+PaEIBNTMxRuxz9RF0Hi5nJWHGVJ3Om9d2fRTe1yOBINJyy/ahV95w== + dependencies: + d3-dispatch "1" + d3-selection "1" + +d3-dsv@1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-1.2.0.tgz#9d5f75c3a5f8abd611f74d3f5847b0d4338b885c" + integrity sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g== + dependencies: + commander "2" + iconv-lite "0.4" + rw "1" + +d3-ease@1: + version "1.0.7" + resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-1.0.7.tgz#9a834890ef8b8ae8c558b2fe55bd57f5993b85e2" + integrity sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ== + +d3-fetch@1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/d3-fetch/-/d3-fetch-1.2.0.tgz#15ce2ecfc41b092b1db50abd2c552c2316cf7fc7" + integrity sha512-yC78NBVcd2zFAyR/HnUiBS7Lf6inSCoWcSxFfw8FYL7ydiqe80SazNwoffcqOfs95XaLo7yebsmQqDKSsXUtvA== + dependencies: + d3-dsv "1" + +d3-force@1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-1.2.1.tgz#fd29a5d1ff181c9e7f0669e4bd72bdb0e914ec0b" + integrity sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg== + dependencies: + d3-collection "1" + d3-dispatch "1" + d3-quadtree "1" + d3-timer "1" + +d3-format@1: + version "1.4.5" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.4.5.tgz#374f2ba1320e3717eb74a9356c67daee17a7edb4" + integrity sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ== + +d3-geo@1: + version "1.12.1" + resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-1.12.1.tgz#7fc2ab7414b72e59fbcbd603e80d9adc029b035f" + integrity sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg== + dependencies: + d3-array "1" + +d3-hierarchy@1: + version "1.1.9" + resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz#2f6bee24caaea43f8dc37545fa01628559647a83" + integrity sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ== + +d3-interpolate@1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.4.0.tgz#526e79e2d80daa383f9e0c1c1c7dcc0f0583e987" + integrity sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA== + dependencies: + d3-color "1" + +d3-path@1: + version "1.0.9" + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.9.tgz#48c050bb1fe8c262493a8caf5524e3e9591701cf" + integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg== + +d3-polygon@1: + version "1.0.6" + resolved "https://registry.yarnpkg.com/d3-polygon/-/d3-polygon-1.0.6.tgz#0bf8cb8180a6dc107f518ddf7975e12abbfbd38e" + integrity sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ== + +d3-quadtree@1: + version "1.0.7" + resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-1.0.7.tgz#ca8b84df7bb53763fe3c2f24bd435137f4e53135" + integrity sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA== + +d3-random@1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-1.1.2.tgz#2833be7c124360bf9e2d3fd4f33847cfe6cab291" + integrity sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ== + +d3-scale-chromatic@1: + version "1.5.0" + resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-1.5.0.tgz#54e333fc78212f439b14641fb55801dd81135a98" + integrity sha512-ACcL46DYImpRFMBcpk9HhtIyC7bTBR4fNOPxwVSl0LfulDAwyiHyPOTqcDG1+t5d4P9W7t/2NAuWu59aKko/cg== + dependencies: + d3-color "1" + d3-interpolate "1" + +d3-scale@2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-2.2.2.tgz#4e880e0b2745acaaddd3ede26a9e908a9e17b81f" + integrity sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw== + dependencies: + d3-array "^1.2.0" + d3-collection "1" + d3-format "1" + d3-interpolate "1" + d3-time "1" + d3-time-format "2" + +d3-selection@1, d3-selection@^1.1.0: + version "1.4.2" + resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.4.2.tgz#dcaa49522c0dbf32d6c1858afc26b6094555bc5c" + integrity sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg== + +d3-shape@1: + version "1.3.7" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.7.tgz#df63801be07bc986bc54f63789b4fe502992b5d7" + integrity sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw== + dependencies: + d3-path "1" + +d3-time-format@2: + version "2.3.0" + resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-2.3.0.tgz#107bdc028667788a8924ba040faf1fbccd5a7850" + integrity sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ== + dependencies: + d3-time "1" + +d3-time@1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.1.0.tgz#b1e19d307dae9c900b7e5b25ffc5dcc249a8a0f1" + integrity sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA== + +d3-timer@1: + version "1.0.10" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.10.tgz#dfe76b8a91748831b13b6d9c793ffbd508dd9de5" + integrity sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw== + +d3-transition@1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-1.3.2.tgz#a98ef2151be8d8600543434c1ca80140ae23b398" + integrity sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA== + dependencies: + d3-color "1" + d3-dispatch "1" + d3-ease "1" + d3-interpolate "1" + d3-selection "^1.1.0" + d3-timer "1" + +d3-voronoi@1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/d3-voronoi/-/d3-voronoi-1.1.4.tgz#dd3c78d7653d2bb359284ae478645d95944c8297" + integrity sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg== + +d3-zoom@1: + version "1.8.3" + resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-1.8.3.tgz#b6a3dbe738c7763121cd05b8a7795ffe17f4fc0a" + integrity sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ== + dependencies: + d3-dispatch "1" + d3-drag "1" + d3-interpolate "1" + d3-selection "1" + d3-transition "1" + +d3@^5.15.1: + version "5.16.0" + resolved "https://registry.yarnpkg.com/d3/-/d3-5.16.0.tgz#9c5e8d3b56403c79d4ed42fbd62f6113f199c877" + integrity sha512-4PL5hHaHwX4m7Zr1UapXW23apo6pexCgdetdJ5kTmADpG/7T9Gkxw0M0tf/pjoB63ezCCm0u5UaFYy2aMt0Mcw== + dependencies: + d3-array "1" + d3-axis "1" + d3-brush "1" + d3-chord "1" + d3-collection "1" + d3-color "1" + d3-contour "1" + d3-dispatch "1" + d3-drag "1" + d3-dsv "1" + d3-ease "1" + d3-fetch "1" + d3-force "1" + d3-format "1" + d3-geo "1" + d3-hierarchy "1" + d3-interpolate "1" + d3-path "1" + d3-polygon "1" + d3-quadtree "1" + d3-random "1" + d3-scale "2" + d3-scale-chromatic "1" + d3-selection "1" + d3-shape "1" + d3-time "1" + d3-time-format "2" + d3-timer "1" + d3-transition "1" + d3-voronoi "1" + d3-zoom "1" dashdash@^1.12.0: version "1.14.1" @@ -2787,15 +3791,14 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -date-format@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/date-format/-/date-format-2.1.0.tgz#31d5b5ea211cf5fd764cd38baf9d033df7e125cf" - integrity sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA== - -date-format@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/date-format/-/date-format-3.0.0.tgz#eb8780365c7d2b1511078fb491e6479780f3ad95" - integrity sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w== +data-urls@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" + integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ== + dependencies: + abab "^2.0.3" + whatwg-mimetype "^2.3.0" + whatwg-url "^8.0.0" debug@2.6.9, debug@^2.2.0, debug@^2.3.3: version "2.6.9" @@ -2804,7 +3807,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3: dependencies: ms "2.0.0" -debug@4, debug@4.3.1, debug@^4.1.0, debug@^4.1.1, debug@~4.3.1: +debug@4, debug@4.3.1, debug@^4.1.0, debug@^4.1.1: version "4.3.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== @@ -2830,11 +3833,21 @@ decamelize@^1.2.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= +decimal.js@^10.2.1: + version "10.3.1" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" + integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== + decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= +dedent@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" + integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= + deep-equal@^1.0.1: version "1.1.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" @@ -2847,6 +3860,16 @@ deep-equal@^1.0.1: object-keys "^1.1.1" regexp.prototype.flags "^1.2.0" +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + +deepmerge@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== + default-gateway@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b" @@ -2934,15 +3957,25 @@ destroy@~1.0.4: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + detect-node@^2.0.4: version "2.1.0" resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== -di@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c" - integrity sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw= +diff-sequences@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" + integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q== + +diff-sequences@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.0.6.tgz#3305cb2e55a033924054695cc66019fd7f8e5723" + integrity sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ== dir-glob@^3.0.1: version "3.0.1" @@ -2971,16 +4004,6 @@ dns-txt@^2.0.2: dependencies: buffer-indexof "^1.0.0" -dom-serialize@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/dom-serialize/-/dom-serialize-2.2.1.tgz#562ae8999f44be5ea3076f5419dcd59eb43ac95b" - integrity sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs= - dependencies: - custom-event "~1.0.0" - ent "~2.2.0" - extend "^3.0.0" - void-elements "^2.0.0" - dom-serializer@^1.0.1: version "1.3.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91" @@ -2995,6 +4018,13 @@ domelementtype@^2.0.1, domelementtype@^2.2.0: resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57" integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== +domexception@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" + integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg== + dependencies: + webidl-conversions "^5.0.0" + domhandler@^4.0.0, domhandler@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.2.0.tgz#f9768a5f034be60a89a27c2e4d0f74eba0d8b059" @@ -3029,6 +4059,11 @@ electron-to-chromium@^1.3.723: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.752.tgz#0728587f1b9b970ec9ffad932496429aef750d09" integrity sha512-2Tg+7jSl3oPxgsBsWKh5H83QazTkmWG/cnNwJplmyZc7KcN61+I10oUgaXSVk/NwfvN3BdkKDR4FYuRBQQ2v0A== +emittery@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860" + integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg== + emoji-regex@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" @@ -3063,26 +4098,6 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" -engine.io-parser@~4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-4.0.2.tgz#e41d0b3fb66f7bf4a3671d2038a154024edb501e" - integrity sha512-sHfEQv6nmtJrq6TKuIz5kyEKH/qSdK56H/A+7DnAuUPWosnIZAS2NHNcPLmyjtY3cGS/MqJdZbUjW97JU72iYg== - dependencies: - base64-arraybuffer "0.1.4" - -engine.io@~4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-4.1.1.tgz#9a8f8a5ac5a5ea316183c489bf7f5b6cf91ace5b" - integrity sha512-t2E9wLlssQjGw0nluF6aYyfX8LwYU8Jj0xct+pAhfWfv/YrBn6TSNtEYsgxHIfaMqfrLx07czcMg9bMN6di+3w== - dependencies: - accepts "~1.3.4" - base64id "2.0.0" - cookie "~0.4.1" - cors "~2.8.5" - debug "~4.3.1" - engine.io-parser "~4.0.0" - ws "~7.4.2" - enhanced-resolve@5.7.0: version "5.7.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.7.0.tgz#525c5d856680fbd5052de453ac83e32049958b5c" @@ -3099,11 +4114,6 @@ enhanced-resolve@^5.8.0: graceful-fs "^4.2.4" tapable "^2.2.0" -ent@~2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d" - integrity sha1-6WQhkyWiHQX0RGai9obtbOX13R0= - entities@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" @@ -3153,6 +4163,23 @@ escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escodegen@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" + integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== + dependencies: + esprima "^4.0.1" + estraverse "^5.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + eslint-scope@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" @@ -3161,6 +4188,11 @@ eslint-scope@5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" +esprima@^4.0.0, esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + esrecurse@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" @@ -3218,6 +4250,26 @@ execa@^1.0.0: signal-exit "^3.0.0" strip-eof "^1.0.0" +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= + expand-brackets@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" @@ -3231,6 +4283,18 @@ expand-brackets@^2.1.4: snapdragon "^0.8.1" to-regex "^3.0.1" +expect@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/expect/-/expect-27.0.6.tgz#a4d74fbe27222c718fff68ef49d78e26a8fd4c05" + integrity sha512-psNLt8j2kwg42jGBDSfAlU49CEZxejN1f1PlANWDZqIhBOVU/c2Pm888FcjWJzFewhIsNWfZJeLjUjtKGiPuSw== + dependencies: + "@jest/types" "^27.0.6" + ansi-styles "^5.0.0" + jest-get-type "^27.0.6" + jest-matcher-utils "^27.0.6" + jest-message-util "^27.0.6" + jest-regex-util "^27.0.6" + express@^4.17.1: version "4.17.1" resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" @@ -3282,7 +4346,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@^3.0.0, extend@~3.0.2: +extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -3337,11 +4401,16 @@ fast-glob@^3.1.1, fast-glob@^3.2.5: micromatch "^4.0.2" picomatch "^2.2.1" -fast-json-stable-stringify@2.1.0, fast-json-stable-stringify@^2.0.0: +fast-json-stable-stringify@2.1.0, fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== +fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + fastq@^1.6.0: version "1.11.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.0.tgz#bb9fb955a07130a918eb63c1f5161cc32a5d0858" @@ -3356,6 +4425,13 @@ faye-websocket@^0.11.3: dependencies: websocket-driver ">=0.5.1" +fb-watchman@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" + integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== + dependencies: + bser "2.1.1" + figures@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" @@ -3385,7 +4461,7 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -finalhandler@1.1.2, finalhandler@~1.1.2: +finalhandler@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== @@ -3414,7 +4490,7 @@ find-up@^3.0.0: dependencies: locate-path "^3.0.0" -find-up@^4.0.0: +find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== @@ -3422,11 +4498,6 @@ find-up@^4.0.0: locate-path "^5.0.0" path-exists "^4.0.0" -flatted@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" - integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== - flatten@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b" @@ -3447,6 +4518,15 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= +form-data@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -3473,15 +4553,6 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= -fs-extra@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" - integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^4.0.0" - universalify "^0.1.0" - fs-minipass@^2.0.0, fs-minipass@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" @@ -3507,7 +4578,7 @@ fsevents@^1.2.7: bindings "^1.5.0" nan "^2.12.1" -fsevents@~2.3.2: +fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -3550,6 +4621,11 @@ get-intrinsic@^1.0.2: has "^1.0.3" has-symbols "^1.0.1" +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + get-stream@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" @@ -3557,6 +4633,11 @@ get-stream@^4.0.0: dependencies: pump "^3.0.0" +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" @@ -3589,7 +4670,7 @@ glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@7.1.7, glob@^7.0.3, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.1.7: +glob@7.1.7, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.1.7" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== @@ -3629,7 +4710,7 @@ globby@^6.1.0: pify "^2.0.0" pinkie-promise "^2.0.0" -graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.3, graceful-fs@^4.2.4, graceful-fs@^4.2.6: +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.2.3, graceful-fs@^4.2.4: version "4.2.6" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== @@ -3742,6 +4823,13 @@ hsla-regex@^1.0.0: resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg= +html-encoding-sniffer@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" + integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ== + dependencies: + whatwg-encoding "^1.0.5" + html-entities@^1.3.1: version "1.4.0" resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.4.0.tgz#cfbd1b01d2afaf9adca1b10ae7dffab98c71d2dc" @@ -3818,7 +4906,7 @@ http-proxy-middleware@0.19.1: lodash "^4.17.11" micromatch "^3.1.10" -http-proxy@^1.17.0, http-proxy@^1.18.1: +http-proxy@^1.17.0: version "1.18.1" resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== @@ -3844,6 +4932,11 @@ https-proxy-agent@5.0.0, https-proxy-agent@^5.0.0: agent-base "6" debug "4" +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + humanize-ms@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" @@ -3851,7 +4944,7 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" -iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4: +iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -3908,6 +5001,14 @@ import-local@^2.0.0: pkg-dir "^3.0.0" resolve-cwd "^2.0.0" +import-local@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.2.tgz#a8cfd0431d1de4a2199703d003e3e62364fa6db6" + integrity sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -4043,6 +5144,13 @@ is-buffer@^1.1.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== +is-ci@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-3.0.0.tgz#c7e7be3c9d8eef7d0fa144390bd1e4b88dc4c994" + integrity sha512-kDXyttuLeslKAHYL/K28F2YkM3x5jvFPEw3yXbRptXydjD9rpLEz+C5K5iutY9ZiUu6AP41JdvRQwF4Iqs4ZCQ== + dependencies: + ci-info "^3.1.1" + is-color-stop@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-color-stop/-/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345" @@ -4138,6 +5246,11 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + is-glob@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" @@ -4200,6 +5313,11 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" +is-potential-custom-element-name@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" + integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== + is-regex@^1.0.4: version "1.1.3" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.3.tgz#d029f9aff6448b93ebbe3f33dac71511fdcbef9f" @@ -4218,7 +5336,12 @@ is-stream@^1.1.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= -is-typedarray@~1.0.0: +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-typedarray@^1.0.0, is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= @@ -4255,11 +5378,6 @@ isarray@1.0.0, isarray@~1.0.0: resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= -isbinaryfile@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.8.tgz#5d34b94865bd4946633ecc78a026fc76c5b11fcf" - integrity sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w== - isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -4287,7 +5405,7 @@ istanbul-lib-coverage@^3.0.0: resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec" integrity sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg== -istanbul-lib-instrument@^4.0.1, istanbul-lib-instrument@^4.0.3: +istanbul-lib-instrument@^4.0.0, istanbul-lib-instrument@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== @@ -4306,27 +5424,442 @@ istanbul-lib-report@^3.0.0: make-dir "^3.0.0" supports-color "^7.1.0" -istanbul-lib-source-maps@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz#75743ce6d96bb86dc7ee4352cf6366a23f0b1ad9" - integrity sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg== +istanbul-lib-source-maps@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz#75743ce6d96bb86dc7ee4352cf6366a23f0b1ad9" + integrity sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.2.tgz#d593210e5000683750cb09fc0644e4b6e27fd53b" + integrity sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jasmine-core@~3.7.0: + version "3.7.1" + resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.7.1.tgz#0401327f6249eac993d47bbfa18d4e8efacfb561" + integrity sha512-DH3oYDS/AUvvr22+xUBW62m1Xoy7tUlY1tsxKEJvl5JeJ7q8zd1K5bUwiOxdH+erj6l2vAMM3hV25Xs9/WrmuQ== + +jest-changed-files@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-27.0.6.tgz#bed6183fcdea8a285482e3b50a9a7712d49a7a8b" + integrity sha512-BuL/ZDauaq5dumYh5y20sn4IISnf1P9A0TDswTxUi84ORGtVa86ApuBHqICL0vepqAnZiY6a7xeSPWv2/yy4eA== + dependencies: + "@jest/types" "^27.0.6" + execa "^5.0.0" + throat "^6.0.1" + +jest-circus@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-27.0.6.tgz#dd4df17c4697db6a2c232aaad4e9cec666926668" + integrity sha512-OJlsz6BBeX9qR+7O9lXefWoc2m9ZqcZ5Ohlzz0pTEAG4xMiZUJoacY8f4YDHxgk0oKYxj277AfOk9w6hZYvi1Q== + dependencies: + "@jest/environment" "^27.0.6" + "@jest/test-result" "^27.0.6" + "@jest/types" "^27.0.6" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^0.7.0" + expect "^27.0.6" + is-generator-fn "^2.0.0" + jest-each "^27.0.6" + jest-matcher-utils "^27.0.6" + jest-message-util "^27.0.6" + jest-runtime "^27.0.6" + jest-snapshot "^27.0.6" + jest-util "^27.0.6" + pretty-format "^27.0.6" + slash "^3.0.0" + stack-utils "^2.0.3" + throat "^6.0.1" + +jest-cli@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-27.0.6.tgz#d021e5f4d86d6a212450d4c7b86cb219f1e6864f" + integrity sha512-qUUVlGb9fdKir3RDE+B10ULI+LQrz+MCflEH2UJyoUjoHHCbxDrMxSzjQAPUMsic4SncI62ofYCcAvW6+6rhhg== + dependencies: + "@jest/core" "^27.0.6" + "@jest/test-result" "^27.0.6" + "@jest/types" "^27.0.6" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.4" + import-local "^3.0.2" + jest-config "^27.0.6" + jest-util "^27.0.6" + jest-validate "^27.0.6" + prompts "^2.0.1" + yargs "^16.0.3" + +jest-config@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-27.0.6.tgz#119fb10f149ba63d9c50621baa4f1f179500277f" + integrity sha512-JZRR3I1Plr2YxPBhgqRspDE2S5zprbga3swYNrvY3HfQGu7p/GjyLOqwrYad97tX3U3mzT53TPHVmozacfP/3w== + dependencies: + "@babel/core" "^7.1.0" + "@jest/test-sequencer" "^27.0.6" + "@jest/types" "^27.0.6" + babel-jest "^27.0.6" + chalk "^4.0.0" + deepmerge "^4.2.2" + glob "^7.1.1" + graceful-fs "^4.2.4" + is-ci "^3.0.0" + jest-circus "^27.0.6" + jest-environment-jsdom "^27.0.6" + jest-environment-node "^27.0.6" + jest-get-type "^27.0.6" + jest-jasmine2 "^27.0.6" + jest-regex-util "^27.0.6" + jest-resolve "^27.0.6" + jest-runner "^27.0.6" + jest-util "^27.0.6" + jest-validate "^27.0.6" + micromatch "^4.0.4" + pretty-format "^27.0.6" + +jest-diff@^26.0.0: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394" + integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA== + dependencies: + chalk "^4.0.0" + diff-sequences "^26.6.2" + jest-get-type "^26.3.0" + pretty-format "^26.6.2" + +jest-diff@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.0.6.tgz#4a7a19ee6f04ad70e0e3388f35829394a44c7b5e" + integrity sha512-Z1mqgkTCSYaFgwTlP/NUiRzdqgxmmhzHY1Tq17zL94morOHfHu3K4bgSgl+CR4GLhpV8VxkuOYuIWnQ9LnFqmg== + dependencies: + chalk "^4.0.0" + diff-sequences "^27.0.6" + jest-get-type "^27.0.6" + pretty-format "^27.0.6" + +jest-docblock@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.0.6.tgz#cc78266acf7fe693ca462cbbda0ea4e639e4e5f3" + integrity sha512-Fid6dPcjwepTFraz0YxIMCi7dejjJ/KL9FBjPYhBp4Sv1Y9PdhImlKZqYU555BlN4TQKaTc+F2Av1z+anVyGkA== + dependencies: + detect-newline "^3.0.0" + +jest-each@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-27.0.6.tgz#cee117071b04060158dc8d9a66dc50ad40ef453b" + integrity sha512-m6yKcV3bkSWrUIjxkE9OC0mhBZZdhovIW5ergBYirqnkLXkyEn3oUUF/QZgyecA1cF1QFyTE8bRRl8Tfg1pfLA== + dependencies: + "@jest/types" "^27.0.6" + chalk "^4.0.0" + jest-get-type "^27.0.6" + jest-util "^27.0.6" + pretty-format "^27.0.6" + +jest-environment-jsdom@^27.0.0, jest-environment-jsdom@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-27.0.6.tgz#f66426c4c9950807d0a9f209c590ce544f73291f" + integrity sha512-FvetXg7lnXL9+78H+xUAsra3IeZRTiegA3An01cWeXBspKXUhAwMM9ycIJ4yBaR0L7HkoMPaZsozCLHh4T8fuw== + dependencies: + "@jest/environment" "^27.0.6" + "@jest/fake-timers" "^27.0.6" + "@jest/types" "^27.0.6" + "@types/node" "*" + jest-mock "^27.0.6" + jest-util "^27.0.6" + jsdom "^16.6.0" + +jest-environment-node@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-27.0.6.tgz#a6699b7ceb52e8d68138b9808b0c404e505f3e07" + integrity sha512-+Vi6yLrPg/qC81jfXx3IBlVnDTI6kmRr08iVa2hFCWmJt4zha0XW7ucQltCAPhSR0FEKEoJ3i+W4E6T0s9is0w== + dependencies: + "@jest/environment" "^27.0.6" + "@jest/fake-timers" "^27.0.6" + "@jest/types" "^27.0.6" + "@types/node" "*" + jest-mock "^27.0.6" + jest-util "^27.0.6" + +jest-get-type@^26.3.0: + version "26.3.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" + integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig== + +jest-get-type@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.0.6.tgz#0eb5c7f755854279ce9b68a9f1a4122f69047cfe" + integrity sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg== + +jest-haste-map@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.0.6.tgz#4683a4e68f6ecaa74231679dca237279562c8dc7" + integrity sha512-4ldjPXX9h8doB2JlRzg9oAZ2p6/GpQUNAeiYXqcpmrKbP0Qev0wdZlxSMOmz8mPOEnt4h6qIzXFLDi8RScX/1w== + dependencies: + "@jest/types" "^27.0.6" + "@types/graceful-fs" "^4.1.2" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.4" + jest-regex-util "^27.0.6" + jest-serializer "^27.0.6" + jest-util "^27.0.6" + jest-worker "^27.0.6" + micromatch "^4.0.4" + walker "^1.0.7" + optionalDependencies: + fsevents "^2.3.2" + +jest-jasmine2@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-27.0.6.tgz#fd509a9ed3d92bd6edb68a779f4738b100655b37" + integrity sha512-cjpH2sBy+t6dvCeKBsHpW41mjHzXgsavaFMp+VWRf0eR4EW8xASk1acqmljFtK2DgyIECMv2yCdY41r2l1+4iA== + dependencies: + "@babel/traverse" "^7.1.0" + "@jest/environment" "^27.0.6" + "@jest/source-map" "^27.0.6" + "@jest/test-result" "^27.0.6" + "@jest/types" "^27.0.6" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + expect "^27.0.6" + is-generator-fn "^2.0.0" + jest-each "^27.0.6" + jest-matcher-utils "^27.0.6" + jest-message-util "^27.0.6" + jest-runtime "^27.0.6" + jest-snapshot "^27.0.6" + jest-util "^27.0.6" + pretty-format "^27.0.6" + throat "^6.0.1" + +jest-leak-detector@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-27.0.6.tgz#545854275f85450d4ef4b8fe305ca2a26450450f" + integrity sha512-2/d6n2wlH5zEcdctX4zdbgX8oM61tb67PQt4Xh8JFAIy6LRKUnX528HulkaG6nD5qDl5vRV1NXejCe1XRCH5gQ== + dependencies: + jest-get-type "^27.0.6" + pretty-format "^27.0.6" + +jest-matcher-utils@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.0.6.tgz#2a8da1e86c620b39459f4352eaa255f0d43e39a9" + integrity sha512-OFgF2VCQx9vdPSYTHWJ9MzFCehs20TsyFi6bIHbk5V1u52zJOnvF0Y/65z3GLZHKRuTgVPY4Z6LVePNahaQ+tA== + dependencies: + chalk "^4.0.0" + jest-diff "^27.0.6" + jest-get-type "^27.0.6" + pretty-format "^27.0.6" + +jest-message-util@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.0.6.tgz#158bcdf4785706492d164a39abca6a14da5ab8b5" + integrity sha512-rBxIs2XK7rGy+zGxgi+UJKP6WqQ+KrBbD1YMj517HYN3v2BG66t3Xan3FWqYHKZwjdB700KiAJ+iES9a0M+ixw== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^27.0.6" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.4" + micromatch "^4.0.4" + pretty-format "^27.0.6" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.0.6.tgz#0efdd40851398307ba16778728f6d34d583e3467" + integrity sha512-lzBETUoK8cSxts2NYXSBWT+EJNzmUVtVVwS1sU9GwE1DLCfGsngg+ZVSIe0yd0ZSm+y791esiuo+WSwpXJQ5Bw== + dependencies: + "@jest/types" "^27.0.6" + "@types/node" "*" + +jest-pnp-resolver@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" + integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== + +jest-preset-angular@^9.0.5: + version "9.0.5" + resolved "https://registry.yarnpkg.com/jest-preset-angular/-/jest-preset-angular-9.0.5.tgz#0cbd86aee68a7ed3a84ffade7f40c277ce38b61c" + integrity sha512-FTvyTA1lXg6voqOV7gH/6ZXqalNCpD3Yuv+uJg11BAhO13wdjy/ebutiuQAqSq/Zu8zFS6oDIhCzan1EpyY0tQ== + dependencies: + jest-environment-jsdom "^27.0.0" + pretty-format "^27.0.0" + ts-jest "^27.0.0" + +jest-regex-util@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-27.0.6.tgz#02e112082935ae949ce5d13b2675db3d8c87d9c5" + integrity sha512-SUhPzBsGa1IKm8hx2F4NfTGGp+r7BXJ4CulsZ1k2kI+mGLG+lxGrs76veN2LF/aUdGosJBzKgXmNCw+BzFqBDQ== + +jest-resolve-dependencies@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-27.0.6.tgz#3e619e0ef391c3ecfcf6ef4056207a3d2be3269f" + integrity sha512-mg9x9DS3BPAREWKCAoyg3QucCr0n6S8HEEsqRCKSPjPcu9HzRILzhdzY3imsLoZWeosEbJZz6TKasveczzpJZA== + dependencies: + "@jest/types" "^27.0.6" + jest-regex-util "^27.0.6" + jest-snapshot "^27.0.6" + +jest-resolve@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-27.0.6.tgz#e90f436dd4f8fbf53f58a91c42344864f8e55bff" + integrity sha512-yKmIgw2LgTh7uAJtzv8UFHGF7Dm7XfvOe/LQ3Txv101fLM8cx2h1QVwtSJ51Q/SCxpIiKfVn6G2jYYMDNHZteA== + dependencies: + "@jest/types" "^27.0.6" + chalk "^4.0.0" + escalade "^3.1.1" + graceful-fs "^4.2.4" + jest-pnp-resolver "^1.2.2" + jest-util "^27.0.6" + jest-validate "^27.0.6" + resolve "^1.20.0" + slash "^3.0.0" + +jest-runner@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-27.0.6.tgz#1325f45055539222bbc7256a6976e993ad2f9520" + integrity sha512-W3Bz5qAgaSChuivLn+nKOgjqNxM7O/9JOJoKDCqThPIg2sH/d4A/lzyiaFgnb9V1/w29Le11NpzTJSzga1vyYQ== + dependencies: + "@jest/console" "^27.0.6" + "@jest/environment" "^27.0.6" + "@jest/test-result" "^27.0.6" + "@jest/transform" "^27.0.6" + "@jest/types" "^27.0.6" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.8.1" + exit "^0.1.2" + graceful-fs "^4.2.4" + jest-docblock "^27.0.6" + jest-environment-jsdom "^27.0.6" + jest-environment-node "^27.0.6" + jest-haste-map "^27.0.6" + jest-leak-detector "^27.0.6" + jest-message-util "^27.0.6" + jest-resolve "^27.0.6" + jest-runtime "^27.0.6" + jest-util "^27.0.6" + jest-worker "^27.0.6" + source-map-support "^0.5.6" + throat "^6.0.1" + +jest-runtime@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-27.0.6.tgz#45877cfcd386afdd4f317def551fc369794c27c9" + integrity sha512-BhvHLRVfKibYyqqEFkybsznKwhrsu7AWx2F3y9G9L95VSIN3/ZZ9vBpm/XCS2bS+BWz3sSeNGLzI3TVQ0uL85Q== + dependencies: + "@jest/console" "^27.0.6" + "@jest/environment" "^27.0.6" + "@jest/fake-timers" "^27.0.6" + "@jest/globals" "^27.0.6" + "@jest/source-map" "^27.0.6" + "@jest/test-result" "^27.0.6" + "@jest/transform" "^27.0.6" + "@jest/types" "^27.0.6" + "@types/yargs" "^16.0.0" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.4" + jest-haste-map "^27.0.6" + jest-message-util "^27.0.6" + jest-mock "^27.0.6" + jest-regex-util "^27.0.6" + jest-resolve "^27.0.6" + jest-snapshot "^27.0.6" + jest-util "^27.0.6" + jest-validate "^27.0.6" + slash "^3.0.0" + strip-bom "^4.0.0" + yargs "^16.0.3" + +jest-serializer@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-27.0.6.tgz#93a6c74e0132b81a2d54623251c46c498bb5bec1" + integrity sha512-PtGdVK9EGC7dsaziskfqaAPib6wTViY3G8E5wz9tLVPhHyiDNTZn/xjZ4khAw+09QkoOVpn7vF5nPSN6dtBexA== dependencies: - debug "^4.1.1" - istanbul-lib-coverage "^3.0.0" - source-map "^0.6.1" + "@types/node" "*" + graceful-fs "^4.2.4" -istanbul-reports@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.2.tgz#d593210e5000683750cb09fc0644e4b6e27fd53b" - integrity sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw== +jest-snapshot@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-27.0.6.tgz#f4e6b208bd2e92e888344d78f0f650bcff05a4bf" + integrity sha512-NTHaz8He+ATUagUgE7C/UtFcRoHqR2Gc+KDfhQIyx+VFgwbeEMjeP+ILpUTLosZn/ZtbNdCF5LkVnN/l+V751A== + dependencies: + "@babel/core" "^7.7.2" + "@babel/generator" "^7.7.2" + "@babel/parser" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/traverse" "^7.7.2" + "@babel/types" "^7.0.0" + "@jest/transform" "^27.0.6" + "@jest/types" "^27.0.6" + "@types/babel__traverse" "^7.0.4" + "@types/prettier" "^2.1.5" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^27.0.6" + graceful-fs "^4.2.4" + jest-diff "^27.0.6" + jest-get-type "^27.0.6" + jest-haste-map "^27.0.6" + jest-matcher-utils "^27.0.6" + jest-message-util "^27.0.6" + jest-resolve "^27.0.6" + jest-util "^27.0.6" + natural-compare "^1.4.0" + pretty-format "^27.0.6" + semver "^7.3.2" + +jest-util@^27.0.0, jest-util@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.0.6.tgz#e8e04eec159de2f4d5f57f795df9cdc091e50297" + integrity sha512-1JjlaIh+C65H/F7D11GNkGDDZtDfMEM8EBXsvd+l/cxtgQ6QhxuloOaiayt89DxUvDarbVhqI98HhgrM1yliFQ== dependencies: - html-escaper "^2.0.0" - istanbul-lib-report "^3.0.0" + "@jest/types" "^27.0.6" + "@types/node" "*" + chalk "^4.0.0" + graceful-fs "^4.2.4" + is-ci "^3.0.0" + picomatch "^2.2.3" -jasmine-core@^3.6.0, jasmine-core@~3.7.0: - version "3.7.1" - resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.7.1.tgz#0401327f6249eac993d47bbfa18d4e8efacfb561" - integrity sha512-DH3oYDS/AUvvr22+xUBW62m1Xoy7tUlY1tsxKEJvl5JeJ7q8zd1K5bUwiOxdH+erj6l2vAMM3hV25Xs9/WrmuQ== +jest-validate@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-27.0.6.tgz#930a527c7a951927df269f43b2dc23262457e2a6" + integrity sha512-yhZZOaMH3Zg6DC83n60pLmdU1DQE46DW+KLozPiPbSbPhlXXaiUTDlhHQhHFpaqIFRrInko1FHXjTRpjWRuWfA== + dependencies: + "@jest/types" "^27.0.6" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^27.0.6" + leven "^3.1.0" + pretty-format "^27.0.6" + +jest-watcher@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-27.0.6.tgz#89526f7f9edf1eac4e4be989bcb6dec6b8878d9c" + integrity sha512-/jIoKBhAP00/iMGnTwUBLgvxkn7vsOweDrOTSPzc7X9uOyUtJIDthQBTI1EXz90bdkrxorUZVhJwiB69gcHtYQ== + dependencies: + "@jest/test-result" "^27.0.6" + "@jest/types" "^27.0.6" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + jest-util "^27.0.6" + string-length "^4.0.1" jest-worker@26.6.2, jest-worker@^26.3.0, jest-worker@^26.6.2: version "26.6.2" @@ -4346,6 +5879,24 @@ jest-worker@^27.0.2: merge-stream "^2.0.0" supports-color "^8.0.0" +jest-worker@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.0.6.tgz#a5fdb1e14ad34eb228cfe162d9f729cdbfa28aed" + integrity sha512-qupxcj/dRuA3xHPMUd40gr2EaAurFbkwzOh7wfPaeE9id7hyjURRQoqNfHifHK3XjJU6YJJUQKILGUnwGPEOCA== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest/-/jest-27.0.6.tgz#10517b2a628f0409087fbf473db44777d7a04505" + integrity sha512-EjV8aETrsD0wHl7CKMibKwQNQc3gIRBXlTikBmmHUeVMKaPFxdcUIBfoDqTSXDoGJIivAYGqCWVlzCSaVjPQsA== + dependencies: + "@jest/core" "^27.0.6" + import-local "^3.0.2" + jest-cli "^27.0.6" + js-sha256@0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.9.0.tgz#0b89ac166583e91ef9123644bd3c5334ce9d0966" @@ -4356,11 +5907,52 @@ js-tokens@^4.0.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= +jsdom@^16.6.0: + version "16.6.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.6.0.tgz#f79b3786682065492a3da6a60a4695da983805ac" + integrity sha512-Ty1vmF4NHJkolaEmdjtxTfSfkdb8Ywarwf63f+F8/mDD1uLSSWDxDuMiZxiPhwunLrn9LOSVItWj4bLYsLN3Dg== + dependencies: + abab "^2.0.5" + acorn "^8.2.4" + acorn-globals "^6.0.0" + cssom "^0.4.4" + cssstyle "^2.3.0" + data-urls "^2.0.0" + decimal.js "^10.2.1" + domexception "^2.0.1" + escodegen "^2.0.0" + form-data "^3.0.0" + html-encoding-sniffer "^2.0.1" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.0" + parse5 "6.0.1" + saxes "^5.0.1" + symbol-tree "^3.2.4" + tough-cookie "^4.0.0" + w3c-hr-time "^1.0.2" + w3c-xmlserializer "^2.0.0" + webidl-conversions "^6.1.0" + whatwg-encoding "^1.0.5" + whatwg-mimetype "^2.3.0" + whatwg-url "^8.5.0" + ws "^7.4.5" + xml-name-validator "^3.0.0" + jsesc@^2.5.1: version "2.5.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" @@ -4406,6 +5998,13 @@ json3@^3.3.3: resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81" integrity sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA== +json5@2.x, json5@^2.1.2: + version "2.2.0" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" + integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== + dependencies: + minimist "^1.2.5" + json5@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" @@ -4413,25 +6012,11 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" -json5@^2.1.2: - version "2.2.0" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" - integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== - dependencies: - minimist "^1.2.5" - jsonc-parser@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.0.0.tgz#abdd785701c7e7eaca8a9ec8cf070ca51a745a22" integrity sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA== -jsonfile@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= - optionalDependencies: - graceful-fs "^4.1.6" - jsonparse@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" @@ -4447,37 +6032,6 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -karma-chrome-launcher@~3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-3.1.0.tgz#805a586799a4d05f4e54f72a204979f3f3066738" - integrity sha512-3dPs/n7vgz1rxxtynpzZTvb9y/GIaW8xjAwcIGttLbycqoFtI7yo1NGnQi6oFTherRE+GIhCAHZC4vEqWGhNvg== - dependencies: - which "^1.2.1" - -karma-coverage@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/karma-coverage/-/karma-coverage-2.0.3.tgz#c10f4711f4cf5caaaa668b1d6f642e7da122d973" - integrity sha512-atDvLQqvPcLxhED0cmXYdsPMCQuh6Asa9FMZW1bhNqlVEhJoB9qyZ2BY1gu7D/rr5GLGb5QzYO4siQskxaWP/g== - dependencies: - istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^4.0.1" - istanbul-lib-report "^3.0.0" - istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.0.0" - minimatch "^3.0.4" - -karma-jasmine-html-reporter@^1.5.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.6.0.tgz#586e17025a1b4128e9fba55d5f1e8921bfc3bc1e" - integrity sha512-ELO9yf0cNqpzaNLsfFgXd/wxZVYkE2+ECUwhMHUD4PZ17kcsPsYsVyjquiRqyMn2jkd2sHt0IeMyAyq1MC23Fw== - -karma-jasmine@~4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/karma-jasmine/-/karma-jasmine-4.0.1.tgz#b99e073b6d99a5196fc4bffc121b89313b0abd82" - integrity sha512-h8XDAhTiZjJKzfkoO1laMH+zfNlra+dEQHUAjpn5JV1zCPtOIVWGQjLBrqhnzQa/hrU2XrZwSyBa6XjEBzfXzw== - dependencies: - jasmine-core "^3.6.0" - karma-source-map-support@1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz#58526ceccf7e8730e56effd97a4de8d712ac0d6b" @@ -4485,35 +6039,6 @@ karma-source-map-support@1.4.0: dependencies: source-map-support "^0.5.5" -karma@~6.3.0: - version "6.3.4" - resolved "https://registry.yarnpkg.com/karma/-/karma-6.3.4.tgz#359899d3aab3d6b918ea0f57046fd2a6b68565e6" - integrity sha512-hbhRogUYIulfkBTZT7xoPrCYhRBnBoqbbL4fszWD0ReFGUxU+LYBr3dwKdAluaDQ/ynT9/7C+Lf7pPNW4gSx4Q== - dependencies: - body-parser "^1.19.0" - braces "^3.0.2" - chokidar "^3.5.1" - colors "^1.4.0" - connect "^3.7.0" - di "^0.0.1" - dom-serialize "^2.2.1" - glob "^7.1.7" - graceful-fs "^4.2.6" - http-proxy "^1.18.1" - isbinaryfile "^4.0.8" - lodash "^4.17.21" - log4js "^6.3.0" - mime "^2.5.2" - minimatch "^3.0.4" - qjobs "^1.2.0" - range-parser "^1.2.1" - rimraf "^3.0.2" - socket.io "^3.1.0" - source-map "^0.6.1" - tmp "^0.2.1" - ua-parser-js "^0.7.28" - yargs "^16.1.1" - keycloak-angular@^8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/keycloak-angular/-/keycloak-angular-8.2.0.tgz#8c9f3902942fee3abfdae7aa88f155265e7fdba6" @@ -4558,6 +6083,11 @@ kind-of@^6.0.0, kind-of@^6.0.2: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + klona@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0" @@ -4587,6 +6117,19 @@ less@4.1.1: needle "^2.5.2" source-map "~0.6.0" +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + license-webpack-plugin@2.3.19: version "2.3.19" resolved "https://registry.yarnpkg.com/license-webpack-plugin/-/license-webpack-plugin-2.3.19.tgz#f02720b2b0bcd9ae27fb63f0bd908d9ac9335d6c" @@ -4653,7 +6196,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.21: +lodash@4.x, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.21, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -4666,17 +6209,6 @@ log-symbols@^4.1.0: chalk "^4.1.0" is-unicode-supported "^0.1.0" -log4js@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.3.0.tgz#10dfafbb434351a3e30277a00b9879446f715bcb" - integrity sha512-Mc8jNuSFImQUIateBFwdOQcmC6Q5maU0VVvdC2R6XMb66/VnT+7WS4D/0EeNMZu1YODmJe5NIn2XftCzEocUgw== - dependencies: - date-format "^3.0.0" - debug "^4.1.1" - flatted "^2.0.1" - rfdc "^1.1.4" - streamroller "^2.2.4" - loglevel@^1.6.8: version "1.7.1" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197" @@ -4711,6 +6243,11 @@ make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: dependencies: semver "^6.0.0" +make-error@1.x: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + make-fetch-happen@^8.0.9: version "8.0.14" resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-8.0.14.tgz#aaba73ae0ab5586ad8eaa68bd83332669393e222" @@ -4732,6 +6269,13 @@ make-fetch-happen@^8.0.9: socks-proxy-agent "^5.0.0" ssri "^8.0.0" +makeerror@1.0.x: + version "1.0.11" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" + integrity sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw= + dependencies: + tmpl "1.0.x" + map-age-cleaner@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" @@ -4830,7 +6374,7 @@ micromatch@^3.1.10, micromatch@^3.1.4: snapdragon "^0.8.1" to-regex "^3.0.2" -micromatch@^4.0.2: +micromatch@^4.0.2, micromatch@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== @@ -4855,7 +6399,7 @@ mime@1.6.0, mime@^1.4.1: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mime@^2.4.4, mime@^2.5.2: +mime@^2.4.4: version "2.5.2" resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg== @@ -4966,6 +6510,11 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" +mkdirp@1.x, mkdirp@^1.0.3, mkdirp@^1.0.4, mkdirp@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + mkdirp@^0.5.1, mkdirp@^0.5.5: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" @@ -4973,11 +6522,6 @@ mkdirp@^0.5.1, mkdirp@^0.5.5: dependencies: minimist "^1.2.5" -mkdirp@^1.0.3, mkdirp@^1.0.4, mkdirp@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== - ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -5043,6 +6587,11 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + needle@^2.5.2: version "2.6.0" resolved "https://registry.yarnpkg.com/needle/-/needle-2.6.0.tgz#24dbb55f2509e2324b4a99d61f413982013ccdbe" @@ -5109,6 +6658,16 @@ node-gyp@^7.1.0: tar "^6.0.2" which "^2.0.2" +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= + +node-modules-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" + integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= + node-releases@^1.1.71: version "1.1.73" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.73.tgz#dd4e81ddd5277ff846b80b52bb40c49edf7a7b20" @@ -5220,6 +6779,13 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + npmlog@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" @@ -5247,12 +6813,17 @@ number-is-nan@^1.0.0: resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= +nwsapi@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" + integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== + oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0: +object-assign@^4.0.1, object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -5327,7 +6898,7 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -onetime@^5.1.0: +onetime@^5.1.0, onetime@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== @@ -5350,6 +6921,18 @@ opn@^5.5.0: dependencies: is-wsl "^1.1.0" +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + ora@5.4.0: version "5.4.0" resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.0.tgz#42eda4855835b9cd14d33864c97a3c95a3f56bf4" @@ -5382,6 +6965,11 @@ p-defer@^1.0.0: resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= +p-each-series@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a" + integrity sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA== + p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" @@ -5508,7 +7096,7 @@ parse5-sax-parser@^6.0.1: dependencies: parse5 "^6.0.1" -parse5@^6.0.1: +parse5@6.0.1, parse5@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== @@ -5553,6 +7141,11 @@ path-key@^2.0.0, path-key@^2.0.1: resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + path-parse@^1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" @@ -5600,6 +7193,13 @@ pinkie@^2.0.0: resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= +pirates@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87" + integrity sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA== + dependencies: + node-modules-regexp "^1.0.0" + pkg-dir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" @@ -5607,7 +7207,7 @@ pkg-dir@^3.0.0: dependencies: find-up "^3.0.0" -pkg-dir@^4.1.0: +pkg-dir@^4.1.0, pkg-dir@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== @@ -6206,11 +7806,36 @@ postcss@^8.2.10, postcss@^8.2.9: nanoid "^3.1.23" source-map-js "^0.6.2" +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + pretty-bytes@^5.3.0: version "5.6.0" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== +pretty-format@^26.0.0, pretty-format@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" + integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg== + dependencies: + "@jest/types" "^26.6.2" + ansi-regex "^5.0.0" + ansi-styles "^4.0.0" + react-is "^17.0.1" + +pretty-format@^27.0.0, pretty-format@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.0.6.tgz#ab770c47b2c6f893a21aefc57b75da63ef49a11f" + integrity sha512-8tGD7gBIENgzqA+UBzObyWqQ5B778VIFZA/S66cclyd5YkFLYs2Js7gxDKf0MXtTc9zcS7t1xhdfcElJ3YIvkQ== + dependencies: + "@jest/types" "^27.0.6" + ansi-regex "^5.0.0" + ansi-styles "^5.0.0" + react-is "^17.0.1" + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -6229,6 +7854,14 @@ promise-retry@^2.0.1: err-code "^2.0.2" retry "^0.12.0" +prompts@^2.0.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.1.tgz#befd3b1195ba052f9fd2fde8a486c4e82ee77f61" + integrity sha512-EQyfIuO2hPDsX1L/blblV+H7I0knhgAd82cVneCwcdND9B8AuCDuRcBH6yIcG4dFzlOUqbazQqwGjx5xmsNLuQ== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + proxy-addr@~2.0.5: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -6242,7 +7875,7 @@ prr@~1.0.1: resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= -psl@^1.1.28: +psl@^1.1.28, psl@^1.1.33: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== @@ -6265,11 +7898,6 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -qjobs@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071" - integrity sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg== - qs@6.7.0: version "6.7.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" @@ -6325,6 +7953,11 @@ raw-loader@4.0.2: loader-utils "^2.0.0" schema-utils "^3.0.0" +react-is@^17.0.1: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + read-cache@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" @@ -6520,6 +8153,13 @@ resolve-cwd@^2.0.0: dependencies: resolve-from "^3.0.0" +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + resolve-from@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" @@ -6530,6 +8170,11 @@ resolve-from@^4.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + resolve-url-loader@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz#d50d4ddc746bb10468443167acf800dcd6c3ad57" @@ -6546,7 +8191,7 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@1.20.0, resolve@^1.1.7, resolve@^1.14.2: +resolve@1.20.0, resolve@^1.1.7, resolve@^1.14.2, resolve@^1.20.0: version "1.20.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== @@ -6577,11 +8222,6 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rfdc@^1.1.4: - version "1.3.0" - resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" - integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== - rgb-regex@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" @@ -6618,6 +8258,11 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" +rw@1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" + integrity sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q= + rxjs@6.6.7, rxjs@^6.6.6, rxjs@~6.6.0: version "6.6.7" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" @@ -6667,6 +8312,13 @@ sax@^1.2.4, sax@~1.2.4: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== +saxes@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" + integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== + dependencies: + xmlchars "^2.2.0" + schema-utils@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" @@ -6711,7 +8363,7 @@ semver@7.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== -semver@7.3.5, semver@^7.0.0, semver@^7.1.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: +semver@7.3.5, semver@7.x, semver@^7.0.0, semver@^7.1.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== @@ -6816,16 +8468,33 @@ shebang-command@^1.2.0: dependencies: shebang-regex "^1.0.0" +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= -signal-exit@^3.0.0, signal-exit@^3.0.2: +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -6866,35 +8535,6 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" -socket.io-adapter@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.1.0.tgz#edc5dc36602f2985918d631c1399215e97a1b527" - integrity sha512-+vDov/aTsLjViYTwS9fPy5pEtTkrbEKsw2M+oVSoFGw6OD1IpvlV1VPhUzNbofCQ8oyMbdYJqDtGdmHQK6TdPg== - -socket.io-parser@~4.0.3: - version "4.0.4" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.0.4.tgz#9ea21b0d61508d18196ef04a2c6b9ab630f4c2b0" - integrity sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g== - dependencies: - "@types/component-emitter" "^1.2.10" - component-emitter "~1.3.0" - debug "~4.3.1" - -socket.io@^3.1.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-3.1.2.tgz#06e27caa1c4fc9617547acfbb5da9bc1747da39a" - integrity sha512-JubKZnTQ4Z8G4IZWtaAZSiRP3I/inpy8c/Bsx2jrwGrTbKeVU5xd6qkKMHpChYeM3dWZSO0QACiGK+obhBNwYw== - dependencies: - "@types/cookie" "^0.4.0" - "@types/cors" "^2.8.8" - "@types/node" ">=10.0.0" - accepts "~1.3.4" - base64id "~2.0.0" - debug "~4.3.1" - engine.io "~4.1.0" - socket.io-adapter "~2.1.0" - socket.io-parser "~4.0.3" - sockjs-client@^1.5.0: version "1.5.1" resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.5.1.tgz#256908f6d5adfb94dabbdbd02c66362cca0f9ea6" @@ -6971,7 +8611,7 @@ source-map-resolve@^0.6.0: atob "^2.1.2" decode-uri-component "^0.2.0" -source-map-support@0.5.19, source-map-support@^0.5.5, source-map-support@~0.5.19: +source-map-support@0.5.19, source-map-support@^0.5.5, source-map-support@^0.5.6, source-map-support@~0.5.19: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== @@ -7034,6 +8674,11 @@ split-string@^3.0.1, split-string@^3.0.2: dependencies: extend-shallow "^3.0.0" +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + sshpk@^1.7.0: version "1.16.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" @@ -7061,6 +8706,13 @@ stable@^0.1.8: resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== +stack-utils@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.3.tgz#cd5f030126ff116b78ccb3c027fe302713b61277" + integrity sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw== + dependencies: + escape-string-regexp "^2.0.0" + static-extend@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" @@ -7074,14 +8726,13 @@ static-extend@^0.1.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= -streamroller@^2.2.4: - version "2.2.4" - resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-2.2.4.tgz#c198ced42db94086a6193608187ce80a5f2b0e53" - integrity sha512-OG79qm3AujAM9ImoqgWEY1xG4HX+Lw+yY6qZj9R1K2mhF5bEmQ849wvrb+4vt4jLMLzwXttJlQbOdPOQVRv7DQ== +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== dependencies: - date-format "^2.1.0" - debug "^4.1.1" - fs-extra "^8.1.0" + char-regex "^1.0.2" + strip-ansi "^6.0.0" string-width@^1.0.1: version "1.0.2" @@ -7160,11 +8811,21 @@ strip-ansi@^6.0.0: dependencies: ansi-regex "^5.0.0" +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + strip-eof@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + style-loader@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-2.0.0.tgz#9669602fd4690740eaaec137799a03addbbc393c" @@ -7232,6 +8893,14 @@ supports-color@^8.0.0: dependencies: has-flag "^4.0.0" +supports-hyperlinks@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb" + integrity sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + svgo@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.3.0.tgz#6b3af81d0cbd1e19c83f5f63cec2cb98c70b5373" @@ -7250,6 +8919,11 @@ symbol-observable@4.0.0: resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-4.0.0.tgz#5b425f192279e87f2f9b937ac8540d1984b39205" integrity sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ== +symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + tapable@^2.1.1, tapable@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.0.tgz#5c373d281d9c672848213d0e037d1c4165ab426b" @@ -7267,6 +8941,14 @@ tar@^6.0.2, tar@^6.1.0: mkdirp "^1.0.3" yallist "^4.0.0" +terminal-link@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" + integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== + dependencies: + ansi-escapes "^4.2.1" + supports-hyperlinks "^2.0.0" + terser-webpack-plugin@5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.1.2.tgz#51d295eb7cc56785a67a372575fdc46e42d5c20c" @@ -7300,11 +8982,25 @@ terser@5.7.0, terser@^5.7.0: source-map "~0.7.2" source-map-support "~0.5.19" +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + text-table@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= +throat@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" + integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w== + through@^2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -7327,12 +9023,10 @@ tmp@^0.0.33: dependencies: os-tmpdir "~1.0.2" -tmp@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" - integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== - dependencies: - rimraf "^3.0.0" +tmpl@1.0.x: + version "1.0.4" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" + integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE= to-fast-properties@^2.0.0: version "2.0.0" @@ -7376,6 +9070,15 @@ toidentifier@1.0.0: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== +tough-cookie@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" + integrity sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.1.2" + tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" @@ -7384,11 +9087,34 @@ tough-cookie@~2.5.0: psl "^1.1.28" punycode "^2.1.1" +tr46@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240" + integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw== + dependencies: + punycode "^2.1.1" + tree-kill@1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== +ts-jest@^27.0.0: + version "27.0.4" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.0.4.tgz#df49683535831560ccb58f94c023d831b1b80df0" + integrity sha512-c4E1ECy9Xz2WGfTMyHbSaArlIva7Wi2p43QOMmCqjSSjHP06KXv+aT+eSY+yZMuqsMi3k7pyGsGj2q5oSl5WfQ== + dependencies: + bs-logger "0.x" + buffer-from "1.x" + fast-json-stable-stringify "2.x" + jest-util "^27.0.0" + json5 "2.x" + lodash "4.x" + make-error "1.x" + mkdirp "1.x" + semver "7.x" + yargs-parser "20.x" + tslib@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c" @@ -7416,6 +9142,18 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + type-fest@^0.21.3: version "0.21.3" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" @@ -7429,16 +9167,18 @@ type-is@~1.6.17, type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + typescript@4.2.4, typescript@~4.2.3: version "4.2.4" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961" integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg== -ua-parser-js@^0.7.28: - version "0.7.28" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31" - integrity sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g== - unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" @@ -7496,7 +9236,7 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" -universalify@^0.1.0: +universalify@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== @@ -7572,6 +9312,15 @@ uuid@^3.3.2, uuid@^3.4.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== +v8-to-istanbul@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-8.0.0.tgz#4229f2a99e367f3f018fa1d5c2b8ec684667c69c" + integrity sha512-LkmXi8UUNxnCC+JlH7/fsfsKr5AU110l+SYGJimWNkWhxbN5EyeOtm1MJ0hhvqMMOhGwBj1Fp70Yv9i+hX0QAg== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^1.6.0" + source-map "^0.7.3" + validate-npm-package-name@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz#5fa912d81eb7d0c74afc140de7317f0ca7df437e" @@ -7579,7 +9328,7 @@ validate-npm-package-name@^3.0.0: dependencies: builtins "^1.0.3" -vary@^1, vary@~1.1.2: +vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= @@ -7598,10 +9347,26 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -void-elements@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" - integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w= +w3c-hr-time@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" + integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== + dependencies: + browser-process-hrtime "^1.0.0" + +w3c-xmlserializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a" + integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA== + dependencies: + xml-name-validator "^3.0.0" + +walker@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" + integrity sha1-L3+bj9ENZ3JisYqITijRlhjgKPs= + dependencies: + makeerror "1.0.x" watchpack@^2.2.0: version "2.2.0" @@ -7625,6 +9390,16 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" +webidl-conversions@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" + integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== + +webidl-conversions@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" + integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== + webpack-dev-middleware@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-4.1.0.tgz#f0c1f12ff4cd855b3b5eec89ee0f69bcc5336364" @@ -7769,19 +9544,40 @@ websocket-extensions@>=0.1.1: resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== +whatwg-encoding@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" + integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== + dependencies: + iconv-lite "0.4.24" + +whatwg-mimetype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" + integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== + +whatwg-url@^8.0.0, whatwg-url@^8.5.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" + integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg== + dependencies: + lodash "^4.7.0" + tr46 "^2.1.0" + webidl-conversions "^6.1.0" + which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which@^1.2.1, which@^1.2.9: +which@^1.2.9: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== dependencies: isexe "^2.0.0" -which@^2.0.2: +which@^2.0.1, which@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== @@ -7800,6 +9596,11 @@ wildcard@^2.0.0: resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + wrap-ansi@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" @@ -7823,6 +9624,16 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= +write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + ws@^6.2.1: version "6.2.2" resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.2.tgz#dd5cdbd57a9979916097652d78f1cc5faea0c32e" @@ -7830,10 +9641,20 @@ ws@^6.2.1: dependencies: async-limiter "~1.0.0" -ws@~7.4.2: - version "7.4.6" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" - integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== +ws@^7.4.5: + version "7.5.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74" + integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg== + +xml-name-validator@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" + integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== + +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== y18n@^4.0.0: version "4.0.3" @@ -7855,6 +9676,11 @@ yaml@^1.10.0: resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== +yargs-parser@20.x: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + yargs-parser@^13.1.2: version "13.1.2" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" @@ -7884,7 +9710,7 @@ yargs@^13.3.2: y18n "^4.0.0" yargs-parser "^13.1.2" -yargs@^16.1.1, yargs@^16.2.0: +yargs@^16.0.3, yargs@^16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== diff --git a/docker-compose.yml b/docker-compose.yml index 9f7381eb6cad64cdedeb17ac1bcf21ae88cc9016..bc9bd7b06cecef4ee1d3fbb01266056a56649ce1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,7 +15,6 @@ services: build: context: ./server dockerfile: ./Dockerfile.dev - working_dir: /project environment: docker: "true" DISPLAY_ERROR_DETAILS: "true" @@ -38,7 +37,6 @@ services: volumes: - ./server:/project - ./conf-dev/dev-php.ini:/usr/local/etc/php/conf.d/dev-php.ini - - ./conf-dev/vhost.conf:/etc/apache2/sites-available/000-default.conf - ./conf-dev/init-keycloak.sh:/mnt/init-keycloak.sh - ./conf-dev/create-db.sh:/mnt/create-db.sh - ./conf-dev/public_key:/mnt/public_key diff --git a/server/.gitlab-ci.yml b/server/.gitlab-ci.yml index c2a7a334480c8343f53a7657455ddfa852057a36..11df89cf2a14fa312773d1a46deb0614a7433e07 100644 --- a/server/.gitlab-ci.yml +++ b/server/.gitlab-ci.yml @@ -3,80 +3,70 @@ stages: - test - sonar - dockerize - - deploy variables: - VERSION: "3.6" + VERSION: "3.7" SONARQUBE_URL: https://sonarqube.lam.fr install_dependencies: image: jakzal/phpqa:php8.0 stage: install_dependencies script: + - cd server - composer install --ignore-platform-reqs cache: - key: ${CI_COMMIT_REF_SLUG} + key: ${CI_COMMIT_REF_SLUG}_server paths: - - vendor + - server/vendor policy: pull-push - only: - refs: - - develop test: image: jakzal/phpqa:php8.0 stage: test cache: - key: ${CI_COMMIT_REF_SLUG} + key: ${CI_COMMIT_REF_SLUG}_server paths: - - vendor + - server/vendor policy: pull script: - - phpdbg -qrr ./vendor/bin/phpunit --bootstrap ./tests/bootstrap.php --whitelist src --coverage-clover ./coverage/clover.xml --log-junit ./coverage/junit-logfile.xml --coverage-text --colors=never ./tests + - cd server + - phpdbg + -qrr ./vendor/bin/phpunit + --bootstrap ./tests/bootstrap.php + --whitelist src + --coverage-clover ./coverage/clover.xml + --log-junit ./coverage/junit-logfile.xml + --coverage-text --colors=never ./tests artifacts: paths: - - coverage - only: - refs: - - develop + - server/coverage sonar_scanner: image: sonarsource/sonar-scanner-cli:latest stage: sonar script: - - sonar-scanner -Dsonar.projectKey=anis-server -Dsonar.sources=src -Dsonar.projectVersion=$VERSION -Dsonar.host.url=$SONARQUBE_URL -Dsonar.login=$SONAR_TOKEN -Dsonar.php.tests.reportPath=./coverage/junit-logfile.xml -Dsonar.php.coverage.reportPaths=./coverage/clover.xml - only: - refs: - - develop + - cd server + - sonar-scanner + -Dsonar.projectKey=anis-server + -Dsonar.sources=src + -Dsonar.projectVersion=$VERSION + -Dsonar.host.url=$SONARQUBE_URL + -Dsonar.login=$SONAR_TOKEN_SERVER + -Dsonar.php.tests.reportPath=./coverage/junit-logfile.xml + -Dsonar.php.coverage.reportPaths=./coverage/clover.xml dockerize: image: docker:stable stage: dockerize cache: - key: ${CI_COMMIT_REF_SLUG} + key: ${CI_COMMIT_REF_SLUG}_server paths: - - vendor + - server/vendor policy: pull dependencies: [] script: + - cd server - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - - docker pull $CI_REGISTRY/anis/anis-server/server:latest || true - - docker build --cache-from $CI_REGISTRY/anis/anis-server/server:latest -t $CI_REGISTRY/anis/anis-server/server:latest . - - docker push $CI_REGISTRY/anis/anis-server/server:latest - only: - refs: - - develop - -deploy: - image: alpine - stage: deploy - variables: - GIT_STRATEGY: none - cache: {} - dependencies: [] - script: - - apk add --update curl - - curl -XPOST $DEV_WEBHOOK - only: - refs: - - develop \ No newline at end of file + - docker pull $CI_REGISTRY/anis/anis-next/server:latest || true + - docker build --cache-from $CI_REGISTRY/anis/anis-next/server:latest -t $CI_REGISTRY/anis/anis-next/server:latest . + - docker push $CI_REGISTRY/anis/anis-next/server:latest diff --git a/server/Dockerfile b/server/Dockerfile index 143738962362f4a04b748864c8be703ed83f5f7f..8cbc2783b01d5ea9270005daa797fbed8fdd943f 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -10,8 +10,8 @@ RUN printf "\n" | pecl install apcu RUN a2enmod rewrite COPY . /project -COPY ./conf-dev/vhost.conf /etc/apache2/sites-available/000-default.conf -COPY ./conf-dev/php.ini /usr/local/etc/php/conf.d/app.ini +COPY ./vhost.conf /etc/apache2/sites-available/000-default.conf +COPY ./php.ini /usr/local/etc/php/conf.d/app.ini WORKDIR /project diff --git a/server/Dockerfile.dev b/server/Dockerfile.dev index 23eda11af4cf248677c6095fe2e510e0eb96412d..5c346c025f2a6b7bb47e2f0999c0f247405229b5 100644 --- a/server/Dockerfile.dev +++ b/server/Dockerfile.dev @@ -14,4 +14,8 @@ RUN touch /var/log/xdebug_remote.log && chown www-data:www-data /var/log/xdebug_ # Install mod_rewrite RUN a2enmod rewrite +COPY ./vhost.conf /etc/apache2/sites-available/000-default.conf + +WORKDIR /project + CMD ["apache2-foreground"] diff --git a/conf-dev/php.ini b/server/php.ini similarity index 100% rename from conf-dev/php.ini rename to server/php.ini diff --git a/server/src/Search/Query/ConeSearch.php b/server/src/Search/Query/ConeSearch.php index f229f130e1a0d35ecb4a4179a9fc2160507bec29..4d4cee16257e82b9b412a6507e67c444f2aaa1a4 100644 --- a/server/src/Search/Query/ConeSearch.php +++ b/server/src/Search/Query/ConeSearch.php @@ -45,12 +45,12 @@ class ConeSearch extends AbstractQueryPart $radius = floatval($radius); $coneSearchConfig = $dataset->getConfig()['cone_search']; - if ($coneSearchConfig['enabled'] !== true) { + if ($coneSearchConfig['cone_search_enabled'] !== true) { throw SearchQueryException::coneSearchUnavailable(); } - $attributeRa = $this->getAttribute($dataset, $coneSearchConfig['column_ra']); - $attributeDec = $this->getAttribute($dataset, $coneSearchConfig['column_dec']); + $attributeRa = $this->getAttribute($dataset, $coneSearchConfig['cone_search_column_ra']); + $attributeDec = $this->getAttribute($dataset, $coneSearchConfig['cone_search_column_dec']); $columnRa = $dataset->getTableRef() . '.' . $attributeRa->getName(); $columnDec = $dataset->getTableRef() . '.' . $attributeDec->getName(); diff --git a/server/tests/Search/Query/ConeSearchTest.php b/server/tests/Search/Query/ConeSearchTest.php index 057db788a1961d380ed96e14f7052b3c24d8c52c..c0bf21091ffbe35f43c1b995e308cfce80175121 100644 --- a/server/tests/Search/Query/ConeSearchTest.php +++ b/server/tests/Search/Query/ConeSearchTest.php @@ -34,9 +34,9 @@ final class ConeSearchTest extends TestCase $datasetSelected = $this->createMock(Dataset::class); $datasetSelected->method('getAttributes')->willReturn(array($id, $ra, $dec)); $datasetSelected->method('getConfig')->willReturn(array('cone_search' => array( - 'enabled' => true, - 'column_ra' => 2, - 'column_dec' => 3 + 'cone_search_enabled' => true, + 'cone_search_column_ra' => 2, + 'cone_search_column_dec' => 3 ))); $doctrineQueryBuilder = $this->createMock(DoctrineQueryBuilder::class); @@ -64,9 +64,9 @@ final class ConeSearchTest extends TestCase $anisQueryBuilder = $this->createMock(AnisQueryBuilder::class); $datasetSelected = $this->createMock(Dataset::class); $datasetSelected->method('getConfig')->willReturn(array('cone_search' => array( - 'enabled' => false, - 'column_ra' => 2, - 'column_dec' => 3 + 'cone_search_enabled' => false, + 'cone_search_column_ra' => 2, + 'cone_search_column_dec' => 3 ))); $queryParams = array('cs' => '102.5:0.0:100'); (new ConeSearch())($anisQueryBuilder, $datasetSelected, $queryParams); @@ -83,9 +83,9 @@ final class ConeSearchTest extends TestCase $datasetSelected = $this->createMock(Dataset::class); $datasetSelected->method('getAttributes')->willReturn(array($id, $ra, $dec)); $datasetSelected->method('getConfig')->willReturn(array('cone_search' => array( - 'enabled' => true, - 'column_ra' => 2, - 'column_dec' => 3 + 'cone_search_enabled' => true, + 'cone_search_column_ra' => 2, + 'cone_search_column_dec' => 3 ))); $doctrineQueryBuilder = $this->createMock(DoctrineQueryBuilder::class); diff --git a/conf-dev/vhost.conf b/server/vhost.conf similarity index 100% rename from conf-dev/vhost.conf rename to server/vhost.conf diff --git a/services/.gitlab-ci.yml b/services/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..38f978456fbe7706e1ad8a1e9a650756a6bc4f05 --- /dev/null +++ b/services/.gitlab-ci.yml @@ -0,0 +1,17 @@ +stages: + - dockerize + +variables: + VERSION: "3.7" + SONARQUBE_URL: https://sonarqube.lam.fr + +dockerize: + image: docker:stable + stage: dockerize + cache: {} + script: + - cd services + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + - docker pull $CI_REGISTRY/anis/anis-next/services:latest || true + - docker build --cache-from $CI_REGISTRY/anis/anis-next/services:latest -t $CI_REGISTRY/anis/anis-next/services:latest . + - docker push $CI_REGISTRY/anis/anis-next/services:latest